Wiki-Quellcode von MacroService

Version 1.1 von admin am 2025/02/23 19:33

Zeige letzte Bearbeiter
1 {{include reference="CKEditor.VelocityMacros" /}}
2
3 {{velocity output="false"}}
4 ## ================================================================
5 ## Returned JSON format:
6 ##
7 ## {
8 ## 'options': {
9 ## 'allMacrosExcludedCategories': [
10 ## (translated category name), ...
11 ## ]
12 ## },
13 ## 'list': [
14 ## {
15 ## 'id': (macro id),
16 ## 'name': (translated macro name),
17 ## 'description': (translated macro description),
18 ## 'defaultCategory': (translated macro category)
19 ## },
20 ## ...
21 ## ],
22 ## 'notinstalled': [
23 ## {
24 ## 'id': (macro id),
25 ## 'name': (translated macro name),
26 ## 'description': (translated macro description),
27 ## 'defaultCategory': '_notinstalled',
28 ## 'extensionId': (extension id)
29 ## 'extensionVersion': (extension version)
30 ## 'extensionType': (extension type)
31 ## 'extensionRecommended': (is extension recommended)
32 ## 'extensionName': (extension name)
33 ## 'extensionSummary': (extension summary)
34 ## ]
35 ## },
36 ## ...
37 ## ]
38 ## }
39 ## ================================================================
40 #macro (getMacroList $syntaxId)
41
42 ## Loads the css resources to display the macros list
43 ## TODO: Refactor once we add support for loading css files from javascript without velocity.
44 #if ($xcontext.action == 'get')
45 #template('display_macros.vm')
46 #initRequiredSkinExtensions()
47 #end
48
49 #set ($discard = $xwiki.linkx.use($services.webjars.url('selectize.js', 'css/selectize.bootstrap3.css'),
50 {'type': 'text/css', 'rel': 'stylesheet'}))
51 #set ($discard = $xwiki.ssfx.use('uicomponents/suggest/xwiki.selectize.css', true))
52
53 #if ($xcontext.action == 'get')
54 #getRequiredSkinExtensions($requiredSkinExtensions)
55 #set ($discard = $response.setHeader('X-XWIKI-HTML-HEAD', $requiredSkinExtensions))
56 #end
57
58 #set ($syntax = $services.rendering.resolveSyntax($syntaxId))
59 #set ($macroDescriptors = $services.rendering.getMacroDescriptors($syntax))
60 #set ($data = {})
61 #set ($allMacrosExcludedCategories = [])
62 #set ($discard = $allMacrosExcludedCategories.add("#maybeTranslate('rendering.macroCategory.Internal' 'Internal')"))
63 #set ($discard = $allMacrosExcludedCategories.add("#maybeTranslate('rendering.macroCategory.Deprecated' 'Deprecated')"))
64 #set ($discard = $data.put('options', { 'allMacrosExcludedCategories' : $allMacrosExcludedCategories }))
65 ## If the current user do not want to display hidden documents, we initialize the set of hidden default
66 ## categories.
67 ## TODO: Make the list of hidden by default categories configurable from the administration (XWIKI-19993).
68 #if(!$services.user.getProperties().displayHiddenDocuments())
69 #set ($hiddenCategories = $services.rendering.getHiddenMacroCategories())
70 #else
71 #set ($hiddenCategories = [])
72 #end
73 #set ($macroList = [])
74 #set ($installedMacros = [])
75 #foreach ($macroDescriptor in $macroDescriptors)
76 #set ($discard = $installedMacros.add($macroDescriptor.id.id))
77
78 #set ($macroTranslationKey = "rendering.macro.$macroDescriptor.id")
79 #set ($categories = [])
80 #set ($hidden = false)
81 #foreach ($category in $services.rendering.getMacroCategories($macroDescriptor.id))
82 #set ($macroCategoryTranslationKey = "rendering.macroCategory.$category")
83 #set ($hidden = $hidden || $hiddenCategories.contains($category))
84 #set ($discard = $categories.add({
85 'id': $category,
86 'label': "#maybeTranslate($macroCategoryTranslationKey $category)"
87 }))
88 #end
89
90 #if (!$hidden)
91 #set ($defaultCategoryTranslation = "rendering.macroCategory.${macroDescriptor.defaultCategory}")
92 #set ($discard = $macroList.add({
93 'id': $macroDescriptor.id,
94 'name': "#maybeTranslate(""${macroTranslationKey}.name"" $macroDescriptor.name)",
95 'description': "#maybeTranslate(""${macroTranslationKey}.description"" $macroDescriptor.description)",
96 'defaultCategory': "#maybeTranslate($defaultCategoryTranslation $macroDescriptor.defaultCategory)",
97 'categories': $categories
98 }))
99 #end
100 #end
101 #set ($macroList = $collectiontool.sort($macroList, 'name'))
102 #set ($discard = $data.put('list', $macroList))
103 ## Get macros provided by compatible available extensions
104 #set ($macroExtensionsList = [])
105 #set($extensionQuery = $services.extension.index.newQuery("$!request.search"))
106 #set ($discard = $extensionQuery.addFilter('components__org.xwiki.rendering.macro.Macro', '', 'MATCH'))
107 #if ($xcontext.isMainWiki())
108 #set ($discard = $extensionQuery.setCompatible(true, '', "wiki:$xcontext.database"))
109 #else
110 #set ($discard = $extensionQuery.setCompatible(true, "wiki:$xcontext.database"))
111 #end
112 #set ($discard = $extensionQuery.setInstalled(false, '', "wiki:$xcontext.database"))
113 #set ($extensions = $services.extension.index.repository.search($extensionQuery))
114 #if ($extensions.size > 0)
115 #set ($macroExtensionsMap = {})
116 #foreach ($extension in $extensions)
117 ## TODO: move to a proper generic API to check if an extension can be installed by a given user
118 #set ($extensionInstallAllowed = $services.security.authorization.hasAccess('programming', $xcontext.userReference, $NULL)
119 || (($extension.type == 'xar' || $extension.type == 'webjar')
120 && $services.security.authorization.hasAccess('admin', $xcontext.userReference, "wiki:$xcontext.database") && $services.extension.isAllowed($extension, "wiki:$xcontext.database")))
121 #foreach ($extensionComponent in $extension.getComponents())
122 #if ($extensionComponent.roleType == 'org.xwiki.rendering.macro.Macro')
123 ## Skip macros identifiers for which a macro is already installed
124 #if (!$installedMacros.contains($extensionComponent.roleHint))
125 #set ($discard = $macroExtensionsList.add({
126 'id' : {
127 'id' : $extensionComponent.roleHint
128 },
129 'name': $extensionComponent.roleHint,
130 'description': $extension.summary,
131 'defaultCategory': '_notinstalled',
132 'categories': [{
133 'id' :'_notinstalled',
134 'label': $services.localization.render('macroSelector.filter.category.notinstalled')
135 }],
136 'extensionId' : $extension.id.id,
137 'extensionVersion' : $extension.id.version.value,
138 'extensionType' : $extension.type,
139 'extensionRecommended': $extension.recommended,
140 'extensionName': $extension.name,
141 'extensionSummary': $extension.summary,
142 'extensionInstallAllowed': $extensionInstallAllowed
143 }))
144 #end
145 #end
146 #end
147 #end
148 #set ($discard = $data.put('notinstalled', $macroExtensionsList))
149 #end
150 #end
151
152 #macro (maybeGetMacroDescriptor $macroIdAsString)
153 #set ($macroId = $services.rendering.resolveMacroId($macroIdAsString))
154 #if ($macroId)
155 #set ($macroDescriptor = $services.rendering.getMacroDescriptor($macroId))
156 #if (!$macroDescriptor && $macroId.syntax)
157 ## Try the macro id without the syntax.
158 #set ($macroId = $services.rendering.resolveMacroId($macroId.id))
159 #set ($macroDescriptor = $services.rendering.getMacroDescriptor($macroId))
160 #end
161 #if ($macroDescriptor)
162 #getMacroDescriptor($macroDescriptor)
163 #end
164 #end
165 #end
166
167 #macro (getMacroDescriptor $macroDescriptor)
168 ## Translate the macro name and description.
169 #set ($macroTranslationKey = "rendering.macro.$macroDescriptor.id")
170 #ckeditor_initRequiredSkinExtensions()
171 #set ($data = {
172 'id': $macroDescriptor.id,
173 'name': "#maybeTranslate(""${macroTranslationKey}.name"" $macroDescriptor.name)",
174 'description': "#maybeTranslate(""${macroTranslationKey}.description"" $macroDescriptor.description)",
175 'defaultCategory': $macroDescriptor.defaultCategory,
176 'supportsInlineMode': $macroDescriptor.supportsInlineMode(),
177 'parameterDescriptorMap': {}
178 })
179 #if ($macroDescriptor.contentDescriptor)
180 ## Translate the content label and description.
181 ## Treat the macro content as if it is the last macro parameter.
182 #set ($data.contentDescriptor = {
183 'name': "#maybeTranslate('rendering.macroContent' 'Content')",
184 'description': "#maybeTranslate(""${macroTranslationKey}.content.description""
185 $macroDescriptor.contentDescriptor.description)",
186 'mandatory': $macroDescriptor.contentDescriptor.mandatory,
187 'deprecated': $macroDescriptor.contentDescriptor.deprecated,
188 'advanced': $macroDescriptor.contentDescriptor.advanced,
189 'defaultValue': $macroDescriptor.contentDescriptor.defaultValue,
190 'type': $macroDescriptor.contentDescriptor.type,
191 'editTemplate': '<textarea name="$content" rows="7"></textarea>',
192 'index': $macroDescriptor.parameterDescriptorMap.size()
193 })
194 #fixDescriptorType($data.contentDescriptor)
195 #end
196 #set ($groupDescriptorTree = {})
197 #foreach ($entry in $macroDescriptor.parameterDescriptorMap.entrySet())
198 #set ($parameterDescriptor = $entry.value)
199 ## Translate the parameter name and description.
200 #set ($parameterTranslationKey = "${macroTranslationKey}.parameter.$parameterDescriptor.id")
201 #set ($translatedParameterDescriptor = {
202 'id': $parameterDescriptor.id,
203 'name': "#maybeTranslate(""${parameterTranslationKey}.name"" $parameterDescriptor.name)",
204 'description': "#maybeTranslate(""${parameterTranslationKey}.description"" $parameterDescriptor.description)",
205 'mandatory': $parameterDescriptor.mandatory,
206 'deprecated': $parameterDescriptor.deprecated,
207 'advanced': $parameterDescriptor.advanced,
208 'defaultValue': $parameterDescriptor.defaultValue,
209 'type': $parameterDescriptor.displayType,
210 'hidden' : $parameterDescriptor.displayHidden,
211 'index': $foreach.index
212 })
213 #set ($translatedParameterDescriptor.caseInsensitive = $translatedParameterDescriptor.type.isEnum())
214 #set ($groupDescriptor = $parameterDescriptor.groupDescriptor)
215 #if ($groupDescriptor)
216 #handleMacroParameterGroup($groupDescriptor $groupDescriptorTree $translatedParameterDescriptor)
217 #end
218 #if ($translatedParameterDescriptor.type.getName() == 'java.lang.String'
219 && ($parameterDescriptor.defaultValue == 'false' || $parameterDescriptor.defaultValue == 'true')
220 && $macroDescriptor.parametersBeanClass.getSimpleName() == 'WikiMacroParameters')
221 #set ($translatedParameterDescriptor.defaultValue = $parameterDescriptor.defaultValue == 'true')
222 #set ($translatedParameterDescriptor.type = $translatedParameterDescriptor.defaultValue.getClass())
223 #end
224 #set ($htmlDisplayerParameters = {'name': $parameterDescriptor.id})
225 #if ($translatedParameterDescriptor.group)
226 #set ($discard = $htmlDisplayerParameters.put('data-property-group',
227 $stringtool.join($translatedParameterDescriptor.group, '/')))
228 #end
229 #set ($translatedParameterDescriptor.editTemplate = $services.display.html.display(
230 $translatedParameterDescriptor.type, $translatedParameterDescriptor.defaultValue, $htmlDisplayerParameters, 'edit'
231 ))
232 #if ("$!translatedParameterDescriptor.editTemplate" == '')
233 #set ($translatedParameterDescriptor.editTemplate = "#getMacroParameterEditTemplate(
234 $translatedParameterDescriptor)")
235 #end
236 #set ($translatedParameterDescriptor.editTemplate = $translatedParameterDescriptor.editTemplate.trim())
237 #fixDescriptorType($translatedParameterDescriptor)
238 #set ($discard = $data.parameterDescriptorMap.put($entry.key, $translatedParameterDescriptor))
239 #end
240 #if ($groupDescriptorTree.groups)
241 #set ($data.groupDescriptorTree = $groupDescriptorTree.groups)
242 #end
243 #set ($data.requiredSkinExtensions = "#ckeditor_getRequiredSkinExtensions()")
244 #end
245
246 #macro (fixDescriptorType $descriptor)
247 ## The goal of this code is to obtain a normalized string representation of the type specified in the descriptor.
248 ## See XCOMMONS-1583: Define a stable way to serialize types
249 ##
250 ## The type specified in the descriptor can be any implementation of java.lang.reflect.Type, not necessarily a
251 ## java.lang.Class. We can't use toString() because the return of Class#toString() is different than Class#getName().
252 ## We can't use Type#getTypeName() either because the access to this method is restricted from Velocity. The only
253 ## option for now is to try #getName() first and fall back on #toString() for types that are not instances of
254 ## java.lang.Class.
255 #set ($typeName = $descriptor.type.getName())
256 #if ("$!typeName" == '')
257 ## Probably not a java.lang.Class. Fall back on #toString().
258 #set ($typeName = "$!descriptor.type")
259 #end
260 ## Remove whitespace from the type name in order to have a single string representation.
261 #set ($descriptor.type = $typeName.replaceAll('\s+', ''))
262 #end
263
264 ## Builds the group tree with the following structure:
265 ##
266 ## {
267 ## 'parentGroupId': {
268 ## 'id': 'parentGroupId',
269 ## 'name': 'Parent Group',
270 ## 'feature': 'someFeature',
271 ## 'groups': {
272 ## 'childGroupId': {...},
273 ## ...
274 ## }
275 ## },
276 ## ...
277 ## }
278 #macro (handleMacroParameterGroup $groupDescriptor $groupDescriptorTree $translatedParameterDescriptor)
279 #if ($groupDescriptor.group && $groupDescriptor.group.size() > 0)
280 #set ($translatedParameterDescriptor.group = $groupDescriptor.group)
281 #set ($parentGroup = $groupDescriptorTree)
282 #foreach ($groupId in $groupDescriptor.group)
283 #if (!$parentGroup.groups)
284 #set ($parentGroup.groups = {})
285 #end
286 #set ($childGroup = $parentGroup.groups.get($groupId))
287 #if (!$childGroup)
288 #if ($groupId == $translatedParameterDescriptor.id)
289 #set ($groupName = $translatedParameterDescriptor.name)
290 #else
291 #set ($groupTranslationKey = "${macroTranslationKey}.group.$groupId")
292 #set ($groupName = "#maybeTranslate(""${groupTranslationKey}.name"" $groupId)")
293 #end
294 #set ($childGroup = {
295 'id': $groupId,
296 'name': $groupName
297 })
298 #set ($discard = $parentGroup.groups.put($groupId, $childGroup))
299 #end
300 #set ($parentGroup = $childGroup)
301 #end
302 #if ("$!groupDescriptor.feature" != '')
303 #set ($parentGroup.feature = $groupDescriptor.feature)
304 #end
305 #elseif ($groupDescriptor.feature)
306 ## This group is made of a single parameter. The feature then refers to this parameter.
307 #set ($translatedParameterDescriptor.feature = $groupDescriptor.feature)
308 #end
309 #end
310
311 #macro (getMacroParameterEditTemplate $translatedParameterDescriptor)
312 #if ($translatedParameterDescriptor.type.getName() == 'boolean'
313 || $translatedParameterDescriptor.type.getName() == 'java.lang.Boolean')
314 <input type="checkbox" name="$escapetool.xml($translatedParameterDescriptor.id)" value="true"/>##
315 ## We need to submit something in case the checkbox is not checked.
316 <input type="hidden" name="$escapetool.xml($translatedParameterDescriptor.id)" value="false"/>
317 #elseif ($translatedParameterDescriptor.type.isEnum())
318 #if ($translatedParameterDescriptor.defaultValue)
319 #set ($enumValues = $translatedParameterDescriptor.defaultValue.values())
320 #else
321 ## A parameter of type enum that doesn't have a default value is very unlikely. We attempt to read the list of
322 ## possible values from the enum type in this case, which is currently forbidden, but at least it will generate
323 ## a warning in the logs that will help us investigate the problem.
324 #set ($enumValues = $translatedParameterDescriptor.type.getEnumConstants())
325 #end
326 <select name="$escapetool.xml($translatedParameterDescriptor.id)">##
327 #foreach ($enumValue in $enumValues)
328 #set ($value = $enumValue.name())
329 #set ($label = "#maybeTranslate(""${parameterTranslationKey}.value.$value"" $enumValue)")
330 <option value="$escapetool.xml($value)">$escapetool.xml($label)</option>##
331 #end
332 </select>
333 #else
334 <input type="text" name="$escapetool.xml($translatedParameterDescriptor.id)"/>
335 #end
336 #end
337
338 #macro (maybeTranslate $key $defaultValue)
339 #if ($services.localization.get($key))
340 $services.localization.render($key)##
341 #else
342 $!defaultValue##
343 #end
344 #end
345
346 #macro (installMacroExtension $extensionId, $extensionVersion)
347 #set ($extension = $services.extension.index.repository.resolve("$extensionId/$extensionVersion"))
348 #if ($extension)
349 ## Find where to install it
350 ## 1) Check if a diffferent version is already installed
351 ## 2) Check if it's allowed to install it at current wiki level
352 #set ($rootNamespace = $NULL)
353 #set ($currentWikiNamespace = "wiki:$xcontext.database")
354 #if ($services.extension.installed.getInstalledExtension($extensionId, $rootNamespace))
355 #set ($extensionNamespace = $rootNamespace)
356 #elseif ($services.extension.installed.getInstalledExtension($extensionId, $currentWikiNamespace))
357 #set ($extensionNamespace = $currentWikiNamespace)
358 #else
359 #if ($services.extension.isAllowed($extension, "wiki:$xcontext.database"))
360 #set ($extensionNamespace = $currentWikiNamespace)
361 #else
362 #set ($extensionNamespace = $NULL)
363 #end
364 #end
365 ## Make the install non interractive
366 #set ($installRequest = $services.extension.createInstallRequest($extensionId, $extensionVersion, $extensionNamespace))
367 #set ($discard = $installRequest.setInteractive(false))
368 ## Start the install
369 #set ($job = $services.extension.install($installRequest))
370 ## Wait for the job to finish
371 #set ($discard = $job.join())
372 #if ($job.status.error)
373 ## The install failed
374 $response.sendError(500, $exceptiontool.getRootCauseMessage($job.status.error))
375 #else
376 ## The install succeeded
377 #set ($data = {
378 'extensionId': $extensionId,
379 'extensionVersion': $extensionVersion,
380 'extensionNamespace': $extensionNamespace
381 })
382 #end
383 #else
384 $response.sendError(404, $exceptiontool.getRootCauseMessage($job.status.error))
385 #end
386 #end
387 {{/velocity}}
388
389 {{velocity wiki="false"}}
390 #if ("$!request.action" == 'install')
391 #if ($services.csrf.isTokenValid($request.form_token))
392 #installMacroExtension($request.extensionId, $request.extensionVersion)
393 #else
394 $response.sendError(403)
395 #end
396 #elseif ("$!request.data" != '')
397 #set ($data = $NULL)
398 #if ($request.data == 'list')
399 #getMacroList($request.syntaxId)
400 #elseif ($request.data == 'descriptor')
401 #maybeGetMacroDescriptor($request.macroId)
402 #end
403 #if ($data)
404 #set ($discard = $response.setContentType('application/json'))
405 $jsontool.serialize($data)
406 #else
407 $response.sendError(404)
408 #end
409 #end
410 {{/velocity}}