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 webdav
.NullResource
import NullResource
37 from zope
.event
import notify
38 from zope
.lifecycleevent
import ObjectCopiedEvent
40 from zope
.app
.container
.contained
import notifyContainerModified
41 from zope
.app
.container
.contained
import ObjectMovedEvent
44 from zope
.container
.contained
import notifyContainerModified
45 from zope
.container
.contained
import ObjectMovedEvent
46 from OFS
.event
import ObjectClonedEvent
47 from OFS
.event
import ObjectWillBeMovedEvent
48 from zope
.component
.factory
import Factory
49 from Acquisition
import aq_base
, aq_inner
, aq_parent
51 from types
import StringType
52 from Products
.CMFCore
.permissions
import ListFolderContents
, View
, ViewManagementScreens
,\
53 ManageProperties
, AddPortalFolders
, AddPortalContent
,\
54 ManagePortal
, ModifyPortalContent
55 from permissions
import DeletePortalContents
, DeleteObjects
, DeleteOwnedObjects
, SetLocalRoles
, CheckMemberPermission
56 from Products
.CMFCore
.utils
import _checkPermission
, getToolByName
57 from Products
.CMFCore
.CMFCatalogAware
import CMFCatalogAware
58 from Products
.CMFCore
.PortalFolder
import PortalFolder
, ContentFilter
59 from Products
.CMFDefault
.DublinCore
import DefaultDublinCoreImpl
61 from zope
.interface
import implements
62 from Products
.CMFCore
.interfaces
import IContentish
64 from utils
import _checkMemberPermission
65 from utils
import Message
as _
66 from Globals
import InitializeClass
67 from AccessControl
import ClassSecurityInfo
70 class PlinnFolder(CMFCatalogAware
, PortalFolder
, DefaultDublinCoreImpl
) :
73 implements(IContentish
)
75 security
= ClassSecurityInfo()
77 manage_options
= PortalFolder
.manage_options
79 ## change security for inherited methods
80 security
.declareProtected(AddPortalContent
, 'manage_pasteObjects')
82 def __init__( self
, id, title
='' ) :
83 PortalFolder
.__init
__(self
, id)
84 DefaultDublinCoreImpl
.__init
__(self
, title
= title
)
86 def __getitem__(self
, key
):
88 return self
._getOb
(key
, None)
89 request
= getattr(self
, 'REQUEST', None)
90 if not isinstance(request
, (str, NoneType
)):
91 method
=request
.get('REQUEST_METHOD', 'GET')
92 if (request
.maybe_webdav_client
and
93 method
not in ('GET', 'POST')):
94 return NullResource(self
, key
, request
).__of
__(self
)
98 security
.declarePublic('allowedContentTypes')
99 def allowedContentTypes(self
):
101 List type info objects for types which can be added in this folder.
102 Types can be filtered using the localContentTypes attribute.
104 allowedTypes
= PortalFolder
.allowedContentTypes(self
)
105 if hasattr(self
, 'localContentTypes'):
106 allowedTypes
= [t
for t
in allowedTypes
if t
.title
in self
.localContentTypes
]
109 security
.declareProtected(View
, 'objectIdCanBeDeleted')
110 def objectIdCanBeDeleted(self
, id) :
111 """ Check permissions and ownership and return True
112 if current user can delete object id.
114 if _checkPermission(DeleteObjects
, self
) : # std zope perm
117 elif _checkPermission(DeletePortalContents
, self
):
118 mtool
= getToolByName(self
, 'portal_membership')
119 authMember
= mtool
.getAuthenticatedMember()
120 ob
= getattr(self
, id)
121 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
122 _checkPermission(DeleteOwnedObjects
, ob
) : return True
128 security
.declareProtected(DeletePortalContents
, 'manage_delObjects')
129 def manage_delObjects(self
, ids
=[], REQUEST
=None):
130 """Delete subordinate objects.
131 A member can delete his owned contents (if he has the 'Delete Portal Contents' permission)
132 without 'Delete objects' permission in this folder.
133 Return skipped object ids.
136 if _checkPermission(DeleteObjects
, self
) : # std zope perm
137 PortalFolder
.manage_delObjects(self
, ids
=ids
, REQUEST
=REQUEST
)
139 mtool
= getToolByName(self
, 'portal_membership')
140 authMember
= mtool
.getAuthenticatedMember()
142 if type(ids
) == StringType
:
146 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
147 _checkPermission(DeleteOwnedObjects
, ob
) : owned
.append(id)
148 else : notOwned
.append(id)
150 PortalFolder
.manage_delObjects(self
, ids
=owned
, REQUEST
=REQUEST
)
152 if REQUEST
is not None:
153 return self
.manage_main(
155 manage_tabs_message
='Object(s) deleted.',
160 security
.declareProtected(AddPortalContent
, 'manage_renameObjects')
161 def manage_renameObjects(self
, ids
=[], new_ids
=[], REQUEST
=None) :
162 """ Rename subordinate objects
163 A member can rename his owned contents if he has the 'Modify Portal Content' permission.
164 Returns skippend object ids.
166 if len(ids
) != len(new_ids
):
167 raise BadRequest(_('Please rename each listed object.'))
169 if _checkPermission(ViewManagementScreens
, self
) : # std zope perm
170 return super(PlinnFolder
, self
).manage_renameObjects(ids
, new_ids
, REQUEST
)
172 mtool
= getToolByName(self
, 'portal_membership')
173 authMember
= mtool
.getAuthenticatedMember()
175 for id, new_id
in zip(ids
, new_ids
) :
176 if id == new_id
: continue
179 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
180 _checkPermission(ModifyPortalContent
, ob
) :
181 self
.manage_renameObject(id, new_id
)
185 if REQUEST
is not None :
186 return self
.manage_main(self
, REQUEST
, update_menu
=1)
191 security
.declareProtected(ListFolderContents
, 'listFolderContents')
192 def listFolderContents( self
, contentFilter
=None ):
193 """ List viewable contentish and folderish sub-objects.
195 items
= self
.contentItems(filter=contentFilter
)
197 for id, obj
in items
:
198 if _checkPermission(View
, obj
) :
204 security
.declareProtected(ListFolderContents
, 'listNearestFolderContents')
205 def listNearestFolderContents(self
, contentFilter
=None, userid
=None, sorted=False) :
206 """ Return folder contents and traverse
207 recursively unaccessfull sub folders to find
213 filt
= contentFilter
.copy()
214 ctool
= getToolByName(self
, 'portal_catalog')
215 mtool
= getToolByName(self
, 'portal_membership')
217 if userid
and _checkPermission(CheckMemberPermission
, getToolByName(self
, 'portal_url').getPortalObject()) :
218 checkFunc
= lambda perm
, ob
: _checkMemberPermission(userid
, View
, ob
)
219 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getMemberById(userid
) )
221 checkFunc
= _checkPermission
222 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getAuthenticatedMember() )
225 # copy from CMFCore.PortalFolder.PortalFolder._filteredItems
226 pt
= filt
.get('portal_type', [])
227 if type(pt
) is type(''):
229 types_tool
= getToolByName(self
, 'portal_types')
230 allowed_types
= types_tool
.listContentTypes()
234 pt
= [t
for t
in pt
if t
in allowed_types
]
236 # After filtering, no types remain, so nothing should be
239 filt
['portal_type'] = pt
242 query
= ContentFilter(**filt
)
245 for o
in self
.objectValues() :
247 if checkFunc(View
, o
):
248 nearestObjects
.append(o
)
249 elif getattr(o
.aq_self
,'isAnObjectManager', False):
250 nearestObjects
.extend(_getDeepObjects(self
, ctool
, o
, filter=filt
))
252 if sorted and len(nearestObjects
) > 0 :
253 key
, reverse
= self
.getDefaultSorting()
254 if key
!= 'position' :
255 indexCallable
= callable(getattr(nearestObjects
[0], key
))
257 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
)(), getattr(b
, key
)())
259 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
), getattr(b
, key
))
260 nearestObjects
.sort(cmp=sortfunc
, reverse
=reverse
)
262 return nearestObjects
264 security
.declareProtected(ListFolderContents
, 'listCatalogedContents')
265 def listCatalogedContents(self
, contentFilter
={}):
266 """ query catalog and returns brains of contents.
267 Requires ExtendedPathIndex
269 ctool
= getToolByName(self
, 'portal_catalog')
270 contentFilter
['path'] = {'query':'/'.join(self
.getPhysicalPath()),
272 return ctool(sort_on
='position', **contentFilter
)
275 security
.declarePublic('synContentValues')
276 def synContentValues(self
):
277 # value for syndication
278 return self
.listNearestFolderContents()
280 security
.declareProtected(View
, 'SearchableText')
281 def SearchableText(self
) :
282 """ for full text indexation
284 return '%s %s' % (self
.title
, self
.description
)
286 security
.declareProtected(AddPortalFolders
, 'manage_addPlinnFolder')
287 def manage_addPlinnFolder(self
, id, title
='', REQUEST
=None):
288 """Add a new PortalFolder object with id *id*.
290 ob
=PlinnFolder(id, title
)
291 # from CMFCore.PortalFolder.PortalFolder :-)
292 self
._setObject
(id, ob
)
293 if REQUEST
is not None:
294 return self
.folder_contents( # XXX: ick!
295 self
, REQUEST
, portal_status_message
="Folder added")
298 # ## overload to maintain ownership if authenticated user has 'Manage portal' permission
299 # def manage_pasteObjects(self, cb_copy_data=None, REQUEST=None):
300 # """Paste previously copied objects into the current object.
302 # If calling manage_pasteObjects from python code, pass the result of a
303 # previous call to manage_cutObjects or manage_copyObjects as the first
306 # Also sends IObjectCopiedEvent and IObjectClonedEvent
307 # or IObjectWillBeMovedEvent and IObjectMovedEvent.
309 # if cb_copy_data is not None:
311 # elif REQUEST is not None and REQUEST.has_key('__cp'):
312 # cp = REQUEST['__cp']
316 # raise CopyError, eNoData
319 # op, mdatas = _cb_decode(cp)
321 # raise CopyError, eInvalid
324 # app = self.getPhysicalRoot()
325 # for mdata in mdatas:
326 # m = Moniker.loadMoniker(mdata)
329 # except ConflictError:
332 # raise CopyError, eNotFound
333 # self._verifyObjectPaste(ob, validate_src=op+1)
339 # mtool = getToolByName(self, 'portal_membership')
340 # utool = getToolByName(self, 'portal_url')
341 # portal = utool.getPortalObject()
342 # userIsPortalManager = mtool.checkPermission(ManagePortal, portal)
345 # orig_id = ob.getId()
346 # if not ob.cb_isCopyable():
347 # raise CopyError, eNotSupported % escape(orig_id)
350 # ob._notifyOfCopyTo(self, op=0)
351 # except ConflictError:
354 # raise CopyError, MessageDialog(
355 # title="Copy Error",
356 # message=sys.exc_info()[1],
357 # action='manage_main')
359 # id = self._get_id(orig_id)
360 # result.append({'id': orig_id, 'new_id': id})
363 # ob = ob._getCopy(self)
365 # notify(ObjectCopiedEvent(ob, orig_ob))
367 # if not userIsPortalManager :
368 # self._setObject(id, ob, suppress_events=True)
370 # self._setObject(id, ob, suppress_events=True, set_owner=0)
371 # ob = self._getOb(id)
374 # ob._postCopy(self, op=0)
376 # OFS.subscribers.compatibilityCall('manage_afterClone', ob, ob)
378 # notify(ObjectClonedEvent(ob))
380 # if REQUEST is not None:
381 # return self.manage_main(self, REQUEST, update_menu=1,
387 # orig_id = ob.getId()
388 # if not ob.cb_isMoveable():
389 # raise CopyError, eNotSupported % escape(orig_id)
392 # ob._notifyOfCopyTo(self, op=1)
393 # except ConflictError:
396 # raise CopyError, MessageDialog(
397 # title="Move Error",
398 # message=sys.exc_info()[1],
399 # action='manage_main')
401 # if not sanity_check(self, ob):
402 # raise CopyError, "This object cannot be pasted into itself"
404 # orig_container = aq_parent(aq_inner(ob))
405 # if aq_base(orig_container) is aq_base(self):
408 # id = self._get_id(orig_id)
409 # result.append({'id': orig_id, 'new_id': id})
411 # notify(ObjectWillBeMovedEvent(ob, orig_container, orig_id,
414 # # try to make ownership explicit so that it gets carried
415 # # along to the new location if needed.
416 # ob.manage_changeOwnershipType(explicit=1)
419 # orig_container._delObject(orig_id, suppress_events=True)
421 # orig_container._delObject(orig_id)
423 # "%s._delObject without suppress_events is discouraged."
424 # % orig_container.__class__.__name__,
425 # DeprecationWarning)
430 # self._setObject(id, ob, set_owner=0, suppress_events=True)
432 # self._setObject(id, ob, set_owner=0)
434 # "%s._setObject without suppress_events is discouraged."
435 # % self.__class__.__name__, DeprecationWarning)
436 # ob = self._getOb(id)
438 # notify(ObjectMovedEvent(ob, orig_container, orig_id, self, id))
439 # notifyContainerModified(orig_container)
440 # if aq_base(orig_container) is not aq_base(self):
441 # notifyContainerModified(self)
443 # ob._postCopy(self, op=1)
444 # # try to make ownership implicit if possible
445 # ob.manage_changeOwnershipType(explicit=0)
447 # if REQUEST is not None:
448 # REQUEST['RESPONSE'].setCookie('__cp', 'deleted',
449 # path='%s' % cookie_path(REQUEST),
450 # expires='Wed, 31-Dec-97 23:59:59 GMT')
451 # REQUEST['__cp'] = None
452 # return self.manage_main(self, REQUEST, update_menu=1,
458 InitializeClass(PlinnFolder
)
459 PlinnFolderFactory
= Factory(PlinnFolder
)
461 def _getDeepObjects(self
, ctool
, o
, filter={}):
462 res
= ctool
.unrestrictedSearchResults(path
= '/'.join(o
.getPhysicalPath()), **filter)
469 res
.sort(lambda a
, b
: cmp(a
.getPath(), b
.getPath()))
470 previousPath
= res
[0].getPath()
472 deepObjects
.append(res
[0].getObject())
474 currentPath
= b
.getPath()
475 if currentPath
.startswith(previousPath
) and len(currentPath
) > len(previousPath
):
478 deepObjects
.append(b
.getObject())
479 previousPath
= currentPath
484 manage_addPlinnFolder
= PlinnFolder
.manage_addPlinnFolder
.im_func