Back to index

plone3  3.1.7
PortalFolder.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ PortalFolder: CMF-enabled Folder objects.
00014 
00015 $Id: PortalFolder.py 78547 2007-08-02 18:33:38Z yuppie $
00016 """
00017 
00018 import base64
00019 import marshal
00020 import re
00021 from warnings import warn
00022 
00023 from AccessControl import ClassSecurityInfo
00024 from AccessControl import getSecurityManager
00025 from Acquisition import aq_parent, aq_inner, aq_base
00026 from Globals import InitializeClass
00027 from OFS.Folder import Folder
00028 from OFS.OrderSupport import OrderSupport
00029 from zope.component.factory import Factory
00030 from zope.interface import implements
00031 
00032 from CMFCatalogAware import CMFCatalogAware
00033 from DynamicType import DynamicType
00034 from exceptions import AccessControl_Unauthorized
00035 from exceptions import BadRequest
00036 from exceptions import zExceptions_Unauthorized
00037 from interfaces import IFolderish
00038 from interfaces import IMutableMinimalDublinCore
00039 from interfaces import ISiteRoot
00040 from interfaces.Folderish import Folderish as z2IFolderish
00041 from permissions import AddPortalContent
00042 from permissions import AddPortalFolders
00043 from permissions import DeleteObjects
00044 from permissions import ListFolderContents
00045 from permissions import ManagePortal
00046 from permissions import ManageProperties
00047 from permissions import View
00048 from utils import _checkPermission
00049 from utils import getToolByName
00050 
00051 
00052 class PortalFolderBase(DynamicType, CMFCatalogAware, Folder):
00053 
00054     """Base class for portal folder.
00055     """
00056 
00057     implements(IFolderish, IMutableMinimalDublinCore)
00058     __implements__ = (z2IFolderish, DynamicType.__implements__,
00059                       Folder.__implements__)
00060 
00061     security = ClassSecurityInfo()
00062 
00063     description = ''
00064 
00065     manage_options = ( Folder.manage_options[:1]
00066                      + ({'label': 'Components',
00067                          'action': 'manage_components'},)
00068                      + Folder.manage_options[1:]
00069                      + CMFCatalogAware.manage_options
00070                      )
00071 
00072     def __init__(self, id, title='', description=''):
00073         self.id = id
00074         self.title = title
00075         self.description = description
00076 
00077     #
00078     #   'IMutableMinimalDublinCore' interface methods
00079     #
00080     security.declareProtected(View, 'Title')
00081     def Title(self):
00082         """ Dublin Core Title element - resource name.
00083         """
00084         return self.title
00085 
00086     security.declareProtected(View, 'Description')
00087     def Description(self):
00088         """ Dublin Core Description element - resource summary.
00089         """
00090         return self.description
00091 
00092     security.declareProtected(View, 'Type')
00093     def Type(self):
00094         """ Dublin Core Type element - resource type.
00095         """
00096         ti = self.getTypeInfo()
00097         return ti is not None and ti.Title() or 'Unknown'
00098 
00099     security.declareProtected(ManageProperties, 'setTitle')
00100     def setTitle(self, title):
00101         """ Set Dublin Core Title element - resource name.
00102         """
00103         self.title = title
00104 
00105     security.declareProtected(ManageProperties, 'setDescription')
00106     def setDescription(self, description):
00107         """ Set Dublin Core Description element - resource summary.
00108         """
00109         self.description = description
00110 
00111     #
00112     #   other methods
00113     #
00114     security.declareProtected(ManageProperties, 'edit')
00115     def edit(self, title='', description=''):
00116         """
00117         Edit the folder title (and possibly other attributes later)
00118         """
00119         self.setTitle( title )
00120         self.setDescription( description )
00121         self.reindexObject()
00122 
00123     security.declarePublic('allowedContentTypes')
00124     def allowedContentTypes( self ):
00125         """
00126             List type info objects for types which can be added in
00127             this folder.
00128         """
00129         result = []
00130         portal_types = getToolByName(self, 'portal_types')
00131         myType = portal_types.getTypeInfo(self)
00132 
00133         if myType is not None:
00134             for contentType in portal_types.listTypeInfo(self):
00135                 if myType.allowType( contentType.getId() ):
00136                     result.append( contentType )
00137         else:
00138             result = portal_types.listTypeInfo()
00139 
00140         return filter( lambda typ, container=self:
00141                           typ.isConstructionAllowed( container )
00142                      , result )
00143 
00144     def _filteredItems( self, ids, filt ):
00145         """
00146             Apply filter, a mapping, to child objects indicated by 'ids',
00147             returning a sequence of ( id, obj ) tuples.
00148         """
00149         # Restrict allowed content types
00150         if filt is None:
00151             filt = {}
00152         else:
00153             # We'll modify it, work on a copy.
00154             filt = filt.copy()
00155         pt = filt.get('portal_type', [])
00156         if isinstance(pt, basestring):
00157             pt = [pt]
00158         types_tool = getToolByName(self, 'portal_types')
00159         allowed_types = types_tool.listContentTypes()
00160         if not pt:
00161             pt = allowed_types
00162         else:
00163             pt = [t for t in pt if t in allowed_types]
00164         if not pt:
00165             # After filtering, no types remain, so nothing should be
00166             # returned.
00167             return []
00168         filt['portal_type'] = pt
00169 
00170         query = ContentFilter(**filt)
00171         result = []
00172         append = result.append
00173         get = self._getOb
00174         for id in ids:
00175             obj = get( id )
00176             if query(obj):
00177                 append( (id, obj) )
00178         return result
00179 
00180     #
00181     #   'IFolderish' interface methods
00182     #
00183     security.declarePublic('contentItems')
00184     def contentItems(self, filter=None):
00185         # List contentish and folderish sub-objects and their IDs.
00186         # (method is without docstring to disable publishing)
00187         #
00188         ids = self.objectIds()
00189         return self._filteredItems(ids, filter)
00190 
00191     security.declarePublic('contentIds')
00192     def contentIds(self, filter=None):
00193         # List IDs of contentish and folderish sub-objects.
00194         # (method is without docstring to disable publishing)
00195         #
00196         return [ item[0] for item in self.contentItems(filter) ]
00197 
00198     security.declarePublic('contentValues')
00199     def contentValues(self, filter=None):
00200         # List contentish and folderish sub-objects.
00201         # (method is without docstring to disable publishing)
00202         #
00203         return [ item[1] for item in self.contentItems(filter) ]
00204 
00205     security.declareProtected(ListFolderContents, 'listFolderContents')
00206     def listFolderContents(self, contentFilter=None):
00207         """ List viewable contentish and folderish sub-objects.
00208         """
00209         l = []
00210         for id, obj in self.contentItems(contentFilter):
00211             # validate() can either raise Unauthorized or return 0 to
00212             # mean unauthorized.
00213             try:
00214                 if getSecurityManager().validate(self, self, id, obj):
00215                     l.append(obj)
00216             except zExceptions_Unauthorized:  # Catch *all* Unauths!
00217                 pass
00218         return l
00219 
00220     #
00221     #   webdav Resource method
00222     #
00223 
00224     # protected by 'WebDAV access'
00225     def listDAVObjects(self):
00226         # List sub-objects for PROPFIND requests.
00227         # (method is without docstring to disable publishing)
00228         #
00229         if _checkPermission(ManagePortal, self):
00230             return self.objectValues()
00231         else:
00232             return self.listFolderContents()
00233 
00234     #
00235     #   other methods
00236     #
00237     security.declarePublic('encodeFolderFilter')
00238     def encodeFolderFilter(self, REQUEST):
00239         """
00240             Parse cookie string for using variables in dtml.
00241         """
00242         filter = {}
00243         for key, value in REQUEST.items():
00244             if key[:10] == 'filter_by_':
00245                 filter[key[10:]] = value
00246         encoded = base64.encodestring( marshal.dumps(filter) ).strip()
00247         encoded = ''.join( encoded.split('\n') )
00248         return encoded
00249 
00250     security.declarePublic('decodeFolderFilter')
00251     def decodeFolderFilter(self, encoded):
00252         """
00253             Parse cookie string for using variables in dtml.
00254         """
00255         filter = {}
00256         if encoded:
00257             filter.update(marshal.loads(base64.decodestring(encoded)))
00258         return filter
00259 
00260     def content_type( self ):
00261         """
00262             WebDAV needs this to do the Right Thing (TM).
00263         """
00264         return None
00265 
00266     # Ensure pure PortalFolders don't get cataloged.
00267     # XXX We may want to revisit this.
00268 
00269     def indexObject(self):
00270         pass
00271 
00272     def unindexObject(self):
00273         pass
00274 
00275     def reindexObject(self, idxs=[]):
00276         pass
00277 
00278     def reindexObjectSecurity(self):
00279         pass
00280 
00281     def PUT_factory( self, name, typ, body ):
00282         """ Factory for PUT requests to objects which do not yet exist.
00283 
00284         Used by NullResource.PUT.
00285 
00286         Returns -- Bare and empty object of the appropriate type (or None, if
00287         we don't know what to do)
00288         """
00289         registry = getToolByName(self, 'content_type_registry', None)
00290         if registry is None:
00291             return None
00292 
00293         typeObjectName = registry.findTypeName( name, typ, body )
00294         if typeObjectName is None:
00295             return None
00296 
00297         self.invokeFactory( typeObjectName, name )
00298 
00299         # invokeFactory does too much, so the object has to be removed again
00300         obj = aq_base( self._getOb( name ) )
00301         self._delObject( name )
00302         return obj
00303 
00304     security.declareProtected(AddPortalContent, 'invokeFactory')
00305     def invokeFactory(self, type_name, id, RESPONSE=None, *args, **kw):
00306         """ Invokes the portal_types tool.
00307         """
00308         pt = getToolByName(self, 'portal_types')
00309         myType = pt.getTypeInfo(self)
00310 
00311         if myType is not None:
00312             if not myType.allowType( type_name ):
00313                 raise ValueError('Disallowed subobject type: %s' % type_name)
00314 
00315         return pt.constructContent(type_name, self, id, RESPONSE, *args, **kw)
00316 
00317     security.declareProtected(AddPortalContent, 'checkIdAvailable')
00318     def checkIdAvailable(self, id):
00319         try:
00320             self._checkId(id)
00321         except BadRequest:
00322             return False
00323         else:
00324             return True
00325 
00326     def MKCOL_handler(self,id,REQUEST=None,RESPONSE=None):
00327         """
00328             Handle WebDAV MKCOL.
00329         """
00330         self.manage_addFolder( id=id, title='' )
00331 
00332     def _checkId(self, id, allow_dup=0):
00333         PortalFolderBase.inheritedAttribute('_checkId')(self, id, allow_dup)
00334 
00335         if allow_dup:
00336             return
00337 
00338         # FIXME: needed to allow index_html for join code
00339         if id == 'index_html':
00340             return
00341 
00342         # Another exception: Must allow "syndication_information" to enable
00343         # Syndication...
00344         if id == 'syndication_information':
00345             return
00346 
00347         # IDs starting with '@@' are reserved for views.
00348         if id[:2] == '@@':
00349             raise BadRequest('The id "%s" is invalid because it begins with '
00350                              '"@@".' % id)
00351 
00352         # This code prevents people other than the portal manager from
00353         # overriding skinned names and tools.
00354         if not getSecurityManager().checkPermission(ManagePortal, self):
00355             ob = aq_inner(self)
00356             while ob is not None:
00357                 if ISiteRoot.providedBy(ob):
00358                     break
00359                 # BBB
00360                 if getattr(ob, '_isPortalRoot', False):
00361                     warn("The '_isPortalRoot' marker attribute for site "
00362                          "roots is deprecated and will be removed in "
00363                          "CMF 2.3;  please mark the root object with "
00364                          "'ISiteRoot' instead.",
00365                          DeprecationWarning, stacklevel=2)
00366                     break
00367                 ob = aq_parent(ob)
00368 
00369             if ob is not None:
00370                 # If the portal root has a non-contentish object by this name,
00371                 # don't allow an override.
00372                 if (hasattr(ob, id) and
00373                     id not in ob.contentIds() and
00374                     # Allow root doted prefixed object name overrides
00375                     not id.startswith('.')):
00376                     raise BadRequest('The id "%s" is reserved.' % id)
00377             # Don't allow ids used by Method Aliases.
00378             ti = self.getTypeInfo()
00379             if ti and ti.queryMethodID(id, context=self):
00380                 raise BadRequest('The id "%s" is reserved.' % id)
00381         # Otherwise we're ok.
00382 
00383     def _verifyObjectPaste(self, object, validate_src=1):
00384         # This assists the version in OFS.CopySupport.
00385         # It enables the clipboard to function correctly
00386         # with objects created by a multi-factory.
00387         mt = getattr(object, '__factory_meta_type__', None)
00388         meta_types = getattr(self, 'all_meta_types', None)
00389 
00390         if mt is not None and meta_types is not None:
00391             method_name = None
00392             mt_permission = None
00393 
00394             if callable(meta_types):
00395                 meta_types = meta_types()
00396 
00397             for d in meta_types:
00398                 if d['name'] == mt:
00399                     method_name = d['action']
00400                     mt_permission = d.get('permission')
00401                     break
00402 
00403             if mt_permission is not None:
00404                 sm = getSecurityManager()
00405 
00406                 if sm.checkPermission(mt_permission, self):
00407                     if validate_src:
00408                         # Ensure the user is allowed to access the object on
00409                         # the clipboard.
00410                         parent = aq_parent(aq_inner(object))
00411 
00412                         if not sm.validate(None, parent, None, object):
00413                             raise AccessControl_Unauthorized(object.getId())
00414 
00415                         if validate_src == 2: # moving
00416                             if not sm.checkPermission(DeleteObjects, parent):
00417                                 raise AccessControl_Unauthorized('Delete not '
00418                                                                  'allowed.')
00419                 else:
00420                     raise AccessControl_Unauthorized('You do not possess the '
00421                             '%r permission in the context of the container '
00422                             'into which you are pasting, thus you are not '
00423                             'able to perform this operation.' % mt_permission)
00424             else:
00425                 raise AccessControl_Unauthorized('The object %r does not '
00426                         'support this operation.' % object.getId())
00427         else:
00428             # Call OFS' _verifyObjectPaste if necessary
00429             PortalFolderBase.inheritedAttribute(
00430                 '_verifyObjectPaste')(self, object, validate_src)
00431 
00432         # Finally, check allowed content types
00433         if hasattr(aq_base(object), 'getPortalTypeName'):
00434 
00435             type_name = object.getPortalTypeName()
00436 
00437             if type_name is not None:
00438 
00439                 pt = getToolByName(self, 'portal_types')
00440                 myType = pt.getTypeInfo(self)
00441 
00442                 if myType is not None and not myType.allowType(type_name):
00443                     raise ValueError('Disallowed subobject type: %s'
00444                                         % type_name)
00445 
00446     security.setPermissionDefault(AddPortalContent, ('Owner','Manager'))
00447 
00448     security.declareProtected(AddPortalFolders, 'manage_addFolder')
00449     def manage_addFolder( self
00450                         , id
00451                         , title=''
00452                         , REQUEST=None
00453                         ):
00454         """ Add a new folder-like object with id *id*.
00455 
00456         IF present, use the parent object's 'mkdir' alias; otherwise, just add
00457         a PortalFolder.
00458         """
00459         ti = self.getTypeInfo()
00460         method_id = ti and ti.queryMethodID('mkdir', context=self)
00461         if method_id:
00462             # call it
00463             getattr(self, method_id)(id=id)
00464         else:
00465             self.invokeFactory( type_name='Folder', id=id )
00466 
00467         ob = self._getOb( id )
00468         ob.setTitle( title )
00469         try:
00470             ob.reindexObject()
00471         except AttributeError:
00472             pass
00473 
00474         if REQUEST is not None:
00475             return self.manage_main(self, REQUEST, update_menu=1)
00476 
00477 InitializeClass(PortalFolderBase)
00478 
00479 
00480 class PortalFolder(OrderSupport, PortalFolderBase):
00481 
00482     """Implements portal content management, but not UI details.
00483     """
00484 
00485     __implements__ = (PortalFolderBase.__implements__,
00486                       OrderSupport.__implements__)
00487     portal_type = 'Folder'
00488 
00489     security = ClassSecurityInfo()
00490 
00491     manage_options = ( OrderSupport.manage_options +
00492                        PortalFolderBase.manage_options[1:] )
00493 
00494     security.declareProtected(AddPortalFolders, 'manage_addPortalFolder')
00495     def manage_addPortalFolder(self, id, title='', REQUEST=None):
00496         """Add a new PortalFolder object with id *id*.
00497         """
00498         ob = PortalFolder(id, title)
00499         self._setObject(id, ob)
00500         if REQUEST is not None:
00501             return self.folder_contents( # XXX: ick!
00502                 self, REQUEST, portal_status_message="Folder added")
00503 
00504 InitializeClass(PortalFolder)
00505 
00506 PortalFolderFactory = Factory(PortalFolder)
00507 
00508 manage_addPortalFolder = PortalFolder.manage_addPortalFolder.im_func
00509 
00510 
00511 class ContentFilter:
00512 
00513     """Represent a predicate against a content object's metadata.
00514     """
00515 
00516     MARKER = []
00517     filterSubject = []
00518     def __init__( self
00519                 , Title=MARKER
00520                 , Creator=MARKER
00521                 , Subject=MARKER
00522                 , Description=MARKER
00523                 , created=MARKER
00524                 , created_usage='range:min'
00525                 , modified=MARKER
00526                 , modified_usage='range:min'
00527                 , Type=MARKER
00528                 , portal_type=MARKER
00529                 , **Ignored
00530                 ):
00531 
00532         self.predicates = []
00533         self.description = []
00534 
00535         if Title is not self.MARKER:
00536             self.predicates.append( lambda x, pat=re.compile( Title ):
00537                                       pat.search( x.Title() ) )
00538             self.description.append( 'Title: %s' % Title )
00539 
00540         if Creator and Creator is not self.MARKER:
00541             self.predicates.append( lambda x, creator=Creator:
00542                                     creator in x.listCreators() )
00543             self.description.append( 'Creator: %s' % Creator )
00544 
00545         if Subject and Subject is not self.MARKER:
00546             self.filterSubject = Subject
00547             self.predicates.append( self.hasSubject )
00548             self.description.append( 'Subject: %s' % ', '.join(Subject) )
00549 
00550         if Description is not self.MARKER:
00551             self.predicates.append( lambda x, pat=re.compile( Description ):
00552                                       pat.search( x.Description() ) )
00553             self.description.append( 'Description: %s' % Description )
00554 
00555         if created is not self.MARKER:
00556             if created_usage == 'range:min':
00557                 self.predicates.append( lambda x, cd=created:
00558                                           cd <= x.created() )
00559                 self.description.append( 'Created since: %s' % created )
00560             if created_usage == 'range:max':
00561                 self.predicates.append( lambda x, cd=created:
00562                                           cd >= x.created() )
00563                 self.description.append( 'Created before: %s' % created )
00564 
00565         if modified is not self.MARKER:
00566             if modified_usage == 'range:min':
00567                 self.predicates.append( lambda x, md=modified:
00568                                           md <= x.modified() )
00569                 self.description.append( 'Modified since: %s' % modified )
00570             if modified_usage == 'range:max':
00571                 self.predicates.append( lambda x, md=modified:
00572                                           md >= x.modified() )
00573                 self.description.append( 'Modified before: %s' % modified )
00574 
00575         if Type:
00576             if isinstance(Type, basestring):
00577                 Type = [Type]
00578             self.predicates.append( lambda x, Type=Type:
00579                                       x.Type() in Type )
00580             self.description.append( 'Type: %s' % ', '.join(Type) )
00581 
00582         if portal_type and portal_type is not self.MARKER:
00583             if isinstance(portal_type, basestring):
00584                 portal_type = [portal_type]
00585             self.predicates.append( lambda x, pt=portal_type:
00586                                     hasattr(aq_base(x), 'getPortalTypeName')
00587                                     and x.getPortalTypeName() in pt )
00588             self.description.append( 'Portal Type: %s'
00589                                      % ', '.join(portal_type) )
00590 
00591     def hasSubject( self, obj ):
00592         """
00593         Converts Subject string into a List for content filter view.
00594         """
00595         for sub in obj.Subject():
00596             if sub in self.filterSubject:
00597                 return 1
00598         return 0
00599 
00600     def __call__( self, content ):
00601 
00602         for predicate in self.predicates:
00603 
00604             try:
00605                 if not predicate( content ):
00606                     return 0
00607             except (AttributeError, KeyError, IndexError, ValueError):
00608                 # predicates are *not* allowed to throw exceptions
00609                 return 0
00610 
00611         return 1
00612 
00613     def __str__( self ):
00614         """
00615             Return a stringified description of the filter.
00616         """
00617         return '; '.join(self.description)