-
Notifications
You must be signed in to change notification settings - Fork 20
/
CMFCatalogAware.py
334 lines (274 loc) · 11.3 KB
/
CMFCatalogAware.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
##############################################################################
#
# Copyright (c) 2001 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Base class for catalog aware content items.
"""
import logging
from AccessControl.class_init import InitializeClass
from AccessControl.SecurityInfo import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager
from Acquisition import aq_base
from App.special_dtml import DTMLFile
from ExtensionClass import Base
from OFS.interfaces import IObjectClonedEvent
from OFS.interfaces import IObjectWillBeMovedEvent
from zope.component import queryUtility
from zope.component import subscribers
from zope.container.interfaces import IObjectAddedEvent
from zope.container.interfaces import IObjectMovedEvent
from zope.interface import implementer
from zope.lifecycleevent.interfaces import IObjectCopiedEvent
from zope.lifecycleevent.interfaces import IObjectCreatedEvent
from Products.CMFCore.interfaces import ICallableOpaqueItem
from Products.CMFCore.interfaces import ICatalogAware
from Products.CMFCore.interfaces import ICatalogTool
from Products.CMFCore.interfaces import IOpaqueItemManager
from Products.CMFCore.interfaces import IWorkflowAware
from Products.CMFCore.interfaces import IWorkflowTool
from Products.CMFCore.permissions import AccessContentsInformation
from Products.CMFCore.permissions import ManagePortal
from Products.CMFCore.permissions import ModifyPortalContent
from Products.CMFCore.utils import _dtmldir
logger = logging.getLogger('CMFCore.CMFCatalogAware')
@implementer(ICatalogAware)
class CatalogAware(Base):
"""Mix-in for notifying the catalog tool.
"""
security = ClassSecurityInfo()
# The following method can be overridden using inheritance so that it's
# possible to specify another catalog tool for a given content type
def _getCatalogTool(self):
return queryUtility(ICatalogTool)
#
# 'ICatalogAware' interface methods
#
@security.protected(ModifyPortalContent)
def indexObject(self):
""" Index the object in the portal catalog.
"""
catalog = self._getCatalogTool()
if catalog is not None:
catalog.indexObject(self)
@security.protected(ModifyPortalContent)
def unindexObject(self):
""" Unindex the object from the portal catalog.
"""
catalog = self._getCatalogTool()
if catalog is not None:
catalog.unindexObject(self)
@security.protected(ModifyPortalContent)
def reindexObject(self, idxs=[]):
""" Reindex the object in the portal catalog.
"""
if idxs == []:
# Update the modification date.
if hasattr(aq_base(self), 'notifyModified'):
self.notifyModified()
catalog = self._getCatalogTool()
if catalog is not None:
catalog.reindexObject(self, idxs=idxs)
_cmf_security_indexes = ('allowedRolesAndUsers',)
@security.protected(ModifyPortalContent)
def reindexObjectSecurity(self, skip_self=False):
""" Reindex security-related indexes on the object.
"""
catalog = self._getCatalogTool()
if catalog is None:
return
path = '/'.join(self.getPhysicalPath())
# XXX if _getCatalogTool() is overriden we will have to change
# this method for the sub-objects.
for brain in catalog.unrestrictedSearchResults(path=path):
brain_path = brain.getPath()
if brain_path == path and skip_self:
continue
# Get the object
try:
ob = brain._unrestrictedGetObject()
except (AttributeError, KeyError):
# don't fail on catalog inconsistency
continue
if ob is None:
# BBB: Ignore old references to deleted objects.
# Can happen only when using
# catalog-getObject-raises off in Zope 2.8
logger.warning("reindexObjectSecurity: Cannot get %s from "
"catalog", brain_path)
continue
# Recatalog with the same catalog uid.
s = getattr(ob, '_p_changed', 0)
catalog.reindexObject(ob, idxs=self._cmf_security_indexes,
update_metadata=0, uid=brain_path)
if s is None:
ob._p_deactivate()
InitializeClass(CatalogAware)
@implementer(IWorkflowAware)
class WorkflowAware(Base):
"""Mix-in for notifying the workflow tool.
"""
security = ClassSecurityInfo()
manage_options = ({'label': 'Workflows', 'action': 'manage_workflowsTab'},)
_manage_workflowsTab = DTMLFile('zmi_workflows', _dtmldir)
#
# ZMI methods
#
@security.protected(ManagePortal)
def manage_workflowsTab(self, REQUEST, manage_tabs_message=None):
""" Tab displaying the current workflows for the content object.
"""
ob = self
wtool = self._getWorkflowTool()
# XXX None ?
if wtool is not None:
wf_ids = wtool.getChainFor(ob)
states = {}
chain = []
for wf_id in wf_ids:
wf = wtool.getWorkflowById(wf_id)
if wf is not None:
# XXX a standard API would be nice
if hasattr(wf, 'getReviewStateOf'):
# Default Workflow
state = wf.getReviewStateOf(ob)
elif hasattr(wf, '_getWorkflowStateOf'):
# DCWorkflow
state = wf._getWorkflowStateOf(ob, id_only=1)
else:
state = '(Unknown)'
states[wf_id] = state
chain.append(wf_id)
return self._manage_workflowsTab(
REQUEST,
chain=chain,
states=states,
management_view='Workflows',
manage_tabs_message=manage_tabs_message)
# The following method can be overridden using inheritance so that it's
# possible to specify another workflow tool for a given content type
def _getWorkflowTool(self):
return queryUtility(IWorkflowTool)
#
# 'IWorkflowAware' interface methods
#
@security.private
def notifyWorkflowCreated(self):
""" Notify the workflow that the object was just created.
"""
wtool = self._getWorkflowTool()
if wtool is not None:
wtool.notifyCreated(self)
InitializeClass(WorkflowAware)
@implementer(IOpaqueItemManager)
class OpaqueItemManager(Base):
"""Mix-in for managing opaque items.
"""
security = ClassSecurityInfo()
# Opaque subitems
# ---------------
@security.protected(AccessContentsInformation)
def opaqueItems(self):
"""
Return opaque items (subelements that are contained
using something that is not an ObjectManager).
"""
items = []
# Call 'talkback' knowing that it is an opaque item.
# This will remain here as long as the discussion item does
# not implement ICallableOpaqueItem (backwards compatibility).
if hasattr(aq_base(self), 'talkback'):
talkback = self.talkback
if talkback is not None:
items.append((talkback.id, talkback))
# Other opaque items than 'talkback' may have callable
# manage_after* and manage_before* hooks.
# Loop over all attributes and add those to 'items'
# implementing 'ICallableOpaqueItem'.
self_base = aq_base(self)
for name in self_base.__dict__.keys():
obj = getattr(self, name)
if ICallableOpaqueItem.providedBy(obj):
items.append((obj.getId(), obj))
return tuple(items)
@security.protected(AccessContentsInformation)
def opaqueIds(self):
"""
Return opaque ids (subelements that are contained
using something that is not an ObjectManager).
"""
return [t[0] for t in self.opaqueItems()]
@security.protected(AccessContentsInformation)
def opaqueValues(self):
"""
Return opaque values (subelements that are contained
using something that is not an ObjectManager).
"""
return [t[1] for t in self.opaqueItems()]
InitializeClass(OpaqueItemManager)
class CMFCatalogAware(CatalogAware, WorkflowAware, OpaqueItemManager):
"""Mix-in for notifying catalog and workflow and managing opaque items.
"""
def handleContentishEvent(ob, event):
""" Event subscriber for (IContentish, IObjectEvent) events.
"""
if IObjectAddedEvent.providedBy(event):
wfaware = IWorkflowAware(ob, None)
if wfaware is not None:
wfaware.notifyWorkflowCreated()
ob.indexObject()
elif IObjectMovedEvent.providedBy(event):
if event.newParent is not None:
ob.indexObject()
elif IObjectWillBeMovedEvent.providedBy(event):
if event.oldParent is not None:
ob.unindexObject()
elif IObjectCopiedEvent.providedBy(event):
if hasattr(aq_base(ob), 'workflow_history'):
del ob.workflow_history
elif IObjectCreatedEvent.providedBy(event):
if hasattr(aq_base(ob), 'addCreator'):
ob.addCreator()
def handleDynamicTypeCopiedEvent(ob, event):
""" Event subscriber for (IDynamicType, IObjectCopiedEvent) events.
"""
# Make sure owner local role is set after pasting
# The standard Zope mechanisms take care of executable ownership
current_user = getSecurityManager().getUser()
if current_user is None:
return
current_user_id = current_user.getId()
if current_user_id is not None:
local_role_holders = [x[0] for x in ob.get_local_roles()]
ob.manage_delLocalRoles(local_role_holders)
ob.manage_setLocalRoles(current_user_id, ['Owner'])
def dispatchToOpaqueItems(ob, event):
"""Dispatch an event to opaque sub-items of a given object.
"""
for opaque in ob.opaqueValues():
s = getattr(opaque, '_p_changed', 0)
for ignored in subscribers((opaque, event), None):
pass # They do work in the adapter fetch
if s is None:
opaque._p_deactivate()
def handleOpaqueItemEvent(ob, event):
""" Event subscriber for (ICallableOpaqueItemEvents, IObjectEvent) events.
"""
if IObjectAddedEvent.providedBy(event):
if event.newParent is not None:
ob.manage_afterAdd(ob, event.newParent)
elif IObjectClonedEvent.providedBy(event):
ob.manage_afterClone(ob)
elif IObjectMovedEvent.providedBy(event):
if event.newParent is not None:
ob.manage_afterAdd(ob, event.newParent)
elif IObjectWillBeMovedEvent.providedBy(event):
if event.oldParent is not None:
ob.manage_beforeDelete(ob, event.oldParent)