Wiki-Quellcode von Makros für benutzerspezifisch konfigurierbare Bereiche
Zuletzt geändert von Daniel Herrmann am 2026/02/07 23:23
Zeige letzte Bearbeiter
| author | version | line-number | content |
|---|---|---|---|
| 1 | {{velocity output="false"}} | ||
| 2 | ## Constants: | ||
| 3 | #set($redirectParameter = 'xredirect') | ||
| 4 | #set($nameOfThisDocument = 'XWiki.ConfigurableClass') | ||
| 5 | |||
| 6 | |||
| 7 | #** | ||
| 8 | * Try to determine whether a document was edited by a user who has edit right on this page. This is tricky because | ||
| 9 | * documents are imported with the name XWiki.XWikiGuest who has no access to anything after import. | ||
| 10 | * | ||
| 11 | * @param theDoc - Document who's editor should be checked for edit access on this document. | ||
| 12 | *# | ||
| 13 | #macro(checkDocumentSavedByAuthorizedUser, $docToCheck, $currentDoc, $hasAccess) | ||
| 14 | ## The system is started and the only user is XWikiGuest who has admin right but gives it up when he imports the default | ||
| 15 | ## documents, we are checking to see if this looks like the guest imported the document with the first import. | ||
| 16 | #if($docToCheck.getWiki() == $xcontext.getMainWikiName() | ||
| 17 | && $docToCheck.getVersion() == '1.1' | ||
| 18 | && $docToCheck.getCreator() != $docToCheck.getContentAuthor() | ||
| 19 | && $docToCheck.getContentAuthor() == 'XWiki.XWikiGuest') | ||
| 20 | ## | ||
| 21 | #set($userToCheck = $docToCheck.getCreator()) | ||
| 22 | #else | ||
| 23 | #set($userToCheck = $docToCheck.getAuthor()) | ||
| 24 | #end | ||
| 25 | #set ($hasAccess = $NULL) | ||
| 26 | #setVariable ("$hasAccess" $xwiki.hasAccessLevel('edit', $userToCheck, $currentDoc)) | ||
| 27 | #end | ||
| 28 | |||
| 29 | |||
| 30 | #** | ||
| 31 | * Find names of documents which contain objects of the class 'XWiki.ConfigurableClass' | ||
| 32 | * | ||
| 33 | * @param $section - String - Look for apps which specify that they should be configured in this section, | ||
| 34 | * if null or "" then returns them for all sections. | ||
| 35 | * @param $globaladmin - boolean - If true then we will look for applications which should be configured globally. | ||
| 36 | * @param $space - String - If not looking for apps which are configured globally, then this is the space where we | ||
| 37 | * will look for apps in. If null or "" or if $globaladmin is true, then all spaces will be | ||
| 38 | * searched. | ||
| 39 | * @param $outputList - List - The returns from this macro will be put in this list, passing the list as a parameter | ||
| 40 | * a safety measure because macros can't return values. | ||
| 41 | *# | ||
| 42 | #macro(findNamesOfAppsToConfigure, $section, $globaladmin, $space, $outputList) | ||
| 43 | ## We keep looking in the old configureGlobally property since we do not provide a migration for it: | ||
| 44 | ## this choice has been made to avoid any problem during the upgrade of old wikis. | ||
| 45 | ## And we are doing this look in a separate query for performance reason: adding a or statement is very bad | ||
| 46 | ## in terms of performance. This all should be improved in the future with proper caching. | ||
| 47 | ## | ||
| 48 | #set ($fromClauseWithoutGlobal = [ | ||
| 49 | 'BaseObject as obj', | ||
| 50 | 'StringProperty as category', | ||
| 51 | 'IntegerProperty as sectionOrder' | ||
| 52 | ]) | ||
| 53 | ## | ||
| 54 | #set ($fromClause = []) | ||
| 55 | #set ($discard = $fromClause.addAll($fromClauseWithoutGlobal)) | ||
| 56 | #set ($discard = $fromClause.add('StringProperty as global')) | ||
| 57 | ## | ||
| 58 | #set ($fromClauseDeprecated = []) | ||
| 59 | #set ($discard = $fromClauseDeprecated.addAll($fromClauseWithoutGlobal)) | ||
| 60 | #set ($discard = $fromClauseDeprecated.add('IntegerProperty as deprecatedGlobal')) | ||
| 61 | ## | ||
| 62 | #set ($whereClauseWithoutGlobal = [ | ||
| 63 | "obj.name = doc.fullName and obj.className = :className", | ||
| 64 | "category.id = obj.id and category.name = 'displayInCategory'", | ||
| 65 | "sectionOrder.id = obj.id and sectionOrder.name = 'sectionOrder'" | ||
| 66 | ]) | ||
| 67 | ## | ||
| 68 | #set ($whereClause = []) | ||
| 69 | #set ($discard = $whereClause.addAll($whereClauseWithoutGlobal)) | ||
| 70 | #set ($discard = $whereClause.add("global.id = obj.id and global.name = 'scope' and global.value in (:global)")) | ||
| 71 | ## | ||
| 72 | #set ($whereClauseDeprecated = []) | ||
| 73 | #set ($discard = $whereClauseDeprecated.addAll($whereClauseWithoutGlobal)) | ||
| 74 | #set ($discard = $whereClauseDeprecated.add("deprecatedGlobal.id = obj.id and deprecatedGlobal.name = 'configureGlobally' and deprecatedGlobal.value = :deprecatedGlobal")) | ||
| 75 | ## | ||
| 76 | #set ($params = {'className' : $nameOfThisDocument}) | ||
| 77 | ## Order by category and section priority, leaving empty/null category at the end, because we want to read the | ||
| 78 | ## category meta data from the first (most important) section in each category. | ||
| 79 | #set ($orderByClause = [ | ||
| 80 | "case when category.value is null or category.value = '' then 1 else 0 end", | ||
| 81 | 'category.value', | ||
| 82 | 'sectionOrder.value' | ||
| 83 | ]) | ||
| 84 | #if ($globaladmin == true) | ||
| 85 | #set ($discard = $params.put('deprecatedGlobal', 1)) | ||
| 86 | #set ($discard = $params.put('global', ['WIKI','WIKI+ALL_SPACES'])) | ||
| 87 | #else | ||
| 88 | #set ($discard = $params.put('deprecatedGlobal', 0)) | ||
| 89 | #if ("$!space" != '') | ||
| 90 | #set ($discard = $params.put('global', ['SPACE'])) | ||
| 91 | #set ($discard = $whereClause.add('doc.space = :space')) | ||
| 92 | #set ($discard = $params.put('space', $space)) | ||
| 93 | #else | ||
| 94 | #set ($discard = $params.put('global', ['ALL_SPACES','WIKI+ALL_SPACES'])) | ||
| 95 | #end | ||
| 96 | #end | ||
| 97 | #if ("$!section" != '') | ||
| 98 | #set ($discard = $fromClause.add('StringProperty as section')) | ||
| 99 | #set ($discard = $fromClauseDeprecated.add('StringProperty as section')) | ||
| 100 | #set ($discard = $whereClause.add("section.id = obj.id and section.name = 'displayInSection' and section.value = :section")) | ||
| 101 | #set ($discard = $whereClauseDeprecated.add("section.id = obj.id and section.name = 'displayInSection' and section.value = :section")) | ||
| 102 | #set ($discard = $params.put('section', $section)) | ||
| 103 | #end | ||
| 104 | #set ($statement = ', ' + $stringtool.join($fromClause, ', ') + | ||
| 105 | ' where ' + $stringtool.join($whereClause, ' and ') + | ||
| 106 | ' order by ' + $stringtool.join($orderByClause, ', ')) | ||
| 107 | #set ($statementDeprecated = ', ' + $stringtool.join($fromClauseDeprecated, ', ') + | ||
| 108 | ' where ' + $stringtool.join($whereClauseDeprecated, ' and ') + | ||
| 109 | ' order by ' + $stringtool.join($orderByClause, ', ')) | ||
| 110 | ## | ||
| 111 | #set ($deprecatedParams = {}) | ||
| 112 | #set ($discard = $deprecatedParams.putAll($params)) | ||
| 113 | #set ($discard = $deprecatedParams.remove('global')) | ||
| 114 | #set ($discard = $deprecatedParams.remove('space')) | ||
| 115 | #set ($discard = $params.remove('deprecatedGlobal')) | ||
| 116 | ## | ||
| 117 | ## We can't remove duplicates using the unique filter because the select clause will be extended with the information | ||
| 118 | ## needed by the order by clause. Thus we remove the duplicates after we get the results. | ||
| 119 | #set ($orderedSetOfAppNames = $collectiontool.orderedSet) | ||
| 120 | #set ($discard = $orderedSetOfAppNames.addAll($services.query.hql($statement).bindValues($params).execute())) | ||
| 121 | #set ($discard = $orderedSetOfAppNames.addAll($services.query.hql($statementDeprecated).bindValues($deprecatedParams).execute())) | ||
| 122 | #set ($discard = $outputList.addAll($orderedSetOfAppNames)) | ||
| 123 | #if ($globaladmin == false && "$!space" != '') | ||
| 124 | ## If we are looking for the apps of a specific space, we should also get the one configured for all spaces. | ||
| 125 | ## Note that we're doing that call at the end to avoid polluting the different velocity variables during the | ||
| 126 | ## execution. | ||
| 127 | #findNamesOfAppsToConfigure($section, false, '', $outputList) | ||
| 128 | #end | ||
| 129 | #end | ||
| 130 | |||
| 131 | #** | ||
| 132 | * Utility for findCustomSectionsToConfigure to get an adminMenu. | ||
| 133 | * | ||
| 134 | * @param appNames (List of Strings) Name of applications to build the adminMenu from | ||
| 135 | * @param adminMenu the pre-filled content for the administration menu (macro is non-destructive on this parameter) | ||
| 136 | * @param outputList | ||
| 137 | *# | ||
| 138 | #macro(_buildAdminMenuFromNameOfApps, $appNames, $adminMenu, $outputList) | ||
| 139 | ## We start by copying what's already in the adminMenu. | ||
| 140 | ## We can't use addAll directly because we want to make a deep copy of the structure | ||
| 141 | #set ($discard = $outputList.addAll($jsontool.fromString($jsontool.serialize($adminMenu)))) | ||
| 142 | ## Reset the category and section helper hashmaps so that they point to $outputlist and not $adminMenu anymore. | ||
| 143 | #set ($categoriesByName = {}) | ||
| 144 | #set ($sectionsByName = {}) | ||
| 145 | #foreach ($category in $outputList) | ||
| 146 | #set ($discard = $categoriesByName.put($category.id, $category)) | ||
| 147 | #foreach ($section in $category.children) | ||
| 148 | #set ($discard = $sectionsByName.put($section.id, $section)) | ||
| 149 | #end | ||
| 150 | #end | ||
| 151 | ## The $query variable is used as a basis for the URL used in sections. (search for ${query} ) | ||
| 152 | #set ($query = "editor=$escapetool.url(${editor})") | ||
| 153 | #if ($editor != 'globaladmin') | ||
| 154 | #set ($query = $query + "&space=$escapetool.url(${currentSpace})") | ||
| 155 | #end | ||
| 156 | #foreach ($appName in $appNames) | ||
| 157 | ## | ||
| 158 | ## Get the configurable application | ||
| 159 | #set ($app = $xwiki.getDocument($appName)) | ||
| 160 | ## | ||
| 161 | ## If getDocument returns null, then warn the user that they don't have view access to that application. | ||
| 162 | #if (!$app) | ||
| 163 | #set ($discard = $appsUserCannotView.add($appName)) | ||
| 164 | #end | ||
| 165 | ## | ||
| 166 | #foreach ($configurableObject in $app.getObjects($nameOfThisDocument)) | ||
| 167 | #set ($displayInCategory = $app.getValue('displayInCategory', $configurableObject)) | ||
| 168 | ## It's OK to not specify a category, in which case the app will be added to a default one (i.e. "other"). | ||
| 169 | #if ("$!displayInCategory" != '') | ||
| 170 | #if ($categoriesByName.containsKey($displayInCategory)) | ||
| 171 | ## Add a new section in this category. | ||
| 172 | #set ($appCategory = $categoriesByName.get($displayInCategory)) | ||
| 173 | #else | ||
| 174 | ## Add a new category. | ||
| 175 | #set ($key = "admin.$displayInCategory.toLowerCase()") | ||
| 176 | #set ($name = $displayInCategory) | ||
| 177 | #if ($services.localization.get($key)) | ||
| 178 | #set ($name = $services.localization.render($key)) | ||
| 179 | #end | ||
| 180 | #set ($appCategory= { | ||
| 181 | 'id': $displayInCategory, | ||
| 182 | 'name': $name, | ||
| 183 | 'children': [] | ||
| 184 | }) | ||
| 185 | #set ($discard = $categoriesByName.put($displayInCategory, $appCategory)) | ||
| 186 | ## Insert the category at the end for now. We'll sort the categories after we add all of them. | ||
| 187 | #set ($discard = $outputList.add($appCategory)) | ||
| 188 | #end | ||
| 189 | #set ($categoryIcon = $app.getValue('categoryIcon', $configurableObject)) | ||
| 190 | #if ("$!categoryIcon" != '') | ||
| 191 | #set ($appCategory.icon = $categoryIcon) | ||
| 192 | #end | ||
| 193 | #set ($displayBeforeCategory = $app.getValue('displayBeforeCategory', $configurableObject)) | ||
| 194 | #if ("$!displayBeforeCategory" != '') | ||
| 195 | #set ($appCategory.displayBeforeCategory = $displayBeforeCategory) | ||
| 196 | #end | ||
| 197 | #else | ||
| 198 | #set ($appCategory = $categoriesByName.get('other')) | ||
| 199 | #end | ||
| 200 | ## | ||
| 201 | #set ($displayInSection = $app.getValue('displayInSection', $configurableObject)) | ||
| 202 | ## | ||
| 203 | ## If there is no section specified in the object, just skip it | ||
| 204 | #if ("$!displayInSection" == '') | ||
| 205 | ## Skip, bad object | ||
| 206 | ## If there is no section for this configurable or if the section cannot be edited, then check if the | ||
| 207 | ## application can be edited by the current user, if so then we display the icon from the current app and | ||
| 208 | ## don't display any message to tell the user they can't edit that section. | ||
| 209 | #elseif ($sectionsByName.containsKey($displayInSection)) | ||
| 210 | #set ($appSection = $sectionsByName.get($displayInSection)) | ||
| 211 | #set ($appSection.configurable = true) | ||
| 212 | #set ($newSection = false) | ||
| 213 | #else | ||
| 214 | ## | ||
| 215 | ## If there is no section for this configurable, then we will have to add one. | ||
| 216 | #set ($key = "admin.$displayInSection.toLowerCase()") | ||
| 217 | #set ($name = $displayInSection) | ||
| 218 | #if ($services.localization.get($key)) | ||
| 219 | #set ($name = $services.localization.render($key)) | ||
| 220 | #end | ||
| 221 | #set ($appSection = { | ||
| 222 | 'id': $displayInSection, | ||
| 223 | 'name': $name, | ||
| 224 | 'url': $xwiki.getURL($currentDoc, $adminAction, "${query}§ion=$escapetool.url($displayInSection)"), | ||
| 225 | 'configurable': true, | ||
| 226 | 'order': $configurableObject.getValue('sectionOrder') | ||
| 227 | }) | ||
| 228 | #if ($app.getValue('configureGlobally', $configurableObject) != 1) | ||
| 229 | #set ($appSection.perSpace = true) | ||
| 230 | #end | ||
| 231 | #set ($key = "admin.${displayInSection.toLowerCase()}.description") | ||
| 232 | #if ($services.localization.get($key)) | ||
| 233 | #set ($appSection.description = $services.localization.render($key)) | ||
| 234 | #end | ||
| 235 | #set ($discard = $sectionsByName.put($displayInSection, $appSection)) | ||
| 236 | #set ($discard = $appCategory.children.add($appSection)) | ||
| 237 | #set ($newSection = true) | ||
| 238 | #end | ||
| 239 | ## | ||
| 240 | ## If an attachment by the filename iconAttachment exists and is an image | ||
| 241 | #set ($attachment = $app.getAttachment("$!app.getValue('iconAttachment', $configurableObject)")) | ||
| 242 | #if ($attachment && $attachment.isImage()) | ||
| 243 | ## Set the icon for this section as the attachment URL. | ||
| 244 | #set ($appSection.iconReference = "${appName}@$attachment.getFilename()") | ||
| 245 | #elseif (!$appSection.iconReference) | ||
| 246 | #set ($appSection.iconReference = 'XWiki.ConfigurableClass@DefaultAdminSectionIcon.png') | ||
| 247 | #end | ||
| 248 | ## | ||
| 249 | ## If the user doesn't have edit access to the application, we want to show a message on the icon | ||
| 250 | #if (!$xcontext.hasAccessLevel('edit', $app.getFullName()) && $newSection) | ||
| 251 | #set ($appSection.readOnly = true) | ||
| 252 | #elseif ($xcontext.hasAccessLevel('edit', $app.getFullName()) && $appSection.readOnly) | ||
| 253 | #set ($appSection.readOnly = false) | ||
| 254 | #end | ||
| 255 | #end## Foreach configurable object in this app. | ||
| 256 | #end## Foreach application which is configurable. | ||
| 257 | #end | ||
| 258 | |||
| 259 | #** | ||
| 260 | * Computes the scores of categories and sorts the list. The first category will be the one with the highest score. | ||
| 261 | * | ||
| 262 | * @param completeOrderMenu the list of categories, containing all of the necessary ordering information; | ||
| 263 | * This macro is destructive on $completeOrderMenu. | ||
| 264 | *# | ||
| 265 | #macro(_computeScores, $completeOrderMenu) | ||
| 266 | ## Initialize scores | ||
| 267 | #foreach ($category in $completeOrderMenu) | ||
| 268 | #set ($category.score = 0) | ||
| 269 | #end | ||
| 270 | ## Compute scores | ||
| 271 | #foreach ($round in [1..$completeOrderMenu.size()]) | ||
| 272 | #set ($scoreChanged = false) | ||
| 273 | #foreach ($category in $completeOrderMenu) | ||
| 274 | #if ($category.displayBeforeCategory) | ||
| 275 | #set ($newScore = $categoriesByName.get($category.displayBeforeCategory).score + 1) | ||
| 276 | #if ($newScore && $newScore > $category.score) | ||
| 277 | #set ($category.score = $newScore) | ||
| 278 | #set ($scoreChanged = true) | ||
| 279 | #end | ||
| 280 | #end | ||
| 281 | #end | ||
| 282 | #if (!$scoreChanged) | ||
| 283 | #break | ||
| 284 | #end | ||
| 285 | #end | ||
| 286 | #end | ||
| 287 | |||
| 288 | #** | ||
| 289 | * Utility macro to compute the full order of categories, which is contained sparsely through xobjects | ||
| 290 | * that are not always retrieved when building the menu. | ||
| 291 | * | ||
| 292 | * @param adminMenu the list of categories to use in the menu (macro is non-destructive on this parameter) | ||
| 293 | * @param outputList the full list of categories, in order. Even categories without any child will be returned. | ||
| 294 | *# | ||
| 295 | #macro(_getCategoriesInOrder, $adminMenu, $outputList) | ||
| 296 | #set ($params = { | ||
| 297 | 'className' : $nameOfThisDocument, | ||
| 298 | 'global': ['WIKI','WIKI+ALL_SPACES'] | ||
| 299 | }) | ||
| 300 | #set ($appNames = []) | ||
| 301 | #set ($discard = $appNames.addAll($services.query.hql($statement).bindValues($params).execute())) | ||
| 302 | ## $completeOrderMenu contains a menu that contains all the information needed for sorting, no matter what. | ||
| 303 | #set ($completeOrderMenu = []) | ||
| 304 | #_buildAdminMenuFromNameOfApps($appNames, $adminMenu, $completeOrderMenu) | ||
| 305 | ## We use $completeOrderMenu to set the score of each category. | ||
| 306 | #_computeScores($completeOrderMenu) | ||
| 307 | #set ($completeOrderMenu = $collectiontool.sort($completeOrderMenu, 'score:desc')) | ||
| 308 | ## Write the now sorted $completeOrderMenu to the output variable. | ||
| 309 | #set ($discard = $outputList.addAll($completeOrderMenu)) | ||
| 310 | #end | ||
| 311 | |||
| 312 | #** | ||
| 313 | * Augment the $adminMenu variable with all $nameOfThisDocument (i.e. XWiki.ConfigurableClass) | ||
| 314 | * XObjects found on this wiki. | ||
| 315 | * | ||
| 316 | * @param adminMenu the basis of the menu on which to add on (the macro makes hard to reverse changes on $adminMenu) | ||
| 317 | *# | ||
| 318 | #macro(findCustomSectionsToConfigure $adminMenu) | ||
| 319 | #set ($appNames = []) | ||
| 320 | #set ($global = ($editor == 'globaladmin')) | ||
| 321 | #findNamesOfAppsToConfigure('', $global, $currentSpace, $appNames) | ||
| 322 | ## | ||
| 323 | ## $completedAdminMenu contains most of the info needed to build the administration menu. | ||
| 324 | #set ($completedAdminMenu = []) | ||
| 325 | #_buildAdminMenuFromNameOfApps($appNames, $adminMenu, $completedAdminMenu) | ||
| 326 | #set ($adminMenu = []) | ||
| 327 | #set ($discard = $adminMenu.addAll($completedAdminMenu)) | ||
| 328 | ## Sort the categories | ||
| 329 | ## The information in $adminMenu is sometimes not enough to create a full order | ||
| 330 | ## in those case we need to retrieve more information to build the full order. | ||
| 331 | ## $categoriesFullOrder contains all the categories and their associated ordering information | ||
| 332 | #if (!$global) | ||
| 333 | #set ($categoriesFullOrder = []) | ||
| 334 | #_getCategoriesInOrder($adminMenu, $categoriesFullOrder) | ||
| 335 | ## Once it's retrieved, we use this total order on the categories in contained $adminMenu. | ||
| 336 | #set ($sortedAdminMenu = []) | ||
| 337 | #foreach ($orderedCategory in $categoriesFullOrder) | ||
| 338 | #foreach ($menuCategory in $adminMenu) | ||
| 339 | #if ($orderedCategory.id == $menuCategory.id) | ||
| 340 | #set ($discard = $sortedAdminMenu.add($menuCategory)) | ||
| 341 | #break | ||
| 342 | #end | ||
| 343 | #end | ||
| 344 | #end | ||
| 345 | #set ($adminMenu = []) | ||
| 346 | #set ($discard = $adminMenu.addAll($sortedAdminMenu)) | ||
| 347 | #else | ||
| 348 | ## We're in the case where all the categories and sections are already in $adminMenu | ||
| 349 | ## We can easily figure out an order | ||
| 350 | #_computeScores($adminMenu) | ||
| 351 | #set ($adminMenu = $collectiontool.sort($adminMenu, 'score:desc')) | ||
| 352 | #end | ||
| 353 | #end | ||
| 354 | |||
| 355 | |||
| 356 | #** | ||
| 357 | * Show the heading for configuration for a given application. | ||
| 358 | * | ||
| 359 | * @param appName (String) Name of the application to show configuration heading for. | ||
| 360 | * @param headingAlreadyShowing (boolean) If true then we don't make another heading. Otherwise it is set to true. | ||
| 361 | *# | ||
| 362 | #macro(showHeading, $appName, $headingAlreadyShowing) | ||
| 363 | #if(!$headingAlreadyShowing) | ||
| 364 | #set($headingAlreadyShowing = true) | ||
| 365 | #set($escapedAppName = $services.rendering.escape($appName, 'xwiki/2.1')) | ||
| 366 | #set($doubleEscapedAppName = $services.rendering.escape($escapedAppName, 'xwiki/2.1')) | ||
| 367 | |||
| 368 | == {{translation key="admin.customize"/}} [[$doubleEscapedAppName>>$escapedAppName]]: == | ||
| 369 | #end | ||
| 370 | #end | ||
| 371 | |||
| 372 | #define($formHtml) | ||
| 373 | #if ($objClass.getPropertyNames().size() > 0) | ||
| 374 | <dl> | ||
| 375 | #foreach($propName in $objClass.getPropertyNames()) | ||
| 376 | #if($propertiesToShow.size() > 0 && !$propertiesToShow.contains($propName)) | ||
| 377 | ## Silently skip over this property. | ||
| 378 | #else | ||
| 379 | #set($hintKey = "${obj.xWikiClass.name}_${propName}.hint") | ||
| 380 | #set($hint = $services.localization.render($hintKey)) | ||
| 381 | #if($hint == $hintKey) | ||
| 382 | #set($hint = $NULL) | ||
| 383 | #end | ||
| 384 | ## | ||
| 385 | ## Further processing of the field display HTML is needed. | ||
| 386 | ## Step 1: Strip <pre> tags which $obj.display inserts, this won't affect content because it's escaped. | ||
| 387 | #set($out = $obj.display($propName, 'edit').replaceAll('<[/]?+pre>', '')) | ||
| 388 | ## Step 2: Select only content between first < and last > because $obj.display inserts html macros. | ||
| 389 | ## Careful not to remove html macros from the content because they are not escaped! | ||
| 390 | #set ($out = $out.substring($out.indexOf('<'), $mathtool.add(1, $out.lastIndexOf('>')))) | ||
| 391 | ## Step 3: Prepend app name to all ID and FOR attributes to prevent id collision with multiple apps on one page. | ||
| 392 | #set ($oldId = "$objClass.getName()_$obj.getNumber()_$propName") | ||
| 393 | #set ($newId = "${escapedAppName}_$oldId") | ||
| 394 | #set ($out = $out.replaceAll(" (id|for)=('|"")$regextool.quote($oldId)", | ||
| 395 | " ${escapetool.d}1=${escapetool.d}2$regextool.quoteReplacement($newId)")) | ||
| 396 | ## App Name is prepended to for= to make label work with id which is modified to prevent collisions. | ||
| 397 | <dt><label#if ($out.matches("(?s).*id=['""]${newId}['""].*")) for="${newId}"#end>## | ||
| 398 | #if ($out.indexOf('type=''checkbox''') != -1 && $out.indexOf('class="xwiki-form-listclass"') == -1) | ||
| 399 | $out ## | ||
| 400 | #set ($out = '') | ||
| 401 | #end | ||
| 402 | $escapetool.xml($app.displayPrettyName($propName, $obj)) | ||
| 403 | </label> | ||
| 404 | #if($linkPrefix != '') | ||
| 405 | <a href="$escapetool.xml($linkPrefix + $propName)" class="xHelp" title="$services.localization.render('admin.documentation')">$services.localization.render('admin.documentation')</a> | ||
| 406 | #end | ||
| 407 | #if ($hint) | ||
| 408 | <span class="xHint">$hint</span> | ||
| 409 | #end | ||
| 410 | </dt> | ||
| 411 | #if ($out != '') | ||
| 412 | <dd>$out</dd> | ||
| 413 | #else | ||
| 414 | ## We always display a dd element to avoid having a last dt element alone, which would lead to an invalid html. | ||
| 415 | <dd class="hidden">$out</dd> | ||
| 416 | #end | ||
| 417 | #end## If property is in propertiesToShow | ||
| 418 | #end## Foreach property in this class | ||
| 419 | </dl> | ||
| 420 | #end | ||
| 421 | #end## define $formHtml | ||
| 422 | {{/velocity}} |