0 Votes

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
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
Change comment: Install extension [org.xwiki.contrib:application-nestedpagesmigrator-ui/0.8.2]

Summary

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> -&gt; <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