User:Perhelion/massrename.js

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: 17:00, 12 June 2020 (UTC)
@required modules: mediawiki.util, mediawiki.user, jquery.ui, ext.gadget.libCommons, ext.gadget.libJQuery, ext.gadget.libUtil, mediawiki.util, ext.gadget.libGlobalReplace, ext.gadget.AjaxQuickDelete
* <nowiki>
**/
/* eslint indent:["error","tab",{"outerIIFEBody":0}], one-var:0, vars-on-top:0, no-console:0, camelcase:0 */
/* 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);
	}).fail(function (t, r, q) {
		mw.log.warn(t, r, q);
	});
}

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.csrftoken);
	// 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();
		},
		// text, result, query
		errCb: function (t, r, q) {
			AQD.fail(t);
			console.error(t, r, q, '\n++++\nTask: ' + AQD.currentTask + '\n:NextTask: ' + AQD.tasks[0] + '\n:LastTask: ' + AQD.tasks.pop());
		},
		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);
}

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

	// FIXME?: if ($('#globalusage').length || !$('#mw-imagepage-nolinkstoimage').length) ; this needs an extra ajax request
	this.inUse = this.mrgAndReplace; // Move only?

	this.addTask('getMoveToken');
	if (doPrompt)
		this.addTask('promptForMoveTarget');
		// Let's be sure we have a fresh token and the latest MIME-Info
		// this.addTask('getMoveToken');

	this.fileNameExistsCB = 'fileExists'; // possible break
	this.addTask('doesFileExist');
	this.addTask('movePage');
	this.addTask('removeTemplate');
	this.addTask('queryRedirects');
	this.addTask('replaceUsage');
	this.nextTask();
	this.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,
		replaceStr = window.mrgDestination = AQD.mrgRep,
		reason = window.mrgReason = AQD.reason,
		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, ''),
			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();
}

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

	this.initialize();
	this.inUse = 0;
	this.addTask('promptForMassMove');
	this.addTask('mrgQueue');

	this.movePage = movePage;
	this.renameFile = renameFile;
	this.fileExists = function (r) {
		if (typeof r !== 'string')
			r = this.destination;
		r += ': ' + this.i18n.moveOtherDestination;
		this.disableReport = true;
		this.fail(r);
		throw new Error(r);
	};
	this.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.call(this);
			// refreshProgressbar();
		});
	};
	this.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 unknown bug, declares fully randomly as empty if not so
		}, {
			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)
			$('#AjaxQuestion8').prop('disabled', true);
		$('#AjaxDeleteContainer').find('br+br').remove(); // Hack: double-lines looks ugly
	};

	this.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(this.pageName, this.destination, reasonShort,
			this.reason, this.replaceUsingCORS)
			.fail(function (r) {
				AQD.showProgress(r);
				if (console.warn) console.warn(r);
			})
			.done(function () {
				refreshProgressbar();
				AQD.nextTask();
			}).progress(function (r) {
				AQD.showProgress(r);
				console.log(r);
			}).fail(function (r) {
				AQD.disableReport = true;
				AQD.fail(r);
				if (console.warn) console.warn(r);
				AQD.showProgress();
			});
	};

	this.nextTask();
};

$(function () {
	// Only on categories
	if (mw.config.get('wgNamespaceNumber') === 14) {
		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'))
					.on('click', function (e) {
						e.preventDefault();
						mw.loader.using(['jquery.ui', 'ext.gadget.libGlobalReplace',
							'ext.gadget.libAPI', 'ext.wikiEditor'], function () { init.call(AQD); });
					});
			}
		});
	}
});
}(mediaWiki, jQuery));
// </nowiki> EOF