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
, NoneType
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
.utils
import getUtilityByInterfaceName
58 from Products
.CMFCore
.CMFCatalogAware
import CMFCatalogAware
59 from Products
.CMFCore
.PortalFolder
import PortalFolder
, ContentFilter
60 from Products
.CMFDefault
.DublinCore
import DefaultDublinCoreImpl
62 from zope
.interface
import implements
63 from Products
.CMFCore
.interfaces
import IContentish
65 from utils
import _checkMemberPermission
66 from utils
import Message
as _
67 from utils
import makeValidId
68 from Globals
import InitializeClass
69 from AccessControl
import ClassSecurityInfo
72 class PlinnFolder(CMFCatalogAware
, PortalFolder
, DefaultDublinCoreImpl
) :
75 implements(IContentish
)
77 security
= ClassSecurityInfo()
79 manage_options
= PortalFolder
.manage_options
81 ## change security for inherited methods
82 security
.declareProtected(AddPortalContent
, 'manage_pasteObjects')
84 def __init__( self
, id, title
='' ) :
85 PortalFolder
.__init
__(self
, id)
86 DefaultDublinCoreImpl
.__init
__(self
, title
= title
)
88 def __getitem__(self
, key
):
90 return self
._getOb
(key
, None)
91 request
= getattr(self
, 'REQUEST', None)
92 if not isinstance(request
, (str, NoneType
)):
93 method
=request
.get('REQUEST_METHOD', 'GET')
94 if (request
.maybe_webdav_client
and
95 method
not in ('GET', 'POST')):
96 id = makeValidId(self
, key
)
97 return NullResource(self
, id, request
).__of
__(self
)
101 security
.declarePublic('allowedContentTypes')
102 def allowedContentTypes(self
):
104 List type info objects for types which can be added in this folder.
105 Types can be filtered using the localContentTypes attribute.
107 allowedTypes
= PortalFolder
.allowedContentTypes(self
)
108 if hasattr(self
, 'localContentTypes'):
109 allowedTypes
= [t
for t
in allowedTypes
if t
.title
in self
.localContentTypes
]
112 security
.declareProtected(View
, 'objectIdCanBeDeleted')
113 def objectIdCanBeDeleted(self
, id) :
114 """ Check permissions and ownership and return True
115 if current user can delete object id.
117 if _checkPermission(DeleteObjects
, self
) : # std zope perm
120 elif _checkPermission(DeletePortalContents
, self
):
121 mtool
= getToolByName(self
, 'portal_membership')
122 authMember
= mtool
.getAuthenticatedMember()
123 ob
= getattr(self
, id)
124 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
125 _checkPermission(DeleteOwnedObjects
, ob
) : return True
131 security
.declareProtected(DeletePortalContents
, 'manage_delObjects')
132 def manage_delObjects(self
, ids
=[], REQUEST
=None):
133 """Delete subordinate objects.
134 A member can delete his owned contents (if he has the 'Delete Portal Contents' permission)
135 without 'Delete objects' permission in this folder.
136 Return skipped object ids.
139 if _checkPermission(DeleteObjects
, self
) : # std zope perm
140 PortalFolder
.manage_delObjects(self
, ids
=ids
, REQUEST
=REQUEST
)
142 mtool
= getToolByName(self
, 'portal_membership')
143 authMember
= mtool
.getAuthenticatedMember()
145 if type(ids
) == StringType
:
149 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
150 _checkPermission(DeleteOwnedObjects
, ob
) : owned
.append(id)
151 else : notOwned
.append(id)
153 PortalFolder
.manage_delObjects(self
, ids
=owned
, REQUEST
=REQUEST
)
155 if REQUEST
is not None:
156 return self
.manage_main(
158 manage_tabs_message
='Object(s) deleted.',
163 security
.declareProtected(AddPortalContent
, 'manage_renameObjects')
164 def manage_renameObjects(self
, ids
=[], new_ids
=[], REQUEST
=None) :
165 """ Rename subordinate objects
166 A member can rename his owned contents if he has the 'Modify Portal Content' permission.
167 Returns skippend object ids.
169 if len(ids
) != len(new_ids
):
170 raise BadRequest(_('Please rename each listed object.'))
172 if _checkPermission(ViewManagementScreens
, self
) : # std zope perm
173 return super(PlinnFolder
, self
).manage_renameObjects(ids
, new_ids
, REQUEST
)
175 mtool
= getToolByName(self
, 'portal_membership')
176 authMember
= mtool
.getAuthenticatedMember()
178 for id, new_id
in zip(ids
, new_ids
) :
179 if id == new_id
: continue
182 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
183 _checkPermission(ModifyPortalContent
, ob
) :
184 self
.manage_renameObject(id, new_id
)
188 if REQUEST
is not None :
189 return self
.manage_main(self
, REQUEST
, update_menu
=1)
194 security
.declareProtected(ListFolderContents
, 'listFolderContents')
195 def listFolderContents( self
, contentFilter
=None ):
196 """ List viewable contentish and folderish sub-objects.
198 items
= self
.contentItems(filter=contentFilter
)
200 for id, obj
in items
:
201 if _checkPermission(View
, obj
) :
207 security
.declareProtected(ListFolderContents
, 'listNearestFolderContents')
208 def listNearestFolderContents(self
, contentFilter
=None, userid
=None, sorted=False) :
209 """ Return folder contents and traverse
210 recursively unaccessfull sub folders to find
216 filt
= contentFilter
.copy()
217 ctool
= getToolByName(self
, 'portal_catalog')
218 mtool
= getToolByName(self
, 'portal_membership')
220 if userid
and _checkPermission(CheckMemberPermission
, getToolByName(self
, 'portal_url').getPortalObject()) :
221 checkFunc
= lambda perm
, ob
: _checkMemberPermission(userid
, View
, ob
)
222 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getMemberById(userid
) )
224 checkFunc
= _checkPermission
225 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getAuthenticatedMember() )
228 # copy from CMFCore.PortalFolder.PortalFolder._filteredItems
229 pt
= filt
.get('portal_type', [])
230 if type(pt
) is type(''):
232 types_tool
= getToolByName(self
, 'portal_types')
233 allowed_types
= types_tool
.listContentTypes()
237 pt
= [t
for t
in pt
if t
in allowed_types
]
239 # After filtering, no types remain, so nothing should be
242 filt
['portal_type'] = pt
245 query
= ContentFilter(**filt
)
248 for o
in self
.objectValues() :
250 if checkFunc(View
, o
):
251 nearestObjects
.append(o
)
252 elif getattr(o
.aq_self
,'isAnObjectManager', False):
253 nearestObjects
.extend(_getDeepObjects(self
, ctool
, o
, filter=filt
))
255 if sorted and len(nearestObjects
) > 0 :
256 key
, reverse
= self
.getDefaultSorting()
257 if key
!= 'position' :
258 indexCallable
= callable(getattr(nearestObjects
[0], key
))
260 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
)(), getattr(b
, key
)())
262 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
), getattr(b
, key
))
263 nearestObjects
.sort(cmp=sortfunc
, reverse
=reverse
)
265 return nearestObjects
267 security
.declareProtected(ListFolderContents
, 'listCatalogedContents')
268 def listCatalogedContents(self
, contentFilter
={}):
269 """ query catalog and returns brains of contents.
270 Requires ExtendedPathIndex
272 ctool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.ICatalogTool')
273 contentFilter
['path'] = {'query':'/'.join(self
.getPhysicalPath()),
275 return ctool(sort_on
='position', **contentFilter
)
277 security
.declarePublic('synContentValues')
278 def synContentValues(self
):
279 # value for syndication
280 return self
.listNearestFolderContents()
282 security
.declareProtected(View
, 'SearchableText')
283 def SearchableText(self
) :
284 """ for full text indexation
286 return '%s %s' % (self
.title
, self
.description
)
288 security
.declareProtected(AddPortalFolders
, 'manage_addPlinnFolder')
289 def manage_addPlinnFolder(self
, id, title
='', REQUEST
=None):
290 """Add a new PortalFolder object with id *id*.
292 ob
=PlinnFolder(id, title
)
293 # from CMFCore.PortalFolder.PortalFolder :-)
294 self
._setObject
(id, ob
)
295 if REQUEST
is not None:
296 return self
.folder_contents( # XXX: ick!
297 self
, REQUEST
, portal_status_message
="Folder added")
300 # ## overload to maintain ownership if authenticated user has 'Manage portal' permission
301 # def manage_pasteObjects(self, cb_copy_data=None, REQUEST=None):
302 # """Paste previously copied objects into the current object.
304 # If calling manage_pasteObjects from python code, pass the result of a
305 # previous call to manage_cutObjects or manage_copyObjects as the first
308 # Also sends IObjectCopiedEvent and IObjectClonedEvent
309 # or IObjectWillBeMovedEvent and IObjectMovedEvent.
311 # if cb_copy_data is not None:
313 # elif REQUEST is not None and REQUEST.has_key('__cp'):
314 # cp = REQUEST['__cp']
318 # raise CopyError, eNoData
321 # op, mdatas = _cb_decode(cp)
323 # raise CopyError, eInvalid
326 # app = self.getPhysicalRoot()
327 # for mdata in mdatas:
328 # m = Moniker.loadMoniker(mdata)
331 # except ConflictError:
334 # raise CopyError, eNotFound
335 # self._verifyObjectPaste(ob, validate_src=op+1)
341 # mtool = getToolByName(self, 'portal_membership')
342 # utool = getToolByName(self, 'portal_url')
343 # portal = utool.getPortalObject()
344 # userIsPortalManager = mtool.checkPermission(ManagePortal, portal)
347 # orig_id = ob.getId()
348 # if not ob.cb_isCopyable():
349 # raise CopyError, eNotSupported % escape(orig_id)
352 # ob._notifyOfCopyTo(self, op=0)
353 # except ConflictError:
356 # raise CopyError, MessageDialog(
357 # title="Copy Error",
358 # message=sys.exc_info()[1],
359 # action='manage_main')
361 # id = self._get_id(orig_id)
362 # result.append({'id': orig_id, 'new_id': id})
365 # ob = ob._getCopy(self)
367 # notify(ObjectCopiedEvent(ob, orig_ob))
369 # if not userIsPortalManager :
370 # self._setObject(id, ob, suppress_events=True)
372 # self._setObject(id, ob, suppress_events=True, set_owner=0)
373 # ob = self._getOb(id)
376 # ob._postCopy(self, op=0)
378 # OFS.subscribers.compatibilityCall('manage_afterClone', ob, ob)
380 # notify(ObjectClonedEvent(ob))
382 # if REQUEST is not None:
383 # return self.manage_main(self, REQUEST, update_menu=1,
389 # orig_id = ob.getId()
390 # if not ob.cb_isMoveable():
391 # raise CopyError, eNotSupported % escape(orig_id)
394 # ob._notifyOfCopyTo(self, op=1)
395 # except ConflictError:
398 # raise CopyError, MessageDialog(
399 # title="Move Error",
400 # message=sys.exc_info()[1],
401 # action='manage_main')
403 # if not sanity_check(self, ob):
404 # raise CopyError, "This object cannot be pasted into itself"
406 # orig_container = aq_parent(aq_inner(ob))
407 # if aq_base(orig_container) is aq_base(self):
410 # id = self._get_id(orig_id)
411 # result.append({'id': orig_id, 'new_id': id})
413 # notify(ObjectWillBeMovedEvent(ob, orig_container, orig_id,
416 # # try to make ownership explicit so that it gets carried
417 # # along to the new location if needed.
418 # ob.manage_changeOwnershipType(explicit=1)
421 # orig_container._delObject(orig_id, suppress_events=True)
423 # orig_container._delObject(orig_id)
425 # "%s._delObject without suppress_events is discouraged."
426 # % orig_container.__class__.__name__,
427 # DeprecationWarning)
432 # self._setObject(id, ob, set_owner=0, suppress_events=True)
434 # self._setObject(id, ob, set_owner=0)
436 # "%s._setObject without suppress_events is discouraged."
437 # % self.__class__.__name__, DeprecationWarning)
438 # ob = self._getOb(id)
440 # notify(ObjectMovedEvent(ob, orig_container, orig_id, self, id))
441 # notifyContainerModified(orig_container)
442 # if aq_base(orig_container) is not aq_base(self):
443 # notifyContainerModified(self)
445 # ob._postCopy(self, op=1)
446 # # try to make ownership implicit if possible
447 # ob.manage_changeOwnershipType(explicit=0)
449 # if REQUEST is not None:
450 # REQUEST['RESPONSE'].setCookie('__cp', 'deleted',
451 # path='%s' % cookie_path(REQUEST),
452 # expires='Wed, 31-Dec-97 23:59:59 GMT')
453 # REQUEST['__cp'] = None
454 # return self.manage_main(self, REQUEST, update_menu=1,
460 InitializeClass(PlinnFolder
)
461 PlinnFolderFactory
= Factory(PlinnFolder
)
463 def _getDeepObjects(self
, ctool
, o
, filter={}):
464 res
= ctool
.unrestrictedSearchResults(path
= '/'.join(o
.getPhysicalPath()), **filter)
471 res
.sort(lambda a
, b
: cmp(a
.getPath(), b
.getPath()))
472 previousPath
= res
[0].getPath()
474 deepObjects
.append(res
[0].getObject())
476 currentPath
= b
.getPath()
477 if currentPath
.startswith(previousPath
) and len(currentPath
) > len(previousPath
):
480 deepObjects
.append(b
.getObject())
481 previousPath
= currentPath
486 manage_addPlinnFolder
= PlinnFolder
.manage_addPlinnFolder
.im_func