Wiki source code of Livetable Results Macros
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 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§ion=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}} |