Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/**
@description: Support for file mass-move/rename at Wikimedia Commons.
@author: User:Legoktm/massrename.js - 2014
@author: User:Perhelion - 2017 (Commons lib, jsHint ready, regex support)
@revision: 01:10, 25 February 2017 (UTC)
@required modules: mediawiki.util, mediawiki.user, jquery.ui, ext.gadget.libCommons, ext.gadget.libJQuery, ext.gadget.libUtil, ext.gadget.libGlobalReplace, ext.gadget.AjaxQuickDelete
* <nowiki>
**/
/*global jQuery, mediaWiki*/
(function (mw, $) {
"use strict";
var mrg_queue, // In-progress queue
	mrg_count, // Total count
	mrg_done, // Already moved count
	AQD;

function fetch_cat_members(name, cont, callback) {
	var params = {
		action: 'query',
		rawcontinue: 1,
		list: 'categorymembers',
		cmtitle: 'Category:' + name,
		cmnamespace: 6,
		cmtype: 'file',
		cmlimit: 'max'
	};
	if (cont)
		params.cmcontinue = cont;

	(new mw.Api()).get(params).done(function (data) {
		if (data['query-continue']) {
			fetch_cat_members(
				name,
				data['query-continue'].categorymembers.cmcontinue,
				callback);
		}
		callback(data.query.categorymembers);
	});
}

function refreshProgressbar() {
	var $sel = $('#mrg-progress');
	if (!$sel.length && AQD.progressDialog) {
		$sel = $('<div>', {
				id: 'mrg-progress'
			}).progressbar();
		AQD.progressDialog.append($sel);
	}
	$sel.progressbar('option', {
		'max': mrg_count,
		'value': mrg_done
	});
}

function movePage() { // Like 'movePage' on AQD
	mw.user.tokens.set('csrfToken', AQD.movetoken);
	// Some users don't get it: They want to move pages to themselves.
	if (AQD.pageName === AQD.destination)
		return AQD.nextTask();
	var moveArgs = {
		cb: function () {
			mrg_done++;
			refreshProgressbar();
			AQD.nextTask();
		},
		// r-result, query, text
		errCb: function (t, r, q) {
			console.log(t, r, q, '\n++++\nTask: ' + AQD.currentTask + '\n:NextTask: ' + AQD.tasks[0] + '\n:LastTask: ' + AQD.tasks[AQD.tasks.length - 1]);
			AQD.fail(t);
		},
		from: AQD.pageName,
		to: AQD.destination,
		reason: AQD.reason,
		movetalk: true,
		// No change won't watch the file under the new location
		// even if it was watched under the old location
		watchlist: AQD.pageWasWatched ? 'watch' : 'nochange'
	};
	// Option to not leave a redirect behind, MediaWiki default does leave one behind
	// Just like movetalk, an empty parameter sets it to true (true to not leave a redirect behind)
	if (!AQD.wpLeaveRedirect)
		moveArgs.noredirect = true;
	AQD.showProgress(AQD.i18n.movingFile + ": " + AQD.pageName);
	mw.libs.commons.api.movePage(moveArgs);
}

function renameFile(oldFile, newFile, fullReason, delinkerOptOut, doPrompt) {
	if (!newFile)
		return AQD.nextQueue();
	// initialize:
	AQD.pageName = "File:" + oldFile.replace(/_/g, ' ');
	// AQD.tasks = [];
	AQD.possibleDestination = newFile;
	AQD.replaceUsingCORS = delinkerOptOut;
	AQD.details = undefined;
	AQD.destination = AQD.cleanFileName(newFile);
	// console.log("Move: ", AQD.pageName, "→", AQD.destination);

	// FIXME?: if ($('#globalusage').length || !$('#mw-imagepage-nolinkstoimage').length) ; this needs an extra ajax request
	AQD.inUse = AQD.mrgAndReplace; // Move only?
	
	AQD.addTask('getMoveToken');
	if (doPrompt) {
		AQD.addTask('promptForMoveTarget');
		// Let's be sure we have a fresh token and the latest MIME-Info
		AQD.addTask('getMoveToken');
	}
	AQD.fileNameExistsCB = 'fileExists'; // possible break
	AQD.addTask('doesFileExist');
	AQD.addTask('movePage');
	AQD.addTask('removeTemplate');
	AQD.addTask('queryRedirects');
	AQD.addTask('replaceUsage');
	AQD.nextTask();
	AQD.addTask('nextQueue');
}

function escapeRegExp(str) {
	// From MDN
	if (!$.trim(str)) {
		str = "Fail: Please fill a source name!";
		alert(str);
		throw new Error(str);
	}
	return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}

function run() {
	if (!mrg_queue.length)
		return alert("Fail: No file found!");
	// Save values
	var find = window.mrgFind = AQD.mrgFind;
	var replaceStr = window.mrgDestination = AQD.mrgRep;
	var reason = window.mrgReason = AQD.reason;
	var flags = 'g';

	if (!AQD.mrgReCase)
		flags += 'i';
	if (!AQD.mrgRegex)
		find = escapeRegExp(find);
	try {
		find = new RegExp(find, flags);
	} catch (e) {
		AQD.showProgress(mw.msg('wikieditor-toolbar-tool-replace-invalidregex',
				e.message));
		AQD.progressDialog.dialog({
			dialogClass: "ajaxDeleteError"
		});
	}

	// if (mw.config.get('debug')) console.log("search ", find);
	
	AQD.nextQueue = function () {
		if (!mrg_queue.length) {
			if (!mrg_done)
				alert("Fail: No match!");
			return AQD.showProgress();
		}
		var oldFile = mrg_queue.shift().replace(/^(?:Image|File):/i, '');
		var newFile = oldFile.replace(find, replaceStr);
		
		if (oldFile && newFile && oldFile !== newFile) {
			window.setTimeout(function () {
				// TODO: Hack to omit file-extension check
				AQD.mimeFileExtension = oldFile.toLowerCase().replace(/.*?\.(\w{2,5})$/, '$1');
				AQD.possibleReason = AQD.reason = AQD.cleanReason(reason);
				AQD.renameFile(oldFile, newFile, reason, AQD.delinkerOptOut, AQD.mrgRePrompt);
			}, 200);
		} else
			AQD.nextQueue();
	};
	
	AQD.nextQueue();
}

function init() {
	var possibleName = window.mrgFind || "";
	var possibleDestination = window.mrgDestination || mw.config.get('wgTitle');
	var possibleReason = window.mrgReason || "[[COM:FR|rename criterion 2]]"; // Prefix c: gets automatic added if global replace
	
	AQD.initialize();
	AQD.inUse = 0;
	AQD.addTask('promptForMassMove');
	AQD.addTask('mrgQueue');

	AQD.movePage = movePage;
	AQD.renameFile = renameFile;
	AQD.getMoveToken = function () { // FIXME: Dupe of AQD - only change his pageName
		var query = { 
			action: 'query',
			prop: 'info|revisions|imageinfo',
			rvprop: 'content|timestamp',
			iiprop: 'mime|mediatype',
			intoken: 'edit|move',
			inprop: 'watched',
			titles: this.pageName
		};
		this.showProgress(this.i18n.preparingToEdit);
		this.queryAPI(query, 'getMoveTokenCB');
	};
	AQD.fileExists = function (r) {
		if (typeof r !== 'string')
			r = this.destination;
		r += ': ' + this.i18n.moveOtherDestination;
		this.disableReport = true;
		this.fail(r);
		throw new Error(r);
	};
	AQD.mrgQueue = function () {
		mrg_queue = [];
		mrg_count = mrg_done = 0;
		
		fetch_cat_members(this.mrgCat, undefined, function (members) {
			var old_length = mrg_queue.length;
			$.each(members, function (_, v) {
				mrg_queue.push(v.title);
			});
			mrg_count += members.length;
			if (!old_length)
				run();
			// refreshProgressbar(); 
		});
	};
	AQD.promptForMassMove = function () {
		this.showProgress();
		this.prompt([{
				message: "Source category name (without namespace):",
				prefill: mw.config.get('wgTitle'),
				returnvalue: 'mrgCat',
				noEmpty: true
			}, {
				message: mw.msg("wikieditor-toolbar-tool-replace-search"),
				prefill: possibleName,
				returnvalue: 'mrgFind',
				noEmpty: true
			}, {
				message: mw.msg("wikieditor-toolbar-tool-replace-replace"),
				prefill: this.possibleDestination || possibleDestination,
				returnvalue: 'mrgRep',
				noEmpty: true
			}, {
				message: "Reason / " + this.i18n.reasonForMove,
				prefill: $.trim((this.reason || this.possibleReason || '').replace(/['\s]{2,}/g, '')) || possibleReason,
				returnvalue: 'reason',
				cleanUp: true,
				noEmpty: true
			}, {
				message: mw.msg("wikieditor-toolbar-tool-replace-case"),
				prefill: true,
				returnvalue: 'mrgReCase',
				type: 'checkbox'
			}, {
				message: mw.msg("wikieditor-toolbar-tool-replace-regex"),
				returnvalue: 'mrgRegex',
				type: 'checkbox'
			}, {
				message: "Do prompt every file?", // TODO: i18n string
				prefill: false,
				returnvalue: 'mrgRePrompt',
				type: 'checkbox'
			}, {
				message: this.i18n.dropdownMove,
				prefill: true,
				returnvalue: 'mrgAndReplace',
				type: 'checkbox'
			}, { // 8
				message: this.i18n.leaveRedirect,
				// prefill: true,
				returnvalue: 'wpLeaveRedirect',
				type: 'checkbox'
			}, {
				message: this.i18n.useCORSForReplace,
				prefill: !window.aqdCORSOptOut,
				returnvalue: 'delinkerOptOut',
				type: 'checkbox'
			}
		], 'File-MassRename-Gadget');
		if (this.inUse || this.userRights === 'filemover')
			$('#AjaxQuestion8').prop('disabled', true);
		$('#AjaxDeleteContainer').find('br+br').remove(); // Hack: double-lines looks ugly
	};

	AQD.replaceUsage = function (reasonShort) {
		this.showProgress(this.i18n.replacingUsage);
		if (typeof reasonShort !== 'string') {
			reasonShort = '[[COM:Duplicate|Duplicate]]:';
			if (!this.details) {
				this.reason = this.reason.replace(/\[\[Commons:File[_ ]renaming[^\[\]]*\]\]:? ?/i, '');
				reasonShort = '[[COM:FR|File renamed]]:';
			}
		}
		mw.libs.globalReplace(AQD.pageName, AQD.destination, reasonShort,
			AQD.reason, AQD.replaceUsingCORS)
		.done(function () {
			refreshProgressbar();
			AQD.nextTask();
		}).progress(function (r) {
			AQD.showProgress(r);
			console.log(r);
		}).fail(function (r) {
			AQD.disableReport = true;
			AQD.fail(r);
			console.log(r);
		});
	};

	// FIXME: Dupe of AQD - only change his pageName
	// Updates the redirects to the current page
	AQD.queryRedirects = function () {
		this.queryAPI({
			action: 'query',
			generator: 'backlinks',
			gblfilterredir: 'redirects',
			prop: 'revisions',
			rvprop: 'content',
			gbltitle: this.pageName
		}, 'updateRedirects'); // FIXME:  unfortunately local pageName
		this.showProgress("What-links-here");
	};
	
	AQD.nextTask();
}

$(function () {
	if ($.inArray(mw.config.get('wgNamespaceNumber'), [14]) !== -1) // Only on categories
		mw.loader.using(['mediawiki.util', 'ext.gadget.AjaxQuickDelete'], function () {
			AQD = window.AjaxQuickDelete;
			if (AQD.userRights === 'filemover' || AQD.userRights === 'sysop') {
				$(mw.util.addPortletLink('p-cactions', '#', 'MassRename', 'ca-mrg', 'Rename stuff'))
				.click(function (e) {
					e.preventDefault();
					mw.loader.using(['jquery.ui', 'ext.gadget.libGlobalReplace',
						'ext.gadget.libAPI', 'ext.wikiEditor'], init);
				});
			}
		});
});
}(mediaWiki, jQuery)); // </nowiki> EOF