source: products/quintagroup.plonetabs/branches/nokss/quintagroup/plonetabs/browser/plonetabs.py @ 3681

Last change on this file since 3681 was 3681, checked in by mike, 7 years ago

reback renderViewlet method; remove test_kssRegistry test

  • Property svn:eol-style set to native
File size: 35.2 KB
Line 
1import urllib
2import re
3import json
4
5from Acquisition import aq_inner
6from DateTime import DateTime
7
8from zope.interface import implements
9from zope.component import getMultiAdapter
10
11# BBB: compatibility with older plone versions
12try:
13    # Plone < 4.3
14    from zope.app.container import interfaces
15    INameChooser = interfaces.INameChooser
16except ImportError:
17    # Plone >= 4.3
18    from zope.container.interfaces import INameChooser
19
20from zope.viewlet.interfaces import IViewletManager, IViewlet
21
22from plone.app.layout.navigation.root import getNavigationRoot
23
24from Products.CMFCore.utils import getToolByName
25from Products.CMFCore.interfaces import IAction, IActionCategory
26from Products.CMFCore.ActionInformation import Action, ActionCategory
27from Products.CMFCore.Expression import Expression
28from Products.CMFPlone import utils
29from Products.CMFPlone.browser.navigation import get_view_url
30from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
31from Products.Five.browser import BrowserView
32from Products.statusmessages.interfaces import IStatusMessage
33
34from quintagroup.plonetabs.config import PROPERTY_SHEET, FIELD_NAME
35from quintagroup.plonetabs.utils import setupViewletByName
36from quintagroup.plonetabs import messageFactory as _
37from interfaces import IPloneTabsControlPanel
38
39ACTION_ATTRS = ["id", "title", "url_expr", "available_expr", "visible"]
40UI_ATTRS = {"id": "id",
41            "title": "name",
42            "url_expr": "action",
43            "available_expr": "condition",
44            "visible": "visible"}
45
46bad_id = re.compile(r'[^a-zA-Z0-9-_~,.$\(\)# @]').search
47
48cookie_name = 'ploneTabsMode'
49
50
51class PloneTabsControlPanel():
52
53    implements(IPloneTabsControlPanel)
54
55    template = ViewPageTemplateFile("templates/plonetabs.pt")
56    actionslist_template = ViewPageTemplateFile("templates/actionslist.pt")
57    autogenerated_template = ViewPageTemplateFile("templates/autogenerated.pt")
58    autogenerated_list = ViewPageTemplateFile("templates/autogeneratedlist.pt")
59    link_template = ViewPageTemplateFile("templates/changemodelink.pt")
60
61    # custom templates used to update page sections
62    sections_template = ViewPageTemplateFile("templates/sections.pt")
63
64    # configuration variables
65    prefix = "tabslist_"
66    sufix = ""
67
68    def __call__(self):
69        """Perform the update and redirect if necessary, or render the page"""
70        postback = True
71        ajaxback = False
72        errors = {}
73
74        form = self.request.form
75        submitted = form.get('form.submitted', False)
76        ajax_request = form.get('ajax_request', False)
77
78        # action handler def handler(self, form)
79        if ajax_request:
80            ajaxback = self.ajax_postback(form, errors)
81        if submitted:
82            postback = self.submitted_postback(form, errors)
83
84        mode = self.request.get(cookie_name, False)
85        if mode in ('plain', 'rich'):
86            # set cookie to remember the choice
87            expires = (DateTime() + 365).toZone('GMT').rfc822()
88            self.request.response.setCookie(cookie_name, mode, path='/',
89                                            expires=expires)
90
91        if ajaxback:
92            return json.dumps(ajaxback)
93        elif postback:
94            return self.template(errors=errors)
95
96    def submitted_postback(self, form, errors):
97        """submitted postback"""
98        if 'add.add' in form.keys():
99            postback = self.manage_addAction(form, errors)
100        elif "edit.save" in form.keys():
101            postback = self.manage_editAction(form, errors)
102        elif "edit.delete" in form.keys():
103            postback = self.manage_deleteAction(form, errors)
104        elif "edit.moveup" in form.keys():
105            postback = self.manage_moveUpAction(form, errors)
106        elif "edit.movedown" in form.keys():
107            postback = self.manage_moveDownAction(form, errors)
108        elif "autogenerated.save" in form.keys():
109            postback = self.manage_setAutogeneration(form, errors)
110        else:
111            postback = True
112        return postback
113
114
115    def ajax_postback(self, form, errors):
116        """ajax_postback ajaxback"""
117        conv_dict = {
118            "edit_moveact": "manage_ajax_moveAction",
119            "category_change": "manage_ajax_changeCategory",
120            "edit_delete": "manage_ajax_deleteAction",
121            "edit_save": "manage_ajax_saveAction",
122            "edit_cancel": "manage_ajax_cancelEditting",
123            "tabslist_visible": "manage_ajax_toggleActionsVisibility",
124            "roottabs_visible": "manage_ajax_toggleRootsVisibility",
125            "generated_tabs": "manage_ajax_toggleGeneratedTabs",
126            "add_add": "manage_ajax_addAction",
127        }
128       
129        for method in conv_dict.keys():
130            if method in form.keys():
131                method_val = conv_dict.get(method)
132                return  getattr(self, method_val)(form, errors)
133
134        return False
135
136    @property
137    def plone_portal_state(self):
138        """plone_portal_state"""
139        return getMultiAdapter((aq_inner(self.context), self.request),
140                               name='plone_portal_state')
141
142    @property
143    def portal_properties(self):
144        """portal_properties"""
145        return getToolByName(self.context, 'portal_properties', None)
146
147    @property
148    def portal_actions(self):
149        """portal_actions"""
150        return getToolByName(self.context, 'portal_actions')
151
152    ########################################
153    # AJAX Methods
154    ########################################
155
156    def manage_ajax_addAction(self, form, errs):
157        # extract posted data
158        resp_dict = {}
159        cat_name = form['cat_name']
160        id, ie7bad_category, data = self.parseAddForm(self.request.form)
161
162        # validate posted data
163        errors = self.validateActionFields(cat_name, data)
164
165        if not errors:
166            action = self.addAction(cat_name, data)
167            # add one more action to actions list
168            content = self.getActionsList(category=cat_name, tabs=[action, ])
169            resp_dict['content'] = content
170            resp_dict['status_code'] = 200
171            resp_dict['status_message'] = "%s action successfully added." % action.id
172        else:
173            resp_dict['status_message'] = "Please correct the indicated errors."
174            resp_dict['status_code'] = 500
175            resp_dict['content'] = errors
176        return resp_dict
177
178    def manage_ajax_toggleGeneratedTabs(self, form, errs):
179        """Toggle autogenaration setting on configlet"""
180        resp_dict = {}
181        errors = []
182       
183        #TODO: parse, validate form
184        checked = form['generated_tabs']
185        field = form['field']
186       
187        if not errors:
188            if checked == 'true':
189                self.setSiteProperties(**{field: False})
190                message = _(u"Generated tabs switched on.")
191            else:
192                self.setSiteProperties(**{field: True})
193                message = _(u"Generated tabs switched off.")
194            content = self.getGeneratedTabs()
195            resp_dict['content'] = content
196            resp_dict['status_code'] = 200
197            resp_dict['status_message'] = message
198        else:
199            resp_dict['status_message'] = errors
200            resp_dict['status_code'] = 500
201        return resp_dict
202
203    def manage_ajax_toggleRootsVisibility(self, form, errs):
204        #Toggle visibility for portal actions
205        resp_dict = {}
206        errors = []
207       
208        #TODO: parse, validate form
209        id = form['orig_id']
210        checked = form['visibility']
211
212        portal = getMultiAdapter((aq_inner(self.context), self.request),
213                                 name='plone_portal_state').portal()
214
215        # remove prefix, added for making ids on configlet unique ("roottabs_")
216        obj_id = id[len("roottabs_"):]
217
218        if obj_id not in portal.objectIds():
219            errors.append("Object with %s id doesn't exist in portal root." % obj_id)
220
221        checked = True if checked == 'true' else False
222
223        if not errors:
224            portal[obj_id].update(excludeFromNav=not checked)
225
226            if checked:
227                message = "%s object was included into navigation." % obj_id
228            else:
229                message = "%s object was excluded from navigation." % obj_id
230            resp_dict['status_message'] = message
231            resp_dict['status_code'] = 200
232        else:
233            resp_dict['status_message'] = errors
234            resp_dict['status_code'] = 500
235        return resp_dict
236
237    def manage_ajax_toggleActionsVisibility(self, form, errs):
238        #Toggle visibility for portal actions
239        resp_dict = {}
240       
241        #TODO: parse, validate form
242        id = form['orig_id']
243        cat_name = form['category']
244        checked = form['visibility']
245
246        act_id, category, action, errors = self.manage_validateAction(id, cat_name)
247
248        if not errors:
249            self.updateAction(act_id, cat_name,
250                              {'id': act_id, 'visible': (checked == 'true') or False})
251            if checked == 'true':
252                message = "%s action is now visible." % act_id
253            else:
254                message = "%s action is now invisible." % act_id
255            resp_dict['status_message'] = message
256            resp_dict['status_code'] = 200
257        else:
258            resp_dict['status_message'] = errors
259            resp_dict['status_code'] = 500
260        return resp_dict
261   
262    def manage_ajax_deleteAction(self, form, errs):
263        """Delete portal action with given id & category"""
264        resp_dict = {}
265       
266        #TODO: parse, validate form
267        id = form['orig_id']
268        cat_name = form['category']
269
270        act_id, category, action, errors = self.manage_validateAction(id, cat_name)
271        if not errors:
272            self.deleteAction(act_id, cat_name)
273            resp_dict['status_message'] =  "%s action successfully removed." % act_id
274            resp_dict['status_code'] = 200
275        else:
276            resp_dict['status_message'] = errors
277            resp_dict['status_code'] = 500
278        return resp_dict
279
280    def manage_ajax_cancelEditting(self, form, errs):
281        """Hide edit form for given action"""
282        resp_dict = {}
283        #TODO: parse, validate form
284        id = form['orig_id']
285        cat_name = form['category']
286
287        act_id, category, action, errors = self.manage_validateAction(id, cat_name)
288
289        if not errors:
290            content = self.getActionsList(category=cat_name, tabs=[action, ])
291            resp_dict['content'] = content
292            resp_dict['status_message'] =  "Changes discarded."
293            resp_dict['status_code'] = 200
294        else:
295            resp_dict['status_message'] = errors
296            resp_dict['status_code'] = 500
297        return resp_dict
298
299    def manage_validateAction(self, action_id, cat_name):
300        """Check whether action with given id exists in cat_name category"""
301        errors = []
302        act_id = ""
303        category = ""
304        action = ""
305
306        act_id = action_id
307
308        try:
309            category = self.getActionCategory(cat_name)
310        except Exception:
311            errors.append("%s action category does not exist." % cat_name)
312
313        try:
314            action = category[act_id]
315        except Exception:
316            errors.append("No %s action in %s category." %
317                                   (act_id, cat_name))
318        return (act_id, category, action, errors)
319
320    def manage_ajax_saveAction(self, form, errs):
321        """Manage Method to update action"""
322        # extract posted data
323        resp_dict = {}
324        id, cat_name, data = self.parseEditForm(form)
325
326        # get category and action to edit
327        category = self.getActionCategory(cat_name)
328        action = category[id]
329
330        # validate posted data
331        errors = self.validateActionFields(
332            cat_name, data, allow_dup=(id == data['id']))
333
334        if not errors:
335            action = self.updateAction(id, cat_name, data)
336            content = self.getActionsList(category=cat_name, tabs=[action, ])
337            resp_dict['content'] = content
338            resp_dict['status_code'] = 200
339            resp_dict['status_message'] = "%s action successfully updated." % action.id
340        else:
341            resp_dict['status_code'] = 500
342            resp_dict['content'] = errors
343            resp_dict['status_message'] = "Please correct the indicated errors."
344        return resp_dict
345
346    def manage_ajax_moveAction(self, form, errs):
347        resp_dict = {}
348        cat_name = form['cat_name']
349        category = self.getActionCategory(cat_name)
350        components = urllib.unquote(form['actions']).split('&')
351        if self.sufix == '':
352            ids = [component[len(self.prefix):] for component in components]
353        else:
354            ids = [component[len(self.prefix):-len(self.sufix)]
355                   for component in components]
356        # do actual sorting
357        resp = category.moveObjectsByDelta(ids, -len(category.objectIds()))
358
359        if resp:
360            resp_dict['status_code'] = 200
361            resp_dict['status_message'] = "Actions successfully sorted."
362        else:
363            resp_dict['status_code'] = 500
364            resp_dict['status_message'] = "There was error while sorting, or list not changed"
365        return resp_dict
366
367    def manage_ajax_changeCategory(self, form, errs):
368        resp_dict = {}
369        errors = []
370
371        """Change action category to manage"""
372        cat_name = form['category']
373        # update actions list
374        resp_dict['actionslist'] = self.getActionsList(category=cat_name)
375        # update autogenerated sections
376        resp_dict['section'] = self.getAutoGenereatedSection(cat_name)
377        # and title
378        ts = getToolByName(self.context, 'translation_service')
379        resp_dict['title'] = ts.translate(self.getPageTitle(cat_name), context=self.context)
380   
381        if not errors:
382            resp_dict['status_code'] = 200
383            resp_dict['status_message'] = "Category changed successfully"
384        else:
385            resp_dict['status_code'] = 500
386            resp_dict['status_message'] = "There was error while changed category"
387
388        return resp_dict
389
390
391    ########################################
392    # Methods for processing configlet posts
393    ########################################
394
395    def manage_setAutogeneration(self, form, errors):
396        """Process managing autogeneration settings"""
397
398        # set excludeFromNav property for root objects
399        portal = self.plone_portal_state.portal()
400        generated_tabs = form.get("generated_tabs", '0')
401        nonfolderish_tabs = form.get("nonfolderish_tabs", '0')
402
403        for item in self.getRootTabs():
404            obj = getattr(portal, item['id'], None)
405            if obj is not None:
406                checked = form.get(item['id'], None)
407                if checked == '1':
408                    obj.update(excludeFromNav=False)
409                else:
410                    obj.update(excludeFromNav=True)
411
412        # set disable_folder_sections property
413        if int(generated_tabs) == 1:
414            self.setSiteProperties(disable_folder_sections=False)
415        else:
416            self.setSiteProperties(disable_folder_sections=True)
417
418        # set disable_nonfolderish_sections property
419        if int(nonfolderish_tabs) == 1:
420            self.setSiteProperties(disable_nonfolderish_sections=False)
421        else:
422            self.setSiteProperties(disable_nonfolderish_sections=True)
423
424        # after successfull form processing make redirect with good message
425        IStatusMessage(self.request).addStatusMessage(_(u"Changes saved!"),
426                                                      type="info")
427        self.redirect()
428        return False
429
430    def manage_addAction(self, form, errs):
431        """Manage method to add a new action to given category,
432        if category doesn't exist, create it
433        """
434        # extract posted data
435        id, cat_name, data = self.parseAddForm(form)
436
437        # validate posted data
438        errors = self.validateActionFields(cat_name, data)
439
440        # if not errors find (or create) category and set action to it
441        if not errors:
442            action = self.addAction(cat_name, data)
443            IStatusMessage(self.request).addStatusMessage(
444                _(u"'${id}' action successfully added.",
445                  mapping={'id': action.id}), type="info")
446            self.redirect(search="category=%s" % cat_name)
447            return False
448        else:
449            errs.update(errors)
450            IStatusMessage(self.request).addStatusMessage(
451                _(u"Please correct the indicated errors."), type="error")
452            return True
453
454    def manage_editAction(self, form, errs):
455        """Manage Method to update action"""
456        # extract posted data
457        id, cat_name, data = self.parseEditForm(form)
458
459        # get category and action to edit
460        category = self.getActionCategory(cat_name)
461        action = category[id]
462
463        # validate posted data
464        errors = self.validateActionFields(
465            cat_name, data, allow_dup=(id == data['id']))
466
467        if not errors:
468            action = self.updateAction(id, cat_name, data)
469            IStatusMessage(self.request).addStatusMessage(
470                _(u"'${id}' action saved.", mapping={'id': action.id}),
471                type="info")
472            self.redirect(search="category=%s" % cat_name)
473            return False
474        else:
475            errs.update(self.processErrors(
476                errors, sufix='_%s' % id))  # add edit form sufix to error ids
477            IStatusMessage(self.request).addStatusMessage(_(
478                u"Please correct the indicated errors."), type="error")
479            return True
480
481    def manage_deleteAction(self, form, errs):
482        """Manage Method to delete action"""
483        # extract posted data
484        id, cat_name, data = self.parseEditForm(form)
485
486        # get category and action to delete
487        category = self.getActionCategory(cat_name)
488        if id in category.objectIds():
489            self.deleteAction(id, cat_name)
490            IStatusMessage(self.request).addStatusMessage(
491                _(u"'${id}' action deleted.", mapping={'id': id}), type="info")
492            self.redirect(search="category=%s" % cat_name)
493            return False
494        else:
495            IStatusMessage(self.request).addStatusMessage(
496                _(u"No '${id}' action in '${cat_name}' category.",
497                  mapping={'id': id, 'cat_name': cat_name}),
498                type="error")
499            return True
500
501    def manage_moveUpAction(self, form, errs):
502        """Manage Method for moving up given action by one position"""
503        # extract posted data
504        id, cat_name, data = self.parseEditForm(form)
505
506        # get category and action to move
507        category = self.getActionCategory(cat_name)
508        if id in category.objectIds():
509            self.moveAction(id, cat_name, steps=1)
510            IStatusMessage(self.request).addStatusMessage(
511                _(u"'${id}' action moved up.", mapping={'id': id}),
512                type="info")
513            self.redirect(search="category=%s" % cat_name)
514            return False
515        else:
516            IStatusMessage(self.request).addStatusMessage(
517                _(u"No '${id}' action in '${cat_name}' category.",
518                  mapping={'id': id, 'cat_name': cat_name}), type="error")
519            return True
520
521    def manage_moveDownAction(self, form, errs):
522        """Manage Method for moving down given action by one position"""
523        # extract posted data
524        id, cat_name, data = self.parseEditForm(form)
525
526        # get category and action to move
527        category = self.getActionCategory(cat_name)
528        if id in category.objectIds():
529            self.moveAction(id, cat_name, steps=-1)
530            IStatusMessage(self.request).addStatusMessage(
531                _(u"'${id}' action moved down.", mapping={'id': id}),
532                type="info")
533            self.redirect(search="category=%s" % cat_name)
534            return False
535        else:
536            IStatusMessage(self.request).addStatusMessage(
537                _(u"No '${id}' action in '${cat_name}' category.",
538                  mapping={'id': id, 'cat_name': cat_name}),
539                type="error")
540            return True
541
542    def redirect(self, url="", search="", url_hash=""):
543        """Redirect to @@plonetabs-controlpanel configlet"""
544        if not url:
545            portal_url = self.plone_portal_state.portal_url()
546            url = '%s/%s' % (portal_url, "@@plonetabs-controlpanel")
547        if search:
548            search = '?%s' % search
549        if url_hash:
550            url_hash = '#%s' % url_hash
551        self.request.response.redirect("%s%s%s" % (url, search, url_hash))
552
553    ###################################
554    #
555    #  Methods - providers for templates
556    #
557    ###################################
558
559    def _charset(self):
560        pp = self.portal_properties
561        if pp is not None:
562            site_properties = getattr(pp, 'site_properties', None)
563            if site_properties is not None:
564                return site_properties.getProperty('default_charset', 'utf-8')
565        return 'utf-8'
566
567    def getPageTitle(self, category="portal_tabs"):
568        """See interface"""
569        portal_props = self.portal_properties
570        default_title = _(u"Plone '${cat_name}' Configuration",
571                          mapping={'cat_name': category})
572
573        if not hasattr(portal_props, PROPERTY_SHEET):
574            return default_title
575
576        sheet = getattr(portal_props, PROPERTY_SHEET)
577        if not hasattr(sheet, FIELD_NAME):
578            return default_title
579
580        field = sheet.getProperty(FIELD_NAME)
581        dict = {}
582        for line in field:
583            cat, title = line.split("|", 2)
584            dict[cat] = title
585
586        title = dict.get(category, None)
587        if title is None:
588            return default_title
589
590        charset = self._charset()
591        if not isinstance(title, unicode):
592            title = title.decode(charset)
593
594        return _(title)
595
596    def hasActions(self, category="portal_tabs"):
597        """See interface"""
598        tool = self.portal_actions
599        return len(tool.listActions(categories=[category, ])) > 0
600
601    def getPortalActions(self, category="portal_tabs"):
602        """See interface"""
603        portal_actions = self.portal_actions
604
605        if category not in portal_actions.objectIds():
606            return []
607
608        actions = []
609        for item in portal_actions[category].objectValues():
610            if IAction.providedBy(item):
611                actions.append(item)
612
613        return actions
614
615    def isGeneratedTabs(self):
616        """See interface"""
617        site_properties = self.portal_properties.site_properties
618        return not site_properties.getProperty("disable_folder_sections",
619                                               False)
620
621    def isNotFoldersGenerated(self):
622        """See interface"""
623        site_properties = self.portal_properties.site_properties
624        prop = site_properties.getProperty("disable_nonfolderish_sections",
625                                           False)
626        return not prop
627
628    def getActionsList(self, category="portal_tabs", errors={}, tabs=[]):
629        """See interface"""
630        kw = {'category': category, 'errors': errors}
631        if tabs:
632            kw['tabs'] = tabs
633        return self.actionslist_template(**kw)
634
635    def getAutoGenereatedSection(self, cat_name, errors={}):
636        """See interface"""
637        return self.autogenerated_template(category=cat_name, errors=errors)
638
639    def getGeneratedTabs(self):
640        """See interface"""
641        return self.autogenerated_list()
642
643    def _add_sort_option(self, query):
644        """ add sort option """
645        navtree_props = getattr(self.portal_properties, 'navtree_properties')
646        sortAttribute = navtree_props.getProperty('sortAttribute', None)
647        if sortAttribute is not None:
648            query['sort_on'] = sortAttribute
649
650            sortOrder = navtree_props.getProperty('sortOrder', None)
651            if sortOrder is not None:
652                query['sort_order'] = sortOrder
653
654    def getRootTabs(self):
655        """See interface"""
656        context = aq_inner(self.context)
657
658        portal_catalog = getToolByName(context, 'portal_catalog')
659        portal_properties = self.portal_properties
660        navtree_properties = getattr(portal_properties, 'navtree_properties')
661
662        # Build result dict
663        result = []
664
665        # check whether tabs autogeneration is turned on
666        if not self.isGeneratedTabs():
667            return result
668
669        query = {}
670        rootPath = getNavigationRoot(context)
671        query['path'] = {'query': rootPath, 'depth': 1}
672        query['portal_type'] = utils.typesToList(context)
673
674        self._add_sort_option(query)
675
676        if navtree_properties.getProperty('enable_wf_state_filtering', False):
677            query['review_state'] = navtree_properties.getProperty(
678                'wf_states_to_show', [])
679
680        query['is_default_page'] = False
681
682        if not self.isNotFoldersGenerated():
683            query['is_folderish'] = True
684
685        # Get ids not to list and make a dict to make the search fast
686        idsNotToList = navtree_properties.getProperty('idsNotToList', ())
687        excludedIds = {}
688        for id in idsNotToList:
689            excludedIds[id] = 1
690
691        rawresult = portal_catalog.searchResults(**query)
692
693        # now add the content to results
694        for item in rawresult:
695            if item.getId not in excludedIds:
696                result.append(self.getItem(item))
697
698        return result
699
700    def getItem(self, item):
701        """get item"""
702        context = aq_inner(self.context)
703        id_, item_url = get_view_url(item)
704        data = {
705            'name': utils.pretty_title_or_id(context, item),
706            'id': id_,
707            'url': item_url,
708            'description': item.Description,
709            'exclude_from_nav': item.exclude_from_nav,
710        }
711        return data
712
713    def getCategories(self):
714        """See interface"""
715        portal_actions = self.portal_actions
716        return portal_actions.objectIds()
717
718    #
719    # Methods to make this class looks like global sections viewlet
720    #
721
722    def test(self, condition, ifTrue, ifFalse):
723        """See interface"""
724        if condition:
725            return ifTrue
726        else:
727            return ifFalse
728
729    # methods for rendering global-sections viewlet via kss,
730    # due to bug in macroContent when global-section list is empty,
731    # ul have condition
732    def portal_tabs(self):
733        """See global-sections viewlet"""
734        actions = getMultiAdapter((self.context, self.request),
735                                  name=u'plone_context_state').actions()
736        actions_tabs = []
737        try:
738            # Plone 4 and higher
739            import plone.app.upgrade
740            plone.app.upgrade  # pyflakes
741        except ImportError:
742            actions_tabs = actions
743        if not actions_tabs and 'portal_tabs' in actions:
744            actions_tabs = actions['portal_tabs']
745
746        portal_tabs_view = getMultiAdapter((self.context, self.request),
747                                           name="portal_tabs_view")
748        return portal_tabs_view.topLevelTabs(actions=actions_tabs)
749
750    def selected_portal_tab(self):
751        """See global-sections viewlet"""
752        # BBB: compatibility with older plone versions.
753        # ``selectedTabs`` Python script was merged into the
754        # GlobalSectionsViewlet.
755        section_viewlet = setupViewletByName(self,
756                                             self.context,
757                                             self.request,
758                                             'plone.global_sections')
759        if hasattr(section_viewlet, 'selectedTabs'):
760            # Plone >= 4.3
761            selected_tabs = section_viewlet.selectedTabs(
762                default_tab='index_html',
763                portal_tabs=self.portal_tabs())
764        else:
765            # Plone < 4.3
766            selectedTabs = self.context.restrictedTraverse('selectedTabs')
767            selected_tabs = selectedTabs('index_html', self.context,
768                                         self.portal_tabs())
769
770        return selected_tabs['portal']
771
772    #
773    # Utility Methods
774    #
775
776    def fixExpression(self, expr):
777        """Fix expression appropriately for tal format"""
778        if expr.find('/') == 0:
779            return 'string:${portal_url}%s' % expr
780        elif re.compile('^(ht|f)tps?\:', re.I).search(expr):
781            return 'string:%s' % expr
782        # elif re.compile('^(python:|string:|not:|exists:|nocall:|path:)',
783                        # re.I).search(expr):
784            # return expr
785        elif expr.find(':') != -1:
786            return expr
787        else:
788            return 'string:${object_url}/%s' % expr
789
790    def copyAction(self, action):
791        """Copy action to dictionary"""
792        action_info = {'description': action.description}
793        for attr in ACTION_ATTRS:
794            action_info[attr] = getattr(action, attr)
795        return action_info
796
797    def _validate_expression(self, name, data, errors):
798        """ validate expression """
799        if data[name]:
800            try:
801                Expression(data[name])
802            except Exception, e:
803                mapping = {'expr': data[name]}
804                idx = data[name].find(':')
805                if idx != -1:
806                    mapping['expr_type'] = data[name][:idx]
807                errors[name] = self._formatError(e, **mapping)
808
809    def validateActionFields(self, cat_name, data, allow_dup=False):
810        """Check action fields on validity"""
811        errors = {}
812
813        if allow_dup:
814            # create dummy category to avoid id
815            # duplication during action update
816            category = ActionCategory(cat_name)
817        else:
818            # get or create (if necessary) actions category
819            category = self.getOrCreateCategory(cat_name)
820
821        # validate action id
822        chooser = INameChooser(category)
823        try:
824            chooser.checkName(data['id'], self.context)
825        except Exception, e:
826            errors['id'] = self._formatError(e, **{'id': data['id']})
827
828        # validate action name
829        if not data['title'].strip():
830            errors['title'] = _(u"Empty or invalid title specified")
831
832        # validate condition expression
833        self._validate_expression('available_expr', data, errors)
834
835        # validate action expression
836        self._validate_expression('url_expr', data, errors)
837
838        return errors
839
840    def _formatError(self, message, **kw):
841        """Make error message a little bit prettier to ease translation"""
842        charset = self._charset()
843        message = str(message)
844        message = message.replace('"', "'")
845        mapping = {}
846        for key, value in kw.items():
847            message = message.replace("'%s'" % value, "'${%s}'" % key)
848            # trying to work around zope.i18n issue
849            mapping[key] = unicode(value, charset)
850        message = message.strip()
851        return _(unicode(message, charset), mapping=mapping)
852
853    def processErrors(self, errors, prefix='', sufix=''):
854        """Add prefixes, sufixes to error ids
855        This is necessary during edit form validation,
856        because every edit form on the page has it's own sufix (id)
857        """
858        if not (prefix or sufix):
859            return errors
860
861        result = {}
862        for key, value in errors.items():
863            result['%s%s%s' % (prefix, key, sufix)] = value
864
865        return result
866
867    def parseEditForm(self, form):
868        """Extract all needed fields from edit form"""
869        # get original id and category
870        info = {}
871        id = form['orig_id']
872        category = form['category']
873
874        # preprocess 'visible' field (checkbox needs special checking)
875        if 'visible_%s' % id in form:
876            form['visible_%s' % id] = True
877        else:
878            form['visible_%s' % id] = False
879
880        # collect all action fields
881        for attr in ACTION_ATTRS:
882            info[attr] = form['%s_%s' % (attr, id)]
883
884        return (id, category, info)
885
886    def parseAddForm(self, form):
887        """Extract all needed fields from add form"""
888        info = {}
889        id = form['id']
890        category = form['category']
891
892        # preprocess 'visible' field (checkbox needs special checking)
893        if 'visible' in form and form['visible']:
894            form['visible'] = True
895        else:
896            form['visible'] = False
897
898        # fix expression fields
899        form['url_expr'] = self.fixExpression(form['url_expr'])
900
901        # collect all action fields
902        for attr in ACTION_ATTRS:
903            info[attr] = form[attr]
904
905        return (id, category, info)
906
907    def getActionCategory(self, name):
908        portal_actions = self.portal_actions
909        return portal_actions[name]
910
911    def getOrCreateCategory(self, name):
912        """Get or create (if necessary) category"""
913        portal_actions = self.portal_actions
914        if name not in map(lambda x: x.id,
915                           filter(lambda x: IActionCategory.providedBy(x),
916                                  portal_actions.objectValues())):
917            portal_actions._setObject(name, ActionCategory(name))
918        return self.getActionCategory(name)
919
920    def setSiteProperties(self, **kw):
921        """Change site_properties"""
922        site_properties = self.portal_properties.site_properties
923        site_properties.manage_changeProperties(**kw)
924        return True
925
926    def renderViewlet(self, manager, name):
927        if isinstance(manager, basestring):
928            manager = getMultiAdapter((self.context, self.request, self,),
929                                      IViewletManager, name=manager)
930        renderer = getMultiAdapter((self.context, self.request, self, manager),
931                                   IViewlet, name=name)
932        renderer = renderer.__of__(self.context)
933        renderer.update()
934        return renderer.render()
935    #
936    # Basic API to work with portal actions tool in a more pleasent way
937    #
938
939    def addAction(self, cat_name, data):
940        """Create and add new action to category with given name"""
941        id = data.pop('id')
942        category = self.getOrCreateCategory(cat_name)
943        action = Action(id, **data)
944        category._setObject(id, action)
945        return action
946
947    def updateAction(self, id, cat_name, data):
948        """Update action with given id and category"""
949        new_id = data.pop('id')
950        category = self.getActionCategory(cat_name)
951
952        # rename action if necessary
953        if id != new_id:
954            category.manage_renameObject(id, new_id)
955
956        # get action
957        action = category[new_id]
958
959        # update action properties
960        for attr in data.keys():
961            if attr in data:
962                action._setPropValue(attr, data[attr])
963
964        return action
965
966    def deleteAction(self, id, cat_name):
967        """Delete action with given id from given category"""
968        category = self.getActionCategory(cat_name)
969        category.manage_delObjects(ids=[id, ])
970        return True
971
972    def moveAction(self, id, cat_name, steps=0):
973        """Move action by a given steps"""
974        if steps != 0:
975            category = self.getActionCategory(cat_name)
976            if steps > 0:
977                category.moveObjectsUp([id, ], steps)
978            else:
979                category.moveObjectsDown([id, ], abs(steps))
980            return True
981        return False
982
983
984class PloneTabsMode(BrowserView):
985
986    def __call__(self):
987        mode = self.request.get(cookie_name, False)
988        if mode in ('plain', 'rich'):
989            return mode
990        mode = self.request.cookies.get(cookie_name, False)
991        if mode in ('plain', 'rich'):
992            return mode
993        return 'rich'
Note: See TracBrowser for help on using the repository browser.