Project

General

Profile

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

root / extra / modules / root.ptl @ 5374ecec

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 payments import PublicPaymentDirectory
39
from events import Event, get_default_event_tags
40

    
41
import admin
42

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

    
46
from saml2 import Saml2Directory
47

    
48
OldRootDirectory = wcs.root.RootDirectory
49

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

    
53
import drupal
54
import ezldap_ui
55

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

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

    
70

    
71
class FormsRootDirectory(wcs.forms.root.RootDirectory):
72

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

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

    
86

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

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

    
150

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

    
154
    def __init__(self, announce):
155
        self.announce = announce
156

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

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

    
165
        '<h3>%s%s</h3>' % (date_heading, self.announce.title)
166

    
167
        '<p>'
168
        self.announce.text
169
        '</p>'
170

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

    
175

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

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

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

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

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

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

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

    
224

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

    
228
        if sms_mode == 'none':
229
            raise errors.TraversalError()
230

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

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

    
248
            if form.get_submit() == 'cancel':
249
                return redirect('subscribe')
250

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

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

    
284
        if mobile:
285
            sub.sms = mobile
286

    
287
        if not get_request().user:
288
            sub.enabled = False
289

    
290
        sub.store()
291

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

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

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

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

    
337
    def sms_unsubscribe [html] (self):
338
        sub = self._get_announce_subscription()
339

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

    
344
        form.add_submit('submit', _('Unsubscribe'))
345
        form.add_submit('cancel', _('Cancel'))
346

    
347
        if form.get_submit() == 'cancel':
348
            return redirect('..')
349

    
350
        get_response().breadcrumb.append(('sms', _('SMS Unsubscription')))
351
        template.html_top()
352

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

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

    
365
            return sms_unsub_ok()
366

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

    
373

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

    
378
        TextsDirectory.get_html_text('aq-announces-subscription')
379

    
380
        sub = self._get_announce_subscription()
381

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

    
402

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

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

    
431
        if form.get_submit() == 'cancel':
432
            return redirect('subscribe')
433

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

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

    
446
        if get_request().user:
447
            sub.user_id = get_request().user.id
448

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

    
454
        if not get_request().user:
455
            sub.enabled = False
456

    
457
        sub.store()
458

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

    
468
            return email_submit_ok()
469

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

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

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

    
492
        return email_submit_ok()
493

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

    
498
        root_url = get_publisher().get_root_url()
499

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

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

    
512
        sub = AnnounceSubscription.get(token.subscription_id)
513

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

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

    
541

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

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

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

    
559
        announces = Announce.get_published_announces()
560

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

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

    
572
        str(feed)
573

    
574
    def email_unsubscribe [html] (self):
575
        sub = self._get_announce_subscription()
576

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

    
581
        form.add_submit('submit', _('Unsubscribe'))
582
        form.add_submit('cancel', _('Cancel'))
583

    
584
        if form.get_submit() == 'cancel':
585
            return redirect('..')
586

    
587
        get_response().breadcrumb.append(('email', _('Email Unsubscription')))
588
        template.html_top()
589

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

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

    
607
            return email_unsub_ok()
608

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

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

    
621
        if announce.hidden:
622
            raise errors.TraversalError()
623

    
624
        get_response().breadcrumb.append((str(announce.id), announce.title))
625
        return AnnounceDirectory(announce)
626

    
627
OldRegisterDirectory = wcs.root.RegisterDirectory
628

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

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

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

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

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

    
654
        return OldRegisterDirectory._q_index(self)
655

    
656
OldLoginDirectory = wcs.root.LoginDirectory
657

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

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

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

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

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

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

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

    
713
            '</div>'
714

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

    
732
            get_request().environ['REQUEST_METHOD'] = 'GET'
733

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

    
738
        else:
739
            return OldLoginDirectory._q_index(self)
740

    
741

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

    
748

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

    
758
    admin = admin.AdminRootDirectory()
759
    announces_dir = AnnouncesDirectory()
760
    register = AlternateRegisterDirectory()
761
    login = AlternateLoginDirectory()
762
    ident = AlternateIdentDirectory()
763
    myspace = MyspaceDirectory()
764
    agenda = AgendaDirectory()
765
    saml = Saml2Directory()
766
    payment = PublicPaymentDirectory()
767

    
768
    def _q_traverse(self, path):
769
        drupal.try_auth()
770
        ezldap_ui.try_auth(self)
771

    
772
        session = get_session()
773
        if session:
774
            get_request().user = session.get_user()
775
        else:
776
            get_request().user = None
777

    
778
        get_publisher().substitutions.feed(get_request().user)
779

    
780
        response = get_response()
781
        if not hasattr(response, 'filter'):
782
            response.filter = {}
783
        response.filter['gauche'] = self.box_side(path)
784
        response.filter['keywords'] = template.get_current_theme().get('keywords')
785
        response.breadcrumb = [ ('', _('Home')) ]
786

    
787
        if not self.admin:
788
            self.admin = get_publisher().admin_directory_class()
789

    
790
        if not self.backoffice:
791
            self.backoffice = get_publisher().backoffice_directory_class()
792

    
793
        try:
794
            return Directory._q_traverse(self, path)
795
        except errors.TraversalError, e:
796
            try:
797
                f = FormDef.get_by_urlname(path[0])
798
            except KeyError:
799
                pass
800
            else:
801
                base_url = get_publisher().get_root_url()
802

    
803
                uri_rest = get_request().environ.get('REQUEST_URI')
804
                if not uri_rest:
805
                    uri_rest = get_request().get_path()
806
                if uri_rest.startswith(base_url):
807
                    uri_rest = uri_rest[len(base_url):]
808
                elif uri_rest.startswith('/'):
809
                    # dirty hack, ezldap reverseproxy uses a fake base_url
810
                    uri_rest = uri_rest[1:]
811
                if f.category_id:
812
                    return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest))
813

    
814
            raise e
815

    
816

    
817
    def _q_lookup(self, component):
818
        if component == 'qo':
819
            dirname = os.path.join(get_publisher().data_dir, 'qommon')
820
            return StaticDirectory(dirname, follow_symlinks = True)
821

    
822
        if component in ('css','images'):
823
            return OldRootDirectory._q_lookup(self, component)
824

    
825
        # is this a category ?
826
        try:
827
            category = Category.get_by_urlname(component)
828
        except KeyError:
829
            pass
830
        else:
831
            return FormsRootDirectory(category)
832

    
833
        # is this a formdef ?
834
        try:
835
            formdef = FormDef.get_by_urlname(component)
836
        except KeyError:
837
            pass
838
        else:
839
            if formdef.category_id is None:
840
                return FormsRootDirectory()._q_lookup(component)
841
            # if there is category, let it fall back to raise TraversalError,
842
            # it will get caught in _q_traverse that will redirect it to an
843
            # URL embedding the category
844

    
845
        return None
846

    
847

    
848
    def _q_index [html] (self):
849
        root_url = get_publisher().get_root_url()
850
        if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump:
851
            return redirect('%smyspace/new' % root_url)
852

    
853
        if get_response().iframe_mode:
854
            # never display home page in an iframe
855
            return redirect('%sservices' % root_url)
856

    
857
        template.html_top()
858

    
859
        t = TextsDirectory.get_html_text('aq-home-page')
860
        if t:
861
            '<div id="home-page-intro">'
862
            t
863
            '</div>'
864

    
865

    
866
        '<div id="centre">'
867
        self.box_services(position='1st')
868
        '</div>'
869
        '<div id="droite">'
870
        self.myspace_snippet()
871
        self.box_services(position='2nd')
872
        self.consultations()
873
        self.announces()
874
        '</div>'
875

    
876
    def services [html] (self):
877
        template.html_top()
878
        get_response().filter['bigdiv'] = 'rub_service'
879
        self.box_services(level = 2)
880

    
881
    def box_services [html] (self, level=3, position=None):
882
        ## Services
883
        if get_request().user and get_request().user.roles:
884
            accepted_roles = get_request().user.roles
885
        else:
886
            accepted_roles = []
887

    
888
        cats = Category.select(order_by = 'name')
889
        cats = [x for x in cats if x.url_name != 'consultations']
890
        Category.sort_by_position(cats)
891

    
892
        all_formdefs = FormDef.select(lambda x: (not x.disabled or x.disabled_redirection)
893
                or x.always_advertise, order_by = 'name')
894

    
895
        if position:
896
            t = self.display_list_of_formdefs(
897
                            [x for x in cats if x.get_homepage_position() == position],
898
                            all_formdefs, accepted_roles)
899
        else:
900
            t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles)
901

    
902
        if not t:
903
            return
904

    
905
        if position == '2nd':
906
            '<div id="services-2nd">'
907
        else:
908
            '<div id="services">'
909
        if level == 2:
910
            '<h2>%s</h2>' % _('Services')
911
        else:
912
            '<h3>%s</h3>' % _('Services')
913

    
914
        if get_response().iframe_mode:
915
            if get_request().user:
916
                message = TextsDirectory.get_html_text('welcome-logged')
917
            else:
918
                message = TextsDirectory.get_html_text('welcome-unlogged')
919

    
920
            if message:
921
                '<div id="welcome-message">'
922
                message
923
                '</div>'
924

    
925
        '<ul>'
926
        t
927
        '</ul>'
928

    
929
        '</div>'
930

    
931
    def display_list_of_formdefs [html] (self, cats, all_formdefs, accepted_roles):
932
        for category in cats:
933
            if category.url_name == 'consultations':
934
                self.consultations_category = category
935
                continue
936
            formdefs = [x for x in all_formdefs if x.category_id == category.id]
937
            formdefs_advertise = []
938

    
939
            for formdef in formdefs[:]:
940
                if formdef.disabled: # is a redirection
941
                    continue
942
                if not formdef.roles:
943
                    continue
944
                if not get_request().user:
945
                    if formdef.always_advertise:
946
                        formdefs_advertise.append(formdef)
947
                    formdefs.remove(formdef)
948
                    continue
949
                if logged_users_role().id in formdef.roles:
950
                    continue
951
                for q in accepted_roles:
952
                    if q in formdef.roles:
953
                        break
954
                else:
955
                    if formdef.always_advertise:
956
                        formdefs_advertise.append(formdef)
957
                    formdefs.remove(formdef)
958

    
959
            if not formdefs and not formdefs_advertise:
960
                continue
961

    
962
            '<li>'
963
            '<strong>'
964
            '<a href="%s/">' % category.url_name
965
            category.name
966
            '</a></strong>\n'
967
            if category.description:
968
                '<p>'
969
                category.description
970
                '</p>'
971
            '<ul>'
972
            limit = category.get_limit()
973
            for formdef in formdefs[:limit]:
974
                '<li>'
975
                if not formdef.disabled:
976
                    '<a href="%s/%s/">%s</a>' % (category.url_name, formdef.url_name, formdef.name)
977
                else: # redirection
978
                    '<a href="%s">%s</a>' % (formdef.disabled_redirection, formdef.name)
979
                '</li>\n'
980
            if len(formdefs) < limit:
981
                for formdef in formdefs_advertise[:limit-len(formdefs)]:
982
                    '<li>'
983
                    '<a href="%s/%s/">%s</a>' % (category.url_name, formdef.url_name, formdef.name)
984
                    ' (%s)' % _('authentication required')
985
                    '</li>\n'
986
            if (len(formdefs)+len(formdefs_advertise)) > limit:
987
                '<li class="all-forms"><a href="%s/" title="%s">%s</a></li>' % (category.url_name,
988
                        _('Access to all forms of the "%s" category') % category.name,
989
                        _('Access to all forms in this category'))
990
            '</ul>'
991
            '</li>\n'
992

    
993
    def consultations [html] (self):
994
        cats = [x for x in Category.select() if x.url_name == 'consultations']
995
        if not cats:
996
            return
997
        consultations_category = cats[0]
998
        formdefs = FormDef.select(lambda x: (
999
                    x.category_id == consultations_category.id and
1000
                        (not x.disabled or x.disabled_redirection)),
1001
                    order_by = 'name')
1002
        if not formdefs:
1003
            return
1004
        ## Consultations
1005
        '<div id="consultations">'
1006
        '<h3>%s</h3>' % _('Consultations')
1007
        if consultations_category.description:
1008
            '<p>'
1009
            consultations_category.description
1010
            '</p>'
1011
        '<ul>'
1012
        for formdef in formdefs:
1013
            '<li>'
1014
            if not formdef.disabled:
1015
                '<a href="%s/%s/">%s</a>' % (consultations_category.url_name,
1016
                    formdef.url_name, formdef.name)
1017
            else: # redirection
1018
                '<a href="%s">%s</a>' % (formdef.disabled_redirection, formdef.name)
1019
            '</li>'
1020
        '</ul>'
1021
        '</div>'
1022

    
1023
    def box_side [html] (self, path):
1024
        '<div id="sidebox">'
1025
        root_url = get_publisher().get_root_url()
1026

    
1027
        if self.has_anonymous_access_codes():
1028
            '<form id="follow-form" action="%saccesscode">' % root_url
1029
            '<h3>%s</h3>' % _('Tracking')
1030
            '<label>%s</label> ' % _('Code:')
1031
            '<input name="code" size="10"/>'
1032
            '</form>'
1033

    
1034
        self.links()
1035

    
1036
        cats = Category.select(order_by = 'name')
1037
        cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side']
1038
        Category.sort_by_position(cats)
1039
        if cats:
1040
            '<div id="side-services">'
1041
            '<h3>%s</h3>' % _('Services')
1042
            '<ul>'
1043
            for cat in cats:
1044
                '<li><a href="%s/">%s</a></li>' % (cat.url_name, cat.name)
1045
            '</ul>'
1046
            '</div>'
1047

    
1048
        if Event.keys(): # if there are events, add a link to the agenda
1049
            tags = get_cfg('misc', {}).get('event_tags')
1050
            if not tags:
1051
                tags = get_default_event_tags()
1052
            '<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>' % (root_url, _('Agenda'))
1053

    
1054
            if path and path[0] == 'agenda':
1055
                '<p class="tags">'
1056
                for tag in tags:
1057
                    '<a href="%sagenda/tag/%s">%s</a> ' % (root_url, tag, tag)
1058
                '</p>'
1059
                self.agenda.display_remote_calendars()
1060

    
1061
                '<p>'
1062
                '  <a href="%sagenda/filter">%s</a>' % (root_url, _('Advanced Filter'))
1063
                '</p>'
1064

    
1065
        '</div>'
1066

    
1067
    def has_anonymous_access_codes(self):
1068
        for workflow in Workflow.select():
1069
            for wfstatus in workflow.possible_status:
1070
                for wfitem in wfstatus.items:
1071
                    if wfitem.key == 'create-anonymous-access-code':
1072
                        return True
1073
        return False
1074

    
1075
    def accesscode(self):
1076
        code = get_request().form.get('code')
1077
        if not code:
1078
            return redirect(get_publisher().get_root_url())
1079
        try:
1080
            token = Token.get(code)
1081
        except KeyError:
1082
            return redirect(get_publisher().get_root_url())
1083
        if token.type != 'anonymous-access-code':
1084
            return redirect(get_publisher().get_root_url())
1085
        formdef_urlname, formdata_id = token.formdata_reference
1086
        try:
1087
            formdata = FormDef.get_by_urlname(formdef_urlname).data_class().get(formdata_id)
1088
        except KeyError:
1089
            return redirect(get_publisher().get_root_url())
1090
        session = get_session()
1091
        if not hasattr(session, '_wf_anonymous_access_authorized'):
1092
            session._wf_anonymous_access_authorized = []
1093
        session._wf_anonymous_access_authorized.append(formdata.get_url())
1094
        return redirect(formdata.get_url() + 'access/')
1095

    
1096
    def links [html] (self):
1097
        links = Link.select()
1098
        if not links:
1099
            return
1100

    
1101
        Link.sort_by_position(links)
1102

    
1103
        '<div id="links">'
1104
        if links[0].url:
1105
            # first link has an URL, so it's not a title, so we display a
1106
            # generic title
1107
            '<h3>%s</h3>' % _('Useful links')
1108
        has_ul = False
1109
        for link in links:
1110
            if not link.url:
1111
                # acting title
1112
                if has_ul:
1113
                    '</ul>'
1114
                '<h3>%s</h3>' % link.title
1115
                '<ul>'
1116
                has_ul = True
1117
            else:
1118
                if not has_ul:
1119
                    '<ul>'
1120
                    has_ul = True
1121
                '<li><a href="%s">%s</a></li>' % (link.url, link.title)
1122
        if has_ul:
1123
            '</ul>'
1124
        '</div>'
1125

    
1126

    
1127
    def announces [html] (self):
1128
        announces = Announce.get_published_announces()
1129
        if not announces:
1130
            return
1131

    
1132
        '<div id="announces">'
1133
        '<h3>%s</h3>' % _('Announces to citizens')
1134
        for item in announces[:3]:
1135
            '<div class="announce-item">'
1136
            '<h4>'
1137
            if item.publication_time:
1138
                time.strftime(misc.date_format(), item.publication_time)
1139
                ' - '
1140
            item.title
1141
            '</h4>'
1142
            '<p>'
1143
            item.text
1144
            '</p>'
1145
            '</div>'
1146

    
1147
        '<ul id="announces-links">'
1148
        '<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces')
1149
        '<li><a href="announces/">%s</a></li>' % _('Previous Announces')
1150
        '</ul>'
1151
        '</div>'
1152

    
1153
    def myspace_snippet [html] (self):
1154
        '<div id="myspace">'
1155
        '<h3>%s</h3>' % _('My Space')
1156
        '<ul>'
1157
        if get_request().user and not get_request().user.anonymous:
1158
            '  <li><a href="myspace/" id="member">%s</a></li>' % _('Access to your personal space')
1159
            '  <li><a href="logout" id="logout">%s</a></li>' % _('Logout')
1160
        else:
1161
            '  <li><a href="register/" id="inscr">%s</a></li>' % _('Registration')
1162
            '  <li><a href="login/" id="login">%s</a></li>' % _('Login')
1163
        '</ul>'
1164
        '</div>'
1165

    
1166

    
1167
    def page_view [html] (self, key, title, urlname = None):
1168
        if not urlname:
1169
            urlname = key[3:].replace(str('_'), str('-'))
1170
        get_response().breadcrumb.append((urlname, title))
1171
        template.html_top(title)
1172
        '<div class="article">'
1173
        htmltext(TextsDirectory.get_html_text(key))
1174
        '</div>'
1175

    
1176
    def informations_editeur [html] (self):
1177
        get_response().filter['bigdiv'] = 'info'
1178
        return self.page_view('aq-editor-info', _('Editor Informations'),
1179
                urlname = 'informations_editeur')
1180

    
1181
    def accessibility(self):
1182
        get_response().filter['bigdiv'] = 'accessibility'
1183
        return self.page_view('aq-accessibility', _('Accessibility Statement'))
1184

    
1185
    def contact(self):
1186
        get_response().filter['bigdiv'] = 'contact'
1187
        return self.page_view('aq-contact', _('Contact'))
1188

    
1189
    def help(self):
1190
        get_response().filter['bigdiv'] = 'help'
1191
        return self.page_view('aq-help', _('Help'))
1192

    
1193

    
1194
from qommon.publisher import get_publisher_class
1195
get_publisher_class().root_directory_class = AlternateRootDirectory
1196
get_publisher_class().after_login_url = 'myspace/'
1197
get_publisher_class().use_sms_feature = True
1198

    
1199

    
1200
EmailsDirectory.register('announces-subscription-confirmation',
1201
        N_('Confirmation of Announces Subscription'),
1202
        N_('Available variables: change_url, cancel_url, time, sitename'),
1203
        default_subject = N_('Announce Subscription Request'),
1204
        default_body = N_("""\
1205
You have (or someone impersonating you has) requested to subscribe to
1206
announces from [sitename].  To confirm this request, visit the
1207
following link:
1208

    
1209
[confirm_url]
1210

    
1211
If you are not the person who made this request, or you wish to cancel
1212
this request, visit the following link:
1213

    
1214
[cancel_url]
1215

    
1216
If you do nothing, the request will lapse after 3 days (precisely on
1217
[time]).
1218
"""))
1219

    
1220

    
1221
TextsDirectory.register('aq-announces-subscription',
1222
        N_('Text on announces subscription page'),
1223
        default = N_('''\
1224
<p>
1225
FIXME
1226
'</p>'''))
1227

    
1228
TextsDirectory.register('aq-sms-demo',
1229
        N_('Text when subscribing to announces SMS and configured as demo'),
1230
        default = N_('''
1231
<p>
1232
Receiving announces by SMS is not possible in this demo
1233
</p>'''))
1234

    
1235
TextsDirectory.register('aq-editor-info', N_('Editor Informations'))
1236
TextsDirectory.register('aq-accessibility', N_('Accessibility Statement'))
1237
TextsDirectory.register('aq-contact', N_('Contact Information'))
1238
TextsDirectory.register('aq-help', N_('Help'))
1239
TextsDirectory.register('aq-sso-text',  N_('Connecting with Identity Provider'),
1240
        default = N_('''<h3>Connecting with Identity Provider</h3>
1241
<p>You can also use your identity provider to connect.
1242
</p>'''))
1243

    
1244
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True)
(22-22/26)