User:SD0001/userRightsManager.js

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.
// forked from [[user:MusikAnimal/userRightsManager.js]] for adding decline options (per [[User_talk:MusikAnimal#PERM]])

/* 
 To install, add: 
	importScript('User:SD0001/userRightsManager.js'); // [[User:SD0001/userRightsManager.js]]
 to your common.js page
*/

// jshint maxerr: 999

// <nowiki>
// Some UI code adapted from [[User:Mr. Stradivarius/gadgets/Draftify.js]]
(function() {
if (!/Wikipedia:Requests for permissions\//.test(document.title)) {
	return;
}

var permissionNames = {
	'Account creator': 'accountcreator',
	'Autopatrolled': 'autoreviewer',
	'Confirmed': 'confirmed',
	'Event coordinator': 'eventcoordinator',
	'Extended confirmed': 'extendedconfirmed',
	'File mover': 'filemover',
	'Mass message sender': 'massmessage-sender',
	'New page reviewer': 'patroller',
	'Page mover': 'extendedmover',
	'Pending changes reviewer': 'reviewer',
	'Rollback': 'rollbacker',
	'Template editor': 'templateeditor'
};

var templates = {
	'Account creator': 'Account creator granted',
	'Autopatrolled': 'Autopatrolledgiven',
	'AutoWikiBrowser': '',
	'Confirmed': '',
	'Event coordinator': 'Event coordinator granted',
	'Extended confirmed': '',
	'File mover': 'Filemovergiven',
	'Mass message sender': 'Mass message sender granted',
	'New page reviewer': 'New Page Reviewer granted',
	'Page mover': 'Page mover granted',
	'Pending changes reviewer': 'Pending changes reviewer granted',
	'Rollback': 'Rollback granted 3',
	'Template editor': 'Template editor granted'
};

var decline_templates = {
	'Account creator': 'permission declined notification|Account creator|Wikipedia Accountcreators.svg',
	'Autopatrolled': 'permission declined notification|Autopatrolled|Wikipedia Autopatrolled.svg',
	'AutoWikiBrowser': '',
	'Confirmed': '',
	'Event coordinator': 'permission declined notification|Event coordinator|Wikipedia Event coordinator.svg',
	'Extended confirmed': '',
	'File mover': 'permission declined notification|File mover|Wikipedia File mover.svg',
	'Mass message sender': 'permission declined notification|Mass message sender|Wikipedia mass messenger.svg',
	'New page reviewer': 'permission declined notification|New page reviewer|Wikipedia New page reviewer.svg',
	'Page mover': 'permission declined notification|Page mover|Wikipedia page mover.svg',
	'Pending changes reviewer': 'permission declined notification|Pending changes reviewer|Wikipedia Reviewer.svg',
	'Rollback': 'permission declined notification|Rollback|Wikipedia Rollbacker.svg',
	'Template editor': 'permission declined notification|Template editor|Wikipedia Template editor icon (1).svg'
};

var api,
	permission = mw.config.get('wgTitle').split('/').slice(-1)[0],
	revisionId = mw.config.get('wgRevisionId'),
	tagLine = ' (using [[User:MusikAnimal/userRightsManager|userRightsManager]])',
	permaLink, userName, dialog;

mw.loader.using(['oojs-ui', 'mediawiki.api', 'mediawiki.widgets.DateInputWidget'], function() {
	api = new mw.Api();
	$('.perm-assign-permissions a').on('click', function(e) {
		if (permission === 'AutoWikiBrowser') {return true;}
		e.preventDefault();
		userName = $(this).parents('.plainlinks').find('a').eq(0).text();
		showDialog();
	});
	$('.perm-assign-permissions').after(
		' (',
		$('<a>').html('<span style="color: #03B;"><b>decline</b></span>').attr('href', '#')
			.click(function(e) {
				e.preventDefault();
				userName = $(this).parents('.plainlinks').find('a').eq(0).text();
				showDeclineDialog();
			}),
		')'
	);
});

function showDialog() {
	var Dialog = function(config) {
		Dialog.super.call(this, config);
	};
	OO.inheritClass(Dialog, OO.ui.ProcessDialog);
	Dialog.static.name = 'user-rights-manager';
	Dialog.static.title = 'Grant ' + permission + ' to ' + userName;
	Dialog.static.actions = [
		{ action: 'submit', label: 'Submit', flags: ['primary', 'constructive'] },
		{ label: 'Cancel', flags: 'safe' }
	];
	Dialog.prototype.getApiManager = function() {
		return this.apiManager;
	};
	Dialog.prototype.getBodyHeight = function() {
		return 208;
	};
	Dialog.prototype.initialize = function() {
		Dialog.super.prototype.initialize.call(this);
		this.editFieldset = new OO.ui.FieldsetLayout({
			classes: ['container']
		});
		this.editPanel = new OO.ui.PanelLayout({
			expanded: false
		});
		this.editPanel.$element.append(this.editFieldset.$element);
		this.rightsChangeSummaryInput = new OO.ui.TextInputWidget({
			value: 'Requested at [[WP:PERM]]'
		});
		this.expiryInput = new mw.widgets.DateInputWidget({
			$overlay: $('.oo-ui-window')
		});
		this.closingRemarksInput = new OO.ui.TextInputWidget({
			value: '{{done}} ~~~~'
		});
		this.watchTalkPageCheckbox = new OO.ui.CheckboxInputWidget({
			selected: false
		});
		var formElements = [
			new OO.ui.FieldLayout(this.rightsChangeSummaryInput, {
				label: 'Summary'
			}),
			new OO.ui.FieldLayout(this.expiryInput, {
				label: 'Expiry (optional)'
			}),
			new OO.ui.FieldLayout(this.closingRemarksInput, {
				label: 'Closing remarks'
			})
		];
		if (templates[permission]) {
			formElements.push(
				new OO.ui.FieldLayout(this.watchTalkPageCheckbox, {
					label: 'Watch user talk page'
				})
			);
		}
		this.editFieldset.addItems(formElements);
		this.submitPanel = new OO.ui.PanelLayout({
			$: this.$,
			expanded: false
		});
		this.submitFieldset = new OO.ui.FieldsetLayout({
			classes: ['container']
		});
		this.submitPanel.$element.append(this.submitFieldset.$element);
		this.changeRightsProgressLabel = new OO.ui.LabelWidget();
		this.changeRightsProgressField = new OO.ui.FieldLayout(this.changeRightsProgressLabel);
		this.markAsDoneProgressLabel = new OO.ui.LabelWidget();
		this.markAsDoneProgressField = new OO.ui.FieldLayout(this.markAsDoneProgressLabel);
		this.issueTemplateProgressLabel = new OO.ui.LabelWidget();
		this.issueTemplateProgressField = new OO.ui.FieldLayout(this.issueTemplateProgressLabel);
		this.stackLayout = new OO.ui.StackLayout({
			items: [this.editPanel, this.submitPanel],
			padded: true
		});
		this.$body.append(this.stackLayout.$element);
		$('.mw-widget-dateInputWidget').css('width', '100%');
	};

	Dialog.prototype.onSubmit = function() {
		var self = this, promiseCount = templates[permission] ? 3 : 2;

		self.actions.setAbilities({ submit: false });

		addPromise = function(field, promise) {
			self.pushPending();
			promise.done(function() {
				field.$field.append($('<span>')
					.text('Complete!')
					.prop('style', 'position:relative; top:0.5em; color: #009000; font-weight: bold')
				);
			}).fail(function(obj) {
				if (obj && obj.error && obj.error.info) {
					field.$field.append($('<span>')
						.text('Error: ' + obj.error.info)
						.prop('style', 'position:relative; top:0.5em; color: #cc0000; font-weight: bold')
					);
				} else {
					field.$field.append($('<span>')
						.text('An unknown error occurred.')
						.prop('style', 'position:relative; top:0.5em; color: #cc0000; font-weight: bold')
					);
				}
			}).always(function() {
				promiseCount--; // FIXME: maybe we could use a self.isPending() or something
				self.popPending();

				if (promiseCount === 0) {
					setTimeout(function() {
						location.reload(true);
					}, 1000);
				}
			});

			return promise;
		};

		self.markAsDoneProgressField.setLabel('Marking request as done...');
		self.submitFieldset.addItems([self.markAsDoneProgressField]);
		self.changeRightsProgressField.setLabel('Assigning rights...');
		self.submitFieldset.addItems([self.changeRightsProgressField]);

		if (templates[permission]) {
			self.issueTemplateProgressField.setLabel('Issuing template...');
			self.submitFieldset.addItems([self.issueTemplateProgressField]);
		}

		addPromise(
			self.markAsDoneProgressField,
			editPERMpage('done', '\n::' + this.closingRemarksInput.getValue())
		).then(function(data) {
			addPromise(
				self.changeRightsProgressField,
				assignPermission(
					this.rightsChangeSummaryInput.getValue(),
					data.edit.newrevid,
					this.expiryInput.getValue()
				)
			).then(function() {
				// silently add user to MMS list
				if (permission === 'New page reviewer') {addToMMSList();}

				if (templates[permission]) {
					addPromise(
						self.issueTemplateProgressField,
						issueTemplate(this.watchTalkPageCheckbox.isSelected(), this.expiryInput.getValue())
					);
				}
			}.bind(this));
		}.bind(this));

		self.stackLayout.setItem(self.submitPanel);
	};

	Dialog.prototype.getActionProcess = function(action) {
		return Dialog.super.prototype.getActionProcess.call(this, action).next(function() {
				if ( action === 'submit' ) {
					return this.onSubmit();
				}
					return Dialog.super.prototype.getActionProcess.call( this, action );

			}, this);
	};

	dialog = new Dialog({
		size: 'medium'
	});

	var windowManager = new OO.ui.WindowManager();
	$('body').append(windowManager.$element);
	windowManager.addWindows([dialog]);
	windowManager.openWindow(dialog);
}

function showDeclineDialog() {
	var Dialog = function(config) {
		Dialog.super.call(this, config);
	};
	OO.inheritClass(Dialog, OO.ui.ProcessDialog);
	Dialog.static.name = 'user-rights-manager';
	Dialog.static.title = 'Decline ' + permission + ' to ' + userName;
	Dialog.static.actions = [
		{ action: 'submit', label: 'Submit', flags: ['primary', 'constructive'] },
		{ label: 'Cancel', flags: 'safe' }
	];
	Dialog.prototype.getApiManager = function() {
		return this.apiManager;
	};
	Dialog.prototype.getBodyHeight = function() {
		return 208;
	};
	Dialog.prototype.initialize = function() {
		Dialog.super.prototype.initialize.call(this);
		this.editFieldset = new OO.ui.FieldsetLayout({
			classes: ['container']
		});
		this.editPanel = new OO.ui.PanelLayout({
			expanded: false
		});
		this.editPanel.$element.append(this.editFieldset.$element);

		this.closingRemarksInput = new OO.ui.TextInputWidget({
			value: '{{not done}} ~~~~'
		});
		this.notifyUserCheckbox = new OO.ui.CheckboxInputWidget({
			selected: true
		});
		this.watchTalkPageCheckbox = new OO.ui.CheckboxInputWidget({
			selected: false
		});
		var formElements = [
			new OO.ui.FieldLayout(this.closingRemarksInput, {
				label: 'Closing remarks'
			})
		];
		if (decline_templates[permission]) {
			formElements.push(
				new OO.ui.FieldLayout(this.notifyUserCheckbox, {
					label: 'Notify user on talk page'
				})
			);
			formElements.push(
				new OO.ui.FieldLayout(this.watchTalkPageCheckbox, {
					label: 'Watch user talk page'
				})
			);
		}
		this.editFieldset.addItems(formElements);
		this.submitPanel = new OO.ui.PanelLayout({
			$: this.$,
			expanded: false
		});
		this.submitFieldset = new OO.ui.FieldsetLayout({
			classes: ['container']
		});
		this.submitPanel.$element.append(this.submitFieldset.$element);

		this.markAsDoneProgressLabel = new OO.ui.LabelWidget();
		this.markAsDoneProgressField = new OO.ui.FieldLayout(this.markAsDoneProgressLabel);
		this.issueTemplateProgressLabel = new OO.ui.LabelWidget();
		this.issueTemplateProgressField = new OO.ui.FieldLayout(this.issueTemplateProgressLabel);
		this.stackLayout = new OO.ui.StackLayout({
			items: [this.editPanel, this.submitPanel],
			padded: true
		});
		this.$body.append(this.stackLayout.$element);
	};

	Dialog.prototype.onSubmit = function() {
		var self = this, promiseCount = decline_templates[permission] ? 2 : 1;

		self.actions.setAbilities({ submit: false });

		addPromise = function(field, promise) {
			self.pushPending();
			promise.done(function() {
				field.$field.append($('<span>')
					.text('Complete!')
					.prop('style', 'position:relative; top:0.5em; color: #009000; font-weight: bold')
				);
			}).fail(function(obj) {
				if (obj && obj.error && obj.error.info) {
					field.$field.append($('<span>')
						.text('Error: ' + obj.error.info)
						.prop('style', 'position:relative; top:0.5em; color: #cc0000; font-weight: bold')
					);
				} else {
					field.$field.append($('<span>')
						.text('An unknown error occurred.')
						.prop('style', 'position:relative; top:0.5em; color: #cc0000; font-weight: bold')
					);
				}
			}).always(function() {
				promiseCount--; // FIXME: maybe we could use a self.isPending() or something
				self.popPending();

				if (promiseCount === 0) {
					setTimeout(function() {
						location.reload(true);
					}, 1000);
				}
			});

			return promise;
		};

		self.markAsDoneProgressField.setLabel('Marking request as not done...');
		self.submitFieldset.addItems([self.markAsDoneProgressField]);
		// self.changeRightsProgressField.setLabel( 'Assigning rights...' );
		// self.submitFieldset.addItems( [self.changeRightsProgressField] );

		if (!!decline_templates[permission] && this.notifyUserCheckbox.getValue()) {
			self.issueTemplateProgressField.setLabel('Issuing template...');
			self.submitFieldset.addItems([self.issueTemplateProgressField]);
		}

		addPromise(
			self.markAsDoneProgressField,
			editPERMpage('not done', '\n::' + this.closingRemarksInput.getValue())
		).then(function(data) {
			if (!!decline_templates[permission] && this.notifyUserCheckbox.isSelected()) {
				addPromise(
					self.issueTemplateProgressField,
					issueDeclineTemplate(this.watchTalkPageCheckbox.isSelected(), data.edit.newrevid)
				);
			}
		}.bind(this));

		self.stackLayout.setItem(self.submitPanel);
	};

	Dialog.prototype.getActionProcess = function(action) {
		return Dialog.super.prototype.getActionProcess.call(this, action).next(function() {
				if ( action === 'submit' ) {
					return this.onSubmit();
				}
					return Dialog.super.prototype.getActionProcess.call( this, action );

			}, this);
	};

	dialog = new Dialog({
		size: 'medium'
	});

	var windowManager = new OO.ui.WindowManager();
	$('body').append(windowManager.$element);
	windowManager.addWindows([dialog]);
	windowManager.openWindow(dialog);
}

function assignPermission(summary, revId, expiry) {
	permaLink = '[[Special:PermaLink/' + revId + '#User:' + userName + '|permalink]]';
	return api.postWithToken('userrights', {
		action: 'userrights',
		format: 'json',
		user: userName.replace(/ /g, '_'),
		add: permissionNames[permission],
		reason: '+' + permissionNames[permission] + '; ' + summary + '; ' + permaLink + tagLine,
		expiry: expiry === '' ? 'infinity' : expiry
	});
}

// `status` is either "done" or "not done"
function editPERMpage(status, closingRemarks) {
	var sectionNode = document.getElementById('User:' + userName.replace(/"/g, '.22').replace(/ /g, '_')),
		sectionNumber = $(sectionNode).siblings('.mw-editsection').find("a:not('.mw-editsection-visualeditor')").prop('href').match(/section=(\d+)/)[1];
	return api.postWithToken('csrf', {
		format: 'json',
		action: 'edit',
		title: mw.config.get('wgPageName'),
		section: sectionNumber,
		summary: '/* User:' + userName + ' */ ' + status + tagLine,
		appendtext: closingRemarks
	});
}

function issueDeclineTemplate(watch, revId) {
	permaLink = '[[Special:PermaLink/' + revId + '#User:' + userName + '|permalink]]';
	var talkPage = 'User talk:' + userName.replace(/ /g, '_');
	return api.postWithToken('csrf', {
		format: 'json',
		action: 'edit',
		title: talkPage,
		section: 'new',
		summary: 'Your request for ' + permission + ' was declined per ' + permaLink + tagLine,
		text: '{{subst:' + decline_templates[permission] + '}}',
		sectiontitle: 'Your request for ' + permission[0].toLowerCase() + permission.slice(1) + ' right',
		watchlist: watch ? 'watch' : 'unwatch'
	});
}

function issueTemplate(watch, expiry) {
	var talkPage = 'User talk:' + userName.replace(/ /g, '_');
	return api.postWithToken('csrf', {
		format: 'json',
		action: 'edit',
		title: talkPage,
		section: 'new',
		summary: permission + ' granted per ' + permaLink + tagLine,
		text: '{{subst:' + templates[permission] + (expiry === '' ? '' : '|expiry=' + expiry) + '}}',
		sectiontitle: permission + ' granted',
		watchlist: watch ? 'watch' : 'unwatch'
	});
}

function addToMMSList() {
	api.postWithToken('csrf', {
		format: 'json',
		action: 'editmassmessagelist',
		spamlist: 'Wikipedia:New pages patrol/Reviewers/Newsletter list',
		add: 'User talk:' + userName
	});
}
})();
// </nowiki>