source: products/quintagroup.plonetabs/trunk/quintagroup/plonetabs/browser/plonetabs.py @ 746

Last change on this file since 746 was 746, checked in by crchemist, 17 years ago

2.2.1 version taged.

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