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.0 (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 __doc__='''Zope-specific versions of ZTUTils classes
15 $Id: Zope.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
16 __version__='$Revision: 1.1 $'[11:-2]
18 import sys, cgi, urllib, cgi
19 from Tree import encodeExpansion, decodeExpansion, TreeMaker
20 from SimpleTree import SimpleTreeMaker
21 from Batch import Batch
22 from Products.ZCatalog.Lazy import Lazy
23 from AccessControl import getSecurityManager
24 from string import split, join
25 from types import StringType, ListType, IntType, FloatType
26 from DateTime import DateTime
28 try:
29 from AccessControl.ZopeGuards import guarded_getitem
30 except ImportError:
31 Unauthorized = 'Unauthorized'
32 def guarded_getitem(object, index):
33 v = object[index]
34 if getSecurityManager().validate(object, object, index, v):
35 return v
36 raise Unauthorized, 'unauthorized access to element %s' % `i`
37 else:
38 from AccessControl import Unauthorized
40 class LazyFilter(Lazy):
41 # A LazyFilter that checks with the security policy
43 def __init__(self, seq, test=None, skip=None):
44 self._seq=seq
45 self._data=[]
46 self._eindex=-1
47 self._test=test
48 if not (skip is None or str(skip) == skip):
49 raise TypeError, 'Skip must be None or a string'
50 self._skip = skip
52 def __getitem__(self,index):
53 data=self._data
54 try: s=self._seq
55 except AttributeError: return data[index]
57 i=index
58 if i < 0: i=len(self)+i
59 if i < 0: raise IndexError, index
61 ind=len(data)
62 if i < ind: return data[i]
63 ind=ind-1
65 test=self._test
66 e=self._eindex
67 skip = self._skip
68 while i > ind:
69 e = e + 1
70 try:
71 try: v = guarded_getitem(s, e)
72 except Unauthorized, vv:
73 if skip is None:
74 self._eindex = e
75 msg = '(item %s): %s' % (index, vv)
76 raise Unauthorized, msg, sys.exc_info()[2]
77 skip_this = 1
78 else:
79 skip_this = 0
80 except IndexError:
81 del self._test
82 del self._seq
83 del self._eindex
84 raise IndexError, index
85 if skip_this: continue
86 if skip and not getSecurityManager().checkPermission(skip, v):
87 continue
88 if test is None or test(v):
89 data.append(v)
90 ind=ind+1
91 self._eindex=e
92 return data[i]
94 class TreeSkipMixin:
95 '''Mixin class to make trees test security, and allow
96 skipping of unauthorized objects. '''
97 skip = None
98 def setSkip(self, skip):
99 self.skip = skip
100 return self
101 def getChildren(self, object):
102 return LazyFilter(self._getChildren(object), skip=self.skip)
104 class TreeMaker(TreeSkipMixin, TreeMaker):
105 _getChildren = TreeMaker.getChildren
107 class SimpleTreeMaker(TreeSkipMixin, SimpleTreeMaker):
108 _getChildren = SimpleTreeMaker.getChildren
109 def cookieTree(self, root_object, default_state=None):
110 '''Make a tree with state stored in a cookie.'''
111 tree_pre = self.tree_pre
112 state_name = '%s-state' % tree_pre
113 set_name = '%s-setstate' % tree_pre
115 req = root_object.REQUEST
116 state = req.get(state_name)
117 if state:
118 setst = req.form.get(set_name)
119 if setst:
120 st, pn, expid = split(setst, ',')
121 state, (m, obid) = decodeExpansion(state, int(pn))
122 if m is None:
123 pass
124 elif st == 'e':
125 if m[obid] is None:
126 m[obid] = {expid: None}
127 else:
128 m[obid][expid] = None
129 elif st == 'c' and m is not state and obid==expid:
130 del m[obid]
131 else:
132 state = decodeExpansion(state)
133 else:
134 state = default_state
135 tree = self.tree(root_object, state)
136 rows = tree.flat()
137 req.RESPONSE.setCookie(state_name, encodeExpansion(rows))
138 return tree, rows
140 # Make the Batch class test security, and let it skip unauthorized.
141 _Batch = Batch
142 class Batch(Batch):
143 def __init__(self, sequence, size, start=0, end=0,
144 orphan=0, overlap=0, skip_unauthorized=None):
145 sequence = LazyFilter(sequence, skip=skip_unauthorized)
146 _Batch.__init__(self, sequence, size, start, end,
147 orphan, overlap)
149 # These functions are meant to be used together in templates that use
150 # trees or batches. For example, given a batch with a 'bstart' query
151 # argument, you would use "url_query(request, omit='bstart')" to get
152 # the base for the batching links, then append
153 # "make_query(bstart=batch.previous.first)" to one and
154 # "make_query(bstart=batch.end)" to the other.
156 def make_query(*args, **kwargs):
157 '''Construct a URL query string, with marshalling markup.
159 If there are positional arguments, they must be dictionaries.
160 They are combined with the dictionary of keyword arguments to form
161 a dictionary of query names and values.
163 Query names (the keys) must be strings. Values may be strings,
164 integers, floats, or DateTimes, and they may also be lists or
165 namespaces containing these types. Names and string values
166 should not be URL-quoted. All arguments are marshalled with
167 complex_marshal().
168 '''
170 d = {}
171 for arg in args:
172 d.update(arg)
173 d.update(kwargs)
175 uq = urllib.quote
176 qlist = complex_marshal(d.items())
177 for i in range(len(qlist)):
178 k, m, v = qlist[i]
179 qlist[i] = '%s%s=%s' % (uq(k), m, uq(str(v)))
181 return join(qlist, '&')
183 def make_hidden_input(*args, **kwargs):
184 '''Construct a set of hidden input elements, with marshalling markup.
186 If there are positional arguments, they must be dictionaries.
187 They are combined with the dictionary of keyword arguments to form
188 a dictionary of query names and values.
190 Query names (the keys) must be strings. Values may be strings,
191 integers, floats, or DateTimes, and they may also be lists or
192 namespaces containing these types. All arguments are marshalled with
193 complex_marshal().
194 '''
196 d = {}
197 for arg in args:
198 d.update(arg)
199 d.update(kwargs)
201 hq = cgi.escape
202 qlist = complex_marshal(d.items())
203 for i in range(len(qlist)):
204 k, m, v = qlist[i]
205 qlist[i] = ('<input type="hidden" name="%s%s" value="%s">'
206 % (hq(k), m, hq(str(v))))
208 return join(qlist, '\n')
210 def complex_marshal(pairs):
211 '''Add request marshalling information to a list of name-value pairs.
213 Names must be strings. Values may be strings,
214 integers, floats, or DateTimes, and they may also be lists or
215 namespaces containing these types.
217 The list is edited in place so that each (name, value) pair
218 becomes a (name, marshal, value) triple. The middle value is the
219 request marshalling string. Integer, float, and DateTime values
220 will have ":int", ":float", or ":date" as their marshal string.
221 Lists will be flattened, and the elements given ":list" in
222 addition to their simple marshal string. Dictionaries will be
223 flattened and marshalled using ":record".
224 '''
225 i = len(pairs)
226 while i > 0:
227 i = i - 1
228 k, v = pairs[i]
229 m = ''
230 sublist = None
231 if isinstance(v, StringType):
232 pass
233 elif hasattr(v, 'items'):
234 sublist = []
235 for sk, sv in v.items():
236 sm = simple_marshal(sv)
237 sublist.append(('%s.%s' % (k, sk), '%s:record' % sm, sv))
238 elif isinstance(v, ListType):
239 sublist = []
240 for sv in v:
241 sm = simple_marshal(sv)
242 sublist.append((k, '%s:list' % sm, sv))
243 else:
244 m = simple_marshal(v)
245 if sublist is None:
246 pairs[i] = (k, m, v)
247 else:
248 pairs[i:i + 1] = sublist
250 return pairs
252 def simple_marshal(v):
253 if isinstance(v, StringType):
254 return ''
255 if isinstance(v, IntType):
256 return ':int'
257 if isinstance(v, FloatType):
258 return ':float'
259 if isinstance(v, DateTime):
260 return ':date'
261 return ''
263 def url_query(request, req_name="URL", omit=None):
264 '''Construct a URL with a query string, using the current request.
266 request: the request object
267 req_name: the name, such as "URL1" or "BASEPATH1", to get from request
268 omit: sequence of name of query arguments to omit. If a name
269 contains a colon, it is treated literally. Otherwise, it will
270 match each argument name that starts with the name and a period or colon.
271 '''
273 base = request[req_name]
274 qs = request.get('QUERY_STRING', '')
276 if qs and omit:
277 qsparts = split(qs, '&')
279 if isinstance(omit, StringType):
280 omits = {omit: None}
281 else:
282 omits = {}
283 for name in omit:
284 omits[name] = None
285 omitted = omits.has_key
287 unq = urllib.unquote
288 for i in range(len(qsparts)):
289 name = unq(split(qsparts[i], '=', 1)[0])
290 if omitted(name):
291 qsparts[i] = ''
292 name = split(name, ':', 1)[0]
293 if omitted(name):
294 qsparts[i] = ''
295 name = split(name, '.', 1)[0]
296 if omitted(name):
297 qsparts[i] = ''
299 qs = join(filter(None, qsparts), '&')
301 # We alway append '?' since arguments will be appended to the URL
302 return '%s?%s' % (base, qs)