Wiki source code of Livetable Results Macros

Last modified by superadmin on 2025/08/29 17:37

Show last authors
1 {{velocity output="false"}}
2 #template('hierarchy_macros.vm')
3
4 #**
5 * Macro to get the results of a livetable data call.
6 * This page is called from live grids via Ajax with the argument xpage=plain. It returns a
7 * set of results serialized in JSON.
8 *#
9 #macro(gridresult $className $collist)
10 #gridresultwithfilter($className $collist '' '' {})
11 #end
12
13
14 #**
15 * Computes the query used to retrieve the results of a live table data call.
16 * NOTE: This macro is not fully encapsulated because it defines a few Velocity variables that are used in subsequent macros.
17 *#
18 #macro(gridresultwithfilter_buildQuery $className $collist $filterfrom $filterwhere $filterParams)
19 ## Additional columns; should *not* contain raw parameters, all added column names must be filtered
20 #set($fromSql = '')
21 ## Parametrized filter part of the query
22 #set($whereSql = '')
23 ## List of values to use with $whereSql
24 #if (!$filterParams)
25 #set($filterParams = {})
26 #end
27 #if ($filterParams.entrySet())
28 #set($whereParams = {})
29 #else
30 #set($whereParams = [])
31 #end
32 #set($class = $xwiki.getDocument($className).getxWikiClass())
33 ##
34 ## Add the columns needed for the actual data
35 ##
36 #set($tablelist = [])
37 #foreach($colname in $collist)
38 ## If a classname is defined and the class field corresponding to the column name,
39 ## we check the type of the field and skip it if it's Password.
40 #if ($className != '' && $class.get($colname))
41 #set ($isPasswordType = $class.get($colname).classType == 'Password')
42 #set ($isEmailType = $class.get($colname).classType == 'Email')
43 #set ($emailObfuscated = $services.mail.general.shouldObfuscate())
44 #if (!($isPasswordType || ($isEmailType && $emailObfuscated)))
45 #livetable_addColumnToQuery($colname)
46 #end
47 #else
48 #livetable_addColumnToQuery($colname)
49 #end
50 #end
51 ##
52 ## Tag filtering
53 ##
54 #if($request.tag)
55 #set($fromSql = "${fromSql} , BaseObject as tobject, DBStringListProperty as tagprop")
56 #set($whereSql = "${whereSql} and tobject.className='XWiki.TagClass' and tobject.name=doc.fullName and tobject.id=tagprop.id.id and tagprop.id.name='tags' and (")
57 #foreach($tag in $request.getParameterValues('tag'))
58 #if($foreach.count > 1) #set($whereSql = "${whereSql} and ") #end
59 ## Tags are case insensitive but they are stored unchanged which means we have to normalize them when performing
60 ## a query. Unfortunately there's no simple way to match multiple tags (AND operator). If we join the list of
61 ## tags in the FROM clause then we match at least one of the tags (OR operator). The only remaining option is to
62 ## check that the list of tags contains each of the desired tags. HQL doesn't help us to lower-case the entire
63 ## list of tags so we use an inner select for this.
64 #if ($whereParams.entrySet())
65 #set($whereSql = "${whereSql} lower(:wikitag${foreach.count}) in (select lower(tag) from tagprop.list tag)")
66 #set($discard = $whereParams.put("wikitag$foreach.count", "${tag}"))
67 #else
68 #set($whereSql = "${whereSql} lower(?) in (select lower(tag) from tagprop.list tag)")
69 #set($discard = $whereParams.add("${tag}"))
70 #end
71 #end
72 #set($whereSql = "${whereSql})")
73 #end
74 ##
75 ##
76 ## Order
77 ##
78 ## if the object for the classname of the order column is not already in the from sql, put it
79 #macro(addObjectClause $objectAlias)
80 #if($fromSql.indexOf($objectAlias) < 0)
81 #set($fromSql = "${fromSql}, BaseObject $objectAlias")
82 #if ($whereParams.entrySet())
83 #set($whereSql = "${whereSql} and ${objectAlias}.name = doc.fullName and ${objectAlias}.className = :${objectAlias}_className")
84 #set($discard = $whereParams.put("${objectAlias}_className", $propClassName))
85 #else
86 #set($whereSql = "${whereSql} and ${objectAlias}.name = doc.fullName and ${objectAlias}.className = ?")
87 #set($discard = $whereParams.add($propClassName))
88 #end
89 #end
90 #end
91 ## Set the order clause for a field. We first ignore the case using the lower function (so that e.g. 'aaa' equals 'AAA')
92 ## but then consider it only for equal values (so that e.g. 'AAA' comes before 'aaa').
93 #macro(setOrderClause $fieldName $direction $useRawValue)
94 #if ($useRawValue)
95 #set($orderSql = " order by ${fieldName} ${direction}")
96 #else
97 #set($orderSql = " order by lower(${fieldName}) ${direction}, ${fieldName} ${direction}")
98 #end
99 #end
100 #set($order = "$!request.sort")
101 #if ($order == 'doc.location')
102 #set ($order = 'doc.fullName')
103 #elseif ($order == 'email' && $services.mail.general.shouldObfuscate())
104 #set ($order = '')
105 #end
106 #set ($orderSql = '')
107 #if($order != '')
108 #set($orderDirection = "$!{request.get('dir').toLowerCase()}")
109 #if("$!orderDirection" != '' && "$!orderDirection" != 'asc')
110 #set($orderDirection = 'desc')
111 #end
112 #livetable_getTableAlias($order)
113 #if($order.startsWith('doc.'))
114 ## The column is a document field.
115 ##
116 ## These document fields need to be ordered as raw values and not as strings.
117 #set($rawDocumentFields = ['translation', 'date', 'contentUpdateDate', 'creationDate', 'elements', 'minorEdit1', 'hidden'])
118 #set($documentField = $stringtool.removeStart($order, 'doc.'))
119 #setOrderClause(${safe_tableAlias.replace('_','.')}, ${orderDirection}, $rawDocumentFields.contains($documentField))
120 #else
121 ## The column is an object property.
122 ##
123 ## Resolve the property.
124 #livetable_getPropertyClassAndType($order)
125 #set ($multiselect = "$!{propClass.get($order).getProperty('multiSelect').getValue()}")
126 ## We can only handle single values, not multiselect ones.
127 #if ($multiselect != '1')
128 ## Some property types do not need lowercasing since they have unique values by design, so we use the raw values to order.
129 #set($rawPropertyTypes = ['NumberClass', 'BooleanClass', 'DateClass', 'LevelsClass'])
130 ## If the order column is also a filer column, this means that it was already added to the query and all we need to do is to add it to the order clause.
131 #if(!$tablelist.contains($order))
132 ## The order column is not also a filter column, so not yet defined in the query.
133 ## We need to first define it (to the from and where clauses) before we can add it to the order clause.
134 ##
135 ## Resolve the table name of the property to be used in the from clause below.
136 #livetable_getTableName($order)
137 ## If the sort column has a _class specified, join that object in
138 #set($orderObjectAlias = 'obj')
139 #if($propClassName != '' && "$!propClass" != '')
140 ## prepare the alias of the BaseObject table that corresponds to the class of this column
141 #set($orderObjectAlias = "$!{propClassName.replaceAll('[^a-zA-Z0-9_]', '')}_obj")
142 #addObjectClause($orderObjectAlias)
143 #end
144 #set($fromSql = "${fromSql}, ${tableName} ${safe_tableAlias}")
145 ## FIXME: Check if this is indeed a property of the class. Hint: $propType can be used.
146 ## Conditions are put on the object coresponding to the column of the order ($orderObjectAlias), which depends on which is the class of the $order
147 #if ($whereParams.entrySet())
148 #set($whereSql = "${whereSql} and ${orderObjectAlias}.id=${safe_tableAlias}.id.id and ${safe_tableAlias}.name = :${safe_tableAlias}_name")
149 #set($discard = $whereParams.put("${safe_tableAlias}_name", "${order}"))
150 #else
151 #set($whereSql = "${whereSql} and ${orderObjectAlias}.id=${safe_tableAlias}.id.id and ${safe_tableAlias}.name = ?")
152 #set($discard = $whereParams.add("${order}"))
153 #end
154 #end
155 ## Add the column to the order clause.
156 #setOrderClause("${safe_tableAlias}.value", ${orderDirection}, $rawPropertyTypes.contains($propType))
157 #end
158 #end
159 #end
160 ##
161 ##
162 ## Compute the final queries
163 ##
164 #if ($filterParams.entrySet())
165 #set($sqlParams = {})
166 #set($tagsMatchingParams = {})
167 #set($allMatchingParams = {})
168 #else
169 #set($sqlParams = [])
170 #set($tagsMatchingParams = [])
171 #set($allMatchingParams = [])
172 #end
173 #if("$!className" != '')
174 ## Class query
175 #if ($sqlParams.entrySet())
176 #set($sql = ", BaseObject as obj $!fromSql $!filterfrom where obj.name=doc.fullName and obj.className = :className and doc.fullName not in (:classTemplate1, :classTemplate2) $!whereSql $!filterwhere")
177 #set($discard = $sqlParams.put('className', "${className}"))
178 #set($discard = $sqlParams.put('classTemplate1', "${className}Template"))
179 #set($discard = $sqlParams.put('classTemplate2', ${className.replaceAll('Class$', 'Template')}))
180 #set($discard = $sqlParams.putAll($whereParams))
181 #else
182 #set($sql = ", BaseObject as obj $!fromSql $!filterfrom where obj.name=doc.fullName and obj.className = ? and doc.fullName not in (?, ?) $!whereSql $!filterwhere")
183 #set($discard = $sqlParams.addAll(["${className}", "${className}Template", ${className.replaceAll('Class$', 'Template')}]))
184 #set($discard = $sqlParams.addAll($whereParams))
185 #end
186 ##
187 #set($tagsMatchingFiltersFrom = ", BaseObject as obj $!fromSql $!filterfrom")
188 #if ($tagsMatchingParams.entrySet())
189 #set($tagsMatchingFiltersWhere = "obj.name=doc.fullName and obj.className = :className and doc.fullName not in (:classTemplate1, :classTemplate2) $!whereSql $!filterwhere")
190 #set($discard = $tagsMatchingParams.put('className', "${className}"))
191 #set($discard = $tagsMatchingParams.put('classTemplate1', "${className}Template"))
192 #set($discard = $tagsMatchingParams.put('classTemplate2', ${className.replaceAll('Class$', 'Template')}))
193 #set($discard = $tagsMatchingParams.putAll($whereParams))
194 #else
195 #set($tagsMatchingFiltersWhere = "obj.name=doc.fullName and obj.className = ? and doc.fullName not in (?, ?) $!whereSql $!filterwhere")
196 #set($discard = $tagsMatchingParams.addAll(["${className}", "${className}Template", ${className.replaceAll('Class$', 'Template')}]))
197 #set($discard = $tagsMatchingParams.addAll($whereParams))
198 #end
199 ##
200 #set($allMatchingTagsFrom = ", BaseObject as obj $!filterfrom")
201 #if ($allMatchingParams.entrySet())
202 #set($allMatchingTagsWhere = "obj.name=doc.fullName and obj.className = :className and doc.fullName not in (:classTemplate1, :classTemplate2) $!filterwhere")
203 #set($discard = $allMatchingParams.put('className', "${className}"))
204 #set($discard = $allMatchingParams.put('classTemplate1', "${className}Template"))
205 #set($discard = $allMatchingParams.put('classTemplate2', ${className.replaceAll('Class$', 'Template')}))
206 #else
207 #set($allMatchingTagsWhere = "obj.name=doc.fullName and obj.className = ? and doc.fullName not in (?, ?) $!filterwhere")
208 #set($discard = $allMatchingParams.addAll(["${className}", "${className}Template", ${className.replaceAll('Class$', 'Template')}]))
209 #end
210 ##
211 #if($filterParams)
212 #if ($filterParams.entrySet())
213 #set($discard = $sqlParams.putAll($filterParams))
214 #set($discard = $tagsMatchingParams.putAll($filterParams))
215 #set($discard = $allMatchingParams.putAll($filterParams))
216 #else
217 #set($discard = $sqlParams.addAll($filterParams))
218 #set($discard = $tagsMatchingParams.addAll($filterParams))
219 #set($discard = $allMatchingParams.addAll($filterParams))
220 #end
221 #end
222 #else
223 ## Document query
224 #set($sql = "$!fromSql $!filterfrom where 1=1 $!whereSql $!filterwhere")
225 #if ($whereParams.entrySet())
226 #set($discard = $sqlParams.putAll($whereParams))
227 #else
228 #set($discard = $sqlParams.addAll($whereParams))
229 #end
230 ##
231 #set($tagsMatchingFiltersFrom = "$!fromSql $!filterfrom")
232 #set($tagsMatchingFiltersWhere = "1=1 $!whereSql $!filterwhere")
233 #if ($whereParams.entrySet())
234 #set($discard = $tagsMatchingParams.putAll($whereParams))
235 #else
236 #set($discard = $tagsMatchingParams.addAll($whereParams))
237 #end
238 ##
239 #set($allMatchingTagsFrom = "$!filterfrom")
240 #set($allMatchingTagsWhere = "1=1 $!filterwhere")
241 ##
242 #if($filterParams)
243 #if ($filterParams.entrySet())
244 #set($discard = $sqlParams.putAll($filterParams))
245 #set($discard = $tagsMatchingParams.putAll($filterParams))
246 #set($discard = $allMatchingParams.putAll($filterParams))
247 #else
248 #set($discard = $sqlParams.addAll($filterParams))
249 #set($discard = $tagsMatchingParams.addAll($filterParams))
250 #set($discard = $allMatchingParams.addAll($filterParams))
251 #end
252 #end
253 #end
254 #if($orderSql != '')
255 #set($sql = "$sql $!{orderSql}")
256 #end
257 #end
258 #**
259 * Adds TagCloud information to the JSON returned by a live table data call.
260 * NOTE: This macro uses Velocity variables defined by gridresultwithfilter_buildQuery.
261 *
262 * @param $map stores the JSON in memory so that it can be adjusted before serialization
263 *#
264 #macro(gridresult_buildTagCloudJSON $map)
265 ##
266 ## TagCloud matching the current filters
267 ##
268 #set($tagsMatchingFilters = $xwiki.tag.getTagCountForQuery($tagsMatchingFiltersFrom, $tagsMatchingFiltersWhere, $tagsMatchingParams))
269 ## FIXME: We use a map just because the client expects an object, but all we really need is a list..
270 #set($matchingTags = {})
271 #foreach($tag in $tagsMatchingFilters.keySet())
272 ## NOTE: The value doesn't have a special meaning. I've used 1 just because it takes less space when serialized.
273 #set($discard = $matchingTags.put($tag, 1))
274 #end
275 #set($discard = $map.put('matchingtags', $matchingTags))
276 ##
277 ## TagCloud matching all the documents used by the live table
278 ##
279 ## If all the query parameters are the same as for $tagsMatchingFilters, no need to run the query again.
280 ## This optimization allows to divide the time to compute the tagcloud by 2 when the table has no filters applied.
281 #if ($allMatchingTagsFrom.trim() != $tagsMatchingFiltersFrom.trim() || $allMatchingTagsWhere.trim() != $tagsMatchingFiltersWhere.trim() || $tagsMatchingParams != $allMatchingParams)
282 #set($allMatchingTags = $xwiki.tag.getTagCountForQuery($allMatchingTagsFrom, $allMatchingTagsWhere, $allMatchingParams))
283 #else
284 #set($allMatchingTags = $tagsMatchingFilters)
285 #end
286 ## FIXME: We use a list of maps just because the client expects an array, but we should simply return $allMatchingTags..
287 #set($tags = [])
288 #foreach($tag in $allMatchingTags.keySet())
289 #set($discard = $tags.add({'tag': $tag, 'count': $allMatchingTags.get($tag)}))
290 #end
291 #set($discard = $map.put('tags', $tags))
292 #end
293
294
295 #**
296 * Adds information about each live table row to the JSON returned by a live table data call.
297 * NOTE: This macro uses Velocity variables defined by gridresultwithfilter_buildQuery.
298 *
299 * @param $map stores the JSON in memory so that it can be adjusted before serialization
300 *#
301 #macro(gridresult_buildRowsJSON $map)
302 #set($offset = $numbertool.toNumber($request.get('offset')).intValue())
303 ## Offset starts from 0 in velocity and 1 in javascript
304 #set($offset = $offset - 1)
305 #if(!$offset || $offset < 0)
306 #set($offset = 0)
307 #end
308 #getAndValidateQueryLimitFromRequest('limit', 15, $limit)
309 #set($query = $services.query.hql($sql))
310 ## Apply query filters if defined. Otherwise use default.
311 #foreach ($queryFilter in $stringtool.split($!request.queryFilters, ', '))
312 #set ($query = $query.addFilter($queryFilter))
313 #end
314 #set ($query = $query.setLimit($limit).setOffset($offset).bindValues($sqlParams))
315 #set($items = $query.execute())
316 #set($discard = $map.put('totalrows', $query.count()))
317 #if ($limit > 0)
318 #set($discard = $map.put('returnedrows', $mathtool.min($items.size(), $limit)))
319 #else
320 ## When the limit is 0, it's equivalent to no limit at all and the actual number of returned results can be used.
321 #set($discard = $map.put('returnedrows', $items.size()))
322 #end
323 #set($discard = $map.put('offset', $mathtool.add($offset, 1)))
324 #set($rows = [])
325 #foreach($item in $items)
326 #gridresult_buildRowJSON($item $rows)
327 #end
328 #set ($discard = $map.put('rows', $rows))
329 #livetable_filterObfuscated($map)
330 #end
331
332
333 #**
334 * Adds information about the specified live table row to the JSON returned by a live table data call.
335 * NOTE: This macro uses Velocity variables available in gridresult_buildRowsJSON.
336 *
337 * @param $item the name of the document that feeds this live table row
338 * @param $rows stores the JSON in memory so that it can be adjusted before serialization
339 *#
340 #macro(gridresult_buildRowJSON $item $rows)
341 ## Handle both the case where the "language" filter is used and thus languages are returned too and the case where
342 ## only the document name is returned. When more than the document name is returned the $item variable is a list.
343 #if($item.size())
344 ## Extract doc name and doc language from $item
345 #set($docName = $item[0])
346 #set($docLanguage = $item[1])
347 #else
348 #set($docName = $item)
349 #set($docLanguage = '')
350 #end
351 #set ($docReference = $services.model.resolveDocument($docName))
352 #set ($isViewable = $services.security.authorization.hasAccess('view', $docReference))
353 #if ($isViewable)
354 #set ($row = {
355 'doc_viewable': $isViewable,
356 'doc_fullName': $services.model.serialize($docReference, 'local'),
357 'doc_space': $services.model.serialize($docReference.parent, 'local'),
358 'doc_location': "#hierarchy($docReference, {'limit': 5, 'plain': false, 'local': true, 'displayTitle': false})",
359 'doc_url': $xwiki.getURL($docReference),
360 'doc_space_url': $xwiki.getURL($docReference.parent),
361 'doc_wiki': $docReference.wikiReference.name,
362 'doc_wiki_url': $xwiki.getURL($docReference.wikiReference),
363 'doc_hasadmin': $xwiki.hasAdminRights(),
364 'doc_hasedit': $services.security.authorization.hasAccess('edit', $docReference),
365 'doc_hasdelete': $services.security.authorization.hasAccess('delete', $docReference),
366 'doc_edit_url': $xwiki.getURL($docReference, 'edit'),
367 'doc_copy_url': $xwiki.getURL($docReference, 'view', 'xpage=copy'),
368 'doc_delete_url': $xwiki.getURL($docReference, 'delete'),
369 'doc_rename_url': $xwiki.getURL($docReference, 'view', 'xpage=rename&step=1')
370 })
371 #set ($isTranslation = "$!docLanguage" != '' && $xwiki.getLanguagePreference() != $docLanguage)
372 ## Display the language after the document name so that not all translated documents have the same name displayed.
373 #set ($row.doc_name = "$docReference.name#if ($isTranslation) ($docLanguage)#end")
374 #set ($row.doc_hascopy = $row.doc_viewable)
375 #set ($row.doc_hasrename = $row.doc_hasdelete)
376 #set ($row.doc_hasrights = $row.doc_hasedit && $isAdvancedUser)
377 #if ($docReference.name == 'WebHome')
378
379 ## For nested pages, use the page administration.
380 #set ($webPreferencesReference = $services.model.createDocumentReference(
381 'WebPreferences', $docReference.lastSpaceReference))
382 #set ($row.doc_rights_url = $xwiki.getURL($webPreferencesReference, 'admin',
383 'editor=spaceadmin&section=PageRights'))
384 #else
385 ## For terminal pages, use the old rights editor.
386 ## TODO: We should create a page administration for terminal pages too.
387 #set ($row.doc_rights_url = $xwiki.getURL($docReference, 'edit', 'editor=rights'))
388 #end
389 #if ($row.doc_viewable)
390 #set ($itemDoc = $xwiki.getDocument($docReference))
391 ## Handle translations. We need to make sure we display the data associated to the correct document if the returned
392 ## result is a translation.
393 #if ($isTranslation)
394 #set ($translatedDoc = $itemDoc.getTranslatedDocument($docLanguage))
395 #else
396 #set ($translatedDoc = $itemDoc.translatedDocument)
397 #end
398 #set($discard = $itemDoc.use($className))
399 #set($discard = $row.put('doc_objectCount', $itemDoc.getObjectNumbers($className)))
400 #set($discard = $row.put('doc_edit_url', $itemDoc.getURL($itemDoc.defaultEditMode)))
401 #set($discard = $row.put('doc_date', $xwiki.formatDate($translatedDoc.date)))
402 #set($discard = $row.put('doc_title', $translatedDoc.plainTitle))
403 #set($rawTitle = $translatedDoc.title)
404 #if($rawTitle != $row['doc_title'])
405 #set($discard = $row.put('doc_title_raw', $rawTitle))
406 #end
407 #set ($metadataAuthor = $translatedDoc.authors.originalMetadataAuthor)
408 #if ($metadataAuthor == $services.user.getGuestUserReference())
409 ## Special handling for guest so that it displays unknown user.
410 #set($discard = $row.put('doc_author', $xwiki.getPlainUserName($NULL)))
411 #else
412 #set($discard = $row.put('doc_author', $xwiki.getPlainUserName($metadataAuthor)))
413 #end
414
415 #set($discard = $row.put('doc_author_url', $xwiki.getURL($metadataAuthor)))
416 #set($discard = $row.put('doc_creationDate', $xwiki.formatDate($translatedDoc.creationDate)))
417 #set($discard = $row.put('doc_creator', $xwiki.getPlainUserName($translatedDoc.creatorReference)))
418 #set($discard = $row.put('doc_hidden', $translatedDoc.isHidden()))
419 #foreach($colname in $collist)
420 #gridresult_buildColumnJSON($colname $row)
421 #end
422 #end
423 #else
424 #set ($row = {
425 'doc_viewable': $isViewable,
426 'doc_fullName': 'obfuscated'
427 })
428 #end
429 #set($discard = $rows.add($row))
430 #end
431
432
433 #**
434 * Adds information about the given column to the JSON returned by a live table data call.
435 * NOTE: This macro uses Velocity variables available in gridresult_buildRowJSON.
436 *
437 * @param $colname the name of the live table column for which to retrieve information
438 * @param $row stores the JSON in memory so that it can be adjusted before serialization
439 *#
440 #macro(gridresult_buildColumnJSON $colname $row)
441 #if($colname.startsWith('doc.'))
442 #elseif($colname == '_action')
443 #set($discard = $row.put($colname, $services.localization.render("${request.transprefix}actiontext")))
444 #elseif($colname == '_attachments')
445 #livetable_getAttachmentsList($translatedDoc)
446 #set($discard = $row.put($colname, $attachlist))
447 #elseif($colname == '_avatar')
448 #livetable_getAvatar($itemDoc)
449 #set($discard = $row.put($colname, $avatar))
450 #elseif($colname == '_images')
451 #livetable_getImagesList($itemDoc)
452 #set($discard = $row.put($colname, $imagesList))
453 ## Output likes if they are available.
454 #elseif($colname == '_likes' && "$!services.like" != "")
455 #set($likes = $services.like.getLikes($docReference))
456 #if ($likes.isPresent())
457 #set($discard = $row.put('_likes', $likes.get()))
458 #end
459 #else
460 #livetable_getPropertyClassAndType($colname)
461 #if(!$propClass.equals($class))
462 #set($discard = $itemDoc.use($propClassName))
463 #end
464 #set($fieldObject = $itemDoc.getFirstObject($colname))
465 #set($fieldProperty = $fieldObject.getProperty($colname))
466 #if ($fieldProperty.getPropertyClass().classType == 'Password')
467 #set($fieldValue = '********')
468 #elseif ($fieldProperty.getPropertyClass().classType == 'Email' && $services.mail.general.shouldObfuscate())
469 #set ($fieldValue = $services.mail.general.obfuscate("$!fieldProperty.getValue()"))
470 #else
471 #set($fieldValue = "$!fieldProperty.getValue()")
472 #end
473 #set($fieldDisplayValue = "#unwrapXPropertyDisplay($itemDoc.display($colname, 'view'))")
474 #if($fieldDisplayValue == '')
475 #set($fieldDisplayValue = $services.localization.render("${request.transprefix}emptyvalue"))
476 #end
477 #set($fieldUrl = '')
478 ## Only retrieve an URL for a DBListClass item
479 #if(($propType == 'DBListClass' || $propType == 'PageClass') && $propClass.get($colname).getProperty('multiSelect').value != 1)
480 #set($fieldUrl = $xwiki.getURL($fieldValue))
481 #if($fieldUrl == $xwiki.getURL($services.model.resolveDocument('', 'default', $doc.documentReference.extractReference('WIKI'))))
482 #set($fieldUrl = '')
483 #end
484 #end
485 #set($discard = $row.put($colname, $fieldDisplayValue))
486 #set($discard = $row.put("${colname}_value", $fieldValue))
487 #set($discard = $row.put("${colname}_url", $fieldUrl))
488 ## Reset to the default class
489 #set($discard = $itemDoc.use($className))
490 #end
491 #end
492
493
494 #**
495 * Builds the JSON response to a live table data call.
496 *
497 * @param $map stores the JSON in memory so that it can be adjusted before serialization
498 *#
499 #macro(gridresultwithfilter_buildJSON $className $collist $filterfrom $filterwhere $filterParams $map)
500 #gridresultwithfilter_buildQuery($className $collist $filterfrom $filterwhere $filterParams)
501 #if("$!request.sql" == '1')
502 #set($discard = $map.put('sql', $sql))
503 #set($discard = $map.put('params', $sqlParams))
504 #end
505 #set($discard = $map.put('reqNo', $numbertool.toNumber($request.reqNo).intValue()))
506 #if("$!request.tagcloud" == 'true')
507 #gridresult_buildTagCloudJSON($map)
508 #end
509 #gridresult_buildRowsJSON($map)
510 #end
511
512
513 #**
514 * Builds the JSON response to a live table data call.
515 *
516 * @param $map stores the JSON in memory so that it can be adjusted before serialization
517 *#
518 #macro(gridresult_buildJSON $className $collist $map)
519 #gridresultwithfilter_buildJSON($className $collist '' '' {} $map)
520 #end
521
522
523 #**
524 * Macro to get the results of a livetable data call.
525 * This page is called from live grids via Ajax with the argument xpage=plain. It returns a
526 * set of results serialized in JSON.
527 *#
528 #macro(gridresultwithfilter $className $collist $filterfrom $filterwhere $filterParams)
529 #if($xcontext.action == 'get' && "$!{request.outputSyntax}" == 'plain')
530 ## Build the JSON in memory (using basic Java data types) so that it can be adjusted before serialization.
531 #set($map = {})
532 #gridresultwithfilter_buildJSON($className $collist $filterfrom $filterwhere $filterParams $map)
533 #jsonResponse($map)
534 #end
535 #end
536
537
538 #**
539 * Get the name of the Property that should be used for a given livetable column.
540 * NOTE the resulting $tableName is safe to use inside SQL queries
541 *#
542 #macro(livetable_getTableName $colname)
543 #livetable_getPropertyClassAndType($colname)
544 #if($propType == 'NumberClass')
545 #set($numberType = $propClass.get($colname).getProperty('numberType').getValue())
546 #if($numberType == 'integer')
547 #set($tableName = 'IntegerProperty')
548 #elseif($numberType == 'float')
549 #set($tableName = 'FloatProperty')
550 #elseif($numberType == 'double')
551 #set($tableName = 'DoubleProperty')
552 #else
553 #set($tableName = 'LongProperty')
554 #end
555 #elseif($propType == 'BooleanClass')
556 #set($tableName = 'IntegerProperty')
557 #elseif($propType == 'DateClass')
558 #set($tableName = 'DateProperty')
559 #elseif($propType == 'TextAreaClass' || $propType == 'UsersClass' || $propType == 'GroupsClass')
560 #set($tableName = 'LargeStringProperty')
561 #elseif($propType == 'StaticListClass' || $propType == 'DBListClass' || $propType == 'DBTreeListClass' || $propType == 'PageClass')
562 #set($multiSelect = $propClass.get($colname).getProperty('multiSelect').getValue())
563 #set($relationalStorage = $propClass.get($colname).getProperty('relationalStorage').getValue())
564 #if($multiSelect == 1)
565 #if($relationalStorage == 1)
566 #set($tableName = 'DBStringListProperty')
567 #else
568 #set($tableName = 'StringListProperty')
569 #end
570 #else
571 #set($tableName = 'StringProperty')
572 #end
573 #else
574 #set($tableName = 'StringProperty')
575 #end
576 #end
577
578 #**
579 * Get the property class and type for a given livetable column.
580 *#
581 #macro(livetable_getPropertyClassAndType $colname)
582 #set($propClassName = "$!request.get(${colname.concat('_class')})")
583 #if($propClassName != '')
584 #set($propClass = $xwiki.getDocument($propClassName).getxWikiClass())
585 #else
586 #set($propClass = $class)
587 #end
588 #set($propType = '')
589 #if($propClass.getPropertyNames().contains($colname))
590 #set($propType = "$!{propClass.get($colname).type}")
591 #end
592 #end
593
594 #**
595 * Old alias of the #livetable_getTableName macro.
596 * @deprecated since 2.2.3, use {@link #livetable_getTableName}
597 *#
598 #macro(grid_gettablename $colname)
599 #livetable_getTableName($colname)
600 #end
601
602
603
604 #**
605 * List attachments for a document, putting the result as HTML markup in the $attachlist variable.
606 *#
607 #macro(livetable_getAttachmentsList $itemDoc)
608 #set($attachlist = '')
609 #foreach($attachment in $itemDoc.attachmentList)
610 #set($attachmentUrl = $itemDoc.getAttachmentURL($attachment.filename))
611 #set($attachlist = "${attachlist}<a href='${attachmentUrl}'>$attachment.filename</a><br/>")
612 #end
613 #end
614
615 #**
616 * Old alias of the #livetable_getAttachmentsList macro.
617 * @deprecated since 2.2.3, use {@link #livetable_getAttachmentsList}
618 *#
619 #macro(grid_attachlist $itemDoc)
620 #livetable_getAttachmentsList($itemDoc)
621 #end
622
623
624
625 #**
626 * List image attachments for a document, putting the result as HTML markup in the $imagesList variable.
627 *#
628 #macro(livetable_getImagesList $itemDoc)
629 #set($imagesList = '')
630 #foreach ($attachment in $itemDoc.attachmentList)
631 #if($attachment.isImage())
632 ## Create a thumbnail by resizing the image on the server side, if needed, to fit inside a 50x50 pixel square.
633 #set($thumbnailURL = $itemDoc.getAttachmentURL($attachment.filename, 'download', "width=50&height=50&keepAspectRatio=true"))
634 #set($imageURL = $itemDoc.getAttachmentURL($attachment.filename))
635 #set($imagesList = "${imagesList}<a href=""$imageURL""><img src=""$thumbnailURL"" alt=""$attachment.filename"" title=""$attachment.filename"" /></a>")
636 #end
637 #end
638 #end
639
640 #**
641 * Old alias of the #livetable_getImagesList macro.
642 * @deprecated since 2.2.3, use {@link #livetable_getImagesList}
643 *#
644 #macro(grid_photolist $itemDoc)
645 #livetable_getImagesList($itemDoc)
646 #end
647
648
649 #**
650 * Generate the HTML code for a user avatar.
651 *#
652 #macro(livetable_getAvatar $itemDoc)
653 #set ($avatar = "#mediumUserAvatar($itemDoc.fullName)")
654 #set ($avatar = $avatar.trim())
655 #end
656
657 #**
658 * Old alias of the #livetable_getAvatar macro.
659 * @deprecated since 2.2.3, use {@link #livetable_getAvatar}
660 *#
661 #macro(grid_avatar $itemDoc)
662 #livetable_getAvatar($itemDoc)
663 #end
664
665
666
667 #**
668 * Macro to extend the query to select the properties for the livetable columns.
669 * NOTE $colName is filtered (all characters but [a-zA-Z0-9_.] are removed) before use
670 *#
671 #macro (livetable_addColumnToQuery $colName)
672 ## Safe because / is not allowed in property names
673 ## The $joinModeMarker is used in #livetable_filterDBStringListProperty.
674 #set ($joinModeMarker = "/join_mode")
675 #if (!$colName.endsWith($joinModeMarker))
676 #set ($filterValue = "$!request.getParameter($colName)")
677 #if ("$!filterValue" != '')
678 #set ($discard = $tablelist.add($colName))
679 ## Some columns may support filtering with multiple constraints (multiple filter values).
680 #set ($filterValues = $request.getParameterValues($colName))
681 #if ($colName.startsWith('doc.'))
682 #if ($colName == 'doc.location')
683 #set ($safeColName = 'doc.fullName')
684 ## Use filterLocation since addLivetableLocationFilter is buggy when called several times (it'll add the
685 ## same HQL binding name every time it's called! See https://jira.xwiki.org/browse/XWIKI-17463).
686 ## Also note that we don't call addLocationFilter since we use a Map for $params.
687 #filterLocation($whereSql, $whereParams, $filterValue, 'locationFilterValue2', true)
688 #elseif ($colName == 'doc.date' || $colName == 'doc.creationDate' || $colName == 'doc.contentUpdateDate')
689 #livetable_getTableAlias($colName)
690 #livetable_filterDateProperty()
691 #else
692 #set ($safeColName = $colName.replaceAll('[^a-zA-Z0-9_.]', '').replace('_', '.'))
693 #if ($whereParams.entrySet())
694 #set ($whereSql = "${whereSql} and upper(str($safeColName)) like upper(:${safeColName.replace('.', '_')}_filter)")
695 #set ($discard = $whereParams.put("${safeColName.replace('.', '_')}_filter", "%$filterValue%"))
696 #else
697 #set ($whereSql = "${whereSql} and upper(str($safeColName)) like upper(?)")
698 #set ($discard = $whereParams.add("%$filterValue%"))
699 #end
700 #end
701 #else
702 #livetable_filterProperty($colName)
703 #end
704 #end
705 #end
706 #end
707
708
709 #**
710 * Determine how the filter values should be matched against the stored values. This macro sets two variables:
711 * <ul>
712 * <li>$matchType: use this when the specified column supports only a single filter value</li>
713 * <li>$matchTypes: use this when the specified column supports multiple filter values.</li>
714 * </ul>
715 *
716 * @param column the column name; each column can have a different match type
717 * @param filterValueCount the number of filter values for which to determine the match type; each filter value can have
718 * a different match type
719 * @param defaultMatchType the default match type to use for the given column when the request doesn't specify one
720 *#
721 #macro (livetable_getMatchTypes $column $filterValueCount $defaultMatchType)
722 #set ($macro.matchTypes = $request.getParameterValues("${column}_match"))
723 #if (!$macro.matchTypes || $macro.matchTypes.isEmpty())
724 ## No match type specified for this column.
725 #set ($matchType = $defaultMatchType)
726 #set ($matchTypes = $stringtool.repeat($matchType, ',', $filterValueCount).split(','))
727 #else
728 ## At least one match type specified for this column.
729 #set ($matchType = $macro.matchTypes.get(0))
730 #set ($matchTypes = [])
731 #set ($discard = $matchTypes.addAll($macro.matchTypes.subList(0, $mathtool.min($macro.matchTypes.size(),
732 $filterValueCount))))
733 #if ($matchTypes.size() < $filterValueCount)
734 ## Add missing match types.
735 #set ($discard = $matchTypes.addAll($stringtool.repeat($matchType, ',', $mathtool.sub($filterValueCount,
736 $matchTypes.size())).split(',')))
737 #end
738 #end
739 #end
740
741
742 #macro (livetable_filterProperty $colname)
743 #livetable_getTableAlias($colname)
744 #livetable_getTableName($colname)
745 #set ($fromSql = "$fromSql, $tableName as $safe_tableAlias")
746 ##
747 ## If the column is not from $class, we need to make sure we join with the proper table.
748 #set ($filterObjectAlias = 'obj')
749 #set ($propClass = $class)
750 #set ($propClassName = $request.getParameter("${colname}_class"))
751 #if ("$!propClassName" != '')
752 #set ($propClass = $xwiki.getDocument($propClassName).getxWikiClass())
753 #if ("$!propClass" != '')
754 ## Prepare the alias of the BaseObject table that corresponds to the class of this column
755 ## Property table is to be joined with its object, determined depending on $propClassName.
756 #set ($filterObjectAlias = "$!{propClassName.replaceAll('[^a-zA-Z0-9_]', '')}_obj")
757 #addObjectClause($filterObjectAlias)
758 #end
759 #end
760 #if ($whereParams.entrySet())
761 #set ($joinObjectTable = "${filterObjectAlias}.id = ${safe_tableAlias}.id.id and ${safe_tableAlias}.id.name = :${safe_tableAlias}_id_name")
762 #set ($discard = $whereParams.put("${safe_tableAlias}_id_name", $colname))
763 #else
764 #set ($joinObjectTable = "${filterObjectAlias}.id = ${safe_tableAlias}.id.id and ${safe_tableAlias}.id.name = ?")
765 #set ($discard = $whereParams.add($colname))
766 #end
767 #set ($whereSql = "$whereSql and $joinObjectTable")
768 ##
769 ## We determine the default match type (when not specified) based on the property meta class (e.g. DateClass).
770 #set ($propMetaClass = $NULL)
771 #if ($propClass && $propClass.getPropertyNames().contains($colname))
772 #set ($propMetaClass = $propClass.get($colname).type)
773 #end
774 ##
775 #set ($numberProperties = ['IntegerProperty', 'LongProperty', 'FloatProperty', 'DoubleProperty'])
776 #if ($numberProperties.contains($tableName))
777 #livetable_filterNumberProperty()
778 #elseif ($tableName == 'DateProperty')
779 #livetable_filterDateProperty()
780 #elseif ($tableName == 'DBStringListProperty')
781 #livetable_filterDBStringListProperty()
782 #elseif ($tableName == 'StringListProperty')
783 #livetable_filterStringListProperty()
784 #else
785 ## StringProperty or LargeStringProperty
786 #livetable_filterStringProperty()
787 #end
788 #end
789
790
791 #**
792 * NOTE: This macro uses variables defined in livetable_filterProperty . It was not meant to be used alone.
793 *#
794 #macro (livetable_filterNumberProperty)
795 #set($numberValue = $numbertool.toNumber($filterValue))
796 #if($tableName == 'IntegerProperty' || $tableName == 'LongProperty')
797 #if($tableName == 'LongProperty')
798 #set($numberValue = $numberValue.longValue())
799 #else
800 ## IntegerProperty
801 #set($numberValue = $numberValue.intValue())
802 #end
803 #if ($whereParams.entrySet())
804 #set($whereSql = "${whereSql} and ${safe_tableAlias}.value = :${safe_tableAlias}_value")
805 #set($discard = $whereParams.put("${safe_tableAlias}_value", $numberValue))
806 #else
807 #set($whereSql = "${whereSql} and ${safe_tableAlias}.value = ?")
808 #set($discard = $whereParams.add($numberValue))
809 #end
810 #else
811 #if($tableName == 'FloatProperty')
812 #set($numberValue = $numberValue.floatValue())
813 #else
814 ## DoubleProperty
815 #set($numberValue = $numberValue.doubleValue())
816 #end
817 #set($precision = 0.000001)
818 #if ($whereParams.entrySet())
819 #set($whereSql = "${whereSql} and abs(:${safe_tableAlias}_value - ${safe_tableAlias}.value) <= ${precision}")
820 #set($discard = $whereParams.put("${safe_tableAlias}_value", $numberValue))
821 #else
822 #set($whereSql = "${whereSql} and abs(? - ${safe_tableAlias}.value) <= ${precision}")
823 #set($discard = $whereParams.add($numberValue))
824 #end
825 #end
826 #end
827
828
829 #**
830 * NOTE: This macro uses variables defined in livetable_filterProperty . It was not meant to be used alone.
831 *#
832 #macro (livetable_filterDateProperty)
833 #if ($safe_tableAlias.startsWith('doc.'))
834 #set ($dateProperty = $safe_tableAlias)
835 #else
836 #set ($dateProperty = "${safe_tableAlias}.value")
837 #end
838 #set ($safeDateProperty = $dateProperty.replace('.', '_'))
839 #set ($dateRange = {})
840 ## Perform partial string matching by default if no match type is specified.
841 ## Note that for the moment we support only one filter value (e.g. one date range) and thus only the first match type
842 ## is taken into account.
843 #livetable_getMatchTypes($colname $filterValues.size() 'partial')
844 #parseDateRange($matchType $filterValue $dateRange)
845 #if ($dateRange.start || $dateRange.end)
846 ## Date range.
847 #if ($dateRange.start)
848 #if ($whereParams.entrySet())
849 #set ($whereSql = "${whereSql} and $dateProperty >= :${safeDateProperty}1")
850 #set ($discard = $whereParams.put("${safeDateProperty}1", $dateRange.start))
851 #else
852 #set ($whereSql = "${whereSql} and $dateProperty >= ?")
853 #set ($discard = $whereParams.add($dateRange.start))
854 #end
855 #end
856 #if ($dateRange.end)
857 #if ($whereParams.entrySet())
858 #set ($whereSql = "${whereSql} and $dateProperty <= :${safeDateProperty}2")
859 #set ($discard = $whereParams.put("${safeDateProperty}2", $dateRange.end))
860 #else
861 #set ($whereSql = "${whereSql} and $dateProperty <= ?")
862 #set ($discard = $whereParams.add($dateRange.end))
863 #end
864 #end
865 #else
866 ## String matching (contains).
867 #if ($whereParams.entrySet())
868 #set ($whereSql = "${whereSql} and upper(str($dateProperty)) like upper(:$safeDateProperty)")
869 #set ($discard = $whereParams.put($safeDateProperty, "%$filterValue%"))
870 #else
871 #set ($whereSql = "${whereSql} and upper(str($dateProperty)) like upper(?)")
872 #set ($discard = $whereParams.add("%$filterValue%"))
873 #end
874 #end
875 #end
876
877
878 #**
879 * NOTE: This macro uses variables defined in livetable_filterProperty . It was not meant to be used alone.
880 *#
881 #macro (livetable_filterDBStringListProperty)
882 ## Perform exact matching by default if no match type is specified.
883 ## Note that for DBStringList properties we take into account only the first match type, even if multiple filter
884 ## values are specified. Basically the first match type is used for all filter values.
885 #livetable_getMatchTypes($colname $filterValues.size() 'exact')
886 #if ($matchType == 'partial' || $matchType == 'prefix')
887 ## We need to join with the list of values in order to be able to use the LIKE operator.
888 #set ($matchTarget = "${safe_tableAlias}_item")
889 #if ($whereParams.entrySet())
890 #set ($paramPrefix = "${safe_tableAlias}_item_")
891 #else
892 #set ($paramPrefix = $NULL)
893 #end
894 #set ($joinPos = $mathtool.add($fromSql.lastIndexOf(" $safe_tableAlias"), $mathtool.add($safe_tableAlias.length(), 1)))
895 #set ($fromSql = "$fromSql.substring(0, $joinPos) join ${safe_tableAlias}.list as $matchTarget $fromSql.substring($joinPos)")
896 #else
897 ## Fall-back on exact matching even if the match type is specified, when its value is not supported.
898 #set ($matchType = 'exact')
899 #set ($matchTarget = "${safe_tableAlias}.list")
900 #if ($whereParams.entrySet())
901 #set ($paramPrefix = "${safe_tableAlias}_list_")
902 #else
903 #set ($paramPrefix = $NULL)
904 #end
905 #end
906 #set ($filterQuery = "#livetable_getFilterQuery($matchTarget $matchType true $filterValues.size() $paramPrefix $NULL)")
907 #set ($whereSql = "$whereSql and ($filterQuery.trim())")
908 #foreach ($filterValue in $filterValues)
909 #livetable_addFilterParam($filterValue $matchType $whereParams "${paramPrefix}${foreach.count}")
910 #end
911 #end
912
913
914 #**
915 * NOTE: This macro uses variables defined in livetable_filterProperty . It was not meant to be used alone.
916 *#
917 #macro (livetable_filterStringListProperty)
918 ## From the user point of view we support only exact matching for StringList properties, due to the way the values of
919 ## these properties are stored (concatenated). But when building the actual query, the match type is in fact partial
920 ## because we have to use the like operator in order to match the concatenated list of values.
921 #livetable_getMatchTypes($colname $filterValues.size() 'exact')
922 #set ($matchTarget = "concat('|', concat(${safe_tableAlias}.textValue, '|'))")
923 #if ($whereParams.entrySet())
924 #set ($paramPrefix = "${safe_tableAlias}_textValue_")
925 #else
926 #set ($paramPrefix = $NULL)
927 #end
928 ## As noted above, we have to use the like operator because the list of values is saved concatenated, so from the
929 ## point of view of the query the match type is always partial.
930 #set ($filterQuery = "#livetable_getFilterQuery($matchTarget 'partial' false $filterValues.size() $paramPrefix $NULL)")
931 #set ($whereSql = "${whereSql} and ($filterQuery.trim())")
932 #foreach ($filterValue in $filterValues)
933 #if ($matchTypes.get($foreach.index) == 'empty')
934 ## The client side cannot pass an empty filter value so it specifies that the value is empty using the match type.
935 #set ($filterValue = '')
936 #end
937 ## As noted above, we can only perform exact matching due to the way the values are stored (concatenated).
938 #livetable_addFilterParam("%|$filterValue|%" 'exact' $whereParams "${paramPrefix}${foreach.count}")
939 #end
940 #end
941
942
943 #**
944 * NOTE: This macro uses variables defined in livetable_filterProperty . It was not meant to be used alone.
945 *#
946 #macro (livetable_filterStringProperty)
947 #if ($propMetaClass.endsWith('ListClass'))
948 ## Perform exact matching by default for StaticListClass, DBListClass and DBTreeListClass
949 ## when they are stored as StringProperty (i.e. single value and no relational storage).
950 #set ($defaultStringMatchType = 'exact')
951 #else
952 ## Perform partial matching by default otherwise.
953 #set ($defaultStringMatchType = 'partial')
954 #end
955 #livetable_getMatchTypes($colname $filterValues.size() $defaultStringMatchType)
956 ## Group the filter values by match type so that we cann optimize the query.
957 #livetable_groupFilterValuesByMatchType($matchTypes $filterValues)
958 #if ($whereParams.entrySet())
959 #set ($paramPrefix = "${safe_tableAlias}_value_")
960 #else
961 #set ($paramPrefix = $NULL)
962 #end
963 ## Note that unlike other property types, the String property supports different match types for different filter
964 ## values. This means we have to call livetable_getFilterQuery for each filter value and then join the constraints
965 ## ourselves.
966 #set ($constraints = [])
967 #set ($paramOffset = 1)
968 #foreach ($entry in $filterValuesByMatchType.entrySet())
969 #set ($matchType = $entry.key)
970 #set ($filterValues = $entry.value)
971 #set ($constraint = "#livetable_getFilterQuery(""${safe_tableAlias}.value"" $matchType false $filterValues.size() $paramPrefix $paramOffset)")
972 #set ($discard = $constraints.add($constraint.trim()))
973 #foreach ($filterValue in $filterValues)
974 #livetable_addFilterParam($filterValue $matchType $whereParams
975 "${paramPrefix}${mathtool.add($paramOffset, $foreach.index)}")
976 #end
977 #set ($paramOffset = $paramOffset + $filterValues.size())
978 #end
979 #set ($whereSql = "${whereSql} and ($stringtool.join($constraints, "" $joinOperator ""))")
980 #end
981
982 #macro (livetable_groupFilterValuesByMatchType $matchTypes $filterValues)
983 #set ($filterValuesByMatchType = {})
984 #foreach ($matchType in $matchTypes)
985 #set ($discard = $filterValuesByMatchType.putIfAbsent($matchType, []))
986 #set ($discard = $filterValuesByMatchType.get($matchType).add($filterValues.get($foreach.index)))
987 #end
988 #end
989
990 #macro (livetable_getJoinOperator $colName)
991 #set ($joinOperator = "$!{request.get(""${colName}${joinModeMarker}"").toUpperCase()}")
992 #if ($joinOperator != 'AND' && $joinOperator != 'OR')
993 #set ($joinOperator = 'AND')
994 #end
995 #end
996
997 #macro (livetable_getFilterQuery $column $matchType $isList $valueCount $paramPrefix $paramOffset)
998 #livetable_getJoinOperator($colname)
999 #if (!$paramOffset)
1000 #set ($paramOffset = 1)
1001 #end
1002 #if ($matchType == 'partial' || $matchType == 'prefix')
1003 #livetable_repeatParams("upper($column) like upper(?)", " $joinOperator ", $valueCount, $paramPrefix, $paramOffset)
1004 #elseif($matchType == 'empty')
1005 ## Check if the value of the column is like the empty parameter (which is often the empty string), or if the value
1006 ## of the column is null (to be compliant with Oracle which stores the empty string as a NULL value).
1007 #livetable_repeatParams("($column like ? or $column is null)", " $joinOperator ", $valueCount, $paramPrefix,
1008 $paramOffset)
1009 #elseif ($isList)
1010 #livetable_repeatParams("? in elements($column)", " $joinOperator ", $valueCount, $paramPrefix, $paramOffset)
1011 #elseif ($valueCount > 1 && $joinOperator == 'OR')
1012 $column in (#livetable_repeatParams('?', ', ', $valueCount, $paramPrefix, $paramOffset))
1013 #else
1014 #livetable_repeatParams("$column = ?", ' AND ', $valueCount, $paramPrefix, $paramOffset)
1015 #end
1016 #end
1017
1018 #macro (livetable_repeatParams $str $separator $valueCount $paramPrefix $paramOffset)
1019 #if ($valueCount > 0)
1020 #foreach ($count in [1..$valueCount])
1021 #if ($count > 1)
1022 $separator##
1023 #end
1024 #if ($paramPrefix)
1025 $str.replace('?', ":${paramPrefix}${mathtool.add($paramOffset, $foreach.index)}")##
1026 #else
1027 $str##
1028 #end
1029 #end
1030 #end
1031 #end
1032
1033 #macro (livetable_addFilterParam $filterValue $matchType $params $paramName)
1034 #if ($matchType == 'partial')
1035 #if ($params.entrySet())
1036 #set ($discard = $params.put($paramName, "%$!filterValue%"))
1037 #else
1038 #set ($discard = $params.add("%$!filterValue%"))
1039 #end
1040 #elseif ($matchType == 'prefix')
1041 #if ($params.entrySet())
1042 #set ($discard = $params.put($paramName, "$!filterValue%"))
1043 #else
1044 #set ($discard = $params.add("$!filterValue%"))
1045 #end
1046 #elseif ($matchType == 'empty')
1047 #if ($params.entrySet())
1048 #set ($discard = $params.put($paramName, ''))
1049 #else
1050 #set ($discard = $params.add(''))
1051 #end
1052 #else
1053 #if ($params.entrySet())
1054 #set ($discard = $params.put($paramName, $filterValue))
1055 #else
1056 #set ($discard = $params.add($filterValue))
1057 #end
1058 #end
1059 #end
1060
1061
1062 #**
1063 * Old alias of the #livetable_addColumnToQuery macro.
1064 * @deprecated since 2.2.3, use {@link #livetable_addColumnToQuery}
1065 *#
1066 #macro(grid_addcolumn $colname)
1067 #livetable_addColumnToQuery($colname)
1068 #end
1069
1070 #**
1071 * Generates a valid SQL table alias for the specified live table column.
1072 *#
1073 #macro (livetable_getTableAlias $columnName)
1074 #set ($prefix = 'doc.')
1075 #if ($columnName.startsWith($prefix))
1076 #set ($suffix = $stringtool.removeStart($columnName, $prefix))
1077 #else
1078 ## Force a prefix to avoid the cases when the column name is a reserved SQL keyword.
1079 #set ($prefix = 'prop_')
1080 #set ($suffix = $columnName)
1081 #end
1082 ## Remove non-word characters.
1083 #set ($safe_tableAlias = "$prefix$suffix.replaceAll('\W', '')")
1084 #end
1085 {{/velocity}}