Wiki source code of Extension Pages

Last modified by Eleni Cojocariu on 2026/10/08 09:27

Show last authors
1 {{velocity}}
2 #set ($Q = '"')
3 #set ($idField = 'property.ExtensionCode.ExtensionClass.id_string')
4 #set ($extBase = "type:DOCUMENT AND wiki:extensions AND hidden:false AND class:${Q}ExtensionCode.ExtensionClass${Q}")
5 ## Only the fields the table actually reads. Without this, Solr ships every stored field of every
6 ## extension page -- the whole rendered content and every object property -- which is where most of
7 ## the page's time went. The first five are mandatory whatever the table shows: SolrQueryExecutor
8 ## resolves each result to a DocumentReference to check view rights, and a result it cannot resolve
9 ## is dropped, so an incomplete 'fl' empties the table while the total still looks right.
10 ## 'website' is a TextArea, so it is NOT indexed under a '_string' field like the id: TypedValue.TEXT
11 ## is null, so DocumentSolrMetadataExtractor suffixes it with the document's locale instead
12 ## (website_en here). A glob in 'fl' asks for it whatever that locale turns out to be, and the row
13 ## loop finds it back by prefix -- the sort twin website_sortString is stored="false" and never comes.
14 #set ($websitePrefix = 'property.ExtensionCode.ExtensionClass.website_')
15 #set ($fields = "wiki,spaces,name,type,doclocale,fullname,title_,${idField},${websitePrefix}*")
16
17 ## The project/pom scope reads a different population: pages carrying an ExtensionCode.ProjectClass
18 ## object (a project groups the modules of one codebase and is NOT an extension page -- it holds no
19 ## ExtensionClass object, so it appears in neither of the two scopes above) and pages carrying an
20 ## ExtensionCode.ExtensionProxyClass object (their fields are imported from a Maven repository, i.e.
21 ## they come from the pom). 19 pages are both, so the query ORs the two classes instead of summing.
22 ## 'class' is the multi-valued field holding every class the page has an object of -- it is what the
23 ## two check-mark columns are read from, so it has to be in 'fl' like any other field the table uses.
24 #set ($projIdField = 'property.ExtensionCode.ProjectClass.id_string')
25 #set ($pomBase = "type:DOCUMENT AND wiki:extensions AND hidden:false AND (class:${Q}ExtensionCode.ProjectClass${Q} OR class:${Q}ExtensionCode.ExtensionProxyClass${Q})")
26 #set ($pomFields = "wiki,spaces,name,type,doclocale,fullname,title_,class,${idField},${projIdField}")
27
28 ## Scopes, in display order; the first one is the default, so the page has something to show when it
29 ## is opened without a parameter -- same contract as the sibling 'details' page and its tree switcher.
30 #set ($scopes = [])
31 #set ($discard = $scopes.add({'key': 'noid', 'label': 'Without id', 'heading': 'Extension pages without id'}))
32 #set ($discard = $scopes.add({'key': 'withid', 'label': 'With id', 'heading': 'Extension pages with id, grouped by id prefix'}))
33 #set ($discard = $scopes.add({'key': 'project', 'label': 'Project / pom pages', 'heading': 'Project pages and pom-imported extension pages'}))
34 #set ($requested = "$!request.get('scope')")
35 #set ($scope = '')
36 #foreach ($candidate in $scopes)
37 #if ($candidate.key == $requested)
38 #set ($scope = $candidate)
39 #end
40 #end
41 #if ($scope == '')
42 #set ($scope = $scopes.get(0))
43 #end
44 #set ($links = [])
45 #foreach ($candidate in $scopes)
46 #if ($candidate.key == $scope.key)
47 #set ($discard = $links.add("**$candidate.label**"))
48 #else
49 #set ($url = $doc.getURL('view', "scope=$candidate.key"))
50 #set ($discard = $links.add("[[$candidate.label>>path:$url]]"))
51 #end
52 #end
53 Scope: $stringtool.join($links, ' | ')
54
55 ## Groups, in display order. "others" receives whatever matches no prefix.
56 #set ($groups = [])
57 #set ($discard = $groups.add({'key': 'platform', 'heading': 'org.xwiki.platform extensions', 'prefix': 'org.xwiki.platform'}))
58 #set ($discard = $groups.add({'key': 'contrib', 'heading': 'org.xwiki.contrib extensions', 'prefix': 'org.xwiki.contrib'}))
59 #set ($discard = $groups.add({'key': 'rendering', 'heading': 'org.xwiki.rendering extensions', 'prefix': 'org.xwiki.rendering'}))
60 #set ($discard = $groups.add({'key': 'commons', 'heading': 'org.xwiki.commons extensions', 'prefix': 'org.xwiki.commons'}))
61 #set ($discard = $groups.add({'key': 'others', 'heading': 'Other extensions', 'prefix': ''}))
62
63 = $scope.heading =
64
65 ## Every extension page has the id field indexed; the ones "without an id" hold the empty string.
66 ## Splitting here rather than in Velocity means a scope only pays for the rows it shows.
67 #if ($scope.key == 'project')
68 #set ($statement = $pomBase)
69 #set ($fl = $pomFields)
70 #else
71 #if ($scope.key == 'noid')
72 #set ($statement = "${extBase} AND ${idField}:${Q}${Q}")
73 #else
74 #set ($statement = "${extBase} AND -${idField}:${Q}${Q}")
75 #end
76 #set ($fl = $fields)
77 #end
78 #set ($query = $services.query.createQuery($statement, 'solr'))
79 #set ($discard = $query.setLimit(2000))
80 #set ($discard = $query.bindValue('fl', $fl))
81 ## Alphabetical by the title the Page column shows. title_sort is the schema's dedicated sort field
82 ## for it (a copyField of title_*, of the case-insensitive 'lowercase' type); title_ itself is
83 ## analysed and cannot be sorted on. Solr sorts before the rights filter removes rows, so the order
84 ## survives filtering, and the buckets below keep it.
85 #set ($discard = $query.bindValue('sort', 'title_sort asc'))
86 #set ($results = $query.execute().get(0).results)
87
88 #if ($scope.key == 'noid')
89 (% class="table" %)
90 |=Page|=Website
91 #set ($withWebsite = 0)
92 #foreach ($solrDoc in $results)
93 #set ($title = "$!solrDoc.getFirstValue('title_')")
94 #if ($title == '')
95 #set ($title = "$!solrDoc.getFirstValue('name')")
96 #end
97 #set ($title = $title.replace('|', '~|'))
98 #set ($docWebsite = '')
99 #foreach ($fieldName in $solrDoc.getFieldNames())
100 #if ($docWebsite == '' && $fieldName.startsWith($websitePrefix))
101 #set ($docWebsite = "$!solrDoc.getFirstValue($fieldName)")
102 #end
103 #end
104 #if ($docWebsite == '')
105 #set ($website = '-')
106 #else
107 #set ($withWebsite = $withWebsite + 1)
108 #set ($website = $docWebsite.replace('|', '~|'))
109 #end
110 |[[$title>>doc:extensions:$solrDoc.getFirstValue('fullname')]]|$website
111 #end
112 |**Total: $results.size()**|**$withWebsite with a website**
113 #elseif ($scope.key == 'project')
114 ## Same buckets as the 'withid' scope, on the id resolved below. Sorting is done here rather than
115 ## in Solr because the id comes from either of two fields depending on the page, which no single
116 ## Solr 'sort' can express -- the bound 'title_sort' only decides the order inside a bucket when
117 ## two rows share an id.
118 #set ($buckets = {})
119 #foreach ($g in $groups)
120 #set ($discard = $buckets.put($g.key, []))
121 #end
122 #set ($bothCount = 0)
123 #foreach ($solrDoc in $results)
124 ## The project object's id when there is one, the extension object's otherwise. Both classes
125 ## index an unset id as the empty string rather than leaving the field out, so the first
126 ## non-empty value is what has to be looked for, not the first value.
127 #set ($extId = '')
128 #foreach ($values in [$solrDoc.getFieldValues($projIdField), $solrDoc.getFieldValues($idField)])
129 #if ($values)
130 #foreach ($v in $values)
131 #if ($extId == '' && "$!v" != '')
132 #set ($extId = "$v")
133 #end
134 #end
135 #end
136 #end
137 ## The first matching prefix wins
138 #set ($docKey = 'others')
139 #foreach ($g in $groups)
140 #if ($docKey == 'others' && $g.key != 'others' && $extId.startsWith($g.prefix))
141 #set ($docKey = $g.key)
142 #end
143 #end
144 ## 'class' is multi-valued: it holds every class the page carries an object of. Resolved to two
145 ## booleans here so the render loop below stays a straight render.
146 #set ($classes = $solrDoc.getFieldValues('class'))
147 #set ($isProject = false)
148 #set ($isPom = false)
149 #if ($classes)
150 #set ($isProject = $classes.contains('ExtensionCode.ProjectClass'))
151 #set ($isPom = $classes.contains('ExtensionCode.ExtensionProxyClass'))
152 #end
153 #if ($isProject && $isPom)
154 #set ($bothCount = $bothCount + 1)
155 #end
156 ## Sorted on 'sortid', not on 'id': $collectiontool.sort compares Strings case-sensitively, so a
157 ## single upper-case group id would otherwise sort ahead of every lower-case one.
158 #set ($discard = $buckets.get($docKey).add({'doc': $solrDoc, 'id': $extId, 'sortid': $extId.toLowerCase(), 'project': $isProject, 'pom': $isPom}))
159 #end
160 #set ($projectCount = 0)
161 #set ($pomCount = 0)
162 #foreach ($g in $groups)
163 #set ($list = $collectiontool.sort($buckets.get($g.key), 'sortid'))
164
165 == $g.heading: $list.size() ==
166
167 (% class="table" %)
168 |=Extension|=Id|=Project page|=Pom page
169 #foreach ($item in $list)
170 #set ($title = "$!item.doc.getFirstValue('title_')")
171 #if ($title == '')
172 #set ($title = "$!item.doc.getFirstValue('name')")
173 #end
174 #set ($title = $title.replace('|', '~|'))
175 #if ($item.id == '')
176 #set ($extId = '-')
177 #else
178 #set ($extId = $item.id.replace('|', '~|'))
179 #end
180 #if ($item.project)
181 #set ($projectCell = '✅')
182 #set ($projectCount = $projectCount + 1)
183 #else
184 #set ($projectCell = '')
185 #end
186 #if ($item.pom)
187 #set ($pomCell = '✅')
188 #set ($pomCount = $pomCount + 1)
189 #else
190 #set ($pomCell = '')
191 #end
192 |[[$title>>doc:extensions:$item.doc.getFirstValue('fullname')]]|$extId|$projectCell|$pomCell
193 #end
194 #end
195
196 == Total ==
197
198 **$results.size()** pages: **$projectCount** project pages and **$pomCount** pom pages, **$bothCount** of them both.
199 #else
200 ## Buckets: group key -> list of {doc, id}
201 #set ($buckets = {})
202 #foreach ($g in $groups)
203 #set ($discard = $buckets.put($g.key, []))
204 #end
205 #foreach ($solrDoc in $results)
206 ## First non-empty id of the page
207 #set ($docId = '')
208 #set ($values = $solrDoc.getFieldValues($idField))
209 #if ($values)
210 #foreach ($v in $values)
211 #if ($docId == '' && "$!v" != '')
212 #set ($docId = "$v")
213 #end
214 #end
215 #end
216 ## The first matching prefix wins
217 #set ($docKey = 'others')
218 #foreach ($g in $groups)
219 #if ($docKey == 'others' && $g.key != 'others' && $docId.startsWith($g.prefix))
220 #set ($docKey = $g.key)
221 #end
222 #end
223 ## The website URL, under whatever locale suffix it was indexed with
224 #set ($docWebsite = '')
225 #foreach ($fieldName in $solrDoc.getFieldNames())
226 #if ($docWebsite == '' && $fieldName.startsWith($websitePrefix))
227 #set ($docWebsite = "$!solrDoc.getFirstValue($fieldName)")
228 #end
229 #end
230 #set ($discard = $buckets.get($docKey).add({'doc': $solrDoc, 'id': $docId, 'website': $docWebsite}))
231 #end
232 #foreach ($g in $groups)
233 #set ($list = $buckets.get($g.key))
234
235 == $g.heading: $list.size() ==
236
237 (% class="table" %)
238 |=Page|=Id|=Website
239 #foreach ($item in $list)
240 #set ($title = "$!item.doc.getFirstValue('title_')")
241 #if ($title == '')
242 #set ($title = "$!item.doc.getFirstValue('name')")
243 #end
244 #set ($title = $title.replace('|', '~|'))
245 #if ($item.website == '')
246 #set ($website = '-')
247 #else
248 #set ($website = $item.website.replace('|', '~|'))
249 #end
250 |[[$title>>doc:extensions:$item.doc.getFirstValue('fullname')]]|$item.id|$website
251 #end
252 #end
253
254 == Total ==
255
256 **$results.size()** extension pages with an id.
257 #end
258 {{/velocity}}

Get Connected