Modifications pour le document Macros de résultats Livetable
Modifié par Florent Charton le 2026/03/13 11:03
Depuis la version 1.1
modifié par superadmin
sur 2022/06/20 08:48
sur 2022/06/20 08:48
Commentaire de modification :
Install extension [org.xwiki.platform:xwiki-platform-livetable-ui/13.10.6]
À la version 6.1
modifié par Florent Charton
sur 2026/03/13 11:03
sur 2026/03/13 11:03
Commentaire de modification :
Install extension [org.xwiki.platform:xwiki-platform-livetable-ui/17.10.4]
Résumé
-
Propriétés de la Page (2 modifications, 0 ajouts, 0 suppressions)
Détails
- Propriétés de la Page
-
- Auteur du document
-
... ... @@ -1,1 +1,1 @@ 1 -XWiki. superadmin1 +xwiki:XWiki.fcharton - Contenu
-
... ... @@ -35,7 +35,19 @@ 35 35 ## 36 36 #set($tablelist = []) 37 37 #foreach($colname in $collist) 38 - #livetable_addColumnToQuery($colname) 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 + #livetable_getPropertyClassAndType($colname) 41 + #if ($propType != '') 42 + #set ($isPasswordType = $propClass.get($colname).classType == 'Password') 43 + #set ($isEmailType = $propClass.get($colname).classType == 'Email') 44 + #set ($emailObfuscated = $services.mail.general.shouldObfuscate()) 45 + #if (!($isPasswordType || ($isEmailType && $emailObfuscated))) 46 + #livetable_addColumnToQuery($colname) 47 + #end 48 + #else 49 + #livetable_addColumnToQuery($colname) 50 + #end 39 39 #end 40 40 ## 41 41 ## Tag filtering ... ... @@ -89,6 +89,8 @@ 89 89 #set($order = "$!request.sort") 90 90 #if ($order == 'doc.location') 91 91 #set ($order = 'doc.fullName') 104 + #elseif ($order == 'email' && $services.mail.general.shouldObfuscate()) 105 + #set ($order = '') 92 92 #end 93 93 #set ($orderSql = '') 94 94 #if($order != '') ... ... @@ -263,7 +263,13 @@ 263 263 ## 264 264 ## TagCloud matching all the documents used by the live table 265 265 ## 266 - #set($allMatchingTags = $xwiki.tag.getTagCountForQuery($allMatchingTagsFrom, $allMatchingTagsWhere, $allMatchingParams)) 280 + ## If all the query parameters are the same as for $tagsMatchingFilters, no need to run the query again. 281 + ## This optimization allows to divide the time to compute the tagcloud by 2 when the table has no filters applied. 282 + #if ($allMatchingTagsFrom.trim() != $tagsMatchingFiltersFrom.trim() || $allMatchingTagsWhere.trim() != $tagsMatchingFiltersWhere.trim() || $tagsMatchingParams != $allMatchingParams) 283 + #set($allMatchingTags = $xwiki.tag.getTagCountForQuery($allMatchingTagsFrom, $allMatchingTagsWhere, $allMatchingParams)) 284 + #else 285 + #set($allMatchingTags = $tagsMatchingFilters) 286 + #end 267 267 ## FIXME: We use a list of maps just because the client expects an array, but we should simply return $allMatchingTags.. 268 268 #set($tags = []) 269 269 #foreach($tag in $allMatchingTags.keySet()) ... ... @@ -286,10 +286,7 @@ 286 286 #if(!$offset || $offset < 0) 287 287 #set($offset = 0) 288 288 #end 289 - #set($limit = $numbertool.toNumber($request.get('limit')).intValue()) 290 - #if(!$limit) 291 - #set ($limit = 15) 292 - #end 309 + #getAndValidateQueryLimitFromRequest('limit', 15, $limit) 293 293 #set($query = $services.query.hql($sql)) 294 294 ## Apply query filters if defined. Otherwise use default. 295 295 #foreach ($queryFilter in $stringtool.split($!request.queryFilters, ', ')) ... ... @@ -298,7 +298,12 @@ 298 298 #set ($query = $query.setLimit($limit).setOffset($offset).bindValues($sqlParams)) 299 299 #set($items = $query.execute()) 300 300 #set($discard = $map.put('totalrows', $query.count())) 301 - #set($discard = $map.put('returnedrows', $mathtool.min($items.size(), $limit))) 318 + #if ($limit > 0) 319 + #set($discard = $map.put('returnedrows', $mathtool.min($items.size(), $limit))) 320 + #else 321 + ## When the limit is 0, it's equivalent to no limit at all and the actual number of returned results can be used. 322 + #set($discard = $map.put('returnedrows', $items.size())) 323 + #end 302 302 #set($discard = $map.put('offset', $mathtool.add($offset, 1))) 303 303 #set($rows = []) 304 304 #foreach($item in $items) ... ... @@ -377,7 +377,6 @@ 377 377 #set($discard = $itemDoc.use($className)) 378 378 #set($discard = $row.put('doc_objectCount', $itemDoc.getObjectNumbers($className))) 379 379 #set($discard = $row.put('doc_edit_url', $itemDoc.getURL($itemDoc.defaultEditMode))) 380 - #set($discard = $row.put('doc_author_url', $xwiki.getURL($translatedDoc.author))) 381 381 #set($discard = $row.put('doc_date', $xwiki.formatDate($translatedDoc.date))) 382 382 #set($discard = $row.put('doc_title', $translatedDoc.plainTitle)) 383 383 #set($rawTitle = $translatedDoc.title) ... ... @@ -384,7 +384,15 @@ 384 384 #if($rawTitle != $row['doc_title']) 385 385 #set($discard = $row.put('doc_title_raw', $rawTitle)) 386 386 #end 387 - #set($discard = $row.put('doc_author', $xwiki.getPlainUserName($translatedDoc.authorReference))) 408 + #set ($metadataAuthor = $translatedDoc.authors.originalMetadataAuthor) 409 + #if ($metadataAuthor == $services.user.getGuestUserReference()) 410 + ## Special handling for guest so that it displays unknown user. 411 + #set($discard = $row.put('doc_author', $xwiki.getPlainUserName($NULL))) 412 + #else 413 + #set($discard = $row.put('doc_author', $xwiki.getPlainUserName($metadataAuthor))) 414 + #end 415 + 416 + #set($discard = $row.put('doc_author_url', $xwiki.getURL($metadataAuthor))) 388 388 #set($discard = $row.put('doc_creationDate', $xwiki.formatDate($translatedDoc.creationDate))) 389 389 #set($discard = $row.put('doc_creator', $xwiki.getPlainUserName($translatedDoc.creatorReference))) 390 390 #set($discard = $row.put('doc_hidden', $translatedDoc.isHidden())) ... ... @@ -437,10 +437,12 @@ 437 437 #set($fieldProperty = $fieldObject.getProperty($colname)) 438 438 #if ($fieldProperty.getPropertyClass().classType == 'Password') 439 439 #set($fieldValue = '********') 469 + #elseif ($fieldProperty.getPropertyClass().classType == 'Email' && $services.mail.general.shouldObfuscate()) 470 + #set ($fieldValue = $services.mail.general.obfuscate("$!fieldProperty.getValue()")) 440 440 #else 441 441 #set($fieldValue = "$!fieldProperty.getValue()") 442 442 #end 443 - #set($fieldDisplayValue = "$ !itemDoc.display($colname, 'view')")474 + #set($fieldDisplayValue = "#unwrapXPropertyDisplay($itemDoc.display($colname, 'view'))") 444 444 #if($fieldDisplayValue == '') 445 445 #set($fieldDisplayValue = $services.localization.render("${request.transprefix}emptyvalue")) 446 446 #end ... ... @@ -452,7 +452,7 @@ 452 452 #set($fieldUrl = '') 453 453 #end 454 454 #end 455 - #set($discard = $row.put($colname, $fieldDisplayValue .replaceFirst($regextool.quote('{{html clean="false" wiki="false"}}'), '').replaceAll("$regextool.quote('{{/html}}')$", '')))486 + #set($discard = $row.put($colname, $fieldDisplayValue)) 456 456 #set($discard = $row.put("${colname}_value", $fieldValue)) 457 457 #set($discard = $row.put("${colname}_url", $fieldUrl)) 458 458 ## Reset to the default class ... ... @@ -473,7 +473,9 @@ 473 473 #set($discard = $map.put('params', $sqlParams)) 474 474 #end 475 475 #set($discard = $map.put('reqNo', $numbertool.toNumber($request.reqNo).intValue())) 476 - #gridresult_buildTagCloudJSON($map) 507 + #if("$!request.tagcloud" == 'true') 508 + #gridresult_buildTagCloudJSON($map) 509 + #end 477 477 #gridresult_buildRowsJSON($map) 478 478 #end 479 479 ... ... @@ -527,8 +527,10 @@ 527 527 #elseif($propType == 'TextAreaClass' || $propType == 'UsersClass' || $propType == 'GroupsClass') 528 528 #set($tableName = 'LargeStringProperty') 529 529 #elseif($propType == 'StaticListClass' || $propType == 'DBListClass' || $propType == 'DBTreeListClass' || $propType == 'PageClass') 563 + ## The following logic is mirrored from ListClass and might need to be updated when the logic in ListClass changes. 530 530 #set($multiSelect = $propClass.get($colname).getProperty('multiSelect').getValue()) 531 531 #set($relationalStorage = $propClass.get($colname).getProperty('relationalStorage').getValue()) 566 + #set($largeStorage = $propClass.get($colname).getProperty('largeStorage').getValue()) 532 532 #if($multiSelect == 1) 533 533 #if($relationalStorage == 1) 534 534 #set($tableName = 'DBStringListProperty') ... ... @@ -535,6 +535,8 @@ 535 535 #else 536 536 #set($tableName = 'StringListProperty') 537 537 #end 573 + #elseif($largeStorage == 1) 574 + #set($tableName = 'LargeStringProperty') 538 538 #else 539 539 #set($tableName = 'StringProperty') 540 540 #end ... ... @@ -656,6 +656,19 @@ 656 656 #elseif ($colName == 'doc.date' || $colName == 'doc.creationDate' || $colName == 'doc.contentUpdateDate') 657 657 #livetable_getTableAlias($colName) 658 658 #livetable_filterDateProperty() 696 + #elseif ($colName == 'doc.hidden' || $colName == 'doc.minorEdit1' || $colName == 'doc.enforceRequiredRights') 697 + ## Boolean document fields need special handling to work across all databases 698 + ## (HSQLDB/PostgreSQL use true/false, MySQL/MariaDB/Oracle use 1/0). 699 + ## Support both true/false and 1/0 as Live Data uses the former while LiveTable uses the latter. 700 + #set ($booleanValue = ($filterValue.toLowerCase() == 'true' || $filterValue == '1')) 701 + ## No need to clean the column name since it's only one of the given values. 702 + #if ($whereParams.entrySet()) 703 + #set ($whereSql = "${whereSql} and $colName = :${colName.replace('.', '_')}_filter") 704 + #set ($discard = $whereParams.put("${colName.replace('.', '_')}_filter", $booleanValue)) 705 + #else 706 + #set ($whereSql = "${whereSql} and $colName = ?") 707 + #set ($discard = $whereParams.add($booleanValue)) 708 + #end 659 659 #else 660 660 #set ($safeColName = $colName.replaceAll('[^a-zA-Z0-9_.]', '').replace('_', '.')) 661 661 #if ($whereParams.entrySet()) ... ... @@ -848,34 +848,79 @@ 848 848 *# 849 849 #macro (livetable_filterDBStringListProperty) 850 850 ## Perform exact matching by default if no match type is specified. 851 - ## Note that for DBStringList properties we takeintoaccount onlythefirstmatch type,evenifmultiplefilter852 - ## valuesare specified. Basicallythefirst match type isused for allfilter values.901 + ## For DBStringList properties we still apply a single match type to all non-empty values, but we also allow 902 + ## combining the special "empty" match type with other match types. 853 853 #livetable_getMatchTypes($colname $filterValues.size() 'exact') 854 - #if ($matchType == 'partial' || $matchType == 'prefix') 855 - ## We need to join with the list of values in order to be able to use the LIKE operator. 856 - #set ($matchTarget = "${safe_tableAlias}_item") 857 - #if ($whereParams.entrySet()) 858 - #set ($paramPrefix = "${safe_tableAlias}_item_") 904 + #livetable_getJoinOperator($colname) 905 + 906 + ## Collect non-empty filter values (those whose match type is not 'empty'). 907 + #set ($nonEmptyValues = []) 908 + #set ($hasEmpty = false) 909 + #set ($matchType = 'invalid') 910 + #foreach ($filterValue in $filterValues) 911 + #if ($matchTypes.get($foreach.index) == 'empty') 912 + #set ($hasEmpty = true) 913 + ## When we want to match empty values, we can't have other match types than exact for non-empty values as for 914 + ## other match types, we need to join with the list of values, which is not compatible with checking for 915 + ## emptiness. 916 + #set ($matchType = 'exact') 917 + #elseif ("$!filterValue" != '') 918 + #set ($discard = $nonEmptyValues.add($filterValue)) 919 + ## Store the first non-empty match type. 920 + #if ($matchType == 'invalid') 921 + #set ($matchType = $matchTypes.get($foreach.index)) 922 + #end 923 + #end 924 + #end 925 + 926 + ## 1) Apply the non-empty constraints. 927 + #if (!$nonEmptyValues.isEmpty()) 928 + #if ($matchType == 'partial' || $matchType == 'prefix') 929 + ## We need to join with the list of values in order to be able to use the LIKE operator. 930 + #set ($matchTarget = "${safe_tableAlias}_item") 931 + #if ($whereParams.entrySet()) 932 + #set ($paramPrefix = "${safe_tableAlias}_item_") 933 + #else 934 + #set ($paramPrefix = $NULL) 935 + #end 936 + #set ($joinPos = $mathtool.add($fromSql.lastIndexOf(" $safe_tableAlias"), $mathtool.add($safe_tableAlias.length(), 1))) 937 + #set ($fromSql = "$fromSql.substring(0, $joinPos) join ${safe_tableAlias}.list as $matchTarget $fromSql.substring($joinPos)") 859 859 #else 860 - #set ($paramPrefix = $NULL) 939 + ## Fall-back on exact matching even if the match type is specified, when its value is not supported. 940 + #set ($matchType = 'exact') 941 + #set ($matchTarget = "${safe_tableAlias}.list") 942 + #if ($whereParams.entrySet()) 943 + #set ($paramPrefix = "${safe_tableAlias}_list_") 944 + #else 945 + #set ($paramPrefix = $NULL) 946 + #end 861 861 #end 862 - #set ($joinPos = $mathtool.add($fromSql.lastIndexOf(" $safe_tableAlias"), $mathtool.add($safe_tableAlias.length(), 1))) 863 - #set ($fromSql = "$fromSql.substring(0, $joinPos) join ${safe_tableAlias}.list as $matchTarget $fromSql.substring($joinPos)") 864 - #else 865 - ## Fall-back on exact matching even if the match type is specified, when its value is not supported. 866 - #set ($matchType = 'exact') 867 - #set ($matchTarget = "${safe_tableAlias}.list") 868 - #if ($whereParams.entrySet()) 869 - #set ($paramPrefix = "${safe_tableAlias}_list_") 948 + 949 + #set ($filterQuery = "#livetable_getFilterQuery($matchTarget $matchType true $nonEmptyValues.size() $paramPrefix $NULL)") 950 + #if (!$hasEmpty) 951 + ## Only non-empty values are used, combine directly with the existing constraints, otherwise, they will be 952 + ## combined later together with the empty constraint. 953 + #set ($whereSql = "$whereSql and ($filterQuery.trim())") 954 + #end 955 + #foreach ($filterValue in $nonEmptyValues) 956 + #livetable_addFilterParam($filterValue $matchType $whereParams "${paramPrefix}${foreach.count}") 957 + #end 958 + #end 959 + 960 + ## 2) Optionally add a single constraint if any match type is 'empty'. 961 + #if ($hasEmpty) 962 + ## "empty" means that there is no list item stored for this property on the filtered object. 963 + ## The proper way to check for that would be "${safe_tableAlias}.list IS EMPTY", but JSQL cannot parse "IS EMPTY" 964 + ## which means that we cannot use it without programming right. 965 + #set ($emptyConstraint = "size(${safe_tableAlias}.list) = 0") 966 + #if ($nonEmptyValues.isEmpty()) 967 + ## Only 'empty' is used, combine with the existing constraints. 968 + #set ($whereSql = "${whereSql} and ${emptyConstraint}") 870 870 #else 871 - #set ($paramPrefix = $NULL) 970 + ## Combine non-empty group and empty condition using the join operator. 971 + #set ($whereSql = "${whereSql} and ($filterQuery.trim() ${joinOperator} ${emptyConstraint})") 872 872 #end 873 873 #end 874 - #set ($filterQuery = "#livetable_getFilterQuery($matchTarget $matchType true $filterValues.size() $paramPrefix $NULL)") 875 - #set ($whereSql = "$whereSql and ($filterQuery.trim())") 876 - #foreach ($filterValue in $filterValues) 877 - #livetable_addFilterParam($filterValue $matchType $whereParams "${paramPrefix}${foreach.count}") 878 - #end 879 879 #end 880 880 881 881 ... ... @@ -970,9 +970,9 @@ 970 970 #if ($matchType == 'partial' || $matchType == 'prefix') 971 971 #livetable_repeatParams("upper($column) like upper(?)", " $joinOperator ", $valueCount, $paramPrefix, $paramOffset) 972 972 #elseif($matchType == 'empty') 973 - ## Check if the value of the column is like the empty parameter (which is often the empty string), or if the value 1068 + ## Check if the value of the column is like the empty parameter (which is often the empty string), or if the value 974 974 ## of the column is null (to be compliant with Oracle which stores the empty string as a NULL value). 975 - #livetable_repeatParams("($column like ? or $column is null)", " $joinOperator ", $valueCount, $paramPrefix, 1070 + #livetable_repeatParams("($column like ? or $column is null)", " $joinOperator ", $valueCount, $paramPrefix, 976 976 $paramOffset) 977 977 #elseif ($isList) 978 978 #livetable_repeatParams("? in elements($column)", " $joinOperator ", $valueCount, $paramPrefix, $paramOffset)