Project

General

Profile

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

root / extra / modules / root.ptl @ 069fda88

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']
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
        response = get_response()
779
        if not hasattr(response, 'filter'):
780
            response.filter = {}
781
        response.filter['gauche'] = self.box_side(path)
782
        response.filter['keywords'] = template.get_current_theme().get('keywords')
783
        response.breadcrumb = [ ('', _('Home')) ]
784

    
785
        if not self.admin:
786
            self.admin = get_publisher().admin_directory_class()
787

    
788
        if not self.backoffice:
789
            self.backoffice = get_publisher().backoffice_directory_class()
790

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

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

    
812
            raise e
813

    
814

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

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

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

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

    
843
        return None
844

    
845

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

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

    
855
        template.html_top()
856

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

    
863

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

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

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

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

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

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

    
900
        if not t:
901
            return
902

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

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

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

    
923
        '<ul>'
924
        t
925
        '</ul>'
926

    
927
        '</div>'
928

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

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

    
957
            if not formdefs and not formdefs_advertise:
958
                continue
959

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

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

    
1021
    def box_side [html] (self, path):
1022
        '<div id="sidebox">'
1023
        self.links()
1024

    
1025
        cats = Category.select(order_by = 'name')
1026
        cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side']
1027
        Category.sort_by_position(cats)
1028
        if cats:
1029
            '<div id="side-services">'
1030
            '<h3>%s</h3>' % _('Services')
1031
            '<ul>'
1032
            for cat in cats:
1033
                '<li><a href="%s/">%s</a></li>' % (cat.url_name, cat.name)
1034
            '</ul>'
1035
            '</div>'
1036

    
1037
        if Event.keys(): # if there are events, add a link to the agenda
1038
            tags = get_cfg('misc', {}).get('event_tags')
1039
            if not tags:
1040
                tags = get_default_event_tags()
1041
            root_url = get_publisher().get_root_url()
1042
            '<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>' % (root_url, _('Agenda'))
1043

    
1044
            if path and path[0] == 'agenda':
1045
                '<p class="tags">'
1046
                for tag in tags:
1047
                    '<a href="%sagenda/tag/%s">%s</a> ' % (root_url, tag, tag)
1048
                '</p>'
1049
                self.agenda.display_remote_calendars()
1050

    
1051
                '<p>'
1052
                '  <a href="%sagenda/filter">%s</a>' % (root_url, _('Advanced Filter'))
1053
                '</p>'
1054

    
1055
        '</div>'
1056

    
1057

    
1058
    def links [html] (self):
1059
        links = Link.select()
1060
        if not links:
1061
            return
1062

    
1063
        Link.sort_by_position(links)
1064

    
1065
        '<div id="links">'
1066
        if links[0].url:
1067
            # first link has an URL, so it's not a title, so we display a
1068
            # generic title
1069
            '<h3>%s</h3>' % _('Useful links')
1070
        has_ul = False
1071
        for link in links:
1072
            if not link.url:
1073
                # acting title
1074
                if has_ul:
1075
                    '</ul>'
1076
                '<h3>%s</h3>' % link.title
1077
                '<ul>'
1078
                has_ul = True
1079
            else:
1080
                if not has_ul:
1081
                    '<ul>'
1082
                    has_ul = True
1083
                '<li><a href="%s">%s</a></li>' % (link.url, link.title)
1084
        if has_ul:
1085
            '</ul>'
1086
        '</div>'
1087

    
1088

    
1089
    def announces [html] (self):
1090
        announces = Announce.get_published_announces()
1091
        if not announces:
1092
            return
1093

    
1094
        '<div id="announces">'
1095
        '<h3>%s</h3>' % _('Announces to citizens')
1096
        for item in announces[:3]:
1097
            '<div class="announce-item">'
1098
            '<h4>'
1099
            if item.publication_time:
1100
                time.strftime(misc.date_format(), item.publication_time)
1101
                ' - '
1102
            item.title
1103
            '</h4>'
1104
            '<p>'
1105
            item.text
1106
            '</p>'
1107
            '</div>'
1108

    
1109
        '<ul id="announces-links">'
1110
        '<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces')
1111
        '<li><a href="announces/">%s</a></li>' % _('Previous Announces')
1112
        '</ul>'
1113
        '</div>'
1114

    
1115
    def myspace_snippet [html] (self):
1116
        '<div id="myspace">'
1117
        '<h3>%s</h3>' % _('My Space')
1118
        '<ul>'
1119
        if get_request().user and not get_request().user.anonymous:
1120
            '  <li><a href="myspace/" id="member">%s</a></li>' % _('Access to your personal space')
1121
            '  <li><a href="logout" id="logout">%s</a></li>' % _('Logout')
1122
        else:
1123
            '  <li><a href="register/" id="inscr">%s</a></li>' % _('Registration')
1124
            '  <li><a href="login/" id="login">%s</a></li>' % _('Login')
1125
        '</ul>'
1126
        '</div>'
1127

    
1128

    
1129
    def page_view [html] (self, key, title, urlname = None):
1130
        if not urlname:
1131
            urlname = key[3:].replace(str('_'), str('-'))
1132
        get_response().breadcrumb.append((urlname, title))
1133
        template.html_top(title)
1134
        '<div class="article">'
1135
        htmltext(TextsDirectory.get_html_text(key))
1136
        '</div>'
1137

    
1138
    def informations_editeur [html] (self):
1139
        get_response().filter['bigdiv'] = 'info'
1140
        return self.page_view('aq-editor-info', _('Editor Informations'),
1141
                urlname = 'informations_editeur')
1142

    
1143
    def accessibility(self):
1144
        get_response().filter['bigdiv'] = 'accessibility'
1145
        return self.page_view('aq-accessibility', _('Accessibility Statement'))
1146

    
1147
    def contact(self):
1148
        get_response().filter['bigdiv'] = 'contact'
1149
        return self.page_view('aq-contact', _('Contact'))
1150

    
1151
    def help(self):
1152
        get_response().filter['bigdiv'] = 'help'
1153
        return self.page_view('aq-help', _('Help'))
1154

    
1155

    
1156
from qommon.publisher import get_publisher_class
1157
get_publisher_class().root_directory_class = AlternateRootDirectory
1158
get_publisher_class().after_login_url = 'myspace/'
1159
get_publisher_class().use_sms_feature = True
1160

    
1161

    
1162
EmailsDirectory.register('announces-subscription-confirmation',
1163
        N_('Confirmation of Announces Subscription'),
1164
        N_('Available variables: change_url, cancel_url, time, sitename'),
1165
        default_subject = N_('Announce Subscription Request'),
1166
        default_body = N_("""\
1167
You have (or someone impersonating you has) requested to subscribe to
1168
announces from [sitename].  To confirm this request, visit the
1169
following link:
1170

    
1171
[confirm_url]
1172

    
1173
If you are not the person who made this request, or you wish to cancel
1174
this request, visit the following link:
1175

    
1176
[cancel_url]
1177

    
1178
If you do nothing, the request will lapse after 3 days (precisely on
1179
[time]).
1180
"""))
1181

    
1182

    
1183
TextsDirectory.register('aq-announces-subscription',
1184
        N_('Text on announces subscription page'),
1185
        default = N_('''\
1186
<p>
1187
FIXME
1188
'</p>'''))
1189

    
1190
TextsDirectory.register('aq-sms-demo',
1191
        N_('Text when subscribing to announces SMS and configured as demo'),
1192
        default = N_('''
1193
<p>
1194
Receiving announces by SMS is not possible in this demo
1195
</p>'''))
1196

    
1197
TextsDirectory.register('aq-editor-info', N_('Editor Informations'))
1198
TextsDirectory.register('aq-accessibility', N_('Accessibility Statement'))
1199
TextsDirectory.register('aq-contact', N_('Contact Information'))
1200
TextsDirectory.register('aq-help', N_('Help'))
1201
TextsDirectory.register('aq-sso-text',  N_('Connecting with Identity Provider'),
1202
        default = N_('''<h3>Connecting with Identity Provider</h3>
1203
<p>You can also use your identity provider to connect.
1204
</p>'''))
1205

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