import copy from Acquisition import aq_inner from zope.interface import implements from zope.component import getUtility, getMultiAdapter from zope.i18n import translate from zope.schema.interfaces import IVocabularyFactory from zope.exceptions import UserError from Products.CMFCore.utils import getToolByName from Products.CMFCore.interfaces import IAction, IActionCategory from Products.CMFCore.ActionInformation import Action, ActionCategory from Products.CMFEditions.setuphandlers import DEFAULT_POLICIES from Products.CMFPlone import PloneMessageFactory as _ from Products.CMFPlone import PloneMessageFactory as pmf from Products.CMFPlone import utils from Products.CMFPlone.browser.navigation import get_view_url from Products.Five.browser import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.statusmessages.interfaces import IStatusMessage from plone.app.layout.navigation.root import getNavigationRoot from plone.app.kss.plonekssview import PloneKSSView from plone.app.workflow.remap import remap_workflow from plone.memoize.instance import memoize from kss.core import kssaction, KSSExplicitError from quintagroup.plonetabs.config import * from interfaces import IPloneTabsControlPanel ACTION_ATTRS = ["id", "visible", "description", "url_expr", "title", "available_expr"] class PloneTabsControlPanel(PloneKSSView): implements(IPloneTabsControlPanel) template = ViewPageTemplateFile("templates/plonetabs.pt") actionslist_template = ViewPageTemplateFile("templates/actionslist.pt") autogenerated_template = ViewPageTemplateFile("templates/autogenerated.pt") sections_template = ViewPageTemplateFile("templates/sections.pt") 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 action == "add_action": if submitted: postback = self.manage_addAction(form, errors) elif action == "edit_action": if submitted: pass elif action == "delete_action": postback = self.manage_deleteAction(self.request, errors) #elif action == "visible_action": #pass elif action == "moveup_action": postback = self.manage_moveUpAction(self.request, errors) elif action == "movedown_action": postback = self.manage_moveDownAction(self.request, errors) elif action == "set_autogeneration": if submitted: postback = self.manage_setAutogeneration(form, errors) 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 changeProperties = getToolByName(self.context, "portal_properties").site_properties.manage_changeProperties if int(generated_tabs) == 1: changeProperties(disable_folder_sections=False) else: changeProperties(disable_folder_sections=True) # set disable_nonfolderish_sections property if int(nonfolderish_tabs) == 1: changeProperties(disable_nonfolderish_sections=False) else: changeProperties(disable_nonfolderish_sections=True) IStatusMessage(self.request).addStatusMessage(_(u"Changes saved!"), type="info") self.redirect() return False def manage_addAction(self, form, errors): """ Add a new action to given category, if category doesn't exist, create it """ # extract data from request's form id = form.get("id") category = form.get("category") # before checking for existence category we will validate all input data # and issue errors if needed # create and validate action if not id: errors["id"] = _(u"Id field is required") if not form.get("title"): errors["title"] = _(u"Title field is required") action = Action(id) for prop in ACTION_ATTRS: if prop != "id": try: if prop == "visible": value = int(form.get(prop, '0')) else: value = form.get(prop) action._setPropValue(prop, value) except Exception, e: errors[prop] = _(u"%s" % str(e)) # if not errors find (or create) category and set action to it if not errors: portal_actions = getToolByName(self.context, "portal_actions") cat_container = None if category not in map(lambda x: x.id, filter(lambda x: IActionCategory.providedBy(x), portal_actions.objectValues())): try: portal_actions._setObject(category, ActionCategory(category)) except Exception, e: errors["select_category"] = _(u"%s" % str(e)) else: cat_container = portal_actions[category] else: cat_container = portal_actions[category] if cat_container is not None: try: cat_container._setObject(id, action) except Exception, e: errors["id"] = _(u"%s" % str(e)) if not errors: IStatusMessage(self.request).addStatusMessage(_(u"'%s' action successfully added." % id), type="info") self.redirect(search="category=%s" % category) return False else: IStatusMessage(self.request).addStatusMessage(_(u"Please correct the indicated errors."), type="error") return True def manage_deleteAction(self, request, errors): """ Delete action with given id/category """ portal_actions = getToolByName(self.context, "portal_actions") id = request.get("id", "") category = request.get("category", "") if category not in portal_actions.objectIds(): IStatusMessage(self.request).addStatusMessage(_(u"Unexistent root portal actions category '%s'" % category), type="error") else: cat_container = portal_actions[category] if id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())): IStatusMessage(self.request).addStatusMessage(_(u"'%s' action does not exist in '%s' category" % (id, category)), type="error") else: cat_container.manage_delObjects(ids=[id,]) IStatusMessage(self.request).addStatusMessage(_(u"'%s' action in '%s' category successfully deleted" % (id, category)), type="info") self.redirect(search="category=%s" % category) return False def manage_moveUpAction(self, request, errors): """ Move up given action by one position """ portal_actions = getToolByName(self.context, "portal_actions") id = request.get("id", "") category = request.get("category", "") if category not in portal_actions.objectIds(): IStatusMessage(self.request).addStatusMessage(_(u"Unexistent root portal actions category '%s'" % category), type="error") else: cat_container = portal_actions[category] if id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())): IStatusMessage(self.request).addStatusMessage(_(u"'%s' action does not exist in '%s' category" % (id, category)), type="error") else: cat_container.moveObjectsUp([id,], 1) IStatusMessage(self.request).addStatusMessage(_(u"'%s' action in '%s' category moved up" % (id, category)), type="info") self.redirect(search="category=%s" % category) return False def manage_moveDownAction(self, request, errors): """ Move up given action by one position """ portal_actions = getToolByName(self.context, "portal_actions") id = request.get("id", "") category = request.get("category", "") if category not in portal_actions.objectIds(): IStatusMessage(self.request).addStatusMessage(_(u"Unexistent root portal actions category '%s'" % category), type="error") else: cat_container = portal_actions[category] if id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())): IStatusMessage(self.request).addStatusMessage(_(u"'%s' action does not exist in '%s' category" % (id, category)), type="error") else: cat_container.moveObjectsDown([id,], 1) IStatusMessage(self.request).addStatusMessage(_(u"'%s' action in '%s' category moved down" % (id, category)), type="info") self.redirect(search="category=%s" % category) return False def redirect(self, url="", search="", hash=""): """ Redirect to @@plonetabs-controlpanel configlet """ if 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 hash != "": hash = "#%s" % hash self.request.response.redirect("%s%s%s" % (url, search, hash)) ################################### ## Methods - providers for templates ################################### def getPageTitle(self, category="portal_tabs"): """ See interface """ portal_props = getToolByName(self.context, "portal_properties") default_title = "Plone '%s' Configuration" % 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 return dict.get(category, None) or default_title def hasActions(self, category="portal_tabs"): """ See interface """ return len(getToolByName(self.context, "portal_actions").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 return not site_properties.getProperty("disable_nonfolderish_sections", False) def getActionsList(self, category="portal_tabs"): """ See interface """ return self.actionslist_template(category=category) def getGeneratedTabs(self): """ See interface """ return self.autogenerated_template() 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 we only want actions 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() 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 = context_state = getMultiAdapter((self.context, self.request), name=u"plone_context_state").actions() portal_tabs_view = getMultiAdapter((self.context, self.request), name="portal_tabs_view") return portal_tabs_view.topLevelTabs(actions=actions) 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 ########################## def updateGlobalSections(self, ksscore): """ Method for updating global-sections on client """ ksscore.replaceHTML( ksscore.getHtmlIdSelector("portal-globalnav"), self.sections_template(), withKssSetup="False") # XXX TODO #def updateSection(self, ksscore, section): #""" Method for updating global-sections on client """ #replace_id = section #macro = SECTION_MAPPING.get(section, None) #if macro is not None: #ksscore.replaceHTML( #ksscore.getHtmlIdSelector(replace_id), #self.macroContent(macro), ##self.sections_template(), #withKssSetup="False") def validateAction(self, id, category, prefix="tabslist_"): """ If action with given id and category doesn't exist - raise kss exception """ portal_actions = getToolByName(self.context, "portal_actions") # remove prefix, added for making ids on configlet unique ("tabslist_") act_id = id[len("tabslist_"):] if category not in portal_actions.objectIds(): raise KSSExplicitError, "Unexistent root portal actions category %s" % category cat_container = portal_actions[category] if act_id not in map(lambda x: x.id, filter(lambda x: IAction.providedBy(x), cat_container.objectValues())): raise KSSExplicitError, "%s action does not exist in %s category" % (act_id, category) return (cat_container, act_id) @kssaction def toggleGeneratedTabs(self, field, checked='0'): """ Toggle autogenaration setting on configlet """ changeProperties = getToolByName(self.context, "portal_properties").site_properties.manage_changeProperties if checked == '1': changeProperties(**{field : False}) else: changeProperties(**{field : True}) ksscore = self.getCommandSet("core") replace_id = "roottabs" content = self.getGeneratedTabs() ksscore.replaceInnerHTML(ksscore.getHtmlIdSelector(replace_id), content, withKssSetup="True") # update global-sections viewlet self.updateGlobalSections(ksscore) @kssaction def toggleActionsVisibility(self, id, checked='0', category=None): """ Toggle visibility for portal actions """ portal_actions = getToolByName(self.context, "portal_actions") cat_container, act_id = self.validateAction(id, category) if checked == '1': checked = True else: checked = False cat_container[act_id].visible = checked ksscore = self.getCommandSet("core") if checked: ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible") else: ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible") # update global-sections viewlet if category == "portal_tabs": self.updateGlobalSections(ksscore) @kssaction def 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, "Object with %s id doesn't exist in portal root" % obj_id if checked == '1': checked = True else: checked = False portal[obj_id].update(excludeFromNav=not checked) ksscore = self.getCommandSet("core") if checked: ksscore.removeClass(ksscore.getHtmlIdSelector(id), value="invisible") else: ksscore.addClass(ksscore.getHtmlIdSelector(id), value="invisible") # update global-sections viewlet self.updateGlobalSections(ksscore) @kssaction def deleteAction(self, id, category): """ Delete portal action with given id & category """ portal_actions = getToolByName(self.context, "portal_actions") cat_container, act_id = self.validateAction(id, category) cat_container.manage_delObjects(ids=[act_id,]) # update action list on client ksscore = self.getCommandSet("core") ksscore.deleteNode(ksscore.getHtmlIdSelector(id)) # add "noitems" class to Reorder controls to hide it if not filter(lambda x: IAction.providedBy(x), cat_container.objectValues()): ksscore.addClass(ksscore.getHtmlIdSelector("reorder"), value="noitems") # XXX TODO: fade effect during removing, for this kukit js action/command plugin needed # update global-sections viewlet if category == "portal_tabs": self.updateGlobalSections(ksscore) @kssaction def editAction(self, id, category): """ Show edit form for given action """ cat_container, act_id = self.validateAction(id, category) # collect data action_info = self.copyAction(cat_container[act_id]) action_info["editing"] = True ksscore = self.getCommandSet("core") content = self.actionslist_template(tabs=[action_info,]) replace_id = id ksscore.replaceHTML(ksscore.getHtmlIdSelector(replace_id), content) # focus name field ksscore.focus(ksscore.getCssSelector("#%s input[name=name_%s]" % (id, act_id))) @kssaction def editCancel(self, id, category): """ Hide edit form for given action """ cat_container, act_id = self.validateAction(id, category) ksscore = self.getCommandSet("core") content = self.actionslist_template(tabs=[cat_container[act_id],]) replace_id = id ksscore.replaceHTML(ksscore.getHtmlIdSelector(replace_id), content) # Utility Methods def copyAction(self, action): """ Copyt action to dictionary """ action_info = {} for attr in ACTION_ATTRS: action_info[attr] = getattr(action, attr) return action_info