Wiki source code of Rendering Pages
Last modified by Eleni Cojocariu on 2026/10/09 18:39
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | {{velocity}} | ||
| 2 | #set ($Q = '"') | ||
| 3 | #set ($mappingClass = 'XWikiOrg.documentation-scripts-queries.MigratedPageClass') | ||
| 4 | #set ($redirectClass = 'XWiki.RedirectClass') | ||
| 5 | ## rendering.xwiki.org is a wiki of the same farm, so the farm-wide index answers for it with | ||
| 6 | ## 'wiki:rendering'. Three filters turn documents into pages: 'hidden:false' drops the technical | ||
| 7 | ## pages, '-name:WebPreferences' the per-space preference pages, and 'doclocale:""' the | ||
| 8 | ## translations -- the index holds one document per locale, and that matters here where it does not | ||
| 9 | ## on the other wikis of this dashboard: Main.WebHome alone is translated 19 times, which is the | ||
| 10 | ## whole difference between 119 documents and 66 pages. | ||
| 11 | #set ($base = "type:DOCUMENT AND wiki:rendering AND hidden:false AND -name:WebPreferences AND doclocale:${Q}${Q}") | ||
| 12 | ## Only the fields the tables read. The first five are mandatory whatever they show: SolrQueryExecutor | ||
| 13 | ## resolves every result to a DocumentReference to check view rights, and a result it cannot resolve | ||
| 14 | ## is dropped -- an incomplete 'fl' empties the table while the total still looks right. 'class' is | ||
| 15 | ## the multi-valued field holding every class the page carries an object of; the Main scope reads it | ||
| 16 | ## to know which pages are worth a getDocument() call. | ||
| 17 | #set ($fields = 'wiki,spaces,name,type,doclocale,fullname,title_,class') | ||
| 18 | |||
| 19 | ## Scopes, in display order; the first one is the default, so the page has something to show when it | ||
| 20 | ## is opened without a parameter -- same contract as the sibling 'details' and 'extensions-details'. | ||
| 21 | #set ($scopes = []) | ||
| 22 | #set ($discard = $scopes.add({'key': 'all', 'label': 'All pages', 'heading': 'Pages of rendering.xwiki.org', 'filter': ''})) | ||
| 23 | #set ($discard = $scopes.add({'key': 'main', 'label': 'Main', 'heading': 'Main space, page by page', 'filter': " AND space_prefix:${Q}Main${Q}"})) | ||
| 24 | #set ($requested = "$!request.get('scope')") | ||
| 25 | #set ($scope = '') | ||
| 26 | #foreach ($candidate in $scopes) | ||
| 27 | #if ($candidate.key == $requested) | ||
| 28 | #set ($scope = $candidate) | ||
| 29 | #end | ||
| 30 | #end | ||
| 31 | #if ($scope == '') | ||
| 32 | #set ($scope = $scopes.get(0)) | ||
| 33 | #end | ||
| 34 | #set ($links = []) | ||
| 35 | #foreach ($candidate in $scopes) | ||
| 36 | #if ($candidate.key == $scope.key) | ||
| 37 | #set ($discard = $links.add("**$candidate.label**")) | ||
| 38 | #else | ||
| 39 | #set ($url = $doc.getURL('view', "scope=$candidate.key")) | ||
| 40 | #set ($discard = $links.add("[[$candidate.label>>path:$url]]")) | ||
| 41 | #end | ||
| 42 | #end | ||
| 43 | Scope: $stringtool.join($links, ' | ') | ||
| 44 | |||
| 45 | = $scope.heading = | ||
| 46 | |||
| 47 | #set ($query = $services.query.createQuery("${base}${scope.filter}", 'solr')) | ||
| 48 | #set ($discard = $query.setLimit(2000)) | ||
| 49 | #set ($discard = $query.bindValue('fl', $fields)) | ||
| 50 | #set ($discard = $query.bindValue('sort', 'fullname asc')) | ||
| 51 | #set ($results = $query.execute().get(0).results) | ||
| 52 | |||
| 53 | #if ($scope.key == 'main') | ||
| 54 | ## The same columns, and the same tests, as the 'details' page uses for the three legacy trees of | ||
| 55 | ## the main wiki, so the two can never disagree about what counts as done. | ||
| 56 | #set ($doneQuery = $services.query.createQuery("${base}${scope.filter} AND property.XWiki.TagClass.tags:done", 'solr')) | ||
| 57 | #set ($discard = $doneQuery.setLimit(2000)) | ||
| 58 | #set ($discard = $doneQuery.bindValue('fl', $fields)) | ||
| 59 | #set ($doneRefs = []) | ||
| 60 | #foreach ($row in $doneQuery.execute().get(0).results) | ||
| 61 | #set ($discard = $doneRefs.add($row.get('fullname'))) | ||
| 62 | #end | ||
| 63 | #set ($redirectCount = 0) | ||
| 64 | ## Distinct replacement pages of the whole scope: several legacy pages often converge on the same | ||
| 65 | ## new page, so this is a count of labels already seen, not a sum of the rows. | ||
| 66 | #set ($allTargets = []) | ||
| 67 | (% class="table" %) | ||
| 68 | |=Page|=Done|=Converted into|=Redirects to | ||
| 69 | #foreach ($row in $results) | ||
| 70 | #set ($fullName = $row.get('fullname')) | ||
| 71 | #set ($title = "$!row.get('title_')") | ||
| 72 | #if ($title == '') | ||
| 73 | #set ($title = "$!row.get('name')") | ||
| 74 | #end | ||
| 75 | #set ($title = $title.replace('|', '~|')) | ||
| 76 | #set ($classes = $row.get('class')) | ||
| 77 | #set ($newPages = '') | ||
| 78 | #set ($redirect = '') | ||
| 79 | #if ($classes && ($classes.contains($mappingClass) || $classes.contains($redirectClass))) | ||
| 80 | ## 'fullname' is the reference inside its own wiki, so it has to be prefixed to be resolved | ||
| 81 | ## from here -- this page runs on the main wiki. | ||
| 82 | #set ($legacyDoc = $xwiki.getDocument("rendering:$fullName")) | ||
| 83 | #set ($mapping = $legacyDoc.getObject($mappingClass)) | ||
| 84 | #if ($mapping) | ||
| 85 | #set ($newPages = "$!mapping.getValue('newPages')") | ||
| 86 | #end | ||
| 87 | #set ($redirectObject = $legacyDoc.getObject($redirectClass)) | ||
| 88 | #if ($redirectObject) | ||
| 89 | #set ($redirect = "$!redirectObject.getValue('location')") | ||
| 90 | #end | ||
| 91 | #end | ||
| 92 | #if ($doneRefs.contains($fullName)) | ||
| 93 | #set ($doneCell = '✅') | ||
| 94 | #else | ||
| 95 | #set ($doneCell = '❌') | ||
| 96 | #end | ||
| 97 | #set ($targets = []) | ||
| 98 | #set ($targetLabels = []) | ||
| 99 | #foreach ($line in $newPages.split('[\r\n]+')) | ||
| 100 | ## The field holds one wiki link per line, so that moving a replacement page rewrites it: the | ||
| 101 | ## rename job only follows links, and a bare reference it cannot see goes stale silently. Values | ||
| 102 | ## written before that change are still bare, hence unwrapping rather than parsing a link. | ||
| 103 | #set ($target = $line.trim().replaceAll('^\[\[', '').replaceAll('\]\]$', '')) | ||
| 104 | #set ($target = $target.replaceAll('\|\|.*$', '').replaceAll('^.*>>', '')) | ||
| 105 | #set ($target = $target.replaceAll('^doc:', '').trim()) | ||
| 106 | #if ($target != '') | ||
| 107 | #set ($label = $target.replaceAll('^xwiki:', '').replaceAll('\.WebHome$', '')) | ||
| 108 | #set ($discard = $targetLabels.add($label)) | ||
| 109 | #set ($discard = $targets.add("[[$label>>doc:$target]]")) | ||
| 110 | #if (!$allTargets.contains($label)) | ||
| 111 | #set ($discard = $allTargets.add($label)) | ||
| 112 | #end | ||
| 113 | #end | ||
| 114 | #end | ||
| 115 | ## A redirect names a replacement too, and it is the only one most pages here have: list it as | ||
| 116 | ## well, unless the mapping object already records it. Its 'location' may or may not name a wiki | ||
| 117 | ## -- the one redirect on this wiki points at the main one -- so prefixing it with 'rendering:' | ||
| 118 | ## unconditionally builds a reference that resolves to nothing and renders as a red link to a | ||
| 119 | ## page that does exist. | ||
| 120 | #if ($redirect == '') | ||
| 121 | #set ($redirectCell = '—') | ||
| 122 | #else | ||
| 123 | #set ($redirectCount = $redirectCount + 1) | ||
| 124 | #set ($redirectRef = $redirect.trim()) | ||
| 125 | #if ($redirectRef.matches('^[^:]+:.*')) | ||
| 126 | #set ($redirectTarget = $redirectRef) | ||
| 127 | #else | ||
| 128 | #set ($redirectTarget = "rendering:$redirectRef") | ||
| 129 | #end | ||
| 130 | #set ($redirectLabel = $redirectTarget.replaceAll('^xwiki:', '').replaceAll('^rendering:', '').replaceAll('\.WebHome$', '')) | ||
| 131 | #set ($redirectCell = "[[$redirectLabel>>doc:$redirectTarget]]") | ||
| 132 | #if (!$targetLabels.contains($redirectLabel)) | ||
| 133 | #set ($discard = $targets.add("[[$redirectLabel>>doc:$redirectTarget]] //(redirect)//")) | ||
| 134 | #end | ||
| 135 | #if (!$allTargets.contains($redirectLabel)) | ||
| 136 | #set ($discard = $allTargets.add($redirectLabel)) | ||
| 137 | #end | ||
| 138 | #end | ||
| 139 | #if ($targets.isEmpty()) | ||
| 140 | #set ($targetCell = '—') | ||
| 141 | #else | ||
| 142 | #set ($targetCell = $stringtool.join($targets, '\\')) | ||
| 143 | #end | ||
| 144 | |[[$title>>doc:rendering:$fullName]]|$doneCell|$targetCell|$redirectCell | ||
| 145 | #end | ||
| 146 | |=**$results.size() pages**|=**$doneRefs.size() done**|=**$allTargets.size() new pages**|=**$redirectCount redirects** | ||
| 147 | #else | ||
| 148 | ## Grouped by top-level space, because half of this wiki is the flavor it was installed with (Help, | ||
| 149 | ## Sandbox, ColorThemes, Panels, XWiki) rather than rendering documentation, and only the grouping | ||
| 150 | ## shows which half is which. A sorted map keeps the groups in alphabetical order; the Solr 'sort' | ||
| 151 | ## keeps the rows inside each of them in reference order. | ||
| 152 | #set ($buckets = $collectiontool.sortedMap) | ||
| 153 | #foreach ($solrDoc in $results) | ||
| 154 | #set ($fullName = "$!solrDoc.getFirstValue('fullname')") | ||
| 155 | #set ($dot = $fullName.indexOf('.')) | ||
| 156 | #if ($dot > 0) | ||
| 157 | #set ($space = $fullName.substring(0, $dot)) | ||
| 158 | #else | ||
| 159 | #set ($space = $fullName) | ||
| 160 | #end | ||
| 161 | #if (!$buckets.get($space)) | ||
| 162 | #set ($discard = $buckets.put($space, [])) | ||
| 163 | #end | ||
| 164 | #set ($discard = $buckets.get($space).add($solrDoc)) | ||
| 165 | #end | ||
| 166 | #foreach ($space in $buckets.keySet()) | ||
| 167 | #set ($list = $buckets.get($space)) | ||
| 168 | |||
| 169 | == $space: $list.size() == | ||
| 170 | |||
| 171 | (% class="table" %) | ||
| 172 | |=Page|=Reference | ||
| 173 | #foreach ($solrDoc in $list) | ||
| 174 | #set ($fullName = "$!solrDoc.getFirstValue('fullname')") | ||
| 175 | #set ($title = "$!solrDoc.getFirstValue('title_')") | ||
| 176 | #if ($title == '') | ||
| 177 | #set ($title = "$!solrDoc.getFirstValue('name')") | ||
| 178 | #end | ||
| 179 | #set ($title = $title.replace('|', '~|')) | ||
| 180 | |[[$title>>doc:rendering:$fullName]]|$fullName | ||
| 181 | #end | ||
| 182 | #end | ||
| 183 | |||
| 184 | == Total == | ||
| 185 | |||
| 186 | **$results.size()** pages, in $buckets.size() top-level spaces. | ||
| 187 | #end | ||
| 188 | {{/velocity}} | ||
| 189 | |||
| 190 | A page counts as **done** when it carries the ##done## tag. //Converted into// is the ##newPages## field of its [[MigratedPageClass>>XWikiOrg.documentation-scripts-queries.MigratedPageClass]] object, so it stays empty until someone records where the content went. The total under //Converted into// counts the **distinct** replacement pages of the scope, so it is smaller than the sum of the rows whenever several pages were merged into one. //Redirects to// is the ##location## of an ##XWiki.RedirectClass## object: such a page already sends its readers elsewhere, whether or not anyone marked it as done. Neither the tag nor the mapping class has been used on this wiki yet, so the Main table currently shows the starting state: nothing done, one redirect. |