Wiki source code of Document Tree Macros
Last modified by ztx lyghters on 2025/01/31 15:59
Show last authors
author | version | line-number | content |
---|---|---|---|
1 | {{include reference="XWiki.SuggestSolrMacros" /}} | ||
2 | |||
3 | {{template name="documentTree_macros.vm" /}} | ||
4 | |||
5 | {{velocity output="false"}} | ||
6 | #macro (updateDocTreeConfigFromRequest) | ||
7 | #foreach ($entry in $docTreeConfig.entrySet()) | ||
8 | #set ($valueFromRequest = $request.getParameter($entry.key)) | ||
9 | #if ("$!valueFromRequest" != '') | ||
10 | #if ($entry.value.getClass().getName() == 'java.lang.Boolean') | ||
11 | #set ($entry.value = $valueFromRequest == 'true') | ||
12 | #elseif ($entry.value.iterator()) | ||
13 | #set ($valuesFromRequest = $request.getParameterValues($entry.key)) | ||
14 | #set ($discard = $entry.value.clear()) | ||
15 | ## We need to convert the String[] to List<String> before calling addAll (which expects a collection). | ||
16 | #set ($discard = $entry.value.addAll($valuesFromRequest.subList(0, $valuesFromRequest.size()))) | ||
17 | #else | ||
18 | #set ($entry.value = $valueFromRequest) | ||
19 | #end | ||
20 | #end | ||
21 | #end | ||
22 | ## Show the wikis only for global users. | ||
23 | #set ($docTreeConfig.showWikis = $docTreeConfig.showWikis && | ||
24 | $xcontext.userReference.wikiReference.name == $xcontext.mainWikiName) | ||
25 | #if ("$!docTreeConfig.root" == '') | ||
26 | #if ($docTreeConfig.showWikis) | ||
27 | #set ($docTreeConfig.root = 'farm:*') | ||
28 | #else | ||
29 | #set ($docTreeConfig.root = "wiki:$xcontext.database") | ||
30 | #end | ||
31 | #end | ||
32 | ## Handle relative references | ||
33 | #makeNodeReferencesAbsolute($docTreeConfig ['root', 'openTo']) | ||
34 | ## FIXME: The 'orderBy' property of the tree API is shared by all tree node types, which means we can't indicate a | ||
35 | ## different sort field per tree node type (e.g. sort wiki nodes by name and document nodes by last modification | ||
36 | ## date). At the same time, this property is currently taken into account only for sorting document tree nodes, so for | ||
37 | ## now we set its value to the specified document sort. In the future we may want to convert this into a map, where | ||
38 | ## the key is the node type. | ||
39 | #set ($docTreeConfig.orderBy = $docTreeConfig.sortDocumentsBy) | ||
40 | ## Sort the child documents by (raw) title when the node label is the document title and there's no sort specified. | ||
41 | #if ($docTreeConfig.showDocumentTitle && "$!docTreeConfig.orderBy" == '') | ||
42 | #set ($docTreeConfig.orderBy = 'title:asc') | ||
43 | #end | ||
44 | ## Determine which hierarchy needs to be used. | ||
45 | #if ($docTreeConfig.showSpaces) | ||
46 | #if ($docTreeConfig.hierarchyMode == 'parentchild') | ||
47 | #set ($tree = $services.tree.parentChildOnNestedSpaces) | ||
48 | #else | ||
49 | #set ($tree = $services.tree.nestedSpaces) | ||
50 | #end | ||
51 | #elseif ($docTreeConfig.hierarchyMode == 'parentchild') | ||
52 | #set ($tree = $services.tree.parentChild) | ||
53 | #else | ||
54 | #set ($tree = $services.tree.nestedPages) | ||
55 | #end | ||
56 | #set ($discard = $tree.properties.putAll($docTreeConfig)) | ||
57 | #end | ||
58 | |||
59 | #set ($documentPseudoNodeTypes = ['translations', 'attachments', 'classProperties', 'objects', 'addDocument', | ||
60 | 'addAttachment']) | ||
61 | #macro (makeNodeReferencesAbsolute $map $keys) | ||
62 | #foreach ($key in $keys) | ||
63 | #set ($nodeId = $map.get($key)) | ||
64 | #set ($parts = $nodeId.split(':', 2)) | ||
65 | #if ($parts && $parts.size() == 2) | ||
66 | #set ($nodeType = $parts[0].toLowerCase()) | ||
67 | #set ($nodeReference = $parts[1]) | ||
68 | #set ($entityType = $nodeType) | ||
69 | #if ($documentPseudoNodeTypes.contains($nodeType)) | ||
70 | #set ($entityType = 'document') | ||
71 | #end | ||
72 | #set ($discard = "#evaluate(""${escapetool.h}set (${escapetool.d}entityReference = | ||
73 | ${escapetool.d}services.model.resolve$stringtool.capitalize($entityType)(${escapetool.d}nodeReference))"")") | ||
74 | #if ($entityReference) | ||
75 | #set ($nodeReference = $services.model.serialize($entityReference, 'default')) | ||
76 | #end | ||
77 | #set ($discard = $map.put($key, "$nodeType:$nodeReference")) | ||
78 | #end | ||
79 | #end | ||
80 | #end | ||
81 | |||
82 | #macro (handleDocumentTreeRequest) | ||
83 | #if ($request.action) | ||
84 | #if ($services.csrf.isTokenValid($request.form_token)) | ||
85 | #if ($request.action == 'create' && $request.type == 'addDocument') | ||
86 | #handleNewNodeCreationRequest() | ||
87 | #else | ||
88 | $response.sendError(400, 'The specified action is not supported.') | ||
89 | #end | ||
90 | #elseif ($isAjaxRequest) | ||
91 | $response.sendError(403, 'The CSRF token is missing.') | ||
92 | #else | ||
93 | $response.sendRedirect($services.csrf.getResubmissionURL()) | ||
94 | #end | ||
95 | #else | ||
96 | #set ($data = $NULL) | ||
97 | #if ($request.data == 'children') | ||
98 | #getChildren($request.id $data) | ||
99 | #elseif ($request.data == 'path') | ||
100 | #getPath($request.id $data) | ||
101 | #elseif ($request.data == 'contextMenu') | ||
102 | #getContextMenu($data) | ||
103 | #elseif ($request.data == 'suggestions') | ||
104 | #getSuggestions($data) | ||
105 | #end | ||
106 | #if ($data) | ||
107 | #postProcessDocumentTreeData($data) | ||
108 | #jsonResponse($data) | ||
109 | #else | ||
110 | $response.sendError(404) | ||
111 | #end | ||
112 | #end | ||
113 | #end | ||
114 | |||
115 | #macro (handleNewNodeCreationRequest) | ||
116 | #set ($cleanId = $stringtool.substring($request.id, $stringtool.length('document:'))) | ||
117 | #set ($parentReference = $services.model.resolveDocument($cleanId)) | ||
118 | #set ($requestedName = $request.name) | ||
119 | #set ($transformedName = $services.modelvalidation.transformName($requestedName)) | ||
120 | #set ($spaceReference = $services.model.createSpaceReference($transformedName, $parentReference.lastSpaceReference)) | ||
121 | #set ($documentReference = $services.model.createDocumentReference('WebHome', $spaceReference)) | ||
122 | #set ($data = []) | ||
123 | #addDocumentNode($documentReference, $data) | ||
124 | ## We want to allow opening the node to add another hierarchy. | ||
125 | #set ($data[0].children = true) | ||
126 | ## We want to display the actual requested name as node name. | ||
127 | #set ($data[0].text = $requestedName) | ||
128 | #jsonResponse($data) | ||
129 | #end | ||
130 | |||
131 | #macro (postProcessDocumentTreeData $data) | ||
132 | ## This is just a hook to allow post processing the document tree data. | ||
133 | #end | ||
134 | |||
135 | ##------------------------------------------------------------ | ||
136 | ## Children | ||
137 | ##------------------------------------------------------------ | ||
138 | |||
139 | #macro (getChildren $nodeId $return) | ||
140 | #set ($children = []) | ||
141 | #if ($nodeId == '#') | ||
142 | ## Return the top level nodes. | ||
143 | #set ($actualNodeId = $docTreeConfig.root) | ||
144 | #else | ||
145 | ## Return the children of the specified node. | ||
146 | #set ($actualNodeId = $nodeId) | ||
147 | #end | ||
148 | #set ($offset = $mathtool.max($numbertool.toNumber($request.offset).intValue(), 0)) | ||
149 | #if ("$!offset" == '') | ||
150 | #set ($offset = 0) | ||
151 | #end | ||
152 | #set ($limit = $mathtool.max($numbertool.toNumber($request.limit).intValue(), 1)) | ||
153 | #if ("$!limit" == '') | ||
154 | #set ($limit = 15) | ||
155 | #end | ||
156 | #if ($nodeId == '#' && $docTreeConfig.showRoot) | ||
157 | #maybeAddNode($actualNodeId $children) | ||
158 | #else | ||
159 | #addChildNodes($actualNodeId $offset $limit $children) | ||
160 | #end | ||
161 | #if ($children.isEmpty() && $nodeId == '#') | ||
162 | ## Inform the user that the tree is empty. | ||
163 | #addEmptyTreeNode($children) | ||
164 | #end | ||
165 | #set ($return = $NULL) | ||
166 | #setVariable("$return" $children) | ||
167 | #end | ||
168 | |||
169 | #macro (maybeAddNode $nodeId $siblings $placeholder) | ||
170 | #set ($parts = $nodeId.split(':', 2)) | ||
171 | #if ($parts && $parts.size() == 2) | ||
172 | #set ($nodeType = $parts[0]) | ||
173 | #set ($nodeReference = $parts[1]) | ||
174 | #set ($discard = "#evaluate(""${escapetool.h}maybeAdd$stringtool.capitalize($nodeType)Node( | ||
175 | ${escapetool.d}nodeReference ${escapetool.d}siblings ${escapetool.d}placeholder)"")") | ||
176 | #end | ||
177 | #end | ||
178 | |||
179 | #macro (addChildNodes $nodeId $offset $limit $children) | ||
180 | ## Avoid pages with only one node when paginating the child nodes. | ||
181 | #set ($actualLimit = $limit + 1) | ||
182 | #set ($childNodeIds = $tree.getChildren($nodeId, $offset, $actualLimit)) | ||
183 | #set ($hasMoreChildNodes = false) | ||
184 | #if ($childNodeIds.size() >= $actualLimit) | ||
185 | #set ($totalCount = $tree.getChildCount($nodeId)) | ||
186 | #set ($newOffset = $offset + $actualLimit) | ||
187 | #if ($newOffset < $totalCount) | ||
188 | ## There are at least 2 more child nodes. | ||
189 | #set ($hasMoreChildNodes = true) | ||
190 | #set ($newOffset = $newOffset - 1) | ||
191 | #set ($childNodeIds = $childNodeIds.subList(0, $limit)) | ||
192 | #end | ||
193 | #end | ||
194 | #foreach ($childNodeId in $childNodeIds) | ||
195 | #maybeAddNode($childNodeId $children) | ||
196 | #end | ||
197 | #if ($hasMoreChildNodes) | ||
198 | #addPaginationNode($nodeId $newOffset $totalCount $children) | ||
199 | #end | ||
200 | #end | ||
201 | |||
202 | ## | ||
203 | ## Farm Node | ||
204 | ## | ||
205 | |||
206 | #macro (maybeAddFarmNode $nodeReference $siblings) | ||
207 | #set ($farmHomeReference = $services.model.resolveDocument('', 'default')) | ||
208 | #set ($discard = $siblings.add({ | ||
209 | 'id': 'farm:*', | ||
210 | 'text': 'Farm', | ||
211 | 'icon': 'fa fa-home', | ||
212 | 'children': true, | ||
213 | 'data': { | ||
214 | 'type': 'farm', | ||
215 | 'validChildren': ['wiki', 'pagination'] | ||
216 | }, | ||
217 | 'a_attr': { | ||
218 | 'href': $xwiki.getURL($farmHomeReference) | ||
219 | } | ||
220 | })) | ||
221 | #end | ||
222 | |||
223 | ## | ||
224 | ## Wiki Nodes | ||
225 | ## | ||
226 | |||
227 | #macro (maybeAddWikiNode $wikiId $siblings $placeholder) | ||
228 | #set ($wiki = $services.wiki.getById($wikiId)) | ||
229 | #if ($wiki && (!$docTreeConfig.showOnlyViewable || $services.security.authorization.hasAccess('view', $wiki.reference))) | ||
230 | #addWikiNode($wiki $siblings) | ||
231 | #elseif ($placeholder) | ||
232 | #set ($discard = $siblings.add($placeholder)) | ||
233 | #end | ||
234 | #end | ||
235 | |||
236 | #macro (addWikiNode $wiki $siblings) | ||
237 | ## The main wiki cannot be deleted. For the rest we need special rights. | ||
238 | #set ($canDeleteWiki = $wiki.id != $services.wiki.mainWikiId | ||
239 | && $services.wiki.canDeleteWiki($xcontext.user, $wiki.id)) | ||
240 | #if ($docTreeConfig.showWikiPrettyName) | ||
241 | #set ($label = $wiki.prettyName) | ||
242 | #else | ||
243 | #set ($label = $wiki.id) | ||
244 | #end | ||
245 | #set ($discard = $siblings.add({ | ||
246 | 'id': "wiki:$wiki.id", | ||
247 | 'text': $label, | ||
248 | 'icon': 'fa fa-hdd-o', | ||
249 | 'children': true, | ||
250 | 'data': { | ||
251 | 'id': $wiki.id, | ||
252 | 'type': 'wiki', | ||
253 | 'validChildren': ['space', 'document', 'pagination'], | ||
254 | 'canDelete': $canDeleteWiki | ||
255 | }, | ||
256 | 'a_attr': { | ||
257 | 'href': $xwiki.getURL($wiki.mainPageReference) | ||
258 | } | ||
259 | })) | ||
260 | #end | ||
261 | |||
262 | ## | ||
263 | ## Space Nodes | ||
264 | ## | ||
265 | |||
266 | #macro (maybeAddSpaceNode $spaceIdOrReference $siblings $placeholder) | ||
267 | #if ($spaceIdOrReference.type) | ||
268 | #set ($spaceReference = $spaceIdOrReference) | ||
269 | #else | ||
270 | #set ($spaceReference = $services.model.resolveSpace($spaceIdOrReference)) | ||
271 | #end | ||
272 | #if (!$docTreeConfig.showOnlyViewable || $services.security.authorization.hasAccess('view', $spaceReference)) | ||
273 | #addSpaceNode($spaceReference $siblings) | ||
274 | #elseif ($placeholder) | ||
275 | #set ($discard = $siblings.add($placeholder)) | ||
276 | #end | ||
277 | #end | ||
278 | |||
279 | #macro (addSpaceNode $spaceReference $siblings) | ||
280 | #set ($spaceId = $services.model.serialize($spaceReference, 'default')) | ||
281 | #set ($hasSpaceAdmin = $services.security.authorization.hasAccess('admin', $spaceReference)) | ||
282 | #set ($canViewSpace = $services.security.authorization.hasAccess('view', $spaceReference)) | ||
283 | #if ($docTreeConfig.showTerminalDocuments) | ||
284 | ## Each space has at least one document or one sub-space. There's no such thing as "empty space" in the model. | ||
285 | #set ($hasChildren = true) | ||
286 | #else | ||
287 | ## We display only the nested spaces. This space might contain only documents. | ||
288 | #set ($hasChildren = $tree.getChildCount("space:$spaceId") > 0) | ||
289 | #end | ||
290 | #set ($discard = $siblings.add({ | ||
291 | 'id': "space:$spaceId", | ||
292 | 'text': $spaceReference.name, | ||
293 | 'icon': 'fa fa-folder-o', | ||
294 | 'iconOpened': 'fa fa-folder-open-o', | ||
295 | 'children': $hasChildren, | ||
296 | 'data': { | ||
297 | 'id': $spaceId, | ||
298 | 'type': 'space', | ||
299 | 'validChildren': ['addDocument', 'space', 'document', 'pagination'], | ||
300 | 'hasContextMenu': true, | ||
301 | 'draggable': $canViewSpace, | ||
302 | 'canMove': $hasSpaceAdmin, | ||
303 | 'canCopy': $canViewSpace, | ||
304 | 'canRename': $hasSpaceAdmin, | ||
305 | 'canDelete': $hasSpaceAdmin, | ||
306 | 'createDocumentURL': $xwiki.getURL($spaceReference, 'create', $NULL), | ||
307 | 'deleteURL': $xwiki.getURL($spaceReference, 'deletespace', $NULL) | ||
308 | }, | ||
309 | 'a_attr': { | ||
310 | 'href': $xwiki.getURL($spaceReference) | ||
311 | } | ||
312 | })) | ||
313 | #end | ||
314 | |||
315 | ## | ||
316 | ## Document Nodes | ||
317 | ## | ||
318 | |||
319 | #macro (maybeAddDocumentNode $documentIdOrReference $siblings $placeholder) | ||
320 | #if ($documentIdOrReference.type) | ||
321 | #set ($documentReference = $documentIdOrReference) | ||
322 | #else | ||
323 | #set ($documentReference = $services.model.resolveDocument($documentIdOrReference)) | ||
324 | #end | ||
325 | #if (!$docTreeConfig.showOnlyViewable || $services.security.authorization.hasAccess('view', $documentReference)) | ||
326 | #addDocumentNode($documentReference $siblings) | ||
327 | #elseif ($placeholder) | ||
328 | #set ($discard = $siblings.add($placeholder)) | ||
329 | #end | ||
330 | #end | ||
331 | |||
332 | #macro (addDocumentNode $documentReference $siblings) | ||
333 | #set ($documentId = $services.model.serialize($documentReference, 'default')) | ||
334 | #set ($label = $documentReference.name) | ||
335 | #if (!$docTreeConfig.showSpaces && | ||
336 | $documentReference.name == $services.model.getEntityReference('DOCUMENT', 'default').name) | ||
337 | ## Use the space name as default value for the node label (in case the document is not viewable). | ||
338 | #set ($label = $documentReference.parent.name) | ||
339 | #end | ||
340 | #set ($canViewDoc = $services.security.authorization.hasAccess('view', $documentReference)) | ||
341 | #set ($canDeleteDoc = $services.security.authorization.hasAccess('delete', $documentReference)) | ||
342 | #if ($canViewDoc && $docTreeConfig.showDocumentTitle) | ||
343 | ## Display the translated title. | ||
344 | #set ($translatedDocument = $xwiki.getDocument($documentReference).translatedDocument) | ||
345 | #set ($plainTitle = $translatedDocument.plainTitle) | ||
346 | #if (!$stringtool.isBlank($plainTitle)) | ||
347 | #set ($label = $plainTitle) | ||
348 | #end | ||
349 | #end | ||
350 | #set ($hasChildren = $tree.getChildCount("document:$documentId") > 0) | ||
351 | #set ($discard = $siblings.add({ | ||
352 | 'id': "document:$documentId", | ||
353 | 'text': $label, | ||
354 | 'icon': 'fa fa-file-o', | ||
355 | 'children': $hasChildren, | ||
356 | 'data': { | ||
357 | 'id': $services.model.serialize($documentReference, 'default'), | ||
358 | 'type': 'document', | ||
359 | 'validChildren': ['translations', 'attachments', 'attachment', 'classProperties', 'objects', 'document', 'pagination'], | ||
360 | 'hasContextMenu': true, | ||
361 | 'draggable': $canViewDoc, | ||
362 | 'canDelete': $canDeleteDoc, | ||
363 | 'canMove': $canDeleteDoc, | ||
364 | 'canCopy': $canViewDoc, | ||
365 | 'createDocumentURL': $xwiki.getURL($documentReference, 'create', $NULL) | ||
366 | }, | ||
367 | 'a_attr': { | ||
368 | 'href': $xwiki.getURL($documentReference) | ||
369 | } | ||
370 | })) | ||
371 | #end | ||
372 | |||
373 | #macro (maybeAddAddDocumentNode $documentId $siblings) | ||
374 | #set ($documentReference = $services.model.resolveDocument($documentId)) | ||
375 | #if ($services.security.authorization.hasAccess('edit', $documentReference.parent)) | ||
376 | #addAddDocumentNode($documentReference $siblings) | ||
377 | #end | ||
378 | #end | ||
379 | |||
380 | #macro (addAddDocumentNode $documentReference $siblings) | ||
381 | ## FIXME: This URL is wrong, it should use the $documentReference as the parent for creation of the node: | ||
382 | ## the reference is already an existing doc, so it shouldn't be the one used for creation. | ||
383 | #set ($discard = $siblings.add({ | ||
384 | 'id': "addDocument:$services.model.serialize($documentReference, 'default')", | ||
385 | 'text': $services.localization.render('index.documentTree.addDocument'), | ||
386 | 'icon': 'fa fa-plus-circle', | ||
387 | 'children': false, | ||
388 | 'data': { | ||
389 | 'type': 'addDocument', | ||
390 | 'validChildren': [], | ||
391 | 'hasContextMenu': true, | ||
392 | 'canRename': true | ||
393 | }, | ||
394 | 'a_attr': { | ||
395 | 'href': $xwiki.getURL($documentReference, 'create') | ||
396 | } | ||
397 | })) | ||
398 | #end | ||
399 | |||
400 | ## | ||
401 | ## Translation Nodes | ||
402 | ## | ||
403 | |||
404 | #macro (maybeAddTranslationsNode $documentId $siblings) | ||
405 | #set ($documentReference = $services.model.resolveDocument($documentId)) | ||
406 | #if ($services.security.authorization.hasAccess('view', $documentReference)) | ||
407 | #addTranslationsNode($documentReference $siblings) | ||
408 | #end | ||
409 | #end | ||
410 | |||
411 | #macro (addTranslationsNode $documentReference $siblings) | ||
412 | #set ($discard = $children.add({ | ||
413 | 'id': "translations:${documentReference}", | ||
414 | 'text': 'Translations', | ||
415 | 'icon': 'fa fa-language', | ||
416 | 'children': true, | ||
417 | 'data': { | ||
418 | 'type': 'translations', | ||
419 | 'validChildren': ['translation'], | ||
420 | 'canDelete': $services.security.authorization.hasAccess('delete', $documentReference) | ||
421 | } | ||
422 | })) | ||
423 | #end | ||
424 | |||
425 | #macro (maybeAddTranslationNode $nodeReference $siblings) | ||
426 | #set ($documentId = $stringtool.substringBeforeLast($nodeReference, '/')) | ||
427 | #set ($locale = $services.localization.toLocale($stringtool.substringAfterLast($nodeReference, '/'))) | ||
428 | #set ($documentReference = $services.model.resolveDocument($documentId)) | ||
429 | #set ($translationReference = $services.model.createDocumentReference($documentReference, $locale)) | ||
430 | #if ($services.security.authorization.hasAccess('view', $documentReference)) | ||
431 | #addTranslationNode($translationReference $siblings) | ||
432 | #end | ||
433 | #end | ||
434 | |||
435 | #macro (addTranslationNode $translationReference $siblings) | ||
436 | #set ($currentLocale = $services.localization.currentLocale) | ||
437 | #set ($discard = $siblings.add({ | ||
438 | 'id': "translation:$services.model.serialize($translationReference, 'default')_$translationReference.locale", | ||
439 | 'text': $translationReference.locale.getDisplayName($currentLocale), | ||
440 | 'icon': 'fa fa-file-text-o', | ||
441 | 'children': false, | ||
442 | 'data': { | ||
443 | 'type': 'translation', | ||
444 | 'validChildren': [], | ||
445 | 'canDelete': $services.security.authorization.hasAccess('delete', $translationReference) | ||
446 | }, | ||
447 | 'a_attr': { | ||
448 | 'href': $xwiki.getURL($translationReference) | ||
449 | } | ||
450 | })) | ||
451 | #end | ||
452 | |||
453 | ## | ||
454 | ## Attachment Nodes | ||
455 | ## | ||
456 | |||
457 | #macro (maybeAddAttachmentsNode $documentId $siblings) | ||
458 | #set ($documentReference = $services.model.resolveDocument($documentId)) | ||
459 | #if ($services.security.authorization.hasAccess('view', $documentReference)) | ||
460 | #addAttachmentsNode($documentReference $siblings) | ||
461 | #end | ||
462 | #end | ||
463 | |||
464 | #macro (addAttachmentsNode $documentReference $siblings) | ||
465 | #set ($discard = $siblings.add({ | ||
466 | 'id': "attachments:${documentReference}", | ||
467 | 'text': 'Attachments', | ||
468 | 'icon': 'fa fa-paperclip', | ||
469 | 'children': true, | ||
470 | 'data': { | ||
471 | 'type': 'attachments', | ||
472 | 'validChildren': ['addAttachment', 'attachment', 'pagination'], | ||
473 | 'hasContextMenu': true, | ||
474 | 'canDelete': $services.security.authorization.hasAccess('edit', $documentReference) | ||
475 | }, | ||
476 | 'a_attr': { | ||
477 | 'href': $xwiki.getURL($documentReference, 'view', 'viewer=attachments') | ||
478 | } | ||
479 | })) | ||
480 | #end | ||
481 | |||
482 | #macro (maybeAddAttachmentNode $attachmentId $siblings)) | ||
483 | #set ($attachmentReference = $services.model.resolveAttachment($attachmentId)) | ||
484 | #set ($document = $xwiki.getDocument($attachmentReference)) | ||
485 | #set ($attachment = $document.getAttachment($attachmentReference.name)) | ||
486 | #if ($attachment) | ||
487 | #addAttachmentNode($attachment $siblings) | ||
488 | #end | ||
489 | #end | ||
490 | |||
491 | #macro (addAttachmentNode $attachment $siblings) | ||
492 | #set ($attachmentReference = $services.model.createAttachmentReference($attachment.document.documentReference, | ||
493 | $attachment.filename)) | ||
494 | #set ($attachmentId = $services.model.serialize($attachmentReference, 'default')) | ||
495 | #set ($canEditDoc = $services.security.authorization.hasAccess('edit', $attachmentReference.parent)) | ||
496 | #getAttachmentIcon($attachment $icon) | ||
497 | #set ($discard = $siblings.add({ | ||
498 | 'id': "attachment:$attachmentId", | ||
499 | 'text': $attachment.filename, | ||
500 | 'icon': $icon, | ||
501 | 'children': false, | ||
502 | 'data': { | ||
503 | 'id': $attachmentId, | ||
504 | 'type': 'attachment', | ||
505 | 'validChildren': [], | ||
506 | 'hasContextMenu': true, | ||
507 | 'draggable': true, | ||
508 | 'canRename': $canEditDoc, | ||
509 | 'canDelete': $canEditDoc, | ||
510 | 'canMove': $canEditDoc, | ||
511 | 'canCopy': true, | ||
512 | 'deleteURL': $attachment.document.getAttachmentURL($attachment.filename, 'delattachment'), | ||
513 | 'mimetype': $attachment.mimeType | ||
514 | }, | ||
515 | 'a_attr': { | ||
516 | 'href': $attachment.document.getAttachmentURL($attachment.filename) | ||
517 | } | ||
518 | })) | ||
519 | #end | ||
520 | |||
521 | #set ($fileIconByMediaType = { | ||
522 | 'text': ['text/', 'application/xml', 'application/javascript', 'application/ecmascript', 'application/json', 'application/x-sh', '+xml'], | ||
523 | 'image': ['image/'], | ||
524 | 'audio': ['audio/'], | ||
525 | 'video': ['video/'], | ||
526 | 'pdf': ['application/pdf', 'application/postscript'], | ||
527 | 'word': ['application/msword', 'application/vnd.ms-word.', 'application/vnd.oasis.opendocument.text', 'application/vnd.openxmlformats-officedocument.word'], | ||
528 | 'powerpoint': ['application/vnd.ms-powerpoint', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.openxmlformats-officedocument.presentation'], | ||
529 | 'excel': ['application/vnd.ms-excel', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.openxmlformats-officedocument.spreadsheet'], | ||
530 | 'archive': ['application/zip', 'application/x-gzip', 'application/x-bzip', 'application/x-tar', 'application/x-gtar', 'application/vnd.xara', '-archive', '-compressed', '-package', '+zip'] | ||
531 | }) | ||
532 | |||
533 | #macro (getAttachmentIcon $attachment $return) | ||
534 | #set ($mediaType = $attachment.mimeType) | ||
535 | #set ($icon = $NULL) | ||
536 | #foreach ($entry in $fileIconByMediaType.entrySet()) | ||
537 | #foreach ($pattern in $entry.value) | ||
538 | #if ($mediaType.startsWith($pattern) || $mediaType.endsWith($pattern)) | ||
539 | #set ($icon = $entry.key) | ||
540 | #break | ||
541 | #end | ||
542 | #end | ||
543 | #if ($icon) | ||
544 | #break | ||
545 | #end | ||
546 | #end | ||
547 | #set ($suffix = $stringtool.substringAfterLast($attachment.filename, '.')) | ||
548 | #set ($codeSuffixes = ['html', 'css', 'js', 'java', 'c', 'cpp', 'c++', 'cs', 'h', 'sql', 'php', 'ruby']) | ||
549 | #if (!$icon) | ||
550 | #set ($icon = 'fa fa-paperclip') | ||
551 | #elseif ($icon == 'text' && $codeSuffixes.contains($suffix)) | ||
552 | #set ($icon = 'fa fa-file-code-o') | ||
553 | #else | ||
554 | #set ($icon = "fa fa-file-${icon}-o") | ||
555 | #end | ||
556 | #set ($return = $NULL) | ||
557 | #setVariable("$return" $icon) | ||
558 | #end | ||
559 | |||
560 | #macro (maybeAddAddAttachmentNode $documentId $siblings) | ||
561 | #set ($documentReference = $services.model.resolveDocument($documentId)) | ||
562 | #if ($services.security.authorization.hasAccess('edit', $documentReference)) | ||
563 | #addAddAttachmentNode($documentReference $siblings) | ||
564 | #end | ||
565 | #end | ||
566 | |||
567 | #macro (addAddAttachmentNode $documentReference $siblings) | ||
568 | #set ($discard = $siblings.add({ | ||
569 | 'id': "addAttachment:$documentReference", | ||
570 | 'text': 'Upload file...', | ||
571 | 'icon': 'fa fa-plus-circle', | ||
572 | 'children': false, | ||
573 | 'data': { | ||
574 | 'type': 'addAttachment', | ||
575 | 'validChildren': [] | ||
576 | }, | ||
577 | 'a_attr': { | ||
578 | 'href': $xwiki.getURL($documentReference, 'view', 'viewer=attachments') | ||
579 | } | ||
580 | })) | ||
581 | #end | ||
582 | |||
583 | ## | ||
584 | ## Class Property Nodes | ||
585 | ## | ||
586 | |||
587 | #macro (maybeAddClassPropertiesNode $documentId $siblings) | ||
588 | #set ($documentReference = $services.model.resolveDocument($documentId)) | ||
589 | #if ($services.security.authorization.hasAccess('view', $documentReference)) | ||
590 | #addClassPropertiesNode($documentReference $siblings) | ||
591 | #end | ||
592 | #end | ||
593 | |||
594 | #macro (addClassPropertiesNode $documentReference $siblings) | ||
595 | #set ($discard = $children.add({ | ||
596 | 'id': "classProperties:${documentReference}", | ||
597 | 'text': 'Class Properties', | ||
598 | 'icon': 'fa fa-gears', | ||
599 | 'children': true, | ||
600 | 'data': { | ||
601 | 'type': 'classProperties', | ||
602 | 'validChildren': ['classProperty'], | ||
603 | 'canDelete': $services.security.authorization.hasAccess('edit', $documentReference) | ||
604 | } | ||
605 | })) | ||
606 | #end | ||
607 | |||
608 | #set ($iconByPropertyType = { | ||
609 | 'Boolean': 'check-square-o', | ||
610 | 'Date': 'calendar-o', | ||
611 | 'DBList': 'database', | ||
612 | 'Groups': 'group', | ||
613 | 'Password': 'asterisk', | ||
614 | 'Levels': 'lock', | ||
615 | 'StaticList': 'list', | ||
616 | 'TextArea': 'paragraph', | ||
617 | 'DBTreeList': 'sitemap', | ||
618 | 'Users': 'user' | ||
619 | }) | ||
620 | |||
621 | #macro (maybeAddClassPropertyNode $classPropertyId $siblings) | ||
622 | #set ($classPropertyReference = $services.model.resolveClassProperty($classPropertyId)) | ||
623 | #if ($services.security.authorization.hasAccess('view', $classPropertyReference.parent)) | ||
624 | #addClassPropertyNode($classPropertyReference $siblings) | ||
625 | #end | ||
626 | #end | ||
627 | |||
628 | #macro (addClassPropertyNode $classPropertyReference $siblings) | ||
629 | #set ($classPropertyId = $services.model.serialize($classPropertyReference, 'default')) | ||
630 | #set ($xclass = $xwiki.getDocument($classPropertyReference).getxWikiClass()) | ||
631 | #set ($property = $xclass.get($classPropertyReference.name)) | ||
632 | #set ($icon = $iconByPropertyType.get($property.classType)) | ||
633 | #if (!$icon) | ||
634 | #set ($icon = 'gear') | ||
635 | #end | ||
636 | #set ($discard = $siblings.add({ | ||
637 | 'id': "classProperty:$classPropertyId", | ||
638 | 'text': $property.name, | ||
639 | 'icon': "fa fa-$icon", | ||
640 | 'children': false, | ||
641 | 'data': { | ||
642 | 'id': $classPropertyId, | ||
643 | 'type': 'classProperty', | ||
644 | 'validChildren': [] | ||
645 | } | ||
646 | })) | ||
647 | #end | ||
648 | |||
649 | ## | ||
650 | ## Object Nodes | ||
651 | ## | ||
652 | |||
653 | #macro (maybeAddObjectsNode $documentId $siblings) | ||
654 | #set ($documentReference = $services.model.resolveDocument($documentId)) | ||
655 | #if ($services.security.authorization.hasAccess('view', $documentReference)) | ||
656 | #addObjectsNode($documentReference $siblings) | ||
657 | #end | ||
658 | #end | ||
659 | |||
660 | #macro (addObjectsNode $documentReference $siblings) | ||
661 | #set ($discard = $children.add({ | ||
662 | 'id': "objects:${documentReference}", | ||
663 | 'text': 'Objects', | ||
664 | 'icon': 'fa fa-cubes', | ||
665 | 'children': true, | ||
666 | 'data': { | ||
667 | 'type': 'objects', | ||
668 | 'validChildren': ['objectsOfType'], | ||
669 | 'canDelete': $services.security.authorization.hasAccess('edit', $documentReference) | ||
670 | } | ||
671 | })) | ||
672 | #end | ||
673 | |||
674 | #macro (maybeAddObjectsOfTypeNode $nodeReference $siblings) | ||
675 | #set ($parts = $nodeReference.split('/', 2)) | ||
676 | #if ($parts && $parts.size() == 2) | ||
677 | #set ($documentReference = $services.model.resolveDocument($parts.get(0))) | ||
678 | #set ($classReference = $services.model.resolveDocument($parts.get(1))) | ||
679 | #if ($services.security.authorization.hasAccess('view', $documentReference)) | ||
680 | #set ($discard = $children.add({ | ||
681 | 'id': "objectsOfType:$documentReference/$classReference", | ||
682 | 'text': $services.model.serialize($classReference, 'local'), | ||
683 | 'icon': 'fa fa-cubes', | ||
684 | 'children': true, | ||
685 | 'data': { | ||
686 | 'type': 'objectsOfType', | ||
687 | 'validChildren': ['object', 'pagination'], | ||
688 | 'canDelete': $services.security.authorization.hasAccess('edit', $documentReference) | ||
689 | } | ||
690 | })) | ||
691 | #end | ||
692 | #end | ||
693 | #end | ||
694 | |||
695 | #macro (maybeAddObjectNode $objectId $siblings) | ||
696 | #set ($objectReference = $services.model.resolveObject($objectId)) | ||
697 | #getXObject($objectReference) | ||
698 | #if ($object) | ||
699 | #addObjectNode($object $objectReference $siblings) | ||
700 | #end | ||
701 | #end | ||
702 | |||
703 | #macro (getXObject $objectReference) | ||
704 | ## Object name is: Space.Class[index] | ||
705 | #set ($separatorIndex = $objectReference.name.lastIndexOf('[')) | ||
706 | #set ($classId = $objectReference.name.substring(0, $separatorIndex)) | ||
707 | #set ($objectNumber = $numbertool.toNumber($objectReference.name.substring($mathtool.add($separatorIndex, 1), | ||
708 | $mathtool.sub($objectReference.name.length(), 1))).intValue()) | ||
709 | #set ($document = $xwiki.getDocument($objectReference)) | ||
710 | #set ($object = $document.getObject($classId, $objectNumber)) | ||
711 | #end | ||
712 | |||
713 | #macro (addObjectNode $object $objectReference $siblings) | ||
714 | #set ($objectId = $services.model.serialize($objectReference, 'default')) | ||
715 | #set ($discard = $children.add({ | ||
716 | 'id': "object:$objectId", | ||
717 | 'text': "[$object.number]", | ||
718 | 'icon': 'fa fa-cube', | ||
719 | 'children': true, | ||
720 | 'data': { | ||
721 | 'id': $objectId, | ||
722 | 'type': 'object', | ||
723 | 'validChildren': ['objectProperty'], | ||
724 | 'canDelete': $services.security.authorization.hasAccess('edit', $objectReference.parent) | ||
725 | } | ||
726 | })) | ||
727 | #end | ||
728 | |||
729 | #macro (maybeAddObjectPropertyNode $objectPropertyId $siblings) | ||
730 | #set ($objectPropertyReference = $services.model.resolveObjectProperty($objectPropertyId)) | ||
731 | #set ($objectReference = $objectPropertyReference.parent) | ||
732 | #getXObject($objectReference) | ||
733 | #set ($property = $object.getProperty($objectPropertyReference.name)) | ||
734 | #if ($property) | ||
735 | #addObjectPropertyNode($property $objectReference $siblings) | ||
736 | #end | ||
737 | #end | ||
738 | |||
739 | #macro (addObjectPropertyNode $property $objRef $siblings) | ||
740 | #set ($classId = $stringtool.substringBeforeLast($objRef.name, '[')) | ||
741 | #set ($classRef = $services.model.resolveDocument($classId, 'explicit', $objRef)) | ||
742 | #set ($xclass = $xwiki.getDocument($classRef).getxWikiClass()) | ||
743 | #set ($icon = $iconByPropertyType.get($xclass.get($property.name).classType)) | ||
744 | #if (!$icon) | ||
745 | #set ($icon = 'gear') | ||
746 | #end | ||
747 | #set ($objectPropertyReference = $services.model.createEntityReference($property.name, 'OBJECT_PROPERTY', $objRef)) | ||
748 | #set ($objectPropertyId = $services.model.serialize($objectPropertyReference, 'default')) | ||
749 | #set ($discard = $siblings.add({ | ||
750 | 'id': "objectProperty:$objectPropertyId", | ||
751 | 'text': $property.name, | ||
752 | 'icon': "fa fa-$icon", | ||
753 | 'children': false, | ||
754 | 'data': { | ||
755 | 'id': $objectPropertyId, | ||
756 | 'type': 'objectProperty', | ||
757 | 'validChildren': [] | ||
758 | } | ||
759 | })) | ||
760 | #end | ||
761 | |||
762 | ## | ||
763 | ## Pagination Nodes | ||
764 | ## | ||
765 | |||
766 | #macro (addPaginationNode $parentId $offset $totalCount $siblings) | ||
767 | #set ($discard = $siblings.add({ | ||
768 | 'id': "pagination:$parentId", | ||
769 | 'text': $services.localization.render('index.documentTree.more', $!mathtool.sub($totalCount, $offset)), | ||
770 | 'icon': 'fa fa-eye', | ||
771 | 'children': false, | ||
772 | 'data': { | ||
773 | 'type': 'pagination', | ||
774 | 'validChildren': [], | ||
775 | 'canDelete': true, | ||
776 | 'offset': $offset | ||
777 | } | ||
778 | })) | ||
779 | #end | ||
780 | |||
781 | ## | ||
782 | ## Empty Tree Node | ||
783 | ## | ||
784 | |||
785 | #macro (addEmptyTreeNode $siblings) | ||
786 | #set ($discard = $siblings.add({ | ||
787 | 'id': "empty", | ||
788 | 'text': $services.localization.render('index.documentTree.empty'), | ||
789 | 'icon': 'fa fa-info-circle', | ||
790 | 'children': false, | ||
791 | 'data': { | ||
792 | 'type': 'empty', | ||
793 | 'validChildren': [] | ||
794 | } | ||
795 | })) | ||
796 | #end | ||
797 | |||
798 | ##------------------------------------------------------------ | ||
799 | ## Path | ||
800 | ##------------------------------------------------------------ | ||
801 | |||
802 | #macro (getPath $nodeId $return) | ||
803 | #set ($path = []) | ||
804 | #if ($docTreeConfig.showRoot) | ||
805 | #maybeAddNode($docTreeConfig.root $path {}) | ||
806 | #end | ||
807 | #foreach ($pathElement in $tree.getPath($nodeId)) | ||
808 | #maybeAddNode($pathElement $path {}) | ||
809 | #end | ||
810 | #set ($return = $NULL) | ||
811 | #setVariable("$return" $path) | ||
812 | #end | ||
813 | |||
814 | ##------------------------------------------------------------ | ||
815 | ## Context Menu | ||
816 | ##------------------------------------------------------------ | ||
817 | |||
818 | #macro (getContextMenu $return) | ||
819 | #set ($contextMenuByNodeType = {}) | ||
820 | #if ($docTreeConfig.showSpaces) | ||
821 | #addSpaceContextMenu($contextMenuByNodeType) | ||
822 | #end | ||
823 | #addDocumentContextMenu($contextMenuByNodeType) | ||
824 | #if ($docTreeConfig.showAttachments) | ||
825 | #addAttachmentsContextMenu($contextMenuByNodeType) | ||
826 | #addAttachmentContextMenu($contextMenuByNodeType) | ||
827 | #end | ||
828 | #set ($return = $NULL) | ||
829 | #setVariable("$return" $contextMenuByNodeType) | ||
830 | #end | ||
831 | |||
832 | #macro (addSpaceContextMenu $contextMenuByNodeType) | ||
833 | #set ($contextMenuByNodeType.space = { | ||
834 | 'createDocument': { | ||
835 | 'label': 'New Page', | ||
836 | 'icon': 'fa fa-file-o', | ||
837 | 'action': 'openLink', | ||
838 | 'parameters': { | ||
839 | 'urlProperty': 'createDocumentURL' | ||
840 | } | ||
841 | }, | ||
842 | 'openLink': { | ||
843 | 'separator_before': true, | ||
844 | 'label': 'Go to Space', | ||
845 | 'icon': 'fa fa-external-link' | ||
846 | }, | ||
847 | 'refresh': { | ||
848 | 'label': 'Refresh', | ||
849 | 'icon': 'fa fa-refresh' | ||
850 | }, | ||
851 | 'paste': { | ||
852 | 'separator_before': true, | ||
853 | 'label': 'Paste Into Space', | ||
854 | 'icon': 'fa fa-clipboard' | ||
855 | }, | ||
856 | 'rename': { | ||
857 | 'label': 'Rename...', | ||
858 | 'icon': 'fa fa-pencil-square-o' | ||
859 | }, | ||
860 | 'remove': { | ||
861 | 'label': 'Delete', | ||
862 | 'icon': 'fa fa-trash-o', | ||
863 | 'parameters': { | ||
864 | 'confirmationMessage': 'Are you sure you want to move ALL the documents from this space to the recycle bin? If there are hidden documents in this space they will also be deleted.' | ||
865 | } | ||
866 | } | ||
867 | }) | ||
868 | #end | ||
869 | |||
870 | #macro (addDocumentContextMenu $contextMenuByNodeType) | ||
871 | #set ($contextMenuByNodeType.document = { | ||
872 | 'createDocument': { | ||
873 | 'label': 'New Page', | ||
874 | 'icon': 'fa fa-file-o', | ||
875 | 'action': 'openLink', | ||
876 | 'parameters': { | ||
877 | 'urlProperty': 'createDocumentURL' | ||
878 | } | ||
879 | }, | ||
880 | 'openLink': { | ||
881 | 'separator_before': true, | ||
882 | 'label': 'Go to Page', | ||
883 | 'icon': 'fa fa-external-link' | ||
884 | }, | ||
885 | 'refresh': { | ||
886 | 'label': 'Refresh', | ||
887 | 'icon': 'fa fa-refresh' | ||
888 | }, | ||
889 | 'cut': { | ||
890 | 'separator_before': true, | ||
891 | 'label': 'Cut', | ||
892 | 'icon': 'fa fa-scissors' | ||
893 | }, | ||
894 | 'copy': { | ||
895 | 'label': 'Copy', | ||
896 | 'icon': 'fa fa-files-o' | ||
897 | }, | ||
898 | 'paste': { | ||
899 | 'label': 'Paste', | ||
900 | 'icon': 'fa fa-clipboard' | ||
901 | }, | ||
902 | 'remove': { | ||
903 | 'separator_before': true, | ||
904 | 'label': 'Delete', | ||
905 | 'icon': 'fa fa-trash-o', | ||
906 | 'parameters': { | ||
907 | 'confirmationMessage': 'Are you sure you want to move this document to the recycle bin? All child documents will become orphan as a result.' | ||
908 | } | ||
909 | } | ||
910 | }) | ||
911 | #end | ||
912 | |||
913 | #macro (addAttachmentsContextMenu $contextMenuByNodeType) | ||
914 | #set ($contextMenuByNodeType.attachments = { | ||
915 | 'openLink': { | ||
916 | 'label': 'Go to Attachments', | ||
917 | 'icon': 'fa fa-external-link' | ||
918 | }, | ||
919 | 'refresh': { | ||
920 | 'label': 'Refresh', | ||
921 | 'icon': 'fa fa-refresh' | ||
922 | }, | ||
923 | 'paste': { | ||
924 | 'separator_before': true, | ||
925 | 'label': 'Paste', | ||
926 | 'icon': 'fa fa-clipboard' | ||
927 | }, | ||
928 | 'remove': { | ||
929 | 'label': 'Delete All', | ||
930 | 'icon': 'fa fa-trash-o', | ||
931 | 'parameters': { | ||
932 | 'confirmationMessage': 'Are you sure you want to delete all the attachments of this page?' | ||
933 | } | ||
934 | } | ||
935 | }) | ||
936 | #end | ||
937 | |||
938 | #macro (addAttachmentContextMenu $contextMenuByNodeType) | ||
939 | #set ($contextMenuByNodeType.attachment = { | ||
940 | 'openLink': { | ||
941 | 'label': 'Go to Attachment', | ||
942 | 'icon': 'fa fa-external-link' | ||
943 | }, | ||
944 | 'cut': { | ||
945 | 'separator_before': true, | ||
946 | 'label': 'Cut', | ||
947 | 'icon': 'fa fa-scissors' | ||
948 | }, | ||
949 | 'copy': { | ||
950 | 'label': 'Copy', | ||
951 | 'icon': 'fa fa-files-o' | ||
952 | }, | ||
953 | 'rename': { | ||
954 | 'separator_before': true, | ||
955 | 'label': 'Rename...', | ||
956 | 'icon': 'fa fa-pencil-square-o' | ||
957 | }, | ||
958 | 'remove': { | ||
959 | 'label': 'Delete', | ||
960 | 'icon': 'fa fa-trash-o', | ||
961 | 'parameters': { | ||
962 | 'confirmationMessage': 'Are you sure you want to delete this attachment?' | ||
963 | } | ||
964 | } | ||
965 | }) | ||
966 | #end | ||
967 | |||
968 | ##------------------------------------------------------------ | ||
969 | ## Finder Suggestions | ||
970 | ##------------------------------------------------------------ | ||
971 | |||
972 | #macro (getSuggestions $return) | ||
973 | #set ($limit = 6) | ||
974 | #set ($text = "$!request.query") | ||
975 | #set ($lists = []) | ||
976 | #getRootReference | ||
977 | #set ($ancestorsOf = { | ||
978 | 'space': ['farm', 'wiki', 'space'], | ||
979 | 'document': ['farm', 'wiki', 'space', 'document'], | ||
980 | 'attachment': ['farm', 'wiki', 'space', 'document', 'attachments'] | ||
981 | }) | ||
982 | #if ((!$docTreeConfig.showSpaces || $docTreeConfig.showTerminalDocuments) | ||
983 | && $ancestorsOf.document.contains($rootType)) | ||
984 | #addDocumentSuggestions($text $limit $lists) | ||
985 | #end | ||
986 | #if ($docTreeConfig.showAttachments && $ancestorsOf.attachment.contains($rootType)) | ||
987 | #addAttachmentSuggestions($text $limit $lists) | ||
988 | #end | ||
989 | #if ($docTreeConfig.showSpaces && $ancestorsOf.space.contains($rootType)) | ||
990 | #addSpaceSuggestions($text $limit $lists) | ||
991 | #end | ||
992 | #limitTotalCount($lists $limit) | ||
993 | #set ($output = []) | ||
994 | #foreach ($list in $lists) | ||
995 | #foreach ($node in $list) | ||
996 | ## Use the node path as suggestion info. | ||
997 | #getPath($node.id $path) | ||
998 | ## The path is empty when the node is not found in the tree. This happens if the tree finder doesn't restrict the | ||
999 | ## search to the nodes that are available in the tree. | ||
1000 | #if ($path.size() > 0) | ||
1001 | #displayPath($path) | ||
1002 | #set ($node.data.info = $stringtool.join($path.subList(0, $mathtool.sub($path.size(), 1)), ' / ')) | ||
1003 | #set ($discard = $output.add($node)) | ||
1004 | #end | ||
1005 | #end | ||
1006 | #end | ||
1007 | #set ($return = $NULL) | ||
1008 | #setVariable("$return" $output) | ||
1009 | #end | ||
1010 | |||
1011 | #macro (getRootReference) | ||
1012 | #set ($parts = $docTreeConfig.root.split(':', 2)) | ||
1013 | #if ($parts.size() == 2) | ||
1014 | #set ($rootType = $parts[0]) | ||
1015 | #set ($rootReference = $parts[1]) | ||
1016 | #if ($rootType == 'wiki') | ||
1017 | #set ($rootReference = $services.model.createWikiReference($parts[1])) | ||
1018 | #elseif ($rootType == 'space') | ||
1019 | #set ($rootReference = $services.model.resolveSpace($parts[1])) | ||
1020 | #elseif ($rootType == 'document' || $rootType == 'attachments') | ||
1021 | #set ($rootReference = $services.model.resolveDocument($parts[1])) | ||
1022 | #end | ||
1023 | #else | ||
1024 | #set ($rootType = 'unknown') | ||
1025 | #set ($rootReference = $parts[0]) | ||
1026 | #end | ||
1027 | #end | ||
1028 | |||
1029 | #macro (addSpaceSuggestions $text $limit $suggestions) | ||
1030 | #searchSpaces($text $limit $spaceReferences) | ||
1031 | #set ($spaceSuggestions = []) | ||
1032 | #foreach ($spaceReference in $spaceReferences) | ||
1033 | #maybeAddSpaceNode($spaceReference $spaceSuggestions) | ||
1034 | #end | ||
1035 | #set ($discard = $suggestions.add($spaceSuggestions)) | ||
1036 | #end | ||
1037 | |||
1038 | #macro (searchSpaces $text $limit $return) | ||
1039 | #set ($constraints = ["upper(space.name) like upper(:spaceNamePattern) escape '!'"]) | ||
1040 | #set ($params = {'spaceNamePattern': "%$!text.replaceAll('([%_!])', '!$1')%"}) | ||
1041 | #addSpaceLocationDatabaseConstraint($rootReference $constraints $params 'space.reference') | ||
1042 | #set ($statement = "select space.reference from XWikiSpace space where $stringtool.join($constraints, ' and ') " | ||
1043 | + "order by lower(space.reference), space.reference") | ||
1044 | #set ($query = $services.query.hql($statement).setLimit($limit)) | ||
1045 | #addWikiLocationDatabaseConstraint($rootReference $query) | ||
1046 | #if ($docTreeConfig.filterHiddenDocuments) | ||
1047 | #set ($query = $query.addFilter('hidden/space')) | ||
1048 | #end | ||
1049 | #foreach ($entry in $params.entrySet()) | ||
1050 | #set ($query = $query.bindValue($entry.key, $entry.value)) | ||
1051 | #end | ||
1052 | #set ($spaceReferences = []) | ||
1053 | #foreach ($localSpaceRef in $query.execute()) | ||
1054 | #set ($discard = $spaceReferences.add($services.model.resolveSpace($localSpaceRef))) | ||
1055 | #end | ||
1056 | #set ($return = $NULL) | ||
1057 | #setVariable("$return" $spaceReferences) | ||
1058 | #end | ||
1059 | |||
1060 | #macro (addDocumentSuggestions $text $limit $suggestions) | ||
1061 | #searchDocuments($text $limit $documentReferences) | ||
1062 | #set ($docSuggestions = []) | ||
1063 | #foreach ($documentReference in $documentReferences) | ||
1064 | #maybeAddDocumentNode($documentReference $docSuggestions) | ||
1065 | #end | ||
1066 | #set ($discard = $suggestions.add($docSuggestions)) | ||
1067 | #end | ||
1068 | |||
1069 | #macro (searchDocuments $text $limit $return) | ||
1070 | #if ($xwiki.exists('XWiki.SuggestSolrMacros')) | ||
1071 | #searchDocumentsSolr($text $limit $return) | ||
1072 | #else | ||
1073 | #searchDocumentsDatabase($text $limit $return) | ||
1074 | #end | ||
1075 | #end | ||
1076 | |||
1077 | #macro (searchDocumentsSolr $text $limit $return) | ||
1078 | #set ($params = [ | ||
1079 | 'fq=type:DOCUMENT', | ||
1080 | 'fq=doclocale:""', | ||
1081 | 'qf=title^6 name^4 doccontent^2 doccontentraw', | ||
1082 | 'fl=wiki spaces name' | ||
1083 | ]) | ||
1084 | #addCommonDocTreeSolrParams($params) | ||
1085 | #set ($params = $stringtool.join($params, $util.newline)) | ||
1086 | #createSearchSuggestQuery($params $text $query) | ||
1087 | #set ($discard = $query.setLimit($limit)) | ||
1088 | #set ($documentReferences = []) | ||
1089 | #foreach ($result in $query.execute()[0].results) | ||
1090 | #set ($discard = $documentReferences.add($services.solr.resolveDocument($result))) | ||
1091 | #end | ||
1092 | #set ($return = $NULL) | ||
1093 | #setVariable("$return" $documentReferences) | ||
1094 | #end | ||
1095 | |||
1096 | #macro (searchDocumentsDatabase $text $limit $return) | ||
1097 | #set ($constraints = [ | ||
1098 | 'doc.translation = 0', | ||
1099 | 'doc.space = space.reference' | ||
1100 | ]) | ||
1101 | #set ($defaultDocumentName = $services.model.getEntityReference('DOCUMENT', 'default').name) | ||
1102 | #set ($matchDocTitle = "upper(doc.title) like upper(:text) escape '!'") | ||
1103 | #set ($params = {'text': "%$!text.replaceAll('([%_!])', '!$1')%"}) | ||
1104 | #if ($docTreeConfig.showTerminalDocuments) | ||
1105 | #set ($matchDocName = "(doc.name <> '$defaultDocumentName' and upper(doc.name) like upper(:text) escape '!')") | ||
1106 | #set ($matchSpaceName = "(doc.name = '$defaultDocumentName' and upper(space.name) like upper(:text) escape '!')") | ||
1107 | #set ($discard = $constraints.add("($matchDocTitle or $matchDocName or $matchSpaceName)")) | ||
1108 | #else | ||
1109 | #set ($matchSpaceName = "upper(space.name) like upper(:text) escape '!'") | ||
1110 | #set ($discard = $constraints.addAll([ | ||
1111 | "doc.name = '$defaultDocumentName'", | ||
1112 | "($matchDocTitle or $matchSpaceName)" | ||
1113 | ])) | ||
1114 | #end | ||
1115 | #addDocumentLocationDatabaseConstraint($rootReference $constraints $params) | ||
1116 | #set ($constraints = $stringtool.join($constraints, ' and ')) | ||
1117 | #set ($statement = "select doc.fullName from XWikiDocument doc, XWikiSpace space where $constraints") | ||
1118 | #set ($query = $services.query.hql($statement).setLimit($limit)) | ||
1119 | #foreach ($entry in $params.entrySet()) | ||
1120 | #set ($query = $query.bindValue($entry.key, $entry.value)) | ||
1121 | #end | ||
1122 | #addWikiLocationDatabaseConstraint($rootReference $query) | ||
1123 | #if ($docTreeConfig.filterHiddenDocuments) | ||
1124 | #set ($query = $query.addFilter('hidden/document')) | ||
1125 | #end | ||
1126 | #set ($documentReferences = []) | ||
1127 | #foreach ($docFullName in $query.execute()) | ||
1128 | #set ($discard = $documentReferences.add($services.model.resolveDocument($docFullName))) | ||
1129 | #end | ||
1130 | #set ($return = $NULL) | ||
1131 | #setVariable("$return" $documentReferences) | ||
1132 | #end | ||
1133 | |||
1134 | #macro (addAttachmentSuggestions $text $limit $suggestions) | ||
1135 | #searchAttachments($text $limit $attachmentReferences) | ||
1136 | #set ($attachmentSuggestions = []) | ||
1137 | #foreach ($attachmentReference in $attachmentReferences) | ||
1138 | #set ($attachment = $xwiki.getDocument($attachmentReference.parent).getAttachment($attachmentReference.name)) | ||
1139 | #addAttachmentNode($attachment $attachmentSuggestions) | ||
1140 | #end | ||
1141 | #set ($discard = $suggestions.add($attachmentSuggestions)) | ||
1142 | #end | ||
1143 | |||
1144 | #macro (searchAttachments $text $limit $return) | ||
1145 | #if ($xwiki.exists('XWiki.SuggestSolrMacros')) | ||
1146 | #searchAttachmentsSolr($text $limit $return) | ||
1147 | #else | ||
1148 | #searchAttachmentsDatabase($text $limit $return) | ||
1149 | #end | ||
1150 | #end | ||
1151 | |||
1152 | #macro (searchAttachmentsSolr $text $limit $return) | ||
1153 | #set ($params = [ | ||
1154 | 'fq=type:ATTACHMENT', | ||
1155 | 'fq=locale:*', | ||
1156 | 'qf=filename^4 attcontent', | ||
1157 | 'fl=type wiki spaces name filename' | ||
1158 | ]) | ||
1159 | #addCommonDocTreeSolrParams($params) | ||
1160 | #set ($params = $stringtool.join($params, $util.newline)) | ||
1161 | #createSearchSuggestQuery($params $text $query) | ||
1162 | #set ($discard = $query.setLimit($limit)) | ||
1163 | #set ($attachmentReferences = []) | ||
1164 | #foreach ($result in $query.execute()[0].results) | ||
1165 | #set ($discard = $attachmentReferences.add($services.solr.resolve($result))) | ||
1166 | #end | ||
1167 | #set ($return = $NULL) | ||
1168 | #setVariable("$return" $attachmentReferences) | ||
1169 | #end | ||
1170 | |||
1171 | #macro (searchAttachmentsDatabase $text $limit $return) | ||
1172 | #set ($constraints = ["upper(attach.filename) like upper(:text) escape'!'"]) | ||
1173 | #set ($params = {'text': "%$!text.replaceAll('([%_!])', '!$1')%"}) | ||
1174 | #if ($docTreeConfig.filterHiddenDocuments && "$!xwiki.getUserPreference('displayHiddenDocuments')" != '1') | ||
1175 | #set ($discard = $constraints.add("(doc.hidden <> true or doc.hidden is null)")) | ||
1176 | #end | ||
1177 | #set ($exactMatch = $rootType == 'attachments') | ||
1178 | #addDocumentLocationDatabaseConstraint($rootReference $constraints $params $exactMatch) | ||
1179 | #set ($statement = "where $stringtool.join($constraints, ' and ')") | ||
1180 | ## | ||
1181 | ## Convert named parameters to positional parameters. | ||
1182 | #set ($paramList = []) | ||
1183 | #foreach ($item in $regextool.findAll($statement, ':(\w+)')) | ||
1184 | #set ($paramName = $item.get(1).getGroup()) | ||
1185 | #set ($discard = $paramList.add($params.get($paramName))) | ||
1186 | #end | ||
1187 | #set ($statement = $statement.replaceAll(':\w+', '\?')) | ||
1188 | ## | ||
1189 | ## TODO: Search in the wiki that corresponds to the root node. | ||
1190 | #set ($attachments = $xwiki.searchAttachments($statement, $limit, 0, $paramList)) | ||
1191 | #set ($attachmentReferences = []) | ||
1192 | #foreach ($attachment in $attachments) | ||
1193 | #set ($discard = $attachmentReferences.add($services.model.createAttachmentReference( | ||
1194 | $attachment.document.documentReference, $attachment.filename))) | ||
1195 | #end | ||
1196 | #set ($return = $NULL) | ||
1197 | #setVariable("$return" $attachmentReferences) | ||
1198 | #end | ||
1199 | |||
1200 | #macro (addCommonDocTreeSolrParams $params) | ||
1201 | #if ($rootType == 'wiki') | ||
1202 | ## Limit the search to the specified wiki. | ||
1203 | #addWikiLocationSolrParams($rootReference $params) | ||
1204 | #elseif ($rootType == 'space') | ||
1205 | ## Limit the search to the specified space. | ||
1206 | #addSpaceLocationSolrParams($rootReference $params) | ||
1207 | #elseif ($rootType == 'document') | ||
1208 | ## Limit the search to the specified document. | ||
1209 | #addDocumentLocationSolrParams($rootReference $params) | ||
1210 | #elseif ($rootType == 'attachments') | ||
1211 | ## Limit the search to the attachments of the specified document. | ||
1212 | #addDocumentLocationSolrParams($rootReference $params true) | ||
1213 | #end | ||
1214 | #if (!$docTreeConfig.showTerminalDocuments) | ||
1215 | #set ($defaultDocumentName = $services.model.getEntityReference('DOCUMENT', 'default').name) | ||
1216 | #set ($discard = $params.add("fq=name:$defaultDocumentName")) | ||
1217 | #end | ||
1218 | #if (!$docTreeConfig.filterHiddenDocuments) | ||
1219 | ## Force the inclusion of the hidden documents. | ||
1220 | #set ($discard = $params.add("fq=hidden:*")) | ||
1221 | #end | ||
1222 | #end | ||
1223 | |||
1224 | #macro (addWikiLocationSolrParams $rootReference $params) | ||
1225 | #set ($wikiReference = $rootReference.extractReference('WIKI')) | ||
1226 | #if ($wikiReference) | ||
1227 | #set ($discard = $params.add("fq=wiki:$wikiReference.name")) | ||
1228 | #end | ||
1229 | #end | ||
1230 | |||
1231 | #macro (addWikiLocationDatabaseConstraint $rootReference $query) | ||
1232 | #set ($wikiReference = $rootReference.extractReference('WIKI')) | ||
1233 | #if ($wikiReference) | ||
1234 | #set ($query = $query.setWiki($wikiReference.name)) | ||
1235 | #end | ||
1236 | #end | ||
1237 | |||
1238 | #macro (addSpaceLocationSolrParams $rootReference $params $exactMatch) | ||
1239 | #addWikiLocationSolrParams($rootReference $params) | ||
1240 | #set ($spaceReference = $rootReference.extractReference('SPACE')) | ||
1241 | #if ($spaceReference && ($docTreeConfig.showSpaces || $docTreeConfig.hierarchyMode == 'reference')) | ||
1242 | #set ($localSpaceReference = $services.model.serialize($spaceReference, 'local')) | ||
1243 | #set ($spaceField = 'space_prefix') | ||
1244 | #if ($exactMatch) | ||
1245 | #set ($spaceField = 'space_exact') | ||
1246 | #end | ||
1247 | #set ($discard = $params.add("fq=$spaceField:""$localSpaceReference""")) | ||
1248 | #end | ||
1249 | #end | ||
1250 | |||
1251 | #macro (addSpaceLocationDatabaseConstraint $rootReference $constraints $params $field) | ||
1252 | #set ($spaceReference = $rootReference.extractReference('SPACE')) | ||
1253 | #if ($spaceReference && ($docTreeConfig.showSpaces || $docTreeConfig.hierarchyMode == 'reference')) | ||
1254 | #set ($discard = $constraints.add("($field = :localSpaceReference or $field like :spaceReferencePattern escape '!')")) | ||
1255 | #set ($localSpaceReference = $services.model.serialize($spaceReference, 'local')) | ||
1256 | #set ($discard = $params.put('localSpaceReference', $localSpaceReference)) | ||
1257 | #set ($spaceReferencePattern = $services.model.createEntityReference('x', 'SPACE', $spaceReference)) | ||
1258 | #set ($spaceReferencePattern = $services.model.serialize($spaceReferencePattern, 'local')) | ||
1259 | #set ($spaceReferencePattern = $stringtool.removeEnd($spaceReferencePattern, 'x').replaceAll('([%_!])', '!$1')) | ||
1260 | #set ($discard = $params.put('spaceReferencePattern', "$spaceReferencePattern%")) | ||
1261 | #end | ||
1262 | #end | ||
1263 | |||
1264 | #macro (addDocumentLocationSolrParams $rootReference $params $exactMatch) | ||
1265 | #set ($documentReference = $rootReference.extractReference('DOCUMENT')) | ||
1266 | #set ($defaultDocumentName = $services.model.getEntityReference('DOCUMENT', 'default').name) | ||
1267 | #set ($macro.exactMatch = $exactMatch || ($docTreeConfig.hierarchyMode == 'reference' | ||
1268 | && $documentReference && $documentReference.name != $defaultDocumentName)) | ||
1269 | #addSpaceLocationSolrParams($rootReference $params $macro.exactMatch) | ||
1270 | #if ($documentReference && $macro.exactMatch) | ||
1271 | #set ($discard = $params.add("fq=name_exact:""$documentReference.name""")) | ||
1272 | #end | ||
1273 | #end | ||
1274 | |||
1275 | #macro (addDocumentLocationDatabaseConstraint $rootReference $constraints $params $exactMatch) | ||
1276 | #set ($documentReference = $rootReference.extractReference('DOCUMENT')) | ||
1277 | #set ($defaultDocumentName = $services.model.getEntityReference('DOCUMENT', 'default').name) | ||
1278 | #set ($macro.exactMatch = $exactMatch || ($docTreeConfig.hierarchyMode == 'reference' | ||
1279 | && $documentReference && $documentReference.name != $defaultDocumentName)) | ||
1280 | #if ($documentReference && $macro.exactMatch) | ||
1281 | #set ($localDocumentReference = $services.model.serialize($documentReference, 'local')) | ||
1282 | #set ($discard = $constraints.add('doc.fullName = :localDocumentReference')) | ||
1283 | #set ($discard = $params.put('localDocumentReference', $localDocumentReference)) | ||
1284 | #elseif (!$macro.exactMatch) | ||
1285 | #addSpaceLocationDatabaseConstraint($rootReference $constraints $params 'doc.space') | ||
1286 | #end | ||
1287 | #end | ||
1288 | |||
1289 | #macro (displayPath $path) | ||
1290 | #foreach ($node in $path) | ||
1291 | #set ($discard = $path.set($foreach.index, $node.text)) | ||
1292 | #end | ||
1293 | #end | ||
1294 | |||
1295 | #macro (limitTotalCount $lists $limit) | ||
1296 | ## Prepare the input. | ||
1297 | #set ($input = []) | ||
1298 | #foreach ($list in $lists) | ||
1299 | ## We use queues to be able to easily remove items from the start. | ||
1300 | #set ($queue = $collectiontool.queue) | ||
1301 | #set ($discard = $queue.addAll($list)) | ||
1302 | #set ($discard = $input.add($queue)) | ||
1303 | ## We will add (part of) the items back later. | ||
1304 | #set ($discard = $list.clear()) | ||
1305 | #end | ||
1306 | ## Limit the total item count. | ||
1307 | #set ($index = -1) | ||
1308 | #foreach ($count in [1..$limit]) | ||
1309 | #foreach ($i in [1..$input.size()]) | ||
1310 | #set ($newIndex = ($index + $i) % $input.size()) | ||
1311 | #if ($input.get($newIndex).size() > 0) | ||
1312 | #set ($index = $newIndex) | ||
1313 | #break | ||
1314 | #end | ||
1315 | #end | ||
1316 | #if ($index < 0 || $input.get($index).isEmpty()) | ||
1317 | #break | ||
1318 | #else | ||
1319 | #set ($discard = $lists.get($index).add($input.get($index).poll())) | ||
1320 | #end | ||
1321 | #end | ||
1322 | #end | ||
1323 | {{/velocity}} |