Projet

Général

Profil

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

root / extra / modules / root.ptl @ ea8c87c3

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> %s</li>' % (base_url,
90
                    f.formdef.category.url_name,
91
                    f.formdef.url_name, f.id, f.formdef.name,
92
                    misc.localstrftime(f.receipt_time))
93
            '</ul>'
94

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

    
127

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

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

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

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

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

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

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

    
152

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

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

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

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

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

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

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

    
201

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

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

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

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

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

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

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

    
261
        if mobile:
262
            sub.sms = mobile
263

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

    
267
        sub.store()
268

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

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

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

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

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

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

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

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

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

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

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

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

    
347
            return sms_unsub_ok()
348

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

    
355

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

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

    
362
        sub = self._get_announce_subscription()
363

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

    
384

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

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

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

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

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

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

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

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

    
439
        sub.store()
440

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

    
450
            return email_submit_ok()
451

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

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

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

    
474
        return email_submit_ok()
475

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

    
480
        root_url = get_publisher().get_root_url()
481

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

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

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

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

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

    
523

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

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

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

    
541
        announces = Announce.get_published_announces()
542

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

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

    
554
        str(feed)
555

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

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

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

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

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

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

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

    
589
            return email_unsub_ok()
590

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

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

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

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

    
609
OldRegisterDirectory = wcs.root.RegisterDirectory
610

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

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

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

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

    
631
        return qommon.ident.register(method)
632

    
633
OldLoginDirectory = wcs.root.LoginDirectory
634

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

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

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

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

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

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

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

    
690
            '</div>'
691

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

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

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

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

    
718

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

    
725

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

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

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

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

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

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

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

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

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

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

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

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

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

    
807
            raise e
808

    
809

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

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

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

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

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

    
843
        return None
844

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
918
        if not t:
919
            return
920

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

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

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

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

    
951
        '</div>'
952

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

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

    
981
            if not formdefs and not formdefs_advertise:
982
                continue
983

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

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

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

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

    
1056
        self.links()
1057

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

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

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

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

    
1087
        '</div>'
1088

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

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

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

    
1123
        Link.sort_by_position(links)
1124

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

    
1149

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

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

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

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

    
1189

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

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

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

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

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

    
1216

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

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

    
1230

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

    
1240
[confirm_url]
1241

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

    
1245
[cancel_url]
1246

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

    
1251

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

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

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

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