import os import Globals from AccessControl import Owned, ClassSecurityInfo, getSecurityManager from AccessControl.Permission import Permission from Acquisition import aq_parent, aq_base, aq_inner, aq_get from OFS.SimpleItem import SimpleItem from ZPublisher.Publish import call_object, missing_name, dont_publish_class from ZPublisher.mapply import mapply from Products.CMFPlone import cmfplone_globals from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.CMFCore.permissions import ManagePortal from Products.CMFCore.utils import UniqueObject, getToolByName from StructuredText.StructuredText import HTML from Products.CMFPlone.PloneFolder import PloneFolder as TempFolderBase from Products.CMFPlone.PloneBaseTool import PloneBaseTool from Products.CMFPlone.utils import base_hasattr from ZODB.POSException import ConflictError ListType=type([]) FACTORY_INFO = '__factory__info__' # ############################################################################## # A class used for generating the temporary folder that will # hold temporary objects. We need a separate class so that # we can add all types to types_tool's allowed_content_types # for the class without having side effects in the rest of # the portal. class TempFolder(TempFolderBase): portal_type = meta_type = 'TempFolder' isPrincipiaFolderish = 0 # override getPhysicalPath so that temporary objects return a full path # that includes the acquisition parent of portal_factory (otherwise we get # portal_root/portal_factory/... no matter where the object will reside) def getPhysicalPath(self): '''Returns a path (an immutable sequence of strings) that can be used to access this object again later, for example in a copy/paste operation. getPhysicalRoot() and getPhysicalPath() are designed to operate together. ''' portal_factory = aq_parent(aq_inner(self)) path = aq_parent(portal_factory).getPhysicalPath() + \ (portal_factory.getId(), self.getId(),) return path # override / delegate local roles methods def __ac_local_roles__(self): """__ac_local_roles__ needs to be handled carefully. Zope's and GRUF's User.getRolesInContext both walk up the acquisition hierarchy using aq_parent(aq_inner(obj)) when they gather local roles, and this process will result in their walking from TempFolder to portal_factory to the portal root.""" object = aq_parent(aq_parent(self)) local_roles = {} while 1: # Get local roles for this user lr = getattr(object, '__ac_local_roles__', None) if lr: if callable(lr): lr=lr() lr = lr or {} for k, v in lr.items(): if not local_roles.has_key(k): local_roles[k] = [] for role in v: if not role in local_roles[k]: local_roles[k].append(role) # Check if local role has to be acquired (PLIP 16) if getattr(object, '__ac_local_roles_block__', None): # Ok, we have to stop there, as lr. blocking is enabled break # Prepare next iteration inner = getattr(object, 'aq_inner', object) parent = getattr(inner, 'aq_parent', None) if parent is not None: object = parent continue if hasattr(object, 'im_self'): object=object.im_self object=getattr(object, 'aq_inner', object) continue break return local_roles def has_local_roles(self): return len(self.__ac_local_roles__()) def get_local_roles_for_userid(self, userid): return tuple(self.__ac_local_roles__().get(userid, [])) def get_valid_userids(self): return aq_parent(aq_parent(self)).get_valid_userids() def valid_roles(self): return aq_parent(aq_parent(self)).valid_roles() def validate_roles(self, roles): return aq_parent(aq_parent(self)).validate_roles(roles) def userdefined_roles(self): return aq_parent(aq_parent(self)).userdefined_roles() # delegate Owned methods def owner_info(self): return aq_parent(aq_parent(self)).owner_info() def getOwner(self, info=0, aq_get=aq_get, UnownableOwner=Owned.UnownableOwner, getSecurityManager=getSecurityManager, ): return aq_parent(aq_parent(self)).getOwner(info, aq_get, UnownableOwner, getSecurityManager) def userCanTakeOwnership(self): return aq_parent(aq_parent(self)).userCanTakeOwnership() # delegate allowedContentTypes def allowedContentTypes(self): return aq_parent(aq_parent(self)).allowedContentTypes() def __getitem__(self, id): # Zope's inner acquisition chain for objects returned by __getitem__ will be # portal -> portal_factory -> temporary_folder -> object # What we really want is for the inner acquisition chain to be # intended_parent_folder -> portal_factory -> temporary_folder -> object # So we need to rewrap... portal_factory = aq_parent(self) intended_parent = aq_parent(portal_factory) # If the intended parent has an object with the given id, just do a passthrough if hasattr(intended_parent, id): return getattr(intended_parent, id) # rewrap portal_factory portal_factory = aq_base(portal_factory).__of__(intended_parent) # rewrap self temp_folder = aq_base(self).__of__(portal_factory) if id in self.objectIds(): return (aq_base(self._getOb(id)).__of__(temp_folder)).__of__(intended_parent) else: type_name = self.getId() try: self.invokeFactory(id=id, type_name=type_name) except ConflictError: raise except: # some errors from invokeFactory (AttributeError, maybe others) # get swallowed -- dump the exception to the log to make sure # developers can see what's going on getToolByName(self, 'plone_utils').logException() raise obj = self._getOb(id) # keep obj out of the catalog obj.unindexObject() # additionally keep it out of Archetypes UID and refs catalogs if base_hasattr(obj, '_uncatalogUID'): obj._uncatalogUID(obj) if base_hasattr(obj, '_uncatalogRefs'): obj._uncatalogRefs(obj) return (aq_base(obj).__of__(temp_folder)).__of__(intended_parent) # ignore rename requests since they don't do anything def manage_renameObject(self, id, new_id, REQUEST=None): pass # ############################################################################## class FactoryTool(PloneBaseTool, UniqueObject, SimpleItem): """ """ id = 'portal_factory' meta_type= 'Plone Factory Tool' toolicon = 'skins/plone_images/add_icon.gif' security = ClassSecurityInfo() isPrincipiaFolderish = 0 __implements__ = (PloneBaseTool.__implements__, SimpleItem.__implements__, ) manage_options = ( ({'label':'Overview', 'action':'manage_overview'}, \ {'label':'Documentation', 'action':'manage_docs'}, \ {'label':'Factory Types', 'action':'manage_portal_factory_types'},) + SimpleItem.manage_options) security.declareProtected(ManagePortal, 'manage_overview') manage_overview = PageTemplateFile('www/portal_factory_manage_overview', globals()) manage_overview.__name__ = 'manage_overview' manage_overview._need__name__ = 0 security.declareProtected(ManagePortal, 'manage_portal_factory_types') manage_portal_factory_types = PageTemplateFile(os.path.join('www', 'portal_factory_manage_types'), globals()) manage_portal_factory_types.__name__ = 'manage_portal_factory_types' manage_portal_factory_types._need__name__ = 0 manage_main = manage_overview security.declareProtected(ManagePortal, 'manage_docs') manage_docs = PageTemplateFile(os.path.join('www','portal_factory_manage_docs'), globals()) manage_docs.__name__ = 'manage_docs' wwwpath = os.path.join(Globals.package_home(cmfplone_globals), 'www') f = open(os.path.join(wwwpath, 'portal_factory_docs.stx'), 'r') _docs = f.read() f.close() _docs = HTML(_docs) security.declarePublic('docs') def docs(self): """Returns FactoryTool docs formatted as HTML""" return self._docs def getFactoryTypes(self): if not hasattr(self, '_factory_types'): self._factory_types = {} return self._factory_types security.declareProtected(ManagePortal, 'manage_setPortalFactoryTypes') def manage_setPortalFactoryTypes(self, REQUEST=None, listOfTypeIds=None): """Set the portal types that should use the factory.""" if listOfTypeIds is not None: dict = {} for l in listOfTypeIds: dict[l] = 1 elif REQUEST is not None: dict = REQUEST.form if dict is None: dict = {} self._factory_types = {} types_tool = getToolByName(self, 'portal_types') for t in types_tool.listContentTypes(): if dict.has_key(t): self._factory_types[t] = 1 self._p_changed = 1 if REQUEST: REQUEST.RESPONSE.redirect('manage_main') def doCreate(self, obj, id=None, **kw): """Create a real object from a temporary object.""" if self.isTemporary(obj=obj): if id is not None: id = id.strip() if not id: if hasattr(obj, 'getId') and callable(getattr(obj, 'getId')): id = obj.getId() else: id = getattr(obj, 'id', None) type_name = aq_parent(aq_inner(obj)).id # get the ID of the TempFolder folder = aq_parent(aq_parent(aq_parent(aq_inner(obj)))) folder.invokeFactory(id=id, type_name=type_name) obj = getattr(folder, id) # give ownership to currently authenticated member if not anonymous # TODO is this necessary? membership_tool = getToolByName(self, 'portal_membership') if not membership_tool.isAnonymousUser(): member = membership_tool.getAuthenticatedMember() obj.changeOwnership(member.getUser(), 1) if hasattr(aq_base(obj), 'manage_afterPortalFactoryCreate'): obj.manage_afterPortalFactoryCreate() return obj def _fixRequest(self): """Our before_publishing_traverse call mangles URL0. This fixes up the REQUEST.""" factory_info = self.REQUEST.get(FACTORY_INFO, None) if not factory_info: return stack = factory_info['stack'] URL = self.REQUEST.URL0 + '/' + '/'.join(stack) self.REQUEST.set('URL', URL) url_list = URL.split('/') n = 0 while len(url_list) > 0 and url_list[-1] != '': self.REQUEST.set('URL%d' % n, '/'.join(url_list)) url_list = url_list[:-1] n = n + 1 url_list = URL.split('/') m = 0 while m < n: self.REQUEST.set('BASE%d' % m, '/'.join(url_list[0:len(url_list)-n+1+m])) m = m + 1 # TODO fix URLPATHn, BASEPATHn here too def isTemporary(self, obj): """Check to see if an object is temporary""" ob = aq_base(aq_parent(aq_inner(obj))) return hasattr(ob, 'meta_type') and ob.meta_type == TempFolder.meta_type def __before_publishing_traverse__(self, other, REQUEST): if REQUEST.get(FACTORY_INFO, None): del REQUEST[FACTORY_INFO] stack = REQUEST.get('TraversalRequestNameStack') stack = [str(s) for s in stack] # convert from unicode if necessary (happens in Epoz for some weird reason) # need 2 more things on the stack at least for portal_factory to kick in: # (1) a type, and (2) an id if len(stack) < 2: # ignore return type_name = stack[-1] types_tool = getToolByName(self, 'portal_types') # make sure this is really a type name if not type_name in types_tool.listContentTypes(): return # nope -- do nothing id = stack[-2] intended_parent = aq_parent(self) if hasattr(intended_parent, id): return # do normal traversal via __bobo_traverse__ # about to create an object - further traversal will be prevented # # before halting traversal, check for method aliases # stack should be [...optional stuff..., id, type_name] key = stack and stack[-3] or '(Default)' ti = types_tool.getTypeInfo(type_name) method_id = ti and ti.queryMethodID(key) if method_id: if key != '(Default)': del(stack[-3]) if method_id != '(Default)': stack.insert(-2, method_id) REQUEST._hacked_path = 1 stack.reverse() factory_info = {'stack':stack} REQUEST.set(FACTORY_INFO, factory_info) REQUEST.set('TraversalRequestNameStack', []) def __bobo_traverse__(self, REQUEST, name): # __bobo_traverse__ can be invoked directly by a restricted_traverse method call # in which case the traversal stack will not have been cleared by __before_publishing_traverse__ name = str(name) # fix unicode weirdness types_tool = getToolByName(self, 'portal_types') if not name in types_tool.listContentTypes(): return getattr(self, name) # not a type name -- do the standard thing return self._getTempFolder(str(name)) # a type name -- return a temp folder security.declarePublic('__call__') def __call__(self, *args, **kwargs): """call method""" self._fixRequest() factory_info = self.REQUEST.get(FACTORY_INFO, {}) stack = factory_info['stack'] type_name = stack[0] id = stack[1] # do a passthrough if parent contains the id if id in aq_parent(self).objectIds(): return aq_parent(self).restrictedTraverse('/'.join(stack[1:]))(*args, **kwargs) tempFolder = self._getTempFolder(type_name) # Mysterious hack that fixes some problematic interactions with SpeedPack: # Get the first item in the stack by explicitly calling __getitem__ temp_obj = tempFolder.__getitem__(id) stack = stack[2:] if stack: obj = temp_obj.restrictedTraverse('/'.join(stack)) else: obj = temp_obj return mapply(obj, self.REQUEST.args, self.REQUEST, call_object, 1, missing_name, dont_publish_class, self.REQUEST, bind=1) index_html = None # call __call__, not index_html def _getTempFolder(self, type_name): factory_info = self.REQUEST.get(FACTORY_INFO, {}) tempFolder = factory_info.get(type_name, None) if tempFolder: tempFolder = aq_inner(tempFolder).__of__(self) return tempFolder # make sure we can add an object of this type to the temp folder types_tool = getToolByName(self, 'portal_types') if not type_name in types_tool.TempFolder.allowed_content_types: # update allowed types for tempfolder types_tool.TempFolder.allowed_content_types=(types_tool.listContentTypes()) tempFolder = TempFolder(type_name).__of__(self) intended_parent = aq_parent(self) portal = getToolByName(self, 'portal_url').getPortalObject() folder_roles = {} # mapping from permission name to list or tuple of roles # list if perm is acquired; tuple if not n_acquired = 0 # number of permissions that are acquired # build initial folder_roles dictionary for p in intended_parent.ac_inherited_permissions(1): name, value = p[:2] p=Permission(name,value,intended_parent) roles = p.getRoles() folder_roles[name] = roles if type(roles) is ListType: n_acquired += 1 # If intended_parent is not the portal, walk up the acquisition hierarchy and # acquire permissions explicitly so we can assign the acquired version to the # temp_folder. In addition to being cumbersome, this is undoubtedly very slow. if intended_parent != portal: parent = aq_parent(aq_inner(intended_parent)) while(n_acquired and parent!=portal): n_acquired = 0 for p in parent.ac_inherited_permissions(1): name, value = p[:2] roles = folder_roles[name] if type(roles) is ListType: p=Permission(name,value,parent) aq_roles=p.getRoles() for r in aq_roles: if not r in roles: roles.append(r) if type(aq_roles) is ListType: n_acquired += 1 else: roles = tuple(roles) folder_roles[name] = roles parent = aq_parent(aq_inner(parent)) for name, roles in folder_roles.items(): tempFolder.manage_permission(name, roles, acquire=type(roles) is ListType) factory_info[type_name] = tempFolder self.REQUEST.set(FACTORY_INFO, factory_info) return tempFolder Globals.InitializeClass(FactoryTool)