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

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

rewrite kss actions via jquery ajax; remove all related to kss resources

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