MediaWiki:ProcessFileMoverRequests.js
Jump to navigation
Jump to search
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.
Documentation for this user script can be added at MediaWiki:ProcessFileMoverRequests. |
/**
** @description
** Easily process the commands left by filemovers and post them to the actual Commons-Delinker-Command page
** @usage
** [[User:CommonsDelinker/commands/filemovers]]
** @author
** Rillke, 2012, 2013
** @license
** GPL, v.3.0
**
** <nowiki>
** jshint valid
**/
/*global jQuery:false, mediaWiki:false, AjaxQuickDelete:false, alert:false*/
/*jshint curly:false, laxbreak: true*/
mediaWiki.loader.using('ext.gadget.AjaxQuickDelete', function () {
'use strict';
if (!AjaxQuickDelete) return;
var sDelinker = 'User:CommonsDelinker/commands';
var sDelinkerF = sDelinker + '/filemovers';
if (sDelinkerF !== mediaWiki.config.get('wgPageName')) return;
AjaxQuickDelete.showProgress("Reading list and checking for errors. This can take up to 1 minute");
setTimeout(function () {
var AQD = AjaxQuickDelete,
$ = jQuery,
mw = mediaWiki;
$.extend(AQD, {
cdFileExists: function () {
this.fail("Can\'t move file back because something exists at the old position. Reason was " + this.reason);
},
cdDone: function () {
this.showProgress();
},
cdRemoveFileMoverBit: function () {
var _this = this,
u = _this.cdFilemover;
_this.showProgress("Obtaining information about " + u);
mw.libs.commons.api.$query({
action: 'query',
list: 'users',
usprop: 'groups',
ususers: u,
meta: 'tokens',
type: 'userrights'
}, {
method: 'POST',
cache: false
}).done(function (r) {
if (r.query.users[0] && r.query.tokens.userrightstoken) {
if ($.inArray('filemover', r.query.users[0].groups) === -1) {
// Nothing to do, simply proceed
_this.nextTask();
}
_this.showProgress("Removing -" + u + "- from file mover group.");
$.post(mw.util.wikiScript('api'), {
format: 'json',
action: 'userrights',
user: u,
remove: 'filemover',
token: r.query.tokens.userrightstoken,
reason: _this.cdFilemoverRemoveReason
}).done(function (r2) {
if (r2.userrights.removed.length) {
_this.nextTask();
} else {
_this.fail("Error while changing user rights!");
}
}).fail(function () {
_this.fail("Server-Error while changing user rights!");
});
} else {
_this.fail("Error while retrieving information about user -- unable to get token!");
}
}).fail(function () {
_this.fail("API-Error while retrieving information about user -- unable to get token!");
});
},
cdMoveBack: function (params) {
var oPN = mw.config.get('wgPageName'),
from = 'File:' + params.movedata.from,
to = 'File:' + params.movedata.to;
mw.config.set('wgPageName', to);
this.pageName = to;
this.initialize();
this.showProgress();
mw.config.set('wgPageName', oPN);
this.reason = 'Moving back because ' + params.reason;
this.wpLeaveRedirect = params.redirect;
this.replaceUsingCORS = true;
this.destination = from;
this.addTask('getMoveToken');
this.addTask('doesFileExist');
this.fileNameExistsCB = 'cdFileExists';
this.addTask('movePage');
this.addTask('queryRedirects');
this.addTask('replaceUsage');
if (params.notifyuser) {
this.addTask('notifyUploaders');
this.uploaders = {};
this.uploaders[params.movedata.moved_by] = true;
// TODO: Create templates
var templateparams = '|from=' + from + '|to=' + to + '|reason=' + params.movedata.reason + '|reason_wrong=' + params.reason;
this.talk_tag = params.removefromfilemovergroup ? '{{subst:Filemover removed' + templateparams + '}}' : '{{subst:Inappropriate move' + templateparams + '}}';
}
if (params.removefromfilemovergroup) {
this.addTask('cdRemoveFileMoverBit');
this.cdFilemover = params.movedata.moved_by;
this.cdFilemoverRemoveReason = params.reason + ' @[[' + from + ']]';
}
this.addTask('cdDone');
this.nextTask();
},
cdMoveRequests: function (requests) {
this.initialize();
this.comDelMoveRequests = requests;
this.pageName = sDelinkerF;
this.addTask('getMoveToken');
this.addTask('cdRemoveItemsToProcess');
this.addTask('cdPostRequests');
this.addTask('cdPostManualRequests');
this.addTask('reloadPage');
this.nextTask();
},
cdRemoveItemsToProcess: function () {
var newContent = this.pageContent;
$.each(this.comDelMoveRequests, function (i, el) {
newContent = newContent.replace(el.raw, '');
});
newContent = $.trim(newContent);
var page = {};
page.title = sDelinkerF;
page.text = newContent;
page.editType = 'text';
page.starttimestamp = this.starttimestamp;
page.timestamp = this.timestamp;
page.watchlist = 'nochange';
this.showProgress('Removing items to process from list');
this.savePage(page, 'Moving replacement requests to [[' + sDelinker + ']]', 'nextTask');
},
cdPostRequests: function () {
var addText = '';
$.each(this.comDelMoveRequests, function (i, el) {
if (el.auto[0].checked) addText += '\n{{universal replace|' + el.from + '|' + el.to + '|reason=' + el.reasonArea.val() + '}}';
});
if (!addText) return this.nextTask();
var page = {};
page.title = sDelinker;
page.text = addText;
page.editType = 'appendtext';
page.watchlist = 'nochange';
this.showProgress(this.i18n.replacingUsage);
this.savePage(page, 'adding requests from [[' + sDelinkerF + ']]', 'nextTask');
},
cdPostManualRequests: function () {
var addText = '';
$.each(this.comDelMoveRequests, function (i, el) {
if (el.manual[0].checked) addText += '\n{{universal replace|' + el.from + '|' + el.to + '|reason=' + el.reasonArea.val() + '}}';
});
if (!addText) return this.nextTask();
var page = {};
page.title = sDelinker + '/byHand';
page.text = addText;
page.editType = 'appendtext';
page.watchlist = 'nochange';
this.showProgress("Listing items to process by hand");
this.savePage(page, 'adding requests from [[' + sDelinkerF + ']]', 'nextTask');
}
});
mw.util.addCSS('table.hovertable tr:hover { background-color: white !important; border: 1px solid #A7D7F9 !important; outline: 1px solid #A7D7F9; }\n' + 'table.hovertable tr:hover > td { background-color: white !important; border: 1px solid #A7D7F9 !important; }\n' + '.pfml-mb-dlg > div { padding: 5px }\n' + 'button.ui-button-icon-only { width: 2.4em !important }\n' // Fixing broken jQuery style (broken by MW)
);
var rxLine = /\{\{\universal replace\|[^\{\}]*\}\}/ig,
rxSubmatches = /\{\{\universal replace\|([^\{\}]*?)\|([^\{\}]*?)(?:\|\s*reason\s*\=([^\{\}]*))?\}\}/i,
$tb = $('#wpTextbox1'),
val = $tb.val(),
matrix = [],
matrixSrcIndex = {},
matrixDestIndex = {},
problemCount = 0;
var cleanFilename = function (fn) {
return fn.toLowerCase().replace(/\.tif$/, '.tiff').replace(/\.jpeg$/, '.jpg').replace(/\.og[av]$/, '.ogg');
};
var isProblem = function (src, dest) {
// if (/BS.?icon/i.test(src)) return 'BSicon';
//special treatment no longer needed, renaming handled by [[User:Jc86035]] and [[User:JJMC89 bot]]
try {
var extSrc = cleanFilename(src, true).match(/\.(\w{2,5})$/)[1];
var extDest = cleanFilename(dest, true).match(/\.(\w{2,5})$/)[1];
if ('svg' === extDest && 'svg' !== extSrc) return 'x → svg';
if (extSrc !== extDest) return 'new filetype';
} catch (ex) {
return 'no file ext';
}
if ((src) in matrixSrcIndex) return 'duplicate request';
if ((src) in matrixDestIndex) return 'file moved twice';
return '';
};
var m = val.match(rxLine);
if (!m) {
alert("Nothing to process!");
AQD.showProgress();
return;
}
$.each(m, function (i, l) {
var p = l.match(rxSubmatches);
if (p.length < 4) return true;
var el = {
from: $.trim(p[1].replace(/_/g, ' ')),
to: $.trim(p[2].replace(/_/g, ' ')),
reason: $.trim(p[3]),
raw: l
};
el.fromHref = mw.util.getUrl('File:' + el.from);
el.toHref = mw.util.getUrl('File:' + el.to);
el.isProblem = isProblem(el.from, el.to);
if (el.isProblem) problemCount++;
matrixSrcIndex[el.from] = matrix.length;
matrixDestIndex[el.to] = matrix.length;
matrix.push(el);
});
var $submitButton,
$dlg = $('<div>', {
text: 'Order Commons Delinker (auto), put the manual requests on the appropriate page and remove all items from this page.'
}),
$table = $('<table>', {
'class': 'hovertable wikitable',
style: 'width:100%;'
}).append($.parseHTML('<thead><tr><th>Source</th><th>Destination</th><th>Move back</th><th>Potential issues</th><th>Reason</th><th>Details</th><th>Manual</th><th>Auto</th></tr></thead>')),
$tbody = $('<tbody>').appendTo($table);
var timoutId = 0;
var updateCount = function () {
var autos = 0,
manuals = 0;
$.each(matrix, function (i, el) {
if (el.manual[0].checked) manuals++;
if (el.auto[0].checked) autos++;
});
$manualCount.text(manuals);
$autoCount.text(autos);
};
var _updateCount = function (e) {
clearTimeout(timoutId);
timoutId = setTimeout(updateCount, 750);
if (e) e.stopPropagation();
};
var _onMoveBackImmediateClick = function () {
mw.loader.using(['ext.gadget.libAPI', 'ext.gadget.jquery.blockUI'], $.proxy(__onMoveBackImmediateClick, this));
};
var __onMoveBackImmediateClick = function () {
var $button = $(this),
$buttonset = $button.closest('div'),
$tr = $buttonset.closest('tr'),
d = $tr.data('movedata'),
$autobox = d.auto;
$autobox[0].checked = false;
$autobox.triggerHandler('change');
$buttonset.block({
message: "Moving back",
css: {
width: '98%'
}
});
d.$moveBackButton.button({
disabled: true
});
d.$moveBackButtonFast.button({
disabled: true
});
mw.libs.commons.api.movePage({
from: 'File:' + d.to,
to: 'File:' + d.from,
reason: 'Not renamed in compliance with the policy (please stick to [[COM:FR]] when renaming files.',
movetalk: true,
watchlist: 'nochange',
cb: function () {
$buttonset.block({
message: "Done"
});
setTimeout(function () {
$buttonset.unblock();
}, 2000);
},
errCb: function (err) {
$tr.block({
message: "ERROR " + err
});
setTimeout(function () {
$buttonset.unblock();
}, 10000);
}
});
};
var _onMoveBackClick = function () {
mw.loader.using(['ext.gadget.libJQuery'], $.proxy(__onMoveBackClick, this));
};
var __onMoveBackClick = function () {
var d = $(this).closest('tr').data('movedata'),
$autobox = d.auto;
var $mb_dlg = $('<div>').attr({
title: "Moving file “" + d.to + "” back to “" + d.from + "”",
'class': 'pfml-mb-dlg'
});
var $r_mb_pfml_wrap = $('<div>').appendTo($mb_dlg),
$r_mb_pfml_l = $('<label>').attr({
'for': 'r_mb_pfml'
}).text("Reason for moving back (this must be a better one than the provided default one)")
.appendTo($r_mb_pfml_wrap),
$r_mb_pfml = $('<textarea>').attr({
id: 'r_mb_pfml',
style: 'width: 98%; height: 3.5em'
}).text("Not renamed in compliance with the policy (please stick to [[COM:FR]] when renaming files as MediaWiki\'s file moving implementation suffers from several issues)")
.appendTo($r_mb_pfml_wrap),
$redir_mb_pfml_wrap = $('<div>').appendTo($mb_dlg),
$redir_mb_pfml = $('<input type="checkbox" id="redir_mb_pfml" checked="checked"/>')
.appendTo($redir_mb_pfml_wrap),
$redir_mb_pfml_l = $('<label>').attr({
'for': 'redir_mb_pfml'
}).text("Leave redirect")
.appendTo($redir_mb_pfml_wrap),
$usr_mb_pfml_wrap = $('<div>').appendTo($mb_dlg),
$usr_mb_pfml = $('<input type="checkbox" id="usr_mb_pfml"/>')
.appendTo($usr_mb_pfml_wrap),
$usr_mb_pfml_l = $('<label>').attr({
'for': 'usr_mb_pfml'
}).text("Send a reminder to the file mover to stick to [[COM:FR]]")
.appendTo($usr_mb_pfml_wrap),
$usr_rm_right_mb_pfml_wrap = $('<div>').appendTo($mb_dlg),
$usr_rm_right_mb_pfml = $('<input type="checkbox" id="usr_rm_right_mb_pfml"/>')
.appendTo($usr_rm_right_mb_pfml_wrap),
$usr_rm_right_mb_pfml_l = $('<label>').attr({
'for': 'usr_rm_right_mb_pfml'
}).text("Remove file mover user right for abusing it providing this one as an example")
.appendTo($usr_rm_right_mb_pfml_wrap);
var _onMoveBackSubmit = function () {
$autobox[0].checked = false;
$autobox.triggerHandler('change');
d.$moveBackButton.button({
disabled: true
});
d.$moveBackButtonFast.button({
disabled: true
});
$(this).dialog('close');
AQD.cdMoveBack({
movedata: d,
reason: $r_mb_pfml.val(),
notifyuser: $usr_mb_pfml[0].checked,
redirect: $redir_mb_pfml[0].checked,
removefromfilemovergroup: $usr_rm_right_mb_pfml[0].checked
});
};
$mb_dlg.dialog({
modal: true,
width: 600,
buttons: {
"Move back": _onMoveBackSubmit,
"Cancel": function () {
$(this).dialog('close');
}
},
close: function () {
$(this).remove();
},
open: function () {
$r_mb_pfml.select();
var $buttons = $(this).parent().find('.ui-dialog-buttonpane button');
$buttons.eq(0).specialButton('proceed');
$buttons.eq(1).specialButton('cancel');
}
});
};
$.each(matrix, function (i, el) {
var $mbb = el.$moveBackButton = $('<button>', {
text: "with opt.",
role: 'button',
title: "Move back with options"
})
.button({
icons: {
primary: 'ui-icon-arrowthick-1-w'
},
disabled: true,
text: false
}).click(_onMoveBackClick),
$mbbf = el.$moveBackButtonFast = $('<button>', {
text: "immediately",
role: 'button',
title: "Move back immediately"
})
.button({
icons: {
primary: 'ui-icon-circle-triangle-w'
},
disabled: true,
text: false
}).click(_onMoveBackImmediateClick),
$tr = $('<tr>').append(
$('<td>', {
style: 'max-width:35%'
}).append(
$('<a>', {
text: el.from,
href: el.fromHref,
target: '_blank'
}),
' ',
$('<a>', {
text: '(gu)',
href: mw.util.getUrl('Special:GlobalUsage/' + el.from),
target: '_blank'
})),
$('<td>').append(
$('<a>', {
text: el.to,
href: el.toHref,
target: '_blank'
}),
' ',
$('<a>', {
text: '(hist)',
href: el.toHref + '?action=history',
target: '_blank'
})),
$('<td>', {
style: 'min-width:70px'
}).append(
$('<div>').append($mbb, $mbbf).buttonset()),
$('<td>', {
'class': el.isProblem ? 'ui-state-highlight' : ''
}).append(
$('<span>', {
text: el.isProblem
})));
el.reasonArea = $('<textarea></textarea>', {
cols: 40,
rows: 2,
style: 'width:99%'
}).val(el.reason);
$('<td>', {
style: 'min-width:35%'
}).append(el.reasonArea).appendTo($tr);
el.$details = $('<td>').appendTo($tr);
el.manual = $('<input>', {
type: 'checkbox'
}).change(_updateCount).click(_updateCount);
el.auto = $('<input>', {
type: 'checkbox'
}).change(_updateCount).click(_updateCount);
if (el.isProblem) el.manual[0].checked = true;
if (!el.isProblem) el.auto[0].checked = true;
$('<td>').append(el.manual).appendTo($tr).click(function (e) {
_updateCount(e);
el.manual[0].checked = !el.manual[0].checked;
});
$('<td>').append(el.auto).appendTo($tr).click(function (e) {
_updateCount(e);
el.auto[0].checked = !el.auto[0].checked;
});
$tr.appendTo($tbody).data('movedata', el);
});
var $problemCount = $('<td>', {
text: problemCount
}),
$manualCount = $('<td>', {
text: 0
}),
$autoCount = $('<td>', {
text: 0
}),
$tf = $('<tfoot>').append($('<tr>').append($.parseHTML('<td></td><td></td><td></td>'), $problemCount, $.parseHTML('<td></td><td></td>'), $manualCount, $autoCount)).appendTo($table);
_updateCount();
var $abuseStatusNote = $('<div>', {
id: 'AbuseFilterStatus',
text: 'AbuseFiler Status: ...'
});
var $delinkerStatus = $('<div>', {
id: 'CommonsDelinkerStatus'
});
$dlg.append($abuseStatusNote, $delinkerStatus, $table);
AQD.showProgress();
var w = Math.min($(window).width(), 1200);
$dlg.dialog({
'title': "Commons Delinker - Universal Replace Transfer",
'width': w,
'height': $(window).height(),
'buttons': {
'Execute': function () {
$submitButton.button('option', 'disabled', true);
AjaxQuickDelete.cdMoveRequests(matrix);
setTimeout(function () {
$submitButton.button('option', 'disabled', false);
}, 1000);
}
},
'open': function () {
// Look out for http://bugs.jqueryui.com/ticket/6830 / jQuery UI 1.9
var $buttons = $(this).parent().find('.ui-dialog-buttonpane button');
$submitButton = $buttons.eq(0).button({
icons: {
primary: 'ui-icon-circle-check'
}
});
if (-1 === $.inArray('sysop', mw.config.get('wgUserGroups'))) $submitButton.button('option', 'disabled', true);
}
});
// Now get the delinker and AbuseFiler status
var _showAbuseFilterStatus = function (result) {
if (!result) {
$abuseStatusNote.text('AbuseFilter Status: page-not-found. Script update required.');
return false;
}
var $r = $(result);
if (0 === $r.find('#wpFilterEnabled').length) {
$abuseStatusNote.text('AbuseFilter Status: Unknown. Script update required or you are not logged in your admin account.');
return false;
}
if ($r.find('#wpFilterEnabled').attr('checked')) {
$abuseStatusNote.text('').append($('<span>', {
style: 'font-weight:bold;',
text: 'AbuseFilter Status: '
})).append($('<span>', {
style: 'font-weight:bold; color:#77E9C7',
text: 'ON'
}));
$abuseStatusNote.append(' (', $r.find('#mw-abusefilter-edit-hitcount').find('.mw-input > a').css('font-size', '0.8em'), ')<br/>Last modification: ', $r.find('#mw-abusefilter-edit-lastmod').find('.mw-input').css('font-size', '0.8em'));
return true;
} else {
$abuseStatusNote.text('').append($('<span>', {
style: 'font-weight:bold;',
text: 'AbuseFilter Status: '
})).append($('<span>', {
style: 'font-weight:bold; color:#E977C7',
text: 'OFF'
}));
$abuseStatusNote.append('<br/>Last modification: ', $r.find('#mw-abusefilter-edit-lastmod').find('.mw-input').css('font-size', '0.8em'));
return true;
}
};
var _showDelinkerLastContribDate = function (result, user) {
var $botNode = $('<div>', {
style: 'font-weight:bold;',
text: user + ' Status: '
}).appendTo($delinkerStatus);
var lastEdit = getDateFromMWDate(result.query.usercontribs[0].timestamp);
if (currentDate - lastEdit > 3 * 60 * 60 * 1000) {
$botNode.append($('<span>', {
style: 'font-weight:bold; color:#E977C7',
text: 'Last edit ' + lastEdit.toLocaleString()
}));
} else {
$botNode.append($('<span>', {
style: 'font-weight:bold; color:#77E9C7',
text: 'Last edit ' + lastEdit.toLocaleString()
}));
}
};
$.get(mw.config.get('wgArticlePath').replace('$1', 'Special:AbuseFilter/75'), '', _showAbuseFilterStatus);
var currentDate;
var setCurrentDate = function (x) {
var shortNames = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
try {
var dat = x.getResponseHeader('date').match(/\D+(\d\d) (\D{3}) (\d{4}) (\d\d):(\d\d):(\d\d)/);
currentDate = new Date(dat[3], $.inArray(dat[2], shortNames), dat[1], dat[4], dat[5], dat[6]);
// The date is initialized/ constructed in local time but the server returned GMT-Time, so remove the offset
// According to w3c under- and overflow (<0, >60) are handled by the date-object itself
currentDate.setMinutes(currentDate.getMinutes() - currentDate.getTimezoneOffset());
} catch (ex) {
currentDate = new Date();
}
};
var getDateFromMWDate = function (stTimestamp) {
var regex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/;
var m1 = stTimestamp.match(regex);
var d = new Date(m1[1], m1[2] - 1, m1[3], m1[4], m1[5], m1[6]); // Wer hat sich diesen Unsinn ausgedacht?
// The date is initialized/ constructed in local time but the server returned GMT-Time, so remove the offset
// According to w3c under- and overflow (<0, >60) are handled by the date-object itself
d.setMinutes(d.getMinutes() - d.getTimezoneOffset());
return d;
};
var doContribsRequest = function (user) {
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'query',
format: 'json',
list: 'usercontribs',
ucuser: user,
ucprop: 'timestamp',
uclimit: 1
},
success: function (result, status, x) {
if (!currentDate && x && x.getResponseHeader) setCurrentDate(x);
_showDelinkerLastContribDate(result, user);
}
});
};
doContribsRequest('CommonsDelinker');
var lastpos = 0,
$checkMoveDef,
killer = 0,
_checkMoveLog = function (result) {
var les = result.query.logevents,
pos;
$.each(les, function (i, le) {
// get the position of the file in our matrix (remove File:-prefix)
pos = matrixSrcIndex[le.title.slice(5)];
if (typeof pos !== 'number') return;
var mpos = matrix[pos],
$el = mpos.$details;
if (mpos.moved_by) return;
mpos.moved_by = le.user;
mpos.$moveBackButton.button({
disabled: false
});
mpos.$moveBackButtonFast.button({
disabled: false
});
$('<a>', {
href: mw.util.getUrl('User talk:' + le.user),
text: 'Moved by ' + le.user
}).appendTo($el);
if (le.params && le.params.target_title !== 'File:' + mpos.to) {
$('<div>', {
'class': 'ui-state-highlight',
text: 'Wrong destination? Log says moved to ' + le.params.target_title
}).appendTo($el);
}
});
if (lastpos === pos || 0 === pos) {
killer++;
if (killer > 2)
$checkMoveDef.kill = true;
} else
killer = 0;
lastpos = pos;
};
mw.loader.using('ext.gadget.libAPI', function () {
$checkMoveDef = mw.libs.commons.api.$autoQuery({
format: 'json',
action: 'query',
list: 'logevents',
utf8: 1,
leprop: 'title|user|timestamp|details',
// not? lenamespace: "6",
lelimit: Math.min(matrix.length * 20, 500),
letype: 'move'
}, {
method: 'POST',
cache: false
}).progress(_checkMoveLog);
});
}, 1);
});
// </nowiki>