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 1539 2009-10-30 13:48:34Z 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
29 from zExceptions
import BadRequest
32 from cgi
import escape
33 from OFS
import Moniker
34 from ZODB
.POSException
import ConflictError
35 import OFS
.subscribers
36 from zope
.event
import notify
37 from zope
.lifecycleevent
import ObjectCopiedEvent
38 from zope
.app
.container
.contained
import ObjectMovedEvent
39 from zope
.app
.container
.contained
import notifyContainerModified
40 from OFS
.event
import ObjectClonedEvent
41 from OFS
.event
import ObjectWillBeMovedEvent
42 from zope
.component
.factory
import Factory
43 from Acquisition
import aq_base
, aq_inner
, aq_parent
45 from types
import StringType
46 from Products
.CMFCore
.permissions
import ListFolderContents
, View
, ViewManagementScreens
,\
47 ManageProperties
, AddPortalFolders
, AddPortalContent
,\
48 ManagePortal
, ModifyPortalContent
49 from permissions
import DeletePortalContents
, DeleteObjects
, DeleteOwnedObjects
, SetLocalRoles
, CheckMemberPermission
50 from Products
.CMFCore
.utils
import _checkPermission
, getToolByName
51 from Products
.CMFCore
.CMFCatalogAware
import CMFCatalogAware
52 from Products
.CMFCore
.PortalFolder
import PortalFolder
, ContentFilter
53 from Products
.CMFDefault
.DublinCore
import DefaultDublinCoreImpl
55 from zope
.interface
import implements
56 from Products
.CMFCore
.interfaces
import IContentish
58 from utils
import _checkMemberPermission
59 from utils
import Message
as _
60 from Globals
import InitializeClass
61 from AccessControl
import ClassSecurityInfo
64 class PlinnFolder(CMFCatalogAware
, PortalFolder
, DefaultDublinCoreImpl
) :
67 implements(IContentish
)
69 security
= ClassSecurityInfo()
71 manage_options
= PortalFolder
.manage_options
73 ## change security for inherited methods
74 security
.declareProtected(AddPortalContent
, 'manage_pasteObjects')
76 def __init__( self
, id, title
='' ) :
77 PortalFolder
.__init
__(self
, id)
78 DefaultDublinCoreImpl
.__init
__(self
, title
= title
)
80 security
.declarePublic('allowedContentTypes')
81 def allowedContentTypes(self
):
83 List type info objects for types which can be added in this folder.
84 Types can be filtered using the localContentTypes attribute.
86 allowedTypes
= PortalFolder
.allowedContentTypes(self
)
87 if hasattr(self
, 'localContentTypes'):
88 allowedTypes
= [t
for t
in allowedTypes
if t
.title
in self
.localContentTypes
]
91 security
.declareProtected(View
, 'objectIdCanBeDeleted')
92 def objectIdCanBeDeleted(self
, id) :
93 """ Check permissions and ownership and return True
94 if current user can delete object id.
96 if _checkPermission(DeleteObjects
, self
) : # std zope perm
99 elif _checkPermission(DeletePortalContents
, self
):
100 mtool
= getToolByName(self
, 'portal_membership')
101 authMember
= mtool
.getAuthenticatedMember()
102 ob
= getattr(self
, id)
103 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
104 _checkPermission(DeleteOwnedObjects
, ob
) : return True
110 security
.declareProtected(DeletePortalContents
, 'manage_delObjects')
111 def manage_delObjects(self
, ids
=[], REQUEST
=None):
112 """Delete subordinate objects.
113 A member can delete his owned contents (if he has the 'Delete Portal Contents' permission)
114 without 'Delete objects' permission in this folder.
115 Return skipped object ids.
118 if _checkPermission(DeleteObjects
, self
) : # std zope perm
119 PortalFolder
.manage_delObjects(self
, ids
=ids
, REQUEST
=REQUEST
)
121 mtool
= getToolByName(self
, 'portal_membership')
122 authMember
= mtool
.getAuthenticatedMember()
124 if type(ids
) == StringType
:
128 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
129 _checkPermission(DeleteOwnedObjects
, ob
) : owned
.append(id)
130 else : notOwned
.append(id)
132 PortalFolder
.manage_delObjects(self
, ids
=owned
, REQUEST
=REQUEST
)
134 if REQUEST
is not None:
135 return self
.manage_main(
137 manage_tabs_message
='Object(s) deleted.',
142 security
.declareProtected(AddPortalContent
, 'manage_renameObjects')
143 def manage_renameObjects(self
, ids
=[], new_ids
=[], REQUEST
=None) :
144 """ Rename subordinate objects
145 A member can rename his owned contents if he has the 'Modify Portal Content' permission.
146 Returns skippend object ids.
148 if len(ids
) != len(new_ids
):
149 raise BadRequest(_('Please rename each listed object.'))
151 if _checkPermission(ViewManagementScreens
, self
) : # std zope perm
152 return super(PlinnFolder
, self
).manage_renameObjects(ids
, new_ids
, REQUEST
)
154 mtool
= getToolByName(self
, 'portal_membership')
155 authMember
= mtool
.getAuthenticatedMember()
157 for id, new_id
in zip(ids
, new_ids
) :
158 if id == new_id
: continue
161 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
162 _checkPermission(ModifyPortalContent
, ob
) :
163 self
.manage_renameObject(id, new_id
)
167 if REQUEST
is not None :
168 return self
.manage_main(self
, REQUEST
, update_menu
=1)
173 security
.declareProtected(ListFolderContents
, 'listFolderContents')
174 def listFolderContents( self
, contentFilter
=None ):
175 """ List viewable contentish and folderish sub-objects.
177 items
= self
.contentItems(filter=contentFilter
)
179 for id, obj
in items
:
180 if _checkPermission(View
, obj
) :
186 security
.declareProtected(ListFolderContents
, 'listNearestFolderContents')
187 def listNearestFolderContents(self
, contentFilter
=None, userid
=None, sorted=False) :
188 """ Return folder contents and traverse
189 recursively unaccessfull sub folders to find
195 filt
= contentFilter
.copy()
196 ctool
= getToolByName(self
, 'portal_catalog')
197 mtool
= getToolByName(self
, 'portal_membership')
199 if userid
and _checkPermission(CheckMemberPermission
, getToolByName(self
, 'portal_url').getPortalObject()) :
200 checkFunc
= lambda perm
, ob
: _checkMemberPermission(userid
, View
, ob
)
201 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getMemberById(userid
) )
203 checkFunc
= _checkPermission
204 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getAuthenticatedMember() )
207 # copy from CMFCore.PortalFolder.PortalFolder._filteredItems
208 pt
= filt
.get('portal_type', [])
209 if type(pt
) is type(''):
211 types_tool
= getToolByName(self
, 'portal_types')
212 allowed_types
= types_tool
.listContentTypes()
216 pt
= [t
for t
in pt
if t
in allowed_types
]
218 # After filtering, no types remain, so nothing should be
221 filt
['portal_type'] = pt
224 query
= ContentFilter(**filt
)
227 for o
in self
.objectValues() :
229 if checkFunc(View
, o
):
230 nearestObjects
.append(o
)
231 elif getattr(o
.aq_self
,'isAnObjectManager', False):
232 nearestObjects
.extend(_getDeepObjects(self
, ctool
, o
, filter=filt
))
234 if sorted and len(nearestObjects
) > 0 :
235 key
, reverse
= self
.getDefaultSorting()
236 if key
!= 'position' :
237 indexCallable
= callable(getattr(nearestObjects
[0], key
))
239 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
)(), getattr(b
, key
)())
241 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
), getattr(b
, key
))
242 nearestObjects
.sort(cmp=sortfunc
, reverse
=reverse
)
244 return nearestObjects
246 security
.declareProtected(ListFolderContents
, 'listCatalogedContents')
247 def listCatalogedContents(self
, contentFilter
={}):
248 """ query catalog and returns brains of contents.
249 Requires ExtendedPathIndex
251 ctool
= getToolByName(self
, 'portal_catalog')
252 contentFilter
['path'] = {'query':'/'.join(self
.getPhysicalPath()),
254 return ctool(sort_on
='position', **contentFilter
)
257 security
.declarePublic('synContentValues')
258 def synContentValues(self
):
259 # value for syndication
260 return self
.listNearestFolderContents()
262 security
.declareProtected(View
, 'SearchableText')
263 def SearchableText(self
) :
264 """ for full text indexation
266 return '%s %s' % (self
.title
, self
.description
)
268 security
.declareProtected(AddPortalFolders
, 'manage_addPlinnFolder')
269 def manage_addPlinnFolder(self
, id, title
='', REQUEST
=None):
270 """Add a new PortalFolder object with id *id*.
272 ob
=PlinnFolder(id, title
)
273 # from CMFCore.PortalFolder.PortalFolder :-)
274 self
._setObject
(id, ob
)
275 if REQUEST
is not None:
276 return self
.folder_contents( # XXX: ick!
277 self
, REQUEST
, portal_status_message
="Folder added")
280 ## overload to maintain ownership if authenticated user has 'Manage portal' permission
281 def manage_pasteObjects(self
, cb_copy_data
=None, REQUEST
=None):
282 """Paste previously copied objects into the current object.
284 If calling manage_pasteObjects from python code, pass the result of a
285 previous call to manage_cutObjects or manage_copyObjects as the first
288 Also sends IObjectCopiedEvent and IObjectClonedEvent
289 or IObjectWillBeMovedEvent and IObjectMovedEvent.
291 if cb_copy_data
is not None:
293 elif REQUEST
is not None and REQUEST
.has_key('__cp'):
298 raise CopyError
, eNoData
301 op
, mdatas
= _cb_decode(cp
)
303 raise CopyError
, eInvalid
306 app
= self
.getPhysicalRoot()
308 m
= Moniker
.loadMoniker(mdata
)
311 except ConflictError
:
314 raise CopyError
, eNotFound
315 self
._verifyObjectPaste
(ob
, validate_src
=op
+1)
321 mtool
= getToolByName(self
, 'portal_membership')
322 utool
= getToolByName(self
, 'portal_url')
323 portal
= utool
.getPortalObject()
324 userIsPortalManager
= mtool
.checkPermission(ManagePortal
, portal
)
328 if not ob
.cb_isCopyable():
329 raise CopyError
, eNotSupported
% escape(orig_id
)
332 ob
._notifyOfCopyTo
(self
, op
=0)
333 except ConflictError
:
336 raise CopyError
, MessageDialog(
338 message
=sys
.exc_info()[1],
339 action
='manage_main')
341 id = self
._get
_id
(orig_id
)
342 result
.append({'id': orig_id
, 'new_id': id})
345 ob
= ob
._getCopy
(self
)
347 notify(ObjectCopiedEvent(ob
, orig_ob
))
349 if not userIsPortalManager
:
350 self
._setObject
(id, ob
, suppress_events
=True)
352 self
._setObject
(id, ob
, suppress_events
=True, set_owner
=0)
356 ob
._postCopy
(self
, op
=0)
358 OFS
.subscribers
.compatibilityCall('manage_afterClone', ob
, ob
)
360 notify(ObjectClonedEvent(ob
))
362 if REQUEST
is not None:
363 return self
.manage_main(self
, REQUEST
, update_menu
=1,
370 if not ob
.cb_isMoveable():
371 raise CopyError
, eNotSupported
% escape(orig_id
)
374 ob
._notifyOfCopyTo
(self
, op
=1)
375 except ConflictError
:
378 raise CopyError
, MessageDialog(
380 message
=sys
.exc_info()[1],
381 action
='manage_main')
383 if not sanity_check(self
, ob
):
384 raise CopyError
, "This object cannot be pasted into itself"
386 orig_container
= aq_parent(aq_inner(ob
))
387 if aq_base(orig_container
) is aq_base(self
):
390 id = self
._get
_id
(orig_id
)
391 result
.append({'id': orig_id
, 'new_id': id})
393 notify(ObjectWillBeMovedEvent(ob
, orig_container
, orig_id
,
396 # try to make ownership explicit so that it gets carried
397 # along to the new location if needed.
398 ob
.manage_changeOwnershipType(explicit
=1)
401 orig_container
._delObject
(orig_id
, suppress_events
=True)
403 orig_container
._delObject
(orig_id
)
405 "%s._delObject without suppress_events is discouraged."
406 % orig_container
.__class
__.__name
__,
412 self
._setObject
(id, ob
, set_owner
=0, suppress_events
=True)
414 self
._setObject
(id, ob
, set_owner
=0)
416 "%s._setObject without suppress_events is discouraged."
417 % self
.__class
__.__name
__, DeprecationWarning)
420 notify(ObjectMovedEvent(ob
, orig_container
, orig_id
, self
, id))
421 notifyContainerModified(orig_container
)
422 if aq_base(orig_container
) is not aq_base(self
):
423 notifyContainerModified(self
)
425 ob
._postCopy
(self
, op
=1)
426 # try to make ownership implicit if possible
427 ob
.manage_changeOwnershipType(explicit
=0)
429 if REQUEST
is not None:
430 REQUEST
['RESPONSE'].setCookie('__cp', 'deleted',
431 path
='%s' % cookie_path(REQUEST
),
432 expires
='Wed, 31-Dec-97 23:59:59 GMT')
433 REQUEST
['__cp'] = None
434 return self
.manage_main(self
, REQUEST
, update_menu
=1,
440 InitializeClass(PlinnFolder
)
441 PlinnFolderFactory
= Factory(PlinnFolder
)
443 def _getDeepObjects(self
, ctool
, o
, filter={}):
444 res
= ctool
.unrestrictedSearchResults(path
= '/'.join(o
.getPhysicalPath()), **filter)
451 res
.sort(lambda a
, b
: cmp(a
.getPath(), b
.getPath()))
452 previousPath
= res
[0].getPath()
454 deepObjects
.append(res
[0].getObject())
456 currentPath
= b
.getPath()
457 if currentPath
.startswith(previousPath
) and len(currentPath
) > len(previousPath
):
460 deepObjects
.append(b
.getObject())
461 previousPath
= currentPath
466 manage_addPlinnFolder
= PlinnFolder
.manage_addPlinnFolder
.im_func