vendor/CMF/1.6.3/CMFCore

view PortalFolder.py @ 2:4c712d7bd1d7

Added tag 1.6.3 for changeset 1babb9d61518
author Georges Racinet on purity.racinet.fr <georges@racinet.fr>
date Fri, 09 Sep 2011 12:44:00 +0200
parents
children
line source
1 ##############################################################################
2 #
3 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE.
11 #
12 ##############################################################################
13 """ PortalFolder: CMF-enabled Folder objects.
15 $Id$
16 """
18 import base64
19 import marshal
20 import re
21 from warnings import warn
23 from AccessControl import ClassSecurityInfo
24 from AccessControl import getSecurityManager
25 from Acquisition import aq_parent, aq_inner, aq_base
26 from Globals import DTMLFile
27 from Globals import InitializeClass
28 from OFS.OrderSupport import OrderSupport
29 from OFS.Folder import Folder
31 from CMFCatalogAware import CMFCatalogAware
32 from DynamicType import DynamicType
33 from exceptions import AccessControl_Unauthorized
34 from exceptions import BadRequest
35 from exceptions import zExceptions_Unauthorized
36 from interfaces.Folderish import Folderish as IFolderish
37 from permissions import AddPortalContent
38 from permissions import AddPortalFolders
39 from permissions import ChangeLocalRoles
40 from permissions import DeleteObjects
41 from permissions import ListFolderContents
42 from permissions import ManagePortal
43 from permissions import ManageProperties
44 from permissions import View
45 from utils import _checkPermission
46 from utils import getToolByName
49 factory_type_information = (
50 { 'id' : 'Folder'
51 , 'meta_type' : 'Portal Folder'
52 , 'description' : """ Use folders to put content in categories."""
53 , 'icon' : 'folder_icon.gif'
54 , 'product' : 'CMFCore'
55 , 'factory' : 'manage_addPortalFolder'
56 , 'filter_content_types' : 0
57 , 'immediate_view' : 'folder_edit_form'
58 , 'aliases' : {'(Default)': 'index_html',
59 'view': 'index_html',
60 'index.html':'index_html'}
61 , 'actions' : ( { 'id' : 'view'
62 , 'name' : 'View'
63 , 'action': 'string:${object_url}'
64 , 'permissions' : (View,)
65 }
66 , { 'id' : 'edit'
67 , 'name' : 'Edit'
68 , 'action': 'string:${object_url}/folder_edit_form'
69 , 'permissions' : (ManageProperties,)
70 }
71 , { 'id' : 'localroles'
72 , 'name' : 'Local Roles'
73 , 'action':
74 'string:${object_url}/folder_localrole_form'
75 , 'permissions' : (ChangeLocalRoles,)
76 }
77 , { 'id' : 'folderContents'
78 , 'name' : 'Folder contents'
79 , 'action': 'string:${object_url}/folder_contents'
80 , 'permissions' : (ListFolderContents,)
81 }
82 , { 'id' : 'new'
83 , 'name' : 'New...'
84 , 'action': 'string:${object_url}/folder_factories'
85 , 'permissions' : (AddPortalContent,)
86 , 'visible' : 0
87 }
88 , { 'id' : 'rename_items'
89 , 'name' : 'Rename items'
90 , 'action': 'string:${object_url}/folder_rename_form'
91 , 'permissions' : (AddPortalContent,)
92 , 'visible' : 0
93 }
94 )
95 }
96 ,
97 )
100 class PortalFolderBase(DynamicType, CMFCatalogAware, Folder):
101 """Base class for portal folder
102 """
103 meta_type = 'Portal Folder Base'
105 __implements__ = (IFolderish, DynamicType.__implements__,
106 Folder.__implements__)
108 security = ClassSecurityInfo()
110 description = ''
112 manage_options = ( Folder.manage_options +
113 CMFCatalogAware.manage_options )
115 def __init__( self, id, title='' ):
116 self.id = id
117 self.title = title
119 #
120 # 'MutableDublinCore' interface methods
121 #
122 security.declareProtected(ManageProperties, 'setTitle')
123 def setTitle( self, title ):
124 """ Set Dublin Core Title element - resource name.
125 """
126 self.title = title
128 security.declareProtected(ManageProperties, 'setDescription')
129 def setDescription( self, description ):
130 """ Set Dublin Core Description element - resource summary.
131 """
132 self.description = description
134 #
135 # other methods
136 #
137 security.declareProtected(ManageProperties, 'edit')
138 def edit(self, title='', description=''):
139 """
140 Edit the folder title (and possibly other attributes later)
141 """
142 self.setTitle( title )
143 self.setDescription( description )
144 self.reindexObject()
146 security.declarePublic('allowedContentTypes')
147 def allowedContentTypes( self ):
148 """
149 List type info objects for types which can be added in
150 this folder.
151 """
152 result = []
153 portal_types = getToolByName(self, 'portal_types')
154 myType = portal_types.getTypeInfo(self)
156 if myType is not None:
157 for contentType in portal_types.listTypeInfo(self):
158 if myType.allowType( contentType.getId() ):
159 result.append( contentType )
160 else:
161 result = portal_types.listTypeInfo()
163 return filter( lambda typ, container=self:
164 typ.isConstructionAllowed( container )
165 , result )
168 def _morphSpec(self, spec):
169 '''
170 spec is a sequence of meta_types, a string containing one meta type,
171 or None. If spec is empty or None, returns all contentish
172 meta_types. Otherwise ensures all of the given meta types are
173 contentish.
174 '''
175 warn('Using the \'spec\' argument is deprecated. In CMF 2.0 '
176 'contentItems(), contentIds(), contentValues() and '
177 'listFolderContents() will no longer support \'spec\'. Use the '
178 '\'filter\' argument with \'portal_type\' instead.',
179 DeprecationWarning)
180 new_spec = []
181 types_tool = getToolByName(self, 'portal_types')
182 types = types_tool.listContentTypes( by_metatype=1 )
183 if spec is not None:
184 if type(spec) == type(''):
185 spec = [spec]
186 for meta_type in spec:
187 if not meta_type in types:
188 raise ValueError('%s is not a content type' % meta_type)
189 new_spec.append(meta_type)
190 return new_spec or types
192 def _filteredItems( self, ids, filt ):
193 """
194 Apply filter, a mapping, to child objects indicated by 'ids',
195 returning a sequence of ( id, obj ) tuples.
196 """
197 # Restrict allowed content types
198 if filt is None:
199 filt = {}
200 else:
201 # We'll modify it, work on a copy.
202 filt = filt.copy()
203 pt = filt.get('portal_type', [])
204 if type(pt) is type(''):
205 pt = [pt]
206 types_tool = getToolByName(self, 'portal_types')
207 allowed_types = types_tool.listContentTypes()
208 if not pt:
209 pt = allowed_types
210 else:
211 pt = [t for t in pt if t in allowed_types]
212 if not pt:
213 # After filtering, no types remain, so nothing should be
214 # returned.
215 return []
216 filt['portal_type'] = pt
218 query = ContentFilter(**filt)
219 result = []
220 append = result.append
221 get = self._getOb
222 for id in ids:
223 obj = get( id )
224 if query(obj):
225 append( (id, obj) )
226 return result
228 #
229 # 'Folderish' interface methods
230 #
231 security.declarePublic('contentItems')
232 def contentItems( self, spec=None, filter=None ):
233 # List contentish and folderish sub-objects and their IDs.
234 # (method is without docstring to disable publishing)
235 #
236 if spec is None:
237 ids = self.objectIds()
238 else:
239 # spec is deprecated, use filter instead!
240 spec = self._morphSpec(spec)
241 ids = self.objectIds(spec)
242 return self._filteredItems( ids, filter )
244 security.declarePublic('contentIds')
245 def contentIds( self, spec=None, filter=None):
246 # List IDs of contentish and folderish sub-objects.
247 # (method is without docstring to disable publishing)
248 #
249 if spec is None:
250 ids = self.objectIds()
251 else:
252 # spec is deprecated, use filter instead!
253 spec = self._morphSpec(spec)
254 ids = self.objectIds(spec)
255 return map( lambda item: item[0],
256 self._filteredItems( ids, filter ) )
258 security.declarePublic('contentValues')
259 def contentValues( self, spec=None, filter=None ):
260 # List contentish and folderish sub-objects.
261 # (method is without docstring to disable publishing)
262 #
263 if spec is None:
264 ids = self.objectIds()
265 else:
266 # spec is deprecated, use filter instead!
267 spec = self._morphSpec(spec)
268 ids = self.objectIds(spec)
269 return map( lambda item: item[1],
270 self._filteredItems( ids, filter ) )
272 security.declareProtected(ListFolderContents, 'listFolderContents')
273 def listFolderContents( self, spec=None, contentFilter=None ):
274 """ List viewable contentish and folderish sub-objects.
275 """
276 items = self.contentItems(spec=spec, filter=contentFilter)
277 l = []
278 for id, obj in items:
279 # validate() can either raise Unauthorized or return 0 to
280 # mean unauthorized.
281 try:
282 if getSecurityManager().validate(self, self, id, obj):
283 l.append(obj)
284 except zExceptions_Unauthorized: # Catch *all* Unauths!
285 pass
286 return l
288 #
289 # webdav Resource method
290 #
292 # protected by 'WebDAV access'
293 def listDAVObjects(self):
294 # List sub-objects for PROPFIND requests.
295 # (method is without docstring to disable publishing)
296 #
297 if _checkPermission(ManagePortal, self):
298 return self.objectValues()
299 else:
300 return self.listFolderContents()
302 #
303 # 'DublinCore' interface methods
304 #
305 security.declareProtected(View, 'Title')
306 def Title( self ):
307 """ Dublin Core Title element - resource name.
308 """
309 return self.title
311 security.declareProtected(View, 'Description')
312 def Description( self ):
313 """ Dublin Core Description element - resource summary.
314 """
315 return self.description
317 security.declareProtected(View, 'Type')
318 def Type( self ):
319 """ Dublin Core Type element - resource type.
320 """
321 if hasattr(aq_base(self), 'getTypeInfo'):
322 ti = self.getTypeInfo()
323 if ti is not None:
324 return ti.Title()
325 return self.meta_type
327 #
328 # other methods
329 #
330 security.declarePublic('encodeFolderFilter')
331 def encodeFolderFilter(self, REQUEST):
332 """
333 Parse cookie string for using variables in dtml.
334 """
335 filter = {}
336 for key, value in REQUEST.items():
337 if key[:10] == 'filter_by_':
338 filter[key[10:]] = value
339 encoded = base64.encodestring( marshal.dumps(filter) ).strip()
340 encoded = ''.join( encoded.split('\n') )
341 return encoded
343 security.declarePublic('decodeFolderFilter')
344 def decodeFolderFilter(self, encoded):
345 """
346 Parse cookie string for using variables in dtml.
347 """
348 filter = {}
349 if encoded:
350 filter.update(marshal.loads(base64.decodestring(encoded)))
351 return filter
353 def content_type( self ):
354 """
355 WebDAV needs this to do the Right Thing (TM).
356 """
357 return None
359 # Ensure pure PortalFolders don't get cataloged.
360 # XXX We may want to revisit this.
362 def indexObject(self):
363 pass
365 def unindexObject(self):
366 pass
368 def reindexObject(self, idxs=[]):
369 pass
371 def reindexObjectSecurity(self):
372 pass
374 def PUT_factory( self, name, typ, body ):
375 """ Factory for PUT requests to objects which do not yet exist.
377 Used by NullResource.PUT.
379 Returns -- Bare and empty object of the appropriate type (or None, if
380 we don't know what to do)
381 """
382 registry = getToolByName(self, 'content_type_registry', None)
383 if registry is None:
384 return None
386 typeObjectName = registry.findTypeName( name, typ, body )
387 if typeObjectName is None:
388 return None
390 self.invokeFactory( typeObjectName, name )
392 # invokeFactory does too much, so the object has to be removed again
393 obj = aq_base( self._getOb( name ) )
394 self._delObject( name )
395 return obj
397 security.declareProtected(AddPortalContent, 'invokeFactory')
398 def invokeFactory(self, type_name, id, RESPONSE=None, *args, **kw):
399 """ Invokes the portal_types tool.
400 """
401 pt = getToolByName(self, 'portal_types')
402 myType = pt.getTypeInfo(self)
404 if myType is not None:
405 if not myType.allowType( type_name ):
406 raise ValueError('Disallowed subobject type: %s' % type_name)
408 return pt.constructContent(type_name, self, id, RESPONSE, *args, **kw)
410 security.declareProtected(AddPortalContent, 'checkIdAvailable')
411 def checkIdAvailable(self, id):
412 try:
413 self._checkId(id)
414 except BadRequest:
415 return False
416 else:
417 return True
419 def MKCOL_handler(self,id,REQUEST=None,RESPONSE=None):
420 """
421 Handle WebDAV MKCOL.
422 """
423 self.manage_addFolder( id=id, title='' )
425 def _checkId(self, id, allow_dup=0):
426 PortalFolderBase.inheritedAttribute('_checkId')(self, id, allow_dup)
428 if allow_dup:
429 return
431 # FIXME: needed to allow index_html for join code
432 if id == 'index_html':
433 return
435 # Another exception: Must allow "syndication_information" to enable
436 # Syndication...
437 if id == 'syndication_information':
438 return
440 # This code prevents people other than the portal manager from
441 # overriding skinned names and tools.
442 if not getSecurityManager().checkPermission(ManagePortal, self):
443 ob = self
444 while ob is not None and not getattr(ob, '_isPortalRoot', False):
445 ob = aq_parent( aq_inner(ob) )
446 if ob is not None:
447 # If the portal root has a non-contentish object by this name,
448 # don't allow an override.
449 if (hasattr(ob, id) and
450 id not in ob.contentIds() and
451 # Allow root doted prefixed object name overrides
452 not id.startswith('.')):
453 raise BadRequest('The id "%s" is reserved.' % id)
454 # Don't allow ids used by Method Aliases.
455 ti = self.getTypeInfo()
456 if ti and ti.queryMethodID(id, context=self):
457 raise BadRequest('The id "%s" is reserved.' % id)
458 # Otherwise we're ok.
460 def _verifyObjectPaste(self, object, validate_src=1):
461 # This assists the version in OFS.CopySupport.
462 # It enables the clipboard to function correctly
463 # with objects created by a multi-factory.
464 securityChecksDone = False
465 sm = getSecurityManager()
466 parent = aq_parent(aq_inner(object))
467 object_id = object.getId()
468 mt = getattr(object, '__factory_meta_type__', None)
469 meta_types = getattr(self, 'all_meta_types', None)
471 if mt is not None and meta_types is not None:
472 method_name=None
473 permission_name = None
475 if callable(meta_types):
476 meta_types = meta_types()
478 for d in meta_types:
480 if d['name']==mt:
481 method_name=d['action']
482 permission_name = d.get('permission', None)
483 break
485 if permission_name is not None:
487 if not sm.checkPermission(permission_name,self):
488 raise AccessControl_Unauthorized, method_name
490 if validate_src:
492 if not sm.validate(None, parent, None, object):
493 raise AccessControl_Unauthorized, object_id
495 if validate_src > 1:
496 if not sm.checkPermission(DeleteObjects, parent):
497 raise AccessControl_Unauthorized
499 # validation succeeded
500 securityChecksDone = 1
502 #
503 # Old validation for objects that may not have registered
504 # themselves in the proper fashion.
505 #
506 elif method_name is not None:
508 meth = self.unrestrictedTraverse(method_name)
510 factory = getattr(meth, 'im_self', None)
512 if factory is None:
513 factory = aq_parent(aq_inner(meth))
515 if not sm.validate(None, factory, None, meth):
516 raise AccessControl_Unauthorized, method_name
518 # Ensure the user is allowed to access the object on the
519 # clipboard.
520 if validate_src:
522 if not sm.validate(None, parent, None, object):
523 raise AccessControl_Unauthorized, object_id
525 if validate_src > 1: # moving
526 if not sm.checkPermission(DeleteObjects, parent):
527 raise AccessControl_Unauthorized
529 securityChecksDone = 1
531 # Call OFS' _verifyObjectPaste if necessary
532 if not securityChecksDone:
533 PortalFolderBase.inheritedAttribute(
534 '_verifyObjectPaste')(self, object, validate_src)
536 # Finally, check allowed content types
537 if hasattr(aq_base(object), 'getPortalTypeName'):
539 type_name = object.getPortalTypeName()
541 if type_name is not None:
543 pt = getToolByName(self, 'portal_types')
544 myType = pt.getTypeInfo(self)
546 if myType is not None and not myType.allowType(type_name):
547 raise ValueError('Disallowed subobject type: %s'
548 % type_name)
550 security.setPermissionDefault(AddPortalContent, ('Owner','Manager'))
552 security.declareProtected(AddPortalFolders, 'manage_addFolder')
553 def manage_addFolder( self
554 , id
555 , title=''
556 , REQUEST=None
557 ):
558 """ Add a new folder-like object with id *id*.
560 IF present, use the parent object's 'mkdir' alias; otherwise, just add
561 a PortalFolder.
562 """
563 ti = self.getTypeInfo()
564 method_id = ti and ti.queryMethodID('mkdir', context=self)
565 if method_id:
566 # call it
567 getattr(self, method_id)(id=id)
568 else:
569 self.invokeFactory( type_name='Folder', id=id )
571 ob = self._getOb( id )
572 ob.setTitle( title )
573 try:
574 ob.reindexObject()
575 except AttributeError:
576 pass
578 if REQUEST is not None:
579 return self.manage_main(self, REQUEST, update_menu=1)
581 InitializeClass(PortalFolderBase)
584 class PortalFolder(OrderSupport, PortalFolderBase):
585 """
586 Implements portal content management, but not UI details.
587 """
588 meta_type = 'Portal Folder'
589 portal_type = 'Folder'
591 __implements__ = (PortalFolderBase.__implements__,
592 OrderSupport.__implements__)
594 security = ClassSecurityInfo()
596 manage_options = ( OrderSupport.manage_options +
597 PortalFolderBase.manage_options[1:] )
599 security.declareProtected(AddPortalFolders, 'manage_addPortalFolder')
600 def manage_addPortalFolder(self, id, title='', REQUEST=None):
601 """Add a new PortalFolder object with id *id*.
602 """
603 ob = PortalFolder(id, title)
604 self._setObject(id, ob)
605 if REQUEST is not None:
606 return self.folder_contents( # XXX: ick!
607 self, REQUEST, portal_status_message="Folder added")
609 InitializeClass(PortalFolder)
612 class ContentFilter:
613 """
614 Represent a predicate against a content object's metadata.
615 """
616 MARKER = []
617 filterSubject = []
618 def __init__( self
619 , Title=MARKER
620 , Creator=MARKER
621 , Subject=MARKER
622 , Description=MARKER
623 , created=MARKER
624 , created_usage='range:min'
625 , modified=MARKER
626 , modified_usage='range:min'
627 , Type=MARKER
628 , portal_type=MARKER
629 , **Ignored
630 ):
632 self.predicates = []
633 self.description = []
635 if Title is not self.MARKER:
636 self.predicates.append( lambda x, pat=re.compile( Title ):
637 pat.search( x.Title() ) )
638 self.description.append( 'Title: %s' % Title )
640 if Creator and Creator is not self.MARKER:
641 self.predicates.append( lambda x, creator=Creator:
642 creator in x.listCreators() )
643 self.description.append( 'Creator: %s' % Creator )
645 if Subject and Subject is not self.MARKER:
646 self.filterSubject = Subject
647 self.predicates.append( self.hasSubject )
648 self.description.append( 'Subject: %s' % ', '.join(Subject) )
650 if Description is not self.MARKER:
651 self.predicates.append( lambda x, pat=re.compile( Description ):
652 pat.search( x.Description() ) )
653 self.description.append( 'Description: %s' % Description )
655 if created is not self.MARKER:
656 if created_usage == 'range:min':
657 self.predicates.append( lambda x, cd=created:
658 cd <= x.created() )
659 self.description.append( 'Created since: %s' % created )
660 if created_usage == 'range:max':
661 self.predicates.append( lambda x, cd=created:
662 cd >= x.created() )
663 self.description.append( 'Created before: %s' % created )
665 if modified is not self.MARKER:
666 if modified_usage == 'range:min':
667 self.predicates.append( lambda x, md=modified:
668 md <= x.modified() )
669 self.description.append( 'Modified since: %s' % modified )
670 if modified_usage == 'range:max':
671 self.predicates.append( lambda x, md=modified:
672 md >= x.modified() )
673 self.description.append( 'Modified before: %s' % modified )
675 if Type:
676 if type( Type ) == type( '' ):
677 Type = [ Type ]
678 self.predicates.append( lambda x, Type=Type:
679 x.Type() in Type )
680 self.description.append( 'Type: %s' % ', '.join(Type) )
682 if portal_type and portal_type is not self.MARKER:
683 if type(portal_type) is type(''):
684 portal_type = [portal_type]
685 self.predicates.append( lambda x, pt=portal_type:
686 hasattr(aq_base(x), 'getPortalTypeName')
687 and x.getPortalTypeName() in pt )
688 self.description.append( 'Portal Type: %s'
689 % ', '.join(portal_type) )
691 def hasSubject( self, obj ):
692 """
693 Converts Subject string into a List for content filter view.
694 """
695 for sub in obj.Subject():
696 if sub in self.filterSubject:
697 return 1
698 return 0
700 def __call__( self, content ):
702 for predicate in self.predicates:
704 try:
705 if not predicate( content ):
706 return 0
707 except (AttributeError, KeyError, IndexError, ValueError):
708 # predicates are *not* allowed to throw exceptions
709 return 0
711 return 1
713 def __str__( self ):
714 """
715 Return a stringified description of the filter.
716 """
717 return '; '.join(self.description)
719 manage_addPortalFolder = PortalFolder.manage_addPortalFolder.im_func
720 manage_addPortalFolderForm = DTMLFile( 'folderAdd', globals() )