Project

General

Profile

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

root / extra / modules / root.ptl @ 30d69e62

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
        forms_by_status_name = {}
129
        workflows = Workflow.select(order_by = 'name')
130
        for workflow in workflows:
131
            # XXX: seperate endpoints from non-endpoints
132
            for status in workflow.possible_status:
133
                fms = [x for x in user_forms if x.status == 'wf-%s' % status.id and \
134
                        x.formdef and x.formdef.workflow_id == workflow.id]
135
                if not fms:
136
                    continue
137
                if status.name in forms_by_status_name:
138
                    forms_by_status_name[status.name] += fms
139
                else:
140
                    forms_by_status_name[status.name] = fms
141
        for status_name in forms_by_status_name:
142
            '<h4>%s</h4>' % _('My forms with status "%s"') % status_name
143
            '<ul>'
144
            forms_by_status_name[status_name].sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))
145
            for f in forms_by_status_name[status_name]:
146
                if f.formdef.category_id:
147
                    category_url = f.formdef.category.url_name
148
                else:
149
                    category_url = '.'
150
                '<li><a href="%s%s/%s/%s/">%s</a>, %s</li>' % (
151
                        base_url,
152
                        category_url,
153
                        f.formdef.url_name, f.id, f.formdef.name, 
154
                        misc.localstrftime(f.receipt_time))
155
            '</ul>'
156

    
157

    
158
class AnnounceDirectory(Directory):
159
    _q_exports = ['', 'edit', 'delete', 'email']
160

    
161
    def __init__(self, announce):
162
        self.announce = announce
163

    
164
    def _q_index [html] (self):
165
        template.html_top(_('Announces to citizens'))
166

    
167
        if self.announce.publication_time:
168
            date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time)
169
        else:
170
            date_heading = ''
171

    
172
        '<h3>%s%s</h3>' % (date_heading, self.announce.title)
173

    
174
        '<p>'
175
        self.announce.text
176
        '</p>'
177

    
178
        '<p>'
179
        '<a href="../">%s</a>' % _('Back')
180
        '</p>'
181

    
182

    
183
class AnnouncesDirectory(Directory):
184
    _q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm',
185
            'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist']
186

    
187
    
188
    def _q_traverse(self, path):
189
        get_response().breadcrumb.append(('announces/', _('Announces')))
190
        return Directory._q_traverse(self, path)
191

    
192
    def _q_index [html] (self):
193
        template.html_top(_('Announces to citizens'))
194
        self.announces_list()
195
        '<ul id="announces-links">'
196
        '<li><a href="subscribe">%s</a></li>' % _('Receiving those Announces')
197
        '</ul>'
198

    
199
    def _get_announce_subscription(self):
200
        """ """
201
        sub = None
202
        if get_request().user:
203
            subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id)
204
            if subs:
205
                sub = subs[0]
206
        return sub
207

    
208
    def rawlist [html] (self):
209
        self.announces_list()
210
        get_response().filter = None
211

    
212
    def announces_list [html] (self):
213
        announces = Announce.get_published_announces()
214
        if not announces:
215
            raise errors.TraversalError()
216

    
217
        # XXX: will need pagination someday
218
        for item in announces:
219
            '<div class="announce-item">\n'
220
            '<h4>'
221
            if item.publication_time:
222
                time.strftime(misc.date_format(), item.publication_time)
223
                ' - '
224
            item.title
225
            '</h4>\n'
226
            '<p>\n'
227
            item.text
228
            '\n</p>\n'
229
            '</div>\n'
230

    
231

    
232
    def sms [html] (self):
233
        sms_mode = get_cfg('sms', {}).get('mode', 'none')
234

    
235
        if sms_mode == 'none':
236
            raise errors.TraversalError()
237

    
238
        get_response().breadcrumb.append(('sms', _('SMS')))
239
        template.html_top(_('Receiving announces by SMS'))
240

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

    
255
            if form.get_submit() == 'cancel':
256
                return redirect('subscribe')
257

    
258
            if form.is_submitted() and not form.has_errors():
259
                s = self.sms_submit(form)
260
                if s == False:
261
                    form.render()
262
                else:
263
                    return redirect("smsconfirm")
264
            else:
265
                form.render()
266

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

    
291
        if mobile:
292
            sub.sms = mobile
293

    
294
        if not get_request().user:
295
            sub.enabled = False
296

    
297
        sub.store()
298

    
299
        # Asking sms confirmation
300
        token = Token(3 * 86400, 4, string.digits)
301
        token.type = 'announces-subscription-confirmation'
302
        token.subscription_id = sub.id
303
        token.store()
304

    
305
        message = _("Confirmation code : %s" % token.id)
306
        sms_cfg = get_cfg('sms', {})
307
        sms = SMS()
308
        try:
309
            sms.send([mobile], message, sms_cfg.get('sender', 'auquotidien'))
310
        except errors.SMSError, e:
311
            get_logger().error(e)
312
            form.set_error("mobile", _("Send SMS confirmation failed"))
313
            sub.remove("sms")
314
            return False
315

    
316
    def smsconfirm [html] (self):
317
        template.html_top(_('Receiving announces by SMS confirmation'))
318
        "<p>%s</p>" % _("You will receive a confirmation code by SMS.")
319
        form = Form(enctype='multipart/form-data')
320
        form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True)
321
        form.add_submit('submit', _('Subscribe'))
322
        form.add_submit('cancel', _('Cancel'))
323

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

    
344
    def sms_unsubscribe [html] (self):
345
        sub = self._get_announce_subscription()
346

    
347
        form = Form(enctype='multipart/form-data')
348
        if not sub:
349
            return redirect('..')
350

    
351
        form.add_submit('submit', _('Unsubscribe'))
352
        form.add_submit('cancel', _('Cancel'))
353

    
354
        if form.get_submit() == 'cancel':
355
            return redirect('..')
356

    
357
        get_response().breadcrumb.append(('sms', _('SMS Unsubscription')))
358
        template.html_top()
359

    
360
        if form.is_submitted() and not form.has_errors():
361
            if sub:
362
                sub.remove("sms")
363

    
364
            def sms_unsub_ok [html] ():
365
                root_url = get_publisher().get_root_url()
366
                '<p>'
367
                _('You have been unsubscribed from announces')
368
                '</p>'
369
                if not get_response().iframe_mode:
370
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
371

    
372
            return sms_unsub_ok()
373

    
374
        else:
375
            '<p>'
376
            _('Do you want to stop receiving announces by sms ?')
377
            '</p>'
378
            form.render()
379

    
380

    
381
    def subscribe [html] (self):
382
        get_response().breadcrumb.append(('subscribe', _('Subscription')))
383
        template.html_top(_('Receiving Announces'))
384

    
385
        TextsDirectory.get_html_text('aq-announces-subscription')
386

    
387
        sub = self._get_announce_subscription()
388

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

    
409

    
410
    def email [html] (self):
411
        get_response().breadcrumb.append(('email', _('Email Subscription')))
412
        template.html_top(_('Receiving Announces by email'))
413

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

    
438
        if form.get_submit() == 'cancel':
439
            return redirect('subscribe')
440

    
441
        if form.is_submitted() and not form.has_errors():
442
            s = self.email_submit(form)
443
            if s is not False:
444
                return s
445
        else:
446
            form.render()
447

    
448
    def email_submit(self, form):
449
        sub = self._get_announce_subscription()
450
        if not sub:
451
            sub = AnnounceSubscription()
452

    
453
        if get_request().user:
454
            sub.user_id = get_request().user.id
455

    
456
        if form.get_widget('email'):
457
            sub.email = form.get_widget('email').parse()
458
        elif get_request().user.email:
459
            sub.email = get_request().user.email
460

    
461
        if not get_request().user:
462
            sub.enabled = False
463

    
464
        sub.store()
465

    
466
        if get_request().user:
467
            def email_submit_ok [html] ():
468
                root_url = get_publisher().get_root_url()
469
                '<p>'
470
                _('You have been subscribed to the announces.')
471
                '</p>'
472
                if not get_response().iframe_mode:
473
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
474

    
475
            return email_submit_ok()
476

    
477
        # asking email confirmation before subscribing someone
478
        token = Token(3 * 86400)
479
        token.type = 'announces-subscription-confirmation'
480
        token.subscription_id = sub.id
481
        token.store()
482
        data = {
483
            'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id,
484
            'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id,
485
            'time': misc.localstrftime(time.localtime(token.expiration)),
486
        }
487

    
488
        emails.custom_ezt_email('announces-subscription-confirmation',
489
                data, sub.email, exclude_current_user = False)
490

    
491
        def email_submit_ok [html] ():
492
            root_url = get_publisher().get_root_url()
493
            '<p>'
494
            _('You have been sent an email for confirmation')
495
            '</p>'
496
            if not get_response().iframe_mode:
497
                '<a href="%s">%s</a>' % (root_url, _('Back Home'))
498

    
499
        return email_submit_ok()
500

    
501
    def emailconfirm(self):
502
        tokenv = get_request().form.get('t')
503
        action = get_request().form.get('a')
504

    
505
        root_url = get_publisher().get_root_url()
506

    
507
        try:
508
            token = Token.get(tokenv)
509
        except KeyError:
510
            return template.error_page(
511
                    _('The token you submitted does not exist, has expired, or has been cancelled.'),
512
                    continue_to = (root_url, _('home page')))
513

    
514
        if token.type != 'announces-subscription-confirmation':
515
            return template.error_page(
516
                    _('The token you submitted is not appropriate for the requested task.'),
517
                    continue_to = (root_url, _('home page')))
518

    
519
        sub = AnnounceSubscription.get(token.subscription_id)
520

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

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

    
548

    
549
    def atom [plain] (self):
550
        response = get_response()
551
        response.set_content_type('application/atom+xml')
552

    
553
        from pyatom import pyatom
554
        xmldoc = pyatom.XMLDoc()
555
        feed = pyatom.Feed()
556
        xmldoc.root_element = feed
557
        feed.title = get_cfg('misc', {}).get('sitename', 'Au Quotidien')
558
        feed.id = get_request().get_url()
559

    
560
        author_email = get_cfg('emails', {}).get('reply_to')
561
        if not author_email:
562
            author_email = get_cfg('emails', {}).get('from')
563
        if author_email:
564
            feed.authors.append(pyatom.Author(author_email))
565

    
566
        announces = Announce.get_published_announces()
567

    
568
        if announces and announces[0].modification_time:
569
            feed.updated = misc.format_time(announces[0].modification_time,
570
                        '%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ',
571
                        gmtime = True)
572
        feed.links.append(pyatom.Link(get_request().get_url(1) + '/'))
573

    
574
        for item in announces:
575
            entry = item.get_atom_entry()
576
            if entry:
577
                feed.entries.append(entry)
578

    
579
        str(feed)
580

    
581
    def email_unsubscribe [html] (self):
582
        sub = self._get_announce_subscription()
583

    
584
        form = Form(enctype='multipart/form-data')
585
        if not sub:
586
            form.add(EmailWidget, 'email', title = _('Email'), required = True)
587

    
588
        form.add_submit('submit', _('Unsubscribe'))
589
        form.add_submit('cancel', _('Cancel'))
590

    
591
        if form.get_submit() == 'cancel':
592
            return redirect('..')
593

    
594
        get_response().breadcrumb.append(('email', _('Email Unsubscription')))
595
        template.html_top()
596

    
597
        if form.is_submitted() and not form.has_errors():
598
            if sub:
599
                sub.remove("email")
600
            else:
601
                email = form.get_widget('email').parse()
602
                for s in AnnounceSubscription.select():
603
                    if s.email == email:
604
                        s.remove("email")
605

    
606
            def email_unsub_ok [html] ():
607
                root_url = get_publisher().get_root_url()
608
                '<p>'
609
                _('You have been unsubscribed from announces')
610
                '</p>'
611
                if not get_response().iframe_mode:
612
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
613

    
614
            return email_unsub_ok()
615

    
616
        else:
617
            '<p>'
618
            _('Do you want to stop receiving announces by email?')
619
            '</p>'
620
            form.render()
621

    
622
    def _q_lookup(self, component):
623
        try:
624
            announce = Announce.get(component)
625
        except KeyError:
626
            raise errors.TraversalError()
627

    
628
        if announce.hidden:
629
            raise errors.TraversalError()
630

    
631
        get_response().breadcrumb.append((str(announce.id), announce.title))
632
        return AnnounceDirectory(announce)
633

    
634
OldRegisterDirectory = wcs.root.RegisterDirectory
635

    
636
class AlternateRegisterDirectory(OldRegisterDirectory):
637
    def _q_traverse(self, path):
638
        get_response().filter['bigdiv'] = 'new_member'
639
        return OldRegisterDirectory._q_traverse(self, path)
640

    
641
    def _q_index [html] (self):
642
        get_logger().info('register')
643
        ident_methods = get_cfg('identification', {}).get('methods', [])
644

    
645
        if len(ident_methods) == 0:
646
            idps = get_cfg('idp', {})
647
            if len(idps) == 0:
648
                return template.error_page(_('Authentication subsystem is not yet configured.'))
649
            ident_methods = ['idp'] # fallback to old behaviour; liberty.
650

    
651
        if len(ident_methods) == 1:
652
            method = ident_methods[0]
653
        else:
654
            method = 'password'
655
            return qommon.ident.register(method)
656

    
657
        if method == 'idp':
658
            root_url = get_publisher().get_root_url()
659
            return redirect('%slogin' % root_url)
660

    
661
        return OldRegisterDirectory._q_index(self)
662

    
663
OldLoginDirectory = wcs.root.LoginDirectory
664

    
665
class AlternateLoginDirectory(OldLoginDirectory):
666
    def _q_traverse(self, path):
667
        get_response().filter['bigdiv'] = 'member'
668
        return OldLoginDirectory._q_traverse(self, path)
669

    
670
    def _q_index [html] (self):
671
        get_logger().info('login')
672
        ident_methods = get_cfg('identification', {}).get('methods', [])
673

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

    
688
        # don't display authentication system choice
689
        if len(ident_methods) == 1:
690
            method = ident_methods[0]
691
            try:
692
                return qommon.ident.login(method)
693
            except KeyError:
694
                get_logger().error('failed to login with method %s' % method)
695
                return errors.TraversalError()
696

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

    
712
            '<div id="login-password">'
713
            get_session().display_message()
714
            form.render()
715

    
716
            base_url = get_publisher().get_root_url()
717
            '<p><a href="%sident/password/forgotten">%s</a></p>' % (
718
                    base_url, _('Forgotten password ?'))
719

    
720
            '</div>'
721

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

    
739
            get_request().environ['REQUEST_METHOD'] = 'GET'
740

    
741
            """<script type="text/javascript">
742
              document.getElementById('login-form')['username'].focus();
743
            </script>"""
744

    
745
        else:
746
            return OldLoginDirectory._q_index(self)
747

    
748

    
749
OldIdentDirectory = wcs.root.IdentDirectory
750
class AlternateIdentDirectory(OldIdentDirectory):
751
    def _q_traverse(self, path):
752
        get_response().filter['bigdiv'] = 'member'
753
        return OldIdentDirectory._q_traverse(self, path)
754

    
755

    
756
class AlternateRootDirectory(OldRootDirectory):
757
    _q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout',
758
            'liberty', 'token', 'saml', 'register', 'ident', 'afterjobs',
759
            ('informations-editeur', 'informations_editeur'), 'index2',
760
            ('announces', 'announces_dir'),
761
            'accessibility', 'contact', 'help',
762
            'myspace', 'services', 'agenda',
763
            'themes', 'pages', 'payment', 'accesscode']
764

    
765
    admin = admin.AdminRootDirectory()
766
    announces_dir = AnnouncesDirectory()
767
    register = AlternateRegisterDirectory()
768
    login = AlternateLoginDirectory()
769
    ident = AlternateIdentDirectory()
770
    myspace = MyspaceDirectory()
771
    agenda = AgendaDirectory()
772
    saml = Saml2Directory()
773
    payment = PublicPaymentDirectory()
774

    
775
    def _q_traverse(self, path):
776
        drupal.try_auth()
777
        ezldap_ui.try_auth(self)
778

    
779
        session = get_session()
780
        if session:
781
            get_request().user = session.get_user()
782
        else:
783
            get_request().user = None
784

    
785
        get_publisher().substitutions.feed(get_request().user)
786

    
787
        response = get_response()
788
        if not hasattr(response, 'filter'):
789
            response.filter = {}
790
        response.filter['gauche'] = self.box_side(path)
791
        response.filter['keywords'] = template.get_current_theme().get('keywords')
792
        response.breadcrumb = [ ('', _('Home')) ]
793

    
794
        if not self.admin:
795
            self.admin = get_publisher().admin_directory_class()
796

    
797
        if not self.backoffice:
798
            self.backoffice = get_publisher().backoffice_directory_class()
799

    
800
        try:
801
            return Directory._q_traverse(self, path)
802
        except errors.TraversalError, e:
803
            try:
804
                f = FormDef.get_by_urlname(path[0])
805
            except KeyError:
806
                pass
807
            else:
808
                base_url = get_publisher().get_root_url()
809

    
810
                uri_rest = get_request().environ.get('REQUEST_URI')
811
                if not uri_rest:
812
                    uri_rest = get_request().get_path()
813
                if uri_rest.startswith(base_url):
814
                    uri_rest = uri_rest[len(base_url):]
815
                elif uri_rest.startswith('/'):
816
                    # dirty hack, ezldap reverseproxy uses a fake base_url
817
                    uri_rest = uri_rest[1:]
818
                if f.category_id:
819
                    return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest))
820

    
821
            raise e
822

    
823

    
824
    def _q_lookup(self, component):
825
        if component == 'qo':
826
            dirname = os.path.join(get_publisher().data_dir, 'qommon')
827
            return StaticDirectory(dirname, follow_symlinks = True)
828

    
829
        if component in ('css','images'):
830
            return OldRootDirectory._q_lookup(self, component)
831

    
832
        # is this a category ?
833
        try:
834
            category = Category.get_by_urlname(component)
835
        except KeyError:
836
            pass
837
        else:
838
            return FormsRootDirectory(category)
839

    
840
        # is this a formdef ?
841
        try:
842
            formdef = FormDef.get_by_urlname(component)
843
        except KeyError:
844
            pass
845
        else:
846
            if formdef.category_id is None:
847
                return FormsRootDirectory()._q_lookup(component)
848
            # if there is category, let it fall back to raise TraversalError,
849
            # it will get caught in _q_traverse that will redirect it to an
850
            # URL embedding the category
851

    
852
        return None
853

    
854

    
855
    def _q_index [html] (self):
856
        root_url = get_publisher().get_root_url()
857
        if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump:
858
            return redirect('%smyspace/new' % root_url)
859

    
860
        if get_response().iframe_mode:
861
            # never display home page in an iframe
862
            return redirect('%sservices' % root_url)
863

    
864
        template.html_top()
865

    
866
        t = TextsDirectory.get_html_text('aq-home-page')
867
        if t:
868
            '<div id="home-page-intro">'
869
            t
870
            '</div>'
871

    
872

    
873
        '<div id="centre">'
874
        self.box_services(position='1st')
875
        '</div>'
876
        '<div id="droite">'
877
        self.myspace_snippet()
878
        self.box_services(position='2nd')
879
        self.consultations()
880
        self.announces()
881
        '</div>'
882

    
883
    def services [html] (self):
884
        template.html_top()
885
        get_response().filter['bigdiv'] = 'rub_service'
886
        self.box_services(level = 2)
887

    
888
    def box_services [html] (self, level=3, position=None):
889
        ## Services
890
        if get_request().user and get_request().user.roles:
891
            accepted_roles = get_request().user.roles
892
        else:
893
            accepted_roles = []
894

    
895
        cats = Category.select(order_by = 'name')
896
        cats = [x for x in cats if x.url_name != 'consultations']
897
        Category.sort_by_position(cats)
898

    
899
        all_formdefs = FormDef.select(lambda x: not x.disabled or x.disabled_redirection,
900
                order_by = 'name')
901

    
902
        if position:
903
            t = self.display_list_of_formdefs(
904
                            [x for x in cats if x.get_homepage_position() == position],
905
                            all_formdefs, accepted_roles)
906
        else:
907
            t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles)
908

    
909
        if not t:
910
            return
911

    
912
        if position == '2nd':
913
            '<div id="services-2nd">'
914
        else:
915
            '<div id="services">'
916
        if level == 2:
917
            '<h2>%s</h2>' % _('Services')
918
        else:
919
            '<h3>%s</h3>' % _('Services')
920

    
921
        if get_response().iframe_mode:
922
            if get_request().user:
923
                message = TextsDirectory.get_html_text('welcome-logged')
924
            else:
925
                message = TextsDirectory.get_html_text('welcome-unlogged')
926

    
927
            if message:
928
                '<div id="welcome-message">'
929
                message
930
                '</div>'
931

    
932
        '<ul>'
933
        t
934
        '</ul>'
935

    
936
        '</div>'
937

    
938
    def display_list_of_formdefs [html] (self, cats, all_formdefs, accepted_roles):
939
        for category in cats:
940
            if category.url_name == 'consultations':
941
                self.consultations_category = category
942
                continue
943
            formdefs = [x for x in all_formdefs if x.category_id == category.id]
944
            formdefs_advertise = []
945

    
946
            for formdef in formdefs[:]:
947
                if formdef.disabled: # is a redirection
948
                    continue
949
                if not formdef.roles:
950
                    continue
951
                if not get_request().user:
952
                    if formdef.always_advertise:
953
                        formdefs_advertise.append(formdef)
954
                    formdefs.remove(formdef)
955
                    continue
956
                if logged_users_role().id in formdef.roles:
957
                    continue
958
                for q in accepted_roles:
959
                    if q in formdef.roles:
960
                        break
961
                else:
962
                    if formdef.always_advertise:
963
                        formdefs_advertise.append(formdef)
964
                    formdefs.remove(formdef)
965

    
966
            if not formdefs and not formdefs_advertise:
967
                continue
968

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

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

    
1030
    def box_side [html] (self, path):
1031
        '<div id="sidebox">'
1032
        root_url = get_publisher().get_root_url()
1033

    
1034
        if self.has_anonymous_access_codes():
1035
            '<form id="follow-form" action="%saccesscode">' % root_url
1036
            '<h3>%s</h3>' % _('Tracking')
1037
            '<label>%s</label> ' % _('Code:')
1038
            '<input name="code" size="10"/>'
1039
            '</form>'
1040

    
1041
        self.links()
1042

    
1043
        cats = Category.select(order_by = 'name')
1044
        cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side']
1045
        Category.sort_by_position(cats)
1046
        if cats:
1047
            '<div id="side-services">'
1048
            '<h3>%s</h3>' % _('Services')
1049
            '<ul>'
1050
            for cat in cats:
1051
                '<li><a href="%s/">%s</a></li>' % (cat.url_name, cat.name)
1052
            '</ul>'
1053
            '</div>'
1054

    
1055
        if Event.keys(): # if there are events, add a link to the agenda
1056
            tags = get_cfg('misc', {}).get('event_tags')
1057
            if not tags:
1058
                tags = get_default_event_tags()
1059
            '<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>' % (root_url, _('Agenda'))
1060

    
1061
            if path and path[0] == 'agenda':
1062
                '<p class="tags">'
1063
                for tag in tags:
1064
                    '<a href="%sagenda/tag/%s">%s</a> ' % (root_url, tag, tag)
1065
                '</p>'
1066
                self.agenda.display_remote_calendars()
1067

    
1068
                '<p>'
1069
                '  <a href="%sagenda/filter">%s</a>' % (root_url, _('Advanced Filter'))
1070
                '</p>'
1071

    
1072
        '</div>'
1073

    
1074
    def has_anonymous_access_codes(self):
1075
        for workflow in Workflow.select():
1076
            for wfstatus in workflow.possible_status:
1077
                for wfitem in wfstatus.items:
1078
                    if wfitem.key == 'create-anonymous-access-code':
1079
                        return True
1080
        return False
1081

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

    
1103
    def links [html] (self):
1104
        links = Link.select()
1105
        if not links:
1106
            return
1107

    
1108
        Link.sort_by_position(links)
1109

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

    
1133

    
1134
    def announces [html] (self):
1135
        announces = Announce.get_published_announces()
1136
        if not announces:
1137
            return
1138

    
1139
        '<div id="announces">'
1140
        '<h3>%s</h3>' % _('Announces to citizens')
1141
        for item in announces[:3]:
1142
            '<div class="announce-item">'
1143
            '<h4>'
1144
            if item.publication_time:
1145
                time.strftime(misc.date_format(), item.publication_time)
1146
                ' - '
1147
            item.title
1148
            '</h4>'
1149
            '<p>'
1150
            item.text
1151
            '</p>'
1152
            '</div>'
1153

    
1154
        '<ul id="announces-links">'
1155
        '<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces')
1156
        '<li><a href="announces/">%s</a></li>' % _('Previous Announces')
1157
        '</ul>'
1158
        '</div>'
1159

    
1160
    def myspace_snippet [html] (self):
1161
        '<div id="myspace">'
1162
        '<h3>%s</h3>' % _('My Space')
1163
        '<ul>'
1164
        if get_request().user and not get_request().user.anonymous:
1165
            '  <li><a href="myspace/" id="member">%s</a></li>' % _('Access to your personal space')
1166
            '  <li><a href="logout" id="logout">%s</a></li>' % _('Logout')
1167
        else:
1168
            '  <li><a href="register/" id="inscr">%s</a></li>' % _('Registration')
1169
            '  <li><a href="login/" id="login">%s</a></li>' % _('Login')
1170
        '</ul>'
1171
        '</div>'
1172

    
1173

    
1174
    def page_view [html] (self, key, title, urlname = None):
1175
        if not urlname:
1176
            urlname = key[3:].replace(str('_'), str('-'))
1177
        get_response().breadcrumb.append((urlname, title))
1178
        template.html_top(title)
1179
        '<div class="article">'
1180
        htmltext(TextsDirectory.get_html_text(key))
1181
        '</div>'
1182

    
1183
    def informations_editeur [html] (self):
1184
        get_response().filter['bigdiv'] = 'info'
1185
        return self.page_view('aq-editor-info', _('Editor Informations'),
1186
                urlname = 'informations_editeur')
1187

    
1188
    def accessibility(self):
1189
        get_response().filter['bigdiv'] = 'accessibility'
1190
        return self.page_view('aq-accessibility', _('Accessibility Statement'))
1191

    
1192
    def contact(self):
1193
        get_response().filter['bigdiv'] = 'contact'
1194
        return self.page_view('aq-contact', _('Contact'))
1195

    
1196
    def help(self):
1197
        get_response().filter['bigdiv'] = 'help'
1198
        return self.page_view('aq-help', _('Help'))
1199

    
1200

    
1201
from qommon.publisher import get_publisher_class
1202
get_publisher_class().root_directory_class = AlternateRootDirectory
1203
get_publisher_class().after_login_url = 'myspace/'
1204
get_publisher_class().use_sms_feature = True
1205

    
1206
# help links
1207
get_publisher_class().backoffice_help_url = {
1208
    'fr': 'http://auquotidien.labs.libre-entreprise.org/doc/fr/manager-guide.html',
1209
}
1210
get_publisher_class().admin_help_url = {
1211
    'fr': 'http://auquotidien.labs.libre-entreprise.org/doc/fr/user-guide.html',
1212
}
1213

    
1214

    
1215
EmailsDirectory.register('announces-subscription-confirmation',
1216
        N_('Confirmation of Announces Subscription'),
1217
        N_('Available variables: change_url, cancel_url, time, sitename'),
1218
        default_subject = N_('Announce Subscription Request'),
1219
        default_body = N_("""\
1220
You have (or someone impersonating you has) requested to subscribe to
1221
announces from [sitename].  To confirm this request, visit the
1222
following link:
1223

    
1224
[confirm_url]
1225

    
1226
If you are not the person who made this request, or you wish to cancel
1227
this request, visit the following link:
1228

    
1229
[cancel_url]
1230

    
1231
If you do nothing, the request will lapse after 3 days (precisely on
1232
[time]).
1233
"""))
1234

    
1235

    
1236
TextsDirectory.register('aq-announces-subscription',
1237
        N_('Text on announces subscription page'),
1238
        default = N_('''\
1239
<p>
1240
FIXME
1241
'</p>'''))
1242

    
1243
TextsDirectory.register('aq-sms-demo',
1244
        N_('Text when subscribing to announces SMS and configured as demo'),
1245
        default = N_('''
1246
<p>
1247
Receiving announces by SMS is not possible in this demo
1248
</p>'''))
1249

    
1250
TextsDirectory.register('aq-editor-info', N_('Editor Informations'))
1251
TextsDirectory.register('aq-accessibility', N_('Accessibility Statement'))
1252
TextsDirectory.register('aq-contact', N_('Contact Information'))
1253
TextsDirectory.register('aq-help', N_('Help'))
1254
TextsDirectory.register('aq-sso-text',  N_('Connecting with Identity Provider'),
1255
        default = N_('''<h3>Connecting with Identity Provider</h3>
1256
<p>You can also use your identity provider to connect.
1257
</p>'''))
1258

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