Projet

Général

Profil

Télécharger (44,4 ko) Statistiques
| Branche: | Tag: | Révision:

root / extra / modules / root.ptl @ 23f4b827

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
from wcs.qommon.misc import get_variadic_url
7

    
8
import os
9
import re
10
import string
11
import urlparse
12

    
13
try:
14
    import lasso
15
except ImportError:
16
    pass
17

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

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

    
36
from links import Link
37
from announces import Announce, AnnounceSubscription
38
from myspace import MyspaceDirectory
39
from agenda import AgendaDirectory
40
from events import Event, get_default_event_tags
41
from payments import PublicPaymentDirectory
42
from payments_ui import InvoicesDirectory
43

    
44
import admin
45

    
46
import wcs.forms.root
47
from wcs.workflows import Workflow
48

    
49
from saml2 import Saml2Directory
50

    
51
OldRootDirectory = wcs.root.RootDirectory
52

    
53
import qommon.ident.password
54
import qommon.ident.idp
55

    
56
import drupal
57
import ezldap_ui
58
import msp_ui
59

    
60
def category_get_homepage_position(self):
61
    if hasattr(self, 'homepage_position') and self.homepage_position:
62
        return self.homepage_position
63
    if self.url_name == 'consultations':
64
        return '2nd'
65
    return '1st'
66
Category.get_homepage_position = category_get_homepage_position
67

    
68
def category_get_limit(self):
69
    if hasattr(self, 'limit') and self.limit is not None:
70
        return self.limit
71
    return 3
72
Category.get_limit = category_get_limit
73

    
74

    
75
class FormsRootDirectory(wcs.forms.root.RootDirectory):
76

    
77
    def _q_index(self, *args):
78
        get_response().filter['is_index'] = True
79
        return wcs.forms.root.RootDirectory._q_index(self, *args)
80

    
81
    def user_forms [html] (self, user_forms):
82
        base_url = get_publisher().get_root_url()
83

    
84
        draft = [x for x in user_forms if x.is_draft()]
85
        if draft:
86
            '<h4 id="drafts">%s</h4>' % _('My Current Drafts')
87
            '<ul>'
88
            for f in draft:
89
                '<li><a href="%s%s/%s/%s">%s</a></li>' % (base_url, 
90
                    f.formdef.category.url_name,
91
                    f.formdef.url_name, f.id, f.formdef.name)
92
            '</ul>'
93

    
94
        forms_by_status_name = {}
95
        for f in user_forms:
96
            if f.is_draft():
97
                continue
98
            status = f.get_visible_status()
99
            if status:
100
                status_name = status.name
101
            else:
102
                status_name = None
103
            if status_name in forms_by_status_name:
104
                forms_by_status_name[status_name].append(f)
105
            else:
106
                forms_by_status_name[status_name] = [f]
107
        for status_name in forms_by_status_name:
108
            if status_name:
109
                '<h4>%s</h4>' % _('My forms with status "%s"') % status_name
110
            else:
111
                '<h4>%s</h4>' % _('My forms with an unknown status') % status_name
112
            '<ul>'
113
            forms_by_status_name[status_name].sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))
114
            for f in forms_by_status_name[status_name]:
115
                if f.formdef.category_id:
116
                    category_url = f.formdef.category.url_name
117
                else:
118
                    category_url = '.'
119
                '<li><a href="%s%s/%s/%s/">%s</a>, %s</li>' % (
120
                        base_url,
121
                        category_url,
122
                        f.formdef.url_name, f.id, f.formdef.name, 
123
                        misc.localstrftime(f.receipt_time))
124
            '</ul>'
125

    
126

    
127
class AnnounceDirectory(Directory):
128
    _q_exports = ['', 'edit', 'delete', 'email']
129

    
130
    def __init__(self, announce):
131
        self.announce = announce
132

    
133
    def _q_index [html] (self):
134
        template.html_top(_('Announces to citizens'))
135

    
136
        if self.announce.publication_time:
137
            date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time)
138
        else:
139
            date_heading = ''
140

    
141
        '<h3>%s%s</h3>' % (date_heading, self.announce.title)
142

    
143
        '<p>'
144
        self.announce.text
145
        '</p>'
146

    
147
        '<p>'
148
        '<a href="../">%s</a>' % _('Back')
149
        '</p>'
150

    
151

    
152
class AnnouncesDirectory(Directory):
153
    _q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm',
154
            'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist']
155

    
156
    
157
    def _q_traverse(self, path):
158
        get_response().breadcrumb.append(('announces/', _('Announces')))
159
        return Directory._q_traverse(self, path)
160

    
161
    def _q_index [html] (self):
162
        template.html_top(_('Announces to citizens'))
163
        self.announces_list()
164
        '<ul id="announces-links">'
165
        '<li><a href="subscribe">%s</a></li>' % _('Receiving those Announces')
166
        '</ul>'
167

    
168
    def _get_announce_subscription(self):
169
        """ """
170
        sub = None
171
        if get_request().user:
172
            subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id)
173
            if subs:
174
                sub = subs[0]
175
        return sub
176

    
177
    def rawlist [html] (self):
178
        self.announces_list()
179
        get_response().filter = None
180

    
181
    def announces_list [html] (self):
182
        announces = Announce.get_published_announces()
183
        if not announces:
184
            raise errors.TraversalError()
185

    
186
        # XXX: will need pagination someday
187
        for item in announces:
188
            '<div class="announce-item">\n'
189
            '<h4>'
190
            if item.publication_time:
191
                time.strftime(misc.date_format(), item.publication_time)
192
                ' - '
193
            item.title
194
            '</h4>\n'
195
            '<p>\n'
196
            item.text
197
            '\n</p>\n'
198
            '</div>\n'
199

    
200

    
201
    def sms [html] (self):
202
        sms_mode = get_cfg('sms', {}).get('mode', 'none')
203

    
204
        if sms_mode == 'none':
205
            raise errors.TraversalError()
206

    
207
        get_response().breadcrumb.append(('sms', _('SMS')))
208
        template.html_top(_('Receiving announces by SMS'))
209

    
210
        if sms_mode == 'demo':
211
            TextsDirectory.get_html_text('aq-sms-demo')
212
        else:
213
            announces_cfg = get_cfg('announces',{})
214
            mobile_mask = announces_cfg.get('mobile_mask')
215
            if mobile_mask:
216
                mobile_mask = ' (' + mobile_mask + ')'
217
            else:
218
                mobile_mask = ''
219
            form = Form(enctype='multipart/form-data')
220
            form.add(StringWidget, 'mobile', title = _('Mobile number %s') % mobile_mask, size=12, required=True)
221
            form.add_submit('submit', _('Subscribe'))
222
            form.add_submit('cancel', _('Cancel'))
223

    
224
            if form.get_submit() == 'cancel':
225
                return redirect('subscribe')
226

    
227
            if form.is_submitted() and not form.has_errors():
228
                s = self.sms_submit(form)
229
                if s == False:
230
                    form.render()
231
                else:
232
                    return redirect("smsconfirm")
233
            else:
234
                form.render()
235

    
236
    def sms_submit(self, form):
237
        mobile = form.get_widget("mobile").parse()
238
        # clean the string, remove any extra character
239
        mobile = re.sub('[^0-9+]','',mobile)
240
        # if a mask was set, validate
241
        announces_cfg = get_cfg('announces',{})
242
        mobile_mask = announces_cfg.get('mobile_mask')
243
        if mobile_mask:
244
            mobile_regexp = re.sub('X','[0-9]', mobile_mask) + '$'
245
            if not re.match(mobile_regexp, mobile):
246
                form.set_error("mobile", _("Phone number invalid ! It must match ") + mobile_mask)
247
                return False
248
        if mobile.startswith('00'):
249
            mobile = '+' + mobile[2:]
250
        else:
251
            # Default to france international prefix
252
            if not mobile.startswith('+'):
253
                mobile = re.sub("^0", "+33", mobile)
254
        sub = self._get_announce_subscription()
255
        if not sub:
256
            sub = AnnounceSubscription()
257
        if get_request().user:
258
            sub.user_id = get_request().user.id
259

    
260
        if mobile:
261
            sub.sms = mobile
262

    
263
        if not get_request().user:
264
            sub.enabled = False
265

    
266
        sub.store()
267

    
268
        # Asking sms confirmation
269
        token = Token(3 * 86400, 4, string.digits)
270
        token.type = 'announces-subscription-confirmation'
271
        token.subscription_id = sub.id
272
        token.store()
273

    
274
        message = _("Confirmation code : %s") % str(token.id)
275
        sms_cfg = get_cfg('sms', {})
276
        sender = sms_cfg.get('sender', 'AuQuotidien')[:11]
277
        mode = sms_cfg.get('mode', 'none')
278
        sms = SMS.get_sms_class(mode)
279
        try:
280
            sms.send(sender, [mobile], message)
281
        except errors.SMSError, e:
282
            get_logger().error(e)
283
            form.set_error("mobile", _("Send SMS confirmation failed"))
284
            sub.remove("sms")
285
            return False
286

    
287
    def smsconfirm [html] (self):
288
        template.html_top(_('Receiving announces by SMS confirmation'))
289
        "<p>%s</p>" % _("You will receive a confirmation code by SMS.")
290
        form = Form(enctype='multipart/form-data')
291
        form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True)
292
        form.add_submit('submit', _('Subscribe'))
293
        form.add_submit('cancel', _('Cancel'))
294

    
295
        if form.get_submit() == 'cancel':
296
            return redirect('..')
297

    
298
        if form.is_submitted() and not form.has_errors():
299
            token = None
300
            id = form.get_widget("code").parse()
301
            try:
302
                token = Token.get(id)
303
            except KeyError:
304
                form.set_error("code",  _('Invalid confirmation code.'))
305
            else:
306
                if token.type != 'announces-subscription-confirmation':
307
                    form.set_error("code",  _('Invalid confirmation code.'))
308
                else:
309
                    sub = AnnounceSubscription.get(token.subscription_id)
310
                    token.remove_self()
311
                    sub.enabled_sms = True
312
                    sub.store()
313
                    return redirect('.')
314
            form.render()
315
        else:
316
            form.render()
317

    
318
    def sms_unsubscribe [html] (self):
319
        sub = self._get_announce_subscription()
320

    
321
        form = Form(enctype='multipart/form-data')
322
        if not sub:
323
            return redirect('..')
324

    
325
        form.add_submit('submit', _('Unsubscribe'))
326
        form.add_submit('cancel', _('Cancel'))
327

    
328
        if form.get_submit() == 'cancel':
329
            return redirect('..')
330

    
331
        get_response().breadcrumb.append(('sms', _('SMS Unsubscription')))
332
        template.html_top()
333

    
334
        if form.is_submitted() and not form.has_errors():
335
            if sub:
336
                sub.remove("sms")
337

    
338
            def sms_unsub_ok [html] ():
339
                root_url = get_publisher().get_root_url()
340
                '<p>'
341
                _('You have been unsubscribed from announces')
342
                '</p>'
343
                if not get_response().iframe_mode:
344
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
345

    
346
            return sms_unsub_ok()
347

    
348
        else:
349
            '<p>'
350
            _('Do you want to stop receiving announces by sms ?')
351
            '</p>'
352
            form.render()
353

    
354

    
355
    def subscribe [html] (self):
356
        get_response().breadcrumb.append(('subscribe', _('Subscription')))
357
        template.html_top(_('Receiving Announces'))
358

    
359
        TextsDirectory.get_html_text('aq-announces-subscription')
360

    
361
        sub = self._get_announce_subscription()
362

    
363
        '<ul id="announce-modes">'
364
        if sub and sub.email:
365
            ' <li>'
366
            '<span id="par_mail">%s</span>' % _('Email (currently subscribed)')
367
            ' <a href="email_unsubscribe" rel="popup">%s</a></li>' % _('Unsubscribe')
368
        else:
369
            ' <li><a href="email" id="par_mail" rel="popup">%s</a></li>' % _('Email')
370
        if sub and sub.sms:
371
            ' <li>'
372
            if sub.enabled_sms:
373
                '<span id="par_sms">%s</span>' % _('SMS %s (currently subscribed)') % sub.sms
374
            else:
375
                '<span id="par_sms">%s</span>' % _('SMS %s (currently not confirmed)') % sub.sms
376
                ' <a href="smsconfirm" rel="popup">%s</a> ' % _('Confirmation')
377
            ' <a href="sms_unsubscribe" rel="popup">%s</a></li>' % _('Unsubscribe')
378
        elif get_cfg('sms', {}).get('mode', 'none') != 'none':
379
            ' <li><a href="sms" id="par_sms">%s</a></li>' % _('SMS')
380
        ' <li><a class="feed-link" href="atom" id="par_rss">%s</a>' % _('Feed')
381
        '</ul>'
382

    
383

    
384
    def email [html] (self):
385
        get_response().breadcrumb.append(('email', _('Email Subscription')))
386
        template.html_top(_('Receiving Announces by email'))
387

    
388
        form = Form(enctype='multipart/form-data')
389
        if get_request().user:
390
            if get_request().user.email:
391
                '<p>'
392
                _('You are logged in and your email is %s, ok to subscribe ?') % \
393
                    get_request().user.email
394
                '</p>'
395
                form.add_submit('submit', _('Subscribe'))
396
            else:
397
                '<p>'
398
                _("You are logged in but there is no email address in your profile.")
399
                '</p>'
400
                form.add(EmailWidget, 'email', title = _('Email'), required = True)
401
                form.add_submit('submit', _('Subscribe'))
402
                form.add_submit('submit-remember', _('Subscribe and add this email to my profile'))
403
        else:
404
            '<p>'
405
            _('FIXME will only be used for this purpose etc.')
406
            '</p>'
407
            form.add(EmailWidget, 'email', title = _('Email'), required = True)
408
            form.add_submit('submit', _('Subscribe'))
409
        
410
        form.add_submit('cancel', _('Cancel'))
411

    
412
        if form.get_submit() == 'cancel':
413
            return redirect('subscribe')
414

    
415
        if form.is_submitted() and not form.has_errors():
416
            s = self.email_submit(form)
417
            if s is not False:
418
                return s
419
        else:
420
            form.render()
421

    
422
    def email_submit(self, form):
423
        sub = self._get_announce_subscription()
424
        if not sub:
425
            sub = AnnounceSubscription()
426

    
427
        if get_request().user:
428
            sub.user_id = get_request().user.id
429

    
430
        if form.get_widget('email'):
431
            sub.email = form.get_widget('email').parse()
432
        elif get_request().user.email:
433
            sub.email = get_request().user.email
434

    
435
        if not get_request().user:
436
            sub.enabled = False
437

    
438
        sub.store()
439

    
440
        if get_request().user:
441
            def email_submit_ok [html] ():
442
                root_url = get_publisher().get_root_url()
443
                '<p>'
444
                _('You have been subscribed to the announces.')
445
                '</p>'
446
                if not get_response().iframe_mode:
447
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
448

    
449
            return email_submit_ok()
450

    
451
        # asking email confirmation before subscribing someone
452
        token = Token(3 * 86400)
453
        token.type = 'announces-subscription-confirmation'
454
        token.subscription_id = sub.id
455
        token.store()
456
        data = {
457
            'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id,
458
            'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id,
459
            'time': misc.localstrftime(time.localtime(token.expiration)),
460
        }
461

    
462
        emails.custom_ezt_email('announces-subscription-confirmation',
463
                data, sub.email, exclude_current_user = False)
464

    
465
        def email_submit_ok [html] ():
466
            root_url = get_publisher().get_root_url()
467
            '<p>'
468
            _('You have been sent an email for confirmation')
469
            '</p>'
470
            if not get_response().iframe_mode:
471
                '<a href="%s">%s</a>' % (root_url, _('Back Home'))
472

    
473
        return email_submit_ok()
474

    
475
    def emailconfirm(self):
476
        tokenv = get_request().form.get('t')
477
        action = get_request().form.get('a')
478

    
479
        root_url = get_publisher().get_root_url()
480

    
481
        try:
482
            token = Token.get(tokenv)
483
        except KeyError:
484
            return template.error_page(
485
                    _('The token you submitted does not exist, has expired, or has been cancelled.'),
486
                    continue_to = (root_url, _('home page')))
487

    
488
        if token.type != 'announces-subscription-confirmation':
489
            return template.error_page(
490
                    _('The token you submitted is not appropriate for the requested task.'),
491
                    continue_to = (root_url, _('home page')))
492

    
493
        sub = AnnounceSubscription.get(token.subscription_id)
494

    
495
        if action == 'cxl':
496
            def cancel [html]():
497
                root_url = get_publisher().get_root_url()
498
                template.html_top(_('Email Subscription'))
499
                '<h1>%s</h1>' % _('Request Cancelled')
500
                '<p>%s</p>' % _('The request for subscription has been cancelled.')
501
                '<p>'
502
                htmltext(_('Continue to <a href="%s">home page</a>') % root_url)
503
                '</p>'
504
            token.remove_self()
505
            sub.remove_self()
506
            return cancel()
507

    
508
        if action == 'cfm':
509
            token.remove_self()
510
            sub.enabled = True
511
            sub.store()
512
            def sub [html] ():
513
                root_url = get_publisher().get_root_url()
514
                template.html_top(_('Email Subscription'))
515
                '<h1>%s</h1>' % _('Subscription Confirmation')
516
                '<p>%s</p>' % _('Your subscription to announces is now effective.')
517
                '<p>'
518
                htmltext(_('Continue to <a href="%s">home page</a>') % root_url)
519
                '</p>'
520
            return sub()
521

    
522

    
523
    def atom [plain] (self):
524
        response = get_response()
525
        response.set_content_type('application/atom+xml')
526

    
527
        from pyatom import pyatom
528
        xmldoc = pyatom.XMLDoc()
529
        feed = pyatom.Feed()
530
        xmldoc.root_element = feed
531
        feed.title = get_cfg('misc', {}).get('sitename') or 'Au Quotidien'
532
        feed.id = get_request().get_url()
533

    
534
        author_email = get_cfg('emails', {}).get('reply_to')
535
        if not author_email:
536
            author_email = get_cfg('emails', {}).get('from')
537
        if author_email:
538
            feed.authors.append(pyatom.Author(author_email))
539

    
540
        announces = Announce.get_published_announces()
541

    
542
        if announces and announces[0].modification_time:
543
            feed.updated = misc.format_time(announces[0].modification_time,
544
                        '%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ',
545
                        gmtime = True)
546
        feed.links.append(pyatom.Link(get_request().get_url(1) + '/'))
547

    
548
        for item in announces:
549
            entry = item.get_atom_entry()
550
            if entry:
551
                feed.entries.append(entry)
552

    
553
        str(feed)
554

    
555
    def email_unsubscribe [html] (self):
556
        sub = self._get_announce_subscription()
557

    
558
        form = Form(enctype='multipart/form-data')
559
        if not sub:
560
            form.add(EmailWidget, 'email', title = _('Email'), required = True)
561

    
562
        form.add_submit('submit', _('Unsubscribe'))
563
        form.add_submit('cancel', _('Cancel'))
564

    
565
        if form.get_submit() == 'cancel':
566
            return redirect('..')
567

    
568
        get_response().breadcrumb.append(('email', _('Email Unsubscription')))
569
        template.html_top()
570

    
571
        if form.is_submitted() and not form.has_errors():
572
            if sub:
573
                sub.remove("email")
574
            else:
575
                email = form.get_widget('email').parse()
576
                for s in AnnounceSubscription.select():
577
                    if s.email == email:
578
                        s.remove("email")
579

    
580
            def email_unsub_ok [html] ():
581
                root_url = get_publisher().get_root_url()
582
                '<p>'
583
                _('You have been unsubscribed from announces')
584
                '</p>'
585
                if not get_response().iframe_mode:
586
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
587

    
588
            return email_unsub_ok()
589

    
590
        else:
591
            '<p>'
592
            _('Do you want to stop receiving announces by email?')
593
            '</p>'
594
            form.render()
595

    
596
    def _q_lookup(self, component):
597
        try:
598
            announce = Announce.get(component)
599
        except KeyError:
600
            raise errors.TraversalError()
601

    
602
        if announce.hidden:
603
            raise errors.TraversalError()
604

    
605
        get_response().breadcrumb.append((str(announce.id), announce.title))
606
        return AnnounceDirectory(announce)
607

    
608
OldRegisterDirectory = wcs.root.RegisterDirectory
609

    
610
class AlternateRegisterDirectory(OldRegisterDirectory):
611
    def _q_traverse(self, path):
612
        get_response().filter['bigdiv'] = 'new_member'
613
        return OldRegisterDirectory._q_traverse(self, path)
614

    
615
    def _q_index [html] (self):
616
        get_logger().info('register')
617
        ident_methods = get_cfg('identification', {}).get('methods', [])
618

    
619
        if len(ident_methods) == 0:
620
            idps = get_cfg('idp', {})
621
            if len(idps) == 0:
622
                return template.error_page(_('Authentication subsystem is not yet configured.'))
623
            ident_methods = ['idp'] # fallback to old behaviour; liberty.
624

    
625
        if len(ident_methods) == 1:
626
            method = ident_methods[0]
627
        else:
628
            method = 'password'
629

    
630
        return qommon.ident.register(method)
631

    
632
OldLoginDirectory = wcs.root.LoginDirectory
633

    
634
class AlternateLoginDirectory(OldLoginDirectory):
635
    def _q_traverse(self, path):
636
        get_response().filter['bigdiv'] = 'member'
637
        return OldLoginDirectory._q_traverse(self, path)
638

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

    
643
        if len(ident_methods) > 1 and 'idp' in ident_methods:
644
            # if there is more than one identification method, and there is a
645
            # possibility of SSO, if we got there as a consequence of an access
646
            # unauthorized url on admin/ or backoffice/, then idp auth method
647
            # is chosen forcefully.
648
            after_url = get_session().after_url
649
            if after_url:
650
                root_url = get_publisher().get_root_url()
651
                after_path = urlparse.urlparse(after_url)[2]
652
                after_path = after_path[len(root_url):]
653
                if after_path.startswith(str('admin')) or \
654
                        after_path.startswith(str('backoffice')):
655
                    ident_methods = ['idp']
656

    
657
        # don't display authentication system choice
658
        if len(ident_methods) == 1:
659
            method = ident_methods[0]
660
            try:
661
                return qommon.ident.login(method)
662
            except KeyError:
663
                get_logger().error('failed to login with method %s' % method)
664
                return errors.TraversalError()
665

    
666
        if sorted(ident_methods) == ['idp', 'password']:
667
            get_response().breadcrumb.append(('login', _('Login')))
668
            identities_cfg = get_cfg('identities', {})
669
            form = Form(enctype = 'multipart/form-data', id = 'login-form', use_tokens = False)
670
            if identities_cfg.get('email-as-username', False):
671
                form.add(StringWidget, 'username', title = _('Email'), size=25, required=True)
672
            else:
673
                form.add(StringWidget, 'username', title = _('Username'), size=25, required=True)
674
            form.add(PasswordWidget, 'password', title = _('Password'), size=25, required=True)
675
            form.add_submit('submit', _('Connect'))
676
            if form.is_submitted() and not form.has_errors():
677
                tmp = qommon.ident.password.MethodDirectory().login_submit(form)
678
                if not form.has_errors():
679
                    return tmp
680

    
681
            '<div id="login-password">'
682
            get_session().display_message()
683
            form.render()
684

    
685
            base_url = get_publisher().get_root_url()
686
            '<p><a href="%sident/password/forgotten">%s</a></p>' % (
687
                    base_url, _('Forgotten password ?'))
688

    
689
            '</div>'
690

    
691
            # XXX: this part only supports a single IdP
692
            '<div id="login-sso">'
693
            TextsDirectory.get_html_text('aq-sso-text')
694
            form = Form(enctype='multipart/form-data',
695
                    action = '%sident/idp/login' % base_url)
696
            form.add_hidden('method', 'idp')
697
            for kidp, idp in get_cfg('idp', {}).items():
698
                p = lasso.Provider(lasso.PROVIDER_ROLE_IDP,
699
                        misc.get_abs_path(idp['metadata']),
700
                        misc.get_abs_path(idp.get('publickey')), None)
701
                form.add_hidden('idp', p.providerId)
702
                break
703
            form.add_submit('submit', _('Connect'))
704
            
705
            form.render()
706
            '</div>'
707

    
708
            get_request().environ['REQUEST_METHOD'] = 'GET'
709

    
710
            """<script type="text/javascript">
711
              document.getElementById('login-form')['username'].focus();
712
            </script>"""
713

    
714
        else:
715
            return OldLoginDirectory._q_index(self)
716

    
717

    
718
OldIdentDirectory = wcs.root.IdentDirectory
719
class AlternateIdentDirectory(OldIdentDirectory):
720
    def _q_traverse(self, path):
721
        get_response().filter['bigdiv'] = 'member'
722
        return OldIdentDirectory._q_traverse(self, path)
723

    
724

    
725
class AlternateRootDirectory(OldRootDirectory):
726
    _q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout',
727
            'liberty', 'token', 'saml', 'register', 'ident', 'afterjobs',
728
            ('informations-editeur', 'informations_editeur'), 'index2',
729
            ('announces', 'announces_dir'),
730
            'accessibility', 'contact', 'help',
731
            'myspace', 'services', 'agenda', 'categories', 'user',
732
            ('tmp-upload', 'tmp_upload'), 'json', '__version__',
733
            'themes', 'pages', 'payment', 'invoices', 'accesscode', 'roles',
734
            'msp']
735

    
736
    admin = admin.AdminRootDirectory()
737
    announces_dir = AnnouncesDirectory()
738
    register = AlternateRegisterDirectory()
739
    login = AlternateLoginDirectory()
740
    ident = AlternateIdentDirectory()
741
    myspace = MyspaceDirectory()
742
    agenda = AgendaDirectory()
743
    saml = Saml2Directory()
744
    payment = PublicPaymentDirectory()
745
    invoices = InvoicesDirectory()
746
    msp = msp_ui.MSPDirectory()
747

    
748
    def get_substitution_variables(self):
749
        d = {}
750
        def print_links(fd):
751
            fd.write(str(self.links()))
752
        d['links'] = print_links
753
        return d
754

    
755
    def _q_traverse(self, path):
756
        if get_publisher().has_site_option('drupal'):
757
            drupal.try_auth()
758
        if get_publisher().has_site_option('ezldap'):
759
            ezldap_ui.try_auth(self)
760

    
761
        session = get_session()
762
        if session:
763
            get_request().user = session.get_user()
764
        else:
765
            get_request().user = None
766

    
767
        get_publisher().substitutions.feed(get_request().user)
768

    
769
        response = get_response()
770
        if not hasattr(response, 'filter'):
771
            response.filter = {}
772

    
773
        response.filter['gauche'] = self.box_side(path)
774
        response.filter['keywords'] = template.get_current_theme().get('keywords')
775
        get_publisher().substitutions.feed(self)
776

    
777
        response.breadcrumb = [ ('', _('Home')) ]
778

    
779
        if not self.admin:
780
            self.admin = get_publisher().admin_directory_class()
781

    
782
        if not self.backoffice:
783
            self.backoffice = get_publisher().backoffice_directory_class()
784

    
785
        try:
786
            return Directory._q_traverse(self, path)
787
        except errors.TraversalError, e:
788
            try:
789
                f = FormDef.get_by_urlname(path[0])
790
            except KeyError:
791
                pass
792
            else:
793
                base_url = get_publisher().get_root_url()
794

    
795
                uri_rest = get_request().environ.get('REQUEST_URI')
796
                if not uri_rest:
797
                    uri_rest = get_request().get_path()
798
                if uri_rest.startswith(base_url):
799
                    uri_rest = uri_rest[len(base_url):]
800
                elif uri_rest.startswith('/'):
801
                    # dirty hack, ezldap reverseproxy uses a fake base_url
802
                    uri_rest = uri_rest[1:]
803
                if f.category_id:
804
                    return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest))
805

    
806
            raise e
807

    
808

    
809
    def _q_lookup(self, component):
810
        if component == 'qo':
811
            dirname = os.path.join(get_publisher().data_dir, 'qommon')
812
            return StaticDirectory(dirname, follow_symlinks = True)
813

    
814
        if component == 'aq':
815
            dirname = os.path.join(get_publisher().data_dir, 'qommon', 'auquotidien')
816
            return StaticDirectory(dirname, follow_symlinks = True)
817

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

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

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

    
842
        return None
843

    
844
    def json(self):
845
        return FormsRootDirectory().json()
846

    
847
    def categories(self):
848
        return FormsRootDirectory().categories()
849

    
850
    def _q_index [html] (self):
851
        if get_request().is_json():
852
            return FormsRootDirectory().json()
853

    
854
        root_url = get_publisher().get_root_url()
855
        if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump:
856
            return redirect('%smyspace/new' % root_url)
857

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

    
862
        template.html_top()
863
        get_response().filter['is_index'] = True
864

    
865
        if not 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []):
866
            t = TextsDirectory.get_html_text('aq-home-page')
867
            if not t:
868
                if get_request().user:
869
                    t = TextsDirectory.get_html_text('welcome-logged')
870
                else:
871
                    t = TextsDirectory.get_html_text('welcome-unlogged')
872
            if t:
873
                '<div id="home-page-intro">'
874
                t
875
                '</div>'
876

    
877
        '<div id="centre">'
878
        self.box_services(position='1st')
879
        '</div>'
880
        '<div id="droite">'
881
        self.myspace_snippet()
882
        self.box_services(position='2nd')
883
        self.consultations()
884
        self.announces()
885
        '</div>'
886

    
887
        user = get_request().user
888
        if user and user.can_go_in_backoffice():
889
            get_response().filter['backoffice'] = True
890

    
891
    def services [html] (self):
892
        template.html_top()
893
        get_response().filter['bigdiv'] = 'rub_service'
894
        self.box_services(level = 2)
895

    
896
    def box_services [html] (self, level=3, position=None):
897
        ## Services
898
        if get_request().user and get_request().user.roles:
899
            accepted_roles = get_request().user.roles
900
        else:
901
            accepted_roles = []
902

    
903
        cats = Category.select(order_by = 'name')
904
        cats = [x for x in cats if x.url_name != 'consultations']
905
        Category.sort_by_position(cats)
906

    
907
        all_formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection,
908
                order_by = 'name')
909

    
910
        if position:
911
            t = self.display_list_of_formdefs(
912
                            [x for x in cats if x.get_homepage_position() == position],
913
                            all_formdefs, accepted_roles)
914
        else:
915
            t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles)
916

    
917
        if not t:
918
            return
919

    
920
        if position == '2nd':
921
            '<div id="services-2nd">'
922
        else:
923
            '<div id="services">'
924
        if level == 2:
925
            '<h2>%s</h2>' % _('Services')
926
        else:
927
            '<h3>%s</h3>' % _('Services')
928

    
929
        if get_response().iframe_mode:
930
            if get_request().user:
931
                message = TextsDirectory.get_html_text('welcome-logged')
932
            else:
933
                message = TextsDirectory.get_html_text('welcome-unlogged')
934

    
935
            if message:
936
                '<div id="welcome-message">'
937
                message
938
                '</div>'
939
        elif 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []):
940
            homepage_text = TextsDirectory.get_html_text('aq-home-page')
941
            if homepage_text:
942
                '<div id="home-page-intro">'
943
                homepage_text
944
                '</div>'
945

    
946
        '<ul>'
947
        t
948
        '</ul>'
949

    
950
        '</div>'
951

    
952
    def display_list_of_formdefs [html] (self, cats, all_formdefs, accepted_roles):
953
        for category in cats:
954
            if category.url_name == 'consultations':
955
                self.consultations_category = category
956
                continue
957
            formdefs = [x for x in all_formdefs if x.category_id == category.id]
958
            formdefs_advertise = []
959

    
960
            for formdef in formdefs[:]:
961
                if formdef.is_disabled(): # is a redirection
962
                    continue
963
                if not formdef.roles:
964
                    continue
965
                if not get_request().user:
966
                    if formdef.always_advertise:
967
                        formdefs_advertise.append(formdef)
968
                    formdefs.remove(formdef)
969
                    continue
970
                if logged_users_role().id in formdef.roles:
971
                    continue
972
                for q in accepted_roles:
973
                    if q in formdef.roles:
974
                        break
975
                else:
976
                    if formdef.always_advertise:
977
                        formdefs_advertise.append(formdef)
978
                    formdefs.remove(formdef)
979

    
980
            if not formdefs and not formdefs_advertise:
981
                continue
982

    
983
            '<li>'
984
            '<strong>'
985
            '<a href="%s/">' % category.url_name
986
            category.name
987
            '</a></strong>\n'
988
            if category.description:
989
                if category.description[0] == '<':
990
                    htmltext(category.description)
991
                else:
992
                    '<p>'
993
                    category.description
994
                    '</p>'
995
            '<ul>'
996
            limit = category.get_limit()
997
            for formdef in formdefs[:limit]:
998
                '<li>'
999
                '<a href="%s/%s/">%s</a>' % (category.url_name, formdef.url_name, formdef.name)
1000
                '</li>\n'
1001
            if len(formdefs) < limit:
1002
                for formdef in formdefs_advertise[:limit-len(formdefs)]:
1003
                    '<li>'
1004
                    '<a href="%s/%s/">%s</a>' % (category.url_name, formdef.url_name, formdef.name)
1005
                    ' (%s)' % _('authentication required')
1006
                    '</li>\n'
1007
            if (len(formdefs)+len(formdefs_advertise)) > limit:
1008
                '<li class="all-forms"><a href="%s/" title="%s">%s</a></li>' % (category.url_name,
1009
                        _('Access to all forms of the "%s" category') % category.name,
1010
                        _('Access to all forms in this category'))
1011
            '</ul>'
1012
            '</li>\n'
1013

    
1014
    def consultations [html] (self):
1015
        cats = [x for x in Category.select() if x.url_name == 'consultations']
1016
        if not cats:
1017
            return
1018
        consultations_category = cats[0]
1019
        formdefs = FormDef.select(lambda x: (
1020
                    x.category_id == consultations_category.id and
1021
                        (not x.is_disabled() or x.disabled_redirection)),
1022
                    order_by = 'name')
1023
        if not formdefs:
1024
            return
1025
        ## Consultations
1026
        '<div id="consultations">'
1027
        '<h3>%s</h3>' % _('Consultations')
1028
        if consultations_category.description:
1029
            if consultations_category.description[0] == '<':
1030
                htmltext(consultations_category.description)
1031
            else:
1032
                '<p>'
1033
                consultations_category.description
1034
                '</p>'
1035
        '<ul>'
1036
        for formdef in formdefs:
1037
            '<li>'
1038
            '<a href="%s/%s/">%s</a>' % (consultations_category.url_name,
1039
                formdef.url_name, formdef.name)
1040
            '</li>'
1041
        '</ul>'
1042
        '</div>'
1043

    
1044
    def box_side [html] (self, path):
1045
        '<div id="sidebox">'
1046
        root_url = get_publisher().get_root_url()
1047

    
1048
        if self.has_anonymous_access_codes():
1049
            '<form id="follow-form" action="%saccesscode">' % root_url
1050
            '<h3>%s</h3>' % _('Tracking')
1051
            '<label>%s</label> ' % _('Code:')
1052
            '<input name="code" size="10"/>'
1053
            '</form>'
1054

    
1055
        self.links()
1056

    
1057
        cats = Category.select(order_by = 'name')
1058
        cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side']
1059
        Category.sort_by_position(cats)
1060
        if cats:
1061
            '<div id="side-services">'
1062
            '<h3>%s</h3>' % _('Services')
1063
            '<ul>'
1064
            for cat in cats:
1065
                '<li><a href="%s/">%s</a></li>' % (cat.url_name, cat.name)
1066
            '</ul>'
1067
            '</div>'
1068

    
1069
        if Event.keys(): # if there are events, add a link to the agenda
1070
            tags = get_cfg('misc', {}).get('event_tags')
1071
            if not tags:
1072
                tags = get_default_event_tags()
1073
            '<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>' % (root_url, _('Agenda'))
1074

    
1075
            if path and path[0] == 'agenda':
1076
                '<p class="tags">'
1077
                for tag in tags:
1078
                    '<a href="%sagenda/tag/%s">%s</a> ' % (root_url, tag, tag)
1079
                '</p>'
1080
                self.agenda.display_remote_calendars()
1081

    
1082
                '<p>'
1083
                '  <a href="%sagenda/filter">%s</a>' % (root_url, _('Advanced Filter'))
1084
                '</p>'
1085

    
1086
        '</div>'
1087

    
1088
    def has_anonymous_access_codes(self):
1089
        for workflow in Workflow.select():
1090
            for wfstatus in workflow.possible_status:
1091
                for wfitem in wfstatus.items:
1092
                    if wfitem.key == 'create-anonymous-access-code':
1093
                        return True
1094
        return False
1095

    
1096
    def accesscode(self):
1097
        code = get_request().form.get('code')
1098
        if not code:
1099
            return redirect(get_publisher().get_root_url())
1100
        try:
1101
            token = Token.get(code)
1102
        except KeyError:
1103
            return redirect(get_publisher().get_root_url())
1104
        if token.type != 'anonymous-access-code':
1105
            return redirect(get_publisher().get_root_url())
1106
        formdef_urlname, formdata_id = token.formdata_reference
1107
        try:
1108
            formdata = FormDef.get_by_urlname(formdef_urlname).data_class().get(formdata_id)
1109
        except KeyError:
1110
            return redirect(get_publisher().get_root_url())
1111
        session = get_session()
1112
        if not hasattr(session, '_wf_anonymous_access_authorized'):
1113
            session._wf_anonymous_access_authorized = []
1114
        session._wf_anonymous_access_authorized.append(formdata.get_url())
1115
        return redirect(formdata.get_url() + 'access/')
1116

    
1117
    def links [html] (self):
1118
        links = Link.select()
1119
        if not links:
1120
            return
1121

    
1122
        Link.sort_by_position(links)
1123

    
1124
        '<div id="links">'
1125
        if links[0].url:
1126
            # first link has an URL, so it's not a title, so we display a
1127
            # generic title
1128
            '<h3>%s</h3>' % _('Useful links')
1129
        has_ul = False
1130
        vars = get_publisher().substitutions.get_context_variables()
1131
        for link in links:
1132
            if not link.url:
1133
                # acting title
1134
                if has_ul:
1135
                    '</ul>'
1136
                '<h3>%s</h3>' % link.title
1137
                '<ul>'
1138
                has_ul = True
1139
            else:
1140
                if not has_ul:
1141
                    '<ul>'
1142
                    has_ul = True
1143
                '<li><a href="%s">%s</a></li>' % (get_variadic_url(link.url, vars), link.title)
1144
        if has_ul:
1145
            '</ul>'
1146
        '</div>'
1147

    
1148

    
1149
    def announces [html] (self):
1150
        announces = Announce.get_published_announces()
1151
        if not announces:
1152
            return
1153

    
1154
        '<div id="announces">'
1155
        '<h3>%s</h3>' % _('Announces to citizens')
1156
        for item in announces[:3]:
1157
            '<div class="announce-item">'
1158
            '<h4>'
1159
            if item.publication_time:
1160
                time.strftime(misc.date_format(), item.publication_time)
1161
                ' - '
1162
            item.title
1163
            '</h4>'
1164
            '<p>'
1165
            item.text
1166
            '</p>'
1167
            '</div>'
1168

    
1169
        '<ul id="announces-links">'
1170
        '<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces')
1171
        '<li><a href="announces/">%s</a></li>' % _('Previous Announces')
1172
        '</ul>'
1173
        '</div>'
1174

    
1175
    def myspace_snippet [html] (self):
1176
        '<div id="myspace">'
1177
        '<h3>%s</h3>' % _('My Space')
1178
        '<ul>'
1179
        if get_request().user and not get_request().user.anonymous:
1180
            '  <li><a href="myspace/" id="member">%s</a></li>' % _('Access to your personal space')
1181
            '  <li><a href="logout" id="logout">%s</a></li>' % _('Logout')
1182
        else:
1183
            '  <li><a href="register/" id="inscr">%s</a></li>' % _('Registration')
1184
            '  <li><a href="login/" id="login">%s</a></li>' % _('Login')
1185
        '</ul>'
1186
        '</div>'
1187

    
1188

    
1189
    def page_view [html] (self, key, title, urlname = None):
1190
        if not urlname:
1191
            urlname = key[3:].replace(str('_'), str('-'))
1192
        get_response().breadcrumb.append((urlname, title))
1193
        template.html_top(title)
1194
        '<div class="article">'
1195
        htmltext(TextsDirectory.get_html_text(key))
1196
        '</div>'
1197

    
1198
    def informations_editeur [html] (self):
1199
        get_response().filter['bigdiv'] = 'info'
1200
        return self.page_view('aq-editor-info', _('Editor Informations'),
1201
                urlname = 'informations_editeur')
1202

    
1203
    def accessibility(self):
1204
        get_response().filter['bigdiv'] = 'accessibility'
1205
        return self.page_view('aq-accessibility', _('Accessibility Statement'))
1206

    
1207
    def contact(self):
1208
        get_response().filter['bigdiv'] = 'contact'
1209
        return self.page_view('aq-contact', _('Contact'))
1210

    
1211
    def help(self):
1212
        get_response().filter['bigdiv'] = 'help'
1213
        return self.page_view('aq-help', _('Help'))
1214

    
1215

    
1216
from qommon.publisher import get_publisher_class
1217
get_publisher_class().root_directory_class = AlternateRootDirectory
1218
get_publisher_class().after_login_url = 'myspace/'
1219
get_publisher_class().use_sms_feature = True
1220

    
1221
# help links
1222
get_publisher_class().backoffice_help_url = {
1223
    'fr': 'https://doc.entrouvert.org/au-quotidien/stable/guide-gestionnaire.html',
1224
}
1225
get_publisher_class().admin_help_url = {
1226
    'fr': 'https://doc.entrouvert.org/auquotidien/dev/',
1227
}
1228

    
1229

    
1230
EmailsDirectory.register('announces-subscription-confirmation',
1231
        N_('Confirmation of Announces Subscription'),
1232
        N_('Available variables: change_url, cancel_url, time, sitename'),
1233
        default_subject = N_('Announce Subscription Request'),
1234
        default_body = N_("""\
1235
You have (or someone impersonating you has) requested to subscribe to
1236
announces from [sitename].  To confirm this request, visit the
1237
following link:
1238

    
1239
[confirm_url]
1240

    
1241
If you are not the person who made this request, or you wish to cancel
1242
this request, visit the following link:
1243

    
1244
[cancel_url]
1245

    
1246
If you do nothing, the request will lapse after 3 days (precisely on
1247
[time]).
1248
"""))
1249

    
1250

    
1251
TextsDirectory.register('aq-announces-subscription',
1252
        N_('Text on announces subscription page'),
1253
        default = N_('''\
1254
<p>
1255
FIXME
1256
'</p>'''))
1257

    
1258
TextsDirectory.register('aq-sms-demo',
1259
        N_('Text when subscribing to announces SMS and configured as demo'),
1260
        default = N_('''
1261
<p>
1262
Receiving announces by SMS is not possible in this demo
1263
</p>'''))
1264

    
1265
TextsDirectory.register('aq-editor-info', N_('Editor Informations'))
1266
TextsDirectory.register('aq-accessibility', N_('Accessibility Statement'))
1267
TextsDirectory.register('aq-contact', N_('Contact Information'))
1268
TextsDirectory.register('aq-help', N_('Help'))
1269
TextsDirectory.register('aq-sso-text',  N_('Connecting with Identity Provider'),
1270
        default = N_('''<h3>Connecting with Identity Provider</h3>
1271
<p>You can also use your identity provider to connect.
1272
</p>'''))
1273

    
1274
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True)
(29-29/33)