# -*- coding: utf-8 -*- from elementtree import ElementTree from Acquisition import aq_inner from zope.component import getMultiAdapter, queryUtility from plone.memoize.instance import memoize from Products.Five.browser import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.CMFCore.utils import getToolByName from Products.CMFCore import permissions from Products.CMFCore.ActionInformation import Action from Products.CMFPlone.utils import getFSVersionTuple from plone.registry.interfaces import IRegistry from collective.portaltabs import messageFactory as _ from collective.portaltabs.interfaces import IPortalTabsSettings def _prettify(url_expr): if url_expr and not url_expr.startswith('python:') and not url_expr.startswith('string:'): return 'tal:' + url_expr if url_expr.startswith('string:${globals_view/navigationRootUrl}'): if len(url_expr)==40: return '/' return url_expr[40:] if url_expr.startswith('string:'): return url_expr[7:] return url_expr def _tallify(url): """ Restore the TAL expression state of the url_expr """ if url.startswith('tal:'): return url[4:] if url.startswith('www.'): url = 'http://' + url if url.startswith('/'): if url=='/': return 'string:${globals_view/navigationRootUrl}' return 'string:${globals_view/navigationRootUrl}' + url if not url.startswith('string:') and not url.startswith('python:'): return 'string:' + url return url def _serialize_category_tabs(category): for action in category.values(): yield { 'id': action.id, 'title': action.title, 'url': _prettify(action.getProperty('url_expr', '')), 'visible': action.getProperty('visible', False), } class ManagePortaltabsView(BrowserView): def __init__(self, context, request): BrowserView.__init__(self, context, request) self.saveRequest = {} self.addRequest = {} self.confirmMessage = '' # will be {action_id: {data}, ...} self.errs = {} self.portal_actions = getToolByName(context, 'portal_actions') self.translation_service = getToolByName(context, 'translation_service') self.plone_utils = getToolByName(context, 'plone_utils') def __call__(self): request = self.request request.set('disable_border', True) fn = None msg = None self._handleRequest(request) form = request.form if form.get('Save'): fn = self.form_save elif form.get('Add'): fn = self.form_add elif request.get('Delete'): fn = self.form_delete elif request.get('move'): fn = self.form_move elif request.get('Upload'): fn = self.form_upload if fn: msg = fn() if msg: self.plone_utils.addPortalMessage(msg, type='info') request.response.redirect('%s/@@%s' % (self.context.absolute_url(), self.__name__)) else: return self.index() def translate(self, msgid, default): return self.translation_service.utranslate(domain = 'collective.portaltabs', msgid = msgid, default = default, context = self.context) @property def confirm_message(self): if not self.confirmMessage: _ = self.translate self.confirmMessage = _(u'confirm_message', default=u'Confirm deletion?') return self.confirmMessage def iter_categories(self): """ Generate (id, title) tuples of the managed categories. Non-existing categories are ignored """ category_ids = self.portal_actions.keys() registry = queryUtility(IRegistry) settings = registry.forInterface(IPortalTabsSettings, check=False) categories = [] for record in settings.manageable_categories: id = record.category_id title = record.category_title # Be sure that the CMF Category exists if id in category_ids: categories.append( (id, title) ) return categories @memoize def iter_translated_categories(self): """ Generate (id, translated_title) tuples of managed categories """ return [(x[0], self.translate(msgid=x[1], default=x[1])) for x in self.iter_categories()] @property def defaults(self): results = [] for id, title in self.iter_translated_categories(): results.append({'id': id, 'title': title}) return results @property def check_disableFolderSections(self): """ Check if the disable_folder_sections is on or off """ return getToolByName(self.context, 'portal_properties').site_properties.disable_folder_sections @property def saved_actions(self): """ Return current saved tabs """ results = [] for category_id, title in self.iter_translated_categories(): results.append({'id': category_id, 'title': title, 'tabs': list(_serialize_category_tabs(self.portal_actions[category_id]))}) return results def getTabs(self, category_id): """Obtain tab for a given category""" for x in self.saved_actions: if category_id==x['id']: return x return {} def _validateInput(self, form): """ Validate possible form input """ errors = {} if not form.get('title'): errors['title'] = _(u'Title field is required, please provide it.') url = form.get('url') if not url: errors['url'] = _(u'URL field is required, please provide it.') else: context = aq_inner(self.context) portal_state = getMultiAdapter((context, self.request), name=u'plone_portal_state') member = portal_state.member() if (url.startswith('tal:') or url.startswith('python:')) and \ not member.has_permission("collective.portaltabs: Use advanced expressions", portal_state.portal()): errors['url'] = _('adv_expression_permission_denied_msg', default=u'You have no permission to handle expressions like "tal:" or "python:".') if url.find('$')>-1 and not \ member.has_permission("collective.portaltabs: Use advanced expressions", portal_state.portal()): errors['url'] = _('tales_expression_permission_denied_msg', default=u'You have no permission to handle TALES expressions in static links.') return errors def form_add(self): form = self.request.form errors = self._validateInput(form) if errors: self.errs['__add__'] = errors return None category_id = form.get('action') title = form.get('title') action_id = form.get('id') or self.plone_utils.normalizeString(title) action = Action(action_id, title=title, url_expr=_tallify(form.get('url')), permissions=(permissions.View,)) self.portal_actions[category_id]._setObject(action_id, action) return _(u'Tab added') def form_save(self): form = self.request.form visible = form.get('visible') params = zip(form.get('id', []), form.get('title', []), form.get('url', [])) stop = False for x in params: errors = self._validateInput({'id': x[0], 'title': x[1], 'url': x[2]}) if errors: self.errs[x[0]] = errors stop = True if stop: return None for cat_action_id, title, url in params: category_id, action_id = cat_action_id.split('|') action = self.portal_actions[category_id][action_id] action.manage_changeProperties(title = title, url_expr = _tallify(url), visible = cat_action_id in visible) return _(u'Changes saved') def form_delete(self): ids = [self.request.get('Delete')] category_id = self.request.get('action') self.portal_actions[category_id].manage_delObjects(ids=ids) return _(u'Tab deleted') def form_upload(self): """Upload an action.xml compatible file""" fin = self.request.form['file'] tree = ElementTree.parse(fin) managed_categories = set(x[0] for x in self.iter_categories()) for el in tree.findall('object'): if el.get('meta_type') != 'CMF Action Category': continue category_id = el.get('name') if category_id not in managed_categories: continue existing_category = self.portal_actions[category_id] for action in el.findall('object'): if action.get('meta_type') != 'CMF Action': continue action_id = action.get('name') props = {} permissions = [] for prop_el in action: name = prop_el.get('name') if name == 'permissions': permissions = [perm.get('value') for perm in prop_el.findall('element')] else: props[name] = prop_el.text or '' if action_id in existing_category: action = self.portal_actions[category_id][action_id] action.manage_changeProperties(title=props['title'], description=props['description'], url_expr=props['url_expr'], icon_expr=props['icon_expr'], available_expr=props['available_expr'], permissions=permissions, visible=(props['visible']=='True'), ) else: action = Action(action_id, title=props['title'], description=props['description'], url_expr=props['url_expr'], icon_expr=props['icon_expr'], available_expr=props['available_expr'], permissions=permissions, visible=(props['visible']=='True')) self.portal_actions[category_id]._setObject(action_id, action) return _(u'File uploaded') def form_move(self): """Move a tab up or down (means left or right commonly)""" action_id = self.request.get('move') where = self.request.get('where') category_id = self.request.get('action') category = self.portal_actions[category_id] if where == 'top': category.moveObjectsToTop(ids=[action_id]) elif where == 'up': category.moveObjectsUp(ids=[action_id]) elif where == 'down': category.moveObjectsDown(ids=[action_id]) elif where == 'bottom': category.moveObjectsToBottom(ids=[action_id]) else: raise ValueError('Bad arguments for moveTab') return _(u'Tab moved') @property def check_canManageNavigationSettings(self): context = aq_inner(self.context) portal_state = getMultiAdapter((context, self.request), name=u'plone_portal_state') member = portal_state.member() plone_version_tuple = getFSVersionTuple() if plone_version_tuple[0]>=4 and plone_version_tuple[1]>=1: # If on Plone 4.1 or better we need to check "Plone Site Setup: Navigation" instead of "Manage portal" return member.has_permission("Plone Site Setup: Navigation", portal_state.portal()) return member.has_permission("Manage portal", portal_state.portal()) @property def check_canPortalTabSettings(self): context = aq_inner(self.context) portal_state = getMultiAdapter((context, self.request), name=u'plone_portal_state') member = portal_state.member() return member.has_permission("collective.portaltabs: Manage action categories", portal_state.portal()) def canSeeRow(self, tab): """Check if the current user can see one of the table's row. If the row contains a dangerous expressions, check if he can handle data containing tal: or python:""" context = aq_inner(self.context) portal_state = getMultiAdapter((context, self.request), name=u'plone_portal_state') member = portal_state.member() if not member.has_permission("collective.portaltabs: Use advanced expressions", portal_state.portal()) \ and (tab['url'].startswith('python:') or tab['url'].startswith('tal:')): return False if not member.has_permission("collective.portaltabs: Use advanced expressions", portal_state.portal()) \ and tab['url'].find('$')>-1: return False return True def _handleRequest(self, request): """manage request, to be used in the template for validation errors """ # User pressed the Add button if request.get('form.submit.edit'): for category_id in request.get('action', []): tabs = self.getTabs(category_id)['tabs'] for category_action_id, title, url, tab in zip(request.get('id'), request.get('title'), request.get('url'), tabs): category_id, action_id = category_action_id.split('|') row = {} row['title'] = title or tab.get('title') row['url'] = url or tab.get('url') self.saveRequest[category_action_id] = row # User pressed the Add button elif request.get('form.submit.add'): row = {} row['title'] = request.get('title') row['url'] = request.get('url') row['id'] = request.get('id') self.addRequest = row