From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.

// <syntaxhighlight lang="javascript">

// [[WP:PMRC#4]] round-robin history swap

// Based on [[:en:User:Andy M. Wang/pageswap.js]] by [[User:Andy M. Wang]] 1.6.1.2018.0920

// Modified by [[User:Ahecht]] -- v1.5.2

/*jshint esversion: 6 */



/**

 * Initialize variables

 */

if (typeof pagemoveDoPostMoveCleanup === 'undefined') { pagemoveDoPostMoveCleanup = true; }

var pagemoveLink = "[[:en:User:Ahecht/Scripts/pageswap|pageswap]]";



$(document).ready(function() {

mw.loader.using( 

	'mediawiki.api',

	'mediawiki.util',

 ).then( function() {

	"use strict";



/**

 * If user is able to perform swaps

 */

function checkUserPermissions() {

	var ret = {};

	ret.canSwap = true;

	var reslt = JSON.parse($.ajax({

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) { mw.notify("Swapping pages unavailable.", { title: 'Page Swap Error', type: 'error' }); return ret; },

		data: { action:'query', format:'json', meta:'userinfo', uiprop:'rights' }

	}).responseText).query.userinfo;



	// check userrights for suppressredirect and move-subpages

	var rightslist = reslt.rights;

	ret.canSwap =

			$.inArray('suppressredirect', rightslist) > -1 &&

			$.inArray('move-subpages', rightslist) > -1;

	ret.allowSwapTemplates =

			$.inArray('templateeditor', rightslist) > -1;



	return ret;

}



/**

 * Given namespace data, title, title namespace, returns expected title of page

 * Along with title without prefix

 * Precondition, title, titleNs is a subject page!

 */

function getTalkPageName(nsData, title, titleNs) {

	var ret = {};

	var prefixLength = nsData'' + titleNs]['*'].length === 0 ?

		0 : nsData'' + titleNs]['*'].length + 1;

	ret.titleWithoutPrefix = title.substring(prefixLength, title.length);

	ret.talkTitle = nsData'' + (titleNs + 1)]['*' + ':' +

		ret.titleWithoutPrefix;

	return ret;

}



/**

 * Given two (normalized) titles, find their namespaces, if they are redirects,

 * if have a talk page, whether the current user can move the pages, suggests

 * whether movesubpages should be allowed, whether talk pages need to be checked

 */

function swapValidate(titleOne, titleTwo, pagesData, nsData, uPerms) {

	var ret = {};

	ret.valid = true;

	if (titleOne === null || titleTwo === null || pagesData === null) {

		ret.valid = false;

		ret.invalidReason = "Unable to validate swap.";

		return ret;

	}



	ret.allowMoveSubpages = true;

	ret.checkTalk = true;

	var count = 0;

	for (var k in pagesData) {

		++count;

		if (k == "-1" || pagesDatak].ns < 0) {

			ret.valid = false;

			ret.invalidReason = ("Page " + pagesDatak].title + " does not exist.");

			return ret;

		}

		// enable only in ns 0..5,12,13,118,119 (Main,Talk,U,UT,WP,WT,H,HT,D,DT)

		if ((pagesDatak].ns >= 6 && pagesDatak].ns <= 9) ||

		 (pagesDatak].ns >= 10 && pagesDatak].ns <= 11 && !uPerms.allowSwapTemplates) ||

		 (pagesDatak].ns >= 14 && pagesDatak].ns <= 117) ||

		 (pagesDatak].ns >= 120)) {

			ret.valid = false;

			ret.invalidReason = ("Namespace of " + pagesDatak].title + " (" +

				pagesDatak].ns + ") not supported.\n\nLikely reasons:\n" +

				"- Names of pages in this namespace relies on other pages\n" +

				"- Namespace features heavily-transcluded pages\n" +

				"- Namespace involves subpages: swaps produce many redlinks\n" +

				"\n\nIf the move is legitimate, consider a careful manual swap.");

			return ret;

		}

		if (titleOne == pagesDatak].title) {

			ret.currTitle   = pagesDatak].title;

			ret.currNs      = pagesDatak].ns;

			ret.currTalkId  = pagesDatak].talkid; // could be undefined

			ret.currCanMove = pagesDatak].actions.move === '';

			ret.currIsRedir = pagesDatak].redirect === '';

		}

		if (titleTwo == pagesDatak].title) {

			ret.destTitle   = pagesDatak].title;

			ret.destNs      = pagesDatak].ns;

			ret.destTalkId  = pagesDatak].talkid; // could be undefined

			ret.destCanMove = pagesDatak].actions.move === '';

			ret.destIsRedir = pagesDatak].redirect === '';

		}

	}



	if (!ret.valid) return ret;

	if (!ret.currCanMove) {

		ret.valid = false;

		ret.invalidReason = ('' + ret.currTitle + " is immovable. Aborting");

		return ret;

	}

	if (!ret.destCanMove) {

		ret.valid = false;

		ret.invalidReason = ('' + ret.destTitle + " is immovable. Aborting");

		return ret;

	}

	if (ret.currNs % 2 !== ret.destNs % 2) {

		ret.valid = false;

		ret.invalidReason = "Namespaces don't match: one is a talk page.";

		return ret;

	}

	if (count !== 2) {

		ret.valid = false;

		ret.invalidReason = "Pages have the same title. Aborting.";

		return ret;

	}

	ret.currNsAllowSubpages = nsData'' + ret.currNs].subpages !== '';

	ret.destNsAllowSubpages = nsData'' + ret.destNs].subpages !== '';



	// if same namespace (subpages allowed), if one is subpage of another,

	// disallow movesubpages

	if (ret.currTitle.startsWith(ret.destTitle + '/') ||

			ret.destTitle.startsWith(ret.currTitle + '/')) {

		if (ret.currNs !== ret.destNs) {

			ret.valid = false;

			ret.invalidReason = "Strange.\n" + ret.currTitle + " in ns " +

				ret.currNs + "\n" + ret.destTitle + " in ns " + ret.destNs +

				". Disallowing.";

			return ret;

		}



		ret.allowMoveSubpages = ret.currNsAllowSubpages;

		if (!ret.allowMoveSubpages)

			ret.addlInfo = "One page is a subpage. Disallowing move-subpages";

	}



	if (ret.currNs % 2 === 1) {

		ret.checkTalk = false; // no need to check talks, already talk pages

	} else { // ret.checkTalk = true;

		var currTPData = getTalkPageName(nsData, ret.currTitle, ret.currNs);

		ret.currTitleWithoutPrefix = currTPData.titleWithoutPrefix;

		ret.currTalkName = currTPData.talkTitle;

		var destTPData = getTalkPageName(nsData, ret.destTitle, ret.destNs);

		ret.destTitleWithoutPrefix = destTPData.titleWithoutPrefix;

		ret.destTalkName = destTPData.talkTitle;

		// possible: ret.currTalkId undefined, but subject page has talk subpages

	}



	return ret;

}



/**

 * Given two talk page titles (may be undefined), retrieves their pages for comparison

 * Assumes that talk pages always have subpages enabled.

 * Assumes that pages are not identical (subject pages were already verified)

 * Assumes namespaces are okay (subject pages already checked)

 * (Currently) assumes that the malicious case of subject pages

 *   not detected as subpages and the talk pages ARE subpages

 *   (i.e. A and A/B vs. Talk:A and Talk:A/B) does not happen / does not handle

 * Returns structure indicating whether move talk should be allowed

 */

function talkValidate(checkTalk, talk1, talk2) {

	var ret = {};

	ret.allowMoveTalk = true;

	if (!checkTalk) { return ret; } // currTitle destTitle already talk pages

	if (talk1 === undefined || talk2 === undefined) {

		mw.notify("Unable to validate talk. Disallowing movetalk to be safe", { title: 'Page Swap Error', type: 'warn' });

		ret.allowMoveTalk = false;

		return ret;

	}

	ret.currTDNE = true;

	ret.destTDNE = true;

	ret.currTCanCreate = true;

	ret.destTCanCreate = true;

	var talkTitleArr = talk1, talk2];

	if (talkTitleArr.length !== 0) {

		var talkData = JSON.parse($.ajax({

			url: mw.util.wikiScript('api'), async:false,

			error: function (jsondata) { mw.notify("Unable to get info on talk pages.", { title: 'Page Swap Error', type: 'error' }); return ret; },

			data: { action:'query', format:'json', prop:'info',

				intestactions:'move|create', titles:talkTitleArr.join('|') }

		}).responseText).query.pages;

		for (var id in talkData) {

			if (talkDataid].title === talk1) {

				ret.currTDNE = talkDataid].invalid === '' || talkDataid].missing === '';

				ret.currTTitle = talkDataid].title;

				ret.currTCanMove = talkDataid].actions.move === '';

				ret.currTCanCreate = talkDataid].actions.create === '';

				ret.currTalkIsRedir = talkDataid].redirect === '';

			} else if (talkDataid].title === talk2) {

				ret.destTDNE = talkDataid].invalid === '' || talkDataid].missing === '';

				ret.destTTitle = talkDataid].title;

				ret.destTCanMove = talkDataid].actions.move === '';

				ret.destTCanCreate = talkDataid].actions.create === '';

				ret.destTalkIsRedir = talkDataid].redirect === '';

			} else {

				mw.notify("Found pageid ("+talkDataid].title+") not matching given ids ("+talk1+" and "+talk2+").", { title: 'Page Swap Error', type: 'error' }); return {};

			}

		}

	}



	ret.allowMoveTalk = (ret.currTCanCreate && ret.currTCanMove) &&

		(ret.destTCanCreate && ret.destTCanMove);

	return ret;

}



/**

 * Given existing title (not prefixed with "/"), optionally searching for talk,

 *   finds subpages (incl. those that are redirs) and whether limits are exceeded

 * As of 2016-08, uses 2 api get calls to get needed details:

 *   whether the page can be moved, whether the page is a redirect

 */

function getSubpages(nsData, title, titleNs, isTalk) {

	if ((!isTalk) && nsData'' + titleNs].subpages !== '') { return { data:[] }; }

	var titlePageData = getTalkPageName(nsData, title, titleNs);

	var subpages = JSON.parse($.ajax({

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) { return { error:"Unable to search for subpages. They may exist" }; },

		data: { action:'query', format:'json', list:'allpages',

			apnamespace:(isTalk ? (titleNs + 1) : titleNs),

			apfrom:(titlePageData.titleWithoutPrefix + '/'),

			apto:(titlePageData.titleWithoutPrefix + '0'),

			aplimit:101 }

	}).responseText).query.allpages;



	// put first 50 in first arr (need 2 queries due to api limits)

	var subpageids = [[],[]];

	for (var idx in subpages) {

		subpageidsidx < 50 ? 0 : 1].push( subpagesidx].pageid );

	}



	if (subpageids0].length === 0) { return { data:[] }; }

	if (subpageids1].length === 51) { return { error:"100+ subpages. Aborting" }; }

	var dataret = [];

	var subpageData0 = JSON.parse($.ajax({ 

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) {

			return { error:"Unable to fetch subpage data." }; },

		data: { action:'query', format:'json', prop:'info', intestactions:'move|create',

			pageids:subpageids0].join('|') }

	}).responseText).query.pages;

	for (var k0 in subpageData0) {

		dataret.push({

			title:subpageData0k0].title,

			isRedir:subpageData0k0].redirect === '',

			canMove:subpageData0k0].actions.move === ''

		});

	}



	if (subpageids1].length === 0) { return { data:dataret }; }

	var subpageData1 = JSON.parse($.ajax({ 

		url: mw.util.wikiScript('api'), async: false,

		error: function (jsondata) {

			return { error:"Unable to fetch subpage data." }; },

		data: { action:'query', format:'json', prop:'info', intestactions:'move|create',

			pageids:subpageids1].join('|') }

	}).responseText).query.pages;

	for (var k1 in subpageData1) {

		dataret.push({

			title:subpageData1k1].title,

			isRedir:subpageData1k1].redirect === '',

			canMove:subpageData1k1].actions.move === ''

		});

	}

	return { data:dataret };

}



/**

 * Prints subpage data given retrieved subpage information returned by getSubpages

 * Returns a suggestion whether movesubpages should be allowed

 */

function printSubpageInfo(basepage, currSp) {

	var ret = {};

	var currSpArr = [];

	var currSpCannotMove = [];

	var redirCount = 0;

	for (var kcs in currSp.data) {

		if (!currSp.datakcs].canMove) {

			currSpCannotMove.push(currSp.datakcs].title);

		}

		currSpArr.push((currSp.datakcs].isRedir ? "(R) " : "  ") +

			currSp.datakcs].title);

		if (currSp.datakcs].isRedir)

			redirCount++;

	}



	if (currSpArr.length > 0) {

		alert((currSpCannotMove.length > 0 ?

			"Disabling move-subpages.\n" +

				"The following " + currSpCannotMove.length + " (of " +

				currSpArr.length + ") total subpages of " +

				basepage + " CANNOT be moved:\n\n  " +

				currSpCannotMove.join("\n  ") + '\n\n'

			: (currSpArr.length + " total subpages of " + basepage + ".\n" +

				(redirCount !== 0 ? ('' + redirCount + " redirects, labeled (R)\n") : '') +

				'\n' + currSpArr.join('\n'))));

	}



	ret.allowMoveSubpages = currSpCannotMove.length === 0;

	ret.noNeed = currSpArr.length === 0;

	ret.spArr = currSpArr;

	return ret;

}



function doDoneMsg(doneMsg, vData) {

	if (/failed/ig.test(doneMsg)) { 

		mw.notify(doneMsg, { tag: 'status', title: 'Page Swap Status', type: 'warn' });

	} else { 

		mw.notify(doneMsg, { tag: 'status', title: 'Page Swap Status', type: 'success' });

	}

	

	var spString = "";

	

	if (vData.allSpArr.length) {

		spString = "\nThe following subpages were moved, and may need new or updated redirects:\n  " + 

			vData.allSpArr.join("\n  ") + "\n";

	}

	

	setTimeout(() => {

		if(confirm(doneMsg +

			"\nPlease create new red-linked talk pages/subpages if there are " +

			"incoming links (check your contribs for \"Talk:\" redlinks), " +

			"correct any moved redirects, and do post-move cleanup if necessary.\n" +

			spString + "\nOpen contribs page?")) {

				window.open(mw.util.getUrl("Special:Contributions")+'/'+mw.util.wikiUrlencode(mw.user.getName()));

				location.reload();

		} else {

			location.reload();

		}

	}, 250);

}



function createMissingTalk(movedTalk, movedSubpages, vData, vTData, doneMsg) {

	if (movedTalk) {

		var fromTalk, toTalk;

		if (vTData.currTDNE && !vTData.destTDNE) {

			fromTalk = vData.destTalkName;

			toTalk = vData.currTalkName;

		} else if (vTData.destTDNE && !vTData.currTDNE) {

			fromTalk = vData.currTalkName;

			toTalk = vData.destTalkName;

		}

		

		if (fromTalk && toTalk) {

			mw.notify("Talk page moved...", { tag: 'status', title: 'Page Swap Status' });

			setTimeout(() => {

				if (confirm(doneMsg + "\nCreate redirect " + fromTalk +

						"\n→ " + toTalk + " if possible?")) {

					var talkRedirect = {

						action:'edit',

						title:fromTalk,

						createonly: true,

						text: "#REDIRECT [[" + toTalk + "]]\n{{R from move}}",

						summary: "Create redirect to [[" + toTalk + "]] using " + pagemoveLink,

						watchlist:"unwatch"

					};

					mw.notify("Creating talk page redirect...", { tag: 'status', title: 'Page Swap Status' });

					new mw.Api().postWithToken("csrf", talkRedirect).done(function (resltc) {

						doDoneMsg(doneMsg + "Redirect " + fromTalk +

							"\n→ " +toTalk + " created.\n", vData);

					}).fail(function (resltc) {

						doDoneMsg(doneMsg + "Failed to create redirect: " + resltc + ".\n", vData);

					});

				} else { doDoneMsg("", vData); }

			}, 250);

		} else { doDoneMsg(doneMsg, vData); }

	} else { doDoneMsg(doneMsg, vData); }

}



/**

 * After successful page swap, post-move cleanup:

 * Make talk page redirect

 * TODO more reasonable cleanup/reporting as necessary

 * vData.(curr|dest)IsRedir

 */

/** TO DO:

 *Check if talk is self redirect

 */

function doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData, doneMsg, current = "currTitle", destination = "destTitle") {

	if (typeof doneMsg === 'undefined') {

		doneMsg = "Moves completed successfully.\n";

		mw.notify(doneMsg, { tag: 'status', title: 'Page Swap Status', type: 'success' });

	}

	

	// Check for self redirect

	var rData = JSON.parse($.ajax({

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) { mw.notify("Unable to get info about " + vDatacurrent + ".\n", { title: 'Page Swap Error', type: 'error' }); },

		data: { action:'query', format:'json', redirects:'true', titles: vDatacurrent }

	}).responseText).query;

		

	if (rData && rData.redirects && rData.redirects0].from == rData.redirects0].to){

		var parseData = JSON.parse($.ajax({

			url: mw.util.wikiScript('api'), async:false,

			error: function (jsondata) { mw.notify("Unable to fetch contents of " + vDatacurrent + ".\n", { title: 'Page Swap Error', type: 'error' });	},

			data: {action:'parse', format:'json', prop:'wikitext', page: vDatacurrent }

		}).responseText).parse;

		

		if (parseData) {

			var newWikitext = parseData.wikitext'*'].replace(/^\s*#REDIRECT +\[\[ *.* *\]\]/i, ('#REDIRECT [[' + vDatadestination + ']]'));

			if (newWikitext != parseData.wikitext'*']) {

				mw.notify("Retargeting redirect at " + vDatacurrent + " to "	+ vDatadestination + "...", { tag: 'status', title: 'Page Swap Status' });

				new mw.Api().postWithToken("csrf", {

					action:'edit',

					title: vDatacurrent],

					text: newWikitext,

					summary : "Retarget redirect to [[" +

						vDatadestination + "]] using " +

						pagemoveLink,

					watchlist: "unwatch"

				} ).done(function (resltc) {

					doneMsg = doneMsg + "Redirect at " +

						vDatacurrent + " retargeted to " +

						vDatadestination + ".\n";

					if (current == "currTitle") {

						doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData, doneMsg, "currTalkName", "destTalkName");

					} else {

						createMissingTalk(movedTalk, movedSubpages, vData, vTData, doneMsg);

					}

				} ).fail(function (resltc) {

					doneMsg = doneMsg + "Failed to retarget redirect at " +

						vDatacurrent + " to " +

						vDatadestination + ": " + resltc + ".\n";

					if (current == "currTitle") {

						doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData, doneMsg, "currTalkName", "destTalkName");

					} else {

						createMissingTalk(movedTalk, movedSubpages, vData, vTData, doneMsg);

					}

				} );

				

				return;

			} else {

				doneMsg = doneMsg + "Failed to retarget redirect at " +

					vDatacurrent + " to " + vDatadestination +

					": String not found.\n";

			}

		} else {

			doneMsg = doneMsg + "Failed to check contents of" +

				vDatacurrent + ": " + err + ".\n";

		}

	}

	

	if (current == "currTitle") {

		doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData, doneMsg, "currTalkName", "destTalkName");

	} else {

		createMissingTalk(movedTalk, movedSubpages, vData, vTData, doneMsg);

	}

}





/**

 * Swaps the two pages (given all prerequisite checks)

 * Optionally moves talk pages and subpages

 */

function swapPages(titleOne, titleTwo, moveReason, intermediateTitlePrefix,

		moveTalk, moveSubpages, vData, vTData) {

	if (titleOne === null || titleTwo === null ||

			moveReason === null || moveReason === '') {

		mw.notify("Titles are null, or move reason given was empty. Swap not done", { title: 'Page Swap Error', type: 'error' });

		return false;

	}



	var intermediateTitle = intermediateTitlePrefix + titleOne;

	var pOne = { action:'move', from:titleTwo, to:intermediateTitle,

		reason:"[[WP:PMRC#4|Round-robin history swap]] step 1 using " + pagemoveLink,

		watchlist:"unwatch", noredirect:1 };

	var pTwo = { action:'move', from:titleOne, to:titleTwo,

		reason:moveReason,

		watchlist:"unwatch", noredirect:1 };

	var pTre = { action:'move', from:intermediateTitle, to:titleOne,

		reason:"[[WP:PMRC#4|Round-robin history swap]] step 3 using " + pagemoveLink,

		watchlist:"unwatch", noredirect:1 };

	if (moveTalk) {

		pOne.movetalk = 1; pTwo.movetalk = 1; pTre.movetalk = 1;

	}

	if (moveSubpages) {

		pOne.movesubpages = 1; pTwo.movesubpages = 1; pTre.movesubpages = 1;

	}

	

	mw.notify("Doing round-robin history swap step 1...", { tag: 'status', title: 'Page Swap Status' });

	new mw.Api().postWithToken("csrf", pOne).done(function (reslt1) {

		mw.notify("Doing round-robin history swap step 2...", { tag: 'status', title: 'Page Swap Status' });

		new mw.Api().postWithToken("csrf", pTwo).done(function (reslt2) {

			mw.notify("Doing round-robin history swap step 3...", { tag: 'status', title: 'Page Swap Status' });

			new mw.Api().postWithToken("csrf", pTre).done(function (reslt3) {

				if (pagemoveDoPostMoveCleanup) {

					doPostMoveCleanup(moveTalk, moveSubpages, vData, vTData);

				} else {

					doDoneMsg("Moves completed successfully.\n", vData);

				}

			}).fail(function (reslt3) {

				doDoneMsg("Fail on third move " + intermediateTitle + " → " + titleOne + "\n", vData);

			});

		}).fail(function (reslt2) {

			doDoneMsg("Fail on second move " + titleOne + " → " + titleTwo + "\n", vData);

		});

	}).fail(function (reslt1) {

		doDoneMsg("Fail on first move " + titleTwo + " → " + intermediateTitle + "\n", vData);

	});

}



/**

 * Given two titles, normalizes, does prerequisite checks for talk/subpages,

 * prompts user for config before swapping the titles

 */

function roundrobin(uPerms, currNs, currTitle, destTitle, intermediateTitlePrefix) {

	// get ns info (nsData.query.namespaces)

	var nsData = JSON.parse($.ajax({ 

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) { mw.notify("Unable to get info about namespaces", { title: 'Page Swap Error', type: 'error' }); },

		data: { action:'query', format:'json', meta:'siteinfo', siprop:'namespaces' }

	}).responseText).query.namespaces;



	// get page data, normalize titles

	var relevantTitles = currTitle + "|" + destTitle;

	var pagesData = JSON.parse($.ajax({ 

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) {

			mw.notify("Unable to get info about " + currTitle + " or " + destTitle, { title: 'Page Swap Error', type: 'error' });

		},

		data: { action:'query', format:'json', prop:'info', inprop:'talkid',

			intestactions:'move|create', titles:relevantTitles }

	}).responseText).query;



	for (var kp in pagesData.normalized) {

		if (currTitle == pagesData.normalizedkp].from) { currTitle = pagesData.normalizedkp].to; }

		if (destTitle == pagesData.normalizedkp].from) { destTitle = pagesData.normalizedkp].to; }

	}

	// validate namespaces, not identical, can move

	var vData = swapValidate(currTitle, destTitle, pagesData.pages, nsData, uPerms);

	if (!vData.valid) { mw.notify(vData.invalidReason, { title: 'Page Swap Error', type: 'error' }); return; }

	if (vData.addlInfo !== undefined) { mw.notify(vData.addlInfo, { title: 'Page Swap Error', type: 'error' }); }



	// subj subpages

	var currSp = getSubpages(nsData, vData.currTitle, vData.currNs, false);

	if (currSp.error !== undefined) { mw.notify(currSp.error, { title: 'Page Swap Error', type: 'error' }); return; }

	var currSpFlags = printSubpageInfo(vData.currTitle, currSp);

	var destSp = getSubpages(nsData, vData.destTitle, vData.destNs, false);

	if (destSp.error !== undefined) { mw.notify(destSp.error, { title: 'Page Swap Error', type: 'error' }); return; }

	var destSpFlags = printSubpageInfo(vData.destTitle, destSp);



	var vTData = talkValidate(vData.checkTalk, vData.currTalkName, vData.destTalkName);



	// future goal: check empty subpage DESTINATIONS on both sides (subj, talk)

	//   for create protection. disallow move-subpages if any destination is salted

	var currTSp = getSubpages(nsData, vData.currTitle, vData.currNs, true);

	if (currTSp.error !== undefined) { mw.notify(currTSp.error, { title: 'Page Swap Error', type: 'error' }); return; }

	var currTSpFlags = printSubpageInfo(vData.currTalkName, currTSp);

	var destTSp = getSubpages(nsData, vData.destTitle, vData.destNs, true);

	if (destTSp.error !== undefined) { mw.notify(destTSp.error, { title: 'Page Swap Error', type: 'error' }); return; }

	var destTSpFlags = printSubpageInfo(vData.destTalkName, destTSp);



	var noSubpages = currSpFlags.noNeed && destSpFlags.noNeed &&

		currTSpFlags.noNeed && destTSpFlags.noNeed;

	// If one ns disables subpages, other enables subpages, AND HAS subpages,

	//   consider abort. Assume talk pages always safe (TODO fix)

	var subpageCollision = (vData.currNsAllowSubpages && !destSpFlags.noNeed) ||

		(vData.destNsAllowSubpages && !currSpFlags.noNeed);



	var moveSubpages = false;

	// TODO future: currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages

	// needs to be separate check. If talk subpages immovable, should not affect subjspace

	if (!subpageCollision && !noSubpages && vData.allowMoveSubpages &&

			(currSpFlags.allowMoveSubpages && destSpFlags.allowMoveSubpages) &&

			(currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages)) {

		moveSubpages = confirm("Move subpages? (OK for yes, Cancel for no)");

	} else if (subpageCollision) {

		mw.notify("One namespace does not have subpages enabled. Disallowing move subpages", { title: 'Page Swap Error', type: 'error' });

	}

	

	if (moveSubpages) { 

		vData.allSpArr = currSpFlags.spArr.concat(destSpFlags.spArr, currTSpFlags.spArr, destTSpFlags.spArr);

	} else {

		vData.allSpArr = [];

	}



	var moveTalk = false;

	// TODO: count subpages and make restrictions?

	if (vData.checkTalk && (!vTData.currTDNE || !vTData.destTDNE || moveSubpages)) {

		if (vTData.allowMoveTalk) {

			moveTalk = confirm("Move talk page(s)? (OK for yes, Cancel for no)");

		} else {

			alert("Disallowing moving talk. " +

				(!vTData.currTCanCreate ? (vData.currTalkName + " is create-protected")

				: (!vTData.destTCanCreate ? (vData.destTalkName + " is create-protected")

				: "Talk page is immovable")));

		}

	}

	

	var moveReason = '';

	var moveReasonPrompt = '';

	if (typeof mw.util.getParamValue("wpReason") === 'string') {

		moveReasonPrompt = mw.util.getParamValue("wpReason");

	} else if (document.getElementsByName("wpReason")[0 && document.getElementsByName("wpReason")[0].value != '') {

		moveReasonPrompt = document.getElementsByName("wpReason")[0].value;

	} else if (typeof moveReasonDefault === 'string') {

		moveReasonPrompt = moveReasonDefault;

	}

	moveReason = prompt("Move reason:", moveReasonPrompt);

	

	var spToMoveString = "";

	

	if (moveSubpages) {

		spToMoveString = "Subpages to move:\n  "+ vData.allSpArr.join("\n  ") + "\n\n";

	}

	

	var confirmString = "Round-robin configuration:\n  " +

		currTitle + " → " + destTitle + "\n    : " + moveReason +

		"\n      with movetalk:" + moveTalk + ", movesubpages:" + moveSubpages +

		"\n\n" + spToMoveString +

		"Proceed? (Cancel to abort)";



	if (confirm(confirmString)) {

		swapPages(currTitle, destTitle, moveReason, intermediateTitlePrefix,

			moveTalk, moveSubpages, vData, vTData);

	}

}



	var currNs = mw.config.get("wgNamespaceNumber");

	var wpOldTitle = mw.util.getParamValue("wpOldTitle");

	if (!wpOldTitle && document.getElementsByName("wpOldTitle")[0 && document.getElementsByName("wpOldTitle")[0].value != ''){

		wpOldTitle = document.getElementsByName("wpOldTitle")[0].value;

	}

	var wpNewTitle = mw.util.getParamValue("wpNewTitle");

	if (!wpNewTitle && document.getElementsByName("wpNewTitleMain")[0 && document.getElementsByName("wpNewTitleMain")[0].value != '' && document.getElementsByName("wpNewTitleNs")[0]){

		wpNewTitle = document.getElementsByName("wpNewTitleMain")[0].value;

		var nsid = document.getElementsByName("wpNewTitleNs")[0].value;

		if (nsid != 0) {

			wpNewTitle = mw.config.get("wgFormattedNamespaces")[nsid + ":" + wpNewTitle;

		}

	}

	if (currNs < -1 || currNs >= 120 ||

			(currNs >=  6 && currNs <= 9) ||

			(currNs >= 14 && currNs <= 99) ||

			(currNs == -1 && mw.config.get("wgCanonicalSpecialPageName") != "Movepage") ||

			(mw.config.get("wgCanonicalSpecialPageName") == "Movepage" && !wpOldTitle)

			)

		return; // special/other page



	var portletLink = mw.util.addPortletLink("p-cactions", "#", "Swap",

		"ca-swappages", "Perform a revision history swap / round-robin move");

	$( portletLink ).click(function(e) {

		e.preventDefault();

		var userPermissions = checkUserPermissions();

		if (!userPermissions.canSwap) {

			mw.notify("User rights insufficient for action.", { title: 'Page Swap Error', type: 'error' }); return;

		}

		

		var currTitle = wpOldTitle || mw.config.get("wgPageName");

		var destTitle = wpNewTitle || prompt("Swap \"" + (currTitle.replace(/_/g, ' ')) + "\" with:", (currTitle.replace(/_/g, ' ')));

		

		return roundrobin(userPermissions, currNs, currTitle, destTitle, "Draft:Move/");

	});

	

	if (mw.config.get("wgCanonicalSpecialPageName") == "Movepage" &&

			$( "div.mw-message-box-error" ).find( "p" ).eq(1).is( ":contains('name already exists')" ) &&

			wpOldTitle)

	{

		$( "div.mw-message-box-error" ).find( "p" ).eq(2).html( 'Please choose another name, or perform a <a title="Perform a revision history swap / round-robin move" href="#" id="pageswapLink">swap</a>.' );

		$( "#pageswapLink" ).click(function(e) {

			e.preventDefault();

			$( portletLink ).click();

		});

	}



});

});

// </syntaxhighlight>
From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.

// <syntaxhighlight lang="javascript">

// [[WP:PMRC#4]] round-robin history swap

// Based on [[:en:User:Andy M. Wang/pageswap.js]] by [[User:Andy M. Wang]] 1.6.1.2018.0920

// Modified by [[User:Ahecht]] -- v1.5.2

/*jshint esversion: 6 */



/**

 * Initialize variables

 */

if (typeof pagemoveDoPostMoveCleanup === 'undefined') { pagemoveDoPostMoveCleanup = true; }

var pagemoveLink = "[[:en:User:Ahecht/Scripts/pageswap|pageswap]]";



$(document).ready(function() {

mw.loader.using( 

	'mediawiki.api',

	'mediawiki.util',

 ).then( function() {

	"use strict";



/**

 * If user is able to perform swaps

 */

function checkUserPermissions() {

	var ret = {};

	ret.canSwap = true;

	var reslt = JSON.parse($.ajax({

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) { mw.notify("Swapping pages unavailable.", { title: 'Page Swap Error', type: 'error' }); return ret; },

		data: { action:'query', format:'json', meta:'userinfo', uiprop:'rights' }

	}).responseText).query.userinfo;



	// check userrights for suppressredirect and move-subpages

	var rightslist = reslt.rights;

	ret.canSwap =

			$.inArray('suppressredirect', rightslist) > -1 &&

			$.inArray('move-subpages', rightslist) > -1;

	ret.allowSwapTemplates =

			$.inArray('templateeditor', rightslist) > -1;



	return ret;

}



/**

 * Given namespace data, title, title namespace, returns expected title of page

 * Along with title without prefix

 * Precondition, title, titleNs is a subject page!

 */

function getTalkPageName(nsData, title, titleNs) {

	var ret = {};

	var prefixLength = nsData'' + titleNs]['*'].length === 0 ?

		0 : nsData'' + titleNs]['*'].length + 1;

	ret.titleWithoutPrefix = title.substring(prefixLength, title.length);

	ret.talkTitle = nsData'' + (titleNs + 1)]['*' + ':' +

		ret.titleWithoutPrefix;

	return ret;

}



/**

 * Given two (normalized) titles, find their namespaces, if they are redirects,

 * if have a talk page, whether the current user can move the pages, suggests

 * whether movesubpages should be allowed, whether talk pages need to be checked

 */

function swapValidate(titleOne, titleTwo, pagesData, nsData, uPerms) {

	var ret = {};

	ret.valid = true;

	if (titleOne === null || titleTwo === null || pagesData === null) {

		ret.valid = false;

		ret.invalidReason = "Unable to validate swap.";

		return ret;

	}



	ret.allowMoveSubpages = true;

	ret.checkTalk = true;

	var count = 0;

	for (var k in pagesData) {

		++count;

		if (k == "-1" || pagesDatak].ns < 0) {

			ret.valid = false;

			ret.invalidReason = ("Page " + pagesDatak].title + " does not exist.");

			return ret;

		}

		// enable only in ns 0..5,12,13,118,119 (Main,Talk,U,UT,WP,WT,H,HT,D,DT)

		if ((pagesDatak].ns >= 6 && pagesDatak].ns <= 9) ||

		 (pagesDatak].ns >= 10 && pagesDatak].ns <= 11 && !uPerms.allowSwapTemplates) ||

		 (pagesDatak].ns >= 14 && pagesDatak].ns <= 117) ||

		 (pagesDatak].ns >= 120)) {

			ret.valid = false;

			ret.invalidReason = ("Namespace of " + pagesDatak].title + " (" +

				pagesDatak].ns + ") not supported.\n\nLikely reasons:\n" +

				"- Names of pages in this namespace relies on other pages\n" +

				"- Namespace features heavily-transcluded pages\n" +

				"- Namespace involves subpages: swaps produce many redlinks\n" +

				"\n\nIf the move is legitimate, consider a careful manual swap.");

			return ret;

		}

		if (titleOne == pagesDatak].title) {

			ret.currTitle   = pagesDatak].title;

			ret.currNs      = pagesDatak].ns;

			ret.currTalkId  = pagesDatak].talkid; // could be undefined

			ret.currCanMove = pagesDatak].actions.move === '';

			ret.currIsRedir = pagesDatak].redirect === '';

		}

		if (titleTwo == pagesDatak].title) {

			ret.destTitle   = pagesDatak].title;

			ret.destNs      = pagesDatak].ns;

			ret.destTalkId  = pagesDatak].talkid; // could be undefined

			ret.destCanMove = pagesDatak].actions.move === '';

			ret.destIsRedir = pagesDatak].redirect === '';

		}

	}



	if (!ret.valid) return ret;

	if (!ret.currCanMove) {

		ret.valid = false;

		ret.invalidReason = ('' + ret.currTitle + " is immovable. Aborting");

		return ret;

	}

	if (!ret.destCanMove) {

		ret.valid = false;

		ret.invalidReason = ('' + ret.destTitle + " is immovable. Aborting");

		return ret;

	}

	if (ret.currNs % 2 !== ret.destNs % 2) {

		ret.valid = false;

		ret.invalidReason = "Namespaces don't match: one is a talk page.";

		return ret;

	}

	if (count !== 2) {

		ret.valid = false;

		ret.invalidReason = "Pages have the same title. Aborting.";

		return ret;

	}

	ret.currNsAllowSubpages = nsData'' + ret.currNs].subpages !== '';

	ret.destNsAllowSubpages = nsData'' + ret.destNs].subpages !== '';



	// if same namespace (subpages allowed), if one is subpage of another,

	// disallow movesubpages

	if (ret.currTitle.startsWith(ret.destTitle + '/') ||

			ret.destTitle.startsWith(ret.currTitle + '/')) {

		if (ret.currNs !== ret.destNs) {

			ret.valid = false;

			ret.invalidReason = "Strange.\n" + ret.currTitle + " in ns " +

				ret.currNs + "\n" + ret.destTitle + " in ns " + ret.destNs +

				". Disallowing.";

			return ret;

		}



		ret.allowMoveSubpages = ret.currNsAllowSubpages;

		if (!ret.allowMoveSubpages)

			ret.addlInfo = "One page is a subpage. Disallowing move-subpages";

	}



	if (ret.currNs % 2 === 1) {

		ret.checkTalk = false; // no need to check talks, already talk pages

	} else { // ret.checkTalk = true;

		var currTPData = getTalkPageName(nsData, ret.currTitle, ret.currNs);

		ret.currTitleWithoutPrefix = currTPData.titleWithoutPrefix;

		ret.currTalkName = currTPData.talkTitle;

		var destTPData = getTalkPageName(nsData, ret.destTitle, ret.destNs);

		ret.destTitleWithoutPrefix = destTPData.titleWithoutPrefix;

		ret.destTalkName = destTPData.talkTitle;

		// possible: ret.currTalkId undefined, but subject page has talk subpages

	}



	return ret;

}



/**

 * Given two talk page titles (may be undefined), retrieves their pages for comparison

 * Assumes that talk pages always have subpages enabled.

 * Assumes that pages are not identical (subject pages were already verified)

 * Assumes namespaces are okay (subject pages already checked)

 * (Currently) assumes that the malicious case of subject pages

 *   not detected as subpages and the talk pages ARE subpages

 *   (i.e. A and A/B vs. Talk:A and Talk:A/B) does not happen / does not handle

 * Returns structure indicating whether move talk should be allowed

 */

function talkValidate(checkTalk, talk1, talk2) {

	var ret = {};

	ret.allowMoveTalk = true;

	if (!checkTalk) { return ret; } // currTitle destTitle already talk pages

	if (talk1 === undefined || talk2 === undefined) {

		mw.notify("Unable to validate talk. Disallowing movetalk to be safe", { title: 'Page Swap Error', type: 'warn' });

		ret.allowMoveTalk = false;

		return ret;

	}

	ret.currTDNE = true;

	ret.destTDNE = true;

	ret.currTCanCreate = true;

	ret.destTCanCreate = true;

	var talkTitleArr = talk1, talk2];

	if (talkTitleArr.length !== 0) {

		var talkData = JSON.parse($.ajax({

			url: mw.util.wikiScript('api'), async:false,

			error: function (jsondata) { mw.notify("Unable to get info on talk pages.", { title: 'Page Swap Error', type: 'error' }); return ret; },

			data: { action:'query', format:'json', prop:'info',

				intestactions:'move|create', titles:talkTitleArr.join('|') }

		}).responseText).query.pages;

		for (var id in talkData) {

			if (talkDataid].title === talk1) {

				ret.currTDNE = talkDataid].invalid === '' || talkDataid].missing === '';

				ret.currTTitle = talkDataid].title;

				ret.currTCanMove = talkDataid].actions.move === '';

				ret.currTCanCreate = talkDataid].actions.create === '';

				ret.currTalkIsRedir = talkDataid].redirect === '';

			} else if (talkDataid].title === talk2) {

				ret.destTDNE = talkDataid].invalid === '' || talkDataid].missing === '';

				ret.destTTitle = talkDataid].title;

				ret.destTCanMove = talkDataid].actions.move === '';

				ret.destTCanCreate = talkDataid].actions.create === '';

				ret.destTalkIsRedir = talkDataid].redirect === '';

			} else {

				mw.notify("Found pageid ("+talkDataid].title+") not matching given ids ("+talk1+" and "+talk2+").", { title: 'Page Swap Error', type: 'error' }); return {};

			}

		}

	}



	ret.allowMoveTalk = (ret.currTCanCreate && ret.currTCanMove) &&

		(ret.destTCanCreate && ret.destTCanMove);

	return ret;

}



/**

 * Given existing title (not prefixed with "/"), optionally searching for talk,

 *   finds subpages (incl. those that are redirs) and whether limits are exceeded

 * As of 2016-08, uses 2 api get calls to get needed details:

 *   whether the page can be moved, whether the page is a redirect

 */

function getSubpages(nsData, title, titleNs, isTalk) {

	if ((!isTalk) && nsData'' + titleNs].subpages !== '') { return { data:[] }; }

	var titlePageData = getTalkPageName(nsData, title, titleNs);

	var subpages = JSON.parse($.ajax({

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) { return { error:"Unable to search for subpages. They may exist" }; },

		data: { action:'query', format:'json', list:'allpages',

			apnamespace:(isTalk ? (titleNs + 1) : titleNs),

			apfrom:(titlePageData.titleWithoutPrefix + '/'),

			apto:(titlePageData.titleWithoutPrefix + '0'),

			aplimit:101 }

	}).responseText).query.allpages;



	// put first 50 in first arr (need 2 queries due to api limits)

	var subpageids = [[],[]];

	for (var idx in subpages) {

		subpageidsidx < 50 ? 0 : 1].push( subpagesidx].pageid );

	}



	if (subpageids0].length === 0) { return { data:[] }; }

	if (subpageids1].length === 51) { return { error:"100+ subpages. Aborting" }; }

	var dataret = [];

	var subpageData0 = JSON.parse($.ajax({ 

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) {

			return { error:"Unable to fetch subpage data." }; },

		data: { action:'query', format:'json', prop:'info', intestactions:'move|create',

			pageids:subpageids0].join('|') }

	}).responseText).query.pages;

	for (var k0 in subpageData0) {

		dataret.push({

			title:subpageData0k0].title,

			isRedir:subpageData0k0].redirect === '',

			canMove:subpageData0k0].actions.move === ''

		});

	}



	if (subpageids1].length === 0) { return { data:dataret }; }

	var subpageData1 = JSON.parse($.ajax({ 

		url: mw.util.wikiScript('api'), async: false,

		error: function (jsondata) {

			return { error:"Unable to fetch subpage data." }; },

		data: { action:'query', format:'json', prop:'info', intestactions:'move|create',

			pageids:subpageids1].join('|') }

	}).responseText).query.pages;

	for (var k1 in subpageData1) {

		dataret.push({

			title:subpageData1k1].title,

			isRedir:subpageData1k1].redirect === '',

			canMove:subpageData1k1].actions.move === ''

		});

	}

	return { data:dataret };

}



/**

 * Prints subpage data given retrieved subpage information returned by getSubpages

 * Returns a suggestion whether movesubpages should be allowed

 */

function printSubpageInfo(basepage, currSp) {

	var ret = {};

	var currSpArr = [];

	var currSpCannotMove = [];

	var redirCount = 0;

	for (var kcs in currSp.data) {

		if (!currSp.datakcs].canMove) {

			currSpCannotMove.push(currSp.datakcs].title);

		}

		currSpArr.push((currSp.datakcs].isRedir ? "(R) " : "  ") +

			currSp.datakcs].title);

		if (currSp.datakcs].isRedir)

			redirCount++;

	}



	if (currSpArr.length > 0) {

		alert((currSpCannotMove.length > 0 ?

			"Disabling move-subpages.\n" +

				"The following " + currSpCannotMove.length + " (of " +

				currSpArr.length + ") total subpages of " +

				basepage + " CANNOT be moved:\n\n  " +

				currSpCannotMove.join("\n  ") + '\n\n'

			: (currSpArr.length + " total subpages of " + basepage + ".\n" +

				(redirCount !== 0 ? ('' + redirCount + " redirects, labeled (R)\n") : '') +

				'\n' + currSpArr.join('\n'))));

	}



	ret.allowMoveSubpages = currSpCannotMove.length === 0;

	ret.noNeed = currSpArr.length === 0;

	ret.spArr = currSpArr;

	return ret;

}



function doDoneMsg(doneMsg, vData) {

	if (/failed/ig.test(doneMsg)) { 

		mw.notify(doneMsg, { tag: 'status', title: 'Page Swap Status', type: 'warn' });

	} else { 

		mw.notify(doneMsg, { tag: 'status', title: 'Page Swap Status', type: 'success' });

	}

	

	var spString = "";

	

	if (vData.allSpArr.length) {

		spString = "\nThe following subpages were moved, and may need new or updated redirects:\n  " + 

			vData.allSpArr.join("\n  ") + "\n";

	}

	

	setTimeout(() => {

		if(confirm(doneMsg +

			"\nPlease create new red-linked talk pages/subpages if there are " +

			"incoming links (check your contribs for \"Talk:\" redlinks), " +

			"correct any moved redirects, and do post-move cleanup if necessary.\n" +

			spString + "\nOpen contribs page?")) {

				window.open(mw.util.getUrl("Special:Contributions")+'/'+mw.util.wikiUrlencode(mw.user.getName()));

				location.reload();

		} else {

			location.reload();

		}

	}, 250);

}



function createMissingTalk(movedTalk, movedSubpages, vData, vTData, doneMsg) {

	if (movedTalk) {

		var fromTalk, toTalk;

		if (vTData.currTDNE && !vTData.destTDNE) {

			fromTalk = vData.destTalkName;

			toTalk = vData.currTalkName;

		} else if (vTData.destTDNE && !vTData.currTDNE) {

			fromTalk = vData.currTalkName;

			toTalk = vData.destTalkName;

		}

		

		if (fromTalk && toTalk) {

			mw.notify("Talk page moved...", { tag: 'status', title: 'Page Swap Status' });

			setTimeout(() => {

				if (confirm(doneMsg + "\nCreate redirect " + fromTalk +

						"\n→ " + toTalk + " if possible?")) {

					var talkRedirect = {

						action:'edit',

						title:fromTalk,

						createonly: true,

						text: "#REDIRECT [[" + toTalk + "]]\n{{R from move}}",

						summary: "Create redirect to [[" + toTalk + "]] using " + pagemoveLink,

						watchlist:"unwatch"

					};

					mw.notify("Creating talk page redirect...", { tag: 'status', title: 'Page Swap Status' });

					new mw.Api().postWithToken("csrf", talkRedirect).done(function (resltc) {

						doDoneMsg(doneMsg + "Redirect " + fromTalk +

							"\n→ " +toTalk + " created.\n", vData);

					}).fail(function (resltc) {

						doDoneMsg(doneMsg + "Failed to create redirect: " + resltc + ".\n", vData);

					});

				} else { doDoneMsg("", vData); }

			}, 250);

		} else { doDoneMsg(doneMsg, vData); }

	} else { doDoneMsg(doneMsg, vData); }

}



/**

 * After successful page swap, post-move cleanup:

 * Make talk page redirect

 * TODO more reasonable cleanup/reporting as necessary

 * vData.(curr|dest)IsRedir

 */

/** TO DO:

 *Check if talk is self redirect

 */

function doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData, doneMsg, current = "currTitle", destination = "destTitle") {

	if (typeof doneMsg === 'undefined') {

		doneMsg = "Moves completed successfully.\n";

		mw.notify(doneMsg, { tag: 'status', title: 'Page Swap Status', type: 'success' });

	}

	

	// Check for self redirect

	var rData = JSON.parse($.ajax({

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) { mw.notify("Unable to get info about " + vDatacurrent + ".\n", { title: 'Page Swap Error', type: 'error' }); },

		data: { action:'query', format:'json', redirects:'true', titles: vDatacurrent }

	}).responseText).query;

		

	if (rData && rData.redirects && rData.redirects0].from == rData.redirects0].to){

		var parseData = JSON.parse($.ajax({

			url: mw.util.wikiScript('api'), async:false,

			error: function (jsondata) { mw.notify("Unable to fetch contents of " + vDatacurrent + ".\n", { title: 'Page Swap Error', type: 'error' });	},

			data: {action:'parse', format:'json', prop:'wikitext', page: vDatacurrent }

		}).responseText).parse;

		

		if (parseData) {

			var newWikitext = parseData.wikitext'*'].replace(/^\s*#REDIRECT +\[\[ *.* *\]\]/i, ('#REDIRECT [[' + vDatadestination + ']]'));

			if (newWikitext != parseData.wikitext'*']) {

				mw.notify("Retargeting redirect at " + vDatacurrent + " to "	+ vDatadestination + "...", { tag: 'status', title: 'Page Swap Status' });

				new mw.Api().postWithToken("csrf", {

					action:'edit',

					title: vDatacurrent],

					text: newWikitext,

					summary : "Retarget redirect to [[" +

						vDatadestination + "]] using " +

						pagemoveLink,

					watchlist: "unwatch"

				} ).done(function (resltc) {

					doneMsg = doneMsg + "Redirect at " +

						vDatacurrent + " retargeted to " +

						vDatadestination + ".\n";

					if (current == "currTitle") {

						doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData, doneMsg, "currTalkName", "destTalkName");

					} else {

						createMissingTalk(movedTalk, movedSubpages, vData, vTData, doneMsg);

					}

				} ).fail(function (resltc) {

					doneMsg = doneMsg + "Failed to retarget redirect at " +

						vDatacurrent + " to " +

						vDatadestination + ": " + resltc + ".\n";

					if (current == "currTitle") {

						doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData, doneMsg, "currTalkName", "destTalkName");

					} else {

						createMissingTalk(movedTalk, movedSubpages, vData, vTData, doneMsg);

					}

				} );

				

				return;

			} else {

				doneMsg = doneMsg + "Failed to retarget redirect at " +

					vDatacurrent + " to " + vDatadestination +

					": String not found.\n";

			}

		} else {

			doneMsg = doneMsg + "Failed to check contents of" +

				vDatacurrent + ": " + err + ".\n";

		}

	}

	

	if (current == "currTitle") {

		doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData, doneMsg, "currTalkName", "destTalkName");

	} else {

		createMissingTalk(movedTalk, movedSubpages, vData, vTData, doneMsg);

	}

}





/**

 * Swaps the two pages (given all prerequisite checks)

 * Optionally moves talk pages and subpages

 */

function swapPages(titleOne, titleTwo, moveReason, intermediateTitlePrefix,

		moveTalk, moveSubpages, vData, vTData) {

	if (titleOne === null || titleTwo === null ||

			moveReason === null || moveReason === '') {

		mw.notify("Titles are null, or move reason given was empty. Swap not done", { title: 'Page Swap Error', type: 'error' });

		return false;

	}



	var intermediateTitle = intermediateTitlePrefix + titleOne;

	var pOne = { action:'move', from:titleTwo, to:intermediateTitle,

		reason:"[[WP:PMRC#4|Round-robin history swap]] step 1 using " + pagemoveLink,

		watchlist:"unwatch", noredirect:1 };

	var pTwo = { action:'move', from:titleOne, to:titleTwo,

		reason:moveReason,

		watchlist:"unwatch", noredirect:1 };

	var pTre = { action:'move', from:intermediateTitle, to:titleOne,

		reason:"[[WP:PMRC#4|Round-robin history swap]] step 3 using " + pagemoveLink,

		watchlist:"unwatch", noredirect:1 };

	if (moveTalk) {

		pOne.movetalk = 1; pTwo.movetalk = 1; pTre.movetalk = 1;

	}

	if (moveSubpages) {

		pOne.movesubpages = 1; pTwo.movesubpages = 1; pTre.movesubpages = 1;

	}

	

	mw.notify("Doing round-robin history swap step 1...", { tag: 'status', title: 'Page Swap Status' });

	new mw.Api().postWithToken("csrf", pOne).done(function (reslt1) {

		mw.notify("Doing round-robin history swap step 2...", { tag: 'status', title: 'Page Swap Status' });

		new mw.Api().postWithToken("csrf", pTwo).done(function (reslt2) {

			mw.notify("Doing round-robin history swap step 3...", { tag: 'status', title: 'Page Swap Status' });

			new mw.Api().postWithToken("csrf", pTre).done(function (reslt3) {

				if (pagemoveDoPostMoveCleanup) {

					doPostMoveCleanup(moveTalk, moveSubpages, vData, vTData);

				} else {

					doDoneMsg("Moves completed successfully.\n", vData);

				}

			}).fail(function (reslt3) {

				doDoneMsg("Fail on third move " + intermediateTitle + " → " + titleOne + "\n", vData);

			});

		}).fail(function (reslt2) {

			doDoneMsg("Fail on second move " + titleOne + " → " + titleTwo + "\n", vData);

		});

	}).fail(function (reslt1) {

		doDoneMsg("Fail on first move " + titleTwo + " → " + intermediateTitle + "\n", vData);

	});

}



/**

 * Given two titles, normalizes, does prerequisite checks for talk/subpages,

 * prompts user for config before swapping the titles

 */

function roundrobin(uPerms, currNs, currTitle, destTitle, intermediateTitlePrefix) {

	// get ns info (nsData.query.namespaces)

	var nsData = JSON.parse($.ajax({ 

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) { mw.notify("Unable to get info about namespaces", { title: 'Page Swap Error', type: 'error' }); },

		data: { action:'query', format:'json', meta:'siteinfo', siprop:'namespaces' }

	}).responseText).query.namespaces;



	// get page data, normalize titles

	var relevantTitles = currTitle + "|" + destTitle;

	var pagesData = JSON.parse($.ajax({ 

		url: mw.util.wikiScript('api'), async:false,

		error: function (jsondata) {

			mw.notify("Unable to get info about " + currTitle + " or " + destTitle, { title: 'Page Swap Error', type: 'error' });

		},

		data: { action:'query', format:'json', prop:'info', inprop:'talkid',

			intestactions:'move|create', titles:relevantTitles }

	}).responseText).query;



	for (var kp in pagesData.normalized) {

		if (currTitle == pagesData.normalizedkp].from) { currTitle = pagesData.normalizedkp].to; }

		if (destTitle == pagesData.normalizedkp].from) { destTitle = pagesData.normalizedkp].to; }

	}

	// validate namespaces, not identical, can move

	var vData = swapValidate(currTitle, destTitle, pagesData.pages, nsData, uPerms);

	if (!vData.valid) { mw.notify(vData.invalidReason, { title: 'Page Swap Error', type: 'error' }); return; }

	if (vData.addlInfo !== undefined) { mw.notify(vData.addlInfo, { title: 'Page Swap Error', type: 'error' }); }



	// subj subpages

	var currSp = getSubpages(nsData, vData.currTitle, vData.currNs, false);

	if (currSp.error !== undefined) { mw.notify(currSp.error, { title: 'Page Swap Error', type: 'error' }); return; }

	var currSpFlags = printSubpageInfo(vData.currTitle, currSp);

	var destSp = getSubpages(nsData, vData.destTitle, vData.destNs, false);

	if (destSp.error !== undefined) { mw.notify(destSp.error, { title: 'Page Swap Error', type: 'error' }); return; }

	var destSpFlags = printSubpageInfo(vData.destTitle, destSp);



	var vTData = talkValidate(vData.checkTalk, vData.currTalkName, vData.destTalkName);



	// future goal: check empty subpage DESTINATIONS on both sides (subj, talk)

	//   for create protection. disallow move-subpages if any destination is salted

	var currTSp = getSubpages(nsData, vData.currTitle, vData.currNs, true);

	if (currTSp.error !== undefined) { mw.notify(currTSp.error, { title: 'Page Swap Error', type: 'error' }); return; }

	var currTSpFlags = printSubpageInfo(vData.currTalkName, currTSp);

	var destTSp = getSubpages(nsData, vData.destTitle, vData.destNs, true);

	if (destTSp.error !== undefined) { mw.notify(destTSp.error, { title: 'Page Swap Error', type: 'error' }); return; }

	var destTSpFlags = printSubpageInfo(vData.destTalkName, destTSp);



	var noSubpages = currSpFlags.noNeed && destSpFlags.noNeed &&

		currTSpFlags.noNeed && destTSpFlags.noNeed;

	// If one ns disables subpages, other enables subpages, AND HAS subpages,

	//   consider abort. Assume talk pages always safe (TODO fix)

	var subpageCollision = (vData.currNsAllowSubpages && !destSpFlags.noNeed) ||

		(vData.destNsAllowSubpages && !currSpFlags.noNeed);



	var moveSubpages = false;

	// TODO future: currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages

	// needs to be separate check. If talk subpages immovable, should not affect subjspace

	if (!subpageCollision && !noSubpages && vData.allowMoveSubpages &&

			(currSpFlags.allowMoveSubpages && destSpFlags.allowMoveSubpages) &&

			(currTSpFlags.allowMoveSubpages && destTSpFlags.allowMoveSubpages)) {

		moveSubpages = confirm("Move subpages? (OK for yes, Cancel for no)");

	} else if (subpageCollision) {

		mw.notify("One namespace does not have subpages enabled. Disallowing move subpages", { title: 'Page Swap Error', type: 'error' });

	}

	

	if (moveSubpages) { 

		vData.allSpArr = currSpFlags.spArr.concat(destSpFlags.spArr, currTSpFlags.spArr, destTSpFlags.spArr);

	} else {

		vData.allSpArr = [];

	}



	var moveTalk = false;

	// TODO: count subpages and make restrictions?

	if (vData.checkTalk && (!vTData.currTDNE || !vTData.destTDNE || moveSubpages)) {

		if (vTData.allowMoveTalk) {

			moveTalk = confirm("Move talk page(s)? (OK for yes, Cancel for no)");

		} else {

			alert("Disallowing moving talk. " +

				(!vTData.currTCanCreate ? (vData.currTalkName + " is create-protected")

				: (!vTData.destTCanCreate ? (vData.destTalkName + " is create-protected")

				: "Talk page is immovable")));

		}

	}

	

	var moveReason = '';

	var moveReasonPrompt = '';

	if (typeof mw.util.getParamValue("wpReason") === 'string') {

		moveReasonPrompt = mw.util.getParamValue("wpReason");

	} else if (document.getElementsByName("wpReason")[0 && document.getElementsByName("wpReason")[0].value != '') {

		moveReasonPrompt = document.getElementsByName("wpReason")[0].value;

	} else if (typeof moveReasonDefault === 'string') {

		moveReasonPrompt = moveReasonDefault;

	}

	moveReason = prompt("Move reason:", moveReasonPrompt);

	

	var spToMoveString = "";

	

	if (moveSubpages) {

		spToMoveString = "Subpages to move:\n  "+ vData.allSpArr.join("\n  ") + "\n\n";

	}

	

	var confirmString = "Round-robin configuration:\n  " +

		currTitle + " → " + destTitle + "\n    : " + moveReason +

		"\n      with movetalk:" + moveTalk + ", movesubpages:" + moveSubpages +

		"\n\n" + spToMoveString +

		"Proceed? (Cancel to abort)";



	if (confirm(confirmString)) {

		swapPages(currTitle, destTitle, moveReason, intermediateTitlePrefix,

			moveTalk, moveSubpages, vData, vTData);

	}

}



	var currNs = mw.config.get("wgNamespaceNumber");

	var wpOldTitle = mw.util.getParamValue("wpOldTitle");

	if (!wpOldTitle && document.getElementsByName("wpOldTitle")[0 && document.getElementsByName("wpOldTitle")[0].value != ''){

		wpOldTitle = document.getElementsByName("wpOldTitle")[0].value;

	}

	var wpNewTitle = mw.util.getParamValue("wpNewTitle");

	if (!wpNewTitle && document.getElementsByName("wpNewTitleMain")[0 && document.getElementsByName("wpNewTitleMain")[0].value != '' && document.getElementsByName("wpNewTitleNs")[0]){

		wpNewTitle = document.getElementsByName("wpNewTitleMain")[0].value;

		var nsid = document.getElementsByName("wpNewTitleNs")[0].value;

		if (nsid != 0) {

			wpNewTitle = mw.config.get("wgFormattedNamespaces")[nsid + ":" + wpNewTitle;

		}

	}

	if (currNs < -1 || currNs >= 120 ||

			(currNs >=  6 && currNs <= 9) ||

			(currNs >= 14 && currNs <= 99) ||

			(currNs == -1 && mw.config.get("wgCanonicalSpecialPageName") != "Movepage") ||

			(mw.config.get("wgCanonicalSpecialPageName") == "Movepage" && !wpOldTitle)

			)

		return; // special/other page



	var portletLink = mw.util.addPortletLink("p-cactions", "#", "Swap",

		"ca-swappages", "Perform a revision history swap / round-robin move");

	$( portletLink ).click(function(e) {

		e.preventDefault();

		var userPermissions = checkUserPermissions();

		if (!userPermissions.canSwap) {

			mw.notify("User rights insufficient for action.", { title: 'Page Swap Error', type: 'error' }); return;

		}

		

		var currTitle = wpOldTitle || mw.config.get("wgPageName");

		var destTitle = wpNewTitle || prompt("Swap \"" + (currTitle.replace(/_/g, ' ')) + "\" with:", (currTitle.replace(/_/g, ' ')));

		

		return roundrobin(userPermissions, currNs, currTitle, destTitle, "Draft:Move/");

	});

	

	if (mw.config.get("wgCanonicalSpecialPageName") == "Movepage" &&

			$( "div.mw-message-box-error" ).find( "p" ).eq(1).is( ":contains('name already exists')" ) &&

			wpOldTitle)

	{

		$( "div.mw-message-box-error" ).find( "p" ).eq(2).html( 'Please choose another name, or perform a <a title="Perform a revision history swap / round-robin move" href="#" id="pageswapLink">swap</a>.' );

		$( "#pageswapLink" ).click(function(e) {

			e.preventDefault();

			$( portletLink ).click();

		});

	}



});

});

// </syntaxhighlight>

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook