Jump to content
 







Main menu
   


Navigation  



Main page
Contents
Current events
Random article
About Wikipedia
Contact us
Donate
 




Contribute  



Help
Learn to edit
Community portal
Recent changes
Upload file
 








Search  

































Create account

Log in
 









Create account
 Log in
 




Pages for logged out editors learn more  



Contributions
Talk
 

















User:Andy M. Wang/pageswap.js

















User page
Talk
 

















Read
View source
View history
 








Tools
   


Actions  



Read
View source
View history
 




General  



What links here
Related changes
User contributions
User logs
View user groups
Upload file
Special pages
Permanent link
Page information
Get shortened URL
Download QR code
 




Print/export  



Download as PDF
Printable version
 
















Appearance
   

 






From Wikipedia, the free encyclopedia
 

< User:Andy M. Wang

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
// by [[User:Andy M. Wang]]
// 1.6.1.2018.0920

$(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) {
   alert("Swapping pages unavailable."); 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" || pagesData[k].ns < 0) {
   ret.valid = false;
   ret.invalidReason = ("Page " + pagesData[k].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 ((pagesData[k].ns >= 6 && pagesData[k].ns <= 9)
   || (pagesData[k].ns >= 10 && pagesData[k].ns <= 11 && !uPerms.allowSwapTemplates)
   || (pagesData[k].ns >= 14 && pagesData[k].ns <= 117)
   || (pagesData[k].ns >= 120)) {
   ret.valid = false;
   ret.invalidReason = ("Namespace of " + pagesData[k].title + " ("
    + pagesData[k].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 == pagesData[k].title) {
   ret.currTitle   = pagesData[k].title;
   ret.currNs      = pagesData[k].ns;
   ret.currTalkId  = pagesData[k].talkid; // could be undefined
   ret.currCanMove = pagesData[k].actions.move === '';
   ret.currIsRedir = pagesData[k].redirect === '';
  }
  if (titleTwo == pagesData[k].title) {
   ret.destTitle   = pagesData[k].title;
   ret.destNs      = pagesData[k].ns;
   ret.destTalkId  = pagesData[k].talkid; // could be undefined
   ret.destCanMove = pagesData[k].actions.move === '';
   ret.destIsRedir = pagesData[k].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) {
  alert("Unable to validate talk. Disallowing movetalk to be safe");
  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) {
    alert("Unable to get info on talk pages."); return ret; },
   data: { action:'query', format:'json', prop:'info',
    intestactions:'move|create', titles:talkTitleArr.join('|') }
  }).responseText).query.pages;
  for (var id in talkData) {
   if (talkData[id].title === talk1) {
    ret.currTDNE = talkData[id].invalid === '' || talkData[id].missing === '';
    ret.currTTitle = talkData[id].title;
    ret.currTCanMove = talkData[id].actions.move === '';
    ret.currTCanCreate = talkData[id].actions.create === '';
    ret.currTalkIsRedir = talkData[id].redirect === '';
   } else if (talkData[id].title === talk2) {
    ret.destTDNE = talkData[id].invalid === '' || talkData[id].missing === '';
    ret.destTTitle = talkData[id].title;
    ret.destTCanMove = talkData[id].actions.move === '';
    ret.destTCanCreate = talkData[id].actions.create === '';
    ret.destTalkIsRedir = talkData[id].redirect === '';
   } else {
    alert("Found pageid not matching given ids."); 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) {
  subpageids[idx < 50 ? 0 : 1].push( subpages[idx].pageid );
 }

 if (subpageids[0].length === 0) { return { data:[] }; }
 if (subpageids[1].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:subpageids[0].join('|') }
 }).responseText).query.pages;
 for (var k0 in subpageData0) {
  dataret.push({
   title:subpageData0[k0].title,
   isRedir:subpageData0[k0].redirect === '',
   canMove:subpageData0[k0].actions.move === ''
  });
 }

 if (subpageids[1].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:subpageids[1].join('|') }
 }).responseText).query.pages;
 for (var k1 in subpageData1) {
  dataret.push({
   title:subpageData1[k1].title,
   isRedir:subpageData1[k1].redirect === '',
   canMove:subpageData1[k1].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.data[kcs].canMove) {
   currSpCannotMove.push(currSp.data[kcs].title);
  }
  currSpArr.push((currSp.data[kcs].isRedir ? "(R) " : "  ")
   + currSp.data[kcs].title);
  if (currSp.data[kcs].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;
 return ret;
}

/**
 * After successful page swap, post-move cleanup:
 * Make talk page redirect
 * TODO more reasonable cleanup/reporting as necessary
 * vData.(curr|dest)IsRedir
 */
function doPostMoveCleanup(movedTalk, movedSubpages, vData, vTData) {
 if (movedTalk && vTData.currTDNE && confirm("Create redirect "
   + vData.currTalkName + " → " + vData.destTalkName + " if possible?")) {
  // means that destination talk now is redlinked TODO
 } else if (movedTalk && vTData.destTDNE && confirm("Create redirect "
   + vData.destTalkName + " → " + vData.currTalkName + " if possible?")) {
  // curr talk now is redlinked TODO
 }
}


/**
 * 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 === '') {
  alert("Titles are null, or move reason given was empty. Swap not done");
  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 [[User:Andy M. Wang/pageswap|pageswap]]",
  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 [[User:Andy M. Wang/pageswap|pageswap]]",
  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;
 }

 new mw.Api().postWithToken("csrf", pOne).done(function (reslt1) {
 new mw.Api().postWithToken("csrf", pTwo).done(function (reslt2) {
 new mw.Api().postWithToken("csrf", pTre).done(function (reslt3) {
  alert("Moves completed successfully.\n"
   + "Please create new red-linked talk pages/subpages if there are incoming links\n"
   + "  (check your contribs for \"Talk:\" redlinks),\n"
   + "  correct any moved redirects, and do post-move cleanup if necessary.");
  //doPostMoveCleanup(moveTalk, moveSubpages, vData, vTData);
 }).fail(function (reslt3) {
  alert("Fail on third move " + intermediateTitle + " → " + titleOne);
 });
 }).fail(function (reslt2) {
  alert("Fail on second move " + titleOne + " → " + titleTwo);
 });
 }).fail(function (reslt1) {
  alert("Fail on first move " + titleTwo + " → " + intermediateTitle);
 });
}

/**
 * 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) { alert("Unable to get info about namespaces"); },
  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) {
   alert("Unable to get info about " + currTitle + " or " + destTitle);
  },
  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.normalized[kp].from) { currTitle = pagesData.normalized[kp].to; }
  if (destTitle == pagesData.normalized[kp].from) { destTitle = pagesData.normalized[kp].to; }
 }
 // validate namespaces, not identical, can move
 var vData = swapValidate(currTitle, destTitle, pagesData.pages, nsData, uPerms);
 if (!vData.valid) { alert(vData.invalidReason); return; }
 if (vData.addlInfo !== undefined) { alert(vData.addlInfo); }

 // subj subpages
 var currSp = getSubpages(nsData, vData.currTitle, vData.currNs, false);
 if (currSp.error !== undefined) { alert(currSp.error); return; }
 var currSpFlags = printSubpageInfo(vData.currTitle, currSp);
 var destSp = getSubpages(nsData, vData.destTitle, vData.destNs, false);
 if (destSp.error !== undefined) { alert(destSp.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) { alert(currTSp.error); return; }
 var currTSpFlags = printSubpageInfo(vData.currTalkName, currTSp);
 var destTSp = getSubpages(nsData, vData.destTitle, vData.destNs, true);
 if (destTSp.error !== undefined) { alert(destTSp.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 moveTalk = false;
 // TODO: count subpages and make restrictions?
 if (vData.checkTalk && vTData.allowMoveTalk) {
  moveTalk = confirm("Move talk page(s)? (OK for yes, Cancel for no)");
 } else if (vData.checkTalk) {
  alert("Disallowing moving talk. "
   + (!vTData.currTCanCreate ? (vData.currTalkName + " is create-protected")
   : (!vTData.destTCanCreate ? (vData.destTalkName + " is create-protected")
   : "Talk page is immovable")));
 }

 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) {
  alert("One namespace does not have subpages enabled. Disallowing move subpages");
 }

 var moveReason = '';
 if (typeof moveReasonDefault === 'string') {
  moveReason = prompt("Move reason:", moveReasonDefault);
 } else {
  moveReason = prompt("Move reason:");
 }

 var confirmString = "Round-robin configuration:\n  "
  + currTitle + " → " + destTitle + "\n    : " + moveReason
  + "\n      with movetalk:" + moveTalk + ", movesubpages:" + moveSubpages
  + "\n\nProceed? (Cancel to abort)";

 if (confirm(confirmString)) {
  swapPages(currTitle, destTitle, moveReason, intermediateTitlePrefix,
   moveTalk, moveSubpages, vData, vTData);
 }
}

 var currNs = mw.config.get("wgNamespaceNumber");
 if (currNs < 0 || currNs >= 120
   || (currNs >=  6 && currNs <= 9)
   || (currNs >= 14 && currNs <= 99))
  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) {
   alert("User rights insufficient for action."); return;
  }
  var currTitle = mw.config.get("wgPageName");
  var destTitle = prompt("Swap \"" + (currTitle.replace(/_/g, ' ')) + "\" with:");

  return roundrobin(userPermissions, currNs, currTitle, destTitle, "Draft:Move/");
 });
});
});
// </syntaxhighlight>

Retrieved from "https://en.wikipedia.org/w/index.php?title=User:Andy_M._Wang/pageswap.js&oldid=860349938"





This page was last edited on 20 September 2018, at 02:00 (UTC).

Text is available under the Creative Commons Attribution-ShareAlike License 4.0; additional terms may apply. By using this site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization.



Privacy policy

About Wikipedia

Disclaimers

Contact Wikipedia

Code of Conduct

Developers

Statistics

Cookie statement

Mobile view



Wikimedia Foundation
Powered by MediaWiki