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

Last change on this file was 3682, checked in by mike, 11 years ago

add icon field

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