source: products/qPloneTabs/branches/quintagroup.plonetabs/trunk/quintagroup/plonetabs/browser/plonetabs.py @ 147

Last change on this file since 147 was 147, checked in by mylan, 18 years ago

adding test of installation/uninstallation product

  • Property svn:eol-style set to native
File size: 26.8 KB
Line 
1import copy, sys
2from Acquisition import aq_inner
3from OFS.CopySupport import CopyError
4
5from zope.interface import implements
6from zope.component import getUtility, getMultiAdapter
7from zope.i18n import translate
8from zope.schema.interfaces import IVocabularyFactory
9from zope.exceptions import UserError
10from zope.app.container.interfaces import INameChooser
11
12from Products.CMFCore.utils import getToolByName
13from Products.CMFCore.interfaces import IAction, IActionCategory
14from Products.CMFCore.ActionInformation import Action, ActionCategory
15from Products.CMFCore.Expression import Expression
16from Products.CMFPlone import PloneMessageFactory as _
17from Products.CMFPlone import utils
18from Products.CMFPlone.browser.navigation import get_view_url
19from Products.Five.browser import BrowserView
20from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
21from Products.statusmessages.interfaces import IStatusMessage
22
23from plone.app.layout.navigation.root import getNavigationRoot
24from plone.app.kss.plonekssview import PloneKSSView
25from plone.app.workflow.remap import remap_workflow
26from plone.memoize.instance import memoize
27from kss.core import kssaction, KSSExplicitError
28
29from quintagroup.plonetabs.config import *
30from interfaces import IPloneTabsControlPanel
31
32ACTION_ATTRS = ["id", "title", "url_expr", "available_expr", "visible"]
33
34class PloneTabsControlPanel(PloneKSSView):
35   
36    implements(IPloneTabsControlPanel)
37   
38    template = ViewPageTemplateFile("templates/plonetabs.pt")
39    actionslist_template = ViewPageTemplateFile("templates/actionslist.pt")
40    autogenerated_template = ViewPageTemplateFile("templates/autogenerated.pt")
41   
42    # custom templates used to update page sections
43    sections_template = ViewPageTemplateFile("templates/sections.pt")
44   
45    def __call__(self):
46        """ Perform the update and redirect if necessary, or render the page """
47        postback = True
48        errors = {}
49        context = aq_inner(self.context)
50       
51        form = self.request.form
52        action = form.get("action", "")
53        submitted = form.get('form.submitted', False)
54       
55        # action handler def handler(self, form)
56        if submitted:
57            if form.has_key('add.add'):
58                postback = self.manage_addAction(form, errors)
59            elif form.has_key("edit.save"):
60                postback = self.manage_editAction(form, errors)
61            elif form.has_key("edit.delete"):
62                postback = self.manage_deleteAction(form, errors)
63            elif form.has_key("edit.moveup"):
64                postback = self.manage_moveUpAction(form, errors)
65            elif form.has_key("edit.movedown"):
66                postback = self.manage_moveDownAction(form, errors)
67            elif form.has_key("autogenerated.save"):
68                postback = self.manage_setAutogeneration(form, errors)
69            else:
70                postback = True
71       
72        if postback:
73            return self.template(errors=errors)
74   
75    ########################################
76    # Methods for processing configlet posts
77    ########################################
78   
79    def manage_setAutogeneration(self, form, errors):
80        """ Process managing autogeneration settings """
81       
82        # set excludeFromNav property for root objects
83        portal = getMultiAdapter((aq_inner(self.context), self.request), name='plone_portal_state').portal()
84        generated_tabs = form.get("generated_tabs", '0')
85        nonfolderish_tabs = form.get("nonfolderish_tabs", '0')
86       
87        for item in self.getRootTabs():
88            obj = getattr(portal, item['id'], None)
89            if obj is not None:
90                checked = form.get(item['id'], None)
91                if checked == '1':
92                    obj.update(excludeFromNav=False)
93                else:
94                    obj.update(excludeFromNav=True)
95
96        # set disable_folder_sections property
97        if int(generated_tabs) == 1:
98            self.setSiteProperties(disable_folder_sections=False)
99        else:
100            self.setSiteProperties(disable_folder_sections=True)
101       
102        # set disable_nonfolderish_sections property
103        if int(nonfolderish_tabs) == 1:
104            self.setSiteProperties(disable_nonfolderish_sections=False)
105        else:
106            self.setSiteProperties(disable_nonfolderish_sections=True)
107       
108        # after successfull form processing make redirect with good message
109        IStatusMessage(self.request).addStatusMessage(_(u"Changes saved!"), type="info")
110        self.redirect()
111        return False
112   
113    def manage_addAction(self, form, errs):
114        """ Manage method to add a new action to given category,
115            if category doesn't exist, create it """
116        # extract posted data
117        id, cat_name, data = self.parseAddForm(form)
118       
119        # validate posted data
120        errors = self.validateActionFields(cat_name, data)
121       
122        # if not errors find (or create) category and set action to it
123        if not errors:
124            action = self.addAction(cat_name, form)
125            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action successfully added." % action.id), type="info")
126            self.redirect(search="category=%s" % cat_name)
127            return False
128        else:
129            errs.update(errors)
130            IStatusMessage(self.request).addStatusMessage(_(u"Please correct the indicated errors."), type="error")
131            return True
132   
133    def manage_editAction(self, form, errs):
134        """ Manage Method to update action """
135        # extract posted data
136        id, cat_name, data = self.parseEditForm(form)
137       
138        # get category and action to edit
139        category = self.getActionCategory(cat_name)
140        action = category[id]
141       
142        # validate posted data
143        errors = self.validateActionFields(cat_name, data, allow_dup=True)
144       
145        if not errors:
146            action = self.updateAction(id, cat_name, data)
147            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action saved." % action.id), type="info")
148            self.redirect(search="category=%s" % cat_name)
149            return False
150        else:
151            errs.update(self.processErrors(errors, sufix='_%s' % id)) # add edit form sufix to error ids
152            IStatusMessage(self.request).addStatusMessage(_(u"Please correct the indicated errors."), type="error")
153            return True
154   
155    def manage_deleteAction(self, form, errs):
156        """ Manage Method to delete action """
157        # extract posted data
158        id, cat_name, data = self.parseEditForm(form)
159       
160        # get category and action to delete
161        category = self.getActionCategory(cat_name)
162        if id in category.objectIds():
163            self.deleteAction(id, cat_name)
164            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action deleted." % id), type="info")
165            self.redirect(search="category=%s" % cat_name)
166            return False
167        else:
168            IStatusMessage(self.request).addStatusMessage(_(u"No '%s' action in '%s' category." % (id, cat_name)), type="error")
169            return True
170   
171    def manage_moveUpAction(self, form, errs):
172        """ Manage Method for moving up given action by one position """
173        # extract posted data
174        id, cat_name, data = self.parseEditForm(form)
175       
176        # get category and action to move
177        category = self.getActionCategory(cat_name)
178        if id in category.objectIds():
179            self.moveAction(id, cat_name, steps=1)
180            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action moved up." % id), type="info")
181            self.redirect(search="category=%s" % cat_name)
182            return False
183        else:
184            IStatusMessage(self.request).addStatusMessage(_(u"No '%s' action in '%s' category." % (id, cat_name)), type="error")
185            return True
186   
187    def manage_moveDownAction(self, form, errs):
188        """ Manage Method for moving down given action by one position """
189        # extract posted data
190        id, cat_name, data = self.parseEditForm(form)
191       
192        # get category and action to move
193        category = self.getActionCategory(cat_name)
194        if id in category.objectIds():
195            self.moveAction(id, cat_name, steps=-1)
196            IStatusMessage(self.request).addStatusMessage(_(u"'%s' action moved down." % id), type="info")
197            self.redirect(search="category=%s" % cat_name)
198            return False
199        else:
200            IStatusMessage(self.request).addStatusMessage(_(u"No '%s' action in '%s' category." % (id, cat_name)), type="error")
201            return True
202   
203    def redirect(self, url="", search="", url_hash=""):
204        """ Redirect to @@plonetabs-controlpanel configlet """
205        portal_url =  getMultiAdapter((self.context, self.request), name=u"plone_portal_state").portal_url()
206        url = (url == "") and "%s/%s" % (portal_url, "@@plonetabs-controlpanel") or url
207        search = (search != "") and "?%s" % search or search
208        url_hash = (url_hash != "") and "#%s" % url_hash or url_hash
209        self.request.response.redirect("%s%s%s" % (url, search, url_hash))
210   
211    ###################################
212    #
213    #  Methods - providers for templates
214    #
215    ###################################
216   
217    def getPageTitle(self, category="portal_tabs"):
218        """ See interface """
219        portal_props = getToolByName(self.context, "portal_properties")
220        default_title = "Plone '%s' Configuration" % category
221       
222        if not hasattr(portal_props, PROPERTY_SHEET):
223            return default_title
224       
225        sheet = getattr(portal_props, PROPERTY_SHEET)
226        if not hasattr(sheet, FIELD_NAME):
227            return default_title
228       
229        field = sheet.getProperty(FIELD_NAME)
230        dict = {}
231        for line in field:
232            cat, title = line.split("|", 2)
233            dict[cat] = title
234       
235        return dict.get(category, None) or default_title
236   
237    def hasActions(self, category="portal_tabs"):
238        """ See interface """
239        return len(getToolByName(self.context, "portal_actions").listActions(categories=[category,])) > 0
240   
241    def getPortalActions(self, category="portal_tabs"):
242        """ See interface """
243        portal_actions = getToolByName(self.context, "portal_actions")
244       
245        if category not in portal_actions.objectIds():
246            return []
247       
248        actions = []
249        for item in portal_actions[category].objectValues():
250            if IAction.providedBy(item):
251                actions.append(item)
252       
253        return actions
254   
255    def isGeneratedTabs(self):
256        """ See interface """
257        site_properties = getToolByName(self.context, "portal_properties").site_properties
258        return not site_properties.getProperty("disable_folder_sections", False)
259   
260    def isNotFoldersGenerated(self):
261        """ See interface """
262        site_properties = getToolByName(self.context, "portal_properties").site_properties
263        return not site_properties.getProperty("disable_nonfolderish_sections", False)
264   
265    def getActionsList(self, category="portal_tabs", errors={}):
266        """ See interface """
267        return self.actionslist_template(category=category, errors=errors)
268   
269    def getGeneratedTabs(self):
270        """ See interface """
271        return self.autogenerated_template()
272   
273    def getRootTabs(self):
274        """ See interface """
275        context = aq_inner(self.context)
276       
277        portal_catalog = getToolByName(context, 'portal_catalog')
278        portal_properties = getToolByName(context, 'portal_properties')
279        navtree_properties = getattr(portal_properties, 'navtree_properties')
280       
281        # Build result dict
282        result = []
283       
284        # check whether tabs autogeneration is turned on
285        if not self.isGeneratedTabs():
286            return result
287       
288        query = {}
289        rootPath = getNavigationRoot(context)
290        query['path'] = {'query' : rootPath, 'depth' : 1}
291        query['portal_type'] = utils.typesToList(context)
292       
293        sortAttribute = navtree_properties.getProperty('sortAttribute', None)
294        if sortAttribute is not None:
295            query['sort_on'] = sortAttribute
296           
297            sortOrder = navtree_properties.getProperty('sortOrder', None)
298            if sortOrder is not None:
299                query['sort_order'] = sortOrder
300       
301        if navtree_properties.getProperty('enable_wf_state_filtering', False):
302            query['review_state'] = navtree_properties.getProperty('wf_states_to_show', [])
303       
304        query['is_default_page'] = False
305
306        if not self.isNotFoldersGenerated():
307            query['is_folderish'] = True
308
309        # Get ids not to list and make a dict to make the search fast
310        idsNotToList = navtree_properties.getProperty('idsNotToList', ())
311        excludedIds = {}
312        for id in idsNotToList:
313            excludedIds[id]=1
314
315        rawresult = portal_catalog.searchResults(**query)
316
317        # now add the content to results
318        for item in rawresult:
319            if not excludedIds.has_key(item.getId):
320                id, item_url = get_view_url(item)
321                data = {'name'       : utils.pretty_title_or_id(context, item),
322                        'id'         : id,
323                        'url'        : item_url,
324                        'description': item.Description,
325                        'exclude_from_nav' : item.exclude_from_nav}
326                result.append(data)
327       
328        return result
329   
330    def getCategories(self):
331        """ See interface """
332        portal_actions = getToolByName(self.context, "portal_actions")
333        return portal_actions.objectIds()
334   
335    #
336    # Methods to make this class looks like global sections viewlet
337    #
338   
339    def test(self, condition, ifTrue, ifFalse):
340        """ See interface """
341        if condition:
342            return ifTrue
343        else:
344            return ifFalse
345   
346    # methods for rendering global-sections viewlet via kss,
347    # due to bug in macroContent when global-section list is empty,
348    # ul have condition
349    def portal_tabs(self):
350        """ See global-sections viewlet """
351        actions = context_state = getMultiAdapter((self.context, self.request), name=u"plone_context_state").actions()
352        portal_tabs_view = getMultiAdapter((self.context, self.request), name="portal_tabs_view")
353       
354        return portal_tabs_view.topLevelTabs(actions=actions)
355   
356    def selected_portal_tab(self):
357        """ See global-sections viewlet """
358        selectedTabs = self.context.restrictedTraverse('selectedTabs')
359        selected_tabs = selectedTabs('index_html', self.context, self.portal_tabs())
360       
361        return selected_tabs['portal']
362   
363    ##########################
364    #
365    # KSS Server Actions
366    #
367    ##########################
368   
369    def validateAction(self, id, category, prefix="tabslist_"):
370        """ If action with given id and category doesn't exist - raise kss exception """
371        portal_actions = getToolByName(self.context, "portal_actions")
372       
373        # remove prefix, added for making ids on configlet unique ("tabslist_")
374        act_id = id[len("tabslist_"):]
375       
376        if category not in portal_actions.objectIds():
377            raise KSSExplicitError, "Unexistent root portal actions category %s" % category
378       
379        cat_container = portal_actions[category]
380        if act_id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())):
381            raise KSSExplicitError, "%s action does not exist in %s category" % (act_id, category)
382       
383        return (cat_container, act_id)
384   
385    @kssaction
386    def toggleGeneratedTabs(self, field, checked='0'):
387        """ Toggle autogenaration setting on configlet """
388       
389        changeProperties = getToolByName(self.context, "portal_properties").site_properties.manage_changeProperties
390        if checked == '1':
391            changeProperties(**{field : False})
392        else:
393            changeProperties(**{field : True})
394       
395        ksscore = self.getCommandSet("core")
396        replace_id = "roottabs"
397        content = self.getGeneratedTabs()
398       
399        ksscore.replaceInnerHTML(ksscore.getHtmlIdSelector(replace_id), content, withKssSetup="True")
400       
401        # update global-sections viewlet
402        self.updatePortalTabs()
403   
404    @kssaction
405    def toggleActionsVisibility(self, id, checked='0', category=None):
406        """ Toggle visibility for portal actions """
407        portal_actions = getToolByName(self.context, "portal_actions")
408        cat_container, act_id = self.validateAction(id, category)
409       
410        if checked == '1':
411            checked = True
412        else:
413            checked = False
414       
415        cat_container[act_id].visible = checked
416       
417        ksscore = self.getCommandSet("core")
418        if checked:
419            ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible")
420        else:
421            ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible")
422       
423        self.updatePage(category)
424   
425    @kssaction
426    def toggleRootsVisibility(self, id, checked='0'):
427        """ Toggle visibility for portal root objects (exclude_from_nav) """
428        portal = getMultiAdapter((aq_inner(self.context), self.request), name='plone_portal_state').portal()
429       
430        # remove prefix, added for making ids on configlet unique ("roottabs_")
431        obj_id = id[len("roottabs_"):]
432       
433        if obj_id not in portal.objectIds():
434            raise KSSExplicitError, "Object with %s id doesn't exist in portal root" % obj_id
435       
436        if checked == '1':
437            checked = True
438        else:
439            checked = False
440       
441        portal[obj_id].update(excludeFromNav=not checked)
442       
443        ksscore = self.getCommandSet("core")
444        if checked:
445            ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible")
446        else:
447            ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible")
448       
449        # update global-sections viewlet
450        self.updatePortalTabs()
451   
452    @kssaction
453    def kss_deleteAction(self, id, category):
454        """ Delete portal action with given id & category """
455        portal_actions = getToolByName(self.context, "portal_actions")
456        cat_container, act_id = self.validateAction(id, category)
457       
458        cat_container.manage_delObjects(ids=[act_id,])
459       
460        # update action list on client
461        ksscore = self.getCommandSet("core")
462       
463        ksscore.deleteNode(ksscore.getHtmlIdSelector(id))
464       
465        # add "noitems" class to Reorder controls to hide it
466        if not filter(lambda x: IAction.providedBy(x), cat_container.objectValues()):
467            ksscore.addClass(ksscore.getHtmlIdSelector("reorder"), value="noitems")
468       
469        # XXX TODO: fade effect during removing, for this kukit js action/command plugin needed
470       
471        self.updatePage(category)
472   
473    @kssaction
474    def oldAddAction(self, id, name, action='', category='portal_tabs', condition='', visible=False):
475        pass
476   
477    @kssaction
478    def editAction(self, id, category):
479        """ Show edit form for given action """
480        cat_container, act_id = self.validateAction(id, category)
481       
482        # collect data
483        action_info = self.copyAction(cat_container[act_id])
484        action_info["editing"] = True
485       
486        ksscore = self.getCommandSet("core")
487        content = self.actionslist_template(tabs=[action_info,])
488        replace_id = id
489       
490        ksscore.replaceHTML(ksscore.getHtmlIdSelector(replace_id), content)
491       
492        # focus name field
493        ksscore.focus(ksscore.getCssSelector("#%s input[name=name_%s]" % (id, act_id)))
494   
495    @kssaction
496    def editCancel(self, id, category):
497        """ Hide edit form for given action """
498        cat_container, act_id = self.validateAction(id, category)
499       
500        ksscore = self.getCommandSet("core")
501        content = self.actionslist_template(tabs=[cat_container[act_id],])
502        replace_id = id
503       
504        ksscore.replaceHTML(ksscore.getHtmlIdSelector(replace_id), content)
505   
506    #
507    # Utility Methods
508    #
509   
510    def copyAction(self, action):
511        """ Copyt action to dictionary """
512        action_info = {}
513        for attr in ACTION_ATTRS:
514            action_info[attr] = getattr(action, attr)
515        return action_info
516   
517    def validateActionFields(self, cat_name, data, allow_dup=False):
518        """ Check action fields on validity """
519        errors = {}
520       
521        if allow_dup:
522            category = ActionCategory(cat_name)           # create dummy category to avoid id duplication during action update
523        else:
524            category = self.getOrCreateCategory(cat_name) # get or create (if necessary) actions category
525       
526        # validate action id
527        chooser = INameChooser(category)
528        try:
529            chooser.checkName(data['id'], self.context)
530        except Exception, e:
531            errors['id'] = "%s" % str(e)
532       
533        # validate action name
534        if not data['title'].strip():
535            errors['title'] = 'Empty or invalid title specified'
536       
537        # validate condition expression
538        if data['available_expr']:
539            try:
540                Expression(data['available_expr'])
541            except Exception, e:
542                errors["available_expr"] = "%s" % str(e)
543       
544        # validate action expression
545        if data['url_expr']:
546            try:
547                Expression(data['url_expr'])
548            except Exception, e:
549                errors["url_expr"] = "%s" % str(e)
550       
551        return errors
552   
553    def processErrors(self, errors, prefix='', sufix=''):
554        """ Add prefixes, sufixes to error ids
555            This is necessary during edit form validation,
556            because every edit form on the page has it's own sufix (id) """
557        if not (prefix or sufix):
558            return errors
559       
560        result = {}
561        for key, value in errors.items():
562            result['%s%s%s' % (prefix, key, sufix)] = value
563       
564        return result
565   
566    def parseEditForm(self, form):
567        """ Extract all needed fields from edit form """
568        # get original id and category
569        info = {}
570        id = form['orig_id']
571        category = form['category']
572       
573        # preprocess 'visible' field (checkbox needs special checking)
574        if form.has_key('visible_%s' % id):
575            form['visible_%s' % id] = True
576        else:
577            form['visible_%s' % id] = False
578       
579        # collect all action fields
580        for attr in ACTION_ATTRS:
581            info[attr] = form['%s_%s' % (attr, id)]
582       
583        return (id, category, info)
584   
585    def parseAddForm(self, form):
586        """ Extract all needed fields from add form """
587        info = {}
588        id = form['id']
589        category = form['category']
590       
591        # preprocess 'visible' field (checkbox needs special checking)
592        if form.has_key('visible'):
593            form['visible'] = True
594        else:
595            form['visible'] = False
596       
597        # collect all action fields
598        for attr in ACTION_ATTRS:
599            info[attr] = form[attr]
600       
601        return (id, category, info)
602   
603    def getActionCategory(self, name):
604        portal_actions = getToolByName(self.context, 'portal_actions')
605        return portal_actions[name]
606   
607    def getOrCreateCategory(self, name):
608        """ Get or create (if necessary) category """
609        portal_actions = getToolByName(self.context, 'portal_actions')
610        if name not in map(lambda x: x.id, filter(lambda x: IActionCategory.providedBy(x), portal_actions.objectValues())):
611            portal_actions._setObject(name, ActionCategory(name))
612        return self.getActionCategory(name)
613   
614    def setSiteProperties(self, **kw):
615        """ Change site_properties """
616        site_properties = getToolByName(self.context, "portal_properties").site_properties
617        site_properties.manage_changeProperties(**kw)
618        return True
619   
620    #
621    # Basic API to work with portal actions tool in a more pleasent way
622    #
623   
624    def addAction(self, cat_name, data):
625        """ Create and add new action to category with given name """
626        id = data.pop('id')
627        category = self.getOrCreateCategory(cat_name)
628        action = Action(id, **data)
629        category._setObject(id, action)
630        return action
631   
632    def updateAction(self, id, cat_name, data):
633        """ Update action with given id and category """
634        new_id = data.pop('id')
635        category = self.getActionCategory(cat_name)
636       
637        # rename action if necessary
638        if id != new_id:
639            category.manage_renameObject(id, new_id)
640       
641        # get action
642        action = category[new_id]
643       
644        # update action properties
645        for attr in ACTION_ATTRS:
646            if data.has_key(attr):
647                action._setPropValue(attr, data[attr])
648       
649        return action
650   
651    def deleteAction(self, id, cat_name):
652        """ Delete action with given id from given category """
653        category = self.getActionCategory(cat_name)
654        category.manage_delObjects(ids=[id,])
655        return True
656   
657    def moveAction(self, id, cat_name, steps=0):
658        """ Move action by a given steps """
659        if steps != 0:
660            category = self.getActionCategory(cat_name)
661            if steps > 0:
662                category.moveObjectsUp([id,], steps)
663            else:
664                category.moveObjectsDown([id,], abs(steps))
665            return True
666        return False
667   
668    #
669    # KSS Methods that are used to update different parts of the page
670    # accordingly to category
671    #
672   
673    def updatePage(self, category):
674        """ Seek for according method in class and calls it if found
675            Example of making up method's name:
676                portal_tabs => updatePortalTabs """
677        method_name = 'update%sPageSection' % ''.join(map(lambda x: x.capitalize(), category.split('_')))
678        if hasattr(self, method_name):
679            getattr(self, method_name)()
680   
681    def updatePortalTabsPageSection(self):
682        """ Method for updating global-sections on client """
683        ksscore = self.getCommandSet("core")
684        ksscore.replaceHTML(
685            ksscore.getHtmlIdSelector("portal-globalnav"),
686            self.sections_template(),
687            withKssSetup="False")
688   
689    def updateSiteActionsPageSection(self):
690        """ Method for updating site action on client """
691        ksszope = self.getCommandSet("zope")
692        ksszope.refreshViewlet(
693            self.getCommandSet("core").getHtmlIdSelector("portal-siteactions"),
694            "plone.portalheader",
695            "plone.site_actions")
696   
697    def updateUserPageSection(self):
698        """ Method for updating site action on client """
699        ksszope = self.getCommandSet("zope")
700        ksszope.refreshViewlet(
701            self.getCommandSet("core").getHtmlIdSelector("portal-personaltools-wrapper"),
702            "plone.portaltop",
703            "plone.personal_bar")
704
705
Note: See TracBrowser for help on using the repository browser.