Changes for page Nested Pages Migration
Last modified by Bart Vastenhouw on 2022/03/31 18:13
From version 1.1
edited by Bart Vastenhouw
on 2022/02/03 12:08
on 2022/02/03 12:08
Change comment:
Install extension [org.xwiki.contrib:application-nestedpagesmigrator-ui/0.7.3]
To version 2.1
edited by Bart Vastenhouw
on 2022/03/31 18:13
on 2022/03/31 18:13
Change comment:
Install extension [org.xwiki.contrib:application-nestedpagesmigrator-ui/0.8.2]
Summary
-
Page properties (2 modified, 0 added, 0 removed)
-
Objects (0 modified, 1 added, 4 removed)
Details
- Page properties
-
- Title
-
... ... @@ -1,1 +1,0 @@ 1 -Nested Pages Migration - Content
-
... ... @@ -1,288 +1,0 @@ 1 -{{velocity output="false"}} 2 -#************************************************************************ 3 - * Compute and return the maximum authorized length for the full name. 4 - ************************************************************************# 5 -#macro(getLocalReferenceMaxLength) 6 - #set ($localReferenceMaxLength = '255') 7 - ## Available since XWiki 11.4RC1. 8 - #if ($doc.localReferenceMaxLength) 9 - #set ($localReferenceMaxLength = $doc.localReferenceMaxLength) 10 - #end 11 - ## The document reference size limit was increased from 255 to 768 (the maximum supported by MySQL) in XWiki 13.2. 12 - #if ($services.extension.core.getCoreExtension('org.xwiki.platform:xwiki-platform-model').id.version.compareTo('13.2') >= 0) 13 - #set ($localReferenceMaxLength = $mathtool.sub($localReferenceMaxLength, $xcontext.database.length())) 14 - #else 15 - ## To avoid issues with documents with path too long, take some extra margin, higher than the wiki name length. 16 - #set ($localReferenceMaxLength = $mathtool.sub($localReferenceMaxLength, 50)) 17 - #end 18 - $localReferenceMaxLength 19 -#end 20 -{{/velocity}} 21 - 22 -{{velocity}} 23 -#if (!$services.security.authorization.hasAccess('admin', $xcontext.userReference, $doc.documentReference.wikiReference)) 24 - {{error}} 25 - You don't have the right to use this tool on this wiki. You need to be administrator. 26 - {{/error}} 27 -#else 28 -## Both job.css and extension.css are needed because the ui-progress classes that we need to display 29 -## a progress bar are in one of these 2 files depending on the XWiki version 30 -#set ($discard = $xwiki.ssfx.use('uicomponents/job/job.css', true)) 31 -#set ($discard = $xwiki.ssfx.use('uicomponents/extension/extension.css', true)) 32 -#set ($discard = $xwiki.ssfx.use('uicomponents/logging/logging.css', true)) 33 -#set ($discard = $xwiki.linkx.use($services.webjars.url('org.xwiki.platform:xwiki-platform-tree-webjar', 'tree.min.css', 34 - {'evaluate': true}), {'type': 'text/css', 'rel': 'stylesheet'})) 35 -{{html clean="false"}} 36 -<!------------------------------------------ 37 - Migration Action Template 38 - -------------------------------------------> 39 -<script id="MigrationActionTemplate" type="text/html"> 40 - <!-- ko foreach: ${escapetool.d}data.actions --> 41 - <li class="jstree-node" data-bind=" 42 - visible: (!targetDocument.equals(sourceDocument) || getNumberOfChildren() > 0 || getNumberOfPreferences() > 0 || getNumberOfRights()> 0), 43 - css: { 44 - 'jstree-closed': !displayChildren(), 45 - 'jstree-open' : displayChildren(), 46 - 'jstree-leaf' : getNumberOfChildren() == 0 && getNumberOfPreferences() == 0, 47 - 'jstree-last' : ${escapetool.d}index() == ${escapetool.d}parent.actions.length - 1 48 - }"> 49 - ## Display the tree branch 50 - <i class="jstree-icon jstree-ocl" role="presentation" data-bind="click: toggleDisplayChildren"></i> 51 - ## Display the checbox 52 - <input type="checkbox" data-bind="checked: enabled" /> 53 - ## Display the 'all' link 54 - <a href="#" data-bind="visible: !enabled() && (getNumberOfChildren() > 0 || getNumberOfPreferences() > 0 || getNumberOfRights()> 0), click: enableWithChildren" >(all)</a> 55 - ## Display the document name 56 - <strong class="documentName" data-bind="text: getTargetName(), click: toggleDisplayChildren, css: { 'bg-danger': isTooLong() }"></strong> 57 - ## Display the target location 58 - [<span data-bind="text: serializedTargetDocument()" class="monospace"></span>] 59 - ## Display if the action is a change or not 60 - <em data-bind="visible: targetDocument.equals(sourceDocument)">(unchanged)</em> 61 - ## Display if a previous document will be deleted 62 - <strong data-bind="visible: deletePrevious">(duplicated document will be deleted)</strong> 63 - ## Display the exclude page button 64 - <button class="btn btn-default btn-xs" data-bind="click: ${escapetool.d}root.excludePage, disable: targetDocument.equals(sourceDocument)">exclude page</button> 65 - ## Display the exclude space button 66 - <button class="btn btn-default btn-xs" data-bind="click: ${escapetool.d}root.excludeSpace">exclude space</button> 67 - ## Display th button to change the parent of ht document 68 - <button class="btn btn-default btn-xs" data-bind="click: ${escapetool.d}root.setParent">set parent</button> 69 - ## Display the number of children 70 - (<span data-bind="text: getNumberOfChildren()"></span> children, 71 - ## Display the number of preferences 72 - <span data-bind="text: getNumberOfPreferences()"></span> preferences, 73 - ## Display the number of rights 74 - <span data-bind="text: getNumberOfRights()"></span> rights) 75 - ## Display the original location 76 - from <a target="_blank" class="monospace" data-bind="text: serializedSourceDocument(), attr: {href: getSourceLink()}"></a> 77 - ## Display all children 78 - <!-- ko if: displayChildren() --> 79 - ## Display preferences 80 - <!-- ko if: preferences.length > 0 --> 81 - <ul data-bind="foreach: preferences" class="jstree-children"> 82 - <li class="text-warning jstree-node jstree-leaf"> 83 - <i class="jstree-icon jstree-ocl" role="presentation"></i> 84 - <input type="checkbox" data-bind="checked: enabled"/> <strong>[Preferences] <span data-bind="text: property"></span> : <span data-bind="text: value"></span></strong> (coming from <a target="_blank" class="monospace" data-bind="text: getSerializedOrigin(), attr: {href: getOriginLink()}"></a>) 85 - </li> 86 - </ul> 87 - <!-- /ko --> 88 - ## Display rights 89 - <!-- ko if: rights.length > 0 --> 90 - <ul data-bind="foreach: rights" class="jstree-children"> 91 - <li class="text-danger jstree-node jstree-leaf"> 92 - <i class="jstree-icon jstree-ocl" role="presentation"></i> 93 - <input type="checkbox" data-bind="checked: enabled"/> <strong>[Right] <span data-bind="text: toString()"></span></strong> (coming from <a target="_blank" class="monospace" data-bind="text: getSerializedOrigin(), attr: {href: getOriginLink()}"></a>) 94 - </li> 95 - </ul> 96 - <!-- /ko --> 97 - ## Display children documents 98 - <ul data-bind="template: { name: 'MigrationActionTemplate', data: {'actions': children()} }" class="jstree-children"/> 99 - <!-- /ko --> 100 - </li> 101 - <!-- /ko --> 102 -</script> 103 -<!------------------------------------------ 104 - Display Logs 105 - -------------------------------------------> 106 -<script id="DisplayLogs" type="text/html"> 107 - <h2 class="log-title">Logs: </h2> 108 - <ul class="log" data-bind="if: logs().length > 0"> 109 - <!-- ko foreach: logs --> 110 - <li class="log-item" data-bind="css: getClass()"> 111 - <div data-bind="text: message"></div> 112 - <!-- ko if: stackTrace --> 113 - <pre data-bind="text: stackTrace"></pre> 114 - <!-- /ko --> 115 - </li> 116 - <!-- /ko --> 117 - </ul> 118 -</script> 119 -<!------------------------------------------ 120 - Display plan 121 - -------------------------------------------> 122 -<script id="DisplayPlan" type="text/html"> 123 - <h2>Plan</h2> 124 - <div class="migration-plan box"> 125 - <div data-bind="if: isComputing()" id="planComputing"> 126 - <p>The plan is being computed and it could take some time. Please wait...</p> 127 - <div class="ui-progress-background"> 128 - <div class="ui-progress-bar green" data-bind="style: {width: progress() + '%'}"></div> 129 - </div> 130 - </div> 131 - <div class="box warningmessage" data-bind="visible: duplicates().length > 0"> 132 - <p>The migration have detected some duplicated documents, that are probably the consequences of a failed attempt to run the migrator.<br /> 133 - <p>If it's the first time you run the migrator, you might have a problem.</p> 134 - <p>Theses documents are:</p> 135 - <ul data-bind="foreach: {data: duplicates(), as: 'doc'}"> 136 - <li data-bind="text: doc"></li> 137 - </ul> 138 - <p>If you are ok with it, just run the migrator and these documents will be overwritten.</p> 139 - </div> 140 - <div class="box errormessage" data-bind="visible: tooLongs().length > 0"> 141 - <p>We have detected some pages that will have too long path after the migration (limit is #getLocalReferenceMaxLength()). You should rename them (or rename one of their parents) before computing a new plan.</p> 142 - <p>Theses pages are:</p> 143 - <ul data-bind="foreach: {data: tooLongs(), as: 'action'}"> 144 - <li class="monospace"><a data-bind="attr: {href: action.getSourceLink()}" target="_blank"><span data-bind="text: action.serializedSourceDocument()"></span></a> -> <span data-bind="text: action.serializedTargetDocument()"></span></li> 145 - </ul> 146 - </div> 147 - <ul data-bind="if: actions() && !isComputing() && !isPlanEmpty()" id="planTree" class="jstree jstree-xwiki jstree-xwiki-responsive jstree-container-ul"> 148 - <!-- ko template: {name: 'MigrationActionTemplate', data: {'actions': actions() }} --> 149 - <!-- /ko --> 150 - </ul> 151 - <!-- ko if: !isComputing() && isPlanEmpty() --> 152 - <div class="box infomessage"> 153 - <p>There is nothing to do!</p> 154 - </div> 155 - <!-- /ko --> 156 - <!-- ko template: {name: 'DisplayLogs', data: ${escapetool.d}root} --> 157 - <!-- /ko --> 158 - </div> 159 -</Script> 160 -<!------------------------------------------ 161 - Display configuration 162 - -------------------------------------------> 163 -<h2>Configuration</h2> 164 -<form class="xform"> 165 - <div class="row"> 166 - <div class="col-xs-12 col-md-6"> 167 - <dl> 168 - <!-- Excluded pages --> 169 - <dt><label for="excludedPages">Excluded pages</label></dt> 170 - <dd> 171 - <p class="xHint">Page references separated by commas (',')</p> 172 - <p><input type="text" id="excludedPages" data-bind="value: configuration.excludedPages"></p> 173 - </dd> 174 - <!-- Excluded spaces --> 175 - <dt><label for="excludedSpaces">Excluded spaces</label></dt> 176 - <dd> 177 - <p class="xHint">Space references separated by commas (',')</p> 178 - <p><input type="text" id="excludedSpaces" data-bind="value: configuration.excludedSpaces"> </p> 179 - </dd> 180 - <!-- Included spaces --> 181 - <dt><label for="includedSpaces">Included spaces</label></dt> 182 - <dd> 183 - <p class="xHint">Space references separated by commas (',')</p> 184 - <p><input type="text" id="includedSpaces" data-bind="value: configuration.includedSpaces"> </p> 185 - </dd> 186 - </dl> 187 - </div> 188 - <div class="col-xs-12 col-md-6"> 189 - <p><button class="btn btn-default" type="button" data-toggle="collapse" data-target="#advancedSettings" aria-expanded="false" aria-controls="advancedSettings">Advanced Settings</button></p> 190 - <dl id="advancedSettings" class="collapse well"> 191 - <!-- Exclude hidden pages --> 192 - <dt><input type="checkbox" id="excludeHiddenPages" data-bind="checked: configuration.excludeHiddenPages"> <label for="excludeHiddenPages">Exclude hidden pages</label></dt> 193 - <dd><span class="xHint">Most of the hidden pages are techinal content. Moving them can break applications.</span></dd> 194 - <!-- Exclude class pages --> 195 - <dt><input type="checkbox" id="excludeClassPages" data-bind="checked: configuration.excludeClassPages"> <label for="excludeClassPages">Exclude pages having a class</label></dt> 196 - <dd><span class="xHint">The pages are technical and moving them can break applications.</span></dd> 197 - <!-- Don't move children --> 198 - <dt><input type="checkbox" id="dontMoveChildren" data-bind="checked: configuration.dontMoveChildren"> <label for="dontMoveChildren">Do not move children</label></dt> 199 - <dd><span class="xHint">Only convert terminal pages to nested pages, without moving them under their parent.</span></dd> 200 - <!-- Add redirection --> 201 - <dt><input type="checkbox" id="addRedirection" data-bind="checked: configuration.addRedirection"> <label for="addRedirection">Add redirection</label></dt> 202 - <dd><span class="xHint">Add a redirection in the old location.</span></dd> 203 - <!-- Convert preferences --> 204 - <dt><input type="checkbox" id="convertPreferences" data-bind="checked: configuration.convertPreferences"> <label for="convertPreferences">Convert preferences</label></dt> 205 - <dd><span class="xHint">Make sure that the preferences applied on the page remain the same after the move, by dupplicating the preferences on the target document.</span></dd> 206 - <!-- Convert rights --> 207 - <dt><input type="checkbox" id="convertRights" data-bind="checked: configuration.convertRights"> <label for="convertRights">Convert rights (experimental)</label></dt> 208 - <dd><span class="xHint">Make sure that the rights applied on the page remain the same after the move <span class="text-danger">(Currently bugged)</span>.</span></dd> 209 - <!-- Excluded Object Classes --> 210 - <dt><label for="excludedObjectClasses" data-bind="click: toggleXClassList">Exclude classes</label></dt> 211 - <dd> 212 - <p class="xHint" data-bind="click: toggleXClassList">Exclude pages holding an object of one of the specified classes (separated by a coma ',').</p> 213 - <p><textarea id="excludedObjectClasses" data-bind="textInput: configuration.excludedObjectClasses, click: showXClassList" data-localReferenceMaxLength="#getLocalReferenceMaxLength()" data-xclasses="#foreach($class in $xwiki.classList)#if($foreach.count>1),#end${class}#end"></textarea></p> 214 - <div data-bind="visible: xclassListVisible"> 215 - <a data-bind="click: hideXClassList" href="#">$services.icon.renderHTML('remove') Hide</a> 216 - <ul data-bind="foreach: {data: xclasses, as: 'xclass'}" style="list-style-type: none; padding: 0;"> 217 - <li><label><input type="checkbox" data-bind="checked: xclass.selected"/> <span data-bind="text: xclass.name"</span></label></li> 218 - </ul> 219 - </div> 220 - </dd> 221 - </dl> 222 - </div> 223 - </div> 224 - <div class="clearfix"> 225 - <h2>Actions</h2> 226 - <button class="btn btn-success" data-bind="click: startBreakageDetection, disable: isComputing() || isPlanExecuting()">Detect breakages</button> 227 - <button class="btn btn-primary" data-bind="click: computePlan, disable: isComputing() || isPlanExecuting()">Compute plan</button> 228 - <button class="btn btn-primary" data-bind="disable: actions().length == 0 || isPlanExecuting() || tooLongs().length > 0, click: executePlan">Execute plan</button> 229 - <button class="btn btn-default" data-bind="disable: actions().length == 0 || isPlanExecuting(), click: cleanPlan">Clean plan (to free the memory)</button> 230 - </div> 231 -</form> 232 -<!------------------------------------------ 233 - Display plan 234 - -------------------------------------------> 235 -<div data-bind="if: isPlanRequested() && !isPlanExecuting()"> 236 - ## We escape the dollar of the knockout variable '$root' because $root also exists in velocity 237 - <!-- ko template: {name: 'DisplayPlan', data: ${escapetool.d}root} --> 238 - <!-- /ko --> 239 -</div> 240 -<!------------------------------------------ 241 - Execute Plan 242 - -------------------------------------------> 243 -<!-- ko if: isPlanExecuting() && !success()--> 244 -<div class="box" id="planExecuting"> 245 - <p>The plan is being executed and it could take some time. Please wait...</p> 246 - <div class="ui-progress-background"> 247 - <div class="ui-progress-bar green" data-bind="style: {width: progress() + '%'}"></div> 248 - </div> 249 - <!-- ko template: {name: 'DisplayLogs', data: ${escapetool.d}root} --> 250 - <!-- /ko --> 251 -</div> 252 -<!-- /ko --> 253 -<!------------------------------------------ 254 - Display breakages 255 - -------------------------------------------> 256 -<!-- ko if: isBreakageListRequested--> 257 -<h2>Breakages</h2> 258 -<div class="box"> 259 - <div data-bind="if: isComputing"> 260 - <p>The list of broken pages is being computed, please wait...</p> 261 - <div class="ui-progress-background"> 262 - <div class="ui-progress-bar green" data-bind="style: {width: progress() + '%'}"></div> 263 - </div> 264 - <!-- ko template: {name: 'DisplayLogs', data: ${escapetool.d}root} --> 265 - <!-- /ko --> 266 - </div> 267 - <div data-bind="ifnot: isComputing"> 268 - <p>If you don't migrate your pages, <strong data-bind="text: breakageList().size()"></strong> documents will lose their current parent.</p> 269 - <ul data-bind="foreach: breakageList"> 270 - <li>Page <span data-bind="text: document" class="monospace box infomessage" ></span> will lose its current parent <span data-bind="text: actualParent" class="monospace box infomessage"></span> because its location parent is <span data-bind="text: locationParent" class="monospace box infomessage"></span>.</li> 271 - </ul> 272 - </div> 273 -</div> 274 -<!-- /ko --> 275 -<!------------------------------------------ 276 - End message 277 - -------------------------------------------> 278 -<!-- ko if: success() --> 279 - <div class="box successmessage" id="planExecuted"> 280 - The plan have been executed! 281 - </div> 282 - <!-- ko template: {name: 'DisplayLogs', data: ${escapetool.d}root} --> 283 - <!-- /ko --> 284 -<!-- /ko --> 285 -{{/html}} 286 -#end 287 -{{/velocity}} 288 -
- XWiki.JavaScriptExtension[1]
-
- Caching policy
-
... ... @@ -1,1 +1,0 @@ 1 -long - Code
-
... ... @@ -1,9 +1,0 @@ 1 -require.config({ 2 - paths: { 3 - #if ("$!request.minify" == false) 4 - 'knockout': "$services.webjars.url('knockout', 'knockout.debug.js')" 5 - #else 6 - 'knockout': "$services.webjars.url('knockout', 'knockout.js')" 7 - #end 8 - } 9 -}); - Name
-
... ... @@ -1,1 +1,0 @@ 1 -Live view configuration - Parse content
-
... ... @@ -1,1 +1,0 @@ 1 -Yes - Use this extension
-
... ... @@ -1,1 +1,0 @@ 1 -currentPage
- XWiki.JavaScriptExtension[2]
-
- Caching policy
-
... ... @@ -1,1 +1,0 @@ 1 -long - Code
-
... ... @@ -1,643 +1,0 @@ 1 -require(['jquery', 'xwiki-meta', 'knockout'], function ($, xm, ko) { 2 - 'use strict'; 3 - 4 - function localSerializer(document) { 5 - var documentReference = XWiki.Model.resolve(document, XWiki.EntityType.DOCUMENT).relativeTo(xm.documentReference.extractReference(XWiki.EntityType.WIKI)); 6 - return XWiki.Model.serialize(documentReference); 7 - } 8 - 9 - function resolveLocally(document) { 10 - return XWiki.Model.resolve(document, XWiki.EntityType.DOCUMENT).relativeTo(xm.documentReference.extractReference(XWiki.EntityType.WIKI)); 11 - } 12 - 13 - /** 14 - * Class representing a migration action 15 - */ 16 - function MigrationAction(source, target, parent) { 17 - var self = this; 18 - 19 - self.parent = parent; 20 - self.sourceDocument = resolveLocally(source); 21 - self.targetDocument = resolveLocally(target); 22 - self.children = ko.observableArray(); 23 - self.displayChildren = ko.observable(false); 24 - self.enabled = ko.observable(true); 25 - self.preferences = []; 26 - self.rights = []; 27 - self.deletePrevious = false; 28 - self.localReferenceMaxLength = $('#excludedObjectClasses').data().localreferencemaxlength; 29 - 30 - self.serializedSourceDocument = function () { 31 - return XWiki.Model.serialize(self.sourceDocument); 32 - }; 33 - 34 - self.serializedTargetDocument = function () { 35 - return XWiki.Model.serialize(self.targetDocument); 36 - }; 37 - 38 - self.getNumberOfChildren = function () { 39 - var number = self.children().length; 40 - for (var i = 0; i < self.children().length; ++i) { 41 - number += self.children()[i].getNumberOfChildren(); 42 - } 43 - return number; 44 - }; 45 - 46 - self.getTargetName = function () { 47 - return self.targetDocument.getName() == 'WebHome' ? self.targetDocument.parent.getName() : self.targetDocument.getName(); 48 - } 49 - 50 - self.isTooLong = function () { 51 - return self.serializedTargetDocument().length > self.localReferenceMaxLength; 52 - } 53 - 54 - self.getSourceLink = function () { 55 - return new XWiki.Document(self.sourceDocument).getURL(); 56 - } 57 - 58 - self.toggleDisplayChildren = function() { 59 - self.displayChildren(!self.displayChildren()); 60 - } 61 - 62 - self.disableChildren = function () { 63 - for (var i = 0; i < self.children().length; ++i) { 64 - self.children()[i].enabled(false); 65 - } 66 - for (var i = 0; i < self.preferences.length; ++i) { 67 - self.preferences[i].enabled(false); 68 - } 69 - for (var i = 0; i < self.rights.length; ++i) { 70 - self.rights[i].enabled(false); 71 - } 72 - }; 73 - 74 - self.enabled.subscribe(function (newValue) { 75 - if (!newValue) { 76 - self.disableChildren(); 77 - } 78 - }); 79 - 80 - self.enableWithChildren = function() { 81 - self.enabled(true); 82 - for (var i = 0; i < self.preferences.length; ++i) { 83 - self.preferences[i].enabled(true); 84 - } 85 - for (var i = 0; i < self.rights.length; ++i) { 86 - self.rights[i].enabled(true); 87 - } 88 - for (var i = 0; i < self.children().length; ++i) { 89 - self.children()[i].enableWithChildren(); 90 - } 91 - } 92 - 93 - self.getNumberOfPreferences = function () { 94 - return self.preferences.length; 95 - } 96 - 97 - self.getNumberOfRights = function () { 98 - return self.rights.length; 99 - } 100 - } 101 - 102 - /** 103 - * Class representing a preference. 104 - */ 105 - function Preference(property, value, origin) { 106 - var self = this; 107 - 108 - self.property = property; 109 - self.value = value; 110 - self.origin = origin; 111 - self.enabled = ko.observable(true); 112 - 113 - self.getSerializedOrigin = function () { 114 - return localSerializer(self.origin); 115 - }; 116 - 117 - self.getOriginLink = function () { 118 - return new XWiki.Document(resolveLocally(self.origin)).getURL('admin'); 119 - }; 120 - } 121 - 122 - /** 123 - * Class representing a right. 124 - */ 125 - function Right(user, group, level, allow, origin) { 126 - var self = this; 127 - 128 - self.user = user; 129 - self.group = group; 130 - self.level = level; 131 - self.allow = allow; 132 - self.origin = origin; 133 - self.enabled = ko.observable(true); 134 - 135 - self.getType = function () { 136 - return self.user ? 'user' : 'group'; 137 - }; 138 - 139 - self.getTarget = function () { 140 - return self.user ? self.user : self.group; 141 - }; 142 - 143 - self.getAllow = function () { 144 - return self.allow ? 'allow' : 'deny'; 145 - }; 146 - 147 - self.toString = function () { 148 - return self.getType() + ' : ' + self.getTarget() + ', ' + self.level + ' : ' + self.getAllow(); 149 - }; 150 - 151 - self.getSerializedOrigin = function () { 152 - return localSerializer(self.origin); 153 - }; 154 - 155 - self.getOriginLink = function () { 156 - var ref = resolveLocally(self.origin); 157 - return new XWiki.Document(ref).getURL('admin', ref.name == 'WebPreferences' ? 'section=PageAndChildrenRights' : 'section=Rights'); 158 - }; 159 - } 160 - 161 - /** 162 - * Represent a breakage between location parent and actual parent 163 - */ 164 - function Breakage(document, locationParent, actualParent) { 165 - var self = this; 166 - self.document = document; 167 - self.locationParent = locationParent; 168 - self.actualParent = actualParent; 169 - } 170 - 171 - /** 172 - * Class holding the configuration used to compute the plan. 173 - */ 174 - function AppConfiguration() { 175 - this.excludeHiddenPages = ko.observable(true); 176 - this.excludeClassPages = ko.observable(true); 177 - this.dontMoveChildren = ko.observable(false); 178 - this.addRedirection = ko.observable(true); 179 - this.convertPreferences = ko.observable(true); 180 - this.convertRights = ko.observable(false); 181 - this.excludedPages = ko.observable(''); 182 - this.excludedSpaces = ko.observable('XWiki,Admin,NestedPagesMigration'); 183 - this.includedSpaces = ko.observable(''); 184 - this.excludedObjectClasses = ko.observable('XWiki.XWikiUsers,XWiki.XWikiSkins,Panels.PanelClass,Blog.BlogClass,Blog.BlogPostClass,Blog.CategoryClass,ColorThemes.ColorThemeClass,FlamingoThemesCode.ThemeClass,IconThemesCode.IconThemeClass,XWiki.SchedulerJobClass,Menu.MenuClass,XWiki.RedirectClass'); 185 - this.excludedObjectClasses.extend({ notify: 'always' }); 186 - } 187 - 188 - function getExcludedClassesArray(model) { 189 - return model.configuration.excludedObjectClasses().split(','); 190 - } 191 - 192 - function inExcludedClassesArray(name, model) { 193 - return $.inArray(name, getExcludedClassesArray(model)) >= 0; 194 - } 195 - 196 - function appendToString(string, toAppend) { 197 - var result = string; 198 - if (result.length > 0) { 199 - result += ','; 200 - } 201 - result += toAppend; 202 - return result; 203 - } 204 - 205 - function computeNewExcludedClassesList(name, value, model) { 206 - var newList = ''; 207 - var oldList = getExcludedClassesArray(model); 208 - // We walk through the old list to respect the order written in it to avoid a WTF effect 209 - for (var i = 0; i < oldList.length; ++i) { 210 - if (oldList[i] != name) { 211 - newList = appendToString(newList, oldList[i]); 212 - } 213 - } 214 - if (value) { 215 - newList = appendToString(newList, name); 216 - } 217 - model.configuration.excludedObjectClasses(newList); 218 - } 219 - 220 - function initXClassCheckbox(name, model) { 221 - var selected = ko.computed({ 222 - read: function () { 223 - return inExcludedClassesArray(name, model); 224 - }, 225 - write: function (value) { 226 - computeNewExcludedClassesList(name, value, model); 227 - } 228 - }); 229 - return {'name': name, 'selected': selected}; 230 - } 231 - 232 - /** 233 - * Represents a log entry. 234 - */ 235 - function Log(message, level, stackTrace) { 236 - var self = this; 237 - 238 - self.message = message; 239 - self.level = level; 240 - self.stackTrace = stackTrace; 241 - 242 - self.getClass = function () { 243 - return 'log-item-' + self.level.toLowerCase(); 244 - } 245 - } 246 - 247 - /** 248 - * The model of the application. All data and functions used by the application view are stored here. 249 - */ 250 - function AppViewModel() { 251 - var self = this; 252 - 253 - // Fields 254 - self.configuration = new AppConfiguration(); 255 - self.actions = ko.observableArray(); 256 - self.isPlanRequested = ko.observable(false); 257 - self.isBreakageListRequested = ko.observable(false); 258 - self.isComputing = ko.observable(false); 259 - self.xclasses = ko.observableArray(); 260 - self.xclassListVisible = ko.observable(false); 261 - self.jobId = false; 262 - self.progress = ko.observable(0); 263 - self.logs = ko.observableArray(); 264 - self.isPlanExecuting = ko.observable(false); 265 - self.success = ko.observable(false); 266 - self.duplicates = ko.observableArray(); 267 - self.tooLongs = ko.observableArray(); 268 - self.breakageList = ko.observableArray(); 269 - 270 - // Do not refresh logs and actions too often (to get better performances, because a lot of actions and logs 271 - // are pushed in the same time, so it is better to no refresh the UI at every push). 272 - self.logs.extend({ rateLimit: 200}); 273 - self.actions.extend({ rateLimit: 200}); 274 - 275 - self.toggleXClassList = function () { 276 - self.xclassListVisible(!self.xclassListVisible()); 277 - } 278 - 279 - self.showXClassList = function () { 280 - self.xclassListVisible(true); 281 - } 282 - 283 - self.hideXClassList = function () { 284 - self.xclassListVisible(false); 285 - } 286 - 287 - self.getExcludedClassesArray = function () { 288 - return self.configuration.excludedObjectClasses().split(','); 289 - } 290 - 291 - self.inExcludedClassesArray = function() { 292 - return $.inArray(name, self.getExcludedClassesArray()) >= 0; 293 - } 294 - 295 - self.computeNewExcludedClassesList = function (name, value) { 296 - var newList = ''; 297 - var oldList = self.getExcludedClassesArray(); 298 - // We walk through the old list to respect the order written in it to avoid a WTF effect 299 - for (var i = 0; i < oldList.length; ++i) { 300 - if (oldList[i] != name) { 301 - if (newList.length > 0) { 302 - newList += ','; 303 - } 304 - newList += oldList[i]; 305 - } 306 - } 307 - if (value) { 308 - if (newList.length > 0) { 309 - newList += ','; 310 - } 311 - newList += name;; 312 - } 313 - self.configuration.excludedObjectClasses(newList); 314 - } 315 - 316 - self.initXClassCheckbox = function (name) { 317 - var selected = ko.computed({ 318 - read: function () { 319 - return inExcludedClassesArray(name, self); 320 - }, 321 - write: function (value) { 322 - computeNewExcludedClassesList(name, value, self); 323 - } 324 - }); 325 - return {'name': name, 'selected': selected}; 326 - } 327 - 328 - /** 329 - * Initialize the XClasses fields. 330 - */ 331 - self.initXClasses = function() { 332 - var xclasses = $('#excludedObjectClasses').attr('data-xclasses').split(','); 333 - for (var i = 0; i < xclasses.length; ++i) { 334 - self.xclasses.push(initXClassCheckbox(xclasses[i], self)); 335 - } 336 - } 337 - 338 - /** 339 - * Computed observable variable that returns if the plan is empty. 340 - */ 341 - self.isPlanEmpty = ko.computed(function () { 342 - return self.actions().length == 0; 343 - }); 344 - 345 - /** 346 - * Send an ajax request to start a new job for the creation of a plan or the breakage detection 347 - */ 348 - self.startComputationJob = function (action, callback) { 349 - self.progress(0); 350 - self.isComputing(true); 351 - self.actions.removeAll(); 352 - self.duplicates.removeAll(); 353 - self.tooLongs.removeAll(); 354 - self.breakageList.removeAll(); 355 - $.getJSON(new XWiki.Document('Service', 'NestedPagesMigration').getURL('get', 'outputSyntax=plain'), { 356 - 'action' : action, 357 - 'excludeHiddenPages' : self.configuration.excludeHiddenPages(), 358 - 'excludeClassPages' : self.configuration.excludeClassPages(), 359 - 'dontMoveChildren' : self.configuration.dontMoveChildren(), 360 - 'addRedirection' : self.configuration.addRedirection(), 361 - 'convertPreferences' : self.configuration.convertPreferences(), 362 - 'convertRights' : self.configuration.convertRights(), 363 - 'excludedPages' : self.configuration.excludedPages(), 364 - 'excludedSpaces' : self.configuration.excludedSpaces(), 365 - 'includedSpaces' : self.configuration.includedSpaces(), 366 - 'excludedObjectClasses': self.configuration.excludedObjectClasses() 367 - }) 368 - .done(callback) 369 - .fail(function () { 370 - console.log(action == 'startBreakageDetection' ? 'ERROR: Failed to start the breakage detection.' 371 - : 'ERROR: Failed to start a new plan computation.' ); 372 - }); 373 - } 374 - 375 - /** 376 - * Send an ajax request to start a new job for the creation of a plan. 377 - */ 378 - self.computePlan = function() { 379 - self.isPlanRequested(true); 380 - self.isBreakageListRequested(false); 381 - self.startComputationJob('createPlan', function (data) { 382 - self.jobId = data.jobId; 383 - self.logs.removeAll(); 384 - self.getJobStatusAndLogs('createmigrationplan', function() { self.getMigrationPlan(); }); 385 - }); 386 - }; 387 - 388 - /** 389 - * Perform an AJAX request to get the current job status and its logs, so we can update the progress bar and the 390 - * logs UI. 391 - */ 392 - self.getJobStatusAndLogs = function (jobAction, successCallback) { 393 - $.getJSON(new XWiki.Document('Service', 'NestedPagesMigration').getURL('get', 'outputSyntax=plain'), { 394 - 'action' : 'printStatusAndLogs', 395 - 'jobAction' : jobAction 396 - }).done(function (data) { 397 - var logs = data.logs; 398 - for (var i = self.logs().length; i < logs.length; ++i) { 399 - self.logs.push(new Log(logs[i].message, logs[i].level, logs[i].stackTrace)); 400 - } 401 - var state = data.state; 402 - if (state == 'FINISHED') { 403 - self.progress(100); 404 - if (successCallback) { 405 - successCallback(); 406 - } 407 - } else if (state == 'RUNNING' || state == 'NONE') { 408 - self.progress(data.progress * 100); 409 - // retry in 0.8 seconds 410 - setTimeout(function() { self.getJobStatusAndLogs(jobAction, successCallback); }, 800); 411 - } 412 - }); 413 - }; 414 - 415 - /** 416 - * Get the migration plan that have been computed, in order to display it. 417 - */ 418 - self.getMigrationPlan = function () { 419 - $.getJSON(new XWiki.Document('Service', 'NestedPagesMigration').getURL('get', 'outputSyntax=plain'), { 420 - 'action': 'printPlan' 421 - }).done(function (data) { 422 - console.log('INFO: Plan computed'); 423 - var parseAction = function (data, parent) { 424 - var action = new MigrationAction(data.sourceDocument, data.targetDocument, parent); 425 - if (data.children) { 426 - for (var i = 0; i < data.children.length; ++i) { 427 - action.children.push(parseAction(data.children[i], action)); 428 - } 429 - } 430 - if (data.preferences) { 431 - for (var i = 0; i < data.preferences.length; ++i) { 432 - action.preferences[action.preferences.length] = new Preference(data.preferences[i].name, data.preferences[i].value, data.preferences[i].origin); 433 - } 434 - } 435 - if (data.rights) { 436 - for (var i = 0; i < data.rights.length; ++i) { 437 - action.rights[action.rights.length] = new Right(data.rights[i].user, data.rights[i].group, data.rights[i].level, data.rights[i].allow == "true", data.rights[i].origin) 438 - } 439 - } 440 - if (data.deletePrevious) { 441 - action.deletePrevious = true; 442 - self.duplicates.push(action.serializedTargetDocument()); 443 - } 444 - if (action.isTooLong()) { 445 - self.tooLongs.push(action); 446 - } 447 - return action; 448 - }; 449 - 450 - if (data) { 451 - for (var i = 0; i < data.length; ++i) { 452 - self.actions.push(parseAction(data[i], false)); 453 - } 454 - } 455 - // Plan is loaded 456 - self.isComputing(false); 457 - console.log('INFO: Plan have been parsed.'); 458 - }).fail(function () { 459 - new XWiki.widgets.Notification('Failed to load the computed plan', 'error'); 460 - //TODO: being able to restart the computation 461 - }); 462 - }; 463 - 464 - self.startBreakageDetection = function () { 465 - self.isPlanRequested(false); 466 - self.isBreakageListRequested(true); 467 - self.startComputationJob('startBreakageDetection', function (data) { 468 - self.jobId = data.jobId; 469 - self.logs.removeAll(); 470 - self.getJobStatusAndLogs('breakagedetection', function() { self.getBreakages(); }); 471 - }); 472 - }; 473 - 474 - /** 475 - * Get breakage list that have been computed, in order to display it. 476 - */ 477 - self.getBreakages = function () { 478 - $.getJSON(new XWiki.Document('Service', 'NestedPagesMigration').getURL('get', 'outputSyntax=plain'), { 479 - 'action': 'printBreakages' 480 - }).done(function (data) { 481 - for (var i = 0; i < data.length; ++i) { 482 - self.breakageList.push(new Breakage(data[i].documentReference, data[i].locationParent, data[i].actualParent)); 483 - } 484 - // Plan is loaded 485 - self.isComputing(false); 486 - }).fail(function () { 487 - new XWiki.widgets.Notification('Failed to load the breakages', 'error'); 488 - //TODO: being able to restart the computation 489 - }); 490 - }; 491 - 492 - /** 493 - * Called when the user click on the "exclude page" button. 494 - */ 495 - self.excludePage = function() { 496 - var page = this.serializedSourceDocument(); 497 - if (confirm('Are you sure to exclude the page ['+page+'] from the migration? The plan may be recomputed.')) { 498 - self.configuration.excludedPages(appendToString(self.configuration.excludedPages(), page)); 499 - // Adding an exclusion can seriously change the plan (if children are moved), so we re-compute it 500 - if (!self.configuration.dontMoveChildren()) { 501 - self.computePlan(); 502 - } else { 503 - var sourceDoc = this.sourceDocument; 504 - var detectAction = function (action) { 505 - return action.sourceDocument.equals(sourceDoc); 506 - }; 507 - if (this.parent) { 508 - this.parent.children.remove(detectAction); 509 - } else { 510 - self.actions.remove(detectAction); 511 - } 512 - } 513 - } 514 - }; 515 - 516 - /** 517 - * Called when the user click on the "exclude space" button. 518 - */ 519 - self.excludeSpace = function() { 520 - var space = XWiki.Model.serialize(this.sourceDocument.extractReference(XWiki.EntityType.SPACE)); 521 - if (confirm('Are you sure to exclude the space ['+space+'] from the migration? The plan will be recomputed.')) { 522 - self.configuration.excludedSpaces(appendToString(self.configuration.excludedSpaces(), space)); 523 - self.computePlan(); 524 - } 525 - }; 526 - 527 - /** 528 - * Called when the user click on the "set parent" button. 529 - */ 530 - self.setParent = function () { 531 - // The reference needs to be complete in order to use XWiki.Document#getRestURL() 532 - if (this.sourceDocument.getRoot().type != XWiki.EntityType.WIKI) { 533 - this.sourceDocument.appendParent(xm.documentReference.extractReference(XWiki.EntityType.WIKI)) 534 - }; 535 - // First get the current parent 536 - var restURL = new XWiki.Document(this.sourceDocument).getRestURL('', 'media=json'); 537 - var notification = new XWiki.widgets.Notification('Getting information', 'inprogress'); 538 - $.getJSON(restURL).done(function (data) { 539 - notification.hide(); 540 - // Now ask the new parent to set 541 - var parent = prompt("Enter the fullName of the parent that you want to set: (this will be applied immediatly)", data.parent); 542 - if (parent != null) { 543 - notification = new XWiki.widgets.Notification('Saving...', 'inprogress'); 544 - // Set the new parent using the REST API 545 - $.ajax(restURL, { 546 - dataType: 'json', 547 - data: {'parent': parent}, 548 - method: 'PUT' 549 - }).done(function(data) { 550 - // TODO: put something here, and handle error; 551 - self.computePlan(); 552 - notification.replace(new XWiki.widgets.Notification('New parent was set, computing the new plan.', 'done')); 553 - }).fail(function() { 554 - notification.replace(new XWiki.widgets.Notification('Failed to save the page.', 'error')); 555 - }); 556 - } 557 - }).fail(function() { 558 - notification.replace(new XWiki.widgets.Notification('Failed to get the current parent of the page which may not exist.', 'error')); 559 - }); 560 - }; 561 - 562 - /** 563 - * Called when the user clicks on "execute plan" 564 - */ 565 - self.executePlan = function () { 566 - if (!confirm('Are you sure? This operation cannot be undone.')) { 567 - return; 568 - } 569 - self.isPlanExecuting(true); 570 - self.progress(0); 571 - 572 - var getDisabledActions = function (action) { 573 - var disabledActions = ''; 574 - if (!action.enabled()) { 575 - disabledActions += action.serializedSourceDocument() + '_page,'; 576 - } 577 - for (var i = 0; i < action.preferences.length; ++i) { 578 - var preference = action.preferences[i]; 579 - if (!preference.enabled()) { 580 - disabledActions += action.serializedSourceDocument() + '_preference_' + i + ','; 581 - } 582 - } 583 - for (var i = 0; i < action.rights.length; ++i) { 584 - var right = action.rights[i]; 585 - if (!right.enabled()) { 586 - disabledActions += action.serializedSourceDocument() + '_right_' + i + ','; 587 - } 588 - } 589 - for (var i = 0; i < action.children().length; ++i) { 590 - disabledActions += getDisabledActions(action.children()[i]); 591 - } 592 - return disabledActions; 593 - }; 594 - 595 - var disabledActions = ''; 596 - for (var i = 0; i < self.actions().length; ++i) { 597 - var action = self.actions()[i]; 598 - disabledActions += getDisabledActions(action); 599 - } 600 - 601 - $.ajax(new XWiki.Document('Service', 'NestedPagesMigration').getURL('get', 'outputSyntax=plain'), { 602 - 'data': { 603 - 'action' : 'executePlan', 604 - 'addRedirection' : self.configuration.addRedirection(), 605 - 'disabledActions' : disabledActions 606 - }, 607 - 'method': 'POST', 608 - 'data-type': 'json' 609 - }).done(function (data) { 610 - self.jobId = data.jobId; 611 - self.logs.removeAll(); 612 - self.getJobStatusAndLogs('executemigrationplan', function() { self.success(true); }); 613 - }).fail(function () { 614 - console.log('ERROR: Failed to execute the plan.'); 615 - }); 616 - } 617 - 618 - /** 619 - * Clean the plan to free the memory on the server. 620 - */ 621 - self.cleanPlan = function() { 622 - $.ajax(new XWiki.Document('Service', 'NestedPagesMigration').getURL('get', 'outputSyntax=plain'), { 623 - 'data': { 624 - 'action': 'cleanPlan'}, 625 - 'method': 'POST' 626 - }).done(function() { 627 - self.actions.removeAll(); 628 - self.duplicates.removeAll(); 629 - self.tooLongs.removeAll(); 630 - self.isPlanRequested(false); 631 - self.logs.removeAll(); 632 - }); 633 - }; 634 - 635 - // Initialize the XClasses field. 636 - self.initXClasses(); 637 - 638 - }; 639 - 640 - // Activates knockout.js 641 - ko.applyBindings(new AppViewModel()); 642 -}); 643 - - Name
-
... ... @@ -1,1 +1,0 @@ 1 -Live view - Parse content
-
... ... @@ -1,1 +1,0 @@ 1 -No - Use this extension
-
... ... @@ -1,1 +1,0 @@ 1 -currentPage
- XWiki.JavaScriptExtension[3]
-
- Caching policy
-
... ... @@ -1,1 +1,0 @@ 1 -long - Code
-
... ... @@ -1,6 +1,0 @@ 1 -require(['jquery'], function ($) { 2 - $(document).ready(function() { 3 - $(".edit_section").remove(); 4 - }); 5 -}); 6 - - Use this extension
-
... ... @@ -1,1 +1,0 @@ 1 -currentPage
- XWiki.StyleSheetExtension[0]
-
- Caching policy
-
... ... @@ -1,1 +1,0 @@ 1 -long - Code
-
... ... @@ -1,15 +1,0 @@ 1 -#template('colorThemeInit.vm') 2 - 3 -.migration-plan .documentName { 4 - cursor: pointer; 5 -} 6 - 7 -.log { 8 - background-color: $theme.pageContentBackgroundColor; 9 -} 10 - 11 -.log-title { 12 - text-transform: uppercase; 13 - font-size: 0.9em; 14 - font-weight: bold; 15 -} - Content Type
-
... ... @@ -1,1 +1,0 @@ 1 -CSS - Name
-
... ... @@ -1,1 +1,0 @@ 1 -CSS - Parse content
-
... ... @@ -1,1 +1,0 @@ 1 -Yes - Use this extension
-
... ... @@ -1,1 +1,0 @@ 1 -currentPage
- XWiki.DocumentSheetBinding[0]
-
- Sheet
-
... ... @@ -1,0 +1,1 @@ 1 +NestedPagesMigration.Code.WebHomeSheet