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
, NoneType
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
.utils
import getUtilityByInterfaceName
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 utils
import makeValidId
67 from Globals
import InitializeClass
68 from AccessControl
import ClassSecurityInfo
71 class PlinnFolder(CMFCatalogAware
, PortalFolder
, DefaultDublinCoreImpl
) :
74 implements(IContentish
)
76 security
= ClassSecurityInfo()
78 manage_options
= PortalFolder
.manage_options
80 ## change security for inherited methods
81 security
.declareProtected(AddPortalContent
, 'manage_pasteObjects')
83 def __init__( self
, id, title
='' ) :
84 PortalFolder
.__init
__(self
, id)
85 DefaultDublinCoreImpl
.__init
__(self
, title
= title
)
87 security
.declarePublic('allowedContentTypes')
88 def allowedContentTypes(self
):
90 List type info objects for types which can be added in this folder.
91 Types can be filtered using the localContentTypes attribute.
93 allowedTypes
= PortalFolder
.allowedContentTypes(self
)
94 if hasattr(self
, 'localContentTypes'):
95 allowedTypes
= [t
for t
in allowedTypes
if t
.title
in self
.localContentTypes
]
98 security
.declareProtected(View
, 'objectIdCanBeDeleted')
99 def objectIdCanBeDeleted(self
, id) :
100 """ Check permissions and ownership and return True
101 if current user can delete object id.
103 if _checkPermission(DeleteObjects
, self
) : # std zope perm
106 elif _checkPermission(DeletePortalContents
, self
):
107 mtool
= getToolByName(self
, 'portal_membership')
108 authMember
= mtool
.getAuthenticatedMember()
109 ob
= getattr(self
, id)
110 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
111 _checkPermission(DeleteOwnedObjects
, ob
) : return True
117 security
.declareProtected(DeletePortalContents
, 'manage_delObjects')
118 def manage_delObjects(self
, ids
=[], REQUEST
=None):
119 """Delete subordinate objects.
120 A member can delete his owned contents (if he has the 'Delete Portal Contents' permission)
121 without 'Delete objects' permission in this folder.
122 Return skipped object ids.
125 if _checkPermission(DeleteObjects
, self
) : # std zope perm
126 PortalFolder
.manage_delObjects(self
, ids
=ids
, REQUEST
=REQUEST
)
128 mtool
= getToolByName(self
, 'portal_membership')
129 authMember
= mtool
.getAuthenticatedMember()
131 if type(ids
) == StringType
:
135 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
136 _checkPermission(DeleteOwnedObjects
, ob
) : owned
.append(id)
137 else : notOwned
.append(id)
139 PortalFolder
.manage_delObjects(self
, ids
=owned
, REQUEST
=REQUEST
)
141 if REQUEST
is not None:
142 return self
.manage_main(
144 manage_tabs_message
='Object(s) deleted.',
149 security
.declareProtected(AddPortalContent
, 'manage_renameObjects')
150 def manage_renameObjects(self
, ids
=[], new_ids
=[], REQUEST
=None) :
151 """ Rename subordinate objects
152 A member can rename his owned contents if he has the 'Modify Portal Content' permission.
153 Returns skippend object ids.
155 if len(ids
) != len(new_ids
):
156 raise BadRequest(_('Please rename each listed object.'))
158 if _checkPermission(ViewManagementScreens
, self
) : # std zope perm
159 return super(PlinnFolder
, self
).manage_renameObjects(ids
, new_ids
, REQUEST
)
161 mtool
= getToolByName(self
, 'portal_membership')
162 authMember
= mtool
.getAuthenticatedMember()
164 for id, new_id
in zip(ids
, new_ids
) :
165 if id == new_id
: continue
168 if authMember
.allowed(ob
, object_roles
=['Owner'] ) and \
169 _checkPermission(ModifyPortalContent
, ob
) :
170 self
.manage_renameObject(id, new_id
)
174 if REQUEST
is not None :
175 return self
.manage_main(self
, REQUEST
, update_menu
=1)
180 security
.declareProtected(ListFolderContents
, 'listFolderContents')
181 def listFolderContents( self
, contentFilter
=None ):
182 """ List viewable contentish and folderish sub-objects.
184 items
= self
.contentItems(filter=contentFilter
)
186 for id, obj
in items
:
187 if _checkPermission(View
, obj
) :
193 security
.declareProtected(ListFolderContents
, 'listNearestFolderContents')
194 def listNearestFolderContents(self
, contentFilter
=None, userid
=None, sorted=False) :
195 """ Return folder contents and traverse
196 recursively unaccessfull sub folders to find
202 filt
= contentFilter
.copy()
203 ctool
= getToolByName(self
, 'portal_catalog')
204 mtool
= getToolByName(self
, 'portal_membership')
206 if userid
and _checkPermission(CheckMemberPermission
, getToolByName(self
, 'portal_url').getPortalObject()) :
207 checkFunc
= lambda perm
, ob
: _checkMemberPermission(userid
, View
, ob
)
208 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getMemberById(userid
) )
210 checkFunc
= _checkPermission
211 filt
['allowedRolesAndUsers'] = ctool
._listAllowedRolesAndUsers
( mtool
.getAuthenticatedMember() )
214 # copy from CMFCore.PortalFolder.PortalFolder._filteredItems
215 pt
= filt
.get('portal_type', [])
216 if type(pt
) is type(''):
218 types_tool
= getToolByName(self
, 'portal_types')
219 allowed_types
= types_tool
.listContentTypes()
223 pt
= [t
for t
in pt
if t
in allowed_types
]
225 # After filtering, no types remain, so nothing should be
228 filt
['portal_type'] = pt
231 query
= ContentFilter(**filt
)
234 for o
in self
.objectValues() :
236 if checkFunc(View
, o
):
237 nearestObjects
.append(o
)
238 elif getattr(o
.aq_self
,'isAnObjectManager', False):
239 nearestObjects
.extend(_getDeepObjects(self
, ctool
, o
, filter=filt
))
241 if sorted and len(nearestObjects
) > 0 :
242 key
, reverse
= self
.getDefaultSorting()
243 if key
!= 'position' :
244 indexCallable
= callable(getattr(nearestObjects
[0], key
))
246 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
)(), getattr(b
, key
)())
248 sortfunc
= lambda a
, b
: cmp(getattr(a
, key
), getattr(b
, key
))
249 nearestObjects
.sort(cmp=sortfunc
, reverse
=reverse
)
251 return nearestObjects
253 security
.declareProtected(ListFolderContents
, 'listCatalogedContents')
254 def listCatalogedContents(self
, contentFilter
={}):
255 """ query catalog and returns brains of contents.
256 Requires ExtendedPathIndex
258 ctool
= getUtilityByInterfaceName('Products.CMFCore.interfaces.ICatalogTool')
259 contentFilter
['path'] = {'query':'/'.join(self
.getPhysicalPath()),
261 return ctool(sort_on
='position', **contentFilter
)
263 security
.declarePublic('synContentValues')
264 def synContentValues(self
):
265 # value for syndication
266 return self
.listNearestFolderContents()
268 security
.declareProtected(View
, 'SearchableText')
269 def SearchableText(self
) :
270 """ for full text indexation
272 return '%s %s' % (self
.title
, self
.description
)
274 security
.declareProtected(AddPortalFolders
, 'manage_addPlinnFolder')
275 def manage_addPlinnFolder(self
, id, title
='', REQUEST
=None):
276 """Add a new PortalFolder object with id *id*.
278 ob
=PlinnFolder(id, title
)
279 # from CMFCore.PortalFolder.PortalFolder :-)
280 self
._setObject
(id, ob
)
281 if REQUEST
is not None:
282 return self
.folder_contents( # XXX: ick!
283 self
, REQUEST
, portal_status_message
="Folder added")
286 security
.declareProtected(AddPortalContent
, 'put_upload')
287 def put_upload(self
, REQUEST
, RESPONSE
):
288 """ Upload a content thru webdav put method.
289 The default behavior (NullRessource.PUT + PortalFolder.PUT_factory)
290 disallow files names with '_' at the begining.
295 # ## overload to maintain ownership if authenticated user has 'Manage portal' permission
296 # def manage_pasteObjects(self, cb_copy_data=None, REQUEST=None):
297 # """Paste previously copied objects into the current object.
299 # If calling manage_pasteObjects from python code, pass the result of a
300 # previous call to manage_cutObjects or manage_copyObjects as the first
303 # Also sends IObjectCopiedEvent and IObjectClonedEvent
304 # or IObjectWillBeMovedEvent and IObjectMovedEvent.
306 # if cb_copy_data is not None:
308 # elif REQUEST is not None and REQUEST.has_key('__cp'):
309 # cp = REQUEST['__cp']
313 # raise CopyError, eNoData
316 # op, mdatas = _cb_decode(cp)
318 # raise CopyError, eInvalid
321 # app = self.getPhysicalRoot()
322 # for mdata in mdatas:
323 # m = Moniker.loadMoniker(mdata)
326 # except ConflictError:
329 # raise CopyError, eNotFound
330 # self._verifyObjectPaste(ob, validate_src=op+1)
336 # mtool = getToolByName(self, 'portal_membership')
337 # utool = getToolByName(self, 'portal_url')
338 # portal = utool.getPortalObject()
339 # userIsPortalManager = mtool.checkPermission(ManagePortal, portal)
342 # orig_id = ob.getId()
343 # if not ob.cb_isCopyable():
344 # raise CopyError, eNotSupported % escape(orig_id)
347 # ob._notifyOfCopyTo(self, op=0)
348 # except ConflictError:
351 # raise CopyError, MessageDialog(
352 # title="Copy Error",
353 # message=sys.exc_info()[1],
354 # action='manage_main')
356 # id = self._get_id(orig_id)
357 # result.append({'id': orig_id, 'new_id': id})
360 # ob = ob._getCopy(self)
362 # notify(ObjectCopiedEvent(ob, orig_ob))
364 # if not userIsPortalManager :
365 # self._setObject(id, ob, suppress_events=True)
367 # self._setObject(id, ob, suppress_events=True, set_owner=0)
368 # ob = self._getOb(id)
371 # ob._postCopy(self, op=0)
373 # OFS.subscribers.compatibilityCall('manage_afterClone', ob, ob)
375 # notify(ObjectClonedEvent(ob))
377 # if REQUEST is not None:
378 # return self.manage_main(self, REQUEST, update_menu=1,
384 # orig_id = ob.getId()
385 # if not ob.cb_isMoveable():
386 # raise CopyError, eNotSupported % escape(orig_id)
389 # ob._notifyOfCopyTo(self, op=1)
390 # except ConflictError:
393 # raise CopyError, MessageDialog(
394 # title="Move Error",
395 # message=sys.exc_info()[1],
396 # action='manage_main')
398 # if not sanity_check(self, ob):
399 # raise CopyError, "This object cannot be pasted into itself"
401 # orig_container = aq_parent(aq_inner(ob))
402 # if aq_base(orig_container) is aq_base(self):
405 # id = self._get_id(orig_id)
406 # result.append({'id': orig_id, 'new_id': id})
408 # notify(ObjectWillBeMovedEvent(ob, orig_container, orig_id,
411 # # try to make ownership explicit so that it gets carried
412 # # along to the new location if needed.
413 # ob.manage_changeOwnershipType(explicit=1)
416 # orig_container._delObject(orig_id, suppress_events=True)
418 # orig_container._delObject(orig_id)
420 # "%s._delObject without suppress_events is discouraged."
421 # % orig_container.__class__.__name__,
422 # DeprecationWarning)
427 # self._setObject(id, ob, set_owner=0, suppress_events=True)
429 # self._setObject(id, ob, set_owner=0)
431 # "%s._setObject without suppress_events is discouraged."
432 # % self.__class__.__name__, DeprecationWarning)
433 # ob = self._getOb(id)
435 # notify(ObjectMovedEvent(ob, orig_container, orig_id, self, id))
436 # notifyContainerModified(orig_container)
437 # if aq_base(orig_container) is not aq_base(self):
438 # notifyContainerModified(self)
440 # ob._postCopy(self, op=1)
441 # # try to make ownership implicit if possible
442 # ob.manage_changeOwnershipType(explicit=0)
444 # if REQUEST is not None:
445 # REQUEST['RESPONSE'].setCookie('__cp', 'deleted',
446 # path='%s' % cookie_path(REQUEST),
447 # expires='Wed, 31-Dec-97 23:59:59 GMT')
448 # REQUEST['__cp'] = None
449 # return self.manage_main(self, REQUEST, update_menu=1,
455 InitializeClass(PlinnFolder
)
456 PlinnFolderFactory
= Factory(PlinnFolder
)
458 def _getDeepObjects(self
, ctool
, o
, filter={}):
459 res
= ctool
.unrestrictedSearchResults(path
= '/'.join(o
.getPhysicalPath()), **filter)
466 res
.sort(lambda a
, b
: cmp(a
.getPath(), b
.getPath()))
467 previousPath
= res
[0].getPath()
469 deepObjects
.append(res
[0].getObject())
471 currentPath
= b
.getPath()
472 if currentPath
.startswith(previousPath
) and len(currentPath
) > len(previousPath
):
475 deepObjects
.append(b
.getObject())
476 previousPath
= currentPath
481 manage_addPlinnFolder
= PlinnFolder
.manage_addPlinnFolder
.im_func