vendor/CMF/1.6.3/CMFCore

view CatalogTool.py @ 2:4c712d7bd1d7

Added tag 1.6.3 for changeset 1babb9d61518
author Georges Racinet on purity.racinet.fr <georges@racinet.fr>
date Fri, 09 Sep 2011 12:44:00 +0200
parents
children
line source
1 ##############################################################################
2 #
3 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE.
11 #
12 ##############################################################################
13 """ Basic portal catalog.
15 $Id$
16 """
18 from warnings import warn
20 from AccessControl import ClassSecurityInfo
21 from AccessControl.PermissionRole import rolesForPermissionOn
22 from Acquisition import aq_base
23 from DateTime import DateTime
24 from Globals import DTMLFile
25 from Globals import InitializeClass
26 from Products.PluginIndexes.common import safe_callable
27 from Products.ZCatalog.ZCatalog import ZCatalog
28 from Products.ZCTextIndex.HTMLSplitter import HTMLWordSplitter
29 from Products.ZCTextIndex.Lexicon import CaseNormalizer
30 from Products.ZCTextIndex.Lexicon import Splitter
31 from Products.ZCTextIndex.Lexicon import StopWordRemover
32 from Products.ZCTextIndex.ZCTextIndex import PLexicon
33 from zope.interface import providedBy
34 from zope.interface.declarations import getObjectSpecification
35 from zope.interface.declarations import ObjectSpecification
36 from zope.interface.declarations import ObjectSpecificationDescriptor
38 from ActionProviderBase import ActionProviderBase
39 from interfaces.portal_catalog \
40 import IndexableObjectWrapper as IIndexableObjectWrapper
41 from interfaces.portal_catalog import portal_catalog as ICatalogTool
42 from permissions import AccessInactivePortalContent
43 from permissions import ManagePortal
44 from permissions import View
45 from utils import _checkPermission
46 from utils import _dtmldir
47 from utils import _getAuthenticatedUser
48 from utils import _mergedLocalRoles
49 from utils import getToolByName
50 from utils import SimpleRecord
51 from utils import UniqueObject
54 class IndexableObjectSpecification(ObjectSpecificationDescriptor):
56 def __get__(self, inst, cls=None):
57 if inst is None:
58 return getObjectSpecification(cls)
59 else:
60 provided = providedBy(inst._IndexableObjectWrapper__ob)
61 cls = type(inst)
62 return ObjectSpecification(provided, cls)
65 class IndexableObjectWrapper(object):
67 __implements__ = IIndexableObjectWrapper
68 __providedBy__ = IndexableObjectSpecification()
70 def __init__(self, vars, ob):
71 self.__vars = vars
72 self.__ob = ob
74 def __str__(self):
75 try:
76 # __str__ is used to get the data of File objects
77 return self.__ob.__str__()
78 except AttributeError:
79 return object.__str__(self)
81 def __getattr__(self, name):
82 vars = self.__vars
83 if vars.has_key(name):
84 return vars[name]
85 return getattr(self.__ob, name)
87 def allowedRolesAndUsers(self):
88 """
89 Return a list of roles and users with View permission.
90 Used by PortalCatalog to filter out items you're not allowed to see.
91 """
92 ob = self.__ob
93 allowed = {}
94 for r in rolesForPermissionOn(View, ob):
95 allowed[r] = 1
96 localroles = _mergedLocalRoles(ob)
97 for user, roles in localroles.items():
98 for role in roles:
99 if allowed.has_key(role):
100 allowed['user:' + user] = 1
101 if allowed.has_key('Owner'):
102 del allowed['Owner']
103 return list(allowed.keys())
105 def cmf_uid(self):
106 """
107 Return the CMFUid UID of the object while making sure
108 it is not accidentally acquired.
109 """
110 cmf_uid = getattr(aq_base(self.__ob), 'cmf_uid', '')
111 if safe_callable(cmf_uid):
112 return cmf_uid()
113 return cmf_uid
116 class CatalogTool(UniqueObject, ZCatalog, ActionProviderBase):
117 """ This is a ZCatalog that filters catalog queries.
118 """
120 __implements__ = (ICatalogTool, ZCatalog.__implements__,
121 ActionProviderBase.__implements__)
123 id = 'portal_catalog'
124 meta_type = 'CMF Catalog'
125 _actions = ()
127 security = ClassSecurityInfo()
129 manage_options = ( ZCatalog.manage_options +
130 ActionProviderBase.manage_options +
131 ({ 'label' : 'Overview', 'action' : 'manage_overview' }
132 ,
133 ))
135 def __init__(self):
136 ZCatalog.__init__(self, self.getId())
137 warn('CatalogTool._initIndexes is deprecated and will be removed in '
138 'CMF 2.0.',
139 DeprecationWarning)
140 self._initIndexes()
142 #
143 # Subclass extension interface
144 #
145 security.declarePublic( 'enumerateIndexes' ) # Subclass can call
146 def enumerateIndexes( self ):
147 # Return a list of ( index_name, type, extra ) tuples for the initial
148 # index set.
149 # Creator is deprecated and may go away, use listCreators!
150 # meta_type is deprecated and may go away, use portal_type!
151 plaintext_extra = SimpleRecord( lexicon_id='plaintext_lexicon'
152 , index_type='Okapi BM25 Rank'
153 )
154 htmltext_extra = SimpleRecord( lexicon_id='htmltext_lexicon'
155 , index_type='Okapi BM25 Rank'
156 )
158 return ( ('Title', 'ZCTextIndex', plaintext_extra)
159 , ('Subject', 'KeywordIndex', None)
160 , ('Description', 'ZCTextIndex', plaintext_extra)
161 , ('Creator', 'FieldIndex', None)
162 , ('listCreators', 'KeywordIndex', None)
163 , ('SearchableText', 'ZCTextIndex', htmltext_extra)
164 , ('Date', 'DateIndex', None)
165 , ('Type', 'FieldIndex', None)
166 , ('created', 'DateIndex', None)
167 , ('effective', 'DateIndex', None)
168 , ('expires', 'DateIndex', None)
169 , ('modified', 'DateIndex', None)
170 , ('allowedRolesAndUsers', 'KeywordIndex', None)
171 , ('review_state', 'FieldIndex', None)
172 , ('in_reply_to', 'FieldIndex', None)
173 , ('meta_type', 'FieldIndex', None)
174 , ('getId', 'FieldIndex', None)
175 , ('path', 'PathIndex', None)
176 , ('portal_type', 'FieldIndex', None)
177 )
179 security.declarePublic('enumerateLexicons')
180 def enumerateLexicons(self):
181 return (
182 ( 'plaintext_lexicon'
183 , Splitter()
184 , CaseNormalizer()
185 , StopWordRemover()
186 )
187 , ( 'htmltext_lexicon'
188 , HTMLWordSplitter()
189 , CaseNormalizer()
190 , StopWordRemover()
191 )
192 )
194 security.declarePublic( 'enumerateColumns' )
195 def enumerateColumns( self ):
196 # Return a sequence of schema names to be cached.
197 # Creator is deprecated and may go away, use listCreators!
198 return ( 'Subject'
199 , 'Title'
200 , 'Description'
201 , 'Type'
202 , 'review_state'
203 , 'Creator'
204 , 'listCreators'
205 , 'Date'
206 , 'getIcon'
207 , 'created'
208 , 'effective'
209 , 'expires'
210 , 'modified'
211 , 'CreationDate'
212 , 'EffectiveDate'
213 , 'ExpirationDate'
214 , 'ModificationDate'
215 , 'getId'
216 , 'portal_type'
217 )
219 def _initIndexes(self):
220 # ZCTextIndex lexicons
221 for id, splitter, normalizer, sw_remover in self.enumerateLexicons():
222 lexicon = PLexicon(id, '', splitter, normalizer, sw_remover)
223 self._setObject(id, lexicon)
225 # Content indexes
226 self._catalog.indexes.clear()
227 for index_name, index_type, extra in self.enumerateIndexes():
228 self.addIndex(index_name, index_type, extra=extra)
230 # Cached metadata
231 self._catalog.names = ()
232 self._catalog.schema.clear()
233 for column_name in self.enumerateColumns():
234 self.addColumn(column_name)
236 #
237 # ZMI methods
238 #
239 security.declareProtected(ManagePortal, 'manage_overview')
240 manage_overview = DTMLFile( 'explainCatalogTool', _dtmldir )
242 #
243 # 'portal_catalog' interface methods
244 #
246 def _listAllowedRolesAndUsers( self, user ):
247 result = list( user.getRoles() )
248 result.append( 'Anonymous' )
249 result.append( 'user:%s' % user.getId() )
250 return result
252 def _convertQuery(self, kw):
253 # Convert query to modern syntax
254 for k in 'effective', 'expires':
255 kusage = k+'_usage'
256 if not kw.has_key(kusage):
257 continue
258 usage = kw[kusage]
259 if not usage.startswith('range:'):
260 raise ValueError("Incorrect usage %s" % `usage`)
261 kw[k] = {'query': kw[k], 'range': usage[6:]}
262 del kw[kusage]
264 # searchResults has inherited security assertions.
265 def searchResults(self, REQUEST=None, **kw):
266 """
267 Calls ZCatalog.searchResults with extra arguments that
268 limit the results to what the user is allowed to see.
269 """
270 user = _getAuthenticatedUser(self)
271 kw[ 'allowedRolesAndUsers' ] = self._listAllowedRolesAndUsers( user )
273 if not _checkPermission( AccessInactivePortalContent, self ):
274 now = DateTime()
276 self._convertQuery(kw)
278 # Intersect query restrictions with those implicit to the tool
279 for k in 'effective', 'expires':
280 if kw.has_key(k):
281 range = kw[k]['range'] or ''
282 query = kw[k]['query']
283 if not isinstance(query, (tuple, list)):
284 query = (query,)
285 else:
286 range = ''
287 query = None
288 if range.find('min') > -1:
289 lo = min(query)
290 else:
291 lo = None
292 if range.find('max') > -1:
293 hi = max(query)
294 else:
295 hi = None
296 if k == 'effective':
297 if hi is None or hi > now:
298 hi = now
299 if lo is not None and hi < lo:
300 return ()
301 else: # 'expires':
302 if lo is None or lo < now:
303 lo = now
304 if hi is not None and hi < lo:
305 return ()
306 # Rebuild a query
307 if lo is None:
308 query = hi
309 range = 'max'
310 elif hi is None:
311 query = lo
312 range = 'min'
313 else:
314 query = (lo, hi)
315 range = 'min:max'
316 kw[k] = {'query': query, 'range': range}
318 return ZCatalog.searchResults(self, REQUEST, **kw)
320 __call__ = searchResults
322 security.declarePrivate('unrestrictedSearchResults')
323 def unrestrictedSearchResults(self, REQUEST=None, **kw):
324 """Calls ZCatalog.searchResults directly without restrictions.
326 This method returns every also not yet effective and already expired
327 objects regardless of the roles the caller has.
329 CAUTION: Care must be taken not to open security holes by
330 exposing the results of this method to non authorized callers!
332 If you're in doubt if you should use this method or
333 'searchResults' use the latter.
334 """
335 return ZCatalog.searchResults(self, REQUEST, **kw)
337 def __url(self, ob):
338 return '/'.join( ob.getPhysicalPath() )
340 manage_catalogFind = DTMLFile( 'catalogFind', _dtmldir )
342 def catalog_object(self, obj, uid=None, idxs=None, update_metadata=1,
343 pghandler=None):
344 # Wraps the object with workflow and accessibility
345 # information just before cataloging.
346 wftool = getToolByName(self, 'portal_workflow', None)
347 if wftool is not None:
348 vars = wftool.getCatalogVariablesFor(obj)
349 else:
350 vars = {}
351 w = IndexableObjectWrapper(vars, obj)
352 try:
353 ZCatalog.catalog_object(self, w, uid, idxs, update_metadata,
354 pghandler)
355 except TypeError:
356 # BBB: for Zope 2.7
357 ZCatalog.catalog_object(self, w, uid, idxs, update_metadata)
359 security.declarePrivate('indexObject')
360 def indexObject(self, object):
361 '''Add to catalog.
362 '''
363 url = self.__url(object)
364 self.catalog_object(object, url)
366 security.declarePrivate('unindexObject')
367 def unindexObject(self, object):
368 '''Remove from catalog.
369 '''
370 url = self.__url(object)
371 self.uncatalog_object(url)
373 security.declarePrivate('reindexObject')
374 def reindexObject(self, object, idxs=[], update_metadata=1, uid=None):
375 """Update catalog after object data has changed.
377 The optional idxs argument is a list of specific indexes
378 to update (all of them by default).
380 The update_metadata flag controls whether the object's
381 metadata record is updated as well.
383 If a non-None uid is passed, it will be used as the catalog uid
384 for the object instead of its physical path.
385 """
386 if uid is None:
387 uid = self.__url(object)
388 if idxs != []:
389 # Filter out invalid indexes.
390 valid_indexes = self._catalog.indexes.keys()
391 idxs = [i for i in idxs if i in valid_indexes]
392 self.catalog_object(object, uid, idxs, update_metadata)
394 InitializeClass(CatalogTool)