Wiki source code of XWiki.Configurable

Last modified by Admin on 2010/02/19 11:21

Show last authors
1 {{velocity}}
2 #*
3 * This part takes the configuration from any documents containing XWiki.Configurable objects and creates a form
4 * for each. To includeForm this document, you may specify:
5 *
6 * $section - String - The section which we are administrating eg: "Registration", "Users", or "Import".
7 * If none is specified then it checks for a request parameter called "section" and uses that,
8 * if no parameter, then this code assumes that it is part of the admin icons sheet and adds icons
9 * for any section which is not in $sections, in that event, this code assumes it is being run
10 * inside of a <ul> block.
11 *
12 * $sections - List<String> - If section is not specified, any sections on this list will not have icons made for them
13 * the assumption being that the icons are already there. If section is specified then this
14 * is not taken into account and may safely be undefined.
15 *
16 * $currentDoc - String (document.fullName) - The administration document, users who don't have permission to edit
17 * it will not be able to include applications (possibly injecting
18 * arbitrary code.) if none specified then $doc.getFullName() is used.
19 *
20 * $globaladmin - boolean - If set true then we will assume we are administrationg the entire wiki.
21 * If not set then we look for a request parameter called "editor" if that exists and equals
22 * "globaladmin" then $globaladmin is true, if it doesn't exist then we check to see if
23 * $currentDoc.getFullName() equals "XWiki.XWikiPreferences".
24 *
25 * $doNotUnlockConfigurableDocuments - boolean - If true then this code will not make any attempt to unlock configurable
26 * documents. By default it does because it locks any document in the
27 * section which is being configured which would lead to a lot of stray
28 * locks if they weren't all cancelled. Only recommended if this page is
29 * being included twice in the same page.
30 *###
31 ## Constants:
32 #set($redirectParameter = 'xredirect')
33 #set($nameOfThisDocument = 'XWiki.Configurable')
34 ##
35 ## Form submission depends on this.
36 $xwiki.jsfx.use('js/xwiki/actionbuttons/actionButtons.js', true)
37 ##
38 #if(!$section)
39 #set($section = $request.getParameter('section'))
40 #end
41 #if(!$currentDoc)
42 #set($currentDoc = $doc.getFullName())
43 #end
44 ## Get value of $globaladmin if not specified.
45 #if("$!globaladmin" == '')
46 #if($editor != 'globaladmin'
47 && $request.getParameter('editor') != 'globaladmin'
48 && $currentDoc != "XWiki.XWikiPreferences")
49 ##
50 #set($globaladmin = false)
51 #else
52 #set($globaladmin = true)
53 #end
54 #end
55 #set($currentSpace = $xwiki.getDocument("$currentDoc").getSpace())
56 ##
57 ## This application should not run with programming rights because it evaluates code which may not be trustworthy.
58 ## Removing the next line will open a security hole.
59 #sandboxDocument($nameOfThisDocument)
60 ##
61 ## This application locks every document in a section while that section is being edited so we should
62 ## check for locks held by the current user on any of the applications configured here and remove them.
63 #if(!$doNotUnlockConfigurableDocuments)
64 #set($outputList = [])
65 #findNamesOfAppsToConfigure("", $globaladmin, $currentSpace, $outputList)
66 ## We don't want to generate javascript which unlocks the current document just after we got finished locking it.
67 #set($discard = $outputList.remove($currentDoc))
68 #unlockDocuments($outputList)
69 #end
70 ##
71 ##------------------------------------------------------------------------------------------------------------
72 ## If $section exists then we are viewing the admin page for a perticular section.
73 ## eg: 'Registration', 'Presentation', 'Import' etc.
74 ##------------------------------------------------------------------------------------------------------------
75 ##
76 #if($section && $section != '')
77 ##
78 ## Searches the database for names of apps to be configured
79 #set($outputList = [])
80 #findNamesOfAppsToConfigure($section, $globaladmin, $xwiki.getDocument("$currentDoc").getSpace(), $outputList)
81 ##
82 #foreach($appName in $outputList)
83 ##
84 = $msg.get("admin.customize") __[[$appName>>$appName]]__: =
85 ##
86 ## Make sure the current user has permission to edit the configurable application.
87 #if(!$xcontext.hasAccessLevel('edit', $appName))
88 {{error}}$msg.get('xe.admin.configurable.noPermissionThisApplication'){{/error}}
89 #else
90 ##
91 ## Get the configurable application
92 #set($app = $xwiki.getDocument($appName))
93 ##
94 ## If the document was not last saved by a user with edit privilage on this page
95 ## then we can't safely display the page but we should warn the viewer.
96 #set($hasAccess = false)
97 #checkDocumentSavedByAuthorizedUser($app, $currentDoc, $hasAccess)
98 #if(!$hasAccess)
99 {{error}}$msg.get('xe.admin.configurable.applicationAuthorNoAdmin', [$app.Author]){{/error}}
100 #else
101 ##
102 ## Locking document
103 ##------------------------------------------------------------------------------------------------------------
104 #if($app.getLocked())
105 #set($locked = true)
106 #end
107 ## If the document is locked and not by the current user and forceEdit is not set true,
108 #if($locked && $app.getLockingUser() != $xcontext.getUser() && !$request.getParameter('forceEdit'))
109 #set($requestURL = "$request.getRequestURL()")
110 #if($requestURL.indexOf("?") == -1)
111 #set($requestURL = "${requestURL}?")
112 #end
113 {{error}}$msg.get("doclockedby") $app.getLockingUser() [[$msg.get("forcelock")>>${requestURL}&forceEdit=1]]{{/error}}
114 #else
115 ## If the document is not already locked, attempt to aquire the lock.
116 #if(!$locked)
117 ## Try to use an ajax call to lock the document.
118 {{html clean=false wiki=true}}
119 </p><noscript>
120 {{warning}}$msg.get('xe.admin.configurable.cannotLockNoJavascript'){{/warning}}
121 </noscript>
122 <script type="text/javascript">
123 document.observe("dom:loaded", function() {
124 new Ajax.Request("$xwiki.getURL($app.getFullName(), 'lock', 'ajax=1')");
125 });
126 </script><p>
127 {{/html}}
128 #set($discard = $lockedDocumentNames.add($app.getFullName()))
129 #end
130 ##------------------------------------------------------------------------------------------------------------
131 ## Done Locking.
132 ##
133 ## Get all objects of the "Configurable" class from this document.
134 #set($allConfigurableObjs = $app.getObjects('XWiki.Configurable'))
135 ## Seperate out the objects which are for this section.
136 #set($configurableObjs = [])
137 #foreach($configurableObj in $allConfigurableObjs)
138 #if($app.getValue('displayInSection', $configurableObj) == $section)
139 ## If this is space admin, then don't display global, if global don't display space.
140 #if($globaladmin == ($app.getValue('configureGlobally', $configurableObj) == 1))
141 #set($discard = $configurableObjs.add($configurableObj))
142 #end
143 #end
144 #end
145 #if($configurableObjs.size() == 0)
146 ## Internal error, not translated.
147 {{error}}Internal error: All objects were filtered out for application: $appName.{{/error}}
148 #else
149 #set($formAction = "$xwiki.getURL($app.getFullName(), 'save')")
150 #set($formId = "$section.toLowerCase()_$app.getFullName()")
151 #set($escapedAppName = $escapetool.html($app.getFullName()))
152 {{html clean=false wiki=false}}
153 #foreach($configurableObj in $configurableObjs)
154 ## Display the header if one exists.
155 #set($heading = $app.getValue('heading', $configurableObj))
156 #if($heading && $heading != '')
157 {{/html}}
158 == #evaluate($heading) ==
159 {{html clean=false wiki=false}}
160 #end
161 ## If the class specifies custom code to evaluate,
162 ## then close the html macro, evaluate it and reopen the macro.
163 #set($codeToExecute = "$!app.getValue('codeToExecute', $configurableObj)")
164 #if($codeToExecute != '')
165 {{/html}}
166 #evaluate($codeToExecute)
167 {{html clean=false wiki=false}}
168 #end
169 ##
170 ## If propertiesToShow is set, then we will only show the properties contained therein.
171 #set($propertiesToShow = $app.getValue('propertiesToShow', $configurableObj))
172 #if(!$propertiesToShow || $propertiesToShow.getClass().getName().indexOf('List') == -1)
173 #set($propertiesToShow = [])
174 #end
175 ##
176 ## If linkPrefix is set, then we will make each property label a link which starts with that prefix.
177 #set($linkPrefix = "$!app.getValue('linkPrefix', $configurableObj)")
178 ##
179 ## If the Configurable object specifies a configuration class, use it,
180 ## otherwise assume custom forms are used instead.
181 #set($configClassName = "$!app.getValue('configurationClass', $configurableObj)")
182 #if($configClassName != '')
183 #set($objClass = $xwiki.getDocument("$configClassName").getxWikiClass())
184 #if(!$objClass || $objClass.getClass().getName().indexOf('.Class') == -1)
185 {{/html}}
186 {{error}}$msg.get('xe.admin.configurable.configurationClassNonexistant'){{/error}}
187 {{html clean=false wiki=false}}
188 #else
189 ## Use the first object from the document which is of the configuration class.
190 #set($obj = $app.getObject($objClass.getName()))
191 ##
192 #if(!$obj || $obj.getClass().getName().indexOf('.Object') == -1)
193 {{/html}}
194 {{error}}
195 $msg.get('xe.admin.configurable.noObjectOfConfigurationClassFound',
196 [$objClass.getName(), $app.getFullName()])
197 {{/error}}
198 {{html clean=false wiki=false}}
199 #else
200 #define($formHtml)
201 ## We don't begin the form until we have content for it so that a configurable can specify a
202 ## custom form in codeToExecute and if that configurable object is the first of it's kind in that
203 ## document, the custom form will not be put inside of our form.
204 #if(!$insideForm)
205 <form id="$formId" method="post" action="$formAction" onsubmit="cancelCancelEdit()">
206 <fieldset>
207 #set($insideForm = true)
208 #end
209 #foreach($propName in $objClass.getPropertyNames())
210 #if($propertiesToShow.size() > 0 && !$propertiesToShow.contains($propName))
211 ## Silently skip over this property.
212 #else
213 <p>
214 #set($prettyName = "#evaluate($app.displayPrettyName($propName, $obj))")
215 ## App Name is prepended to for= to make label work with id which is modified to prevent collisions.
216 <label for="${escapedAppName}_$objClass.getName()_0_$propName">
217 #if($linkPrefix != '')
218 #set($linkScript = "$linkPrefix$propName")
219 <a href="$escapetool.html("#evaluate($linkScript)")">$escapetool.html($prettyName)</a>
220 #else
221 $escapetool.html($prettyName)
222 #end
223 </label>
224 $obj.display($propName, "edit")
225 </p>
226 #end## If property is in propertiesToShow
227 #end## Foreach property in this class
228 #end## define $formHtml
229 ## Strip pre tags and html macro invocations which $obj.display inserts.
230 ## then prepend application name to ids to prevent id collissions.
231 $formHtml.toString().replaceAll('\{\{[/]?html[^}]*\}\}|<[/]?pre>', '').replaceAll(" id='", " id='${escapedAppName}_")
232 #end## If object exists
233 #end## If class exists
234 #end## If class name is specified.
235 #end## Foreach configurable object found in this document
236 ## If a form was started then we end it.
237 #if($insideForm)
238 ## We add in a redirect field to prevent the user from being carried away when they save
239 ## if they don't have javascript.
240 #set($thisURL = $request.getRequestURL())
241 #if($request.getQueryString() && $request.getQueryString().length() > 0)
242 #set($thisURL = "${thisURL}?$request.getQueryString()")
243 #end
244 <input type="hidden" id="${escapedAppName}_redirect" name="$redirectParameter" value="$escapetool.html($thisURL)" />
245 </fieldset>
246 <div class="bottombuttons">
247 <p class="admin-buttons">
248 <span class="buttonwrapper">
249 ## Text to display on the button
250 #set($buttonText = "$msg.get('admin.save') $escapedAppName")
251 <input class="button" type="submit" name="action_saveandcontinue" value="$buttonText" />
252 </span>
253 </p>
254 </div> ## bottombuttons
255 </form>
256 #set($insideForm = false)
257 #end
258 {{/html}}
259 #end## If there are configurable objects
260 #end## If document is not locked or forceEdit is enabled
261 #end## If app author has permission to edit admin page
262 #end## If the current user has permission to edit the configurable application.
263 #end## Foreach document name in names to configure
264 {{html clean=false wiki=false}}
265 <script type="text/javascript">
266 /* <![CDATA[ */
267 ## Alt+Shift+S presses the first saveAndContinue button it finds, not what we want so we will disable edit shortcuts.
268 XWiki.actionButtons.EditActions = Object.extend(XWiki.actionButtons.EditActions, {addShortcuts : function() { }});
269 ##
270 ## TODO: cancel and "submit all" buttons.
271 //]]>
272 </script>
273 {{/html}}
274 ##
275 ##
276 #else
277 ##
278 ##------------------------------------------------------------------------------------------------------------
279 ## If section is not set then we are viewing the main administration page.
280 ##------------------------------------------------------------------------------------------------------------
281 ##
282 ## If there is no list called sections then we set sections to an empty list.
283 #if(!$sections || $sections.getClass().getName().indexOf("List") == -1)
284 #set($sections = [])
285 #end
286 ##
287 ## We have to create a list of documents which the current user doesn't have permission to view.
288 ## So we can add an error messsage to the bottom of the page if there are any.
289 #set($appsUserCannotView = [])
290 ##
291 ## A list of sections (to be added) which the user is not allowed to edit, icons will be displayed with a message
292 #set($sectionsUserCannotEdit = [])
293 ## List of sections to be added, in order by creationDate of oldest contained application.
294 #set($sectionsToAdd = [])
295 ## Map of URL of icon to use by the name of the section to use that icon on.
296 #set($iconBySection = {})
297 ##
298 #set($outputList = [])
299 #findNamesOfAppsToConfigure("", $globaladmin, $currentSpace, $outputList)
300 ##
301 #foreach($appName in $outputList)
302 ##
303 ## Get the configurable application
304 #set($app = $xwiki.getDocument($appName))
305 ##
306 ## If getDocument returns null, then warn the user that they don't have view access to that application.
307 #if(!$app)
308 #set($discard = $appsUserCannotView.add($appName))
309 #end
310 ##
311 #set($configurableObjects = $app.getObjects('XWiki.Configurable'))
312 #foreach($configurableObject in $configurableObjects)
313 #set($displayInSection = $app.getValue('displayInSection', $configurableObject))
314 ##
315 ## If there is no section for this configurable or if the section cannot be edited, then check if the
316 ## application can be edited by the current user, if so then we display the icon from the current app and
317 ## don't display any message to tell the user they can't edit that section.
318 #if(!$sections.contains($displayInSection) || $sectionsUserCannotEdit.contains($displayInSection))
319 ##
320 ## If there is no section for this configurable, then we will have to add one.
321 #if(!$sections.contains($displayInSection) && !$sectionsToAdd.contains($displayInSection))
322 #set($discard = $sectionsToAdd.add($displayInSection))
323 #end
324 ##
325 ## If an attachment by the filename iconAttachment exists and is an image
326 #set($attachment = $app.getAttachment("$app.getValue('iconAttachment', $configurableObject)"))
327 #if($attachment && $attachment.isImage())
328 ## Set the icon for this section as the attachment URL.
329 #set($discard = $iconBySection.put($displayInSection, $app.getAttachmentURL($attachment.getFilename())))
330 #end
331 ##
332 ## If the user doesn't have edit access to the application, we want to should show a message on the icon
333 #if(!$xcontext.hasAccessLevel("edit", $app.getFullName()))
334 #if(!$sectionsUserCannotEdit.contains($displayInSection))
335 #set($discard = $sectionsUserCannotEdit.add($displayInSection))
336 #end
337 #elseif($sectionsUserCannotEdit.contains($displayInSection))
338 ## If the user didn't have access to the section before but does have access to _this_ app which is
339 ## configured in the section, then the section becomes accessable.
340 #set($discard = $sectionsUserCannotEdit.remove($displayInSection))
341 #end
342 #end## If section doesn't exist or user doesn't have access.
343 #end## Foreach configurable object in this app.
344 #end## Foreach application which is configurable.
345 ##
346 ## Now we go through sectionsToAdd and generate icons for them
347 #set($defaultIcon = $xwiki.getAttachmentURL('XWiki.Configurable', 'DefaultAdminSectionIcon.png'))
348 #if($globaladmin)
349 #set($queryString = "editor=globaladmin&amp;section=")
350 #else
351 #set($queryString = "space=${currentSpace}&amp;section=")
352 #if($request.getParameter('editor'))
353 #set($queryString = "editor=$escapetool.url($request.getParameter('editor'))&amp;$queryString")
354 #end
355 #end
356 {{html clean=false wiki=false}}
357 #foreach($sectionToAdd in $sectionsToAdd)
358 #set($icon = $iconBySection.get($sectionToAdd))
359 #if(!$icon)
360 #set($icon = $defaultIcon)
361 #end
362 <li class="$escapetool.html($sectionToAdd).replaceAll(' ', '_')">
363 #set($hasAccess = !$sectionsUserCannotEdit.contains($sectionToAdd))
364 #if($hasAccess)
365 <a href="$xwiki.getURL($currentDoc, $xcontext.getAction(), "$queryString$escapetool.url($sectionToAdd)")">
366 #else
367 <a title="$msg.get('xe.admin.configurable.sectionIconNoAccessTooltip')">
368 #end
369 <img src="$icon" alt="$escapetool.html($sectionToAdd) icon"/>
370 $escapetool.html($sectionToAdd)
371 #if(!$hasAccess)
372 <br/><span class="errormessage">$msg.get('xe.admin.configurable.sectionIconNoAccess')</span>
373 #end
374 </a>
375 </li>
376 #end
377 {{/html}}
378 ##
379 ## Finally we display an error message if there are any applications which we were unable to view.
380 #if($appsUserCannotView.size() > 0)
381 {{error}}$msg.get('xe.admin.configurable.noViewAccessSomeApplications', [$appsUserCannotView]){{/error}}
382 #end
383 #end## If we should be looking at the main administration page.
384 ##
385 ##------------------------------------------------------------------------------------------------------------
386 ## The Macros, nothing below this point is run directly.
387 ##------------------------------------------------------------------------------------------------------------
388 ##
389 #*
390 *
391 * Any documents which are on the provided list ($documentNames) which are locked by the current user will be unlocked.
392 * If this macro has programming rights, then they are unlocked programmatically, otherwise a javascript tag is
393 * generated with ajax calls to cancel for all of the documents. If there are documents on this list which are not
394 * locked by the current user, then they are ignored.
395 *
396 * @param $documentNames - List<String> - fullNames of documents which should be unlocked if they are locked by the
397 * current user.
398 *###
399 #macro(unlockDocuments $documentNames)
400 #if($documentNames.size() > 0)
401 #set($sql = "doc.fullName=")
402 #foreach($documentName in $documentNames)
403 #set($sql = "${sql}'$documentName' or doc.fullName=")
404 #end
405 ## Trim the dangling ' or doc.fullName=?'
406 #set($sql = $sql.substring(0, $sql.lastIndexOf(' or doc.fullName=')))
407 #set($sql = ", XWikiLock lock where lock.docId=doc.id and lock.userName='$xcontext.getUser()' and (${sql})")
408 #set($namesOfdocumentsToUnlock = $xwiki.searchDocuments($sql))
409 ## Use ajax and hope the user runs javascript.
410 {{html clean=false wiki=false}}
411 <script type="text/javascript">
412 document.observe("dom:loaded", function() {
413 #foreach($nameOflockedDocument in $namesOfdocumentsToUnlock)
414 new Ajax.Request("$xwiki.getURL($nameOflockedDocument, 'cancel', 'ajax=1')");
415 #end
416 });
417 </script>
418 {{/html}}
419 #end## If output list size > 0
420 #end## Macro
421 ##
422 #*
423 * Find names of documents which contain objects of the class 'XWiki.Configurable'
424 *
425 * @param $section - String - Look for apps which specify that they should be configured in this section,
426 * if null or "" then returns them for all sections.
427 *
428 * @param $globaladmin - boolean - If true then we will look for applications which should be configured globally.
429 *
430 * @param $space - String - If not looking for apps which are configured globally, then this is the space where we
431 * will look for apps in. If null or "" or if $globaladmin is true, then all spaces will be
432 * searched.
433 *
434 * @param $outputList - List - The returns from this macro will be put in this list, passing the list as a parameter
435 * a safety measure because macros can't return values.
436 *###
437 #macro(findNamesOfAppsToConfigure, $section, $globaladmin, $space, $outputList)
438 ## Use a parameterized sql query to prevent injection.
439 #set($params = [])
440 #if($section && $section != '')
441 #set($discard = $params.add("$section"))
442 #set($sqlA = ' StringProperty as section,')
443 #set($sqlB = " and section.id=obj.id and section.name='displayInSection' and section.value=?")
444 #else
445 ## Make sure they are "" in case they were set prior to calling the macro.
446 #set($sqlA = '')
447 #set($sqlB = '')
448 #end
449 ## Set up query based on value of $globaladmin
450 #if($globaladmin == true)
451 #set($sqlC = '1')
452 #else
453 #if($space && $space != '')
454 #set($sqlC = '0 and doc.space = ?')
455 #set($discard = $params.add($space))
456 #else
457 #set($sqlC = '0')
458 #end
459 #end
460 #set($sql = ", BaseObject as obj,$sqlA IntegerProperty as global where "
461 + "doc.fullName=obj.name and obj.className='XWiki.Configurable'$sqlB "
462 + "and global.id=obj.id and global.name='configureGlobally' and global.value=$sqlC "
463 + "order by doc.creationDate")
464 ##
465 ## Run the search
466 #set($outputList = $xwiki.searchDocuments($sql, 0, 0, $params))
467 ##
468 #end
469 ##
470 #*
471 * If this document is saved with programming access or is includeForm'd into a document with programming, we have to
472 * drop programming rights in order for it to run safely because it evaluates potentially untrustworthy code.
473 *###
474 #macro(sandboxDocument)
475 #if($xcontext.hasProgrammingRights())
476 $xcontext.getContext().getDoc().setContentAuthor('XWiki.XWikiGuest')
477 #end
478 #end
479 ##
480 #*
481 * Try to determine whether a document was edited by a user who has edit right on this page. This is trickey because
482 * documents are imported with the name XWiki.XWikiGuest who has no access to anything after import.
483 *
484 * @param theDoc - Document who's editor should be checked for edit access on this document.
485 *#
486 #macro(checkDocumentSavedByAuthorizedUser, $docToCheck, $currentDoc, $hasAccess)
487 ## The system is started and the only user is XWikiGuest who has admin right but gives it up when he imports the default
488 ## documents, we are checking to see if this looks like the guest imported the document with the first import.
489 #if($docToCheck.getWiki() == $xcontext.getMainWikiName()
490 && $docToCheck.getVersion() == '1.1'
491 && $docToCheck.getCreator() != $docToCheck.getContentAuthor()
492 && $docToCheck.getContentAuthor() == 'XWiki.XWikiGuest')
493 ##
494 #set($userToCheck = $docToCheck.getCreator())
495 #else
496 #set($userToCheck = $docToCheck.getAuthor())
497 #end
498 #set($hasAccess = $xwiki.hasAccessLevel("edit", $userToCheck, $currentDoc))
499 #end
500 {{/velocity}}