1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # Copyright (C) 2005-2007 Benoît PIN <benoit.pin@ensmp.fr> #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ Plinn portal folder implementation
22 $Id: Folder.py 1459 2009-02-02 00:57:24Z pin $
23 $URL: http://svn.cri.ensmp.fr/svn/Plinn/branches/CMF-2.1/Folder.py $
26 from OFS
.CopySupport
import CopyError
, eNoData
, _cb_decode
, eInvalid
, eNotFound
,\
27 eNotSupported
, sanity_check
, cookie_path
28 from App
.Dialogs
import MessageDialog
31 from cgi
import escape
32 from OFS
import Moniker
33 from ZODB
.POSException
import ConflictError
34 import OFS
.subscribers
35 from zope
.event
import notify
36 from zope
.lifecycleevent
import ObjectCopiedEvent
37 from zope
.app
.container
.contained
import ObjectMovedEvent
38 from zope
.app
.container
.contained
import notifyContainerModified
39 from OFS
.event
import ObjectClonedEvent
40 from OFS
.event
import ObjectWillBeMovedEvent
41 from zope
.component
.factory
import Factory
42 from Acquisition
import aq_base
, aq_inner
, aq_parent
44 from types
import StringType
45 from Products
.CMFCore
.permissions
import ListFolderContents
, View
, ManageProperties
, AddPortalFolders
, AddPortalContent
, ManagePortal
46 from permissions
import DeletePortalContents
, DeleteObjects
, DeleteOwnedObjects
, SetLocalRoles
, CheckMemberPermission
47 from Products
.CMFCore
.utils
import _checkPermission
, getToolByName
48 from Products
.CMFCore
.CMFCatalogAware
import CMFCatalogAware
49 from Products
.CMFCore
.PortalFolder
import PortalFolder
, ContentFilter
50 from Products
.CMFDefault
.DublinCore
import DefaultDublinCoreImpl
52 from zope
.interface
import implements
53 from Products
.CMFCore
.interfaces
import IContentish
55 from utils
import _checkMemberPermission
56 from Globals
import InitializeClass
57 from AccessControl
import ClassSecurityInfo
60 class PlinnFolder(CMFCatalogAware
, PortalFolder
, DefaultDublinCoreImpl
) :
63 implements(IContentish
)
65 security
= ClassSecurityInfo()
67 manage_options
= PortalFolder
.manage_options
69 ## change security for inherited methods
70 security
.declareProtected(AddPortalContent
, 'manage_pasteObjects')
72 def __init__( self
, id, title
='' ) :
73 PortalFolder
.__init
__(self
, id)
74 DefaultDublinCoreImpl
.__init
__(self
, title
= title
)
76 security
.declarePublic('allowedContentTypes')
77 def allowedContentTypes(self
):
79 List type info objects for types which can be added in this folder.
80 Types can be filtered using the localContentTypes attribute.
82 allowedTypes
= PortalFolder
.allowedContentTypes(self
)
83 if hasattr(self
, 'localContentTypes'):
84 allowedTypes
= [t
for t
in allowedTypes
if t
.title
in self
.localContentTypes
]
87 security
.declareProtected(View
, 'objectIdCanBeDeleted')
88 def objectIdCanBeDeleted(self
, id) :
89 """ Check permissions and ownership and return True
90 if current user can delete object id.
92 if _checkPermission(DeleteObjects
, self
) : # std zope perm
95 elif _checkPermission(DeletePortalContents
, self
):
96 mtool
= getToolByName(self
, 'portal_membership')
97 authMember
= mtool
.getAuthenticatedMember()
98 ob
= getattr(self
, id)
99 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
100 _checkPermission(DeleteOwnedObjects
, ob
) : return True
106 security
.declareProtected(DeletePortalContents
, 'manage_delObjects')
107 def manage_delObjects(self
, ids
=[], REQUEST
=None):
108 """Delete a subordinate object.
109 A member can delete his owned contents (if he has the 'Delete Portal Contents' permission)
110 without 'Delete objects' permission in this folder.
111 Return skipped object ids.
114 if _checkPermission(DeleteObjects
, self
) : # std zope perm
115 PortalFolder
.manage_delObjects(self
, ids
=ids
, REQUEST
=REQUEST
)
117 mtool
= getToolByName(self
, 'portal_membership')
118 authMember
= mtool
.getAuthenticatedMember()
120 if type(ids
) == StringType
:
124 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
125 _checkPermission(DeleteOwnedObjects
, ob
) : owned
.append(id)
126 else : notOwned
.append(id)
128 PortalFolder
.manage_delObjects(self
, ids
=owned
, REQUEST
=REQUEST
)
130 if REQUEST
is not None:
131 return self
.manage_main(
133 manage_tabs_message
='Object(s) deleted.',
138 security
.declareProtected(ListFolderContents
, 'listFolderContents')
139 def listFolderContents( self
, contentFilter
=None ):
140 """ List viewable contentish and folderish sub-objects.
142 items
= self
.contentItems(filter=contentFilter
)
144 for id, obj
in items
:
145 if _checkPermission(View
, obj
) :
151 security
.declareProtected(ListFolderContents
, 'listNearestFolderContents')
152 def listNearestFolderContents(self
, contentFilter
=None, userid
=None, sorted=False) :
153 """ Return folder contents and traverse
154 recursively unaccessfull sub folders to find
160 filt
= contentFilter
.copy()
161 ctool
= getToolByName(self
, 'portal_catalog')
162 mtool
= getToolByName(self
, 'portal_membership')
164 if userid
and _checkPermission(CheckMemberPermission
, getToolByName(self
, 'portal_url').getPortalObject()) :
165 checkFunc
= lambda perm
, ob
: _checkMemberPermission(userid
, View
, ob
)
166 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getMemberById(userid
) )
168 checkFunc
= _checkPermission
169 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getAuthenticatedMember() )
172 # copy from CMFCore.PortalFolder.PortalFolder._filteredItems
173 pt
= filt
.get('portal_type', [])
174 if type(pt
) is type(''):
176 types_tool
= getToolByName(self
, 'portal_types')
177 allowed_types
= types_tool
.listContentTypes()
181 pt
= [t
for t
in pt
if t
in allowed_types
]
183 # After filtering, no types remain, so nothing should be
186 filt
['portal_type'] = pt
189 query
= ContentFilter(**filt
)
192 for o
in self
.objectValues() :
194 if checkFunc(View
, o
):
195 nearestObjects
.append(o
)
196 elif getattr(o
.aq_self
,'isAnObjectManager', False):
197 nearestObjects
.extend(_getDeepObjects(self
, ctool
, o
, filter=filt
))
199 if sorted and len(nearestObjects
) > 0 :
200 key
, reverse
= self
.getDefaultSorting()
201 if key
!= 'position' :
202 indexCallable
= callable(getattr(nearestObjects
[0], key
))
204 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
)(), getattr(b
, key
)())
206 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
), getattr(b
, key
))
207 nearestObjects
.sort(cmp=sortfunc
, reverse
=reverse
)
209 return nearestObjects
211 security
.declareProtected(ListFolderContents
, 'listCatalogedContents')
212 def listCatalogedContents(self
, contentFilter
={}):
213 """ query catalog and returns brains of contents.
214 Requires ExtendedPathIndex
216 ctool
= getToolByName(self
, 'portal_catalog')
217 contentFilter
['path'] = {'query':'/'.join(self
.getPhysicalPath()),
219 return ctool(sort_on
='position', **contentFilter
)
222 security
.declarePublic('synContentValues')
223 def synContentValues(self
):
224 # value for syndication
225 return self
.listNearestFolderContents()
227 security
.declareProtected(View
, 'SearchableText')
228 def SearchableText(self
) :
229 """ for full text indexation
231 return '%s %s' % (self
.title
, self
.description
)
233 security
.declareProtected(AddPortalFolders
, 'manage_addPlinnFolder')
234 def manage_addPlinnFolder(self
, id, title
='', REQUEST
=None):
235 """Add a new PortalFolder object with id *id*.
237 ob
=PlinnFolder(id, title
)
238 # from CMFCore.PortalFolder.PortalFolder :-)
239 self
._setObject
(id, ob
)
240 if REQUEST
is not None:
241 return self
.folder_contents( # XXX: ick!
242 self
, REQUEST
, portal_status_message
="Folder added")
245 ## overload to maintain ownership if authenticated user has 'Manage portal' permission
246 def manage_pasteObjects(self
, cb_copy_data
=None, REQUEST
=None):
247 """Paste previously copied objects into the current object.
249 If calling manage_pasteObjects from python code, pass the result of a
250 previous call to manage_cutObjects or manage_copyObjects as the first
253 Also sends IObjectCopiedEvent and IObjectClonedEvent
254 or IObjectWillBeMovedEvent and IObjectMovedEvent.
256 if cb_copy_data
is not None:
258 elif REQUEST
is not None and REQUEST
.has_key('__cp'):
263 raise CopyError
, eNoData
266 op
, mdatas
= _cb_decode(cp
)
268 raise CopyError
, eInvalid
271 app
= self
.getPhysicalRoot()
273 m
= Moniker
.loadMoniker(mdata
)
276 except ConflictError
:
279 raise CopyError
, eNotFound
280 self
._verifyObjectPaste
(ob
, validate_src
=op
+1)
286 mtool
= getToolByName(self
, 'portal_membership')
287 utool
= getToolByName(self
, 'portal_url')
288 portal
= utool
.getPortalObject()
289 userIsPortalManager
= mtool
.checkPermission(ManagePortal
, portal
)
293 if not ob
.cb_isCopyable():
294 raise CopyError
, eNotSupported
% escape(orig_id
)
297 ob
._notifyOfCopyTo
(self
, op
=0)
298 except ConflictError
:
301 raise CopyError
, MessageDialog(
303 message
=sys
.exc_info()[1],
304 action
='manage_main')
306 id = self
._get
_id
(orig_id
)
307 result
.append({'id': orig_id
, 'new_id': id})
310 ob
= ob
._getCopy
(self
)
312 notify(ObjectCopiedEvent(ob
, orig_ob
))
314 if not userIsPortalManager
:
315 self
._setObject
(id, ob
, suppress_events
=True)
317 self
._setObject
(id, ob
, suppress_events
=True, set_owner
=0)
321 ob
._postCopy
(self
, op
=0)
323 OFS
.subscribers
.compatibilityCall('manage_afterClone', ob
, ob
)
325 notify(ObjectClonedEvent(ob
))
327 if REQUEST
is not None:
328 return self
.manage_main(self
, REQUEST
, update_menu
=1,
335 if not ob
.cb_isMoveable():
336 raise CopyError
, eNotSupported
% escape(orig_id
)
339 ob
._notifyOfCopyTo
(self
, op
=1)
340 except ConflictError
:
343 raise CopyError
, MessageDialog(
345 message
=sys
.exc_info()[1],
346 action
='manage_main')
348 if not sanity_check(self
, ob
):
349 raise CopyError
, "This object cannot be pasted into itself"
351 orig_container
= aq_parent(aq_inner(ob
))
352 if aq_base(orig_container
) is aq_base(self
):
355 id = self
._get
_id
(orig_id
)
356 result
.append({'id': orig_id
, 'new_id': id})
358 notify(ObjectWillBeMovedEvent(ob
, orig_container
, orig_id
,
361 # try to make ownership explicit so that it gets carried
362 # along to the new location if needed.
363 ob
.manage_changeOwnershipType(explicit
=1)
366 orig_container
._delObject
(orig_id
, suppress_events
=True)
368 orig_container
._delObject
(orig_id
)
370 "%s._delObject without suppress_events is discouraged."
371 % orig_container
.__class
__.__name
__,
377 self
._setObject
(id, ob
, set_owner
=0, suppress_events
=True)
379 self
._setObject
(id, ob
, set_owner
=0)
381 "%s._setObject without suppress_events is discouraged."
382 % self
.__class
__.__name
__, DeprecationWarning)
385 notify(ObjectMovedEvent(ob
, orig_container
, orig_id
, self
, id))
386 notifyContainerModified(orig_container
)
387 if aq_base(orig_container
) is not aq_base(self
):
388 notifyContainerModified(self
)
390 ob
._postCopy
(self
, op
=1)
391 # try to make ownership implicit if possible
392 ob
.manage_changeOwnershipType(explicit
=0)
394 if REQUEST
is not None:
395 REQUEST
['RESPONSE'].setCookie('__cp', 'deleted',
396 path
='%s' % cookie_path(REQUEST
),
397 expires
='Wed, 31-Dec-97 23:59:59 GMT')
398 REQUEST
['__cp'] = None
399 return self
.manage_main(self
, REQUEST
, update_menu
=1,
405 InitializeClass(PlinnFolder
)
406 PlinnFolderFactory
= Factory(PlinnFolder
)
408 def _getDeepObjects(self
, ctool
, o
, filter={}):
409 res
= ctool
.unrestrictedSearchResults(path
= '/'.join(o
.getPhysicalPath()), **filter)
416 res
.sort(lambda a
, b
: cmp(a
.getPath(), b
.getPath()))
417 previousPath
= res
[0].getPath()
419 deepObjects
.append(res
[0].getObject())
421 currentPath
= b
.getPath()
422 if currentPath
.startswith(previousPath
) and len(currentPath
) > len(previousPath
):
425 deepObjects
.append(b
.getObject())
426 previousPath
= currentPath
431 manage_addPlinnFolder
= PlinnFolder
.manage_addPlinnFolder
.im_func