Änderungen von Dokument Attachments

Zuletzt geändert von xwikiadmin am 2026/03/05 08:08

Von Version 1.1
bearbeitet von xwikiadmin
am 2023/03/07 16:26
Änderungskommentar: Install extension [com.xwiki.pro:xwiki-pro-macros/1.7]
Auf Version 6.1
bearbeitet von xwikiadmin
am 2025/12/11 06:32
Änderungskommentar: Migrated property [executionIsolated] from class [XWiki.WikiMacroClass]

Zusammenfassung

Details

Seiteneigenschaften
Inhalt
... ... @@ -10,17 +10,17 @@
10 10  = Parameters =
11 11  
12 12  |=Parameter|=Description|=Required|=Default
13 -|**patterns**|Comma-separated list of regular expressions, used to filter attachments by name.|No|
13 +|**patterns**|Comma-separated list of regular expressions, used to filter attachments by name.|No|
14 14  |**sortBy**|Sort attachments by {{{ date }}}, {{{ size }}} or {{{ name }}}|No|{{{ date }}}
15 15  |**sortOrder**|Sort attachments in {{{ ascending }}} or {{{ descending }}} order|No|{{{ ascending }}}
16 16  |**upload**|Allow users to attach new files|No|{{{ true }}}
17 -|**page**|Pages containing the attachments to display. Current page if empty.|No|
17 +|**page**|Pages containing the attachments to display. Current page if empty.|No|
18 18  
19 19  = Example Usage =
20 20  
21 21  
22 22  {{code}}
23 -{{attachments
23 +{{confluence_attachments
24 24   patterns=".*png"
25 25   sortBy="name"
26 26  /}}
XWiki.JavaScriptExtension[0]
Code
... ... @@ -1,8 +1,95 @@
1 -require(['jquery', 'xwiki-events-bridge'], function ($) {
2 - $('#xwikiuploadfile').on('xwiki:html5upload:message', function (event, data) {
3 - if (data.content === 'UPLOAD_FINISHED' && data.type === 'done') {
4 - location.reload();
1 +define('xwiki-confluence-attachments-messages', {
2 + prefix: 'core.viewers.attachments.delete.',
3 + keys: [
4 + 'inProgress',
5 + 'done',
6 + 'error'
7 + ]
8 +});
9 +
10 +require(['jquery', 'xwiki-l10n!xwiki-confluence-attachments-messages'], function($, l10n) {
11 + var enhanceUploadInputs = function(liveDataElems) {
12 + $.each(liveDataElems.find('input[type=file]'), function() {
13 + // Since the attachments liveData is refreshed on file upload, there is no need for a response container.
14 + new XWiki.FileUploader(this, {
15 + 'progressAutohide': true,
16 + 'responseContainer' : document.createElement('div'),
17 + 'maxFilesize' : parseInt(this.readAttribute('data-max-file-size'))
18 + });
19 + })
20 + };
21 +
22 + $(function() {
23 + enhanceUploadInputs($('.confluenceAttachmentsMacro'));
24 + })
25 +
26 + $(document).on('xwiki:dom:updated', function(e, data) {
27 + let liveDataElems = $(data.elements).find('.confluenceAttachmentsMacro');
28 + enhanceUploadInputs(liveDataElems);
29 + });
30 +
31 + $(document).on('xwiki:html5upload:done', function(e) {
32 + if ($(e.target).prop('id').startsWith('confluenceAttachments')) {
33 + // Select the livedata above the upload form.
34 + const uploadForm = $(e.target).closest('form');
35 + // The 'xwiki-livedata' CSS class is present only before XWiki version 14.4.
36 + let associatedLivedata = uploadForm.prevAll('.liveData, .xwiki-livedata').first();
37 + // We do not have access to a liveData object before XWiki 14.4.
38 + if (associatedLivedata.data('liveData') === undefined) {
39 + location.reload();
40 + } else {
41 + associatedLivedata.data('liveData').updateEntries();
42 + }
5 5   }
6 6   });
45 +
46 + //
47 + // The delete action methods are similar to the ones from the attachments tab, but couldn't be reused. The problem is
48 + // that when the tab is not opened we cannot just load the attachments.js file, since it expects some elements to be
49 + // already loaded and it produces errors.
50 + //
51 +
52 + /**
53 + * Open the delete confirmation modal on liveData attachment delete button.
54 + */
55 + $(document).on('click', '.confluenceAttachmentsMacro .attachmentActions .actiondelete', function(e) {
56 + e.preventDefault();
57 + // The 'xwiki-livedata' CSS class is present only before XWiki version 14.4.
58 + let liveData = $(e.currentTarget).closest('.liveData, .xwiki-livedata');
59 + var modal = liveData.nextAll('.deleteConfluenceAttachment');
60 + modal.data('relatedTarget', e.currentTarget);
61 + modal.modal('show');
62 + });
63 +
64 + /**
65 + * On delete confirmation, delete attachment and refresh liveData.
66 + */
67 + $(document).on('click', '.confluenceAttachmentsMacro .deleteConfluenceAttachment input.btn-danger', function(e) {
68 + e.preventDefault();
69 + var modal = $(e.currentTarget).closest('.deleteConfluenceAttachment');
70 + var button = $(modal.data('relatedTarget'));
71 + // The 'xwiki-livedata' CSS class is present only before XWiki version 14.4.
72 + let liveData = button.closest('.liveData, .xwiki-livedata');
73 + var notification;
74 +
75 + $.ajax({
76 + url : button.prop('href'),
77 + beforeSend : function() {
78 + notification = new XWiki.widgets.Notification(l10n['inProgress'], 'inprogress');
79 + },
80 + success : function() {
81 + // We do not have access to a liveData object before XWiki 14.4.
82 + if (liveData.data('liveData') !== undefined) {
83 + liveData.data('liveData').updateEntries();
84 + notification.replace(new XWiki.widgets.Notification(l10n['done'], 'done'));
85 + } else {
86 + location.reload();
87 + }
88 + },
89 + error: function() {
90 + notification.replace(new XWiki.widgets.Notification(l10n['failed'], 'error'));
91 + }
92 + });
93 + });
7 7  });
8 8  
Name
... ... @@ -1,0 +1,1 @@
1 +Confluence Attachments Macro
XWiki.StyleSheetExtension[0]
Code
... ... @@ -1,15 +1,15 @@
1 -.confluence-attachment-macro {
2 - .upload-response {
3 - display: none;
4 - }
5 -
6 - #AddAttachment {
7 - margin-top: 15px;
8 -
9 - #attachform {
10 - .buttonwrapper {
11 - display: none;
12 - }
13 - }
14 - }
1 +.confluenceAttachmentsMacro legend {
2 + font-size: 1em;
15 15  }
4 + /* These should be deleted after upgrading to a XWiki parent >= 14.6. */
5 +.attachmentMimeType {
6 + color: $theme.textSecondaryColor;
7 + font-size: 32px;
8 + height: 32px;
9 + line-height: 32px;
10 + text-align: center;
11 +}
12 +.attachmentMimeType img {
13 + max-height: 32px;
14 + max-width: 48px;
15 +}
Content Type
... ... @@ -1,1 +1,1 @@
1 -LESS
1 +CSS
Name
... ... @@ -1,0 +1,1 @@
1 +Confluence Attachments Macro
XWiki.WikiMacroClass[0]
Makro-Code
... ... @@ -1,169 +1,223 @@
1 1  {{velocity output="false"}}
2 -$xwiki.ssx.use('Confluence.Macros.Attachments')
3 -$xwiki.jsx.use('Confluence.Macros.Attachments')
4 -#if ("$!{request.forceTestRights}" == "1")#template("xwikivars.vm")#end
5 -#template('attachment_macros.vm')
6 -## Get attachments
7 -#if ("$!wikimacro.parameters.tags" != '')
8 - #set ($tags = $!wikimacro.parameters.tags.split(','))
9 - #set ($pageReferenceSet = $collectiontool.getSet())
10 - #foreach ($tag in $tags)
11 - #set ($references = $xwiki.tag.getDocumentsWithTag("$tag"))
12 - #set ($discard = $pageReferenceSet.addAll($references))
2 +#macro (getLiveDataSort $return)
3 + ##property
4 + #set ($confluenceSortBy = "$!wikimacro.parameters.sortBy")
5 + #set ($invalidSortBy = false)
6 + #if ($confluenceSortBy == 'date')
7 + #set ($sortBy = 'date')
8 + #elseif ($confluenceSortBy == 'size')
9 + #set ($sortBy = 'filesize')
10 + #elseif ($confluenceSortBy == 'name')
11 + #set ($sortBy = 'filename')
12 + #elseif ($confluenceSortBy == 'creation date')
13 + #set ($sortBy = 'date')
14 + #else
15 + #set ($invalidSortBy = true)
13 13   #end
14 - #set ($attachments = [])
15 - #foreach ($pageReference in $pageReferenceSet)
16 - #set ($document = $xwiki.getDocument($pageReference))
17 - #set ($documentAttachments = $document.attachmentList)
18 - #set ($discard = $attachments.addAll($documentAttachments))
17 + ## order
18 + #set ($confluenceSortOrder = "$!wikimacro.parameters.sortOrder")
19 + #set ($invalidSortOrder = false)
20 + #set ($reverseSortOrderProperties = ['filesize', 'date'])
21 + #if ($confluenceSortOrder == 'ascending')
22 + #if ($reverseSortOrderProperties.contains($sortBy))
23 + #set ($sortOrder = 'desc')
24 + #else
25 + #set ($sortOrder = 'asc')
26 + #end
27 + #elseif ($confluenceSortOrder == 'descending')
28 + #if ($reverseSortOrderProperties.contains($sortBy))
29 + #set ($sortOrder = 'asc')
30 + #else
31 + #set ($sortOrder = 'desc')
32 + #end
33 + #else
34 + #set ($invalidSortOrder = true)
19 19   #end
20 -#else
21 - #set ($attachments = $doc.attachmentList)
22 - #if ("$!wikimacro.parameters.page" != '')
23 - #set ($document = $xwiki.getDocument("$!wikimacro.parameters.page"))
24 - #set ($attachments = $document.attachmentList)
36 + ## return
37 + #if ($invalidSortBy || $invalidSortOrder)
38 + #setVariable("$return", {})
39 + #else
40 + #setVariable("$return", "$sortBy:$sortOrder")
25 25   #end
26 26  #end
27 -## Filter attachments
28 -#set ($confluencePatterns = "$!wikimacro.parameters.patterns")
29 -#if ($confluencePatterns == '')
30 - #set($attachmentsFiltered = $attachments)
31 -#else
32 - #set ($patterns = $!confluencePatterns.split(','))
33 - #set($attachmentsFiltered = [])
34 - #foreach ($attachment in $attachments)
35 - #foreach ($pattern in $patterns)
36 - #set ($matches = $attachment.filename.matches("(?i)$!pattern"))
37 - #if ($matches)
38 - #set ($discard = $attachmentsFiltered.add($attachment))
39 - #break
40 - #end
41 - #end
43 +
44 +#macro (deleteConfluenceAttachmentModal)
45 + ## Copied from the attachments tab code. The original macro cannot be used, since it will interfere with some js
46 + ## specific to attachments tab. Precisely, if the attachments tab is already opened, everything is working correctly.
47 + ## If it is not opened, we need to load the attachments.js code anyway, to use it's listeners, but there are parts
48 + ## that rightfully assume that some elements are present on the page and errors will occur.
49 + <div class="modal fade deleteConfluenceAttachment" tabindex="-1" role="dialog">
50 + <div class="modal-dialog">
51 + <div class="modal-content">
52 + <div class="modal-header">
53 + <button type="button" class="close" data-dismiss="modal">&times;</button>
54 + <div class="modal-title">$services.localization.render('core.viewers.attachments.delete')</div>
55 + </div>
56 + <div class="modal-body">
57 + <div>$services.localization.render('core.viewers.attachments.delete.confirm')</div>
58 + </div>
59 + <div class="modal-footer">
60 + <input type="button" class="btn btn-danger" data-dismiss="modal"
61 + value="$escapetool.xml($services.localization.render('core.viewers.attachments.delete'))">
62 + <input type="button" class="btn btn-default" data-dismiss="modal"
63 + value="$escapetool.xml($services.localization.render('cancel'))">
64 + </div>
65 + </div>
66 + </div>
67 + </div>
68 +#end
69 +
70 +#set ($confluenceAttachmentMacroIndex = -1)
71 +## Code taken and adapted
72 +#macro (showConfluenceAttachments $document)
73 + #template('display_macros.vm')
74 + #set ($confluenceAttachmentMacroIndex = $confluenceAttachmentMacroIndex + 1)
75 + #set ($confluenceAttachmentMacroId = "confluenceAttachments$confluenceAttachmentMacroIndex")
76 + ##
77 + #showConfluenceAttachmentsLiveData($document $confluenceAttachmentMacroId)
78 +#end
79 +
80 +#macro (uploadFileForm $liveDataId)
81 + #set ($upload = "$!wikimacro.parameters.upload")
82 + #if ($upload == 'true' && ($hasEdit || $hasAdmin) && $xcontext.action == 'view')
83 + <form action="$attachmentsDoc.getURL("upload")" enctype="multipart/form-data" method="post">
84 + <div>
85 + ## CSRF prevention
86 + <input type="hidden" name="form_token" value="$!{services.csrf.getToken()}" />
87 + <fieldset>
88 + <legend>$services.localization.render('promacros.attachments.upload.title')</legend>
89 + <div>
90 + #set ($inputID = "${liveDataId}-uploadFile")
91 + <label class="sr-only" for="${inputID}">
92 + $services.localization.render('core.viewers.attachments.upload.file')
93 + </label>
94 + <input id="${inputID}" type="file" name="filepath" size="40" class="noitems"
95 + data-max-file-size="$!escapetool.xml($xwiki.getSpacePreference('upload_maxsize'))"/>
96 + </div>
97 + </fieldset>
98 + </div>
99 + </form>
42 42   #end
43 43  #end
44 -## Sort attachments
45 -#set ($confluenceSortBy = "$!wikimacro.parameters.sortBy")
46 -#set ($invalidSortBy = false)
47 -#if ($confluenceSortBy == 'date')
48 - #set ($sortBy = 'date')
49 -#elseif ($confluenceSortBy == 'size')
50 - #set ($sortBy = 'filesize')
51 -#elseif ($confluenceSortBy == 'name')
52 - #set ($sortBy = 'filename')
53 -#elseif ($confluenceSortBy == 'creation date')
54 - #set ($sortBy = 'date')
55 -#else
56 - #set ($invalidSortBy = true)
102 +
103 +#macro (supportsAttachJSON $supportsAttachJSON)
104 + ## The attachments.json code uses macros from attachment_macro.vm, so the existence of the attachments livedata macro
105 + ## is an indicator that the template exists and is adapted for livedata.
106 + #template('attachment_macros.vm')
107 + #set ($macroResult = "#showAttachmentsLiveData($doc 'id')")
108 + #set ($supportsAttachJSON = $macroResult.startsWith('{{liveData'))
57 57  #end
58 -#set ($confluenceSortOrder = "$!wikimacro.parameters.sortOrder")
59 -#set ($invalidSortOrder = false)
60 -#set ($reverseSortOrderProperties = ['filesize', 'date'])
61 -#if ($confluenceSortOrder == 'ascending')
62 - #if ($reverseSortOrderProperties.contains($sortBy))
63 - #set ($sortOrder = 'desc')
64 - #else
65 - #set ($sortOrder = 'asc')
110 +
111 +## Display a liveData with attachments from the specified document.
112 +#macro (showConfluenceAttachmentsLiveData $attachmentsDoc $liveDataId)
113 + #set ($liveDataConfig = {
114 + 'meta': {
115 + 'propertyDescriptors': [
116 + { 'id': 'mimeType', 'displayer': 'html'},
117 + { 'id': 'filename', 'displayer': 'html' },
118 + { 'id': 'filesize', 'displayer': 'html' },
119 + { 'id': 'date', 'filter': 'date'},
120 + { 'id': 'author', 'displayer': 'html' },
121 + { 'id': 'actions', 'displayer': 'html' }
122 + ],
123 + 'entryDescriptor': {
124 + 'idProperty': 'id'
125 + }
126 + }
127 + })
128 +
129 + #if ("$!wikimacro.parameters.patterns" != '')
130 + #set ($liveDataConfig.query = {})
131 + #set ($liveDataConfig.query.filters = [
132 + {
133 + "property": "filename",
134 + "matchAll": true,
135 + "constraints": []
136 + }
137 + ])
138 + #set ($filters = $stringtool.split($wikimacro.parameters.patterns, ','))
139 + #foreach ($filter in $filters)
140 + #set ($discard = $liveDataConfig.query.filters[0].constraints.add(
141 + { "operator": "contains", "value": "$!filter.trim()" }
142 + ))
143 + #end
66 66   #end
67 -#elseif ($confluenceSortOrder == 'descending')
68 - #if ($reverseSortOrderProperties.contains($sortBy))
69 - #set ($sortOrder = 'asc')
145 + #set ($sourceParams = {
146 + 'translationPrefix': 'core.viewers.attachments.livetable.',
147 + 'className': 'XWiki.AllAttachments',
148 + "\$doc": "$attachmentsDoc"
149 + })
150 + ## Since the correct attachmentsjson.vm was added in XWiki 14.8, we use a copy of its code for backwards compatibility.
151 + #supportsAttachJSON($supportsAttachJSON)
152 + #if ($supportsAttachJSON)
153 + #set ($discard = $sourceParams.put('template', 'xpart.vm'))
154 + #set ($discard = $sourceParams.put('vm', 'attachmentsjson.vm'))
70 70   #else
71 - #set ($sortOrder = 'desc')
156 + #set ($discard = $sourceParams.put('resultPage', 'Confluence.Macros.AttachmentsJSON'))
72 72   #end
73 -#else
74 - #set ($invalidSortOrder = false)
75 -#end
76 -#set ($attachmentsFiltered = $collectiontool.sort($attachmentsFiltered, "$sortBy:$sortOrder"))
77 -{{/velocity}}
158 + #getLiveDataSort($liveDataSort)
159 + #if ($invalidSortBy)
160 + {{warning}}
161 + Attachment macro: sortBy parameter must be one of 'date', 'size', or 'name'
162 + {{/warning}}
163 + #end
164 + #if ($invalidSortOrder)
165 + {{warning}}
166 + Attachment macro: sortOrder parameter must be one of 'ascending' or 'descending'
167 + {{/warning}}
168 + #end
78 78  
79 -{{velocity}}
80 -#if ($invalidSortBy)
81 - {{error}}
82 - Attachment macro error: sortBy parameter must be one of 'date', 'size', or 'name'
83 - {{/error}}
84 - #break
170 + {{liveData
171 + id="$liveDataId"
172 + properties="mimeType,filename,filesize,date,author,actions"
173 + source='liveTable'
174 + sourceParameters="$escapetool.url($sourceParams)"
175 + sort="$liveDataSort"
176 + limit=5
177 + }}$jsontool.serialize($liveDataConfig){{/liveData}}
178 +
179 + {{html clean="false"}}
180 + #uploadFileForm($liveDataId)
181 + #deleteConfluenceAttachmentModal()
182 + {{/html}}
85 85  #end
86 -#if ($invalidSortOrder)
87 - {{error}}
88 - Attachment macro error: sortOrder parameter must be one of 'ascending' or 'descending'
89 - {{/error}}
90 - #break
91 -#end
92 -## Display attachments
93 -{{html clean="false"}}
94 - <div class="confluence-attachment-macro">
95 - #if ($attachmentsFiltered.size() > 0)
96 - #displayAttachments($attachmentsFiltered)
97 - #deleteAttachmentModal()
98 - #else
99 - <p class="noitems">
100 - $!escapetool.xml($services.localization.render('core.viewers.attachments.noAttachments'))
101 - </p>
102 - #end
103 103  
104 - ## Allow upload
105 - #set ($upload = "$!wikimacro.parameters.upload")
106 - #if ($upload == 'true' && ($hasEdit || $hasAdmin) && $xcontext.action == 'view')
107 - <form
108 - id="AddAttachment"
109 - action="$doc.getURL("upload")"
110 - method="post"
111 - enctype="multipart/form-data"
112 - >
113 - <div>
114 - ## CSRF prevention
115 - <input
116 - type="hidden"
117 - name="form_token"
118 - value="$!{services.csrf.getToken()}"
119 - />
120 120  
121 - <fieldset id="attachform">
122 - <legend>
123 - $!escapetool.xml($services.localization.render('core.viewers.attachments.upload.title'))
124 - </legend>
186 +#macro (executeMacro)
187 + #set ($discard = $xwiki.ssfx.use('js/xwiki/viewers/attachments.css', true))
188 + #set ($discard = $xwiki.ssfx.use('uicomponents/widgets/upload.css', true))
189 + #set ($discard = $xwiki.jsfx.use('uicomponents/widgets/upload.js', {
190 + 'forceSkinAction': true,
191 + 'language': $xcontext.locale
192 + }))
193 + #set ($discard = $xwiki.jsx.use("Confluence.Macros.Attachments"))
194 + #set ($discard = $xwiki.ssx.use("Confluence.Macros.Attachments"))
195 + #set ($document = $doc)
196 + #if ("$!wikimacro.parameters.page" != '')
197 + #set ($document = $xwiki.getDocument("$!wikimacro.parameters.page"))
198 + #end
125 125  
126 - <div class="fileupload-field">
127 - <label
128 - class="hidden"
129 - for="xwikiuploadfile"
130 - >
131 - $!escapetool.xml($services.localization.render('core.viewers.attachments.upload.file'))
132 - </label>
200 + {{html clean="false" wiki="true"}}
201 + <div class='confluenceAttachmentsMacro'>
202 + #showConfluenceAttachments($document)
203 + </div>
204 + {{/html}}
133 133  
134 - <input
135 - id="xwikiuploadfile"
136 - class="uploadFileInput noitems"
137 - type="file"
138 - name="filepath"
139 - size="40"
140 - data-max-file-size="$!escapetool.xml($xwiki.getSpacePreference('upload_maxsize'))"
141 - />
142 - </div>
206 +#end
207 +{{/velocity}}
143 143  
144 - <div>
145 - <span class="buttonwrapper">
146 - <input
147 - class="button btn btn-primary"
148 - type="submit"
149 - value="$!escapetool.xml($services.localization.render('core.viewers.attachments.upload.submit'))"
150 - />
151 - </span>
209 +{{include reference="Licenses.Code.VelocityMacros"/}}
152 152  
153 - <span class="buttonwrapper">
154 - <a
155 - class="cancel secondary button btn btn-primary"
156 - href="$doc.getURL()"
157 - >
158 - $!escapetool.xml($services.localization.render('core.viewers.attachments.upload.cancel'))
159 - </a>
160 - </span>
161 - </div>
162 - </fieldset>
163 - </div>
164 - </form>
165 - #end
166 - </div> ## .confluence-attachment-macro
167 -{{/html}}
211 +{{velocity}}
212 +## We need to check if there is a valid license because the macro is registered even if the user doesn't have view right
213 +## on the macro definition page. See XWIKI-14828: Rendering macros defined in wiki pages are available to users that
214 +## don't have view right on those pages.
215 +#if ($services.licensing.licensor.hasLicensureForEntity($xcontext.macro.doc.documentReference))
216 + #executeMacro
217 +#else
218 + {{error}}
219 + #getMissingLicenseMessage('proMacros.extension.name')
220 + {{/error}}
221 +#end
168 168  {{/velocity}}
169 169  
Standardkategorie
... ... @@ -1,1 +1,0 @@
1 -confluence
XWiki.WikiMacroParameterClass[9]
Parameter-Vorgabe
... ... @@ -1,0 +1,1 @@
1 +true
Parameter-Beschreibung
... ... @@ -1,0 +1,1 @@
1 +Display an option for uploading files to the selected page.
Parameter verpflichtend
... ... @@ -1,0 +1,1 @@
1 +Nein
Parameter-Name
... ... @@ -1,0 +1,1 @@
1 +upload
Parameter-Typ
... ... @@ -1,0 +1,1 @@
1 +java.lang.Boolean