Project

General

Profile

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

root / extra / modules / root.ptl @ d16a94ad

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',
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
                return FormsRootDirectory()._q_lookup(component)
823
            # if there is category, let it fall back to raise TraversalError,
824
            # it will get caught in _q_traverse that will redirect it to an
825
            # URL embedding the category
826

    
827
        return None
828

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

    
832
    def _q_index [html] (self):
833
        if get_request().get_header(str('Accept'), '') == 'application/json':
834
            return FormsRootDirectory().json()
835

    
836
        root_url = get_publisher().get_root_url()
837
        if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump:
838
            return redirect('%smyspace/new' % root_url)
839

    
840
        if get_response().iframe_mode:
841
            # never display home page in an iframe
842
            return redirect('%sservices' % root_url)
843

    
844
        template.html_top()
845

    
846
        t = TextsDirectory.get_html_text('aq-home-page')
847
        if t:
848
            '<div id="home-page-intro">'
849
            t
850
            '</div>'
851

    
852

    
853
        '<div id="centre">'
854
        self.box_services(position='1st')
855
        '</div>'
856
        '<div id="droite">'
857
        self.myspace_snippet()
858
        self.box_services(position='2nd')
859
        self.consultations()
860
        self.announces()
861
        '</div>'
862

    
863
        user = get_request().user
864
        if user and user.can_go_in_backoffice():
865
            get_response().filter['backoffice'] = True
866

    
867
    def services [html] (self):
868
        template.html_top()
869
        get_response().filter['bigdiv'] = 'rub_service'
870
        self.box_services(level = 2)
871

    
872
    def box_services [html] (self, level=3, position=None):
873
        ## Services
874
        if get_request().user and get_request().user.roles:
875
            accepted_roles = get_request().user.roles
876
        else:
877
            accepted_roles = []
878

    
879
        cats = Category.select(order_by = 'name')
880
        cats = [x for x in cats if x.url_name != 'consultations']
881
        Category.sort_by_position(cats)
882

    
883
        all_formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection,
884
                order_by = 'name')
885

    
886
        if position:
887
            t = self.display_list_of_formdefs(
888
                            [x for x in cats if x.get_homepage_position() == position],
889
                            all_formdefs, accepted_roles)
890
        else:
891
            t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles)
892

    
893
        if not t:
894
            return
895

    
896
        if position == '2nd':
897
            '<div id="services-2nd">'
898
        else:
899
            '<div id="services">'
900
        if level == 2:
901
            '<h2>%s</h2>' % _('Services')
902
        else:
903
            '<h3>%s</h3>' % _('Services')
904

    
905
        if get_response().iframe_mode:
906
            if get_request().user:
907
                message = TextsDirectory.get_html_text('welcome-logged')
908
            else:
909
                message = TextsDirectory.get_html_text('welcome-unlogged')
910

    
911
            if message:
912
                '<div id="welcome-message">'
913
                message
914
                '</div>'
915

    
916
        '<ul>'
917
        t
918
        '</ul>'
919

    
920
        '</div>'
921

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

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

    
950
            if not formdefs and not formdefs_advertise:
951
                continue
952

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

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

    
1014
    def box_side [html] (self, path):
1015
        '<div id="sidebox">'
1016
        root_url = get_publisher().get_root_url()
1017

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

    
1025
        self.links()
1026

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

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

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

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

    
1056
        '</div>'
1057

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

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

    
1087
    def links [html] (self):
1088
        links = Link.select()
1089
        if not links:
1090
            return
1091

    
1092
        Link.sort_by_position(links)
1093

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

    
1117

    
1118
    def announces [html] (self):
1119
        announces = Announce.get_published_announces()
1120
        if not announces:
1121
            return
1122

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

    
1138
        '<ul id="announces-links">'
1139
        '<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces')
1140
        '<li><a href="announces/">%s</a></li>' % _('Previous Announces')
1141
        '</ul>'
1142
        '</div>'
1143

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

    
1157

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

    
1167
    def informations_editeur [html] (self):
1168
        get_response().filter['bigdiv'] = 'info'
1169
        return self.page_view('aq-editor-info', _('Editor Informations'),
1170
                urlname = 'informations_editeur')
1171

    
1172
    def accessibility(self):
1173
        get_response().filter['bigdiv'] = 'accessibility'
1174
        return self.page_view('aq-accessibility', _('Accessibility Statement'))
1175

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

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

    
1184

    
1185
from qommon.publisher import get_publisher_class
1186
get_publisher_class().root_directory_class = AlternateRootDirectory
1187
get_publisher_class().after_login_url = 'myspace/'
1188
get_publisher_class().use_sms_feature = True
1189

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

    
1198

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

    
1208
[confirm_url]
1209

    
1210
If you are not the person who made this request, or you wish to cancel
1211
this request, visit the following link:
1212

    
1213
[cancel_url]
1214

    
1215
If you do nothing, the request will lapse after 3 days (precisely on
1216
[time]).
1217
"""))
1218

    
1219

    
1220
TextsDirectory.register('aq-announces-subscription',
1221
        N_('Text on announces subscription page'),
1222
        default = N_('''\
1223
<p>
1224
FIXME
1225
'</p>'''))
1226

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

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

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