Project

General

Profile

Download (40.1 KB) Statistics
| Branch: | Tag: | Revision:

root / extra / modules / root.ptl @ 9d071a47

1
from quixote import get_publisher, get_response, get_request, redirect, get_session
2
from quixote.directory import Directory
3
from quixote.html import htmltext
4
from quixote.util import StaticDirectory
5

    
6
import os
7
import re
8
import string
9
import urlparse
10

    
11
try:
12
    import lasso
13
except ImportError:
14
    pass
15

    
16
import wcs
17
import wcs.root
18
import qommon
19
from qommon import get_cfg, get_logger
20
from qommon import template
21
from qommon import errors
22
from qommon.form import *
23
from qommon import logger
24
from wcs.roles import logged_users_role
25

    
26
from qommon import emails
27
from qommon.sms import SMS
28
from wcs.categories import Category
29
from wcs.formdef import FormDef, status_labels
30
from qommon.tokens import Token
31
from qommon.admin.emails import EmailsDirectory
32
from qommon.admin.texts import TextsDirectory
33

    
34
from links import Link
35
from announces import Announce, AnnounceSubscription
36
from myspace import MyspaceDirectory
37
from agenda import AgendaDirectory
38
from events import Event, get_default_event_tags
39

    
40
import admin
41

    
42
import wcs.forms.root
43
from wcs.workflows import Workflow
44

    
45
from saml2 import Saml2Directory
46

    
47
OldRootDirectory = wcs.root.RootDirectory
48

    
49
import qommon.ident.password
50
import qommon.ident.idp
51

    
52
import drupal
53

    
54
def category_get_homepage_position(self):
55
    if hasattr(self, 'homepage_position') and self.homepage_position:
56
        return self.homepage_position
57
    if self.url_name == 'consultations':
58
        return '2nd'
59
    return '1st'
60
Category.get_homepage_position = category_get_homepage_position
61

    
62
def category_get_limit(self):
63
    if hasattr(self, 'limit') and self.limit is not None:
64
        return self.limit
65
    return 3
66
Category.get_limit = category_get_limit
67

    
68

    
69
class FormsRootDirectory(wcs.forms.root.RootDirectory):
70

    
71
    def user_forms [html] (self, user_forms):
72
        base_url = get_publisher().get_root_url()
73

    
74
        draft = [x for x in user_forms if x.status == 'draft']
75
        if draft:
76
            '<h3 id="drafts">%s</h3>' % _('Your Current Drafts')
77
            '<ul>'
78
            for f in draft:
79
                '<li><a href="%s%s/%s/%s">%s</a></li>' % (base_url, 
80
                    f.formdef.category.url_name,
81
                    f.formdef.url_name, f.id, f.formdef.name)
82
            '</ul>'
83

    
84

    
85
        # (COMPAT) without workflows
86
        current = [x for x in user_forms if x.status in ('new', 'accepted')]
87
        if current:
88
            '<h3 id="submitted">%s</h3>' % _('Your Current Forms')
89
            '<ul>'
90
            for f in current:
91
                if f.formdef is None:
92
                    continue
93
                if f.formdef.category_id:
94
                    category_url = f.formdef.category.url_name
95
                else:
96
                    category_url = '.'
97
                '<li><a href="%s%s/%s/%s/">%s</a>, %s, %s: %s</li>' % (
98
                    base_url,
99
                    category_url,
100
                    f.formdef.url_name, f.id, f.formdef.name, 
101
                    misc.localstrftime(f.receipt_time),
102
                    _('status'),
103
                    _(status_labels[f.status]) )
104
            '</ul>'
105
        done = [x for x in user_forms if x.status in ('rejected', 'finished')]
106
        if done:
107
            '<h3 id="done">%s</h3>' % _('Your Old Forms')
108
            '<ul>'
109
            for f in done:
110
                if not f.formdef:
111
                    continue
112
                if f.formdef.category_id:
113
                    category_url = f.formdef.category.url_name
114
                else:
115
                    category_url = '.'
116
                '<li><a href="%s%s/%s/%s/">%s</a>, %s, %s: %s</li>' % (
117
                    base_url,
118
                    category_url,
119
                    f.formdef.url_name, f.id, f.formdef.name, 
120
                    misc.localstrftime(f.receipt_time),
121
                    _('status'),
122
                    _(status_labels[f.status]) )
123
            '</ul>'
124

    
125
        # with workflows
126
        workflows = Workflow.select(order_by = 'name')
127
        for workflow in workflows:
128
            # XXX: seperate endpoints from non-endpoints
129
            for status in workflow.possible_status:
130
                fms = [x for x in user_forms if x.status == 'wf-%s' % status.id and \
131
                        x.formdef and x.formdef.workflow_id == workflow.id]
132
                if not fms:
133
                    continue
134
                '<h3>%s</h3>' % _('Your forms with status "%s"') % status.name
135
                '<ul>'
136
                for f in fms:
137
                    if f.formdef.category_id:
138
                        category_url = f.formdef.category.url_name
139
                    else:
140
                        category_url = '.'
141
                    '<li><a href="%s%s/%s/%s/">%s</a>, %s</li>' % (
142
                            base_url,
143
                            category_url,
144
                            f.formdef.url_name, f.id, f.formdef.name, 
145
                            misc.localstrftime(f.receipt_time))
146
                '</ul>'
147

    
148

    
149
class AnnounceDirectory(Directory):
150
    _q_exports = ['', 'edit', 'delete', 'email']
151

    
152
    def __init__(self, announce):
153
        self.announce = announce
154

    
155
    def _q_index [html] (self):
156
        template.html_top(_('Announces to citizens'))
157

    
158
        if self.announce.publication_time:
159
            date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time)
160
        else:
161
            date_heading = ''
162

    
163
        '<h3>%s%s</h3>' % (date_heading, self.announce.title)
164

    
165
        '<p>'
166
        self.announce.text
167
        '</p>'
168

    
169
        '<p>'
170
        '<a href="../">%s</a>' % _('Back')
171
        '</p>'
172

    
173

    
174
class AnnouncesDirectory(Directory):
175
    _q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm',
176
            'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist']
177

    
178
    
179
    def _q_traverse(self, path):
180
        get_response().breadcrumb.append(('announces/', _('Announces')))
181
        return Directory._q_traverse(self, path)
182

    
183
    def _q_index [html] (self):
184
        template.html_top(_('Announces to citizens'))
185
        self.announces_list()
186
        '<ul id="announces-links">'
187
        '<li><a href="subscribe">%s</a></li>' % _('Receiving those Announces')
188
        '</ul>'
189

    
190
    def _get_announce_subscription(self):
191
        """ """
192
        sub = None
193
        if get_request().user:
194
            subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id)
195
            if subs:
196
                sub = subs[0]
197
        return sub
198

    
199
    def rawlist [html] (self):
200
        self.announces_list()
201
        get_response().filter = None
202

    
203
    def announces_list [html] (self):
204
        announces = Announce.get_published_announces()
205
        if not announces:
206
            raise errors.TraversalError()
207

    
208
        # XXX: will need pagination someday
209
        for item in announces:
210
            '<div class="announce-item">\n'
211
            '<h4>'
212
            if item.publication_time:
213
                time.strftime(misc.date_format(), item.publication_time)
214
                ' - '
215
            item.title
216
            '</h4>\n'
217
            '<p>\n'
218
            item.text
219
            '\n</p>\n'
220
            '</div>\n'
221

    
222

    
223
    def sms [html] (self):
224
        sms_mode = get_cfg('sms', {}).get('mode', 'none')
225

    
226
        if sms_mode == 'none':
227
            raise errors.TraversalError()
228

    
229
        get_response().breadcrumb.append(('sms', _('SMS')))
230
        template.html_top(_('Receiving announces by SMS'))
231

    
232
        if sms_mode == 'demo':
233
            TextsDirectory.get_html_text('aq-sms-demo')
234
        elif sms_mode == 'mobyt':
235
            announces_cfg = get_cfg('announces',{})
236
            mobile_mask = announces_cfg.get('mobile_mask')
237
            if mobile_mask:
238
                mobile_mask = ' (' + mobile_mask + ')'
239
            else:
240
                mobile_mask = ''
241
            form = Form(enctype='multipart/form-data')
242
            form.add(StringWidget, 'mobile', title = _('Mobile number %s') % mobile_mask, size=12, required=True)
243
            form.add_submit('submit', _('Subscribe'))
244
            form.add_submit('cancel', _('Cancel'))
245

    
246
            if form.get_submit() == 'cancel':
247
                return redirect('subscribe')
248

    
249
            if form.is_submitted() and not form.has_errors():
250
                s = self.sms_submit(form)
251
                if s == False:
252
                    form.render()
253
                else:
254
                    return redirect("smsconfirm")
255
            else:
256
                form.render()
257

    
258
    def sms_submit(self, form):
259
        mobile = form.get_widget("mobile").parse()
260
        # clean the string, remove any extra character
261
        mobile = re.sub('[^0-9+]','',mobile)
262
        # if a mask was set, validate
263
        announces_cfg = get_cfg('announces',{})
264
        mobile_mask = announces_cfg.get('mobile_mask')
265
        if mobile_mask:
266
            mobile_regexp = re.sub('X','[0-9]', mobile_mask) + '$'
267
            if not re.match(mobile_regexp, mobile):
268
                form.set_error("mobile", _("Phone number invalid ! It must match ") + mobile_mask)
269
                return False
270
        if mobile.startswith('00'):
271
            mobile = '+' + mobile[2:]
272
        else:
273
            # Default to france international prefix
274
            if not mobile.startswith('+'):
275
                mobile = re.sub("^0", "+33", mobile)
276
        sub = self._get_announce_subscription()
277
        if not sub:
278
            sub = AnnounceSubscription()
279
        if get_request().user:
280
            sub.user_id = get_request().user.id
281

    
282
        if mobile:
283
            sub.sms = mobile
284

    
285
        if not get_request().user:
286
            sub.enabled = False
287

    
288
        sub.store()
289

    
290
        # Asking sms confirmation
291
        token = Token(3 * 86400, 4, string.digits)
292
        token.type = 'announces-subscription-confirmation'
293
        token.subscription_id = sub.id
294
        token.store()
295

    
296
        message = _("Confirmation code : %s" % token.id)
297
        sms_cfg = get_cfg('sms', {})
298
        sms = SMS()
299
        try:
300
            sms.send([mobile], message, sms_cfg.get('sender', 'auquotidien'))
301
        except errors.SMSError, e:
302
            get_logger().error(e)
303
            form.set_error("mobile", _("Send SMS confirmation failed"))
304
            sub.remove("sms")
305
            return False
306

    
307
    def smsconfirm [html] (self):
308
        template.html_top(_('Receiving announces by SMS confirmation'))
309
        "<p>%s</p>" % _("You will receive a confirmation code by SMS.")
310
        form = Form(enctype='multipart/form-data')
311
        form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True)
312
        form.add_submit('submit', _('Subscribe'))
313
        form.add_submit('cancel', _('Cancel'))
314

    
315
        if form.is_submitted() and not form.has_errors():
316
            token = None
317
            id = form.get_widget("code").parse()
318
            try:
319
                token = Token.get(id)
320
            except KeyError:
321
                form.set_error("code",  _('Invalid confirmation code.'))
322
            else:
323
                if token.type != 'announces-subscription-confirmation':
324
                    form.set_error("code",  _('Invalid confirmation code.'))
325
                else:
326
                    sub = AnnounceSubscription.get(token.subscription_id)
327
                    token.remove_self()
328
                    sub.enabled_sms = True
329
                    sub.store()
330
                    return redirect('.')
331
            form.render()
332
        else:
333
            form.render()
334

    
335
    def sms_unsubscribe [html] (self):
336
        sub = self._get_announce_subscription()
337

    
338
        form = Form(enctype='multipart/form-data')
339
        if not sub:
340
            return redirect('..')
341

    
342
        form.add_submit('submit', _('Unsubscribe'))
343
        form.add_submit('cancel', _('Cancel'))
344

    
345
        if form.get_submit() == 'cancel':
346
            return redirect('..')
347

    
348
        get_response().breadcrumb.append(('sms', _('SMS Unsubscription')))
349
        template.html_top()
350

    
351
        if form.is_submitted() and not form.has_errors():
352
            if sub:
353
                sub.remove("sms")
354

    
355
            def sms_unsub_ok [html] ():
356
                root_url = get_publisher().get_root_url()
357
                '<p>'
358
                _('You have been unsubscribed from announces')
359
                '</p>'
360
                if not get_response().iframe_mode:
361
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
362

    
363
            return sms_unsub_ok()
364

    
365
        else:
366
            '<p>'
367
            _('Do you want to stop receiving announces by sms ?')
368
            '</p>'
369
            form.render()
370

    
371

    
372
    def subscribe [html] (self):
373
        get_response().breadcrumb.append(('subscribe', _('Subscription')))
374
        template.html_top(_('Receiving Announces'))
375

    
376
        TextsDirectory.get_html_text('aq-announces-subscription')
377

    
378
        sub = self._get_announce_subscription()
379

    
380
        '<ul id="announce-modes">'
381
        if sub and sub.email:
382
            ' <li>'
383
            '<span id="par_mail">%s</span>' % _('Email (currently subscribed)')
384
            ' <a href="email_unsubscribe" rel="popup">%s</a></li>' % _('Unsubscribe')
385
        else:
386
            ' <li><a href="email" id="par_mail" rel="popup">%s</a></li>' % _('Email')
387
        if sub and sub.sms:
388
            ' <li>'
389
            if sub.enabled_sms:
390
                '<span id="par_sms">%s</span>' % _('SMS %s (currently subscribed)') % sub.sms
391
            else:
392
                '<span id="par_sms">%s</span>' % _('SMS %s (currently not confirmed)') % sub.sms
393
                ' <a href="smsconfirm" rel="popup">%s</a> ' % _('Confirmation')
394
            ' <a href="sms_unsubscribe" rel="popup">%s</a></li>' % _('Unsubscribe')
395
        elif get_cfg('sms', {}).get('mode', 'none') != 'none':
396
            ' <li><a href="sms" id="par_sms">%s</a></li>' % _('SMS')
397
        ' <li><a class="feed-link" href="atom" id="par_rss">%s</a>' % _('Feed')
398
        '</ul>'
399

    
400

    
401
    def email [html] (self):
402
        get_response().breadcrumb.append(('email', _('Email Subscription')))
403
        template.html_top(_('Receiving Announces by email'))
404

    
405
        form = Form(enctype='multipart/form-data')
406
        if get_request().user:
407
            if get_request().user.email:
408
                '<p>'
409
                _('You are logged in and your email is %s, ok to subscribe ?') % \
410
                    get_request().user.email
411
                '</p>'
412
                form.add_submit('submit', _('Subscribe'))
413
            else:
414
                '<p>'
415
                _("You are logged in but there is no email address in your profile.")
416
                '</p>'
417
                form.add(EmailWidget, 'email', title = _('Email'), required = True)
418
                form.add_submit('submit', _('Subscribe'))
419
                form.add_submit('submit-remember', _('Subscribe and add this email to my profile'))
420
        else:
421
            '<p>'
422
            _('FIXME will only be used for this purpose etc.')
423
            '</p>'
424
            form.add(EmailWidget, 'email', title = _('Email'), required = True)
425
            form.add_submit('submit', _('Subscribe'))
426
        
427
        form.add_submit('cancel', _('Cancel'))
428

    
429
        if form.get_submit() == 'cancel':
430
            return redirect('subscribe')
431

    
432
        if form.is_submitted() and not form.has_errors():
433
            s = self.email_submit(form)
434
            if s is not False:
435
                return s
436
        else:
437
            form.render()
438

    
439
    def email_submit(self, form):
440
        sub = self._get_announce_subscription()
441
        if not sub:
442
            sub = AnnounceSubscription()
443

    
444
        if get_request().user:
445
            sub.user_id = get_request().user.id
446

    
447
        if form.get_widget('email'):
448
            sub.email = form.get_widget('email').parse()
449
        elif get_request().user.email:
450
            sub.email = get_request().user.email
451

    
452
        if not get_request().user:
453
            sub.enabled = False
454

    
455
        sub.store()
456

    
457
        if get_request().user:
458
            def email_submit_ok [html] ():
459
                root_url = get_publisher().get_root_url()
460
                '<p>'
461
                _('You have been subscribed to the announces.')
462
                '</p>'
463
                if not get_response().iframe_mode:
464
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
465

    
466
            return email_submit_ok()
467

    
468
        # asking email confirmation before subscribing someone
469
        token = Token(3 * 86400)
470
        token.type = 'announces-subscription-confirmation'
471
        token.subscription_id = sub.id
472
        token.store()
473
        data = {
474
            'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id,
475
            'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id,
476
            'time': misc.localstrftime(time.localtime(token.expiration)),
477
        }
478

    
479
        emails.custom_ezt_email('announces-subscription-confirmation',
480
                data, sub.email, exclude_current_user = False)
481

    
482
        def email_submit_ok [html] ():
483
            root_url = get_publisher().get_root_url()
484
            '<p>'
485
            _('You have been sent an email for confirmation')
486
            '</p>'
487
            if not get_response().iframe_mode:
488
                '<a href="%s">%s</a>' % (root_url, _('Back Home'))
489

    
490
        return email_submit_ok()
491

    
492
    def emailconfirm(self):
493
        tokenv = get_request().form.get('t')
494
        action = get_request().form.get('a')
495

    
496
        root_url = get_publisher().get_root_url()
497

    
498
        try:
499
            token = Token.get(tokenv)
500
        except KeyError:
501
            return template.error_page(
502
                    _('The token you submitted does not exist, has expired, or has been cancelled.'),
503
                    continue_to = (root_url, _('home page')))
504

    
505
        if token.type != 'announces-subscription-confirmation':
506
            return template.error_page(
507
                    _('The token you submitted is not appropriate for the requested task.'),
508
                    continue_to = (root_url, _('home page')))
509

    
510
        sub = AnnounceSubscription.get(token.subscription_id)
511

    
512
        if action == 'cxl':
513
            def cancel [html]():
514
                root_url = get_publisher().get_root_url()
515
                template.html_top(_('Email Subscription'))
516
                '<h1>%s</h1>' % _('Request Cancelled')
517
                '<p>%s</p>' % _('The request for subscription has been cancelled.')
518
                '<p>'
519
                htmltext(_('Continue to <a href="%s">home page</a>') % root_url)
520
                '</p>'
521
            token.remove_self()
522
            sub.remove_self()
523
            return cancel()
524

    
525
        if action == 'cfm':
526
            token.remove_self()
527
            sub.enabled = True
528
            sub.store()
529
            def sub [html] ():
530
                root_url = get_publisher().get_root_url()
531
                template.html_top(_('Email Subscription'))
532
                '<h1>%s</h1>' % _('Subscription Confirmation')
533
                '<p>%s</p>' % _('Your subscription to announces is now effective.')
534
                '<p>'
535
                htmltext(_('Continue to <a href="%s">home page</a>') % root_url)
536
                '</p>'
537
            return sub()
538

    
539

    
540
    def atom [plain] (self):
541
        response = get_response()
542
        response.set_content_type('application/atom+xml')
543

    
544
        from pyatom import pyatom
545
        xmldoc = pyatom.XMLDoc()
546
        feed = pyatom.Feed()
547
        xmldoc.root_element = feed
548
        feed.title = get_cfg('misc', {}).get('sitename', 'Au Quotidien')
549
        feed.id = get_request().get_url()
550

    
551
        author_email = get_cfg('emails', {}).get('reply_to')
552
        if not author_email:
553
            author_email = get_cfg('emails', {}).get('from')
554
        if author_email:
555
            feed.authors.append(pyatom.Author(author_email))
556

    
557
        announces = Announce.get_published_announces()
558

    
559
        if announces and announces[0].modification_time:
560
            feed.updated = misc.format_time(announces[0].modification_time,
561
                        '%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ',
562
                        gmtime = True)
563
        feed.links.append(pyatom.Link(get_request().get_url(1) + '/'))
564

    
565
        for item in announces:
566
            entry = item.get_atom_entry()
567
            if entry:
568
                feed.entries.append(entry)
569

    
570
        str(feed)
571

    
572
    def email_unsubscribe [html] (self):
573
        sub = self._get_announce_subscription()
574

    
575
        form = Form(enctype='multipart/form-data')
576
        if not sub:
577
            form.add(EmailWidget, 'email', title = _('Email'), required = True)
578

    
579
        form.add_submit('submit', _('Unsubscribe'))
580
        form.add_submit('cancel', _('Cancel'))
581

    
582
        if form.get_submit() == 'cancel':
583
            return redirect('..')
584

    
585
        get_response().breadcrumb.append(('email', _('Email Unsubscription')))
586
        template.html_top()
587

    
588
        if form.is_submitted() and not form.has_errors():
589
            if sub:
590
                sub.remove("email")
591
            else:
592
                email = form.get_widget('email').parse()
593
                for s in AnnounceSubscription.select():
594
                    if s.email == email:
595
                        s.remove("email")
596

    
597
            def email_unsub_ok [html] ():
598
                root_url = get_publisher().get_root_url()
599
                '<p>'
600
                _('You have been unsubscribed from announces')
601
                '</p>'
602
                if not get_response().iframe_mode:
603
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
604

    
605
            return email_unsub_ok()
606

    
607
        else:
608
            '<p>'
609
            _('Do you want to stop receiving announces by email?')
610
            '</p>'
611
            form.render()
612

    
613
    def _q_lookup(self, component):
614
        try:
615
            announce = Announce.get(component)
616
        except KeyError:
617
            raise errors.TraversalError()
618

    
619
        if announce.hidden:
620
            raise errors.TraversalError()
621

    
622
        get_response().breadcrumb.append((str(announce.id), announce.title))
623
        return AnnounceDirectory(announce)
624

    
625
OldRegisterDirectory = wcs.root.RegisterDirectory
626

    
627
class AlternateRegisterDirectory(OldRegisterDirectory):
628
    def _q_traverse(self, path):
629
        get_response().filter['bigdiv'] = 'new_member'
630
        return OldRegisterDirectory._q_traverse(self, path)
631

    
632
    def _q_index [html] (self):
633
        get_logger().info('register')
634
        ident_methods = get_cfg('identification', {}).get('methods', [])
635

    
636
        if len(ident_methods) == 0:
637
            idps = get_cfg('idp', {})
638
            if len(idps) == 0:
639
                return template.error_page(_('Authentication subsystem is not yet configured.'))
640
            ident_methods = ['idp'] # fallback to old behaviour; liberty.
641

    
642
        if len(ident_methods) == 1:
643
            method = ident_methods[0]
644
        else:
645
            method = 'password'
646
            return qommon.ident.register(method)
647

    
648
        if method == 'idp':
649
            root_url = get_publisher().get_root_url()
650
            return redirect('%slogin' % root_url)
651

    
652
        return OldRegisterDirectory._q_index(self)
653

    
654
OldLoginDirectory = wcs.root.LoginDirectory
655

    
656
class AlternateLoginDirectory(OldLoginDirectory):
657
    def _q_traverse(self, path):
658
        get_response().filter['bigdiv'] = 'member'
659
        return OldLoginDirectory._q_traverse(self, path)
660

    
661
    def _q_index [html] (self):
662
        get_logger().info('login')
663
        ident_methods = get_cfg('identification', {}).get('methods', [])
664

    
665
        if len(ident_methods) > 1 and 'idp' in ident_methods:
666
            # if there is more than one identification method, and there is a
667
            # possibility of SSO, if we got there as a consequence of an access
668
            # unauthorized url on admin/ or backoffice/, then idp auth method
669
            # is chosen forcefully.
670
            after_url = get_session().after_url
671
            if after_url:
672
                root_url = get_publisher().get_root_url()
673
                after_path = urlparse.urlparse(after_url)[2]
674
                after_path = after_path[len(root_url):]
675
                if after_path.startswith(str('admin')) or \
676
                        after_path.startswith(str('backoffice')):
677
                    ident_methods = ['idp']
678

    
679
        # don't display authentication system choice
680
        if len(ident_methods) == 1:
681
            method = ident_methods[0]
682
            try:
683
                return qommon.ident.login(method)
684
            except KeyError:
685
                get_logger().error('failed to login with method %s' % method)
686
                return errors.TraversalError()
687

    
688
        if sorted(ident_methods) == ['idp', 'password']:
689
            get_response().breadcrumb.append(('login', _('Login')))
690
            identities_cfg = get_cfg('identities', {})
691
            form = Form(enctype = 'multipart/form-data', id = 'login-form', use_tokens = False)
692
            if identities_cfg.get('email-as-username', False):
693
                form.add(StringWidget, 'username', title = _('Email'), size=25, required=True)
694
            else:
695
                form.add(StringWidget, 'username', title = _('Username'), size=25, required=True)
696
            form.add(PasswordWidget, 'password', title = _('Password'), size=25, required=True)
697
            form.add_submit('submit', _('Connect'))
698
            if form.is_submitted() and not form.has_errors():
699
                tmp = qommon.ident.password.MethodDirectory().login_submit(form)
700
                if not form.has_errors():
701
                    return tmp
702

    
703
            '<div id="login-password">'
704
            get_session().display_message()
705
            form.render()
706

    
707
            base_url = get_publisher().get_root_url()
708
            '<p><a href="%sident/password/forgotten">%s</a></p>' % (
709
                    base_url, _('Forgotten password ?'))
710

    
711
            '</div>'
712

    
713
            # XXX: this part only supports a single IdP
714
            '<div id="login-sso">'
715
            TextsDirectory.get_html_text('aq-sso-text')
716
            form = Form(enctype='multipart/form-data',
717
                    action = '%sident/idp/login' % base_url)
718
            form.add_hidden('method', 'idp')
719
            for kidp, idp in get_cfg('idp', {}).items():
720
                p = lasso.Provider(lasso.PROVIDER_ROLE_IDP,
721
                        misc.get_abs_path(idp['metadata']),
722
                        misc.get_abs_path(idp.get('publickey')), None)
723
                form.add_hidden('idp', p.providerId)
724
                break
725
            form.add_submit('submit', _('Connect'))
726
            
727
            form.render()
728
            '</div>'
729

    
730
            get_request().environ['REQUEST_METHOD'] = 'GET'
731

    
732
            """<script type="text/javascript">
733
              document.getElementById('login-form')['username'].focus();
734
            </script>"""
735

    
736
        else:
737
            return OldLoginDirectory._q_index(self)
738

    
739

    
740
OldIdentDirectory = wcs.root.IdentDirectory
741
class AlternateIdentDirectory(OldIdentDirectory):
742
    def _q_traverse(self, path):
743
        get_response().filter['bigdiv'] = 'member'
744
        return OldIdentDirectory._q_traverse(self, path)
745

    
746

    
747
class AlternateRootDirectory(OldRootDirectory):
748
    _q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout',
749
            'liberty', 'token', 'saml', 'register', 'ident', 'afterjobs',
750
            ('informations-editeur', 'informations_editeur'), 'index2',
751
            ('announces', 'announces_dir'),
752
            'accessibility', 'contact', 'help',
753
            'myspace', 'services', 'agenda',
754
            'themes']
755

    
756
    admin = admin.AdminRootDirectory()
757
    announces_dir = AnnouncesDirectory()
758
    register = AlternateRegisterDirectory()
759
    login = AlternateLoginDirectory()
760
    ident = AlternateIdentDirectory()
761
    myspace = MyspaceDirectory()
762
    agenda = AgendaDirectory()
763
    saml = Saml2Directory()
764

    
765
    def _q_traverse(self, path):
766
        drupal.try_auth()
767

    
768
        session = get_session()
769
        if session:
770
            get_request().user = session.get_user()
771
        else:
772
            get_request().user = None
773

    
774
        response = get_response()
775
        if not hasattr(response, 'filter'):
776
            response.filter = {}
777
        response.filter['gauche'] = self.box_side(path)
778
        response.filter['keywords'] = template.get_current_theme().get('keywords')
779
        response.breadcrumb = [ ('', _('Home')) ]
780

    
781
        if not self.admin:
782
            self.admin = get_publisher().admin_directory_class()
783

    
784
        if not self.backoffice:
785
            self.backoffice = get_publisher().backoffice_directory_class()
786

    
787
        try:
788
            return Directory._q_traverse(self, path)
789
        except errors.TraversalError, e:
790
            try:
791
                f = FormDef.get_by_urlname(path[0])
792
            except KeyError:
793
                pass
794
            else:
795
                base_url = get_publisher().get_root_url()
796
                uri_rest = get_request().environ.get('REQUEST_URI')
797
                if uri_rest.startswith(base_url):
798
                    uri_rest = uri_rest[len(base_url):]
799
                if f.category_id:
800
                    return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest))
801

    
802
            raise e
803

    
804

    
805
    def _q_lookup(self, component):
806
        if component == 'qo':
807
            dirname = os.path.join(get_publisher().data_dir, 'qommon')
808
            return StaticDirectory(dirname, follow_symlinks = True)
809

    
810
        if component in ('css','images'):
811
            return OldRootDirectory._q_lookup(self, component)
812

    
813
        # is this a category ?
814
        try:
815
            category = Category.get_by_urlname(component)
816
        except KeyError:
817
            pass
818
        else:
819
            return FormsRootDirectory(category)
820

    
821
        # is this a formdef ?
822
        try:
823
            formdef = FormDef.get_by_urlname(component)
824
        except KeyError:
825
            pass
826
        else:
827
            if formdef.category_id is None:
828
                return FormsRootDirectory()._q_lookup(component)
829
            # if there is category, let it fall back to raise TraversalError,
830
            # it will get caught in _q_traverse that will redirect it to an
831
            # URL embedding the category
832

    
833
        return None
834

    
835

    
836
    def _q_index [html] (self):
837
        root_url = get_publisher().get_root_url()
838
        if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump:
839
            return redirect('%smyspace/new' % root_url)
840

    
841
        if get_response().iframe_mode:
842
            # never display home page in an iframe
843
            return redirect('%sservices' % root_url)
844

    
845
        template.html_top()
846

    
847
        t = TextsDirectory.get_html_text('aq-home-page')
848
        if t:
849
            '<div id="home-page-intro">'
850
            t
851
            '</div>'
852

    
853

    
854
        '<div id="centre">'
855
        self.box_services(position='1st')
856
        '</div>'
857
        '<div id="droite">'
858
        self.myspace_snippet()
859
        self.box_services(position='2nd')
860
        self.consultations()
861
        self.announces()
862
        '</div>'
863

    
864
    def services [html] (self):
865
        template.html_top()
866
        get_response().filter['bigdiv'] = 'rub_service'
867
        self.box_services(level = 2)
868

    
869
    def box_services [html] (self, level=3, position=None):
870
        ## Services
871
        if get_request().user and get_request().user.roles:
872
            accepted_roles = get_request().user.roles
873
        else:
874
            accepted_roles = []
875

    
876
        cats = Category.select(order_by = 'name')
877
        cats = [x for x in cats if x.url_name != 'consultations']
878
        Category.sort_by_position(cats)
879

    
880
        all_formdefs = FormDef.select(lambda x: not x.disabled, order_by = 'name')
881

    
882
        if position:
883
            t = self.display_list_of_formdefs(
884
                            [x for x in cats if x.get_homepage_position() == position],
885
                            all_formdefs, accepted_roles)
886
        else:
887
            t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles)
888

    
889
        if not t:
890
            return
891

    
892
        if position == '2nd':
893
            '<div id="services-2nd">'
894
        else:
895
            '<div id="services">'
896
        if level == 2:
897
            '<h2>%s</h2>' % _('Services')
898
        else:
899
            '<h3>%s</h3>' % _('Services')
900

    
901
        '<ul>'
902
        t
903
        '</ul>'
904

    
905
        '</div>'
906

    
907
    def display_list_of_formdefs [html] (self, cats, all_formdefs, accepted_roles):
908
        for category in cats:
909
            if category.url_name == 'consultations':
910
                self.consultations_category = category
911
                continue
912
            formdefs = [x for x in all_formdefs if x.category_id == category.id]
913

    
914
            for formdef in formdefs[:]:
915
                if not formdef.roles:
916
                    continue
917
                if not get_request().user:
918
                    formdefs.remove(formdef)
919
                    continue
920
                if logged_users_role().id in formdef.roles:
921
                    continue
922
                for q in accepted_roles:
923
                    if q in formdef.roles:
924
                        break
925
                else:
926
                    formdefs.remove(formdef)
927

    
928
            if not formdefs:
929
                continue
930

    
931
            '<li>'
932
            '<strong>'
933
            '<a href="%s/">' % category.url_name
934
            category.name
935
            '</a></strong>\n'
936
            if category.description:
937
                '<p>'
938
                category.description
939
                '</p>'
940
            '<ul>'
941
            limit = category.get_limit()
942
            for formdef in formdefs[:limit]:
943
                '<li>'
944
                '<a href="%s/%s/">%s</a>' % (category.url_name, formdef.url_name, formdef.name)
945
                '</li>\n'
946
            if len(formdefs) > limit:
947
                '<li class="all-forms"><a href="%s/" title="%s">%s</a></li>' % (category.url_name,
948
                        _('Access to all forms of the "%s" category') % category.name,
949
                        _('Access to all forms in this category'))
950
            '</ul>'
951
            '</li>\n'
952

    
953
    def consultations [html] (self):
954
        cats = [x for x in Category.select() if x.url_name == 'consultations']
955
        if not cats:
956
            return
957
        consultations_category = cats[0]
958
        formdefs = FormDef.select(lambda x: (
959
                    x.category_id == consultations_category.id and not x.disabled),
960
                    order_by = 'name')
961
        if not formdefs:
962
            return
963
        ## Consultations
964
        '<div id="consultations">'
965
        '<h3>%s</h3>' % _('Consultations')
966
        if consultations_category.description:
967
            '<p>'
968
            consultations_category.description
969
            '</p>'
970
        '<ul>'
971
        for formdef in formdefs:
972
            '<li>'
973
            '<a href="%s/%s/">%s</a>' % (
974
                    consultations_category.url_name, formdef.url_name, formdef.name)
975
            '</li>'
976
        '</ul>'
977
        '</div>'
978

    
979
    def box_side [html] (self, path):
980
        '<div id="sidebox">'
981
        self.links()
982

    
983
        cats = Category.select(order_by = 'name')
984
        cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side']
985
        Category.sort_by_position(cats)
986
        if cats:
987
            '<div id="side-services">'
988
            '<h3>%s</h3>' % _('Services')
989
            '<ul>'
990
            for cat in cats:
991
                '<li><a href="%s/">%s</a></li>' % (cat.url_name, cat.name)
992
            '</ul>'
993
            '</div>'
994

    
995
        all_formdefs = FormDef.select(lambda x: not x.disabled, order_by = 'name')
996
        if Event.keys(): # if there are events, add a link to the agenda
997
            tags = get_cfg('misc', {}).get('event_tags')
998
            if not tags:
999
                tags = get_default_event_tags()
1000
            root_url = get_publisher().get_root_url()
1001
            '<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>' % (root_url, _('Agenda'))
1002

    
1003
            if path and path[0] == 'agenda':
1004
                '<p class="tags">'
1005
                for tag in tags:
1006
                    '<a href="%sagenda/tag/%s">%s</a> ' % (root_url, tag, tag)
1007
                '</p>'
1008
                self.agenda.display_remote_calendars()
1009

    
1010
                '<p>'
1011
                '  <a href="%sagenda/filter">%s</a>' % (root_url, _('Advanced Filter'))
1012
                '</p>'
1013

    
1014
        '</div>'
1015

    
1016

    
1017
    def links [html] (self):
1018
        links = Link.select()
1019
        if not links:
1020
            return
1021

    
1022
        Link.sort_by_position(links)
1023

    
1024
        '<div id="links">'
1025
        if links[0].url:
1026
            # first link has an URL, so it's not a title, so we display a
1027
            # generic title
1028
            '<h3>%s</h3>' % _('Useful links')
1029
        has_ul = False
1030
        for link in links:
1031
            if not link.url:
1032
                # acting title
1033
                if has_ul:
1034
                    '</ul>'
1035
                '<h3>%s</h3>' % link.title
1036
                '<ul>'
1037
                has_ul = True
1038
            else:
1039
                if not has_ul:
1040
                    '<ul>'
1041
                    has_ul = True
1042
                '<li><a href="%s">%s</a></li>' % (link.url, link.title)
1043
        if has_ul:
1044
            '</ul>'
1045
        '</div>'
1046

    
1047

    
1048
    def announces [html] (self):
1049
        announces = Announce.get_published_announces()
1050
        if not announces:
1051
            return
1052

    
1053
        '<div id="announces">'
1054
        '<h3>%s</h3>' % _('Announces to citizens')
1055
        for item in announces[:3]:
1056
            '<div class="announce-item">'
1057
            '<h4>'
1058
            if item.publication_time:
1059
                time.strftime(misc.date_format(), item.publication_time)
1060
                ' - '
1061
            item.title
1062
            '</h4>'
1063
            '<p>'
1064
            item.text
1065
            '</p>'
1066
            '</div>'
1067

    
1068
        '<ul id="announces-links">'
1069
        '<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces')
1070
        '<li><a href="announces/">%s</a></li>' % _('Previous Announces')
1071
        '</ul>'
1072
        '</div>'
1073

    
1074
    def myspace_snippet [html] (self):
1075
        '<div id="myspace">'
1076
        '<h3>%s</h3>' % _('My Space')
1077
        '<ul>'
1078
        if get_request().user and not get_request().user.anonymous:
1079
            '  <li><a href="myspace/" id="member">%s</a></li>' % _('Access to your personal space')
1080
            '  <li><a href="logout" id="logout">%s</a></li>' % _('Logout')
1081
        else:
1082
            '  <li><a href="register/" id="inscr">%s</a></li>' % _('Registration')
1083
            '  <li><a href="login/" id="login">%s</a></li>' % _('Login')
1084
        '</ul>'
1085
        '</div>'
1086

    
1087

    
1088
    def page_view [html] (self, key, title, urlname = None):
1089
        if not urlname:
1090
            urlname = key[3:].replace(str('_'), str('-'))
1091
        get_response().breadcrumb.append((urlname, title))
1092
        template.html_top(title)
1093
        '<div class="article">'
1094
        htmltext(TextsDirectory.get_html_text(key))
1095
        '</div>'
1096

    
1097
    def informations_editeur [html] (self):
1098
        get_response().filter['bigdiv'] = 'info'
1099
        return self.page_view('aq-editor-info', _('Editor Informations'),
1100
                urlname = 'informations_editeur')
1101

    
1102
    def accessibility(self):
1103
        get_response().filter['bigdiv'] = 'accessibility'
1104
        return self.page_view('aq-accessibility', _('Accessibility Statement'))
1105

    
1106
    def contact(self):
1107
        get_response().filter['bigdiv'] = 'contact'
1108
        return self.page_view('aq-contact', _('Contact'))
1109

    
1110
    def help(self):
1111
        get_response().filter['bigdiv'] = 'help'
1112
        return self.page_view('aq-help', _('Help'))
1113

    
1114

    
1115
from qommon.publisher import get_publisher_class
1116
get_publisher_class().root_directory_class = AlternateRootDirectory
1117
get_publisher_class().after_login_url = 'myspace/'
1118
get_publisher_class().use_sms_feature = True
1119

    
1120

    
1121
EmailsDirectory.register('announces-subscription-confirmation',
1122
        N_('Confirmation of Announces Subscription'),
1123
        N_('Available variables: change_url, cancel_url, time, sitename'),
1124
        default_subject = N_('Announce Subscription Request'),
1125
        default_body = N_("""\
1126
You have (or someone impersonating you has) requested to subscribe to
1127
announces from [sitename].  To confirm this request, visit the
1128
following link:
1129

    
1130
[confirm_url]
1131

    
1132
If you are not the person who made this request, or you wish to cancel
1133
this request, visit the following link:
1134

    
1135
[cancel_url]
1136

    
1137
If you do nothing, the request will lapse after 3 days (precisely on
1138
[time]).
1139
"""))
1140

    
1141

    
1142
TextsDirectory.register('aq-announces-subscription',
1143
        N_('Text on announces subscription page'),
1144
        default = N_('''\
1145
<p>
1146
FIXME
1147
'</p>'''))
1148

    
1149
TextsDirectory.register('aq-sms-demo',
1150
        N_('Text when subscribing to announces SMS and configured as demo'),
1151
        default = N_('''
1152
<p>
1153
Receiving announces by SMS is not possible in this demo
1154
</p>'''))
1155

    
1156
TextsDirectory.register('aq-editor-info', N_('Editor Informations'))
1157
TextsDirectory.register('aq-accessibility', N_('Accessibility Statement'))
1158
TextsDirectory.register('aq-contact', N_('Contact Information'))
1159
TextsDirectory.register('aq-help', N_('Help'))
1160
TextsDirectory.register('aq-sso-text',  N_('Connecting with Identity Provider'),
1161
        default = N_('''<h3>Connecting with Identity Provider</h3>
1162
<p>You can also use your identity provider to connect.
1163
</p>'''))
1164

    
1165
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True)
(14-14/16)