diff --git a/product/ERP5Type/Base.py b/product/ERP5Type/Base.py index df0e8ba4997091696209ce7300c1525d8eaf0d82..1cfb578540781bbf6558d39ea29d11b32b6bd702 100644 --- a/product/ERP5Type/Base.py +++ b/product/ERP5Type/Base.py @@ -43,9 +43,11 @@ import OFS.History from OFS.SimpleItem import SimpleItem from OFS.PropertyManager import PropertyManager -from Products.CMFCore.PortalContent import PortalContent +from ZopePatch import ERP5PropertyManager + +from Products.CMFCore.PortalContent import PortalContent, _getViewFor from Products.CMFCore.Expression import Expression -from Products.CMFCore.utils import getToolByName, _getViewFor +from Products.CMFCore.utils import getToolByName from Products.CMFCore.WorkflowCore import ObjectDeleted, ObjectMoved from Products.CMFCore.CMFCatalogAware import CMFCatalogAware @@ -67,7 +69,6 @@ from Accessor import WorkflowState from Products.ERP5Type.Log import log as unrestrictedLog from Products.ERP5Type.TransactionalVariable import getTransactionalVariable from Products.ERP5Type.Accessor.TypeDefinition import type_definition -from ZopePatch import ERP5PropertyManager from CopySupport import CopyContainer, CopyError,\ tryMethodCallWithTemporaryPermission diff --git a/product/ERP5Type/Core/ActionInformation.py b/product/ERP5Type/Core/ActionInformation.py index 00b1e24f8f9c83d6e3564c7912d785d7b36d6bb2..110159d2bf681c44026906cf8382612a8cff2438 100644 --- a/product/ERP5Type/Core/ActionInformation.py +++ b/product/ERP5Type/Core/ActionInformation.py @@ -28,7 +28,7 @@ # ############################################################################## -from AccessControl import ClassSecurityInfo +from AccessControl import ClassSecurityInfo, getSecurityManager from Acquisition import aq_base from Products.CMFCore.Expression import Expression from Products.CMFCore.ActionInformation import ActionInfo @@ -60,10 +60,29 @@ class ActionInformation(XMLObject): , PropertySheet.ActionInformation ) - def testCondition(self, ec): - """Evaluate condition using context, 'ec', and return 0 or 1""" - condition = self.getCondition() - return condition is None and 1 or condition(ec) + security.declarePrivate('test') + def test(self, ec): + if self.isVisible(): + permission_list = self.getActionPermissionList() + if permission_list: + category = self.getActionType() + info = ec.vars + if (info['here'] is not None and + (category.startswith('object') or + category.startswith('workflow'))): + context = info['here'] + elif (info['folder'] is not None and + category.startswith('folder')): + context = info['folder'] + else: + context = info['portal'] + has_permission = getSecurityManager().getUser().has_permission + for permission in permission_list: + if not has_permission(permission, context): + return False + condition = self.getCondition() + return condition is None or condition(ec) + return False security.declarePublic('getVisibility') def getVisibility(self): @@ -123,44 +142,6 @@ class ActionInformation(XMLObject): self.getConditionText()] return ' '.join(filter(None, search_source_list)) - # - # XXX CMF compatibility - # - - def _getActionObject(self): - return self.getActionExpression() - - security.declarePrivate('getCategory') - def getCategory(self): - return self.getActionType() - - security.declarePrivate('getPermissions') - def getPermissions(self): - return self.getActionPermissionList() - - #def getActionCategorySelectionList(self): - # return self._getCategoryTool().action_type.objectIds() - #def getPriority(self): - # return self.getFloatIndex() - - security.declarePrivate('getMapping') - def getMapping(self): - """ Get a mapping of this object's data. - """ - return { 'id': self.getReference(), - 'title': self.getTitle(), - 'description': self.getDescription(), - 'category': self.getActionType(), - 'condition': self.getCondition(), - 'permissions': self.getPermissions(), #self.permissions, - 'visible': self.getVisibility(), #bool(self.visible), - 'action': self.getActionText() } - - security.declarePrivate('getAction') - def getAction(self, ec): - """ Compute the action using context, 'ec'; return a mapping of - info about the action. - XXX To be renamed or removed, - so that 'action_expression' property can be renamed to 'action'. - """ - return ActionInfo(self, ec) + security.declarePrivate('getActionUrl') + def getActionUrl(self, ec): + return self.getActionExpression()(ec) diff --git a/product/ERP5Type/ERP5Type.py b/product/ERP5Type/ERP5Type.py index 85de3dbd6064af546cace8dd6338b58faf38dc45..75fb8ec43c73e9c203fc1ec761ca95716b351894 100644 --- a/product/ERP5Type/ERP5Type.py +++ b/product/ERP5Type/ERP5Type.py @@ -21,23 +21,15 @@ ############################################################################## import zope.interface -from Globals import InitializeClass, DTMLFile +from Globals import InitializeClass from AccessControl import ClassSecurityInfo, getSecurityManager from Acquisition import aq_base, aq_inner, aq_parent -import Products -import Products.CMFCore.TypesTool -from Products.CMFCore.TypesTool import TypeInformation from Products.CMFCore.TypesTool import FactoryTypeInformation -from Products.CMFCore.TypesTool import TypesTool -from Products.CMFCore.interfaces.portal_types import ContentTypeInformation\ - as ITypeInformation -from Products.CMFCore.ActionProviderBase import ActionProviderBase -from Products.CMFCore.utils import SimpleItemWithProperties from Products.CMFCore.Expression import createExprContext, Expression from Products.CMFCore.exceptions import AccessControl_Unauthorized from Products.CMFCore.utils import _checkPermission -from Products.ERP5Type import _dtmldir, interfaces, Permissions, PropertySheet +from Products.ERP5Type import interfaces, Constraint, Permissions, PropertySheet from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from Products.ERP5Type.XMLObject import XMLObject @@ -63,6 +55,22 @@ from zLOG import LOG, ERROR from Products.CMFCore.exceptions import zExceptions_Unauthorized +def getExprContext(context, ob=None): + portal = context.getPortalObject() + if ob is None: + folder = portal + else: + folder = aq_parent(ob) + # Search up the containment hierarchy until we find an + # object that claims it's a folder. + while folder is not None: + if getattr(aq_base(folder), 'isPrincipiaFolderish', 0): + break # found it. + else: + folder = aq_parent(folder) + return createExprContext(folder, portal, ob) + + class LocalRoleAssignorMixIn(object): security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) @@ -147,20 +155,7 @@ class LocalRoleAssignorMixIn(object): security.declarePrivate('getFilteredRoleListFor') def getFilteredRoleListFor(self, ob=None): """Return all role generators applicable to the object.""" - portal = self.getPortalObject() - if ob is None: - folder = portal - else: - folder = aq_parent(ob) - # Search up the containment hierarchy until we find an - # object that claims it's a folder. - while folder is not None: - if getattr(aq_base(folder), 'isPrincipiaFolderish', 0): - break # found it. - else: - folder = aq_parent(folder) - - ec = createExprContext(folder, portal, ob) + ec = getExprContext(self, ob) for role in self.getRoleInformationList(): if role.testCondition(ec): yield role @@ -402,18 +397,6 @@ class ERP5TypeInformation(XMLObject, return ob - security.declareProtected(Permissions.ManagePortal, - 'setPropertySheetList') - def setPropertySheetList(self, property_sheet_list): - # XXX CMF compatibility - self._setTypePropertySheetList(property_sheet_list) - - security.declareProtected(Permissions.AccessContentsInformation, - 'getHiddenContentTypeList') - def getHiddenContentTypeList(self): - # XXX CMF compatibility - return self.getTypeHiddenContentTypeList(()) - security.declareProtected(Permissions.AccessContentsInformation, 'getInstanceBaseCategoryList') def getInstanceBaseCategoryList(self): @@ -459,10 +442,7 @@ class ERP5TypeInformation(XMLObject, id = id + "d" return factory_method(portal, id).propertyMap() - # - # Helper methods - # - def manage_editProperties(self, REQUEST): + def edit(self, *args, **kw): """ Method overload @@ -473,11 +453,10 @@ class ERP5TypeInformation(XMLObject, in order to implement a broadcast update on production hosts """ - previous_property_sheet_list = self.property_sheet_list - base_category_list = self.base_category_list - result = FactoryTypeInformation.manage_editProperties(self, REQUEST) - if previous_property_sheet_list != self.property_sheet_list or \ - base_category_list != self.base_category_list: + property_list = 'factory', 'property_sheet_list', 'base_category_list' + previous_state = [getattr(aq_base(self), x) for x in property_list] + result = XMLObject.edit(self, *args, **kw) + if previous_state != [getattr(aq_base(self), x) for x in property_list]: from Products.ERP5Type.Base import _aq_reset _aq_reset() return result @@ -494,6 +473,12 @@ class ERP5TypeInformation(XMLObject, search_source_list += self.getTypeBaseCategoryList(()) return ' '.join(filter(None, search_source_list)) + security.declarePrivate('getFilteredActionListFor') + def getFilteredActionListFor(self, ob=None): + ec = getExprContext(self, ob) + return (action for action in self.getActionInformationList() + if action.test(ec)) + security.declareProtected(Permissions.AccessContentsInformation, 'getActionInformationList') def getActionInformationList(self): @@ -511,13 +496,13 @@ class ERP5TypeInformation(XMLObject, security.declareProtected(Permissions.AccessContentsInformation, 'getAvailablePropertySheetList') def getAvailablePropertySheetList(self): - return sorted(k for k in Products.ERP5Type.PropertySheet.__dict__ + return sorted(k for k in PropertySheet.__dict__ if not k.startswith('__')) security.declareProtected(Permissions.AccessContentsInformation, 'getAvailableConstraintList') def getAvailableConstraintList(self): - return sorted(k for k in Products.ERP5Type.Constraint.__dict__ + return sorted(k for k in Constraint.__dict__ if k != 'Constraint' and not k.startswith('__')) security.declareProtected(Permissions.AccessContentsInformation, @@ -531,9 +516,21 @@ class ERP5TypeInformation(XMLObject, return sorted(self._getCategoryTool().getBaseCategoryList()) # - # Compatibitility code for actions + # XXX CMF compatibility # + security.declareProtected(Permissions.ManagePortal, + 'setPropertySheetList') + def setPropertySheetList(self, property_sheet_list): + self._setTypePropertySheetList(property_sheet_list) + + security.declareProtected(Permissions.AccessContentsInformation, + 'getHiddenContentTypeList') + def getHiddenContentTypeList(self): + return self.getTypeHiddenContentTypeList(()) + + # Compatibitility code for actions + security.declareProtected(Permissions.ModifyPortalContent, 'addAction') def addAction(self, id, name, action, condition, permission, category, icon=None, visible=1, priority=1.0, REQUEST=None, diff --git a/product/ERP5Type/Tool/TypesTool.py b/product/ERP5Type/Tool/TypesTool.py index 145af9cb335160eef030041a4fcf2e8cf39b91f8..fea55da398b81b96e37ea7f312316225e9af6188 100644 --- a/product/ERP5Type/Tool/TypesTool.py +++ b/product/ERP5Type/Tool/TypesTool.py @@ -39,6 +39,13 @@ class TypesTool(BaseTool, CMFCore_TypesTool.TypesTool): security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) + security.declarePrivate('getFilteredActionListFor') + def getFilteredActionListFor(self, ob=None): + if ob is not None: + type_info = self.getTypeInfo(ob) + if type_info is not None: + return type_info.getFilteredActionListFor(ob) + return () def getTypeInfo(self, *args): if not len(args): diff --git a/product/ERP5Type/patches/ActionsTool.py b/product/ERP5Type/patches/ActionsTool.py index 41186e272e7928b20ad99ae529eb5a7d5b0cf4aa..6cf31a13893136ed8287d6cb15be5aafd99d53c7 100644 --- a/product/ERP5Type/patches/ActionsTool.py +++ b/product/ERP5Type/patches/ActionsTool.py @@ -19,7 +19,7 @@ ActionsTool_listFilteredActionsFor = ActionsTool.listFilteredActionsFor def listFilteredActionsFor(self, object=None): """ List all actions available to the user. - This patch remove inclusion of actions from the object itself. + This patch removes inclusion of actions from the object itself. It was never used and now, it breaks objects inside Types Tool. """ actions = [] @@ -29,6 +29,18 @@ def listFilteredActionsFor(self, object=None): provider = getattr(self, provider_name) if IActionProvider.isImplementedBy(provider): actions.extend( provider.listActionInfos(object=object) ) + elif hasattr(provider, 'getFilteredActionListFor'): + from Products.ERP5Type.ERP5Type import getExprContext + ec = getExprContext(self, object) + actions += sorted(({ + 'id': action.getReference(), + 'name': action.getTitle(), + 'description': action.getDescription(), + 'category': action.getActionType(), + 'priority': action.getFloatIndex(), + 'url': action.getActionUrl(ec), + } for action in provider.getFilteredActionListFor(object)), + key=lambda x: x['priority']) else: # for Action Providers written for CMF versions before 1.5 actions.extend( self._listActionInfos(provider, object) ) diff --git a/product/ERP5Type/patches/CMFCoreUtils.py b/product/ERP5Type/patches/CMFCoreUtils.py index f00372cb0c4b0545c38b71103de005d76b896218..2f0d653725a1dce023be1686ced3f035d0f19bcb 100644 --- a/product/ERP5Type/patches/CMFCoreUtils.py +++ b/product/ERP5Type/patches/CMFCoreUtils.py @@ -15,7 +15,6 @@ from warnings import warn from Products.CMFCore.exceptions import AccessControl_Unauthorized, NotFound from Products.CMFCore.utils import getActionContext -from Products.CMFCore.utils import _verifyActionPermissions from Products.CMFCore.Expression import getExprContext from Products.CMFCore import PortalContent @@ -25,8 +24,8 @@ from zLOG import LOG This patch is based on Products/CMFCore/utils.py file from CMF 1.5.0 package. Please update the following file if you update CMF to greater version. The modifications in this method are: - * new filter on default action (= "action.getCategory().endswith('_%s' % view)" statement) - * new test on action condition (= "action.testCondition(context)" statement) + * new filter on default action (= ".endswith('_%s' % view)" statement) + * use action API to check its visibility in the context This method was patched to let CMF choose between several default actions according conditions. """ @@ -37,38 +36,33 @@ def CMFCoreUtils_getViewFor(obj, view='view'): 'Aliases.', DeprecationWarning) ti = obj.getTypeInfo() + if ti is None: + raise NotFound('Cannot find default view for %r' % obj.getPath()) - if ti is not None: - context = getActionContext( obj ) - test_context = getExprContext(obj, obj) # Patch 1: mimic _listActionInfos in ActionsTool - actions = ti.listActions() - for action in actions: - # portal_types hack - action_type = action.getActionType() - reference = getattr(action, 'reference', action.id) - if reference == view or action_type.endswith('_%s' % view): # Patch 2: consider anything ending by _view - if _verifyActionPermissions(obj, action): - if action.isVisible() and action.testCondition(test_context): # Patch 3: test actions - target = action.action(context).strip() - if target.startswith('/'): - target = target[1:] - __traceback_info__ = ( ti.getId(), target ) - return obj.restrictedTraverse( target ) - - # "view" action is not present or not allowed. - # Find something that's allowed. - for action in actions: - if _verifyActionPermissions(obj, action): - if action.visible and action.testCondition(test_context): # Patch 3: test actions - target = action.action(context).strip() - if target.startswith('/'): - target = target[1:] - __traceback_info__ = ( ti.getId(), target ) - return obj.restrictedTraverse( target ) - raise AccessControl_Unauthorized( 'No accessible views available for ' - '%s' % '/'.join( obj.getPhysicalPath() ) ) + context = getActionContext(obj) + ec = getExprContext(obj, obj) + best_action = (), None + for action in ti.getFilteredActionListFor(obj): + if action.getReference() == view: + if action.test(ec): + break + else: + # In case that "view" action is not present or not allowed, + # find something that's allowed. + index = (action.getActionType().endswith('_' + view), + -action.getFloatIndex()) + if best_action[0] < index and action.test(ec): + best_action = index, action else: - raise NotFound('Cannot find default view for "%s"' % - '/'.join(obj.getPhysicalPath())) + action = best_action[1] + if action is None: + raise AccessControl_Unauthorized('No accessible views available for %r' + % obj.getPath()) + + target = action.getActionUrl(context).strip() + if target.startswith('/'): + target = target[1:] + __traceback_info__ = ti.getId(), target + return obj.restrictedTraverse(target) PortalContent._getViewFor = CMFCoreUtils_getViewFor