Project

General

Profile

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

root / extra / modules / root.ptl @ e458e2d4

1
from quixote import get_publisher, get_response, get_request, redirect, get_session
2
from quixote.directory import Directory
3
from quixote.html import htmltext
4
from quixote.util import StaticDirectory
5

    
6
import os
7
import re
8
import string
9
import urlparse
10

    
11
try:
12
    import lasso
13
except ImportError:
14
    pass
15

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

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

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

    
42
import admin
43

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

    
47
from saml2 import Saml2Directory
48

    
49
OldRootDirectory = wcs.root.RootDirectory
50

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

    
54
import drupal
55
import ezldap_ui
56

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

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

    
71

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

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

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

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

    
119

    
120
class AnnounceDirectory(Directory):
121
    _q_exports = ['', 'edit', 'delete', 'email']
122

    
123
    def __init__(self, announce):
124
        self.announce = announce
125

    
126
    def _q_index [html] (self):
127
        template.html_top(_('Announces to citizens'))
128

    
129
        if self.announce.publication_time:
130
            date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time)
131
        else:
132
            date_heading = ''
133

    
134
        '<h3>%s%s</h3>' % (date_heading, self.announce.title)
135

    
136
        '<p>'
137
        self.announce.text
138
        '</p>'
139

    
140
        '<p>'
141
        '<a href="../">%s</a>' % _('Back')
142
        '</p>'
143

    
144

    
145
class AnnouncesDirectory(Directory):
146
    _q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm',
147
            'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist']
148

    
149
    
150
    def _q_traverse(self, path):
151
        get_response().breadcrumb.append(('announces/', _('Announces')))
152
        return Directory._q_traverse(self, path)
153

    
154
    def _q_index [html] (self):
155
        template.html_top(_('Announces to citizens'))
156
        self.announces_list()
157
        '<ul id="announces-links">'
158
        '<li><a href="subscribe">%s</a></li>' % _('Receiving those Announces')
159
        '</ul>'
160

    
161
    def _get_announce_subscription(self):
162
        """ """
163
        sub = None
164
        if get_request().user:
165
            subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id)
166
            if subs:
167
                sub = subs[0]
168
        return sub
169

    
170
    def rawlist [html] (self):
171
        self.announces_list()
172
        get_response().filter = None
173

    
174
    def announces_list [html] (self):
175
        announces = Announce.get_published_announces()
176
        if not announces:
177
            raise errors.TraversalError()
178

    
179
        # XXX: will need pagination someday
180
        for item in announces:
181
            '<div class="announce-item">\n'
182
            '<h4>'
183
            if item.publication_time:
184
                time.strftime(misc.date_format(), item.publication_time)
185
                ' - '
186
            item.title
187
            '</h4>\n'
188
            '<p>\n'
189
            item.text
190
            '\n</p>\n'
191
            '</div>\n'
192

    
193

    
194
    def sms [html] (self):
195
        sms_mode = get_cfg('sms', {}).get('mode', 'none')
196

    
197
        if sms_mode == 'none':
198
            raise errors.TraversalError()
199

    
200
        get_response().breadcrumb.append(('sms', _('SMS')))
201
        template.html_top(_('Receiving announces by SMS'))
202

    
203
        if sms_mode == 'demo':
204
            TextsDirectory.get_html_text('aq-sms-demo')
205
        else:
206
            announces_cfg = get_cfg('announces',{})
207
            mobile_mask = announces_cfg.get('mobile_mask')
208
            if mobile_mask:
209
                mobile_mask = ' (' + mobile_mask + ')'
210
            else:
211
                mobile_mask = ''
212
            form = Form(enctype='multipart/form-data')
213
            form.add(StringWidget, 'mobile', title = _('Mobile number %s') % mobile_mask, size=12, required=True)
214
            form.add_submit('submit', _('Subscribe'))
215
            form.add_submit('cancel', _('Cancel'))
216

    
217
            if form.get_submit() == 'cancel':
218
                return redirect('subscribe')
219

    
220
            if form.is_submitted() and not form.has_errors():
221
                s = self.sms_submit(form)
222
                if s == False:
223
                    form.render()
224
                else:
225
                    return redirect("smsconfirm")
226
            else:
227
                form.render()
228

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

    
253
        if mobile:
254
            sub.sms = mobile
255

    
256
        if not get_request().user:
257
            sub.enabled = False
258

    
259
        sub.store()
260

    
261
        # Asking sms confirmation
262
        token = Token(3 * 86400, 4, string.digits)
263
        token.type = 'announces-subscription-confirmation'
264
        token.subscription_id = sub.id
265
        token.store()
266

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

    
280
    def smsconfirm [html] (self):
281
        template.html_top(_('Receiving announces by SMS confirmation'))
282
        "<p>%s</p>" % _("You will receive a confirmation code by SMS.")
283
        form = Form(enctype='multipart/form-data')
284
        form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True)
285
        form.add_submit('submit', _('Subscribe'))
286
        form.add_submit('cancel', _('Cancel'))
287

    
288
        if form.get_submit() == 'cancel':
289
            return redirect('..')
290

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

    
311
    def sms_unsubscribe [html] (self):
312
        sub = self._get_announce_subscription()
313

    
314
        form = Form(enctype='multipart/form-data')
315
        if not sub:
316
            return redirect('..')
317

    
318
        form.add_submit('submit', _('Unsubscribe'))
319
        form.add_submit('cancel', _('Cancel'))
320

    
321
        if form.get_submit() == 'cancel':
322
            return redirect('..')
323

    
324
        get_response().breadcrumb.append(('sms', _('SMS Unsubscription')))
325
        template.html_top()
326

    
327
        if form.is_submitted() and not form.has_errors():
328
            if sub:
329
                sub.remove("sms")
330

    
331
            def sms_unsub_ok [html] ():
332
                root_url = get_publisher().get_root_url()
333
                '<p>'
334
                _('You have been unsubscribed from announces')
335
                '</p>'
336
                if not get_response().iframe_mode:
337
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
338

    
339
            return sms_unsub_ok()
340

    
341
        else:
342
            '<p>'
343
            _('Do you want to stop receiving announces by sms ?')
344
            '</p>'
345
            form.render()
346

    
347

    
348
    def subscribe [html] (self):
349
        get_response().breadcrumb.append(('subscribe', _('Subscription')))
350
        template.html_top(_('Receiving Announces'))
351

    
352
        TextsDirectory.get_html_text('aq-announces-subscription')
353

    
354
        sub = self._get_announce_subscription()
355

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

    
376

    
377
    def email [html] (self):
378
        get_response().breadcrumb.append(('email', _('Email Subscription')))
379
        template.html_top(_('Receiving Announces by email'))
380

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

    
405
        if form.get_submit() == 'cancel':
406
            return redirect('subscribe')
407

    
408
        if form.is_submitted() and not form.has_errors():
409
            s = self.email_submit(form)
410
            if s is not False:
411
                return s
412
        else:
413
            form.render()
414

    
415
    def email_submit(self, form):
416
        sub = self._get_announce_subscription()
417
        if not sub:
418
            sub = AnnounceSubscription()
419

    
420
        if get_request().user:
421
            sub.user_id = get_request().user.id
422

    
423
        if form.get_widget('email'):
424
            sub.email = form.get_widget('email').parse()
425
        elif get_request().user.email:
426
            sub.email = get_request().user.email
427

    
428
        if not get_request().user:
429
            sub.enabled = False
430

    
431
        sub.store()
432

    
433
        if get_request().user:
434
            def email_submit_ok [html] ():
435
                root_url = get_publisher().get_root_url()
436
                '<p>'
437
                _('You have been subscribed to the announces.')
438
                '</p>'
439
                if not get_response().iframe_mode:
440
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
441

    
442
            return email_submit_ok()
443

    
444
        # asking email confirmation before subscribing someone
445
        token = Token(3 * 86400)
446
        token.type = 'announces-subscription-confirmation'
447
        token.subscription_id = sub.id
448
        token.store()
449
        data = {
450
            'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id,
451
            'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id,
452
            'time': misc.localstrftime(time.localtime(token.expiration)),
453
        }
454

    
455
        emails.custom_ezt_email('announces-subscription-confirmation',
456
                data, sub.email, exclude_current_user = False)
457

    
458
        def email_submit_ok [html] ():
459
            root_url = get_publisher().get_root_url()
460
            '<p>'
461
            _('You have been sent an email for confirmation')
462
            '</p>'
463
            if not get_response().iframe_mode:
464
                '<a href="%s">%s</a>' % (root_url, _('Back Home'))
465

    
466
        return email_submit_ok()
467

    
468
    def emailconfirm(self):
469
        tokenv = get_request().form.get('t')
470
        action = get_request().form.get('a')
471

    
472
        root_url = get_publisher().get_root_url()
473

    
474
        try:
475
            token = Token.get(tokenv)
476
        except KeyError:
477
            return template.error_page(
478
                    _('The token you submitted does not exist, has expired, or has been cancelled.'),
479
                    continue_to = (root_url, _('home page')))
480

    
481
        if token.type != 'announces-subscription-confirmation':
482
            return template.error_page(
483
                    _('The token you submitted is not appropriate for the requested task.'),
484
                    continue_to = (root_url, _('home page')))
485

    
486
        sub = AnnounceSubscription.get(token.subscription_id)
487

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

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

    
515

    
516
    def atom [plain] (self):
517
        response = get_response()
518
        response.set_content_type('application/atom+xml')
519

    
520
        from pyatom import pyatom
521
        xmldoc = pyatom.XMLDoc()
522
        feed = pyatom.Feed()
523
        xmldoc.root_element = feed
524
        feed.title = get_cfg('misc', {}).get('sitename') or 'Au Quotidien'
525
        feed.id = get_request().get_url()
526

    
527
        author_email = get_cfg('emails', {}).get('reply_to')
528
        if not author_email:
529
            author_email = get_cfg('emails', {}).get('from')
530
        if author_email:
531
            feed.authors.append(pyatom.Author(author_email))
532

    
533
        announces = Announce.get_published_announces()
534

    
535
        if announces and announces[0].modification_time:
536
            feed.updated = misc.format_time(announces[0].modification_time,
537
                        '%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ',
538
                        gmtime = True)
539
        feed.links.append(pyatom.Link(get_request().get_url(1) + '/'))
540

    
541
        for item in announces:
542
            entry = item.get_atom_entry()
543
            if entry:
544
                feed.entries.append(entry)
545

    
546
        str(feed)
547

    
548
    def email_unsubscribe [html] (self):
549
        sub = self._get_announce_subscription()
550

    
551
        form = Form(enctype='multipart/form-data')
552
        if not sub:
553
            form.add(EmailWidget, 'email', title = _('Email'), required = True)
554

    
555
        form.add_submit('submit', _('Unsubscribe'))
556
        form.add_submit('cancel', _('Cancel'))
557

    
558
        if form.get_submit() == 'cancel':
559
            return redirect('..')
560

    
561
        get_response().breadcrumb.append(('email', _('Email Unsubscription')))
562
        template.html_top()
563

    
564
        if form.is_submitted() and not form.has_errors():
565
            if sub:
566
                sub.remove("email")
567
            else:
568
                email = form.get_widget('email').parse()
569
                for s in AnnounceSubscription.select():
570
                    if s.email == email:
571
                        s.remove("email")
572

    
573
            def email_unsub_ok [html] ():
574
                root_url = get_publisher().get_root_url()
575
                '<p>'
576
                _('You have been unsubscribed from announces')
577
                '</p>'
578
                if not get_response().iframe_mode:
579
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
580

    
581
            return email_unsub_ok()
582

    
583
        else:
584
            '<p>'
585
            _('Do you want to stop receiving announces by email?')
586
            '</p>'
587
            form.render()
588

    
589
    def _q_lookup(self, component):
590
        try:
591
            announce = Announce.get(component)
592
        except KeyError:
593
            raise errors.TraversalError()
594

    
595
        if announce.hidden:
596
            raise errors.TraversalError()
597

    
598
        get_response().breadcrumb.append((str(announce.id), announce.title))
599
        return AnnounceDirectory(announce)
600

    
601
OldRegisterDirectory = wcs.root.RegisterDirectory
602

    
603
class AlternateRegisterDirectory(OldRegisterDirectory):
604
    def _q_traverse(self, path):
605
        get_response().filter['bigdiv'] = 'new_member'
606
        return OldRegisterDirectory._q_traverse(self, path)
607

    
608
    def _q_index [html] (self):
609
        get_logger().info('register')
610
        ident_methods = get_cfg('identification', {}).get('methods', [])
611

    
612
        if len(ident_methods) == 0:
613
            idps = get_cfg('idp', {})
614
            if len(idps) == 0:
615
                return template.error_page(_('Authentication subsystem is not yet configured.'))
616
            ident_methods = ['idp'] # fallback to old behaviour; liberty.
617

    
618
        if len(ident_methods) == 1:
619
            method = ident_methods[0]
620
        else:
621
            method = 'password'
622
            return qommon.ident.register(method)
623

    
624
        if method == 'idp':
625
            root_url = get_publisher().get_root_url()
626
            return redirect('%slogin' % root_url)
627

    
628
        return OldRegisterDirectory._q_index(self)
629

    
630
OldLoginDirectory = wcs.root.LoginDirectory
631

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

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

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

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

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

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

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

    
687
            '</div>'
688

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

    
706
            get_request().environ['REQUEST_METHOD'] = 'GET'
707

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

    
712
        else:
713
            return OldLoginDirectory._q_index(self)
714

    
715

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

    
722

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

    
733
    admin = admin.AdminRootDirectory()
734
    announces_dir = AnnouncesDirectory()
735
    register = AlternateRegisterDirectory()
736
    login = AlternateLoginDirectory()
737
    ident = AlternateIdentDirectory()
738
    myspace = MyspaceDirectory()
739
    agenda = AgendaDirectory()
740
    saml = Saml2Directory()
741
    payment = PublicPaymentDirectory()
742
    invoices = InvoicesDirectory()
743

    
744
    def _q_traverse(self, path):
745
        if get_publisher().has_site_option('drupal'):
746
            drupal.try_auth()
747
        if get_publisher().has_site_option('ezldap'):
748
            ezldap_ui.try_auth(self)
749

    
750
        session = get_session()
751
        if session:
752
            get_request().user = session.get_user()
753
        else:
754
            get_request().user = None
755

    
756
        get_publisher().substitutions.feed(get_request().user)
757

    
758
        response = get_response()
759
        if not hasattr(response, 'filter'):
760
            response.filter = {}
761
        response.filter['gauche'] = self.box_side(path)
762
        response.filter['keywords'] = template.get_current_theme().get('keywords')
763
        response.breadcrumb = [ ('', _('Home')) ]
764

    
765
        if not self.admin:
766
            self.admin = get_publisher().admin_directory_class()
767

    
768
        if not self.backoffice:
769
            self.backoffice = get_publisher().backoffice_directory_class()
770

    
771
        try:
772
            return Directory._q_traverse(self, path)
773
        except errors.TraversalError, e:
774
            try:
775
                f = FormDef.get_by_urlname(path[0])
776
            except KeyError:
777
                pass
778
            else:
779
                base_url = get_publisher().get_root_url()
780

    
781
                uri_rest = get_request().environ.get('REQUEST_URI')
782
                if not uri_rest:
783
                    uri_rest = get_request().get_path()
784
                if uri_rest.startswith(base_url):
785
                    uri_rest = uri_rest[len(base_url):]
786
                elif uri_rest.startswith('/'):
787
                    # dirty hack, ezldap reverseproxy uses a fake base_url
788
                    uri_rest = uri_rest[1:]
789
                if f.category_id:
790
                    return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest))
791

    
792
            raise e
793

    
794

    
795
    def _q_lookup(self, component):
796
        if component == 'qo':
797
            dirname = os.path.join(get_publisher().data_dir, 'qommon')
798
            return StaticDirectory(dirname, follow_symlinks = True)
799

    
800
        if component == 'aq':
801
            dirname = os.path.join(get_publisher().data_dir, 'qommon', 'auquotidien')
802
            return StaticDirectory(dirname, follow_symlinks = True)
803

    
804
        if component in ('css','images'):
805
            return OldRootDirectory._q_lookup(self, component)
806

    
807
        # is this a category ?
808
        try:
809
            category = Category.get_by_urlname(component)
810
        except KeyError:
811
            pass
812
        else:
813
            return FormsRootDirectory(category)
814

    
815
        # is this a formdef ?
816
        try:
817
            formdef = FormDef.get_by_urlname(component)
818
        except KeyError:
819
            pass
820
        else:
821
            if formdef.category_id is None:
822
                get_response().filter['bigdiv'] = 'rub_service'
823
                return FormsRootDirectory()._q_lookup(component)
824
            # if there is category, let it fall back to raise TraversalError,
825
            # it will get caught in _q_traverse that will redirect it to an
826
            # URL embedding the category
827

    
828
        return None
829

    
830
    def json(self):
831
        return FormsRootDirectory().json()
832

    
833
    def categories(self):
834
        return FormsRootDirectory().categories()
835

    
836
    def _q_index [html] (self):
837
        if get_request().get_header(str('Accept'), '') == 'application/json':
838
            return FormsRootDirectory().json()
839

    
840
        root_url = get_publisher().get_root_url()
841
        if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump:
842
            return redirect('%smyspace/new' % root_url)
843

    
844
        if get_response().iframe_mode:
845
            # never display home page in an iframe
846
            return redirect('%sservices' % root_url)
847

    
848
        template.html_top()
849

    
850
        t = TextsDirectory.get_html_text('aq-home-page')
851
        if t:
852
            '<div id="home-page-intro">'
853
            t
854
            '</div>'
855

    
856

    
857
        '<div id="centre">'
858
        self.box_services(position='1st')
859
        '</div>'
860
        '<div id="droite">'
861
        self.myspace_snippet()
862
        self.box_services(position='2nd')
863
        self.consultations()
864
        self.announces()
865
        '</div>'
866

    
867
        user = get_request().user
868
        if user and user.can_go_in_backoffice():
869
            get_response().filter['backoffice'] = True
870

    
871
    def services [html] (self):
872
        template.html_top()
873
        get_response().filter['bigdiv'] = 'rub_service'
874
        self.box_services(level = 2)
875

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

    
883
        cats = Category.select(order_by = 'name')
884
        cats = [x for x in cats if x.url_name != 'consultations']
885
        Category.sort_by_position(cats)
886

    
887
        all_formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection,
888
                order_by = 'name')
889

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

    
897
        if not t:
898
            return
899

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

    
909
        if get_response().iframe_mode:
910
            if get_request().user:
911
                message = TextsDirectory.get_html_text('welcome-logged')
912
            else:
913
                message = TextsDirectory.get_html_text('welcome-unlogged')
914

    
915
            if message:
916
                '<div id="welcome-message">'
917
                message
918
                '</div>'
919

    
920
        '<ul>'
921
        t
922
        '</ul>'
923

    
924
        '</div>'
925

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

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

    
954
            if not formdefs and not formdefs_advertise:
955
                continue
956

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

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

    
1018
    def box_side [html] (self, path):
1019
        '<div id="sidebox">'
1020
        root_url = get_publisher().get_root_url()
1021

    
1022
        if self.has_anonymous_access_codes():
1023
            '<form id="follow-form" action="%saccesscode">' % root_url
1024
            '<h3>%s</h3>' % _('Tracking')
1025
            '<label>%s</label> ' % _('Code:')
1026
            '<input name="code" size="10"/>'
1027
            '</form>'
1028

    
1029
        self.links()
1030

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

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

    
1049
            if path and path[0] == 'agenda':
1050
                '<p class="tags">'
1051
                for tag in tags:
1052
                    '<a href="%sagenda/tag/%s">%s</a> ' % (root_url, tag, tag)
1053
                '</p>'
1054
                self.agenda.display_remote_calendars()
1055

    
1056
                '<p>'
1057
                '  <a href="%sagenda/filter">%s</a>' % (root_url, _('Advanced Filter'))
1058
                '</p>'
1059

    
1060
        '</div>'
1061

    
1062
    def has_anonymous_access_codes(self):
1063
        for workflow in Workflow.select():
1064
            for wfstatus in workflow.possible_status:
1065
                for wfitem in wfstatus.items:
1066
                    if wfitem.key == 'create-anonymous-access-code':
1067
                        return True
1068
        return False
1069

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

    
1091
    def links [html] (self):
1092
        links = Link.select()
1093
        if not links:
1094
            return
1095

    
1096
        Link.sort_by_position(links)
1097

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

    
1121

    
1122
    def announces [html] (self):
1123
        announces = Announce.get_published_announces()
1124
        if not announces:
1125
            return
1126

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

    
1142
        '<ul id="announces-links">'
1143
        '<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces')
1144
        '<li><a href="announces/">%s</a></li>' % _('Previous Announces')
1145
        '</ul>'
1146
        '</div>'
1147

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

    
1161

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

    
1171
    def informations_editeur [html] (self):
1172
        get_response().filter['bigdiv'] = 'info'
1173
        return self.page_view('aq-editor-info', _('Editor Informations'),
1174
                urlname = 'informations_editeur')
1175

    
1176
    def accessibility(self):
1177
        get_response().filter['bigdiv'] = 'accessibility'
1178
        return self.page_view('aq-accessibility', _('Accessibility Statement'))
1179

    
1180
    def contact(self):
1181
        get_response().filter['bigdiv'] = 'contact'
1182
        return self.page_view('aq-contact', _('Contact'))
1183

    
1184
    def help(self):
1185
        get_response().filter['bigdiv'] = 'help'
1186
        return self.page_view('aq-help', _('Help'))
1187

    
1188

    
1189
from qommon.publisher import get_publisher_class
1190
get_publisher_class().root_directory_class = AlternateRootDirectory
1191
get_publisher_class().after_login_url = 'myspace/'
1192
get_publisher_class().use_sms_feature = True
1193

    
1194
# help links
1195
get_publisher_class().backoffice_help_url = {
1196
    'fr': 'http://doc.entrouvert.org/au-quotidien/stable/guide-gestionnaire.html',
1197
}
1198
get_publisher_class().admin_help_url = {
1199
    'fr': 'http://auquotidien.labs.libre-entreprise.org/doc/fr/user-guide.html',
1200
}
1201

    
1202

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

    
1212
[confirm_url]
1213

    
1214
If you are not the person who made this request, or you wish to cancel
1215
this request, visit the following link:
1216

    
1217
[cancel_url]
1218

    
1219
If you do nothing, the request will lapse after 3 days (precisely on
1220
[time]).
1221
"""))
1222

    
1223

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

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

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

    
1247
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True)
(28-28/32)