Исходный код вики Document Tree Macros

Редактировал(а) Андрей Ганьков 2026/01/23 10:50

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