MediaWiki:Gadget-Massblock.js

Questa pagina definisce alcuni parametri di aspetto e comportamento generale di tutte le pagine. Per personalizzarli vedi Aiuto:Stile utente.


Nota: dopo aver salvato è necessario pulire la cache del proprio browser per vedere i cambiamenti (per le pagine globali è comunque necessario attendere qualche minuto). Per Mozilla / Firefox / Safari: fare clic su Ricarica tenendo premuto il tasto delle maiuscole, oppure premere Ctrl-F5 o Ctrl-R (Command-R su Mac); per Chrome: premere Ctrl-Shift-R (Command-Shift-R su un Mac); per Konqueror: premere il pulsante Ricarica o il tasto F5; per Opera può essere necessario svuotare completamente la cache dal menù Strumenti → Preferenze; per Internet Explorer: mantenere premuto il tasto Ctrl mentre si preme il pulsante Aggiorna o premere Ctrl-F5.

/* jshint maxerr: 10000 */
/* global mediaWiki, jQuery, OO, window, document */
/**
 * Tool for mass-blocking a list of IPs/users.
 * Adapted from [[:en:User:Timotheus Canens/massblock.js]].
 * Go to [[Special:Massblock]] to use it.
 *
 * In order to use it, please IMPORT this page, and then add some code calling
 * mw.messages.set with a list of localized messages if you wish. For an example,
 * see [[:it:MediaWiki:Gadget-Massblock-it.js]]
 *
 * Memo: only then() can return a new promise object, done() and fail() can't.
 * 
 * @author (i.e. blame him) [[:it:User:Daimona Eaytoy]]
 */
( function( mw, $ ) {
	'use strict';

// Default messages
	mw.messages.set( {
		'massblock-toolbar-text': 'Massblock',
		'massblock-document-title': 'Mass-blocking tool - Wikipedia, the free encyclopedia',
		'massblock-page-title': 'Mass-blocking tool',
		'massblock-abuse-disclaimer': 'If you abuse this tool, it\'s your fault, not mine.',
		'massblock-blockusers': 'Users to block (one on each line, please):',
		'massblock-talkmsg': 'Replace talk page with, or text to add if the expiry is not infinite (leave blank to leave no message):',
		'massblock-upmsg': 'Replace user page text with (leave blank for no change):',
		'massblock-block-options-label': 'Block options',
		'massblock-further-options-label': 'Further options',
		'massblock-common-reasons': 'Common reasons:',
		'massblock-other-reason': 'Other reason',
		'massblock-extra-reason': 'Other/additional reason:',
		'massblock-exptime': 'Expiration time, in english (blank for indefinite):',
		'massblock-summary-default': 'Blocked user.',
		'massblock-talksummary': 'Edit summary for talk page edit:',
		'massblock-talkprotect': 'Protect the talk (sysop level, infinite):',
		'massblock-upsummary': 'Edit summary for user page edit:',
		'massblock-upprotect': 'Protect the user page (sysop level, infinite):',
		'massblock-protect-reason-label': 'Reason for protection:',
		'massblock-protect-reason-default': 'Blocked user.',
		'massblock-anononly': 'Block anonymous users only (IPs only):',
		'massblock-autoblock': 'Enable autoblock (accounts only):',
		'massblock-nocreate': 'Block account creation:',
		'massblock-noemail': 'Block email:',
		'massblock-notalk': 'Remove talk page access:',
		'massblock-override': 'Override existing blocks:',
		'massblock-submit-text': 'Block',
		'massblock-result-alert': 'Blocked $1 users. Edited $2 talk pages and $3 user pages. Protected $4 talk pages and $5 user pages.',
		'massblock-failed-actions': 'Failed actions with errors:',
		'massblock-failure-help': 'help',
		'massblock-init-failure': 'Unable to load the Massblock gadget. Error:'
	} );

	// OOUI element
	var submitBtn,
		// Ideally this would use $wgBlockAllowsUTEdit, but it's not available.
		blockAllowsTalkEdit = window.location.href.indexOf( 'it.wikipedia' ) === -1,
		mwapi = new mw.Api();

	var ErrorHandler = {
		errors: {},
		/**
		 * Process an error in any part of the process
		 *
		 * @param {string} e The error code
		 * @param {string} user The user we're processing
		 * @param {string} action The action we're doing. This is of the form
		 *   {$actionname}-{$page}, where $action name is the name of the API module
		 *   being used (e.g. 'block' or 'edit') and $page is either 'talk' or 'user',
		 *   representing the target of the action. The only special case is 'block',
		 *   which has no hyphen and no page.
		 */
		add: function( e, user, action ) {
			var obj = { action: e };
			if ( !this.errors[ user ] ) {
				this.errors[ user ] = [ obj ];
			} else {
				this.errors[ user ].push( obj );
			}
		},
		getCodesForUser: function( user ) {
			var cb = function( el ) {
				var key = Object.keys( el )[ 0 ],
					action = key.split( "-" )[ 0 ],
					link = '//mediawiki.org/wiki/API:' + action + '#Possible_errors';
				return key + ': <code style="color:red">' +
					el[ key ] + '</code> (<a href="' + link + '">' +
					msg( 'failure-help' ) + '</a>)';
			};
			return this.errors[ user ].map( cb );
		}
	};

	var FormData = {
		init: function() {
			this.dropdownReason = $( "#wpMassBlockReasons select :selected" ).val().trim();
			this.otherReason = $( "#wpMassBlockReason input" ).val().trim();
			this.anononly = $( "#wpMassBlockAnononly input" ).prop( 'checked' );
			this.nocreate = $( "#wpMassBlockNocreate input" ).prop( 'checked' );
			this.noEmail = $( "#wpMassBlockEmail input" ).prop( 'checked' );
			this.autoblock = $( "#wpMassBlockAutoblock input" ).prop( 'checked' );
			this.talkpageBlocked = blockAllowsTalkEdit ? $( "#wpMassBlockTalkpage input" ).prop( 'checked' ) : true;
			this.reblock = $( "#wpMassBlockReblock input" ).prop( 'checked' );
			this.talkMessage = $( "#wpMassBlockMessage textarea" ).val().trim();
			this.expiry = $( "#wpMassBlockExpiry input" ).val().trim();
			this.talkSummary = $( "#wpMassBlockSummaryTalk input" ).val().trim();
			this.upSummary = $( "#wpMassBlockSummaryUser input" ).val().trim();
			this.protectReason = $( "#wpMassBlockProtectReason input" ).val().trim();

			this.isInfty = isInfinity( this.expiry );
			// Several actions can only be executed if the block is infinite
			this.protectTalk = $( "#wpMassBlockProtectTalk input" ).prop( 'checked' ) && this.isInfty;
			this.protectUser = $( "#wpMassBlockProtectUser input" ).prop( 'checked' ) && this.isInfty;
			this.upMessage = this.isInfty ? $( "#wpMassBlockTag textarea" ).val().trim() : '';
		}
	};

	var Main = {
		blocked: 0,
		talkpageedited: 0,
		userpageedited: 0,
		talkpageprotected: 0,
		userpageprotected: 0,

		_protect: function( page, exists, counter, user ) {
			var prType = exists ? 'edit=sysop|move=sysop' : 'create=sysop';
			return doProtectPage( page, 'edit=sysop|move=sysop' )
				.done( function() {
					this[counter]++;
				} )
				.fail( function( e ) {
					ErrorHandler.add( e, user, "protect-talk" );
				} )
				.always( function() {
					return $.when();
				} );
		},
		
		editTalk: function( user ) {
			var talkPage = getTalkTitle( user );
			return doEditPage( talkPage, FormData.talkMessage, FormData.talkSummary, !FormData.isInfty )
				.then(
					function() {
						this.talkpageedited++;
						if ( FormData.protectTalk ) {
							return this._protect( talkPage, true, 'talkpageprotected', user );
						}
					}.bind( this ),
					function( e ) {
						ErrorHandler.add( e, user, "edit-talk" );
						return $.when();
					}
				);
		},
		protectTalk: function( user ) {
			var talkPage = getTalkTitle( user );
			mwapi.get( {
				action: 'query',
				titles: talkPage
			} )
				.then(
					function( data ) {
						var exists = Object.keys( data.query.pages )[ 0 ] !== -1;
						return this._protect( talkPage, exists, 'talkpageprotected', user );
					}.bind( this ),
					function( e ) {
						ErrorHandler.add( e, user, "query-talk" );
						return $.when();
					}
				);
		},
		editUP: function( user ) {
			var userPage = getUPTitle( user );
			doEditPage( userPage, FormData.upMessage, FormData.upSummary )
				.then(
					function() {
						this.userpageedited++;
						if ( FormData.protectUser ) {
							return this._protect( userPage, true, 'userpageprotected', user );
						}
					}.bind( this ),
					function( e ) {
						ErrorHandler.add( e, user, "edit-user" );
						return $.when();
					}
				);
		},
		protectUP: function( user ) {
			var userPage = getUPTitle( user );
			return mwapi.get( {
					action: 'query',
					titles: userPage
				} )
				.then(
					function( data ) {
						var exists = Object.keys( data.query.pages )[ 0 ] !== -1;
						return this._protect( userPage, exists, 'userpageprotected', user );
					}.bind( this ),
					function( e ) {
						ErrorHandler.add( e, user, "query-user" );
						return $.when();
					}
				);
		},
		
		getAlertText: function() {
			return msg( 'result-alert' )
				.replace( '$1', this.blocked )
				.replace( '$2', this.talkpageedited )
				.replace( '$3', this.userpageedited )
				.replace( '$4', this.talkpageprotected )
				.replace( '$5', this.userpageprotected );
		}
	};

	function getTalkTitle( user ) {
		return 'User talk:' + user;
	}
	
	function getUPTitle( user ) {
		return 'User:' + user;
	}

	/**
	 * The post-block handler. Performs edits and protections.
	 *
	 * @param {string} user
	 * @return {Promise} A promise which is resolved after all actions are done
	 *   for all pages. This will never be rejected.
	 */
	function successHandler( user ) {
		// @var {Promise} These track the state for all actions (edit and protect)
		//  on every page. They are resolved as soon as a page is processed, with or
		//  without a failure. This way the final $.when will resolve after all promises
		//  have been processed, and not after the first rejection.
		var talkDone, userDone;

		Main.blocked++;

		if ( FormData.talkMessage !== "" ) {
			talkDone = Main.editTalk( user );
		} else if ( FormData.protectTalk ) {
			talkDone = Main.protectTalk( user );
		} else {
			talkDone = $.when();
		}

		if ( FormData.upMessage !== "" ) {
			userDone = Main.editUP( user );
		} else if ( FormData.protectUser ) {
			userDone = Main.protectUP( user );
		} else {
			userDone = $.when();
		}

		return $.when( talkDone, userDone );
	}

	/**
	 * Main form processing routine, called on form submit
	 */
	function doMassBlock() {
		var users = $( "#wpMassBlockUsers textarea" ).val().split( "\n" );

		users = users
			// First trim everything
			.map( function( s ) {
				return s.trim();
			} )
			// Then remove blanks and duplicates
			.filter( function( el, index, me ) {
				return el !== '' && me.indexOf( el ) === index;
			} );

		if ( users.length === 0 ) {
			// Easy
			return;
		}
		submitBtn.setDisabled( true );

		FormData.init();

		// Array of Promises, one for each user. Each one is resolved after the user
		//  is processed, even in case of failure.
		var deferreds = [];

		users.forEach( function( user ) {
			deferreds.push(
				doBlock( user )
					.then(
						function() {
							return successHandler( user );
						},
						function( e ) {
							ErrorHandler.add( e, user, "block" );
							// Return so that this counts as resolved and won't leave
							// other promises unresolved.
							return $.when();
						}
					)
			);
		} );

		$.when.apply( $, deferreds ).always( doPostBlockActions );
	}

	/**
	 * Executed after all users have been processed.
	 */
	function doPostBlockActions() {
		var errors = ErrorHandler.errors;
		if ( Object.keys( errors ).length > 0 ) {
			var linkedList = '';

			for ( var user in errors ) {
				var codes = ErrorHandler.getCodesForUser( user );
				linkedList +=
					"<li><a href=\"" + mw.config.get( 'wgScript' ) + "?title=Special:Contributions/" + encodeURIComponent( user ) + "\">" +
					user + "</a>: " + codes.join( "; " ) + "</li>";
			}
			$( "#wpMassBlockFailedContainer" ).html(
				'<h3>' + msg( 'failed-actions' ) + '</h3><ul>' + linkedList + '</ul><hr />'
			);
		}

		OO.ui.alert( Main.getAlertText() );
	}

	/**
	 * Perform a single block via API
	 *
	 * @param {string} user The user to block
	 * @return {Promise}
	 */
	function doBlock( user ) {
		return mwapi.postWithToken( "csrf", {
			action: 'block',
			allowusertalk: !FormData.talkpageBlocked,
			autoblock: FormData.autoblock,
			nocreate: FormData.nocreate,
			expiry: FormData.expiry === "" ? "indefinite" : FormData.expiry,
			anononly: FormData.anononly,
			noemail: FormData.noEmail,
			reblock: FormData.reblock,
			reason: FormData.dropdownReason === "other" ? FormData.otherReason : FormData.dropdownReason + ( FormData.otherReason ? ": " + FormData.otherReason : "" ),
			user: user
		} );
	}

	/**
	 * Edit the given page.
	 *
	 * @param {string} title The title of the page
	 * @param {string} text The text to add
	 * @param {string} summary The summary to use
	 * @param {bool} append Whether to append the text or replace the whole page content
	 * @return {Promise}
	 */
	function doEditPage( title, text, summary, append ) {
		var appendText = append || false,
			params = {
				action: 'edit',
				title: title,
				summary: summary,
				watchlist: 'nochange'
			};
		if ( appendText ) {
			params.appendtext = text;
		} else {
			params.text = text;
		}
		return mwapi.postWithEditToken( params );
	}

	/**
	 * Protect the given page
	 *
	 * @param {string} title The page to protect
	 * @param {string} protections As accepted by the Protect API module
	 * @return {Promise}
	 */
	function doProtectPage( title, protections ) {
		return mwapi.postWithToken( 'csrf', {
			action: 'protect',
			title: title,
			protections: protections,
			reason: FormData.protectReason,
			watchlist: 'nochange'
		} );
	}

	/**
	 * Get a localised messages, or the default one as fallback.
	 *
	 * @param {string} msg The key of the message to get
	 * @return {string}
	 */
	function msg( msg ) {
		return mw.messages.get( 'massblock-' + msg );
	}

	/**
	 * Build the form
	 */
	function massblockform() {
		var reasons = mw.msg( 'Ipbreason-dropdown' ).split( '**' ),
			// OOUI elements
			talkTextField, userTextField, talkProtectCb, talkSummaryField,
			userSummaryField, userProtectCb, protectReasonField;

		$( "h1" ).first().html( msg( 'page-title' ) );
		document.title = msg( 'document-title' );

		var form = new OO.ui.FormLayout( {
			id: 'wpMassBlock'
		} );

		talkTextField = new OO.ui.MultilineTextInputWidget( {
			rows: 10
		} );

		userTextField = new OO.ui.MultilineTextInputWidget( {
			rows: 10
		} );

		var mainField = new OO.ui.FieldsetLayout( {
			label: msg( 'blockusers' ),
			items: [
				new OO.ui.MultilineTextInputWidget( {
					rows: 10,
					id: 'wpMassBlockUsers'
				} )
			]
		} );

		var reasonOpts = [ {
				optgroup: msg( 'other-reason' )
			},
			{
				data: 'other',
				label: msg( 'other-reason' )
			},
			{
				optgroup: msg( 'common-reasons' )
			}
		];
		for ( var i = 1, j = reasons.length; i < j; i++ ) {
			reasonOpts.push( {
				data: reasons[ i ],
				label: reasons[ i ]
			} );
		}

		var expiryField = new OO.ui.TextInputWidget( {
			maxLength: 255,
			id: 'wpMassBlockExpiry'
		} );

		var otherReasonField = new OO.ui.TextInputWidget( {
			maxLength: 255,
			id: 'wpMassBlockReason'
		} );

		var reasonsDropdown = new OO.ui.DropdownInputWidget( {
			options: reasonOpts,
			id: 'wpMassBlockReasons'
		} ).on( 'change', function() {
			var reason = reasonsDropdown.getValue(),
				maxlength = ( reason === "other" ? 255 : ( 255 - ': '.length ) - reason.length );

			$( '#wpMassBlockReason input' ).attr( "maxlength", maxlength );
		} );


		var blockOptsArray = [
			new OO.ui.FieldLayout(
				reasonsDropdown, {
					label: msg( 'common-reasons' )
				}
			),

			new OO.ui.FieldLayout(
				otherReasonField, {
					label: msg( 'extra-reason' )
				}
			),

			new OO.ui.FieldLayout( expiryField, {
				label: msg( 'exptime' )
			} ),

			new OO.ui.FieldLayout(
				new OO.ui.CheckboxInputWidget( {
					id: 'wpMassBlockAnononly',
					selected: true
				} ), {
					label: msg( 'anononly' )
				}
			),
			new OO.ui.FieldLayout(
				new OO.ui.CheckboxInputWidget( {
					id: 'wpMassBlockAutoblock',
					selected: true
				} ), {
					label: msg( 'autoblock' )
				}
			),
			new OO.ui.FieldLayout(
				new OO.ui.CheckboxInputWidget( {
					id: 'wpMassBlockNocreate',
					selected: true
				} ), {
					label: msg( 'nocreate' )
				}
			),
			new OO.ui.FieldLayout(
				new OO.ui.CheckboxInputWidget( {
					id: 'wpMassBlockEmail'
				} ), {
					label: msg( 'noemail' )
				}
			)
		];

		if ( blockAllowsTalkEdit ) {
			blockOptsArray.push( new OO.ui.FieldLayout(
				new OO.ui.CheckboxInputWidget( {
					id: 'wpMassBlockTalkpage'
				} ), {
					label: msg( 'notalk' )
				}
			) );
		}

		blockOptsArray.push( new OO.ui.FieldLayout(
			new OO.ui.CheckboxInputWidget( {
				id: 'wpMassBlockReblock'
			} ), {
				label: msg( 'override' )
			}
		) );

		var blockOpts = new OO.ui.FieldsetLayout( {
			label: msg( 'block-options-label' ),
			items: blockOptsArray
		} );


		talkSummaryField =
			new OO.ui.TextInputWidget( {
				maxLength: 255,
				id: 'wpMassBlockSummaryTalk',
				value: msg( 'summary-default' ),
				// The text is empty by default
				disabled: true
			} );

		talkTextField.on( 'change', function() {
			talkSummaryField.setDisabled( talkTextField.getValue().trim() === '' );
		} );

		userSummaryField =
			new OO.ui.TextInputWidget( {
				maxLength: 255,
				id: 'wpMassBlockSummaryUser',
				value: msg( 'summary-default' ),
				// The text is empty by default
				disabled: true
			} );

		var toggleUserTextField = function() {
			var disabled = userTextField.getValue().trim() === '' ||
				!isInfinity( $( '#wpMassBlockExpiry input' ).val().trim() );
			userSummaryField.setDisabled( disabled );
		};

		userTextField.on( 'change', toggleUserTextField );

		talkProtectCb =
			new OO.ui.CheckboxInputWidget( {
				id: 'wpMassBlockProtectTalk'
			} );

		userProtectCb =
			new OO.ui.CheckboxInputWidget( {
				id: 'wpMassBlockProtectUser'
			} );

		protectReasonField =
			new OO.ui.TextInputWidget( {
				maxLength: 255,
				id: 'wpMassBlockProtectReason',
				value: msg( 'protect-reason-default' ),
				disabled: true
			} );

		/**
		 * Toggle protection fields
		 */
		var toggleProtectionFields = function() {
			if (
				!( talkProtectCb.isSelected() || userProtectCb.isSelected() ) ||
				!isInfinity( $( '#wpMassBlockExpiry input' ).val().trim() )
			) {
				protectReasonField.setDisabled( true );
			} else {
				protectReasonField.setDisabled( false );
			}
		};

		talkProtectCb.on( 'change', toggleProtectionFields );
		userProtectCb.on( 'change', toggleProtectionFields );

		var furtherOpts = new OO.ui.FieldsetLayout( {
			label: msg( 'further-options-label' ),
			items: [
				new OO.ui.HorizontalLayout( {
					items: [
						new OO.ui.FieldLayout(
							talkTextField,
							{
								label: msg( 'talkmsg' ),
								align: 'top',
								id: 'wpMassBlockMessage',
								classes: [ 'massblock-horiz-element' ]
							}
						),
						new OO.ui.FieldLayout(
							userTextField,
							{
								label: msg( 'upmsg' ),
								align: 'top',
								id: 'wpMassBlockTag',
								classes: [ 'massblock-horiz-element' ]
							}
						)
					]
				} ),
				new OO.ui.FieldLayout( talkSummaryField, {
					label: msg( 'talksummary' )
				} ),
				new OO.ui.FieldLayout( userSummaryField, {
					label: msg( 'upsummary' )
				} ),
				new OO.ui.FieldLayout( talkProtectCb, {
					label: msg( 'talkprotect' )
				} ),
				new OO.ui.FieldLayout( userProtectCb, {
					label: msg( 'upprotect' )
				} ),
				new OO.ui.FieldLayout( protectReasonField, {
					label: msg( 'protect-reason-label' )
				} )
			]
		} );

		expiryField.on( 'change', function() {
			// Several fields cannot be used if the expiry isn't infinite
			var enable = isInfinity( $( '#wpMassBlockExpiry input' ).val().trim() ),
				// These are OOUI elements
				disableEls = [
					userTextField,
					talkProtectCb,
					userProtectCb
				];

			for ( var el in disableEls ) {
				disableEls[ el ].setDisabled( !enable );
			}

			toggleProtectionFields();
			toggleUserTextField();
		} );

		submitBtn = new OO.ui.ButtonInputWidget( {
				label: msg( 'submit-text' ),
				title: msg( 'submit-text' ),
				id: 'wpMassBlockSubmit',
				flags: [
					'primary',
					'progressive'
				]
			} )
			.on( 'click', doMassBlock );

		form.addItems( [ mainField, blockOpts, furtherOpts, new OO.ui.FieldLayout( submitBtn ) ] );


		var bodyContentID = ( mw.config.get( 'skin' ) === "cologneblue" ? "#article" : "#bodyContent" );
		$( bodyContentID ).html( '<div style="font-size:150%"><u>' + msg( 'abuse-disclaimer' ) + '</u></div><br /><hr /><div id="wpMassBlockFailedContainer"></div>' );
		$( bodyContentID ).append( form.$element );
	}

	/**
	 * Utility function to tell if an expiry is infinite
	 *
	 * @param {string} expiry
	 * @return {bool}
	 */
	function isInfinity( expiry ) {
		return /^(indefinite|infinite|infinity|never|)$/i.test( expiry );
	}

	$( function() {
		if ( mw.config.get( "wgNamespaceNumber" ) === -1 &&
			( mw.config.get( "wgTitle" ) === "Massblock" || mw.config.get( "wgTitle" ) === "MassBlock" ) &&
			( /sysop/ ).test( mw.config.get( "wgUserGroups" ) )
		) {
			var style ='.massblock-horiz-element{ width: 40%; }';
			mw.util.addCSS( style );
			mw.loader.using( [ 'mediawiki.jqueryMsg', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows' ], $.ready )
				.done( function loadMsg() {
					mwapi.loadMessagesIfMissing( [ 'Ipbreason-dropdown' ] )
						.done( massblockform )
						.fail( function ( e ) {
							mw.log.error( msg( 'init-failure' ) + ' ' + e );
						} );
				} )
				.fail( function ( e ) {
					mw.log.error( msg( 'init-failure' ) + ' '  + e );
				} );
		} else {
			mw.util.addPortletLink( 'p-tb', mw.util.getUrl( 'Special:Massblock' ), msg( 'toolbar-text' ) );
		}
	} );
}( mediaWiki, jQuery ) );