Back to index

plone3  3.1.7
FactoryTool.py
Go to the documentation of this file.
00001 import logging
00002 import os
00003 
00004 from zope.interface import implements
00005 
00006 import Globals
00007 from AccessControl import Owned, ClassSecurityInfo, getSecurityManager
00008 from AccessControl.Permission import Permission
00009 from Acquisition import aq_parent, aq_base, aq_inner, aq_get
00010 from OFS.SimpleItem import SimpleItem
00011 from ZPublisher.Publish import call_object, missing_name, dont_publish_class
00012 from ZPublisher.mapply import mapply
00013 from Products.CMFPlone import cmfplone_globals
00014 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00015 from Products.CMFCore.permissions import ManagePortal
00016 from Products.CMFCore.utils import UniqueObject
00017 from Products.CMFCore.utils import getToolByName
00018 from StructuredText.StructuredText import HTML
00019 from Products.CMFPlone.interfaces import IFactoryTool
00020 from Products.CMFPlone.PloneFolder import PloneFolder as TempFolderBase
00021 from Products.CMFPlone.PloneBaseTool import PloneBaseTool
00022 from Products.CMFPlone.utils import base_hasattr
00023 from Products.CMFPlone.utils import log_exc
00024 from ZODB.POSException import ConflictError
00025 
00026 FACTORY_INFO = '__factory__info__'
00027 
00028 # ##############################################################################
00029 # A class used for generating the temporary folder that will
00030 # hold temporary objects.  We need a separate class so that
00031 # we can add all types to types_tool's allowed_content_types
00032 # for the class without having side effects in the rest of
00033 # the portal.
00034 class TempFolder(TempFolderBase):
00035     portal_type = meta_type = 'TempFolder'
00036     isPrincipiaFolderish = 0
00037 
00038     # override getPhysicalPath so that temporary objects return a full path
00039     # that includes the acquisition parent of portal_factory (otherwise we get
00040     # portal_root/portal_factory/... no matter where the object will reside)
00041     def getPhysicalPath(self):
00042         '''Returns a path (an immutable sequence of strings)
00043         that can be used to access this object again
00044         later, for example in a copy/paste operation.  getPhysicalRoot()
00045         and getPhysicalPath() are designed to operate together.
00046         '''
00047         portal_factory = aq_parent(aq_inner(self))
00048         path = aq_parent(portal_factory).getPhysicalPath() + \
00049             (portal_factory.getId(), self.getId(),)
00050         return path
00051 
00052     # override / delegate local roles methods
00053     def __ac_local_roles__(self):
00054         """__ac_local_roles__ needs to be handled carefully.
00055         Zope's and GRUF's User.getRolesInContext both walk up the
00056         acquisition hierarchy using aq_parent(aq_inner(obj)) when
00057         they gather local roles, and this process will result in
00058         their walking from TempFolder to portal_factory to the portal root."""
00059         object = aq_parent(aq_parent(self))
00060         local_roles = {}
00061         while 1:
00062             # Get local roles for this user
00063             lr = getattr(object, '__ac_local_roles__', None)
00064             if lr:
00065                 if callable(lr):
00066                     lr=lr()
00067                 lr = lr or {}
00068                 for k, v in lr.items():
00069                     if not local_roles.has_key(k):
00070                         local_roles[k] = []
00071                     for role in v:
00072                         if not role in local_roles[k]:
00073                             local_roles[k].append(role)
00074 
00075             # Check if local role has to be acquired (PLIP 16)
00076             if getattr(object, '__ac_local_roles_block__', None):
00077                 # Ok, we have to stop there, as lr. blocking is enabled
00078                 break
00079 
00080             # Prepare next iteration
00081             inner = getattr(object, 'aq_inner', object)
00082             parent = getattr(inner, 'aq_parent', None)
00083             if parent is not None:
00084                 object = parent
00085                 continue
00086             if hasattr(object, 'im_self'):
00087                 object=object.im_self
00088                 object=getattr(object, 'aq_inner', object)
00089                 continue
00090             break
00091         return local_roles
00092 
00093     def has_local_roles(self):
00094         return len(self.__ac_local_roles__())
00095 
00096     def get_local_roles_for_userid(self, userid):
00097         return tuple(self.__ac_local_roles__().get(userid, []))
00098 
00099     def get_valid_userids(self):
00100         return aq_parent(aq_parent(self)).get_valid_userids()
00101 
00102     def valid_roles(self):
00103         return aq_parent(aq_parent(self)).valid_roles()
00104 
00105     def validate_roles(self, roles):
00106         return aq_parent(aq_parent(self)).validate_roles(roles)
00107 
00108     def userdefined_roles(self):
00109         return aq_parent(aq_parent(self)).userdefined_roles()
00110 
00111     # delegate Owned methods
00112     def owner_info(self):
00113         return aq_parent(aq_parent(self)).owner_info()
00114 
00115     def getOwner(self, info=0,
00116                  aq_get=aq_get,
00117                  UnownableOwner=Owned.UnownableOwner,
00118                  getSecurityManager=getSecurityManager,
00119                  ):
00120         return aq_parent(aq_parent(self)).getOwner(info, aq_get, UnownableOwner, getSecurityManager)
00121 
00122     def userCanTakeOwnership(self):
00123         return aq_parent(aq_parent(self)).userCanTakeOwnership()
00124 
00125     # delegate allowedContentTypes
00126     def allowedContentTypes(self):
00127         return aq_parent(aq_parent(self)).allowedContentTypes()
00128 
00129     def __getitem__(self, id):
00130         # Zope's inner acquisition chain for objects returned by __getitem__ will be
00131         # portal -> portal_factory -> temporary_folder -> object
00132         # What we really want is for the inner acquisition chain to be
00133         # intended_parent_folder -> portal_factory -> temporary_folder -> object
00134         # So we need to rewrap...
00135         portal_factory = aq_parent(self)
00136         intended_parent = aq_parent(portal_factory)
00137 
00138         # If the intended parent has an object with the given id, just do a passthrough
00139         if hasattr(intended_parent, id):
00140             return getattr(intended_parent, id)
00141 
00142         # rewrap portal_factory
00143         portal_factory = aq_base(portal_factory).__of__(intended_parent)
00144         # rewrap self
00145         temp_folder = aq_base(self).__of__(portal_factory)
00146 
00147         if id in self.objectIds():
00148             return (aq_base(self._getOb(id)).__of__(temp_folder)).__of__(intended_parent)
00149         else:
00150             type_name = self.getId()
00151             try:
00152                 self.invokeFactory(id=id, type_name=type_name)
00153             except ConflictError:
00154                 raise
00155             except:
00156                 # some errors from invokeFactory (AttributeError, maybe others)
00157                 # get swallowed -- dump the exception to the log to make sure
00158                 # developers can see what's going on
00159                 log_exc(severity=logging.DEBUG)
00160                 raise
00161             obj = self._getOb(id)
00162             
00163             # keep obj out of the catalog
00164             obj.unindexObject()
00165 
00166             # additionally keep it out of Archetypes UID and refs catalogs
00167             if base_hasattr(obj, '_uncatalogUID'):
00168                 obj._uncatalogUID(obj)
00169             if base_hasattr(obj, '_uncatalogRefs'):
00170                 obj._uncatalogRefs(obj)
00171 
00172             return (aq_base(obj).__of__(temp_folder)).__of__(intended_parent)
00173 
00174     # ignore rename requests since they don't do anything
00175     def manage_renameObject(self, id, new_id, REQUEST=None):
00176         pass
00177 
00178 
00179 # ##############################################################################
00180 class FactoryTool(PloneBaseTool, UniqueObject, SimpleItem):
00181     """ """
00182     id = 'portal_factory'
00183     meta_type= 'Plone Factory Tool'
00184     toolicon = 'skins/plone_images/add_icon.gif'
00185     security = ClassSecurityInfo()
00186     isPrincipiaFolderish = 0
00187 
00188     implements(IFactoryTool)
00189 
00190     __implements__ = (PloneBaseTool.__implements__, SimpleItem.__implements__, )
00191 
00192     manage_options = ( ({'label':'Overview', 'action':'manage_overview'}, \
00193                         {'label':'Documentation', 'action':'manage_docs'}, \
00194                         {'label':'Factory Types', 'action':'manage_portal_factory_types'},) +
00195                        SimpleItem.manage_options)
00196 
00197     security.declareProtected(ManagePortal, 'manage_overview')
00198     manage_overview = PageTemplateFile('www/portal_factory_manage_overview', globals())
00199     manage_overview.__name__ = 'manage_overview'
00200     manage_overview._need__name__ = 0
00201 
00202     security.declareProtected(ManagePortal, 'manage_portal_factory_types')
00203     manage_portal_factory_types = PageTemplateFile(os.path.join('www', 'portal_factory_manage_types'), globals())
00204     manage_portal_factory_types.__name__ = 'manage_portal_factory_types'
00205     manage_portal_factory_types._need__name__ = 0
00206 
00207     manage_main = manage_overview
00208 
00209     security.declareProtected(ManagePortal, 'manage_docs')
00210     manage_docs = PageTemplateFile(os.path.join('www','portal_factory_manage_docs'), globals())
00211     manage_docs.__name__ = 'manage_docs'
00212 
00213     wwwpath = os.path.join(Globals.package_home(cmfplone_globals), 'www')
00214     f = open(os.path.join(wwwpath, 'portal_factory_docs.stx'), 'r')
00215     _docs = f.read()
00216     f.close()
00217     _docs = HTML(_docs)
00218 
00219     security.declarePublic('docs')
00220     def docs(self):
00221         """Returns FactoryTool docs formatted as HTML"""
00222         return self._docs
00223 
00224     def getFactoryTypes(self):
00225         if not hasattr(self, '_factory_types'):
00226             self._factory_types = {}
00227         return self._factory_types
00228 
00229     security.declareProtected(ManagePortal, 'manage_setPortalFactoryTypes')
00230     def manage_setPortalFactoryTypes(self, REQUEST=None, listOfTypeIds=None):
00231         """Set the portal types that should use the factory."""
00232         if listOfTypeIds is not None:
00233             dict = {}
00234             for l in listOfTypeIds:
00235                 dict[l] = 1
00236         elif REQUEST is not None:
00237             dict = REQUEST.form
00238         if dict is None:
00239             dict = {}
00240         self._factory_types = {}
00241         types_tool = getToolByName(self, 'portal_types')
00242         for t in types_tool.listContentTypes():
00243             if dict.has_key(t):
00244                 self._factory_types[t] = 1
00245         self._p_changed = 1
00246         if REQUEST:
00247             REQUEST.RESPONSE.redirect('manage_main')
00248 
00249     def doCreate(self, obj, id=None, **kw):
00250         """Create a real object from a temporary object."""
00251         if self.isTemporary(obj=obj):
00252             if id is not None:
00253                 id = id.strip()
00254             if not id:
00255                 if hasattr(obj, 'getId') and callable(getattr(obj, 'getId')):
00256                     id = obj.getId()
00257                 else:
00258                     id = getattr(obj, 'id', None)
00259             type_name = aq_parent(aq_inner(obj)).id  # get the ID of the TempFolder
00260             folder = aq_parent(aq_parent(aq_parent(aq_inner(obj))))
00261             folder.invokeFactory(id=id, type_name=type_name)
00262             obj = getattr(folder, id)
00263 
00264             # give ownership to currently authenticated member if not anonymous
00265             # TODO is this necessary?
00266             membership_tool = getToolByName(self, 'portal_membership')
00267             if not membership_tool.isAnonymousUser():
00268                 member = membership_tool.getAuthenticatedMember()
00269                 obj.changeOwnership(member.getUser(), 1)
00270             if hasattr(aq_base(obj), 'manage_afterPortalFactoryCreate'):
00271                 obj.manage_afterPortalFactoryCreate()
00272         return obj
00273 
00274     def _fixRequest(self):
00275         """Our before_publishing_traverse call mangles URL0.  This fixes up
00276         the REQUEST."""
00277         factory_info = self.REQUEST.get(FACTORY_INFO, None)
00278         if not factory_info:
00279             return
00280         stack = factory_info['stack']
00281         URL = self.REQUEST.URL0 + '/' + '/'.join(stack)
00282         self.REQUEST.set('URL', URL)
00283 
00284         url_list = URL.split('/')
00285         n = 0
00286         while len(url_list) > 0 and url_list[-1] != '':
00287             self.REQUEST.set('URL%d' % n, '/'.join(url_list))
00288             url_list = url_list[:-1]
00289             n = n + 1
00290 
00291         url_list = URL.split('/')
00292         m = 0
00293         while m < n:
00294             self.REQUEST.set('BASE%d' % m, '/'.join(url_list[0:len(url_list)-n+1+m]))
00295             m = m + 1
00296         # TODO fix URLPATHn, BASEPATHn here too
00297 
00298     def isTemporary(self, obj):
00299         """Check to see if an object is temporary"""
00300         ob = aq_base(aq_parent(aq_inner(obj)))
00301         return hasattr(ob, 'meta_type') and ob.meta_type == TempFolder.meta_type
00302 
00303     def __before_publishing_traverse__(self, other, REQUEST):
00304         if REQUEST.get(FACTORY_INFO, None):
00305             del REQUEST[FACTORY_INFO]
00306 
00307         stack = REQUEST.get('TraversalRequestNameStack')
00308         stack = [str(s) for s in stack]  # convert from unicode if necessary (happens in Epoz for some weird reason)
00309         # need 2 more things on the stack at least for portal_factory to kick in:
00310         #    (1) a type, and (2) an id
00311         if len(stack) < 2: # ignore
00312             return
00313         type_name = stack[-1]
00314         types_tool = getToolByName(self, 'portal_types')
00315         # make sure this is really a type name
00316         if not type_name in types_tool.listContentTypes():
00317             return # nope -- do nothing
00318 
00319         id = stack[-2]
00320         intended_parent = aq_parent(self)
00321         if hasattr(intended_parent, id):
00322             return # do normal traversal via __bobo_traverse__
00323 
00324         # about to create an object - further traversal will be prevented
00325         #
00326         # before halting traversal, check for method aliases
00327         # stack should be [...optional stuff..., id, type_name]
00328         key = stack and stack[-3] or '(Default)'
00329         ti = types_tool.getTypeInfo(type_name)
00330         method_id = ti and ti.queryMethodID(key)
00331         if method_id:
00332             if key != '(Default)':
00333                 del(stack[-3])
00334             if method_id != '(Default)':
00335                 stack.insert(-2, method_id)
00336             REQUEST._hacked_path = 1
00337         
00338         stack.reverse()
00339         factory_info = {'stack':stack}
00340         REQUEST.set(FACTORY_INFO, factory_info)
00341         REQUEST.set('TraversalRequestNameStack', [])
00342 
00343     def __bobo_traverse__(self, REQUEST, name):
00344         # __bobo_traverse__ can be invoked directly by a restricted_traverse method call
00345         # in which case the traversal stack will not have been cleared by __before_publishing_traverse__
00346         name = str(name) # fix unicode weirdness
00347         types_tool = getToolByName(self, 'portal_types')
00348         if not name in types_tool.listContentTypes():
00349             return getattr(self, name) # not a type name -- do the standard thing
00350         return self._getTempFolder(str(name)) # a type name -- return a temp folder
00351 
00352     security.declarePublic('__call__')
00353     def __call__(self, *args, **kwargs):
00354         """call method"""
00355         self._fixRequest()
00356         factory_info = self.REQUEST.get(FACTORY_INFO, {})
00357         stack = factory_info['stack']
00358         type_name = stack[0]
00359         id = stack[1]
00360 
00361         # do a passthrough if parent contains the id
00362         if id in aq_parent(self).objectIds():
00363             return aq_parent(self).restrictedTraverse('/'.join(stack[1:]))(*args, **kwargs)
00364 
00365         tempFolder = self._getTempFolder(type_name)
00366         # Mysterious hack that fixes some problematic interactions with SpeedPack:
00367         #   Get the first item in the stack by explicitly calling __getitem__
00368         temp_obj = tempFolder.__getitem__(id)
00369         stack = stack[2:]
00370         if stack:
00371             obj = temp_obj.restrictedTraverse('/'.join(stack))
00372         else:
00373             obj = temp_obj
00374         return mapply(obj, self.REQUEST.args, self.REQUEST,
00375                                call_object, 1, missing_name, dont_publish_class,
00376                                self.REQUEST, bind=1)
00377 
00378     index_html = None  # call __call__, not index_html
00379 
00380     def _getTempFolder(self, type_name):
00381         
00382         factory_info = self.REQUEST.get(FACTORY_INFO, {})
00383         tempFolder = factory_info.get(type_name, None)
00384         if tempFolder:
00385             tempFolder = aq_inner(tempFolder).__of__(self)
00386             return tempFolder
00387         
00388         # make sure we can add an object of this type to the temp folder
00389         types_tool = getToolByName(self, 'portal_types')
00390         if not type_name in types_tool.TempFolder.allowed_content_types:
00391             # update allowed types for tempfolder
00392             types_tool.TempFolder.allowed_content_types=(types_tool.listContentTypes())
00393             
00394         tempFolder = TempFolder(type_name).__of__(self)
00395         intended_parent = aq_parent(self)
00396         portal = getToolByName(self, 'portal_url').getPortalObject()
00397         folder_roles = {} # mapping from permission name to list or tuple of roles
00398                           # list if perm is acquired; tuple if not
00399         n_acquired = 0    # number of permissions that are acquired
00400 
00401         # build initial folder_roles dictionary
00402         for p in intended_parent.ac_inherited_permissions(1):
00403             name, value = p[:2]
00404             p=Permission(name,value,intended_parent)
00405             roles = p.getRoles()
00406             folder_roles[name] = roles
00407             if isinstance(roles, list):
00408                 n_acquired += 1
00409 
00410         # If intended_parent is not the portal, walk up the acquisition hierarchy and
00411         # acquire permissions explicitly so we can assign the acquired version to the
00412         # temp_folder.  In addition to being cumbersome, this is undoubtedly very slow.
00413         if intended_parent != portal:
00414             parent = aq_parent(aq_inner(intended_parent))
00415             while(n_acquired and parent!=portal):
00416                 n_acquired = 0
00417                 for p in parent.ac_inherited_permissions(1):
00418                     name, value = p[:2]
00419                     roles = folder_roles[name]
00420                     if isinstance(roles, list):
00421                         p=Permission(name,value,parent)
00422                         aq_roles=p.getRoles()
00423                         for r in aq_roles:
00424                             if not r in roles:
00425                                 roles.append(r)
00426                         if isinstance(aq_roles, list):
00427                             n_acquired += 1
00428                         else:
00429                             roles = tuple(roles)
00430                         folder_roles[name] = roles
00431                 parent = aq_parent(aq_inner(parent))
00432         for name, roles in folder_roles.items():
00433             tempFolder.manage_permission(name, roles, acquire=isinstance(roles, list))
00434 
00435         factory_info[type_name] = tempFolder
00436         self.REQUEST.set(FACTORY_INFO, factory_info)
00437         return tempFolder
00438 
00439 Globals.InitializeClass(FactoryTool)