Project

General

Profile

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

root / extra / modules / root.ptl @ 0c7d90c5

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

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

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

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

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

    
34
from links import Link
35
from announces import Announce, AnnounceSubscription
36
from myspace import MyspaceDirectory
37
from agenda import AgendaDirectory
38
from events import Event, get_default_event_tags
39

    
40
import admin
41

    
42
import wcs.forms.root
43
from wcs.workflows import Workflow
44

    
45
from saml2 import Saml2Directory
46

    
47
OldRootDirectory = wcs.root.RootDirectory
48

    
49
import qommon.ident.password
50
import qommon.ident.idp
51

    
52
def category_get_homepage_position(self):
53
    if hasattr(self, 'homepage_position') and self.homepage_position:
54
        return self.homepage_position
55
    if self.url_name == 'consultations':
56
        return '2nd'
57
    return '1st'
58
Category.get_homepage_position = category_get_homepage_position
59

    
60
def category_get_limit(self):
61
    if hasattr(self, 'limit') and self.limit is not None:
62
        return self.limit
63
    return 3
64
Category.get_limit = category_get_limit
65

    
66

    
67
class FormsRootDirectory(wcs.forms.root.RootDirectory):
68

    
69
    def user_forms [html] (self, user_forms):
70
        base_url = get_publisher().get_root_url()
71

    
72
        draft = [x for x in user_forms if x.status == 'draft']
73
        if draft:
74
            '<h3 id="drafts">%s</h3>' % _('Your Current Drafts')
75
            '<ul>'
76
            for f in draft:
77
                '<li><a href="%s%s/%s/%s">%s</a></li>' % (base_url, 
78
                    f.formdef.category.url_name,
79
                    f.formdef.url_name, f.id, f.formdef.name)
80
            '</ul>'
81

    
82

    
83
        # (COMPAT) without workflows
84
        current = [x for x in user_forms if x.status in ('new', 'accepted')]
85
        if current:
86
            '<h3 id="submitted">%s</h3>' % _('Your Current Forms')
87
            '<ul>'
88
            for f in current:
89
                if f.formdef is None:
90
                    continue
91
                if f.formdef.category_id:
92
                    category_url = f.formdef.category.url_name
93
                else:
94
                    category_url = '.'
95
                '<li><a href="%s%s/%s/%s/">%s</a>, %s, %s: %s</li>' % (
96
                    base_url,
97
                    category_url,
98
                    f.formdef.url_name, f.id, f.formdef.name, 
99
                    misc.localstrftime(f.receipt_time),
100
                    _('status'),
101
                    _(status_labels[f.status]) )
102
            '</ul>'
103
        done = [x for x in user_forms if x.status in ('rejected', 'finished')]
104
        if done:
105
            '<h3 id="done">%s</h3>' % _('Your Old Forms')
106
            '<ul>'
107
            for f in done:
108
                if not f.formdef:
109
                    continue
110
                if f.formdef.category_id:
111
                    category_url = f.formdef.category.url_name
112
                else:
113
                    category_url = '.'
114
                '<li><a href="%s%s/%s/%s/">%s</a>, %s, %s: %s</li>' % (
115
                    base_url,
116
                    category_url,
117
                    f.formdef.url_name, f.id, f.formdef.name, 
118
                    misc.localstrftime(f.receipt_time),
119
                    _('status'),
120
                    _(status_labels[f.status]) )
121
            '</ul>'
122

    
123
        # with workflows
124
        workflows = Workflow.select(order_by = 'name')
125
        for workflow in workflows:
126
            # XXX: seperate endpoints from non-endpoints
127
            for status in workflow.possible_status:
128
                fms = [x for x in user_forms if x.status == 'wf-%s' % status.id and \
129
                        x.formdef and x.formdef.workflow_id == workflow.id]
130
                if not fms:
131
                    continue
132
                '<h3>%s</h3>' % _('Your forms with status "%s"') % status.name
133
                '<ul>'
134
                for f in fms:
135
                    if f.formdef.category_id:
136
                        category_url = f.formdef.category.url_name
137
                    else:
138
                        category_url = '.'
139
                    '<li><a href="%s%s/%s/%s/">%s</a>, %s</li>' % (
140
                            base_url,
141
                            category_url,
142
                            f.formdef.url_name, f.id, f.formdef.name, 
143
                            misc.localstrftime(f.receipt_time))
144
                '</ul>'
145

    
146

    
147
class AnnounceDirectory(Directory):
148
    _q_exports = ['', 'edit', 'delete', 'email']
149

    
150
    def __init__(self, announce):
151
        self.announce = announce
152

    
153
    def _q_index [html] (self):
154
        template.html_top(_('Announces to citizens'))
155

    
156
        if self.announce.publication_time:
157
            date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time)
158
        else:
159
            date_heading = ''
160

    
161
        '<h3>%s%s</h3>' % (date_heading, self.announce.title)
162

    
163
        '<p>'
164
        self.announce.text
165
        '</p>'
166

    
167
        '<p>'
168
        '<a href="../">%s</a>' % _('Back')
169
        '</p>'
170

    
171

    
172
class AnnouncesDirectory(Directory):
173
    _q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm',
174
            'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist']
175

    
176
    
177
    def _q_traverse(self, path):
178
        get_response().breadcrumb.append(('announces/', _('Announces')))
179
        return Directory._q_traverse(self, path)
180

    
181
    def _q_index [html] (self):
182
        template.html_top(_('Announces to citizens'))
183
        self.announces_list()
184
        '<ul id="announces-links">'
185
        '<li><a href="subscribe">%s</a></li>' % _('Receiving those Announces')
186
        '</ul>'
187

    
188
    def _get_announce_subscription(self):
189
        """ """
190
        sub = None
191
        if get_request().user:
192
            subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id)
193
            if subs:
194
                sub = subs[0]
195
        return sub
196

    
197
    def rawlist [html] (self):
198
        self.announces_list()
199
        get_response().filter = None
200

    
201
    def announces_list [html] (self):
202
        announces = Announce.get_published_announces()
203
        if not announces:
204
            raise errors.TraversalError()
205

    
206
        # XXX: will need pagination someday
207
        for item in announces:
208
            '<div class="announce-item">\n'
209
            '<h4>'
210
            if item.publication_time:
211
                time.strftime(misc.date_format(), item.publication_time)
212
                ' - '
213
            item.title
214
            '</h4>\n'
215
            '<p>\n'
216
            item.text
217
            '\n</p>\n'
218
            '</div>\n'
219

    
220

    
221
    def sms [html] (self):
222
        sms_mode = get_cfg('sms', {}).get('mode', 'none')
223

    
224
        if sms_mode == 'none':
225
            raise errors.TraversalError()
226

    
227
        get_response().breadcrumb.append(('sms', _('SMS')))
228
        template.html_top(_('Receiving announces by SMS'))
229

    
230
        if sms_mode == 'demo':
231
            TextsDirectory.get_html_text('aq-sms-demo')
232
        elif sms_mode == 'mobyt':
233
            announces_cfg = get_cfg('announces',{})
234
            mobile_mask = announces_cfg.get('mobile_mask')
235
            if mobile_mask:
236
                mobile_mask = ' (' + mobile_mask + ')'
237
            else:
238
                mobile_mask = ''
239
            form = Form(enctype='multipart/form-data')
240
            form.add(StringWidget, 'mobile', title = _('Mobile number %s') % mobile_mask, size=12, required=True)
241
            form.add_submit('submit', _('Subscribe'))
242
            form.add_submit('cancel', _('Cancel'))
243

    
244
            if form.get_submit() == 'cancel':
245
                return redirect('subscribe')
246

    
247
            if form.is_submitted() and not form.has_errors():
248
                s = self.sms_submit(form)
249
                if s == False:
250
                    form.render()
251
                else:
252
                    return redirect("smsconfirm")
253
            else:
254
                form.render()
255

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

    
280
        if mobile:
281
            sub.sms = mobile
282

    
283
        if not get_request().user:
284
            sub.enabled = False
285

    
286
        sub.store()
287

    
288
        # Asking sms confirmation
289
        token = Token(3 * 86400, 4, string.digits)
290
        token.type = 'announces-subscription-confirmation'
291
        token.subscription_id = sub.id
292
        token.store()
293

    
294
        message = _("Confirmation code : %s" % token.id)
295
        sms_cfg = get_cfg('sms', {})
296
        sms = SMS()
297
        try:
298
            sms.send([mobile], message, sms_cfg.get('sender', 'auquotidien'))
299
        except errors.SMSError, e:
300
            get_logger().error(e)
301
            form.set_error("mobile", _("Send SMS confirmation failed"))
302
            sub.remove("sms")
303
            return False
304

    
305
    def smsconfirm [html] (self):
306
        template.html_top(_('Receiving announces by SMS confirmation'))
307
        "<p>%s</p>" % _("You will receive a confirmation code by SMS.")
308
        form = Form(enctype='multipart/form-data')
309
        form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True)
310
        form.add_submit('submit', _('Subscribe'))
311
        form.add_submit('cancel', _('Cancel'))
312

    
313
        if form.is_submitted() and not form.has_errors():
314
            token = None
315
            id = form.get_widget("code").parse()
316
            try:
317
                token = Token.get(id)
318
            except KeyError:
319
                form.set_error("code",  _('Invalid confirmation code.'))
320
            else:
321
                if token.type != 'announces-subscription-confirmation':
322
                    form.set_error("code",  _('Invalid confirmation code.'))
323
                else:
324
                    sub = AnnounceSubscription.get(token.subscription_id)
325
                    token.remove_self()
326
                    sub.enabled_sms = True
327
                    sub.store()
328
                    return redirect('.')
329
            form.render()
330
        else:
331
            form.render()
332

    
333
    def sms_unsubscribe [html] (self):
334
        sub = self._get_announce_subscription()
335

    
336
        form = Form(enctype='multipart/form-data')
337
        if not sub:
338
            return redirect('..')
339

    
340
        form.add_submit('submit', _('Unsubscribe'))
341
        form.add_submit('cancel', _('Cancel'))
342

    
343
        if form.get_submit() == 'cancel':
344
            return redirect('..')
345

    
346
        get_response().breadcrumb.append(('sms', _('SMS Unsubscription')))
347
        template.html_top()
348

    
349
        if form.is_submitted() and not form.has_errors():
350
            if sub:
351
                sub.remove("sms")
352

    
353
            def sms_unsub_ok [html] ():
354
                root_url = get_publisher().get_root_url()
355
                '<p>'
356
                _('You have been unsubscribed from announces')
357
                '</p>'
358
                if not get_response().iframe_mode:
359
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
360

    
361
            return sms_unsub_ok()
362

    
363
        else:
364
            '<p>'
365
            _('Do you want to stop receiving announces by sms ?')
366
            '</p>'
367
            form.render()
368

    
369

    
370
    def subscribe [html] (self):
371
        get_response().breadcrumb.append(('subscribe', _('Subscription')))
372
        template.html_top(_('Receiving Announces'))
373

    
374
        TextsDirectory.get_html_text('aq-announces-subscription')
375

    
376
        sub = self._get_announce_subscription()
377

    
378
        '<ul id="announce-modes">'
379
        if sub and sub.email:
380
            ' <li>'
381
            '<span id="par_mail">%s</span>' % _('Email (currently subscribed)')
382
            ' <a href="email_unsubscribe" rel="popup">%s</a></li>' % _('Unsubscribe')
383
        else:
384
            ' <li><a href="email" id="par_mail" rel="popup">%s</a></li>' % _('Email')
385
        if sub and sub.sms:
386
            ' <li>'
387
            if sub.enabled_sms:
388
                '<span id="par_sms">%s</span>' % _('SMS %s (currently subscribed)') % sub.sms
389
            else:
390
                '<span id="par_sms">%s</span>' % _('SMS %s (currently not confirmed)') % sub.sms
391
                ' <a href="smsconfirm" rel="popup">%s</a> ' % _('Confirmation')
392
            ' <a href="sms_unsubscribe" rel="popup">%s</a></li>' % _('Unsubscribe')
393
        elif get_cfg('sms', {}).get('mode', 'none') != 'none':
394
            ' <li><a href="sms" id="par_sms">%s</a></li>' % _('SMS')
395
        ' <li><a class="feed-link" href="atom" id="par_rss">%s</a>' % _('Feed')
396
        '</ul>'
397

    
398

    
399
    def email [html] (self):
400
        get_response().breadcrumb.append(('email', _('Email Subscription')))
401
        template.html_top(_('Receiving Announces by email'))
402

    
403
        form = Form(enctype='multipart/form-data')
404
        if get_request().user:
405
            if get_request().user.email:
406
                '<p>'
407
                _('You are logged in and your email is %s, ok to subscribe ?') % \
408
                    get_request().user.email
409
                '</p>'
410
                form.add_submit('submit', _('Subscribe'))
411
            else:
412
                '<p>'
413
                _("You are logged in but there is no email address in your profile.")
414
                '</p>'
415
                form.add(EmailWidget, 'email', title = _('Email'), required = True)
416
                form.add_submit('submit', _('Subscribe'))
417
                form.add_submit('submit-remember', _('Subscribe and add this email to my profile'))
418
        else:
419
            '<p>'
420
            _('FIXME will only be used for this purpose etc.')
421
            '</p>'
422
            form.add(EmailWidget, 'email', title = _('Email'), required = True)
423
            form.add_submit('submit', _('Subscribe'))
424
        
425
        form.add_submit('cancel', _('Cancel'))
426

    
427
        if form.get_submit() == 'cancel':
428
            return redirect('subscribe')
429

    
430
        if form.is_submitted() and not form.has_errors():
431
            s = self.email_submit(form)
432
            if s is not False:
433
                return s
434
        else:
435
            form.render()
436

    
437
    def email_submit(self, form):
438
        sub = self._get_announce_subscription()
439
        if not sub:
440
            sub = AnnounceSubscription()
441

    
442
        if get_request().user:
443
            sub.user_id = get_request().user.id
444

    
445
        if form.get_widget('email'):
446
            sub.email = form.get_widget('email').parse()
447
        elif get_request().user.email:
448
            sub.email = get_request().user.email
449

    
450
        if not get_request().user:
451
            sub.enabled = False
452

    
453
        sub.store()
454

    
455
        if get_request().user:
456
            def email_submit_ok [html] ():
457
                root_url = get_publisher().get_root_url()
458
                '<p>'
459
                _('You have been subscribed to the announces.')
460
                '</p>'
461
                if not get_response().iframe_mode:
462
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
463

    
464
            return email_submit_ok()
465

    
466
        # asking email confirmation before subscribing someone
467
        token = Token(3 * 86400)
468
        token.type = 'announces-subscription-confirmation'
469
        token.subscription_id = sub.id
470
        token.store()
471
        data = {
472
            'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id,
473
            'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id,
474
            'time': misc.localstrftime(time.localtime(token.expiration)),
475
        }
476

    
477
        emails.custom_ezt_email('announces-subscription-confirmation',
478
                data, sub.email, exclude_current_user = False)
479

    
480
        def email_submit_ok [html] ():
481
            root_url = get_publisher().get_root_url()
482
            '<p>'
483
            _('You have been sent an email for confirmation')
484
            '</p>'
485
            if not get_response().iframe_mode:
486
                '<a href="%s">%s</a>' % (root_url, _('Back Home'))
487

    
488
        return email_submit_ok()
489

    
490
    def emailconfirm(self):
491
        tokenv = get_request().form.get('t')
492
        action = get_request().form.get('a')
493

    
494
        root_url = get_publisher().get_root_url()
495

    
496
        try:
497
            token = Token.get(tokenv)
498
        except KeyError:
499
            return template.error_page(
500
                    _('The token you submitted does not exist, has expired, or has been cancelled.'),
501
                    continue_to = (root_url, _('home page')))
502

    
503
        if token.type != 'announces-subscription-confirmation':
504
            return template.error_page(
505
                    _('The token you submitted is not appropriate for the requested task.'),
506
                    continue_to = (root_url, _('home page')))
507

    
508
        sub = AnnounceSubscription.get(token.subscription_id)
509

    
510
        if action == 'cxl':
511
            def cancel [html]():
512
                root_url = get_publisher().get_root_url()
513
                template.html_top(_('Email Subscription'))
514
                '<h1>%s</h1>' % _('Request Cancelled')
515
                '<p>%s</p>' % _('The request for subscription has been cancelled.')
516
                '<p>'
517
                htmltext(_('Continue to <a href="%s">home page</a>') % root_url)
518
                '</p>'
519
            token.remove_self()
520
            sub.remove_self()
521
            return cancel()
522

    
523
        if action == 'cfm':
524
            token.remove_self()
525
            sub.enabled = True
526
            sub.store()
527
            def sub [html] ():
528
                root_url = get_publisher().get_root_url()
529
                template.html_top(_('Email Subscription'))
530
                '<h1>%s</h1>' % _('Subscription Confirmation')
531
                '<p>%s</p>' % _('Your subscription to announces is now effective.')
532
                '<p>'
533
                htmltext(_('Continue to <a href="%s">home page</a>') % root_url)
534
                '</p>'
535
            return sub()
536

    
537

    
538
    def atom [plain] (self):
539
        response = get_response()
540
        response.set_content_type('application/atom+xml')
541

    
542
        from pyatom import pyatom
543
        xmldoc = pyatom.XMLDoc()
544
        feed = pyatom.Feed()
545
        xmldoc.root_element = feed
546
        feed.title = get_cfg('misc', {}).get('sitename', 'Au Quotidien')
547
        feed.id = get_request().get_url()
548

    
549
        author_email = get_cfg('emails', {}).get('reply_to')
550
        if not author_email:
551
            author_email = get_cfg('emails', {}).get('from')
552
        if author_email:
553
            feed.authors.append(pyatom.Author(author_email))
554

    
555
        announces = Announce.get_published_announces()
556

    
557
        if announces and announces[0].modification_time:
558
            feed.updated = misc.format_time(announces[0].modification_time,
559
                        '%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ',
560
                        gmtime = True)
561
        feed.links.append(pyatom.Link(get_request().get_url(1) + '/'))
562

    
563
        for item in announces:
564
            entry = item.get_atom_entry()
565
            if entry:
566
                feed.entries.append(entry)
567

    
568
        str(feed)
569

    
570
    def email_unsubscribe [html] (self):
571
        sub = self._get_announce_subscription()
572

    
573
        form = Form(enctype='multipart/form-data')
574
        if not sub:
575
            form.add(EmailWidget, 'email', title = _('Email'), required = True)
576

    
577
        form.add_submit('submit', _('Unsubscribe'))
578
        form.add_submit('cancel', _('Cancel'))
579

    
580
        if form.get_submit() == 'cancel':
581
            return redirect('..')
582

    
583
        get_response().breadcrumb.append(('email', _('Email Unsubscription')))
584
        template.html_top()
585

    
586
        if form.is_submitted() and not form.has_errors():
587
            if sub:
588
                sub.remove("email")
589
            else:
590
                email = form.get_widget('email').parse()
591
                for s in AnnounceSubscription.select():
592
                    if s.email == email:
593
                        s.remove("email")
594

    
595
            def email_unsub_ok [html] ():
596
                root_url = get_publisher().get_root_url()
597
                '<p>'
598
                _('You have been unsubscribed from announces')
599
                '</p>'
600
                if not get_response().iframe_mode:
601
                    '<a href="%s">%s</a>' % (root_url, _('Back Home'))
602

    
603
            return email_unsub_ok()
604

    
605
        else:
606
            '<p>'
607
            _('Do you want to stop receiving announces by email?')
608
            '</p>'
609
            form.render()
610

    
611
    def _q_lookup(self, component):
612
        try:
613
            announce = Announce.get(component)
614
        except KeyError:
615
            raise errors.TraversalError()
616

    
617
        if announce.hidden:
618
            raise errors.TraversalError()
619

    
620
        get_response().breadcrumb.append((str(announce.id), announce.title))
621
        return AnnounceDirectory(announce)
622

    
623
OldRegisterDirectory = wcs.root.RegisterDirectory
624

    
625
class AlternateRegisterDirectory(OldRegisterDirectory):
626
    def _q_traverse(self, path):
627
        get_response().filter['bigdiv'] = 'new_member'
628
        return OldRegisterDirectory._q_traverse(self, path)
629

    
630
    def _q_index [html] (self):
631
        get_logger().info('register')
632
        ident_methods = get_cfg('identification', {}).get('methods', [])
633

    
634
        if len(ident_methods) == 0:
635
            idps = get_cfg('idp', {})
636
            if len(idps) == 0:
637
                return template.error_page(_('Authentication subsystem is not yet configured.'))
638
            ident_methods = ['idp'] # fallback to old behaviour; liberty.
639

    
640
        if len(ident_methods) == 1:
641
            method = ident_methods[0]
642
        else:
643
            method = 'password'
644
            return qommon.ident.register(method)
645

    
646
        if method == 'idp':
647
            root_url = get_publisher().get_root_url()
648
            return redirect('%slogin' % root_url)
649

    
650
        return OldRegisterDirectory._q_index(self)
651

    
652
OldLoginDirectory = wcs.root.LoginDirectory
653

    
654
class AlternateLoginDirectory(OldLoginDirectory):
655
    def _q_traverse(self, path):
656
        get_response().filter['bigdiv'] = 'member'
657
        return OldLoginDirectory._q_traverse(self, path)
658

    
659
    def _q_index [html] (self):
660
        get_logger().info('login')
661
        ident_methods = get_cfg('identification', {}).get('methods', [])
662

    
663
        if len(ident_methods) > 1 and 'idp' in ident_methods:
664
            # if there is more than one identification method, and there is a
665
            # possibility of SSO, if we got there as a consequence of an access
666
            # unauthorized url on admin/ or backoffice/, then idp auth method
667
            # is chosen forcefully.
668
            after_url = get_session().after_url
669
            if after_url:
670
                root_url = get_publisher().get_root_url()
671
                after_path = urlparse.urlparse(after_url)[2]
672
                after_path = after_path[len(root_url):]
673
                if after_path.startswith(str('admin')) or \
674
                        after_path.startswith(str('backoffice')):
675
                    ident_methods = ['idp']
676

    
677
        # don't display authentication system choice
678
        if len(ident_methods) == 1:
679
            method = ident_methods[0]
680
            try:
681
                return qommon.ident.login(method)
682
            except KeyError:
683
                get_logger().error('failed to login with method %s' % method)
684
                return errors.TraversalError()
685

    
686
        if sorted(ident_methods) == ['idp', 'password']:
687
            get_response().breadcrumb.append(('login', _('Login')))
688
            identities_cfg = get_cfg('identities', {})
689
            form = Form(enctype = 'multipart/form-data', id = 'login-form', use_tokens = False)
690
            if identities_cfg.get('email-as-username', False):
691
                form.add(StringWidget, 'username', title = _('Email'), size=25, required=True)
692
            else:
693
                form.add(StringWidget, 'username', title = _('Username'), size=25, required=True)
694
            form.add(PasswordWidget, 'password', title = _('Password'), size=25, required=True)
695
            form.add_submit('submit', _('Connect'))
696
            if form.is_submitted() and not form.has_errors():
697
                tmp = qommon.ident.password.MethodDirectory().login_submit(form)
698
                if not form.has_errors():
699
                    return tmp
700

    
701
            '<div id="login-password">'
702
            get_session().display_message()
703
            form.render()
704

    
705
            base_url = get_publisher().get_root_url()
706
            '<p><a href="%sident/password/forgotten">%s</a></p>' % (
707
                    base_url, _('Forgotten password ?'))
708

    
709
            '</div>'
710

    
711
            # XXX: this part only supports a single IdP
712
            '<div id="login-sso">'
713
            TextsDirectory.get_html_text('aq-sso-text')
714
            form = Form(enctype='multipart/form-data',
715
                    action = '%sident/idp/login' % base_url)
716
            form.add_hidden('method', 'idp')
717
            for kidp, idp in get_cfg('idp', {}).items():
718
                p = lasso.Provider(lasso.PROVIDER_ROLE_IDP,
719
                        misc.get_abs_path(idp['metadata']),
720
                        misc.get_abs_path(idp.get('publickey')), None)
721
                form.add_hidden('idp', p.providerId)
722
                break
723
            form.add_submit('submit', _('Connect'))
724
            
725
            form.render()
726
            '</div>'
727

    
728
            get_request().environ['REQUEST_METHOD'] = 'GET'
729

    
730
            """<script type="text/javascript">
731
              document.getElementById('login-form')['username'].focus();
732
            </script>"""
733

    
734
        else:
735
            return OldLoginDirectory._q_index(self)
736

    
737

    
738
class AlternateRootDirectory(OldRootDirectory):
739
    _q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout',
740
            'liberty', 'token', 'saml', 'register', 'ident', 'afterjobs',
741
            ('informations-editeur', 'informations_editeur'), 'index2',
742
            ('announces', 'announces_dir'),
743
            'accessibility', 'contact', 'help',
744
            'myspace', 'services', 'agenda',
745
            'themes']
746

    
747
    admin = admin.AdminRootDirectory()
748
    announces_dir = AnnouncesDirectory()
749
    register = AlternateRegisterDirectory()
750
    login = AlternateLoginDirectory()
751
    myspace = MyspaceDirectory()
752
    agenda = AgendaDirectory()
753
    saml = Saml2Directory()
754

    
755
    def _q_traverse(self, path):
756
        session = get_session()
757
        if session:
758
            get_request().user = session.get_user()
759
        else:
760
            get_request().user = None
761

    
762
        response = get_response()
763
        if not hasattr(response, 'filter'):
764
            response.filter = {}
765
        response.filter['gauche'] = self.box_side(path)
766
        response.breadcrumb = [ ('', _('Home')) ]
767

    
768
        if not self.admin:
769
            self.admin = get_publisher().admin_directory_class()
770

    
771
        if not self.backoffice:
772
            self.backoffice = get_publisher().backoffice_directory_class()
773

    
774
        try:
775
            return Directory._q_traverse(self, path)
776
        except errors.TraversalError, e:
777
            try:
778
                f = FormDef.get_by_urlname(path[0])
779
            except KeyError:
780
                pass
781
            else:
782
                base_url = get_publisher().get_root_url()
783
                uri_rest = get_request().environ.get('REQUEST_URI')
784
                if uri_rest.startswith(base_url):
785
                    uri_rest = uri_rest[len(base_url):]
786
                if f.category_id:
787
                    return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest))
788

    
789
            raise e
790

    
791

    
792

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

    
798
        if component in ('css','images'):
799
            return OldRootDirectory._q_lookup(self, component)
800

    
801
        # is this a category ?
802
        try:
803
            category = Category.get_by_urlname(component)
804
        except KeyError:
805
            pass
806
        else:
807
            return FormsRootDirectory(category)
808

    
809
        # is this a formdef ?
810
        try:
811
            formdef = FormDef.get_by_urlname(component)
812
        except KeyError:
813
            pass
814
        else:
815
            if formdef.category_id is None:
816
                return FormsRootDirectory()._q_lookup(component)
817
            # if there is category, let it fall back to raise TraversalError,
818
            # it will get caught in _q_traverse that will redirect it to an
819
            # URL embedding the category
820

    
821
        return None
822

    
823

    
824
    def _q_index [html] (self):
825
        root_url = get_publisher().get_root_url()
826
        if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump:
827
            return redirect('%smyspace/new' % root_url)
828

    
829
        if get_response().iframe_mode:
830
            # never display home page in an iframe
831
            return redirect('%sservices' % root_url)
832

    
833
        template.html_top()
834

    
835
        t = TextsDirectory.get_html_text('aq-home-page')
836
        if t:
837
            '<div id="home-page-intro">'
838
            t
839
            '</div>'
840

    
841

    
842
        '<div id="centre">'
843
        self.box_services(position='1st')
844
        '</div>'
845
        '<div id="droite">'
846
        self.myspace_snippet()
847
        self.box_services(position='2nd')
848
        self.consultations()
849
        self.announces()
850
        '</div>'
851

    
852
    def services [html] (self):
853
        template.html_top()
854
        get_response().filter['bigdiv'] = 'rub_service'
855
        self.box_services(level = 2)
856

    
857
    def box_services [html] (self, level=3, position=None):
858
        ## Services
859
        if get_request().user and get_request().user.roles:
860
            accepted_roles = get_request().user.roles
861
        else:
862
            accepted_roles = []
863

    
864
        cats = Category.select(order_by = 'name')
865
        cats = [x for x in cats if x.url_name != 'consultations']
866
        Category.sort_by_position(cats)
867

    
868
        all_formdefs = FormDef.select(lambda x: not x.disabled, order_by = 'name')
869

    
870
        if position:
871
            t = self.display_list_of_formdefs(
872
                            [x for x in cats if x.get_homepage_position() == position],
873
                            all_formdefs, accepted_roles)
874
        else:
875
            t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles)
876

    
877
        if not t:
878
            return
879

    
880
        if position == '2nd':
881
            '<div id="services-2nd">'
882
        else:
883
            '<div id="services">'
884
        if level == 2:
885
            '<h2>%s</h2>' % _('Services')
886
        else:
887
            if position == '1st':
888
                '<h3>%s</h3>' % _('Services')
889
            else:
890
                '<h3></h3>'
891

    
892
        '<ul>'
893
        t
894
        '</ul>'
895

    
896
        '</div>'
897

    
898
    def display_list_of_formdefs [html] (self, cats, all_formdefs, accepted_roles):
899
        for category in cats:
900
            if category.url_name == 'consultations':
901
                self.consultations_category = category
902
                continue
903
            formdefs = [x for x in all_formdefs if x.category_id == category.id]
904

    
905
            for formdef in formdefs[:]:
906
                if not formdef.roles:
907
                    continue
908
                if not get_request().user:
909
                    formdefs.remove(formdef)
910
                    continue
911
                if logged_users_role().id in formdef.roles:
912
                    continue
913
                for q in accepted_roles:
914
                    if q in formdef.roles:
915
                        break
916
                else:
917
                    formdefs.remove(formdef)
918

    
919
            if not formdefs:
920
                continue
921

    
922
            '<li>'
923
            '<strong>'
924
            '<a href="%s/">' % category.url_name
925
            category.name
926
            '</a></strong>\n'
927
            if category.description:
928
                '<p>'
929
                category.description
930
                '</p>'
931
            '<ul>'
932
            limit = category.get_limit()
933
            for formdef in formdefs[:limit]:
934
                '<li>'
935
                '<a href="%s/%s/">%s</a>' % (category.url_name, formdef.url_name, formdef.name)
936
                '</li>\n'
937
            if len(formdefs) > limit:
938
                '<li class="all-forms"><a href="%s/">%s</a></li>' % (category.url_name,
939
                        _('Access to all forms in this category'))
940
            '</ul>'
941
            '</li>\n'
942

    
943
    def consultations [html] (self):
944
        cats = [x for x in Category.select() if x.url_name == 'consultations']
945
        if not cats:
946
            return
947
        consultations_category = cats[0]
948
        formdefs = FormDef.select(lambda x: (
949
                    x.category_id == consultations_category.id and not x.disabled),
950
                    order_by = 'name')
951
        if not formdefs:
952
            return
953
        ## Consultations
954
        '<div id="consultations">'
955
        '<h3>%s</h3>' % _('Consultations')
956
        if consultations_category.description:
957
            '<p>'
958
            consultations_category.description
959
            '</p>'
960
        '<ul>'
961
        for formdef in formdefs:
962
            '<li>'
963
            '<a href="%s/%s/">%s</a>' % (
964
                    consultations_category.url_name, formdef.url_name, formdef.name)
965
            '</li>'
966
        '</ul>'
967
        '</div>'
968

    
969
    def box_side [html] (self, path):
970
        '<div id="sidebox">'
971
        self.links()
972

    
973
        cats = Category.select(order_by = 'name')
974
        cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side']
975
        Category.sort_by_position(cats)
976
        if cats:
977
            '<div id="side-services">'
978
            '<h3>%s</h3>' % _('Services')
979
            '<ul>'
980
            for cat in cats:
981
                '<li><a href="%s/">%s</a></li>' % (cat.url_name, cat.name)
982
            '</ul>'
983
            '</div>'
984

    
985
        all_formdefs = FormDef.select(lambda x: not x.disabled, order_by = 'name')
986
        if Event.keys(): # if there are events, add a link to the agenda
987
            tags = get_cfg('misc', {}).get('event_tags')
988
            if not tags:
989
                tags = get_default_event_tags()
990
            root_url = get_publisher().get_root_url()
991
            '<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>' % (root_url, _('Agenda'))
992
            '<p class="tags">'
993
            for tag in tags:
994
                '<a href="%sagenda/tag/%s">%s</a> ' % (root_url, tag, tag)
995
            '</p>'
996
            if path and path[0] == 'agenda':
997
                self.agenda.display_remote_calendars()
998

    
999
            '<p>'
1000
            '<a href="%sagenda/filter">%s</a>' % (root_url, _('Advanced Filter'))
1001
            '</p>'
1002

    
1003
        '</div>'
1004

    
1005

    
1006
    def links [html] (self):
1007
        links = Link.select()
1008
        if not links:
1009
            return
1010

    
1011
        Link.sort_by_position(links)
1012

    
1013
        '<div id="links">'
1014
        if links[0].url:
1015
            # first link has an URL, so it's not a title, so we display a
1016
            # generic title
1017
            '<h3>%s</h3>' % _('Useful links')
1018
        has_ul = False
1019
        for link in links:
1020
            if not link.url:
1021
                # acting title
1022
                if has_ul:
1023
                    '</ul>'
1024
                '<h3>%s</h3>' % link.title
1025
                '<ul>'
1026
                has_ul = True
1027
            else:
1028
                if not has_ul:
1029
                    '<ul>'
1030
                    has_ul = True
1031
                '<li><a href="%s">%s</a></li>' % (link.url, link.title)
1032
        if has_ul:
1033
            '</ul>'
1034
        '</div>'
1035

    
1036

    
1037
    def announces [html] (self):
1038
        announces = Announce.get_published_announces()
1039
        if not announces:
1040
            return
1041

    
1042
        '<div id="announces">'
1043
        '<h3>%s</h3>' % _('Announces to citizens')
1044
        for item in announces[:3]:
1045
            '<div class="announce-item">'
1046
            '<h4>'
1047
            if item.publication_time:
1048
                time.strftime(misc.date_format(), item.publication_time)
1049
                ' - '
1050
            item.title
1051
            '</h4>'
1052
            '<p>'
1053
            item.text
1054
            '</p>'
1055
            '</div>'
1056

    
1057
        '<ul id="announces-links">'
1058
        '<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces')
1059
        '<li><a href="announces/">%s</a></li>' % _('Previous Announces')
1060
        '</ul>'
1061
        '</div>'
1062

    
1063
    def myspace_snippet [html] (self):
1064
        '<div id="myspace">'
1065
        '<h3>%s</h3>' % _('My Space')
1066
        '<ul>'
1067
        if get_request().user and not get_request().user.anonymous:
1068
            '  <li><a href="myspace/" id="member">%s</a></li>' % _('Access to your personal space')
1069
            '  <li><a href="logout" id="logout">%s</a></li>' % _('Logout')
1070
        else:
1071
            '  <li><a href="register/" id="inscr">%s</a></li>' % _('Registration')
1072
            '  <li><a href="login/" id="login">%s</a></li>' % _('Login')
1073
        '</ul>'
1074
        '</div>'
1075

    
1076

    
1077
    def page_view [html] (self, key, title, urlname = None):
1078
        if not urlname:
1079
            urlname = key[3:].replace(str('_'), str('-'))
1080
        get_response().breadcrumb.append((urlname, title))
1081
        template.html_top(title)
1082
        '<div class="article">'
1083
        htmltext(TextsDirectory.get_html_text(key))
1084
        '</div>'
1085

    
1086
    def informations_editeur [html] (self):
1087
        get_response().filter['bigdiv'] = 'info'
1088
        return self.page_view('aq-editor-info', _('Editor Informations'),
1089
                urlname = 'informations_editeur')
1090

    
1091
    def accessibility(self):
1092
        get_response().filter['bigdiv'] = 'accessibility'
1093
        return self.page_view('aq-accessibility', _('Accessibility Statement'))
1094

    
1095
    def contact(self):
1096
        get_response().filter['bigdiv'] = 'contact'
1097
        return self.page_view('aq-contact', _('Contact'))
1098

    
1099
    def help(self):
1100
        get_response().filter['bigdiv'] = 'help'
1101
        return self.page_view('aq-help', _('Help'))
1102

    
1103

    
1104
from qommon.publisher import get_publisher_class
1105
get_publisher_class().root_directory_class = AlternateRootDirectory
1106
get_publisher_class().after_login_url = 'myspace/'
1107
get_publisher_class().use_sms_feature = True
1108

    
1109

    
1110
EmailsDirectory.register('announces-subscription-confirmation',
1111
        N_('Confirmation of Announces Subscription'),
1112
        N_('Available variables: change_url, cancel_url, time, sitename'),
1113
        default_subject = N_('Announce Subscription Request'),
1114
        default_body = N_("""\
1115
You have (or someone impersonating you has) requested to subscribe to
1116
announces from [sitename].  To confirm this request, visit the
1117
following link:
1118

    
1119
[confirm_url]
1120

    
1121
If you are not the person who made this request, or you wish to cancel
1122
this request, visit the following link:
1123

    
1124
[cancel_url]
1125

    
1126
If you do nothing, the request will lapse after 3 days (precisely on
1127
[time]).
1128
"""))
1129

    
1130

    
1131
TextsDirectory.register('aq-announces-subscription',
1132
        N_('Text on announces subscription page'),
1133
        default = N_('''\
1134
<p>
1135
FIXME
1136
'</p>'''))
1137

    
1138
TextsDirectory.register('aq-sms-demo',
1139
        N_('Text when subscribing to announces SMS and configured as demo'),
1140
        default = N_('''
1141
<p>
1142
Receiving announces by SMS is not possible in this demo
1143
</p>'''))
1144

    
1145
TextsDirectory.register('aq-editor-info', N_('Editor Informations'))
1146
TextsDirectory.register('aq-accessibility', N_('Accessibility Statement'))
1147
TextsDirectory.register('aq-contact', N_('Contact Information'))
1148
TextsDirectory.register('aq-help', N_('Help'))
1149
TextsDirectory.register('aq-sso-text',  N_('Connecting with Identity Provider'),
1150
        default = N_('''<h3>Connecting with Identity Provider</h3>
1151
<p>You can also use your identity provider to connect.
1152
</p>'''))
1153

    
1154
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True)
(14-14/16)