import urllib import re from Acquisition import aq_inner from DateTime import DateTime from zope.interface import implements from zope.component import getMultiAdapter from zope.app.container.interfaces import INameChooser from zope.viewlet.interfaces import IViewletManager, IViewlet from plone.app.layout.navigation.root import getNavigationRoot from plone.app.kss.plonekssview import PloneKSSView from kss.core import kssaction, KSSExplicitError from Products.CMFCore.utils import getToolByName from Products.CMFCore.interfaces import IAction, IActionCategory from Products.CMFCore.ActionInformation import Action, ActionCategory from Products.CMFCore.Expression import Expression from Products.CMFPlone import utils from Products.CMFPlone.browser.navigation import get_view_url from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.Five.browser import BrowserView from Products.statusmessages.interfaces import IStatusMessage from quintagroup.plonetabs.config import * from quintagroup.plonetabs import messageFactory as _ from interfaces import IPloneTabsControlPanel ACTION_ATTRS = ["id", "title", "url_expr", "available_expr", "visible"] UI_ATTRS = {"id": "id", "title": "name", "url_expr": "action", "available_expr": "condition", "visible": "visible"} bad_id = re.compile(r'[^a-zA-Z0-9-_~,.$\(\)# @]').search cookie_name = 'ploneTabsMode' class PloneTabsControlPanel(PloneKSSView): implements(IPloneTabsControlPanel) template = ViewPageTemplateFile("templates/plonetabs.pt") actionslist_template = ViewPageTemplateFile("templates/actionslist.pt") autogenerated_template = ViewPageTemplateFile("templates/autogenerated.pt") autogenerated_list = ViewPageTemplateFile("templates/autogeneratedlist.pt") link_template = ViewPageTemplateFile("templates/changemodelink.pt") # custom templates used to update page sections sections_template = ViewPageTemplateFile("templates/sections.pt") # configuration variables prefix = "tabslist_" sufix = "" def __call__(self): """Perform the update and redirect if necessary, or render the page""" postback = True errors = {} context = aq_inner(self.context) form = self.request.form action = form.get("action", "") submitted = form.get('form.submitted', False) # action handler def handler(self, form) if submitted: if form.has_key('add.add'): postback = self.manage_addAction(form, errors) elif form.has_key("edit.save"): postback = self.manage_editAction(form, errors) elif form.has_key("edit.delete"): postback = self.manage_deleteAction(form, errors) elif form.has_key("edit.moveup"): postback = self.manage_moveUpAction(form, errors) elif form.has_key("edit.movedown"): postback = self.manage_moveDownAction(form, errors) elif form.has_key("autogenerated.save"): postback = self.manage_setAutogeneration(form, errors) else: postback = True mode = self.request.get(cookie_name, False) if mode in ('plain', 'rich'): # set cookie to remember the choice expires = (DateTime() + 365).toZone('GMT').rfc822() self.request.response.setCookie(cookie_name, mode, path='/', expires=expires) if postback: return self.template(errors=errors) ######################################## # Methods for processing configlet posts ######################################## def manage_setAutogeneration(self, form, errors): """Process managing autogeneration settings""" # set excludeFromNav property for root objects portal = getMultiAdapter((aq_inner(self.context), self.request), name='plone_portal_state').portal() generated_tabs = form.get("generated_tabs", '0') nonfolderish_tabs = form.get("nonfolderish_tabs", '0') for item in self.getRootTabs(): obj = getattr(portal, item['id'], None) if obj is not None: checked = form.get(item['id'], None) if checked == '1': obj.update(excludeFromNav=False) else: obj.update(excludeFromNav=True) # set disable_folder_sections property if int(generated_tabs) == 1: self.setSiteProperties(disable_folder_sections=False) else: self.setSiteProperties(disable_folder_sections=True) # set disable_nonfolderish_sections property if int(nonfolderish_tabs) == 1: self.setSiteProperties(disable_nonfolderish_sections=False) else: self.setSiteProperties(disable_nonfolderish_sections=True) # after successfull form processing make redirect with good message IStatusMessage(self.request).addStatusMessage(_(u"Changes saved!"), type="info") self.redirect() return False def manage_addAction(self, form, errs): """Manage method to add a new action to given category, if category doesn't exist, create it """ # extract posted data id, cat_name, data = self.parseAddForm(form) # validate posted data errors = self.validateActionFields(cat_name, data) # if not errors find (or create) category and set action to it if not errors: action = self.addAction(cat_name, data) IStatusMessage(self.request).addStatusMessage( _(u"'${id}' action successfully added.", mapping={'id': action.id}), type="info") self.redirect(search="category=%s" % cat_name) return False else: errs.update(errors) IStatusMessage(self.request).addStatusMessage( _(u"Please correct the indicated errors."), type="error") return True def manage_editAction(self, form, errs): """Manage Method to update action""" # extract posted data id, cat_name, data = self.parseEditForm(form) # get category and action to edit category = self.getActionCategory(cat_name) action = category[id] # validate posted data errors = self.validateActionFields(cat_name, data, allow_dup=(id == data['id'])) if not errors: action = self.updateAction(id, cat_name, data) IStatusMessage(self.request).addStatusMessage( _(u"'${id}' action saved.", mapping={'id': action.id}), type="info") self.redirect(search="category=%s" % cat_name) return False else: errs.update(self.processErrors(errors, sufix='_%s' % id)) # add edit form sufix to error ids IStatusMessage(self.request).addStatusMessage( _(u"Please correct the indicated errors."), type="error") return True def manage_deleteAction(self, form, errs): """Manage Method to delete action""" # extract posted data id, cat_name, data = self.parseEditForm(form) # get category and action to delete category = self.getActionCategory(cat_name) if id in category.objectIds(): self.deleteAction(id, cat_name) IStatusMessage(self.request).addStatusMessage( _(u"'${id}' action deleted.", mapping={'id': id}), type="info") self.redirect(search="category=%s" % cat_name) return False else: IStatusMessage(self.request).addStatusMessage( _(u"No '${id}' action in '${cat_name}' category.", mapping={'id': id, 'cat_name': cat_name}), type="error") return True def manage_moveUpAction(self, form, errs): """Manage Method for moving up given action by one position""" # extract posted data id, cat_name, data = self.parseEditForm(form) # get category and action to move category = self.getActionCategory(cat_name) if id in category.objectIds(): self.moveAction(id, cat_name, steps=1) IStatusMessage(self.request).addStatusMessage( _(u"'${id}' action moved up.", mapping={'id': id}), type="info") self.redirect(search="category=%s" % cat_name) return False else: IStatusMessage(self.request).addStatusMessage( _(u"No '${id}' action in '${cat_name}' category.", mapping={'id': id, 'cat_name': cat_name}), type="error") return True def manage_moveDownAction(self, form, errs): """Manage Method for moving down given action by one position""" # extract posted data id, cat_name, data = self.parseEditForm(form) # get category and action to move category = self.getActionCategory(cat_name) if id in category.objectIds(): self.moveAction(id, cat_name, steps=-1) IStatusMessage(self.request).addStatusMessage( _(u"'${id}' action moved down.", mapping={'id': id}), type="info") self.redirect(search="category=%s" % cat_name) return False else: IStatusMessage(self.request).addStatusMessage( _(u"No '${id}' action in '${cat_name}' category.", mapping={'id': id, 'cat_name': cat_name}), type="error") return True def redirect(self, url="", search="", url_hash=""): """Redirect to @@plonetabs-controlpanel configlet""" if not url: portal_url = getMultiAdapter((self.context, self.request), name=u"plone_portal_state").portal_url() url = '%s/%s' % (portal_url, "@@plonetabs-controlpanel") if search: search = '?%s' % search if url_hash: url_hash = '#%s' % url_hash self.request.response.redirect("%s%s%s" % (url, search, url_hash)) ################################### # # Methods - providers for templates # ################################### def _charset(self): pp = getToolByName(self.context, 'portal_properties', None) if pp is not None: site_properties = getattr(pp, 'site_properties', None) if site_properties is not None: return site_properties.getProperty('default_charset', 'utf-8') return 'utf-8' def getPageTitle(self, category="portal_tabs"): """See interface""" portal_props = getToolByName(self.context, "portal_properties") default_title = _(u"Plone '${cat_name}' Configuration", mapping={'cat_name': category}) if not hasattr(portal_props, PROPERTY_SHEET): return default_title sheet = getattr(portal_props, PROPERTY_SHEET) if not hasattr(sheet, FIELD_NAME): return default_title field = sheet.getProperty(FIELD_NAME) dict = {} for line in field: cat, title = line.split("|", 2) dict[cat] = title title = dict.get(category, None) if title is None: return default_title charset = self._charset() if not isinstance(title, unicode): title = title.decode(charset) return _(title) def hasActions(self, category="portal_tabs"): """See interface""" tool = getToolByName(self.context, "portal_actions") return len(tool.listActions(categories=[category, ])) > 0 def getPortalActions(self, category="portal_tabs"): """See interface""" portal_actions = getToolByName(self.context, "portal_actions") if category not in portal_actions.objectIds(): return [] actions = [] for item in portal_actions[category].objectValues(): if IAction.providedBy(item): actions.append(item) return actions def isGeneratedTabs(self): """See interface""" site_properties = getToolByName(self.context, "portal_properties").site_properties return not site_properties.getProperty("disable_folder_sections", False) def isNotFoldersGenerated(self): """See interface""" site_properties = getToolByName(self.context, "portal_properties").site_properties prop = site_properties.getProperty("disable_nonfolderish_sections", False) return not prop def getActionsList(self, category="portal_tabs", errors={}, tabs=[]): """See interface""" kw = {'category': category, 'errors': errors} if tabs: kw['tabs'] = tabs return self.actionslist_template(**kw) def getAutoGenereatedSection(self, cat_name, errors={}): """See interface""" return self.autogenerated_template(category=cat_name, errors=errors) def getGeneratedTabs(self): """See interface""" return self.autogenerated_list() def getRootTabs(self): """See interface""" context = aq_inner(self.context) portal_catalog = getToolByName(context, 'portal_catalog') portal_properties = getToolByName(context, 'portal_properties') navtree_properties = getattr(portal_properties, 'navtree_properties') # Build result dict result = [] # check whether tabs autogeneration is turned on if not self.isGeneratedTabs(): return result query = {} rootPath = getNavigationRoot(context) query['path'] = {'query': rootPath, 'depth': 1} query['portal_type'] = utils.typesToList(context) sortAttribute = navtree_properties.getProperty('sortAttribute', None) if sortAttribute is not None: query['sort_on'] = sortAttribute sortOrder = navtree_properties.getProperty('sortOrder', None) if sortOrder is not None: query['sort_order'] = sortOrder if navtree_properties.getProperty('enable_wf_state_filtering', False): query['review_state'] = navtree_properties.getProperty( 'wf_states_to_show', []) query['is_default_page'] = False if not self.isNotFoldersGenerated(): query['is_folderish'] = True # Get ids not to list and make a dict to make the search fast idsNotToList = navtree_properties.getProperty('idsNotToList', ()) excludedIds = {} for id in idsNotToList: excludedIds[id] = 1 rawresult = portal_catalog.searchResults(**query) # now add the content to results for item in rawresult: if not excludedIds.has_key(item.getId): id, item_url = get_view_url(item) data = {'name': utils.pretty_title_or_id(context, item), 'id': id, 'url': item_url, 'description': item.Description, 'exclude_from_nav': item.exclude_from_nav} result.append(data) return result def getCategories(self): """See interface""" portal_actions = getToolByName(self.context, "portal_actions") return portal_actions.objectIds() # # Methods to make this class looks like global sections viewlet # def test(self, condition, ifTrue, ifFalse): """See interface""" if condition: return ifTrue else: return ifFalse # methods for rendering global-sections viewlet via kss, # due to bug in macroContent when global-section list is empty, # ul have condition def portal_tabs(self): """See global-sections viewlet""" actions = getMultiAdapter((self.context, self.request), name=u'plone_context_state').actions() try: # Plone 4 and higher import plone.app.upgrade if 'portal_tabs' in actions: actions_tabs = actions['portal_tabs'] else: actions_tabs = [] except ImportError: actions_tabs = actions portal_tabs_view = getMultiAdapter((self.context, self.request), name="portal_tabs_view") return portal_tabs_view.topLevelTabs(actions=actions_tabs) def selected_portal_tab(self): """See global-sections viewlet""" selectedTabs = self.context.restrictedTraverse('selectedTabs') selected_tabs = selectedTabs('index_html', self.context, self.portal_tabs()) return selected_tabs['portal'] ########################## # # KSS Server Actions # ########################## @kssaction def kss_insertModeLink(self): """Insert link which allows change categories between plain and rich""" ksscore = self.getCommandSet('core') html = self.link_template() target = ksscore.getCssSelector('.link-parent') ksscore.insertHTMLAfter(target, html, withKssSetup='False') @kssaction def kss_changeCategory(self, cat_name): """Change action category to manage""" ksscore = self.getCommandSet('core') # update actions list actionslist = self.getActionsList(category=cat_name) ksscore.replaceInnerHTML(ksscore.getHtmlIdSelector('tabslist'), actionslist) # update autogenerated sections section = self.getAutoGenereatedSection(cat_name) ksscore.replaceHTML( ksscore.getHtmlIdSelector('autogeneration_section'), section) # and title ts = getToolByName(self.context, 'translation_service') title = ts.translate(self.getPageTitle(cat_name), context=self.context) ksscore.replaceInnerHTML( ksscore.getHtmlIdSelector('plonetabs-form-title'), title) # update category hidden field on adding form ksscore.setAttribute(ksscore.getCssSelector( 'form[name=addaction_form] input[name=category]'), 'value', cat_name) # update state variable 'plonetabs-category' on client ksscore.setStateVar('plonetabs-category', cat_name) # hide adding form self.kss_hideAddForm() # issue portal status message self.kss_issueMessage(_(u"Category changed successfully.")) @kssaction def kss_toggleGeneratedTabs(self, field, checked='0'): """Toggle autogenaration setting on configlet""" if checked == '1': self.setSiteProperties(**{field: False}) if field == 'disable_nonfolderish_sections': message = _(u"Generated not folderish tabs switched on.") else: message = _(u"Generated tabs switched on.") else: self.setSiteProperties(**{field: True}) if field == 'disable_nonfolderish_sections': message = _(u"Generated not folderish tabs switched off.") else: message = _(u"Generated tabs switched off.") # update client ksscore = self.getCommandSet("core") content = self.getGeneratedTabs() ksscore.replaceInnerHTML(ksscore.getHtmlIdSelector('roottabs'), content) # update global-sections viewlet self.updatePortalTabsPageSection() # issue portal status message self.kss_issueMessage(message) @kssaction def kss_toggleRootsVisibility(self, id, checked='0'): """Toggle visibility for portal root objects (exclude_from_nav)""" portal = getMultiAdapter((aq_inner(self.context), self.request), name='plone_portal_state').portal() # remove prefix, added for making ids on configlet unique ("roottabs_") obj_id = id[len("roottabs_"):] if obj_id not in portal.objectIds(): raise KSSExplicitError, \ _(u"Object with '${id}' id doesn't exist in portal root.", mapping={'id': obj_id}) if checked == '1': checked = True else: checked = False portal[obj_id].update(excludeFromNav=not checked) # update client ksscore = self.getCommandSet("core") if checked: ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible") message = _(u"'${id}' object was included into navigation.", mapping={'id': obj_id}) else: ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible") message = _(u"'${id}' object was excluded from navigation.", mapping={'id': obj_id}) # update global-sections viewlet self.updatePortalTabsPageSection() # issue portal status message self.kss_issueMessage(message) @kssaction def kss_toggleActionsVisibility(self, id, checked='0', cat_name=None): """Toggle visibility for portal actions""" # validate input act_id, category, action = self.kss_validateAction(id, cat_name) self.updateAction(act_id, cat_name, {'id': act_id, 'visible': (checked == '1') or False}) # update client ksscore = self.getCommandSet("core") if checked == '1': ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible") message = _(u"'${id}' action is now visible.", mapping={'id': act_id}) else: ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible") message = _(u"'${id}' action is now invisible.", mapping={'id': act_id}) self.updatePage(cat_name) # issue portal status message self.kss_issueMessage(message) @kssaction def kss_deleteAction(self, id, cat_name): """Delete portal action with given id & category""" # validate input act_id, category, action = self.kss_validateAction(id, cat_name) self.deleteAction(act_id, cat_name) # update client ksscore = self.getCommandSet("core") # XXX TODO: fade effect during removing, to do this # we need kukit js action/command plugin ksscore.deleteNode(ksscore.getHtmlIdSelector(id)) # update different sections of page depending on actions category self.updatePage(cat_name) # issue portal status message self.kss_issueMessage(_(u"'${id}' action successfully removed.", mapping={'id': act_id})) @kssaction def kss_addAction(self, cat_name): """KSS method to add new portal action""" # extract posted data id, ie7bad_category, data = self.parseAddForm(self.request.form) # validate posted data errors = self.validateActionFields(cat_name, data) # if not errors find (or create) category and set action to it ksscore = self.getCommandSet('core') kssplone = self.getCommandSet('plone') if not errors: action = self.addAction(cat_name, data) # update client # add one more action to actions list content = self.getActionsList(category=cat_name, tabs=[action, ]) ksscore.insertHTMLAsLastChild( ksscore.getHtmlIdSelector('tabslist'), content) # update reorder controls #self.kss_checkReorderControls(cat_name) # hide adding form ksscore.removeClass(ksscore.getHtmlIdSelector('addaction'), 'adding') self.kss_toggleCollapsible( ksscore.getCssSelector('form[name=addaction_form] ' '.headerAdvanced'), collapse='true') # set client state var 'plonetabs-addingTitle' to empty # string for correct id autogeneration functionality ksscore.setStateVar('plonetabs-addingTitle', '') # reset adding form self.kss_resetForm(ksscore.getHtmlIdSelector('addaction')) # remove focus from name input self.kss_blur(ksscore.getHtmlIdSelector('actname')) message = _(u"'${id}' action successfully added.", mapping={'id': action.id}) msgtype = "info" # update page self.updatePage(cat_name) else: # expand advanced section if there are errors in id or condition if errors.has_key('id') or errors.has_key('available_expr'): self.kss_toggleCollapsible( ksscore.getCssSelector('form[name=addaction_form] ' '.headerAdvanced'), collapse='false') message = _(u"Please correct the indicated errors.") msgtype = "error" # update errors on client form self.kss_issueErrors(errors) # issue portal status message self.kss_issueMessage(message, msgtype) @kssaction def kss_hideAddForm(self): """"Hide Add form, reset it and remove error messages""" # no server changes, only update client ksscore = self.getCommandSet("core") # hide form itself ksscore.removeClass(ksscore.getHtmlIdSelector('addaction'), 'adding') # collapse advanced section self.kss_toggleCollapsible( ksscore.getCssSelector('form[name=addaction_form] .headerAdvanced'), collapse='true') # reset form inputs self.kss_resetForm(ksscore.getHtmlIdSelector('addaction')) # set client state var 'plonetabs-addingTitle' to empty string for # correct id autogeneration functionality ksscore.setStateVar('plonetabs-addingTitle', '') # remove form errors if such exist self.kss_issueErrors({}) # issue portal status message self.kss_issueMessage(_(u"Adding canceled.")) @kssaction def kss_showEditForm(self, id, cat_name): """Show edit form for given action""" act_id, category, action = self.kss_validateAction(id, cat_name) # fetch data action_info = self.copyAction(action) action_info["editing"] = True # update client ksscore = self.getCommandSet("core") content = self.getActionsList(category=cat_name, tabs=[action_info, ]) ksscore.replaceHTML(ksscore.getHtmlIdSelector(id), content) # focus name field ksscore.focus( ksscore.getCssSelector("#%s input[name=title_%s]" % (id, act_id))) # issue portal status message self.kss_issueMessage(_(u"Fill in required fields and click on Add.")) @kssaction def kss_hideEditForm(self, id, cat_name): """Hide edit form for given action""" act_id, category, action = self.kss_validateAction(id, cat_name) # update client ksscore = self.getCommandSet("core") content = self.getActionsList(category=cat_name, tabs=[action, ]) ksscore.replaceHTML(ksscore.getHtmlIdSelector(id), content) # issue portal status message self.kss_issueMessage(_(u"Changes discarded.")) @kssaction def kss_editAction(self): """Update action's properties""" id, cat_name, data = self.parseEditForm(self.request.form) # get category and action to edit category = self.getActionCategory(cat_name) action = category[id] # validate posted data errors = self.validateActionFields(cat_name, data, allow_dup=(id == data['id'])) html_id = '%s%s%s' % (self.prefix, id, self.sufix) ksscore = self.getCommandSet('core') kssplone = self.getCommandSet('plone') if not errors: action = self.updateAction(id, cat_name, data) # update client # replace action item with updated one content = self.getActionsList(category=cat_name, tabs=[action, ]) ksscore.replaceHTML(ksscore.getHtmlIdSelector(html_id), content) message = _(u"'${id}' action successfully updated.", mapping={'id': action.id}) msgtype = "info" # update page self.updatePage(cat_name) else: # issue error messages self.kss_issueErrors(errors, editform=id) # expand advanced section if there are errors in id, # action url or condition if errors.has_key('id') or errors.has_key('available_expr') or \ errors.has_key('url_expr'): self.kss_toggleCollapsible( ksscore.getCssSelector('#%s .headerAdvanced' % html_id), collapse='false') message = _(u"Please correct the indicated errors.") msgtype = "error" # issue portal status message self.kss_issueMessage(message, msgtype) @kssaction def kss_orderActions(self): """Update actions order in the given category""" form = self.request.form cat_name = form['cat_name'] category = self.getActionCategory(cat_name) # decode URI components and collect ids from request components = urllib.unquote(form['actions']).split('&') if self.sufix == '': ids = [component[len(self.prefix):] for component in components] else: ids = [component[len(self.prefix):-len(self.sufix)] for component in components] # do actual sorting category.moveObjectsByDelta(ids, -len(category.objectIds())) # update client self.updatePage(cat_name) # issue portal status message self.kss_issueMessage(_(u"Actions successfully sorted.")) # # Utility Methods # def fixExpression(self, expr): """Fix expression appropriately for tal format""" if expr.find('/') == 0: return 'string:${portal_url}%s' % expr elif re.compile('^(ht|f)tps?\:', re.I).search(expr): return 'string:%s' % expr #elif re.compile('^(python:|string:|not:|exists:|nocall:|path:)', #re.I).search(expr): #return expr elif expr.find(':') != -1: return expr else: return 'string:${object_url}/%s' % expr def copyAction(self, action): """Copy action to dictionary""" action_info = {'description': action.description} for attr in ACTION_ATTRS: action_info[attr] = getattr(action, attr) return action_info def validateActionFields(self, cat_name, data, allow_dup=False): """Check action fields on validity""" errors = {} if allow_dup: # create dummy category to avoid id # duplication during action update category = ActionCategory(cat_name) else: # get or create (if necessary) actions category category = self.getOrCreateCategory(cat_name) # validate action id chooser = INameChooser(category) try: chooser.checkName(data['id'], self.context) except Exception, e: errors['id'] = self._formatError(e, **{'id': data['id']}) # validate action name if not data['title'].strip(): errors['title'] = _(u"Empty or invalid title specified") # validate condition expression if data['available_expr']: try: Expression(data['available_expr']) except Exception, e: mapping = {'expr': data['available_expr']} idx = data['available_expr'].find(':') if idx != -1: mapping['expr_type'] = data['available_expr'][:idx] errors["available_expr"] = self._formatError(e, **mapping) # validate action expression if data['url_expr']: try: Expression(data['url_expr']) except Exception, e: mapping = {'expr': data['url_expr']} idx = data['url_expr'].find(':') if idx != -1: mapping['expr_type'] = data['url_expr'][:idx] errors["url_expr"] = self._formatError(e, **mapping) return errors def _formatError(self, message, **kw): """Make error message a little bit prettier to ease translation""" charset = self._charset() message = str(message) message = message.replace('"', "'") mapping = {} for key, value in kw.items(): message = message.replace("'%s'" % value, "'${%s}'" % key) # trying to work around zope.i18n issue mapping[key] = unicode(value, charset) message = message.strip() return _(unicode(message, charset), mapping=mapping) def processErrors(self, errors, prefix='', sufix=''): """Add prefixes, sufixes to error ids This is necessary during edit form validation, because every edit form on the page has it's own sufix (id) """ if not (prefix or sufix): return errors result = {} for key, value in errors.items(): result['%s%s%s' % (prefix, key, sufix)] = value return result def parseEditForm(self, form): """Extract all needed fields from edit form""" # get original id and category info = {} id = form['orig_id'] category = form['category'] # preprocess 'visible' field (checkbox needs special checking) if form.has_key('visible_%s' % id): form['visible_%s' % id] = True else: form['visible_%s' % id] = False # collect all action fields for attr in ACTION_ATTRS: info[attr] = form['%s_%s' % (attr, id)] return (id, category, info) def parseAddForm(self, form): """Extract all needed fields from add form""" info = {} id = form['id'] category = form['category'] # preprocess 'visible' field (checkbox needs special checking) if form.has_key('visible') and form['visible']: form['visible'] = True else: form['visible'] = False # fix expression fields form['url_expr'] = self.fixExpression(form['url_expr']) # collect all action fields for attr in ACTION_ATTRS: info[attr] = form[attr] return (id, category, info) def getActionCategory(self, name): portal_actions = getToolByName(self.context, 'portal_actions') return portal_actions[name] def getOrCreateCategory(self, name): """Get or create (if necessary) category""" portal_actions = getToolByName(self.context, 'portal_actions') if name not in map(lambda x: x.id, filter(lambda x: IActionCategory.providedBy(x), portal_actions.objectValues())): portal_actions._setObject(name, ActionCategory(name)) return self.getActionCategory(name) def setSiteProperties(self, **kw): """Change site_properties""" site_properties = getToolByName(self.context, "portal_properties").site_properties site_properties.manage_changeProperties(**kw) return True # # Utility methods for the kss actions management # def kss_validateAction(self, id, cat_name): """Check whether action with given id exists in cat_name category""" try: category = self.getActionCategory(cat_name) except Exception: raise KSSExplicitError, \ _(u"'${cat_name}' action category does not exist.", mapping={'cat_name': cat_name}) # extract action id from given list item id on client action_id = self.sufix and id[len(self.prefix):-len(self.sufix)] or \ id[len(self.prefix):] try: action = category[action_id] except Exception: raise KSSExplicitError, \ _(u"No '${id}' action in '${cat_name}' category.", mapping={'id': action_id, 'cat_name': cat_name}) return (action_id, category, action) def kss_issueErrors(self, errors, editform=False, fields=ACTION_ATTRS): """Display error messages on the client""" ksscore = self.getCommandSet('core') ts = getToolByName(self.context, 'translation_service') for field in fields: self.kss_issueFieldError(ksscore, field, errors.get(field, False), editform, ts) def kss_issueFieldError(self, ksscore, name, error, editform, ts): """Issue this error message for the field""" if editform: id = '%s%s%s' % (self.prefix, editform, self.sufix) field_selector = ksscore.getCssSelector('#%s .edit-field-%s' % (id, UI_ATTRS.get(name, name))) field_error_selector = ksscore.getCssSelector('#%s .edit-field-%s ' '.error-container' % (id, UI_ATTRS.get(name, name))) else: field_selector = ksscore.getCssSelector('form' +\ '[name=addaction_form] ' '.field-%s' % UI_ATTRS.get(name, name)) field_error_selector = ksscore.getCssSelector('form[name=' 'addaction_form] .field-%s ' '.error-container' % UI_ATTRS.get(name, name)) if error: error = ts.translate(error, context=self.context) ksscore.replaceInnerHTML(field_error_selector, error) ksscore.addClass(field_selector, 'error') else: ksscore.clearChildNodes(field_error_selector) ksscore.removeClass(field_selector, 'error') def kss_toggleCollapsible(self, selector, collapsed=None, expanded=None, collapse=None): """KSS Server command to add plonetabs-toggleCollapsible client action to response """ command = self.commands.addCommand('plonetabs-toggleCollapsible', selector) if collapsed is not None: data = command.addParam('collapsed', collapsed) if expanded is not None: data = command.addParam('expanded', expanded) if collapse is not None: data = command.addParam('collapse', collapse) def kss_resetForm(self, selector): """KSS Server command to reset form on client""" command = self.commands.addCommand('plonetabs-resetForm', selector) def kss_blur(self, selector): """KSS Server command to remove focus from input""" command = self.commands.addCommand('plonetabs-blur', selector) def kss_replaceOrInsert(self, selector, parentSelector, html, withKssSetup='True', alternativeHTML='', selectorType='', position='', positionSelector='', positionSelectorType=''): """KSS Server command to execute replaceOrInsert client action""" command = self.commands.addCommand('plonetabs-replaceOrInsert', selector) data = command.addParam('selector', parentSelector) data = command.addHtmlParam('html', html) data = command.addParam('withKssSetup', withKssSetup) if alternativeHTML: data = command.addHtmlParam('alternativeHTML', alternativeHTML) if selectorType: data = command.addParam('selectorType', selectorType) if position: data = command.addParam('position', position) if positionSelector: data = command.addParam('positionSelector', positionSelector) if positionSelectorType: data = command.addParam('positionSelectorType', positionSelectorType) def kss_issueMessage(self, message, msgtype="info"): """"Issues portal status message and removes it afte 10 seconds""" ksscore = self.getCommandSet('core') self.getCommandSet('plone').issuePortalMessage(message, msgtype=msgtype) self.kss_timeout( ksscore.getHtmlIdSelector('kssPortalMessage'), delay='10000', repeat='false', cmd_name='setStyle', name='display', value='none' ) def kss_timeout(self, selector, **kw): """KSS Server command to execute plonetabs-timeout client action""" command = self.commands.addCommand('plonetabs-timeout', selector, **kw) def renderViewlet(self, manager, name): if isinstance(manager, basestring): manager = getMultiAdapter((self.context, self.request, self,), IViewletManager, name=manager) renderer = getMultiAdapter((self.context, self.request, self, manager), IViewlet, name=name) renderer = renderer.__of__(self.context) renderer.update() return renderer.render() # # Basic API to work with portal actions tool in a more pleasent way # def addAction(self, cat_name, data): """Create and add new action to category with given name""" id = data.pop('id') category = self.getOrCreateCategory(cat_name) action = Action(id, **data) category._setObject(id, action) return action def updateAction(self, id, cat_name, data): """Update action with given id and category""" new_id = data.pop('id') category = self.getActionCategory(cat_name) # rename action if necessary if id != new_id: category.manage_renameObject(id, new_id) # get action action = category[new_id] # update action properties for attr in data.keys(): if data.has_key(attr): action._setPropValue(attr, data[attr]) return action def deleteAction(self, id, cat_name): """Delete action with given id from given category""" category = self.getActionCategory(cat_name) category.manage_delObjects(ids=[id, ]) return True def moveAction(self, id, cat_name, steps=0): """Move action by a given steps""" if steps != 0: category = self.getActionCategory(cat_name) if steps > 0: category.moveObjectsUp([id, ], steps) else: category.moveObjectsDown([id, ], abs(steps)) return True return False # # KSS Methods that are used to update different parts of the page # accordingly to category # def updatePage(self, category): """Seek for according method in class and calls it if found Example of making up method's name: portal_tabs => updatePortalTabsPageSection """ method_name = 'update%sPageSection' % ''.join( map(lambda x: x.capitalize(), category.split('_'))) if hasattr(self, method_name): getattr(self, method_name)() def updatePortalTabsPageSection(self): """Method for updating global-sections on client""" ksscore = self.getCommandSet("core") self.kss_replaceOrInsert(ksscore.getHtmlIdSelector("portal-header"), "portal-globalnav", self.sections_template(), withKssSetup='False', selectorType='htmlid') def updateSiteActionsPageSection(self): """Method for updating site action on client""" ksscore = self.getCommandSet("core") self.kss_replaceOrInsert(ksscore.getHtmlIdSelector("portal-header"), "portal-siteactions", self.renderViewlet("plone.portalheader", "plone.site_actions"), withKssSetup='False', selectorType='htmlid', position="before", positionSelector="portal-searchbox", positionSelectorType="htmlid") #ksszope = self.getCommandSet("zope") #ksszope.refreshViewlet( #self.getCommandSet("core").getHtmlIdSelector("portal-siteactions"), #"plone.portalheader", #"plone.site_actions") def updateUserPageSection(self): """Method for updating site action on client""" ksszope = self.getCommandSet("zope") ksszope.refreshViewlet( self.getCommandSet("core").getHtmlIdSelector( "portal-personaltools-wrapper"), "plone.portaltop", "plone.personal_bar") class PloneTabsMode(BrowserView): def __call__(self): mode = self.request.get(cookie_name, False) if mode in ('plain', 'rich'): return mode mode = self.request.cookies.get(cookie_name, False) if mode in ('plain', 'rich'): return mode return 'rich'