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
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
39 from zope
.app
.container
.contained
import notifyContainerModified
40 from zope
.app
.container
.contained
import ObjectMovedEvent
43 from zope
.container
.contained
import notifyContainerModified
44 from zope
.container
.contained
import ObjectMovedEvent
45 from OFS
.event
import ObjectClonedEvent
46 from OFS
.event
import ObjectWillBeMovedEvent
47 from zope
.component
.factory
import Factory
48 from Acquisition
import aq_base
, aq_inner
, aq_parent
50 from types
import StringType
51 from Products
.CMFCore
.permissions
import ListFolderContents
, View
, ViewManagementScreens
,\
52 ManageProperties
, AddPortalFolders
, AddPortalContent
,\
53 ManagePortal
, ModifyPortalContent
54 from permissions
import DeletePortalContents
, DeleteObjects
, DeleteOwnedObjects
, SetLocalRoles
, CheckMemberPermission
55 from Products
.CMFCore
.utils
import _checkPermission
, getToolByName
56 from Products
.CMFCore
.CMFCatalogAware
import CMFCatalogAware
57 from Products
.CMFCore
.PortalFolder
import PortalFolder
, ContentFilter
58 from Products
.CMFDefault
.DublinCore
import DefaultDublinCoreImpl
60 from zope
.interface
import implements
61 from Products
.CMFCore
.interfaces
import IContentish
63 from utils
import _checkMemberPermission
64 from utils
import Message
as _
65 from Globals
import InitializeClass
66 from AccessControl
import ClassSecurityInfo
69 class PlinnFolder(CMFCatalogAware
, PortalFolder
, DefaultDublinCoreImpl
) :
72 implements(IContentish
)
74 security
= ClassSecurityInfo()
76 manage_options
= PortalFolder
.manage_options
78 ## change security for inherited methods
79 security
.declareProtected(AddPortalContent
, 'manage_pasteObjects')
81 def __init__( self
, id, title
='' ) :
82 PortalFolder
.__init
__(self
, id)
83 DefaultDublinCoreImpl
.__init
__(self
, title
= title
)
85 security
.declarePublic('allowedContentTypes')
86 def allowedContentTypes(self
):
88 List type info objects for types which can be added in this folder.
89 Types can be filtered using the localContentTypes attribute.
91 allowedTypes
= PortalFolder
.allowedContentTypes(self
)
92 if hasattr(self
, 'localContentTypes'):
93 allowedTypes
= [t
for t
in allowedTypes
if t
.title
in self
.localContentTypes
]
96 security
.declareProtected(View
, 'objectIdCanBeDeleted')
97 def objectIdCanBeDeleted(self
, id) :
98 """ Check permissions and ownership and return True
99 if current user can delete object id.
101 if _checkPermission(DeleteObjects
, self
) : # std zope perm
104 elif _checkPermission(DeletePortalContents
, self
):
105 mtool
= getToolByName(self
, 'portal_membership')
106 authMember
= mtool
.getAuthenticatedMember()
107 ob
= getattr(self
, id)
108 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
109 _checkPermission(DeleteOwnedObjects
, ob
) : return True
115 security
.declareProtected(DeletePortalContents
, 'manage_delObjects')
116 def manage_delObjects(self
, ids
=[], REQUEST
=None):
117 """Delete subordinate objects.
118 A member can delete his owned contents (if he has the 'Delete Portal Contents' permission)
119 without 'Delete objects' permission in this folder.
120 Return skipped object ids.
123 if _checkPermission(DeleteObjects
, self
) : # std zope perm
124 PortalFolder
.manage_delObjects(self
, ids
=ids
, REQUEST
=REQUEST
)
126 mtool
= getToolByName(self
, 'portal_membership')
127 authMember
= mtool
.getAuthenticatedMember()
129 if type(ids
) == StringType
:
133 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
134 _checkPermission(DeleteOwnedObjects
, ob
) : owned
.append(id)
135 else : notOwned
.append(id)
137 PortalFolder
.manage_delObjects(self
, ids
=owned
, REQUEST
=REQUEST
)
139 if REQUEST
is not None:
140 return self
.manage_main(
142 manage_tabs_message
='Object(s) deleted.',
147 security
.declareProtected(AddPortalContent
, 'manage_renameObjects')
148 def manage_renameObjects(self
, ids
=[], new_ids
=[], REQUEST
=None) :
149 """ Rename subordinate objects
150 A member can rename his owned contents if he has the 'Modify Portal Content' permission.
151 Returns skippend object ids.
153 if len(ids
) != len(new_ids
):
154 raise BadRequest(_('Please rename each listed object.'))
156 if _checkPermission(ViewManagementScreens
, self
) : # std zope perm
157 return super(PlinnFolder
, self
).manage_renameObjects(ids
, new_ids
, REQUEST
)
159 mtool
= getToolByName(self
, 'portal_membership')
160 authMember
= mtool
.getAuthenticatedMember()
162 for id, new_id
in zip(ids
, new_ids
) :
163 if id == new_id
: continue
166 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
167 _checkPermission(ModifyPortalContent
, ob
) :
168 self
.manage_renameObject(id, new_id
)
172 if REQUEST
is not None :
173 return self
.manage_main(self
, REQUEST
, update_menu
=1)
178 security
.declareProtected(ListFolderContents
, 'listFolderContents')
179 def listFolderContents( self
, contentFilter
=None ):
180 """ List viewable contentish and folderish sub-objects.
182 items
= self
.contentItems(filter=contentFilter
)
184 for id, obj
in items
:
185 if _checkPermission(View
, obj
) :
191 security
.declareProtected(ListFolderContents
, 'listNearestFolderContents')
192 def listNearestFolderContents(self
, contentFilter
=None, userid
=None, sorted=False) :
193 """ Return folder contents and traverse
194 recursively unaccessfull sub folders to find
200 filt
= contentFilter
.copy()
201 ctool
= getToolByName(self
, 'portal_catalog')
202 mtool
= getToolByName(self
, 'portal_membership')
204 if userid
and _checkPermission(CheckMemberPermission
, getToolByName(self
, 'portal_url').getPortalObject()) :
205 checkFunc
= lambda perm
, ob
: _checkMemberPermission(userid
, View
, ob
)
206 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getMemberById(userid
) )
208 checkFunc
= _checkPermission
209 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getAuthenticatedMember() )
212 # copy from CMFCore.PortalFolder.PortalFolder._filteredItems
213 pt
= filt
.get('portal_type', [])
214 if type(pt
) is type(''):
216 types_tool
= getToolByName(self
, 'portal_types')
217 allowed_types
= types_tool
.listContentTypes()
221 pt
= [t
for t
in pt
if t
in allowed_types
]
223 # After filtering, no types remain, so nothing should be
226 filt
['portal_type'] = pt
229 query
= ContentFilter(**filt
)
232 for o
in self
.objectValues() :
234 if checkFunc(View
, o
):
235 nearestObjects
.append(o
)
236 elif getattr(o
.aq_self
,'isAnObjectManager', False):
237 nearestObjects
.extend(_getDeepObjects(self
, ctool
, o
, filter=filt
))
239 if sorted and len(nearestObjects
) > 0 :
240 key
, reverse
= self
.getDefaultSorting()
241 if key
!= 'position' :
242 indexCallable
= callable(getattr(nearestObjects
[0], key
))
244 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
)(), getattr(b
, key
)())
246 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
), getattr(b
, key
))
247 nearestObjects
.sort(cmp=sortfunc
, reverse
=reverse
)
249 return nearestObjects
251 security
.declareProtected(ListFolderContents
, 'listCatalogedContents')
252 def listCatalogedContents(self
, contentFilter
={}):
253 """ query catalog and returns brains of contents.
254 Requires ExtendedPathIndex
256 ctool
= getToolByName(self
, 'portal_catalog')
257 contentFilter
['path'] = {'query':'/'.join(self
.getPhysicalPath()),
259 return ctool(sort_on
='position', **contentFilter
)
262 security
.declarePublic('synContentValues')
263 def synContentValues(self
):
264 # value for syndication
265 return self
.listNearestFolderContents()
267 security
.declareProtected(View
, 'SearchableText')
268 def SearchableText(self
) :
269 """ for full text indexation
271 return '%s %s' % (self
.title
, self
.description
)
273 security
.declareProtected(AddPortalFolders
, 'manage_addPlinnFolder')
274 def manage_addPlinnFolder(self
, id, title
='', REQUEST
=None):
275 """Add a new PortalFolder object with id *id*.
277 ob
=PlinnFolder(id, title
)
278 # from CMFCore.PortalFolder.PortalFolder :-)
279 self
._setObject
(id, ob
)
280 if REQUEST
is not None:
281 return self
.folder_contents( # XXX: ick!
282 self
, REQUEST
, portal_status_message
="Folder added")
285 # ## overload to maintain ownership if authenticated user has 'Manage portal' permission
286 # def manage_pasteObjects(self, cb_copy_data=None, REQUEST=None):
287 # """Paste previously copied objects into the current object.
289 # If calling manage_pasteObjects from python code, pass the result of a
290 # previous call to manage_cutObjects or manage_copyObjects as the first
293 # Also sends IObjectCopiedEvent and IObjectClonedEvent
294 # or IObjectWillBeMovedEvent and IObjectMovedEvent.
296 # if cb_copy_data is not None:
298 # elif REQUEST is not None and REQUEST.has_key('__cp'):
299 # cp = REQUEST['__cp']
303 # raise CopyError, eNoData
306 # op, mdatas = _cb_decode(cp)
308 # raise CopyError, eInvalid
311 # app = self.getPhysicalRoot()
312 # for mdata in mdatas:
313 # m = Moniker.loadMoniker(mdata)
316 # except ConflictError:
319 # raise CopyError, eNotFound
320 # self._verifyObjectPaste(ob, validate_src=op+1)
326 # mtool = getToolByName(self, 'portal_membership')
327 # utool = getToolByName(self, 'portal_url')
328 # portal = utool.getPortalObject()
329 # userIsPortalManager = mtool.checkPermission(ManagePortal, portal)
332 # orig_id = ob.getId()
333 # if not ob.cb_isCopyable():
334 # raise CopyError, eNotSupported % escape(orig_id)
337 # ob._notifyOfCopyTo(self, op=0)
338 # except ConflictError:
341 # raise CopyError, MessageDialog(
342 # title="Copy Error",
343 # message=sys.exc_info()[1],
344 # action='manage_main')
346 # id = self._get_id(orig_id)
347 # result.append({'id': orig_id, 'new_id': id})
350 # ob = ob._getCopy(self)
352 # notify(ObjectCopiedEvent(ob, orig_ob))
354 # if not userIsPortalManager :
355 # self._setObject(id, ob, suppress_events=True)
357 # self._setObject(id, ob, suppress_events=True, set_owner=0)
358 # ob = self._getOb(id)
361 # ob._postCopy(self, op=0)
363 # OFS.subscribers.compatibilityCall('manage_afterClone', ob, ob)
365 # notify(ObjectClonedEvent(ob))
367 # if REQUEST is not None:
368 # return self.manage_main(self, REQUEST, update_menu=1,
374 # orig_id = ob.getId()
375 # if not ob.cb_isMoveable():
376 # raise CopyError, eNotSupported % escape(orig_id)
379 # ob._notifyOfCopyTo(self, op=1)
380 # except ConflictError:
383 # raise CopyError, MessageDialog(
384 # title="Move Error",
385 # message=sys.exc_info()[1],
386 # action='manage_main')
388 # if not sanity_check(self, ob):
389 # raise CopyError, "This object cannot be pasted into itself"
391 # orig_container = aq_parent(aq_inner(ob))
392 # if aq_base(orig_container) is aq_base(self):
395 # id = self._get_id(orig_id)
396 # result.append({'id': orig_id, 'new_id': id})
398 # notify(ObjectWillBeMovedEvent(ob, orig_container, orig_id,
401 # # try to make ownership explicit so that it gets carried
402 # # along to the new location if needed.
403 # ob.manage_changeOwnershipType(explicit=1)
406 # orig_container._delObject(orig_id, suppress_events=True)
408 # orig_container._delObject(orig_id)
410 # "%s._delObject without suppress_events is discouraged."
411 # % orig_container.__class__.__name__,
412 # DeprecationWarning)
417 # self._setObject(id, ob, set_owner=0, suppress_events=True)
419 # self._setObject(id, ob, set_owner=0)
421 # "%s._setObject without suppress_events is discouraged."
422 # % self.__class__.__name__, DeprecationWarning)
423 # ob = self._getOb(id)
425 # notify(ObjectMovedEvent(ob, orig_container, orig_id, self, id))
426 # notifyContainerModified(orig_container)
427 # if aq_base(orig_container) is not aq_base(self):
428 # notifyContainerModified(self)
430 # ob._postCopy(self, op=1)
431 # # try to make ownership implicit if possible
432 # ob.manage_changeOwnershipType(explicit=0)
434 # if REQUEST is not None:
435 # REQUEST['RESPONSE'].setCookie('__cp', 'deleted',
436 # path='%s' % cookie_path(REQUEST),
437 # expires='Wed, 31-Dec-97 23:59:59 GMT')
438 # REQUEST['__cp'] = None
439 # return self.manage_main(self, REQUEST, update_menu=1,
445 InitializeClass(PlinnFolder
)
446 PlinnFolderFactory
= Factory(PlinnFolder
)
448 def _getDeepObjects(self
, ctool
, o
, filter={}):
449 res
= ctool
.unrestrictedSearchResults(path
= '/'.join(o
.getPhysicalPath()), **filter)
456 res
.sort(lambda a
, b
: cmp(a
.getPath(), b
.getPath()))
457 previousPath
= res
[0].getPath()
459 deepObjects
.append(res
[0].getObject())
461 currentPath
= b
.getPath()
462 if currentPath
.startswith(previousPath
) and len(currentPath
) > len(previousPath
):
465 deepObjects
.append(b
.getObject())
466 previousPath
= currentPath
471 manage_addPlinnFolder
= PlinnFolder
.manage_addPlinnFolder
.im_func