Wiki source code of Custom configurable sections
Last modified by superadmin on 2019/11/08 17:49
Show last authors
author | version | line-number | content |
---|---|---|---|
1 | {{include reference="XWiki.ConfigurableClassMacros" /}} | ||
2 | |||
3 | {{velocity}} | ||
4 | #* | ||
5 | * This part takes the configuration from any documents containing XWiki.ConfigurableClass objects and creates a form | ||
6 | * for each. To includeForm this document, you may specify: | ||
7 | * | ||
8 | * $section - String - The section which we are administrating eg: "Registration", "Users", or "Import". | ||
9 | * If none is specified then it checks for a request parameter called "section" and uses that, | ||
10 | * if no parameter, then this code assumes that it is part of the admin icons sheet and adds icons | ||
11 | * for any section which is not in $sections, in that event, this code assumes it is being run | ||
12 | * inside of a <ul> block. | ||
13 | * | ||
14 | * $sections - List<String> - If section is not specified, any sections on this list will not have icons made for them, | ||
15 | * the assumption being that the icons are already there. If section is specified then this | ||
16 | * is not taken into account and may safely be undefined. | ||
17 | * | ||
18 | * $currentDoc - String (document.fullName) - The administration document, users who don't have permission to edit | ||
19 | * it will not be able to include applications (possibly injecting | ||
20 | * arbitrary code.) if none specified then $doc.getFullName() is used. | ||
21 | * | ||
22 | * $globaladmin - boolean - If set true then we will assume we are administrationg the entire wiki. | ||
23 | * If not set then we look for a request parameter called "editor" if that exists and equals | ||
24 | * "globaladmin" then $globaladmin is true, if it doesn't exist then we check to see if | ||
25 | * $currentDoc.getFullName() equals "XWiki.XWikiPreferences". | ||
26 | *### | ||
27 | ## | ||
28 | ## Form submission depends on this. | ||
29 | $xwiki.jsfx.use('js/xwiki/actionbuttons/actionButtons.js', true) | ||
30 | ## In case of conflict issue we want to display the diff properly | ||
31 | #set ($discard = $xwiki.ssfx.use('uicomponents/viewers/diff.css', true)) | ||
32 | #set ($discard = $xwiki.jsfx.use('uicomponents/viewers/diff.js')) | ||
33 | ## | ||
34 | #if(!$section) | ||
35 | #set($section = $request.getParameter('section')) | ||
36 | #end | ||
37 | #if(!$currentDoc) | ||
38 | #set($currentDoc = $doc.getFullName()) | ||
39 | #end | ||
40 | ## Get value of $globaladmin if not specified. | ||
41 | #if("$!globaladmin" == '') | ||
42 | #if($editor != 'globaladmin' | ||
43 | && $request.getParameter('editor') != 'globaladmin' | ||
44 | && $currentDoc != 'XWiki.XWikiPreferences') | ||
45 | ## | ||
46 | #set($globaladmin = false) | ||
47 | #else | ||
48 | #set($globaladmin = true) | ||
49 | #end | ||
50 | #end | ||
51 | #set($currentSpace = $xwiki.getDocument($currentDoc).getSpace()) | ||
52 | ## | ||
53 | ## This application should not run with programming rights because it evaluates code which may not be trustworthy. | ||
54 | ## Removing the next line will open a security hole. | ||
55 | $doc.dropPermissions()## | ||
56 | ## | ||
57 | ##------------------------------------------------------------------------------------------------------------ | ||
58 | ## If $section exists then we are viewing the admin page for a particular section. | ||
59 | ## eg: 'Registration', 'Presentation', 'Import' etc. | ||
60 | ##------------------------------------------------------------------------------------------------------------ | ||
61 | ## | ||
62 | #if($section && $section != '') | ||
63 | ## | ||
64 | ## This is for keeping track of whether we have shown the heading yet or not. | ||
65 | ## If the heading doesn't need to be shown, but an error occurs in processing, then we show the heading | ||
66 | ## so that the user knows what the error relates to. | ||
67 | #set($headingShowing = false) | ||
68 | ## | ||
69 | ## Searches the database for names of apps to be configured | ||
70 | #set($outputList = []) | ||
71 | #findNamesOfAppsToConfigure($section, $globaladmin, $xwiki.getDocument($currentDoc).getSpace(), $outputList) | ||
72 | ## | ||
73 | #foreach($appName in $outputList) | ||
74 | ## | ||
75 | ## Make sure the current user has permission to edit the configurable application. | ||
76 | #set($userHasAccessToDocument = $xcontext.hasAccessLevel('edit', $appName)) | ||
77 | ## | ||
78 | ## If the document was not last saved by a user with edit privilege on this page | ||
79 | ## then we can't safely display the page but we should warn the viewer. | ||
80 | #if($userHasAccessToDocument) | ||
81 | ## Get the configurable application | ||
82 | #set($app = $xwiki.getDocument($appName)) | ||
83 | ## | ||
84 | #set($documentSavedByAuthorizedUser = false) | ||
85 | #checkDocumentSavedByAuthorizedUser($app, $currentDoc, $documentSavedByAuthorizedUser) | ||
86 | #end | ||
87 | ## | ||
88 | ## There is no need to display a heading unless: | ||
89 | ## 1. There was already a section before this document. | ||
90 | ## 2. This is not the first document in this section. | ||
91 | ## | ||
92 | ## If we are displaying the heading and there is an error to be shown Javascript will not strip the heading. | ||
93 | #if(!$appName.equals($outputList.get(0)) || $sections.contains($section)) | ||
94 | ## Create a document heading. | ||
95 | #showHeading($appName, $headingShowing) | ||
96 | #end | ||
97 | ## | ||
98 | #if(!$userHasAccessToDocument) | ||
99 | #showHeading($appName, $headingShowing) | ||
100 | |||
101 | {{error}}{{translation key="xe.admin.configurable.noPermissionThisApplication"/}}{{/error}} | ||
102 | |||
103 | #elseif(!$documentSavedByAuthorizedUser) | ||
104 | #showHeading($appName, $headingShowing) | ||
105 | |||
106 | {{error}}{{translation key="xe.admin.configurable.applicationAuthorNoAdmin" parameters="$app.Author"/}}{{/error}} | ||
107 | |||
108 | ## | ||
109 | ##------------------------------------------------------------------------------------------------------------ | ||
110 | ## If the document is locked and not by the current user and forceEdit is not set true, | ||
111 | #elseif($app.getLocked() && $app.getLockingUser() != $xcontext.getUser() && !$request.getParameter('forceEdit')) | ||
112 | #set($requestURL = "$request.getRequestURL()") | ||
113 | #if($requestURL.indexOf('?') == -1) | ||
114 | #set($requestURL = "${requestURL}?$request.queryString") | ||
115 | #end | ||
116 | #showHeading($appName, $headingShowing) | ||
117 | |||
118 | {{error}}{{translation key="doclockedby"/}} $app.getLockingUser() [[{{translation key="forcelock"/}}>>${requestURL}&forceEdit=1]]{{/error}} | ||
119 | |||
120 | #else | ||
121 | ## If the document is not already locked, attempt to acquire the lock. | ||
122 | #if(!$app.getLocked()) | ||
123 | |||
124 | {{html wiki=true}} | ||
125 | <noscript> | ||
126 | |||
127 | {{warning}}{{translation key="xe.admin.configurable.cannotLockNoJavascript"/}}{{/warning}} | ||
128 | |||
129 | </noscript> | ||
130 | <script type="text/javascript"> | ||
131 | document.observe("xwiki:dom:loaded", function() { | ||
132 | XWiki.DocumentLock && new XWiki.DocumentLock('$escapetool.javascript($app.prefixedFullName)').lock(); | ||
133 | }); | ||
134 | </script> | ||
135 | {{/html}} | ||
136 | #end | ||
137 | ##------------------------------------------------------------------------------------------------------------ | ||
138 | ## Done Locking. | ||
139 | ## | ||
140 | ## Get all objects of the "ConfigurableClass" from this document. | ||
141 | #set($allConfigurableObjs = $app.getObjects($nameOfThisDocument)) | ||
142 | ## Separate out the objects which are for this section. | ||
143 | #set($configurableObjs = []) | ||
144 | #foreach($configurableObj in $allConfigurableObjs) | ||
145 | #if($app.getValue('displayInSection', $configurableObj) == $section) | ||
146 | ## If this is space admin, then don't display global, if global don't display space. | ||
147 | #if($globaladmin == ($app.getValue('configureGlobally', $configurableObj) == 1)) | ||
148 | #set($discard = $configurableObjs.add($configurableObj)) | ||
149 | #end | ||
150 | #end | ||
151 | #end | ||
152 | #if($configurableObjs.size() == 0) | ||
153 | ## Internal error, not translated. | ||
154 | #showHeading($appName, $headingShowing) | ||
155 | |||
156 | {{error}}Internal error: All objects were filtered out for application: $appName.{{/error}} | ||
157 | |||
158 | #else | ||
159 | #set($formAction = $xwiki.getURL($app.getFullName(), 'save')) | ||
160 | #set($formId = "${section.toLowerCase()}_${app.getFullName()}") | ||
161 | #set($escapedAppName = $escapetool.xml($app.getFullName())) | ||
162 | #foreach($configurableObj in $configurableObjs) | ||
163 | ## Display the header if one exists. | ||
164 | #set($heading = $app.getValue('heading', $configurableObj)) | ||
165 | #if($heading && $heading != '') | ||
166 | == #evaluate($heading) == | ||
167 | #end | ||
168 | ## | ||
169 | #set($codeToExecute = "$!app.getValue('codeToExecute', $configurableObj)") | ||
170 | #if($codeToExecute != '') | ||
171 | (%class="codeToExecute"%)(((## | ||
172 | #evaluate($codeToExecute)## | ||
173 | ))) | ||
174 | #end | ||
175 | ## | ||
176 | ## If propertiesToShow is set, then we will only show the properties contained therein. | ||
177 | #set($propertiesToShow = $app.getValue('propertiesToShow', $configurableObj)) | ||
178 | #if(!$propertiesToShow || $propertiesToShow.getClass().getName().indexOf('List') == -1) | ||
179 | #set($propertiesToShow = []) | ||
180 | #end | ||
181 | ## | ||
182 | ## If linkPrefix is set, then we will make each property label a link which starts with that prefix. | ||
183 | #set($linkPrefix = "$!app.getValue('linkPrefix', $configurableObj)") | ||
184 | ## | ||
185 | ## If the Configurable object specifies a configuration class, use it, | ||
186 | ## otherwise assume custom forms are used instead. | ||
187 | #set($configClassName = "$!app.getValue('configurationClass', $configurableObj)") | ||
188 | #if($configClassName != '') | ||
189 | #set($objClass = $xwiki.getDocument($configClassName).getxWikiClass()) | ||
190 | #if(!$objClass || $objClass.getClass().getName().indexOf('.Class') == -1) | ||
191 | #showHeading($appName, $headingShowing) | ||
192 | |||
193 | {{error}}{{translation key="xe.admin.configurable.configurationClassNonexistant"/}}{{/error}} | ||
194 | |||
195 | #else | ||
196 | ## Use the first object from the document which is of the configuration class. | ||
197 | #set($obj = $app.getObject($objClass.getName())) | ||
198 | ## | ||
199 | #if(!$obj || $obj.getClass().getName().indexOf('.Object') == -1) | ||
200 | #showHeading($appName, $headingShowing) | ||
201 | |||
202 | {{error}} | ||
203 | {{translation key="xe.admin.configurable.noObjectOfConfigurationClassFound" parameters="$objClass.getName(), $app.getFullName()"/}} | ||
204 | {{/error}} | ||
205 | |||
206 | #else | ||
207 | ## | ||
208 | ## Merge save buttons, remove headings from subsections, and make information links into popups. | ||
209 | ## This is not done if there is only a custom defined form. | ||
210 | $xwiki.jsx.use($nameOfThisDocument)## | ||
211 | ## | ||
212 | ## We don't begin the form until we have content for it so that a configurable can specify a | ||
213 | ## custom form in codeToExecute and if that configurable object is the first of it's kind in that | ||
214 | ## document, the custom form will not be put inside of our form. | ||
215 | #if(!$insideForm) | ||
216 | ## We are opening a form and fieldset without closing it, thus we cannot clean this html. | ||
217 | |||
218 | {{html clean=false}} | ||
219 | <form id="$formId" method="post" action="$formAction" class="xform"> | ||
220 | <fieldset> | ||
221 | {{/html}} | ||
222 | #set($insideForm = true) | ||
223 | #end | ||
224 | |||
225 | {{html}} | ||
226 | $formHtml.toString() | ||
227 | {{/html}} | ||
228 | #end## If object exists | ||
229 | #end## If class exists | ||
230 | #end## If class name is specified. | ||
231 | #end## Foreach configurable object found in this document | ||
232 | ## If a form was started then we end it. | ||
233 | #if($insideForm) | ||
234 | |||
235 | ## This is closing an open form which was opened above, we cannot clean this html. | ||
236 | {{html clean=false}} | ||
237 | ## We add in a redirect field to prevent the user from being carried away when they save | ||
238 | ## if they don't have javascript. | ||
239 | #set($thisURL = $request.getRequestURL()) | ||
240 | #if($request.getQueryString() && $request.getQueryString().length() > 0) | ||
241 | #set($thisURL = "${thisURL}?$request.getQueryString()") | ||
242 | #end | ||
243 | <input type="hidden" id="${escapedAppName}_redirect" name="$redirectParameter" value="$escapetool.xml($thisURL)" /> | ||
244 | <input type="hidden" name="form_token" value="$!{services.csrf.getToken()}" /> | ||
245 | </fieldset> | ||
246 | <div class="bottombuttons"> | ||
247 | <p class="admin-buttons"> | ||
248 | <span class="buttonwrapper"> | ||
249 | ## Text to display on the button. If there is a heading then this button should be labeled | ||
250 | ## that it is for saving this section. Otherwise it should be a generic "save" button. | ||
251 | #if($headingShowing) | ||
252 | #set($buttonText = "$services.localization.render('admin.save') $escapedAppName") | ||
253 | #else | ||
254 | #set($buttonText = "$services.localization.render('admin.save')") | ||
255 | #end | ||
256 | <input class="button" type="submit" name="action_saveandcontinue" value="$buttonText" /> | ||
257 | </span> | ||
258 | </p> | ||
259 | </div> ## bottombuttons | ||
260 | </form> | ||
261 | #set($insideForm = false) | ||
262 | {{/html}} | ||
263 | #end | ||
264 | #end## If there are configurable objects | ||
265 | #end## If document is not locked or forceEdit is enabled | ||
266 | #end## Foreach document name in names to configure | ||
267 | |||
268 | {{html}} | ||
269 | <script type="text/javascript"> | ||
270 | /* <![CDATA[ */ | ||
271 | ## Alt+Shift+S presses the first saveAndContinue button it finds, not what we want so we will disable edit shortcuts. | ||
272 | document.observe('xwiki:dom:loaded', function() { | ||
273 | XWiki.actionButtons.EditActions = Object.extend(XWiki.actionButtons.EditActions, {addShortcuts : function() { }}); | ||
274 | }); | ||
275 | //]]> | ||
276 | </script> | ||
277 | {{/html}}## | ||
278 | ## | ||
279 | #else | ||
280 | ## | ||
281 | ##------------------------------------------------------------------------------------------------------------ | ||
282 | ## If section is not set then we are viewing the main administration page. | ||
283 | ##------------------------------------------------------------------------------------------------------------ | ||
284 | ## | ||
285 | ## If there is no list called sections then we set sections to an empty list. | ||
286 | #if(!$sections || $sections.getClass().getName().indexOf('List') == -1) | ||
287 | #set($sections = []) | ||
288 | #end | ||
289 | ## | ||
290 | ## We have to create a list of documents which the current user doesn't have permission to view. | ||
291 | ## So we can add an error message to the bottom of the page if there are any. | ||
292 | #set($appsUserCannotView = []) | ||
293 | ## | ||
294 | ## A list of sections (to be added) which the user is not allowed to edit, icons will be displayed with a message | ||
295 | #set($sectionsUserCannotEdit = []) | ||
296 | ## List of sections to be added, in order by creationDate of oldest contained application. | ||
297 | #set($sectionsToAdd = []) | ||
298 | ## Map of URL of icon to use by the name of the section to use that icon on. | ||
299 | #set($iconBySection = {}) | ||
300 | ## | ||
301 | #set($outputList = []) | ||
302 | #findNamesOfAppsToConfigure("", $globaladmin, $currentSpace, $outputList) | ||
303 | ## | ||
304 | #foreach($appName in $outputList) | ||
305 | ## | ||
306 | ## Get the configurable application | ||
307 | #set($app = $xwiki.getDocument($appName)) | ||
308 | ## | ||
309 | ## If getDocument returns null, then warn the user that they don't have view access to that application. | ||
310 | #if(!$app) | ||
311 | #set($discard = $appsUserCannotView.add($appName)) | ||
312 | #end | ||
313 | ## | ||
314 | #set($configurableObjects = $app.getObjects($nameOfThisDocument)) | ||
315 | #foreach($configurableObject in $configurableObjects) | ||
316 | #set($displayInSection = $app.getValue('displayInSection', $configurableObject)) | ||
317 | ## | ||
318 | ## If there is no section for this configurable or if the section cannot be edited, then check if the | ||
319 | ## application can be edited by the current user, if so then we display the icon from the current app and | ||
320 | ## don't display any message to tell the user they can't edit that section. | ||
321 | #if(!$sections.contains($displayInSection) || $sectionsUserCannotEdit.contains($displayInSection)) | ||
322 | ## | ||
323 | ## If there is no section for this configurable, then we will have to add one. | ||
324 | #if(!$sections.contains($displayInSection) && !$sectionsToAdd.contains($displayInSection)) | ||
325 | #set($discard = $sectionsToAdd.add($displayInSection)) | ||
326 | #end | ||
327 | ## | ||
328 | ## If an attachment by the filename iconAttachment exists and is an image | ||
329 | #set($attachment = $app.getAttachment("$app.getValue('iconAttachment', $configurableObject)")) | ||
330 | #if($attachment && $attachment.isImage()) | ||
331 | ## Set the icon for this section as the attachment URL. | ||
332 | #set($discard = $iconBySection.put($displayInSection, $app.getAttachmentURL($attachment.getFilename()))) | ||
333 | #end | ||
334 | ## | ||
335 | ## If the user doesn't have edit access to the application, we want to show a message on the icon | ||
336 | #if(!$xcontext.hasAccessLevel("edit", $app.getFullName())) | ||
337 | #if(!$sectionsUserCannotEdit.contains($displayInSection)) | ||
338 | #set($discard = $sectionsUserCannotEdit.add($displayInSection)) | ||
339 | #end | ||
340 | #elseif($sectionsUserCannotEdit.contains($displayInSection)) | ||
341 | ## If the user didn't have access to the section before but does have access to _this_ app which is | ||
342 | ## configured in the section, then the section becomes accessible. | ||
343 | #set($discard = $sectionsUserCannotEdit.remove($displayInSection)) | ||
344 | #end | ||
345 | #end## If section doesn't exist or user doesn't have access. | ||
346 | #end## Foreach configurable object in this app. | ||
347 | #end## Foreach application which is configurable. | ||
348 | ## | ||
349 | ## Now we go through sectionsToAdd and generate icons for them | ||
350 | #set($defaultIcon = $xwiki.getAttachmentURL($nameOfThisDocument, 'DefaultAdminSectionIcon.png')) | ||
351 | #if($globaladmin) | ||
352 | #set($queryString = "editor=globaladmin&section=") | ||
353 | #else | ||
354 | #set($queryString = "space=${currentSpace}&section=") | ||
355 | #if($request.getParameter('editor')) | ||
356 | #set($queryString = "editor=$escapetool.url($request.getParameter('editor'))&$queryString") | ||
357 | #end | ||
358 | #end | ||
359 | |||
360 | ## This is an html fragment and thus cannot be cleaned | ||
361 | {{html clean=false}} | ||
362 | #foreach($sectionToAdd in $sectionsToAdd) | ||
363 | #set($icon = $iconBySection.get($sectionToAdd)) | ||
364 | #if(!$icon) | ||
365 | #set($icon = $defaultIcon) | ||
366 | #end | ||
367 | <li class="$escapetool.xml($sectionToAdd).replaceAll(' ', '_')"> | ||
368 | #set($hasAccess = !$sectionsUserCannotEdit.contains($sectionToAdd)) | ||
369 | #if($hasAccess) | ||
370 | <a href="$xwiki.getURL($currentDoc, $xcontext.getAction(), "$queryString$escapetool.url($sectionToAdd)")"> | ||
371 | #else | ||
372 | <a title="$services.localization.render('xe.admin.configurable.sectionIconNoAccessTooltip')"> | ||
373 | #end | ||
374 | <span> | ||
375 | <img src="$icon" alt="$escapetool.xml($sectionToAdd) icon"/> | ||
376 | ## Try to translate the names of the sections, build the key by adding an "admin." in front. | ||
377 | ## Not the best way to translate, but very inline with the way the translations are done in XWiki.AdminSheet for individual administration page titles. | ||
378 | ## If there is no translation (translated message is equals to key), don't display the message key, but the section name instead. | ||
379 | #if($services.localization.get("admin.${sectionToAdd.toLowerCase()}")) | ||
380 | #set($sectionDisplayName = $services.localization.render("admin.${sectionToAdd.toLowerCase()}")) | ||
381 | #else | ||
382 | #set($sectionDisplayName = $sectionToAdd) | ||
383 | #end | ||
384 | $escapetool.xml($sectionDisplayName) | ||
385 | </span> | ||
386 | #if(!$hasAccess) | ||
387 | <br/><span class="errormessage">$services.localization.render('xe.admin.configurable.sectionIconNoAccess')</span> | ||
388 | #end | ||
389 | </a> | ||
390 | </li> | ||
391 | #end | ||
392 | {{/html}} | ||
393 | |||
394 | ## Finally we display an error message if there are any applications which we were unable to view. | ||
395 | #if($appsUserCannotView.size() > 0) | ||
396 | |||
397 | {{error}}$services.localization.render('xe.admin.configurable.noViewAccessSomeApplications', [$appsUserCannotView]){{/error}} | ||
398 | |||
399 | #end | ||
400 | #end## If we should be looking at the main administration page. | ||
401 | {{/velocity}} |