Utente:Ricordisamoa/PDC.js

Da Wikipedia, l'enciclopedia libera.
Versione del 9 ott 2018 alle 00:47 di Valerio Bozzolan (discussione | contributi) (TODO: aggiornare le API revisions per trasmettere rvslots:main introdotto da MediaWiki 1.32)
Vai alla navigazione Vai alla ricerca

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.

// <nowiki>
// TODO: aggiornare le API revisions per trasmettere rvslots:main introdotto da MediaWiki 1.32
/* jshint browser:true, devel:true */
/* global mediaWiki, jQuery, OO */
( function ( mw, $ ) {
	'use strict';

	var namespaces, AssessmentsHistoryEditor, VoteCounter, PdcAgent, PDC, VoteButtons,
		ProgressHandler,
		AllCategoriesLookupWidget, PdcDialog, PublicButtons, SysopButtons;

	namespaces = mw.config.get( 'wgFormattedNamespaces' );

	/**
	 * @class AssessmentsHistoryEditor
	 *
	 * @constructor
	 */
	AssessmentsHistoryEditor = function () {
		this.titoloTemplate = 'Cronologia valutazioni';
		this.regex = /(\{\{\s*(?:[Cc]ronologia[_ ]valutazioni|[Pp]rocedure|[Vv]alutazioni)\s*)([\S\s]*?)\}\}/;
	};

	/**
	 * Restituisce dei parametri numerati.
	 *
	 * @private
	 * @param {Object} parametri - I parametri non numerati
	 * @param {number} numero - Il numero da aggiungere ai nomi dei parametri
	 * @return {string}
	 */
	AssessmentsHistoryEditor.prototype.ottieniParametriNumerati = function ( parametri, numero ) {
		return $.map( parametri, function ( value, key ) {
				return key + numero + '=' + value;
			} ).join( '|' );
	};

	/**
	 * Restituisce il massimo numero utilizzato da una serie di parametri numerati.
	 *
	 * @private
	 * @param {string[]} parametri
	 * @return {number}
	 */
	AssessmentsHistoryEditor.prototype.ottieniMassimoNumeroParametri = function ( parametri ) {
		var max, paramIndex, m, num;

		max = 0;
		for ( paramIndex = 0; paramIndex < parametri.length; paramIndex++ ) {
			m = parametri[ paramIndex ].match( /^\s*[A-Za-z]+(\d+)\s*\=/ );
			if ( m !== null ) {
				num = parseInt( m[ 1 ], 10 );
				if ( num.toString() === m[ 1 ] && num > max ) {
					max = num;
				}
			}
		}

		return max;
	};

	/**
	 * Aggiunge dei parametri a un template Cronologia valutazioni, eventualmente creandolo.
	 *
	 * @param {string} testo - Il contenuto della pagina
	 * @param {Object} parametri - I parametri da aggiungere al template, non numerati
	 * @return {string}
	 */
	AssessmentsHistoryEditor.prototype.aggiungiParametri = function ( testo, parametri ) {
		var match, newText, start, max;

		match = this.regex.exec( testo );

		if ( match === null ) {
			// TODO: inserire sotto ai template {{Progetti interessati}} e {{Tradotto da}} qualora presenti
			newText =
				'{{' + this.titoloTemplate + '\n' +
				'|' + this.ottieniParametriNumerati( parametri, 1 ) + '\n' +
				'}}' +
				( testo !== '' && testo[ 0 ] !== '\n' ? '\n' : '' ) + // A capo solo se necessario
				testo;
		} else {
			start = match.index + match[ 1 ].length + match[ 2 ].length;
			newText = testo.slice( 0, start );
			if ( newText[ newText.length - 1 ] !== '\n' ) {
				newText += '\n';
			}
			max = this.ottieniMassimoNumeroParametri( match[ 2 ].split( '|' ) );
			newText += '|' + this.ottieniParametriNumerati( parametri, max + 1 ) + '\n';
			newText += testo.slice( start );
		}

		return newText;
	};

	/**
	 * @class VoteCounter
	 *
	 * @constructor
	 * @param {Object} config - Opzioni di configurazione
	 * @cfg {jQuery} $content
	 * @cfg {string} pageName
	 * @cfg {string[]} voteNullItems
	 * @cfg {Object} voteSections
	 */
	VoteCounter = function ( config ) {
		this.$content = config.$content;
		this.pageName = config.pageName;
		this.voteNullItems = config.voteNullItems;
		this.voteSections = config.voteSections;
	};

	/**
	 * Restituisce il numero della sezione a cui si riferisce un collegamento di modifica sezione.
	 *
	 * @private
	 * @param {HTMLAnchorElement} link - Il collegamento di modifica sezione
	 * @return {string|undefined}
	 */
	VoteCounter.prototype.mapEditSectionLinks = function ( link ) {
		var linkUri;

		// Skip every URI that mw.Uri cannot parse
		try {
			linkUri = new mw.Uri( link.href );
		} catch ( e ) {
			return;
		}

		if (
			linkUri.host === window.location.hostname &&
			linkUri.query !== undefined &&
			linkUri.query.section !== undefined &&
			linkUri.query.title === this.pageName &&
			linkUri.query.action === 'edit'
		) {
			return linkUri.query.section;
		}
	};

	/**
	 * Restituisce le sezioni di votazione della procedura di cancellazione.
	 *
	 * @return {Object}
	 */
	VoteCounter.prototype.getSections = function () {
		var self = this,
			sections = {};

		self.$content.find( '.mw-headline' ).each( function () {
			var sectionNums,
				$this = $( this ),
				sectionId = $this.attr( 'id' ),
				voteIcon = self.voteSections[ sectionId ];

			if ( voteIcon === undefined ) {
				return;
			}

			sectionNums = $this.next( '.mw-editsection' ).find( 'a[href]' )
			.map( function ( i, link ) {
				return self.mapEditSectionLinks( link );
			} )
			.get();

			if ( sectionNums.length !== 1 ) {
				// Absent or non-unique 'edit' link
				return;
			}

			sections[ sectionId ] = {
				icon: voteIcon,
				num: sectionNums[ 0 ],
				votes: $this.parent().next( 'ol' ).find( 'li' ).filter( function () {
						return self.voteNullItems.indexOf( this.textContent ) === -1;
					} ).length
			};
		} );

		return sections;
	};

	/**
	 * @class PdcAgent
	 *
	 * @constructor
	 * @param {Object} config - Configuration options
	 * @cfg {mw.Api} api - MediaWiki API instance
	 * @cfg {AssessmentsHistoryEditor} assessmentsHistoryEditor - Assessments history editor
	 * @cfg {mw.Map} i18n - Language translation
	 * @cfg {Object} l10n - Site localization
	 * @cfg {Object} namespaces - Site namespaces
	 */
	PdcAgent = function ( config ) {
		this.api = config.api;
		this.assessmentsHistoryEditor = config.assessmentsHistoryEditor;
		this.i18n = config.i18n;
		this.config = config.l10n;
		this.l10nMsgs = new mw.Map( this.config );
		this.namespaces = config.namespaces;
	};

	PdcAgent.prototype.msg = function ( nome ) {
		var params = Array.prototype.slice.call( arguments, 1 );
		return new mw.Message( this.i18n, nome, params ).text();
	};

	PdcAgent.prototype.l10nMsg = function ( nome ) {
		var params = Array.prototype.slice.call( arguments, 1 );
		return new mw.Message( this.l10nMsgs, nome, params ).text();
	};

	/**
	 * Ottiene un messaggio di errore.
	 *
	 * @param {string} messaggio - Il codice corrispondente al messaggio di errore da visualizzare
	 * @param {Object|string} [dati] - Eventuali dettagli sull'errore
	 * @return {string}
	 */
	PdcAgent.prototype.errorMsg = function ( messaggio, dati ) {
		messaggio = this.msg( messaggio );
		if ( dati ) {
			if ( dati.error && dati.error.info ) {
				dati = dati.error.info;
			}
			messaggio = this.msg( 'error-msg', messaggio, dati );
		}
		return messaggio;
	};

	/**
	 * Restituisce il codice HTML per un link alla pagina di un dato utente.
	 *
	 * @param {string} nome - Il nome dell'utente
	 * @return {string}
	 */
	PdcAgent.prototype.linkUtente = function ( nome ) {
		return $( '<a>' )
			.attr( 'href', mw.util.getUrl( this.namespaces[ 2 ] + ':' + nome ) )
			.text( nome )
			.get( 0 ).outerHTML;
	};

	/**
	 * Ottiene il titolo della pagina relativa a una procedura di cancellazione.
	 *
	 * @param {string} procedura - La procedura di cancellazione
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.ottieniTitoloPagina = function ( procedura ) {
		var self = this;
		return self.ottieniUltimaVersionePagina( procedura, 'content' )
		.then( function ( rev ) {
			var m = ( rev.content || '' ).match( self.config.nominationTemplateRegex );
			if ( m ) {
				return m[ 1 ];
			}
			return $.Deferred().reject();
		} );
	};

	/**
	 * Verifica i requisiti di apertura delle procedure da parte di un utente.
	 * vedi [[Wikipedia:Requisiti di voto#Requisiti relativi alle votazioni sulle pagine]]
	 *
	 * @param {string} [utente] - Il nome dell'utente del quale verificare i contributi
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.verificaRequisiti = function ( utente ) {
		var self = this;

		if ( utente === undefined ) {
			utente = mw.config.get( 'wgUserName' );
		}
		if ( mw.config.get( 'wgDBname' ) === 'testwiki' ) {
			return $.Deferred().resolve( true );
		}

		return self.api.get( {
			action: 'query',
			format: 'json',
			formatversion: 2,
			list: 'users|usercontribs',
			usprop: 'editcount',
			ususers: utente,
			ucuser: utente,
			ucprop: 'timestamp',
			ucdir: 'newer',
			uclimit: 1
		} )
		.then( function ( data ) {
			var user, uc;
			if ( !data.query || !data.query.users || data.query.users.length !== 1 ) {
				return false;
			}
			user = data.query.users[ 0 ];
			if ( !user.name || user.name !== utente || !user.editcount || user.editcount < self.config.minEdits || !data.query.usercontribs ) {
				return false;
			}
			uc = data.query.usercontribs;
			if ( uc.length !== 1 || !uc[ 0 ].user || !uc[ 0 ].timestamp || ( new Date() - new Date( uc[ 0 ].timestamp ) ) / 86400000 < self.config.minDaysOld ) {
				return false;
			}
			return true;
		} );
	};

	/**
	 * Ottiene un array con tutte le revisioni della pagina,
	 * come oggetti contenenti l'autore.
	 *
	 * @param {string} titolo - Il titolo della pagina
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.ottieniContributori = function ( titolo ) {
		var self = this,
		params = {
			action: 'query',
			format: 'json',
			formatversion: 2,
			prop: 'revisions',
			titles: titolo,
			rvprop: 'user',
			rvlimit: 'max'
		},
		revs = [],
		getRevsRecursive = function ( continua ) {
			return self.api.get(
				$.extend( {}, params, continua || {} )
			)
			.then( function ( data ) {
				var pages = data.query.pages;
				if ( pages.length === 1 && pages[ 0 ].revisions ) {
					// Aggiunge tutte le revisioni con la proprietà "user"
					revs = revs.concat(
						$.grep( pages[ 0 ].revisions, function ( e ) {
							return e.user !== undefined;
						} )
					);
				}
				if ( data[ 'continue' ] ) {
					// Ci sono ancora revisioni, si continua
					return getRevsRecursive( data[ 'continue' ] );
				} else {
					// Finito!
					return revs;
				}
			} );
		};
		return getRevsRecursive();
	};

	/**
	 * Ottiene il nome dell'utente registrato con più contributi da una lista di revisioni.
	 *
	 * @param {Object[]} contributori - Le revisioni della pagina
	 * @return {string|null}
	 */
	PdcAgent.prototype.maggiorContributore = function ( contributori ) {
		var ordinati, maggiore,
			utenti = {};
		$.each( contributori, function ( i, rv ) {
			if ( rv.anon === undefined ) {
				if ( utenti[ rv.user ] ) {
					utenti[ rv.user ] = utenti[ rv.user ] + 1;
				} else {
					utenti[ rv.user ] = 1;
				}
			}
		} );
		ordinati = Object.keys( utenti ).sort( function ( a, b ) {
			return utenti[ b ] - utenti[ a ];
		} );
		maggiore = ordinati[ 0 ] || null;
		if ( maggiore !== null ) {
			$.each( utenti, function ( nome, contributi ) {
				if ( nome !== maggiore && contributi === utenti[ maggiore ] ) {
					// Pari merito
					maggiore = null;
					return false;
				}
			} );
		}
		return maggiore;
	};

	/**
	 * Aggiunge il template {{Cancellazione}} all'inizio di una pagina.
	 *
	 * @param {string} titolo - Il titolo della pagina proposta per la cancellazione
	 * @param {integer} numero - Il numero della procedura di cancellazione
	 * @param {string[]} argomenti - Gli eventuali argomenti al quale la pagina è relativa
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.aggiungiAvvisoVoce = function ( titolo, numero, argomenti ) {
		argomenti = $.map( [ '', '2' ], function ( suf, i ) {
			if ( argomenti.length > i ) {
				return '|arg' + suf + '=' + argomenti[ i ];
			}
		} )
		.join( '' );
		return this.api.postWithToken( 'csrf', {
			action: 'edit',
			format: 'json',
			formatversion: 2,
			title: titolo,
			summary: this.l10nMsg( 'baseSummary', this.config.nominationNoticeSummary ),
			prependtext: '<noinclude>{{' + this.config.nominationNoticeTemplate + argomenti + '}}</noinclude>\n',
			nocreate: 1
		} );
	};

	/**
	 * Rimuove il template {{Cancellazione}} da una pagina.
	 *
	 * @param {string} titolo - Il titolo della pagina dalla quale rimuovere l'avviso
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.rimuoviAvvisoVoce = function ( titolo ) {
		var self = this;

		return self.ottieniUltimaVersionePagina( titolo, 'content|timestamp' )
			.then( function ( rev ) {
				var newText = rev.content.replace(
					self.config.removeNominationNoticeReplace,
					self.config.removeNominationNoticeReplaceWith
				);
				if ( newText === rev.content ) {
					// sostituzione fallita
					return $.Deferred().reject();
				}

				return self.api.postWithToken( 'csrf', {
					action: 'edit',
					format: 'json',
					formatversion: 2,
					title: titolo,
					summary: self.l10nMsg( 'baseSummary', self.config.removeNominationNoticeSummary ),
					text: newText,
					basetimestamp: rev.timestamp,
					nocreate: 1
				} );
			} );
	};

	/**
	 * Restituisce il titolo della pagina di discussione corrispondente a una pagina.
	 *
	 * @param {string} titolo - Il titolo della pagina
	 * @return {string|null}
	 */
	PdcAgent.prototype.ottieniTitoloDiscussione = function ( titolo ) {
		var namespaceId;

		titolo = mw.Title.newFromText( titolo );
		if ( titolo === null ) {
			return null;
		}

		namespaceId = titolo.getNamespaceId();
		if ( namespaceId % 2 !== 0 ) {
			return null;
		}

		titolo = mw.Title.makeTitle( namespaceId + 1, titolo.getMainText() );
		if ( titolo === null ) {
			return null;
		}

		return titolo.getPrefixedText();
	};

	/**
	 * Aggiorna il template {{Cronologia valutazioni}} nella discussione di una pagina.
	 *
	 * @param {string} titolo - Il titolo della pagina valutata
	 * @param {Object} parametri - I parametri da aggiungere al template, non numerati
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.aggiornaCronologiaValutazioni = function ( titolo, parametri ) {
		var fallback,
			self = this;

		titolo = self.ottieniTitoloDiscussione( titolo );
		if ( titolo === null ) {
			return $.Deferred().reject();
		}

		parametri = $.extend( {}, self.config.assessmentsHistoryDefaultParams, parametri );

		fallback = {
			content: ''
		};

		return self.ottieniUltimaVersionePagina( titolo, 'content|timestamp', fallback )
			.then( function ( rev ) {
				var newText, params;

				newText = self.assessmentsHistoryEditor.aggiungiParametri( rev.content, parametri );
				if ( newText === undefined || newText === rev.content ) {
					// modifica non riuscita
					return $.Deferred().reject();
				}

				params = {
					action: 'edit',
					format: 'json',
					formatversion: 2,
					title: titolo,
					summary: self.l10nMsg( 'baseSummary', self.config.assessmentsHistoryUpdateSummary ),
					text: newText
				};

				if ( rev === fallback ) {
					// Pagina nuova
					params.createonly = true;
				} else {
					// Pagina esistente
					params.basetimestamp = rev.timestamp;
					params.nocreate = true;
				}

				return self.api.postWithToken( 'csrf', params );
			} );
	};

	/**
	 * Aggiunge il template {{Cancellazione}} in una nuova sezione di una o più pagine di discussione.
	 *
	 * @param {string} titolo - Il titolo della pagina da proporre per la cancellazione
	 * @param {number} numero - Il numero della procedura di cancellazione
	 * @param {string[]} discussioni - I titoli delle pagine di discussione degli utenti o dei progetti da avvisare
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.notificaProcedura = function ( titolo, numero, discussioni ) {
		var self = this,
		deferreds = [],
		sectionTitle = self.l10nMsg( 'notificationNewSectionTitle', titolo ),
		args = ( numero === 1 ? '' : ( '|' + numero ) ),
		sectionContent = '{{' + self.config.notificationNewSectionTemplate + '|' + titolo + args + '}}--~~~~';
		$.each( discussioni, function ( i, discussione ) {
			deferreds.push(
				self.api.postWithToken( 'csrf', {
					action: 'edit',
					format: 'json',
					formatversion: 2,
					title: discussione,
					section: 'new',
					summary: sectionTitle,
					text: sectionContent
				} )
				.done( function () {
					console.info( self.msg( 'notify-done', discussione ) );
				} )
			);
		} );
		return $.when.apply( $, deferreds );
	};

	/**
	 * Controlla l'esistenza di una pagina.
	 *
	 * @param {string} titolo - Il titolo della pagina
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.controllaEsistenzaPagina = function ( titolo ) {
		return this.api.get( {
			action: 'query',
			format: 'json',
			formatversion: 2,
			titles: titolo
		} )
		.then( function ( data ) {
			if ( data && data.query && data.query.pages && data.query.pages.length === 1 ) {
				return data.query.pages[ 0 ].missing !== true;
			}
			return $.Deferred().reject();
		} );
	};

	/**
	 * Ottiene il primo numero disponibile per una procedura di cancellazione.
	 *
	 * @param {string} titolo - Il titolo della pagina da proporre per la cancellazione
	 * @param {number} [numero=1] - Il numero da cui iniziare
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.ottieniNumeroDisponibile = function ( titolo, numero ) {
		var procedura,
			self = this;
		if ( numero === undefined ) {
			numero = 1;
		}
		procedura = self.config.base + '/' + titolo + ( numero !== 1 ? '/' + numero : '' );
		return self.controllaEsistenzaPagina( procedura )
			.then( function ( exists ) {
				if ( exists ) {
					return self.ottieniNumeroDisponibile( titolo, numero + 1 );
				}
				return $.Deferred().resolve( procedura, numero );
			} );
	};

	/**
	 * Crea una procedura di cancellazione dal titolo della pagina da cancellare.
	 *
	 * @param {string} titolo - Il titolo della pagina da proporre per la cancellazione
	 * @param {string} motivazione - La motivazione per la quale proporre la cancellazione della pagina
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.creaProceduraDaPagina = function ( titolo, motivazione ) {
		var self = this;

		return self.ottieniNumeroDisponibile( titolo )
			.then( function ( procedura, numero ) {
				return self.creaProcedura( procedura, motivazione )
					.then( function () {
						return $.Deferred().resolve( procedura, numero );
					} );
			} );
	};

	/**
	 * Crea una procedura di cancellazione dal titolo della procedura.
	 *
	 * @param {string} procedura - Il titolo della procedura di cancellazione
	 * @param {string} motivazione - La motivazione per la quale proporre la cancellazione della pagina
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.creaProcedura = function ( procedura, motivazione ) {
		return this.api.postWithToken( 'csrf', {
			action: 'edit',
			format: 'json',
			formatversion: 2,
			title: procedura,
			summary: this.l10nMsg( 'baseSummary', this.config.newNominationSummary ),
			text: '{{' + this.config.newNominationTemplate + '}}\n\n' + motivazione + ' --~~~~',
			createonly: 1
		} );
	};

	/**
	 * Ottiene la versione più recente di una pagina.
	 *
	 * @param {string} titolo - Il titolo della pagina
	 * @param {string|string[]} rvprop - Le proprietà da ottenere
	 * @param {Mixed} [fallback] - Il valore da utilizzare in caso di pagina mancante
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.ottieniUltimaVersionePagina = function ( titolo, rvprop, fallback ) {
		return this.api.get( {
			action: 'query',
			format: 'json',
			formatversion: 2,
			titles: titolo,
			prop: 'revisions',
			rvprop: rvprop,
			rvlimit: 1
		} )
		.then( function ( data ) {
			var deferred = $.Deferred();
			if (
				data &&
				data.query &&
				data.query.pages &&
				data.query.pages.length === 1
			) {
				if ( data.query.pages[ 0 ].missing === true && fallback !== undefined ) {
					return deferred.resolve( fallback );
				}
				if (
					data.query.pages[ 0 ].revisions &&
					data.query.pages[ 0 ].revisions.length === 1
				) {
					return deferred.resolve( data.query.pages[ 0 ].revisions[ 0 ] );
				}
			}
			return deferred.reject();
		} );
	};

	/**
	 * Annulla una procedura di cancellazione.
	 *
	 * @param {string} titolo - Il titolo della procedura da annullare
	 * @param {string} motivazione - La motivazione per la quale la procedura va annullata
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.annullaProcedura = function ( titolo, motivazione ) {
		var self = this;

		return self.ottieniUltimaVersionePagina( titolo, 'content|timestamp' )
			.then( function ( rev ) {
				var newText = rev.content.replace(
					self.config.cancelledReplace,
					self.config.cancelledReplaceWith
				);
				if ( newText === rev.content ) {
					// sostituzione fallita
					return $.Deferred().reject();
				}
				newText += '\n\n{{' + self.config.cancelledTemplate + '}} ' + motivazione.trim() + ' --~~~~';

				return self.api.postWithToken( 'csrf', {
					action: 'edit',
					format: 'json',
					formatversion: 2,
					title: titolo,
					summary: self.l10nMsg( 'baseSummary', self.config.cancelledSummary ),
					text: newText,
					basetimestamp: rev.timestamp,
					nocreate: 1
				} );
			} );
	};

	/**
	 * Passa alla modalità consensuale.
	 *
	 * @param {string} titolo - Il titolo della procedura di cancellazione (già esistente)
	 * @param {string} motivazione - La motivazione per la quale l'utente intende opporsi alla cancellazione tacita della pagina
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.consensuale = function ( titolo, motivazione ) {
		return this.api.postWithToken( 'csrf', {
			action: 'edit',
			format: 'json',
			formatversion: 2,
			title: titolo,
			summary: this.l10nMsg( 'baseSummary', this.config.consensualeStartSummary ),
			appendtext: '\n\n{{' + this.config.consensualeStartTemplate + '}}\n\n' +
				motivazione.trim() + ' --~~~~',
			nocreate: 1
		} );
	};

	/**
	 * Proroga una procedura di cancellazione
	 *
	 * @param {string} titolo - Il titolo della procedura di cancellazione (già esistente)
	 * @param {integer} giorni - Il numero di giorni per i quali prorogare la procedura
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.prorogaProcedura = function ( titolo, giorni ) {
		var giorniArg;
		if ( giorni === this.config.deferDefaultDays ) {
			giorniArg = '';
		} else {
			giorniArg = '|' + giorni;
		}
		return this.api.postWithToken( 'csrf', {
			action: 'edit',
			format: 'json',
			formatversion: 2,
			title: titolo,
			summary: this.l10nMsg( 'baseSummary', this.l10nMsg( 'deferStartSummary', giorni ) ),
			appendtext: '\n\n{{' + this.config.deferStartTemplate + giorniArg + '}}',
			nocreate: 1
		} );
	};

	/**
	 * Per gli amministratori: passa alla modalità votazione.
	 *
	 * @param {string} titolo - Il titolo della procedura di cancellazione (già esistente)
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.avviaVotazione = function ( titolo ) {
		return this.api.postWithToken( 'csrf', {
			action: 'edit',
			format: 'json',
			formatversion: 2,
			title: titolo,
			summary: this.l10nMsg( 'baseSummary', this.config.voteStartSummary ),
			appendtext: '\n\n{{' + this.config.voteStartTemplate + '}}',
			nocreate: 1
		} );
	};

	/**
	 * Vota a favore o contro la cancellazione di una pagina.
	 *
	 * @param {string} titolo - Il titolo della procedura di cancellazione (già esistente)
	 * @param {integer|string} numeroSezione - Il numero della sezione in cui votare
	 * @param {string} motivazione - La motivazione (facoltativa) per il voto espresso
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.vota = function ( titolo, numeroSezione, motivazione ) {
		motivazione = motivazione.trim();
		return this.api.postWithToken( 'csrf', {
			action: 'edit',
			format: 'json',
			formatversion: 2,
			title: titolo,
			section: numeroSezione,
			summary: this.l10nMsg( 'baseSummary', this.config.voteSummary ),
			appendtext: '\n# ' + motivazione + ( motivazione !== '' ? ' ' : '' ) + '--~~~~',
			nocreate: 1
		} );
	};

	/**
	 * Controlla se una pagina contiene già un avviso di cancellazione.
	 *
	 * @param {string} titolo - Il titolo della pagina da controllare
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.inCancellazione = function ( titolo ) {
		return this.api.get( {
			action: 'query',
			format: 'json',
			formatversion: 2,
			titles: titolo,
			prop: 'templates',
			tltemplates: this.config.baseTemplate
		} )
		.then( function ( data ) {
			return (
				data.query &&
				data.query.pages &&
				data.query.pages.length === 1 &&
				( data.query.pages[ 0 ].templates || [] ).length === 1
			);
		} );
	};

	/**
	 * Controlla lo stato di una procedura di cancellazione.
	 *
	 * @param {string} titolo - Il titolo della procedura da controllare
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.ottieniStatoProcedura = function ( titolo ) {
		return this.api.get( {
			action: 'query',
			format: 'json',
			formatversion: 2,
			titles: titolo,
			prop: 'categories|revisions',
			rvprop: 'timestamp|user',
			cllimit: 'max',
			rvdir: 'newer',
			rvlimit: 1
		} )
		.then( function ( data ) {
			var stato, page;
			if (
				data === undefined ||
				data.query === undefined ||
				data.query.pages === undefined ||
				data.query.pages.length !== 1
			) {
				return $.Deferred().reject();
			}

			page = data.query.pages[ 0 ];
			if ( page.missing === true || page.title === undefined ) {
				return $.Deferred().reject();
			}

			stato = {
				procedura: page.title
			};

			$.each( page.categories || [], function () {
				var inizio, consensuale, prorogata, votazione;

				if ( this.title === 'Categoria:Procedure di cancellazione in corso' ) {
					stato.terminata = false;
				} else if ( this.title === 'Categoria:Procedure di cancellazione protette' ) {
					stato.terminata = true;
				}

				inizio = this.title.match( /^Categoria\:Cancellazioni del (\d\d? .+ \d{4})$/ );
				consensuale = this.title.match( /^Categoria\:Cancellazioni consensuali del (\d\d? .+ \d{4})$/ );
				prorogata = this.title.match( /^Categoria\:Cancellazioni consensuali prorogate del (\d\d? .+ \d{4})$/ );
				votazione = this.title.match( /^Categoria\:Cancellazioni con votazione del (\d\d? .+ \d{4})$/ );

				if ( inizio ) {
					stato.inizio = inizio[ 1 ];
				}
				if ( consensuale ) {
					stato.consensuale = consensuale[ 1 ];
				}
				if ( prorogata ) {
					stato.prorogata = prorogata[ 1 ];
				}
				if ( votazione ) {
					stato.votazione = votazione[ 1 ];
				}
			} );
			if ( page.revisions && page.revisions.length === 1 ) {
				stato.creazione = page.revisions[ 0 ];
			}
			return stato;
		} );
	};

	/**
	 * Per gli amministratori: protegge la procedura di cancellazione.
	 *
	 * @param {string} titolo - Il titolo della procedura da proteggere
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.proteggiPagina = function ( titolo ) {
		return this.api.postWithToken( 'csrf', {
			action: 'protect',
			format: 'json',
			formatversion: 2,
			title: titolo,
			protections: this.config.protectLevel,
			reason: this.l10nMsg( 'baseSummary', this.config.protectReason )
		} );
	};

	/**
	 * Per gli amministratori: cancella una pagina.
	 *
	 * @param {string} titolo - Il titolo della pagina da cancellare
	 * @param {string} motivazione - La motivazione della cancellazione
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.cancellaPagina = function ( titolo, motivazione ) {
		return this.api.postWithToken( 'csrf', {
			action: 'delete',
			format: 'json',
			formatversion: 2,
			title: titolo,
			reason: this.l10nMsg( 'baseSummary', motivazione )
		} );
	};

	/**
	 * Per gli amministratori: termina una procedura di cancellazione.
	 *
	 * @param {string} titolo - Il titolo della procedura di cancellazione (terminata)
	 * @param {string} esito - L'esito della procedura di cancellazione: "Deleted" o "Kept"
	 * @param {string} modalità - La modalità della procedura di cancellazione
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.terminaProcedura = function ( titolo, esito, modalità ) {
		if ( [ 'Deleted', 'Kept' ].indexOf( esito ) === -1 ) {
			return $.Deferred().reject();
		}

		return this.api.postWithToken( 'csrf', {
			action: 'edit',
			format: 'json',
			formatversion: 2,
			title: titolo,
			// simpleDeletedTemplate, consensualeDeletedTemplate, votazioneDeletedTemplate,
			// consensualeKeptTemplate, votazioneKeptTemplate
			appendtext: this.l10nMsg( modalità + esito + 'Template' ),
			// simpleDeletedSummary, consensualeDeletedSummary, votazioneDeletedSummary,
			// consensualeKeptSummary, votazioneKeptSummary
			summary: this.l10nMsg( 'baseSummary', this.config[ modalità + esito + 'Summary' ] ),
			nocreate: 1
		} );
	};

	/**
	 * Purga la cache del server relativa a una pagina, compresa la cache dei link (utile per le categorie).
	 *
	 * @param {string} titolo - Il titolo della pagina da purgare
	 * @return {jQuery.Promise}
	 */
	PdcAgent.prototype.purgaPagina = function ( titolo ) {
		return this.api.post( {
			action: 'purge',
			format: 'json',
			formatversion: 2,
			forcelinkupdate: 1,
			titles: titolo
		} );
	};

	PDC = {
		api: new mw.Api( {
			ajax: {
				headers: {
					'Api-User-Agent': 'PDC.js (https://it.wikipedia.org/wiki/Utente:Ricordisamoa/PDC.js)'
				}
			}
		} ),

		namespaces: namespaces,

		dependencies: {

			/**
			 * Moduli ResourceLoader da cui dipende lo script.
			 */
			general: [
				'mediawiki.jqueryMsg',
				'mediawiki.api',
				'mediawiki.Title',
				'oojs-ui-widgets'
			],

			/**
			 * Moduli ResourceLoader da cui dipende la funzionalità della finestra di dialogo.
			 */
			dialog: [
				'oojs-ui-windows'
			]

		},

		/**
		 * Configurazione per Wikipedia in italiano.
		 */
		config: {
			baseSummary: '[[' + namespaces[ 2 ] + ':Ricordisamoa/PDC.js|PDC.js]]: $1',
			base: namespaces[ 4 ] + ':Pagine da cancellare',
			help: namespaces[ 4 ] + ':Regole per la cancellazione',
			baseTemplate: namespaces[ 10 ] + ':Cancellazione',
			actInNamespaces: [ 0 ],
			afdCatPrefix: 'Pagine in cancellazione - ',
			wikiprojectNamespace: 102,
			autoRfd: [
				[ 'non enciclopedica, puoi ', 'proporne', ' la ' ]
			],
			minEdits: 50,
			minDaysOld: 30,
			minDeferrableDays: 7,
			newNominationTemplate: 'subst:Cancellazione/creavvisotesto',
			newNominationSummary: 'nuova richiesta di cancellazione',
			nominationNoticeTemplate: 'Cancellazione',
			nominationNoticeSummary: 'richiesta di cancellazione',
			notificationNewSectionTitle: 'Cancellazione [[$1]]',
			notificationNewSectionTemplate: 'Cancellazione',
			consensualeStartTemplate: 'subst:Consensuale',
			consensualeStartSummary: 'avvio della modalità consensuale',
			deferMinDays: 1,
			deferMaxDays: 7,
			deferDefaultDays: 7,
			deferStartTemplate: 'subst:Proroga',
			deferStartSummary: 'modalità consensuale prorogata per $1 {{PLURAL:$1|giorno|giorni}}',
			voteStartTemplate: 'subst:Votazione',
			voteStartSummary: 'avvio della votazione',
			voteSummary: 'voto',
			voteSections: {
				Cancellare: '//upload.wikimedia.org/wikipedia/commons/thumb/8/89/Symbol_delete_vote.svg/30px-Symbol_delete_vote.svg.png',
				Mantenere: '//upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Symbol_kept_vote.svg/30px-Symbol_kept_vote.svg.png'
			},
			voteNullItems: [ '...' ],
			getVoteOutcome: function ( sections ) {
				// vedi [[Template:Paginecancellare/Criteri]]
				return (
					sections.Cancellare >= 7 &&
					sections.Cancellare >= sections.Mantenere * 2
				) ? 'Cancellare' : 'Mantenere';
			},
			simpleDeletedTemplate: '\n\n{{subst:FineCanc}}--~~~~',
			simpleDeletedSummary: 'notifica dell\'avvenuta cancellazione in modalità semplificata',
			simpleDeletedReason: 'Come da procedura di [[WP:RPC|cancellazione semplificata]]: [[$1]]',
			consensualeDeletedTemplate: '\n\n{{subst:FineCons|cancellata}} --~~~~',
			consensualeDeletedSummary: 'notifica dell\'avvenuta cancellazione in modalità consensuale',
			consensualeDeletedReason: 'Come da procedura di [[WP:RPC|cancellazione consensuale]]: [[$1]]',
			consensualeKeptTemplate: '\n\n{{subst:FineCons|mantenuta}} --~~~~',
			consensualeKeptSummary: 'notifica dell\'avvenuto mantenimento in modalità consensuale',
			votazioneDeletedTemplate: '\n\n{{subst:FineVoto|cancellata}} --~~~~',
			votazioneDeletedSummary: 'notifica dell\'avvenuta cancellazione in seguito a votazione',
			votazioneDeletedReason: 'Come da [[WP:RPC|votazione sulla cancellazione]]: [[$1]]',
			votazioneKeptTemplate: '\n\n{{subst:FineVoto|mantenuta}} --~~~~',
			votazioneKeptSummary: 'notifica dell\'avvenuto mantenimento in seguito a votazione',
			deletedTalkReason: '([[WP:IMMEDIATA|C10]]) Discussione di pagina cancellata: [[$1]]',

			// [[Template:Cronologia valutazioni/man]]
			assessmentsHistoryDefaultParams: {
				azione: 'PDC',
				data: '{{subst:LOCALDAY}} {{subst:LOCALMONTHNAME}} {{subst:LOCALYEAR}}'
			},
			assessmentsHistoryUpdateSummary: 'aggiornamento di Cronologia valutazioni',

			// [[Template:Cancellazione/richiesta]] per vecchie procedure
			nominationTemplateRegex: /\{\{\s*[Cc]ancellazione\/(?:proposta|richiesta)\s*\|\s*(?:1\s*\=\s*|)([^\{\|\}]+)\s*(\}\}|\|)/,

			// [[Wikipedia:Regole per la cancellazione#Annullamento della cancellazione]]
			cancelledReplace: /(\{\{\s*[Cc]ancellazione\/proposta\|.+?)(\}\})/,
			cancelledReplaceWith: '$1|annullata$2',
			cancelledTemplate: 'subst:Interrotto|Procedura annullata',
			cancelledSummary: 'procedura annullata',

			removeNominationNoticeReplace: /(<noinclude>)?\s*\{\{\s*[Cc]ancellazione.*?\}\}\s*(<\/noinclude>)?\n+/,
			removeNominationNoticeReplaceWith: '',
			removeNominationNoticeSummary: 'rimozione del template Cancellazione',

			protectLevel: 'edit=sysop|move=sysop',
			protectReason: '[[WP:PDC|Procedura di cancellazione]] terminata o annullata'
		},

		/**
		 * Messaggi di interfaccia per la lingua italiana.
		 */
		i18n: {
			it: {
				'menu-link-label': 'Richiedi cancellazione',
				'menu-link-tooltip': 'Procedura guidata per richiedere la cancellazione di questa pagina',
				'dialog-title': 'Richiesta di cancellazione: procedura guidata',
				'dialog-button-cancel': 'Annulla',
				'dialog-button-proceed': 'Procedi',
				'dialog-button-final': 'Richiedi la cancellazione',
				'error-msg': '$1: $2',
				'process-init': '{{GENDER:$1|Benvenuto|Benvenuta|Benvenuto/a}}!<br>Sarai {{GENDER:$1|guidato|guidata|guidato/a}} nella richiesta di cancellazione per la pagina "<b>$2</b>".<br><br>Se invece desideri effettuare la procedura manualmente, consulta $3.',
				'process-loading-contributors': 'Caricamento dei contributori della pagina in corso...',
				'process-confirm': 'Stai per proporre la cancellazione della pagina "$1".',
				'option-reason': 'Perché la pagina dovrebbe essere cancellata? (La firma è inserita automaticamente)',
				'option-reason-missing': 'Inserisci una motivazione',
				'option-notify-creator': 'Notifica il creatore della pagina ($1)',
				'option-notify-biggest': 'Notifica l\'utente registrato con più contributi alla pagina ($1)',
				'option-notify-topics': 'Eventuale/i argomento/i relativo/i: ',
				'option-notify-wikiprojects': 'Invia notifiche ai progetti di riferimento',
				'new-nomination-wait': 'Creazione della pagina di procedura in corso...',
				'new-nomination-error': 'Errore durante la creazione della pagina di procedura',
				'nominate-wait': 'Aggiunta dell\'avviso nella pagina in corso...',
				'nominate-error': 'Errore durante l\'aggiunta dell\'avviso nella pagina',
				'notify-wait': 'Notifica della proposta di cancellazione in corso...',
				'notify-error': 'Errore durante la notifica della proposta di cancellazione',
				'notify-done': 'Notifica della proposta di cancellazione inviata con successo alla pagina "$1"',
				'toolbox-title': 'Strumenti PDC per "$1"',
				'toolbox-days-ago': '$1 {{PLURAL:$1|giorno|giorni}} fa',
				'toolbox-today': 'oggi',
				'toolbox-yesterday': 'ieri',
				'toolbox-user-unknown': 'un utente sconosciuto',
				'toolbox-open-generic': 'Procedura aperta',
				'toolbox-open': 'Procedura aperta $1 da $2',
				'toolbox-closed': 'Procedura chiusa',
				'toolbox-purge': 'Aggiorna',
				'toolbox-defer': 'Proroga',
				'toolbox-defer-deferrable': 'Proroga la procedura',
				'toolbox-defer-already-poll': 'Già in votazione',
				'toolbox-defer-already-deferred': 'Già prorogata',
				'toolbox-defer-not-deferrable-yet': 'Non ancora prorogabile',
				'toolbox-defer-days': 'Giorni di proroga',
				'toolbox-defer-cancel': 'Non prorogare',
				'toolbox-defer-wait': 'Proroga della procedura in corso...',
				'toolbox-defer-error': 'Errore durante la proroga della procedura',
				'toolbox-consensuale': 'Passa alla modalità consensuale',
				'toolbox-consensuale-cancel': 'Annulla',
				'toolbox-consensuale-reason': 'Perché non vuoi che la pagina sia cancellata?',
				'toolbox-consensuale-wait': 'Passaggio alla modalità consensuale in corso...',
				'toolbox-consensuale-error': 'Errore durante il passaggio alla modalità consensuale',
				'toolbox-assessments-history-update-wait': 'Aggiornamento di Cronologia valutazioni in corso...',
				'toolbox-assessments-history-update-error': 'Errore durante l\'aggiornamento di Cronologia valutazioni',
				'toolbox-cancel': 'Annulla la procedura',
				'toolbox-cancel-cancel': 'Non annullare la procedura',
				'toolbox-cancel-reason': 'Perché la procedura deve essere annullata?',
				'toolbox-cancel-wait': 'Annullamento della procedura in corso...',
				'toolbox-cancel-error': 'Errore durante l\'annullamento della procedura',
				'toolbox-cancel-protect-wait': 'Protezione della pagina di procedura in corso...',
				'toolbox-cancel-protect-error': 'Errore durante la protezione della procedura',
				'toolbox-vote': 'Votazione',
				'toolbox-vote-for': 'Vota per $1',
				'toolbox-vote-wait': 'Invio del voto in corso...',
				'toolbox-vote-error': 'Errore durante l\'invio del voto',
				'toolbox-sysops': 'Per gli amministratori',
				'toolbox-vote-start': 'Avvia votazione',
				'toolbox-vote-start-wait': 'Avvio della votazione in corso...',
				'toolbox-vote-start-error': 'Errore durante l\'avvio della votazione',
				'toolbox-close': 'Chiudi la procedura',
				'toolbox-close-delete': 'Cancellare la pagina?',
				'toolbox-close-delete-yes': 'Sì',
				'toolbox-close-delete-no': 'No',
				'toolbox-close-cancel': 'Annulla',
				'toolbox-close-deletetalk': 'Cancellare la pagina di discussione?',
				'toolbox-close-deletetalk-yes': 'Sì',
				'toolbox-close-deletetalk-no': 'No',
				'toolbox-close-option-protect': 'Proteggi questa procedura',
				'toolbox-close-kept-wait': 'Notifica dell\'avvenuto mantenimento della pagina in corso...',
				'toolbox-close-kept-error': 'Errore durante la notifica dell\'avvenuto mantenimento',
				'toolbox-close-remove-notice-wait': 'Rimozione dell\'avviso di cancellazione dalla pagina in corso...',
				'toolbox-close-remove-notice-error': 'Errore durante la rimozione dell\'avviso di cancellazione dalla pagina',
				'toolbox-close-delete-wait': 'Cancellazione della pagina in corso...',
				'toolbox-close-delete-error': 'Errore durante la cancellazione della pagina',
				'toolbox-close-deletetalk-wait': 'Cancellazione della pagina di discussione in corso...',
				'toolbox-close-deletetalk-error': 'Errore durante la cancellazione della pagina di discussione',
				'toolbox-close-notice-wait': 'Notifica dell\'avvenuta cancellazione della pagina in corso...',
				'toolbox-close-notice-error': 'Errore durante la notifica dell\'avvenuta cancellazione',
				'toolbox-close-protect-wait': 'Protezione della pagina di procedura in corso...',
				'toolbox-close-protect-error': 'Errore durante la protezione della procedura',
				'toolbox-purge-wait': 'Purge della pagina di procedura in corso...'
			}
		},

		msg: function () {
			return this.agent.msg.apply( this.agent, arguments );
		},

		l10nMsg: function () {
			return this.agent.l10nMsg.apply( this.agent, arguments );
		},

		sysop: ( mw.config.get( 'wgUserGroups' ).indexOf( 'sysop' ) !== -1 || mw.config.get( 'wgUserName' ) === 'Ricordisamoa' ),

		/**
		 * Genera i pulsanti per votare in una procedura di cancellazione.
		 */
		defineVoteButtons: function () {
			/**
			 * @class VoteButtons
			 * @extends OO.ui.PanelLayout
			 *
			 * @constructor
			 * @param {Object} config - Opzioni di configurazione
			 * @cfg {PdcAgent} agent
			 * @cfg {string} pageName
			 * @cfg {ProgressHandler} progressHandler
			 * @cfg {Function} getVoteOutcome
			 * @cfg {VoteCounter} voteCounter
			 */
			VoteButtons = function ( config ) {
				VoteButtons.parent.call( this, { expanded: false } );

				this.agent = config.agent;
				this.pageName = config.pageName;
				this.progressHandler = config.progressHandler;
				this.getVoteOutcome = config.getVoteOutcome;
				this.voteCounter = config.voteCounter;

				this.addButtons();
			};
			OO.inheritClass( VoteButtons, OO.ui.PanelLayout );

			/**
			 * Gestisce il clic su un collegamento per votare.
			 *
			 * @private
			 * @param {Object} cfg - L'oggetto corrispondente alla sezione in cui votare
			 * @param {jQuery.Event} event - L'evento del clic
			 */
			VoteButtons.prototype.onVoteLinkClick = function ( cfg, event ) {
				var self = this;

				event.preventDefault();
				self.progressHandler.showMessage( self.agent.msg( 'toolbox-vote-wait' ) );
				self.agent.vota( self.pageName, cfg.num, '' )
				.done( function () {
					window.location.reload();
				} )
				.fail( function ( code, info ) {
					self.progressHandler.showError( self.agent.errorMsg( 'toolbox-vote-error', info ) );
				} );
			};

			/**
			 * Restituisce un collegamento per votare in una determinata sezione della procedura.
			 *
			 * @private
			 * @param {string} sectionId - Il nome univoco della sezione in cui votare
			 * @param {Object} cfg - L'oggetto corrispondente alla sezione in cui votare
			 * @return {jQuery}
			 */
			VoteButtons.prototype.getVoteLink = function ( sectionId, cfg ) {
				return $( '<a>' )
					.text( this.agent.msg( 'toolbox-vote-for', sectionId ) )
					.attr( {
						href: '#'
					} )
					.click( this.onVoteLinkClick.bind( this, cfg ) );
			};

			/**
			 * Restituisce una voce dell'elenco delle sezioni di votazione.
			 *
			 * @private
			 * @param {string} outcome - Il nome univoco della sezione attualmente vincente
			 * @param {Object} cfg - L'oggetto corrispondente alla sezione a cui la voce si riferisce
			 * @param {string} sectionId - Il nome univoco della sezione a cui la voce si riferisce
			 * @return {jQuery}
			 */
			VoteButtons.prototype.mapSections = function ( outcome, cfg, sectionId ) {
				return $( '<li>' )
				.prepend(
					$( '<img>' )
					.attr( {
						src: cfg.icon,
						alt: sectionId
					} )
				)
				.append( ' ' )
				.append(
					$( '<a>' )
					.text( cfg.votes )
					.attr( {
						href: '#' + sectionId
					} )
					.css( outcome === sectionId ? { 'font-weight': 'bold' } : {} )
				)
				.append( ' — ' )
				.append( this.getVoteLink( sectionId, cfg ) );
			};

			/**
			 * Aggiunge l'elenco delle sezioni di votazione con i collegamenti per votare.
			 *
			 * @private
			 */
			VoteButtons.prototype.addButtons = function () {
				var sections, outcome,
					voteCounts = {},
					$ul = $( '<ul>' ).appendTo( this.$element );

				sections = this.voteCounter.getSections();
				$.each( sections, function ( sectionId, cfg ) {
					voteCounts[ sectionId ] = cfg.votes;
				} );
				outcome = this.getVoteOutcome( voteCounts );

				$ul.append( $.map( sections, this.mapSections.bind( this, outcome ) ) );
			};
		},

		defineProgressHandler: function () {
			/**
			 * @class ProgressHandler
			 * @mixins OO.EventEmitter
			 *
			 * @constructor
			 */
			ProgressHandler = function () {
				OO.EventEmitter.call( this );

				this.bar = new OO.ui.ProgressBarWidget();
				this.actions = null;
				this.done = 0;

				this.$text = $( '<p>' );

				this.$container = $( '<div>' ).append( [
					this.$text.hide(),
					this.bar.$element.hide()
				] );
			};
			OO.mixinClass( ProgressHandler, OO.EventEmitter );

			/**
			 * Un evento 'message' viene emesso quando viene impostato un messaggio di progresso.
			 *
			 * @event message
			 * @param {string} messaggio - Il messaggio di progresso impostato
			 */

			/**
 			 * Un evento 'error' viene emesso quando viene impostato un messaggio di errore.
 			 *
 			 * @event error
 			 * @param {string} messaggio - Il messaggio di errore impostato
 			 */

			/**
			 * Imposta il numero totale delle azioni da compiere.
			 *
			 * @param {number} actions - Numero di azioni
			 */
			ProgressHandler.prototype.setActions = function ( actions ) {
				this.actions = actions;
				this.updateProgress();
			};

			/**
			 * Incrementa di un'unità il numero delle azioni completate.
			 */
			ProgressHandler.prototype.tick = function () {
				this.done++;
				this.updateProgress();
			};

			/**
			 * Aggiorna la barra di progresso in base alla percentuale di azioni completate.
			 *
			 * @private
			 */
			ProgressHandler.prototype.updateProgress = function () {
				this.bar.setProgress( this.actions !== null ? this.done / this.actions * 100 : false );
			};

			/**
			 * Imposta il messaggio relativo all'azione attuale.
			 *
			 * @param {string} messaggio - Il messaggio da impostare
			 */
			ProgressHandler.prototype.showMessage = function ( messaggio ) {
				console.info( messaggio );

				this.$text.empty().removeClass( 'error' ).show().append( messaggio );

				this.bar.$element.show();

				this.emit( 'message', messaggio );
			};

			/**
			 * Imposta un messaggio di errore relativo all'azione attuale.
			 *
			 * @param {string} messaggio - Il messaggio da impostare
			 */
			ProgressHandler.prototype.showError = function ( messaggio ) {
				console.error( messaggio );

				this.$text.empty().addClass( 'error' ).show().append( messaggio );

				this.bar.$element.hide();

				this.emit( 'error', messaggio );
			};
		},

		defineLookupWidget: function () {
			/**
			 * @class AllCategoriesLookupWidget
			 * @extends OO.ui.TextInputWidget
			 * @mixins OO.ui.mixin.LookupElement
			 *
			 * @constructor
			 * @param {Object} config Configuration options
			 * @cfg {mw.Api} api MediaWiki API instance
			 * @cfg {string} categoryPrefix The titles of the suggested categories should start with it
			 */
			AllCategoriesLookupWidget = function ( config ) {
				OO.ui.TextInputWidget.call( this, config );
				OO.ui.mixin.LookupElement.call( this, config );
				this.api = config.api;
				this.categoryPrefix = config.categoryPrefix;
			};
			OO.inheritClass( AllCategoriesLookupWidget, OO.ui.TextInputWidget );
			OO.mixinClass( AllCategoriesLookupWidget, OO.ui.mixin.LookupElement );

			AllCategoriesLookupWidget.prototype.getLookupRequest = function () {
				return this.api.get( {
					action: 'query',
					format: 'json',
					formatversion: 2,
					list: 'allcategories',
					acprefix: this.categoryPrefix,
					acprop: '',
					acfrom: this.categoryPrefix + this.value,
					aclimit: 10
				} );
			};

			AllCategoriesLookupWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
				var self = this;
				if ( response.query === undefined || response.query.allcategories === undefined ) {
					return [];
				}
				return $.map( response.query.allcategories || [], function ( e ) {
					return e.category.replace( self.categoryPrefix, '' );
				} );
			};

			AllCategoriesLookupWidget.prototype.getLookupMenuOptionsFromData = function ( data ) {
				return $.map( data, function ( value ) {
					return new OO.ui.MenuOptionWidget( {
						data: value,
						label: value
					} );
				} );
			};
		},

		defineDialog: function () {
			var dialogMsg, // FIXME: https://phabricator.wikimedia.org/T129972
				deferMsg = function ( arg ) {
					return function () {
						return dialogMsg( arg );
					};
				};

			/**
			 * @class PdcDialog
			 * @extends OO.ui.ProcessDialog
			 *
			 * @constructor
			 * @param {Object} config - Opzioni di configurazione
			 * @cfg {string} afdCatPrefix - Prefisso delle categorie di pagine in cancellazione
			 * @cfg {PdcAgent} agent
			 * @cfg {mw.Api} api
			 * @cfg {string} helpPageTitle - Il titolo della pagina sulle regole per la cancellazione
			 * @cfg {Object} namespaces - I namespace del sito
			 * @cfg {ProgressHandler} progressHandler - Gestore della barra di progresso
			 * @cfg {string} titolo - Il titolo della pagina attuale
			 * @cfg {number} wikiprojectNamespace - Il numero del namespace "Progetto"
			 */
			PdcDialog = function ( config ) {
				PdcDialog.parent.call( this, config );
				this.afdCatPrefix = config.afdCatPrefix;
				this.agent = config.agent;
				this.api = config.api;
				this.helpPageTitle = config.helpPageTitle;
				this.namespaces = config.namespaces;
				this.progressHandler = config.progressHandler;
				this.titolo = config.titolo;
				// namespace "Discussioni progetto"
				this.wikiprojectTalkNamespace = config.wikiprojectNamespace + 1;

				dialogMsg = this.agent.msg.bind( this.agent );
			};
			OO.inheritClass( PdcDialog, OO.ui.ProcessDialog );

			PdcDialog.static.name = 'PdcDialog';
			PdcDialog.static.title = 'PDC.js';
			PdcDialog.static.size = 'large';
			PdcDialog.static.actions = [
				{
					action: 'proceed',
					modes: 'step1',
					label: deferMsg( 'dialog-button-proceed' ),
					flags: [ 'primary', 'progressive' ]
				},
				{
					action: 'final',
					modes: 'step2',
					label: deferMsg( 'dialog-button-final' ),
					flags: [ 'primary', 'constructive' ]
				},
				{
					action: 'cancel',
					modes: [ 'step1', 'step2' ],
					label: deferMsg( 'dialog-button-cancel' ),
					flags: 'safe'
				}
			];

			/**
			 * @inheritdoc
			 */
			PdcDialog.prototype.initialize = function () {
				PdcDialog.parent.prototype.initialize.apply( this, arguments );
				this.content = new OO.ui.PanelLayout( { padded: true } );
				this.fieldset = new OO.ui.FieldsetLayout( {
					label: this.agent.msg( 'dialog-title' ), icon: 'alert'
				} );
				this.description = new OO.ui.LabelWidget( {
					padded: true,
					label: $( '<p>' )
						.append(
							this.agent.msg(
								'process-init',
								'', // utente attuale per GENDER
								this.titolo,
								$( '<a>' ).attr( 'href', mw.util.getUrl( this.helpPageTitle ) ).text( this.helpPageTitle ).get( 0 ).outerHTML
							)
						)
				} );
				this.fieldset.addItems( [ this.description ] );
				this.content.$element.append( [
					this.fieldset.$element,
					this.progressHandler.$container
				] );
				this.$body.append( this.content.$element );

				this.actions.once( 'add', function () {
					this.actions.setMode( 'step1' );
				}.bind( this ) );

				this.options = {};
			};

			/**
			 * Aggiunge la casella per specificare il motivo della richiesta di cancellazione.
			 *
			 * @private
			 */
			PdcDialog.prototype.addReasonWidget = function () {
				this.options.reason = new OO.ui.TextInputWidget( {
					multiline: true,
					autosize: true,
					validate: /\S/
				} );
				this.fieldset.addItems( [
					new OO.ui.FieldLayout(
						this.options.reason,
						{
							label: this.agent.msg( 'option-reason' ),
							align: 'top'
						}
					)
				] );
			};

			/**
			 * Aggiunge l'opzione per notificare la procedura al primo autore della pagina.
			 *
			 * @private
			 */
			PdcDialog.prototype.addNotifyCreatorOption = function () {
				if (
					this.creatore.anon === undefined &&
					this.creatore.user !== mw.config.get( 'wgUserName' )
				) {
					this.options.notifyCreator = new OO.ui.CheckboxInputWidget( {} );
					this.fieldset.addItems( [
						new OO.ui.FieldLayout(
							this.options.notifyCreator,
							{
								align: 'inline',
								label: $( '<span>' )
									.append( this.agent.msg(
										'option-notify-creator',
										this.agent.linkUtente( this.creatore.user )
									) )
							}
						)
					] );
				}
			};

			/**
			 * Aggiunge l'opzione per notificare la procedura al maggior contributore della pagina.
			 *
			 * @private
			 */
			PdcDialog.prototype.addNotifyBiggestOption = function () {
				if (
					this.maggiore !== null &&
					this.maggiore !== this.creatore.user &&
					this.maggiore !== mw.config.get( 'wgUserName' )
				) {
					this.options.notifyBiggest = new OO.ui.CheckboxInputWidget( {} );
					this.fieldset.addItems( [
						new OO.ui.FieldLayout(
							this.options.notifyBiggest,
							{
								align: 'inline',
								label: $( '<span>' )
									.append( this.agent.msg(
										'option-notify-biggest',
										this.agent.linkUtente( this.maggiore )
									) )
							}
						)
					] );
				}
			};

			/**
			 * Aggiunge le caselle per specificare gli argomenti trattati dalla pagina da cancellare.
			 *
			 * @private
			 */
			PdcDialog.prototype.addNotifyTopicOptions = function () {
				this.options.notifyTopic1 = new AllCategoriesLookupWidget( {
					$overlay: this.$overlay,
					api: this.api,
					categoryPrefix: this.afdCatPrefix
				} );
				this.fieldset.addItems( [
					new OO.ui.FieldLayout(
						this.options.notifyTopic1,
						{
							align: 'left',
							label: this.agent.msg( 'option-notify-topics' )
						}
					)
				] );
				this.options.notifyTopic2 = new AllCategoriesLookupWidget( {
					$overlay: this.$overlay,
					api: this.api,
					categoryPrefix: this.afdCatPrefix
				} );
				this.fieldset.addItems( [
					new OO.ui.FieldLayout(
						this.options.notifyTopic2,
						{
							align: 'left',
							label: '+'
						}
					)
				] );
			};

			/**
			 * Aggiunge l'opzione per notificare la procedura ai progetti competenti.
			 *
			 * @private
			 */
			PdcDialog.prototype.addNotifyWikiprojectsOption = function () {
				this.options.notifyWikiprojects = new OO.ui.CheckboxInputWidget( {
					disabled: this.namespaces[ this.wikiprojectTalkNamespace ] === undefined
				} );
				this.fieldset.addItems( [
					new OO.ui.FieldLayout(
						this.options.notifyWikiprojects,
						{
							align: 'inline',
							label: this.agent.msg( 'option-notify-wikiprojects' )
						}
					)
				] );
			};

			/**
			 * Aggiorna la finestra con le opzioni per richiedere la cancellazione della pagina.
			 *
			 * @private
			 * @param {Object[]} contributori - Le revisioni della pagina
			 */
			PdcDialog.prototype.onGetContributors = function ( contributori ) {
				this.popPending();

				this.description.setLabel(
					$( '<p>' )
					.append(
						this.agent.msg( 'process-confirm', this.titolo )
					)
				);

				this.creatore = contributori[ contributori.length - 1 ];
				this.maggiore = this.agent.maggiorContributore( contributori );

				this.addReasonWidget();
				this.addNotifyCreatorOption();
				this.addNotifyBiggestOption();
				this.addNotifyTopicOptions();
				this.addNotifyWikiprojectsOption();

				// Forza il ridimensionamento della finestra
				this.updateSize();

				// Mostra il pulsante 'richiedi la cancellazione' al posto di 'procedi'
				this.actions.setMode( 'step2' );
			};

			/**
			 * Richiede i contributori della pagina e aggiorna la finestra.
			 *
			 * @private
			 */
			PdcDialog.prototype.proceed = function () {
				this.pushPending();

				this.agent.ottieniContributori( this.titolo )
				.done( this.onGetContributors.bind( this ) );
			};

			/**
			 * Ottiene i titoli delle pagine in cui notificare la procedura di cancellazione.
			 *
			 * @private
			 * @param {string[]} argomenti
			 * @return {string[]}
			 */
			PdcDialog.prototype.getNotificationPages = function ( argomenti ) {
				var daNotificare,
					self = this;

				daNotificare = $.map( {
					notifyCreator: self.creatore.user,
					notifyBiggest: self.maggiore
				}, function ( user, opt ) {
					if (
						self.options[ opt ] !== undefined &&
						self.options[ opt ].isSelected()
					) {
						return self.namespaces[ 3 ] + ':' + user;
					}
				} );

				if (
					!self.options.notifyWikiprojects.isDisabled() &&
					self.options.notifyWikiprojects.isSelected()
				) {
					daNotificare = daNotificare.concat(
						$.map( argomenti, function ( argomento ) {
							return self.namespaces[ self.wikiprojectTalkNamespace ] + ':' + argomento;
						} )
					);
				}

				return daNotificare;
			};

			/**
			 * Nomina la pagina per la cancellazione e segnala la procedura dove necessario.
			 *
			 * @private
			 */
			PdcDialog.prototype.final = function () {
				var self = this,
				motivazione = self.options.reason.getValue().trim(),
				argomenti = $.map( [ 'notifyTopic1', 'notifyTopic2' ], function ( opt ) {
					return self.options[ opt ].getValue().trim();
				} )
				.filter( function ( el, i, arr ) {
					// Elimina i duplicati
					return el !== '' && arr.indexOf( el ) === i;
				} ),
				daNotificare = self.getNotificationPages( argomenti );

				self.progressHandler.setActions( 3 );
				self.progressHandler.showMessage( self.agent.msg( 'new-nomination-wait' ) );
				self.agent.creaProceduraDaPagina( self.titolo, motivazione )
				.done( function ( procedura, numero ) {
					self.progressHandler.tick();
					self.progressHandler.showMessage( self.agent.msg( 'nominate-wait' ) );
					self.agent.aggiungiAvvisoVoce( self.titolo, numero, argomenti )
					.done( function () {
						self.progressHandler.tick();
						self.progressHandler.showMessage( self.agent.msg( 'notify-wait' ) );
						self.agent.notificaProcedura( self.titolo, numero, daNotificare )
						.done( function () {
							self.progressHandler.tick();
							window.location = mw.util.getUrl( procedura );
						} )
						.fail( function () {
							self.progressHandler.showError( self.agent.msg( 'notify-error' ) );
						} );
					} )
					.fail( function () {
						self.progressHandler.showError( self.agent.msg( 'nominate-error' ) );
					} );
				} )
				.fail( function () {
					self.progressHandler.showError( self.agent.msg( 'new-nomination-error' ) );
				} );
			};

			/**
			 * Controlla la validità della motivazione e procede in caso di esito positivo.
			 *
			 * @private
			 */
			PdcDialog.prototype.checkReasonAndFinal = function () {
				this.options.reason.getValidity()
				.done( this.final.bind( this ) );
			};

			/**
			 * @inheritdoc
			 */
			PdcDialog.prototype.getActionProcess = function ( action ) {
				if ( action === 'proceed' ) {
					return new OO.ui.Process( this.proceed, this );
				}
				if ( action === 'final' ) {
					return new OO.ui.Process( this.checkReasonAndFinal, this );
				}
				if ( action === 'cancel' ) {
					return new OO.ui.Process( this.close() );
				}

				return PdcDialog.parent.prototype.getActionProcess.call( this, action );
			};
		},

		/**
		 * Avvia una finestra di dialogo per guidare l'utente durante l'avvio della procedura.
		 *
		 * @param {string} titolo - Il titolo della pagina della quale proporre la cancellazione
		 */
		proceduraGuidata: function ( titolo ) {
			var self = this;
			titolo = titolo.replace( /_/g, ' ' );
			mw.loader.using( self.dependencies.dialog )
			.done( function () {
				var windowManager;

				if ( AllCategoriesLookupWidget === undefined ) {
					self.defineLookupWidget();
				}

				self.defineDialog();

				windowManager = new OO.ui.WindowManager();
				$( 'body' ).append( windowManager.$element );

				if ( ProgressHandler === undefined ) {
					self.defineProgressHandler();
				}
				self.progressHandler = new ProgressHandler();

				self.dialog = new PdcDialog( {
					afdCatPrefix: self.config.afdCatPrefix,
					agent: self.agent,
					api: self.api,
					helpPageTitle: self.config.help,
					namespaces: self.namespaces,
					progressHandler: self.progressHandler,
					titolo: titolo,
					wikiprojectNamespace: self.config.wikiprojectNamespace
				} );
				windowManager.addWindows( [ self.dialog ] );
				windowManager.openWindow( self.dialog );
			} );
		},

		/**
		 * Trasforma alcune frasi appositamente configurate
		 * in link per richiedere la cancellazione della pagina.
		 */
		autoRfd: function () {
			var self = this;
			if ( self.config.autoRfd.length === 0 ) {
				return;
			}
			$( 'div' ).each( function () {
				$( this )
				.contents()
				.filter( function () {
					// solo testo semplice
					return this.nodeType === this.TEXT_NODE;
				} )
				.each( function () {
					var htmlDiv = this;
					$.each( self.config.autoRfd, function ( replIdx, replParts ) {
						// cerca la frase intera,
						var node,
							find = htmlDiv.textContent.indexOf( replParts.join( '' ) );
						if ( find !== -1 ) {
							// ...la isola
							node = htmlDiv.splitText( find + replParts[ 0 ].length );
							// ...e la rende cliccabile
							$( node.splitText( replParts[ 1 ].length ).previousSibling )
							.wrap( '<a>' ).parent()
							.attr( {
								href: '#',
								title: self.msg( 'menu-link-tooltip' )
							} )
							.click( function ( event ) {
								event.preventDefault();
								self.proceduraGuidata( mw.config.get( 'wgPageName' ) );
							} );
						}
					} );
				} );
			} );
		},

		/**
		 * Genera il contenitore per pulsanti che non richiedono permessi particolari.
		 */
		definePublicButtons: function () {
			/**
			 * @class PublicButtons
			 * @extends OO.ui.PanelLayout
			 *
			 * @constructor
			 * @param {Object} config - Opzioni di configurazione
			 * @cfg {PdcAgent} agent
			 * @cfg {ProgressHandler} progressHandler
			 * @cfg {boolean} prorogabile - Se la procedura di cancellazione può essere prorogata
			 * @cfg {boolean} protectOnCancel - Se la procedura di cancellazione va protetta dopo l'annullamento
			 * @cfg {Object} stato - Lo stato della procedura di cancellazione
			 */
			PublicButtons = function ( config ) {
				PublicButtons.parent.call( this, { expanded: false } );

				this.agent = config.agent;
				this.progressHandler = config.progressHandler;
				this.prorogabile = config.prorogabile;
				this.protectOnCancel = config.protectOnCancel;
				this.stato = config.stato;

				this.addButtons();
			};
			OO.inheritClass( PublicButtons, OO.ui.PanelLayout );

			PublicButtons.prototype.switchButtons = function ( show ) {
				var m = show ? 'show' : 'hide';
				if ( show ) {
					this.$element.next().remove();
				}
				return this.$element[ m ]();
			};

			/**
			 * Gestisce il clic sul pulsante "aggiorna".
			 *
			 * @private
			 */
			PublicButtons.prototype.onPurgeButtonClick = function () {
				this.progressHandler.showMessage( this.agent.msg( 'toolbox-purge-wait' ) );
				this.agent.purgaPagina( this.stato.procedura )
				.done( function () {
					window.location.reload();
				} );
			};

			/**
			 * Genera il pulsante "aggiorna" con effetto immediato (innocuo).
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			PublicButtons.prototype.getPurgeButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-purge' )
				} )
				.on( 'click', this.onPurgeButtonClick.bind( this ) );
			};

			/**
			 * Gestisce il clic sul pulsante "proroga".
			 *
			 * @private
			 */
			PublicButtons.prototype.onDeferButtonClick = function () {
				var self = this,
				deferForm = new OO.ui.PanelLayout( { expanded: false } ),
				$deferForm = deferForm.$element,

				deferDays = new OO.ui.NumberInputWidget( {
					min: self.agent.config.deferMinDays,
					max: self.agent.config.deferMaxDays,
					isInteger: true,
					input: {
						value: self.agent.config.deferDefaultDays
					}
				} ),

				// Conferma
				confirmButton = new OO.ui.ButtonWidget( {
					label: self.agent.msg( 'toolbox-defer' ),
					flags: [ 'constructive' ]
				} )
				.on( 'click', function () {
					self.progressHandler.showMessage( self.agent.msg( 'toolbox-defer-wait' ) );
					self.agent.prorogaProcedura( self.stato.procedura, parseInt( deferDays.getValue() ) )
					.done( function () {
						self.progressHandler.showMessage( self.agent.msg( 'toolbox-purge-wait' ) );
						self.agent.purgaPagina( self.stato.procedura )
						.done( function () {
							window.location.reload();
						} );
					} )
					.fail( function ( code, info ) {
						self.progressHandler.showError( self.agent.errorMsg( 'toolbox-defer-error', info ) );
					} );
				} ),

				// Annulla
				cancelButton = new OO.ui.ButtonWidget( {
					label: self.agent.msg( 'toolbox-defer-cancel' )
				} )
				.on( 'click', function () {
					self.switchButtons( true );
				} );

				$deferForm.append(
					// Di quanti giorni?
					new OO.ui.FieldLayout(
						deferDays,
						{
							label: self.agent.msg( 'toolbox-defer-days' )
						}
					)
					.$element,
					confirmButton.$element,
					cancelButton.$element
				)
				.insertAfter( self.switchButtons() );
			};

			/**
			 * Genera il pulsante "proroga" con conferma richiesta.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			PublicButtons.prototype.getDeferButton = function () {
				var deferButtonTooltip;

				if ( this.prorogabile ) {
					deferButtonTooltip = 'toolbox-defer-deferrable';
				} else if ( this.stato.votazione ) {
					deferButtonTooltip = 'toolbox-defer-already-poll';
				} else if ( this.stato.prorogata ) {
					deferButtonTooltip = 'toolbox-defer-already-deferred';
				} else {
					deferButtonTooltip = 'toolbox-defer-not-deferrable-yet';
				}

				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-defer' ),
					title: this.agent.msg( deferButtonTooltip ),
					flags: [ 'progressive' ],
					disabled: !this.prorogabile
				} )
				.on( 'click', this.onDeferButtonClick.bind( this ) );
			};

			/**
			 * Gestisce il clic sul pulsante "passa alla modalità consensuale".
			 *
			 * @private
			 */
			PublicButtons.prototype.onConsensualeButtonClick = function () {
				var self = this,
				consensualeForm = new OO.ui.PanelLayout( { expanded: false } ),
				$consensualeForm = consensualeForm.$element,

				// Motivazione
				consensualeReason = new OO.ui.TextInputWidget( {
					multiline: true,
					autosize: true
				} ),

				// Conferma
				confirmButton = new OO.ui.ButtonWidget( {
					label: self.agent.msg( 'toolbox-consensuale' ),
					flags: [ 'constructive' ]
				} )
				.on( 'click', function () {
					self.progressHandler.showMessage( self.agent.msg( 'toolbox-consensuale-wait' ) );
					self.agent.consensuale( self.stato.procedura, consensualeReason.getValue() )
					.done( function () {
						window.location.reload();
					} )
					.fail( function ( code, info ) {
						self.progressHandler.showError( self.agent.errorMsg( 'toolbox-consensuale-error', info ) );
					} );
				} ),

				// Annulla
				cancelButton = new OO.ui.ButtonWidget( {
					label: self.agent.msg( 'toolbox-consensuale-cancel' )
				} )
				.on( 'click', function () {
					self.switchButtons( true );
				} );

				$consensualeForm.append(
					new OO.ui.FieldLayout(
						consensualeReason,
						{
							label: self.agent.msg( 'toolbox-consensuale-reason' ),
							align: 'top'
						}
					)
					.$element,
					confirmButton.$element,
					cancelButton.$element
				)
				.insertAfter( self.switchButtons() );
			};

			/**
			 * Genera il pulsante "passa alla modalità consensuale" con conferma richiesta.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			PublicButtons.prototype.getConsensualeButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-consensuale' ),
					flags: [ 'progressive' ]
				} )
				.on( 'click', this.onConsensualeButtonClick.bind( this ) );
			};

			/**
			 * Protegge la procedura di cancellazione dopo l'annullamento se richiesto.
			 *
			 * @private
			 */
			PublicButtons.prototype.maybeProtectOnCancel = function () {
				if ( this.protectOnCancel ) {
					this.progressHandler.showMessage( this.agent.msg( 'toolbox-cancel-protect-wait' ) );
					return this.agent.proteggiPagina( this.stato.procedura );
				}
				return $.Deferred().resolve();
			};

			/**
			 * Gestisce il clic sul pulsante "annulla la procedura".
			 *
			 * @private
			 */
			PublicButtons.prototype.onCancelButtonClick = function () {
				var self = this,
				cancelForm = new OO.ui.PanelLayout( { expanded: false } ),
				$cancelForm = cancelForm.$element,

				// Motivazione
				cancelReason = new OO.ui.TextInputWidget( {
					multiline: true,
					autosize: true
				} ),

				// Conferma
				confirmButton = new OO.ui.ButtonWidget( {
					label: self.agent.msg( 'toolbox-cancel' ),
					flags: [ 'destructive' ]
				} )
				.on( 'click', function () {
					self.progressHandler.showMessage( self.agent.msg( 'toolbox-cancel-wait' ) );
					self.agent.annullaProcedura( self.stato.procedura, cancelReason.getValue() )
					.done( function () {
						var parametri = {
							esito: 'annullata',
							collegamento: self.stato.procedura
						};

						self.progressHandler.showMessage( self.agent.msg( 'toolbox-assessments-history-update-wait' ) );
						self.agent.aggiornaCronologiaValutazioni( self.stato.pagina, parametri )
						.done( function () {
							self.progressHandler.showMessage( '' );
							self.maybeProtectOnCancel()
							.done( function () {
								self.progressHandler.showMessage( self.agent.msg( 'toolbox-purge-wait' ) );
								self.agent.purgaPagina( self.stato.procedura )
								.done( function () {
									window.location.reload();
								} );
							} )
							.fail( function ( code, info ) {
								self.progressHandler.showError( self.agent.errorMsg( 'toolbox-cancel-protect-error', info ) );
							} );
						} )
						.fail( function ( code, info ) {
							self.progressHandler.showError( self.agent.errorMsg( 'toolbox-assessments-history-update-error', info ) );
						} );
					} )
					.fail( function ( code, info ) {
						self.progressHandler.showError( self.agent.errorMsg( 'toolbox-cancel-error', info ) );
					} );
				} ),

				// Annulla
				cancelButton = new OO.ui.ButtonWidget( {
					label: self.agent.msg( 'toolbox-cancel-cancel' )
				} )
				.on( 'click', function () {
					self.switchButtons( true );
				} );

				$cancelForm.append(
					new OO.ui.FieldLayout(
						cancelReason,
						{
							label: self.agent.msg( 'toolbox-cancel-reason' ),
							align: 'top'
						}
					)
					.$element,
					confirmButton.$element,
					cancelButton.$element
				)
				.insertAfter( self.switchButtons() );
			};

			/**
			 * Genera il pulsante "annulla la procedura" con conferma richiesta.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			PublicButtons.prototype.getCancelButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-cancel' ),
					flags: [ 'progressive' ]
				} )
				.on( 'click', this.onCancelButtonClick.bind( this ) );
			};

			/**
			 * Aggiunge i pulsanti appropriati in base allo stato della procedura.
			 */
			PublicButtons.prototype.addButtons = function () {
				this.$element.append( this.getPurgeButton().$element );

				if ( !this.stato.terminata ) {
					if ( this.stato.consensuale ) {
						this.$element.append( this.getDeferButton().$element );
					} else {
						this.$element.append( this.getConsensualeButton().$element );
					}
					this.$element.append( this.getCancelButton().$element );
				}
			};
		},

		/**
		 * Genera il contenitore per pulsanti che richiedono permessi di amministratore.
		 */
		defineSysopButtons: function () {
			/**
			 * @class SysopButtons
			 * @extends OO.ui.PanelLayout
			 *
			 * @constructor
			 * @param {Object} config - Opzioni di configurazione
			 * @cfg {PdcAgent} agent
			 * @cfg {ProgressHandler} progressHandler
			 * @cfg {Object} stato - Lo stato della procedura di cancellazione
			 * @cfg {VoteCounter} voteCounter
			 */
			SysopButtons = function ( config ) {
				SysopButtons.parent.call( this, { expanded: false } );

				this.agent = config.agent;
				this.progressHandler = config.progressHandler;
				this.stato = config.stato;
				this.voteCounter = config.voteCounter;

				this.$element.append( [
					this.getVoteStartButton().$element,
					this.getCloseButton().$element
				] );
			};
			OO.inheritClass( SysopButtons, OO.ui.PanelLayout );

			SysopButtons.prototype.switchButtons = function ( show ) {
				var m = show ? 'show' : 'hide';
				if ( show ) {
					this.$element.next().remove();
				}
				return this.$element[ m ]();
			};

			/**
			 * Ottiene la modalità della procedura di cancellazione.
			 *
			 * @private
			 * @return {string}
			 */
			SysopButtons.prototype.getModeId = function () {
				if ( this.stato.votazione ) {
					return 'votazione';
				}
				if ( this.stato.consensuale ) {
					return 'consensuale';
				}
				return 'simple';
			};

			/**
			 * Gestisce il clic sul pulsante "avvia votazione".
			 *
			 * @private
			 */
			SysopButtons.prototype.onVoteStartButtonClick = function () {
				var self = this;

				self.progressHandler.showMessage( self.agent.msg( 'toolbox-vote-start-wait' ) );
				self.agent.avviaVotazione( self.stato.procedura )
				.done( function () {
					window.location.reload();
				} )
				.fail( function () {
					self.progressHandler.showError( self.agent.msg( 'toolbox-vote-start-error' ) );
				} );
			};

			/**
			 * Genera il pulsante "avvia votazione" con effetto immediato.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			SysopButtons.prototype.getVoteStartButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-vote-start' ),
					flags: [ 'constructive' ],
					disabled: ( this.stato.votazione || !this.stato.consensuale )
				} )
				.on( 'click', this.onVoteStartButtonClick.bind( this ) );
			};

			/**
			 * Genera il pulsante per rispondere "sì" alla cancellazione della pagina di discussione.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			SysopButtons.prototype.getCloseDeleteTalkYesButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-close-deletetalk-yes' ),
					flags: [ 'destructive' ]
				} );
			};

			/**
			 * Genera il pulsante per rispondere "no" alla cancellazione della pagina di discussione.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			SysopButtons.prototype.getCloseDeleteTalkNoButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-close-deletetalk-no' ),
					flags: [ 'constructive' ]
				} );
			};

			/**
			 * Chiede all'utente se cancellare la pagina di discussione.
			 *
			 * @private
			 * @return {jQuery.Promise}
			 */
			SysopButtons.prototype.askDeleteTalk = function ( titolo ) {
				var self = this,
					deferred = $.Deferred();

				$( '<div>' )
				.append( [
					// Cancellare la pagina?
					self.agent.msg( 'toolbox-close-deletetalk' ),
					'<br>',
					// Sì
					self.getCloseDeleteTalkYesButton().$element.on( 'click', function () {
						var motivazione = self.agent.l10nMsg( 'deletedTalkReason', self.stato.procedura );
						self.switchButtons( true );
						self.progressHandler.showMessage( self.agent.msg( 'toolbox-close-deletetalk-wait' ) );
						self.agent.cancellaPagina( titolo, motivazione ).then( deferred.resolve, deferred.reject );
					} ),
					// No
					self.getCloseDeleteTalkNoButton().$element.on( 'click', function () {
						self.switchButtons( true );
						deferred.resolve();
					} )
				] )
				.insertAfter( self.switchButtons() );

				return deferred;
			};

			/**
			 * Controlla l'esistenza dell'eventuale pagina di discussione e, in caso positivo, chiede all'utente se cancellarla.
			 *
			 * @private
			 * @return {jQuery.Promise}
			 */
			SysopButtons.prototype.maybeDeleteTalkPage = function () {
				var self = this,
					titolo = self.agent.ottieniTitoloDiscussione( self.stato.pagina );

				if ( titolo === null ) {
					return $.Deferred().resolve();
				}

				return self.agent.controllaEsistenzaPagina( titolo )
					.then( function ( exists ) {
						if ( exists ) {
							return self.askDeleteTalk( titolo );
						}
						return $.Deferred().resolve();
					} );
			};

			/**
			 * Gestisce il clic sul pulsante per rispondere "sì" alla cancellazione della pagina.
			 *
			 * @private
			 */
			SysopButtons.prototype.onCloseDeleteYesButtonClick = function () {
				var self = this,
					modalità = self.getModeId(),
					// simpleDeletedReason, consensualeDeletedReason, votazioneDeletedReason
					motivazione = self.agent.l10nMsg( modalità + 'DeletedReason', self.stato.procedura );

				self.progressHandler.setActions( 5 );
				self.progressHandler.showMessage( self.agent.msg( 'toolbox-close-delete-wait' ) );
				self.agent.cancellaPagina( self.stato.pagina, motivazione )
				.done( function () {
					self.progressHandler.tick();
					self.progressHandler.showMessage( '' );
					self.maybeDeleteTalkPage()
					.done( function () {
						self.progressHandler.tick();
						self.progressHandler.showMessage( self.agent.msg( 'toolbox-close-notice-wait' ) );
						self.agent.terminaProcedura( self.stato.procedura, 'Deleted', modalità )
						.done( function () {
							self.progressHandler.tick();
							self.progressHandler.showMessage( self.agent.msg( 'toolbox-close-protect-wait' ) );
							self.agent.proteggiPagina( self.stato.procedura )
							.done( function () {
								self.progressHandler.tick();
								self.progressHandler.showMessage( self.agent.msg( 'toolbox-purge-wait' ) );
								self.agent.purgaPagina( self.stato.procedura )
								.done( function () {
									self.progressHandler.tick();
									window.location.reload();
								} );
							} )
							.fail( function ( code, info ) {
								self.progressHandler.showError( self.agent.errorMsg( 'toolbox-close-protect-error', info ) );
							} );
						} )
						.fail( function ( code, info ) {
							self.progressHandler.showError( self.agent.errorMsg( 'toolbox-close-notice-error', info ) );
						} );
					} )
					.fail( function ( code, info ) {
						self.progressHandler.showError( self.agent.errorMsg( 'toolbox-close-deletetalk-error', info ) );
					} );
				} )
				.fail( function ( code, info ) {
					self.progressHandler.showError( self.agent.errorMsg( 'toolbox-close-delete-error', info ) );
				} );
			};

			/**
			 * Genera il pulsante per rispondere "sì" alla cancellazione della pagina.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			SysopButtons.prototype.getCloseDeleteYesButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-close-delete-yes' ),
					flags: [ 'destructive' ]
				} )
				.on( 'click', this.onCloseDeleteYesButtonClick.bind( this ) );
			};

			/**
			 * Aggiorna il template Cronologia valutazioni in seguito al mantenimento della pagina.
			 *
			 * @private
			 * @return {jQuery.Promise}
			 */
			SysopButtons.prototype.updateAssessmentsHistoryKept = function () {
				var parametri, sections;

				parametri = {
					collegamento: this.stato.procedura
				};

				if ( this.stato.votazione ) {
					sections = this.voteCounter.getSections();

					if ( sections.Cancellare === undefined || sections.Mantenere === undefined ) {
						return $.Deferred.reject();
					}

					parametri.si = sections.Cancellare.votes;
					parametri.no = sections.Mantenere.votes;
				}

				return this.agent.aggiornaCronologiaValutazioni( this.stato.pagina, parametri );
			};

			/**
			 * Gestisce il clic sul pulsante per rispondere "no" alla cancellazione della pagina.
			 *
			 * @private
			 */
			SysopButtons.prototype.onCloseDeleteNoButtonClick = function () {
				var self = this;

				self.progressHandler.showMessage( self.agent.msg( 'toolbox-close-kept-wait' ) );
				self.agent.terminaProcedura( self.stato.procedura, 'Kept', self.getModeId() )
				.done( function () {
					self.progressHandler.showMessage( self.agent.msg( 'toolbox-assessments-history-update-wait' ) );
					self.updateAssessmentsHistoryKept()
					.done( function () {
						self.progressHandler.showMessage( self.agent.msg( 'toolbox-close-protect-wait' ) );
						self.agent.proteggiPagina( self.stato.procedura )
						.done( function () {
							self.progressHandler.showMessage( self.agent.msg( 'toolbox-close-remove-notice-wait' ) );
							self.agent.rimuoviAvvisoVoce( self.stato.pagina )
							.done( function () {
								self.progressHandler.showMessage( self.agent.msg( 'toolbox-purge-wait' ) );
								self.agent.purgaPagina( self.stato.procedura )
								.done( function () {
									window.location.reload();
								} );
							} )
							.fail( function ( code, info ) {
								self.progressHandler.showError( self.agent.errorMsg( 'toolbox-close-remove-notice-error', info ) );
							} );
						} )
						.fail( function ( code, info ) {
							self.progressHandler.showError( self.agent.errorMsg( 'toolbox-close-protect-error', info ) );
						} );
					} )
					.fail( function ( code, info ) {
						self.progressHandler.showError( self.agent.errorMsg( 'toolbox-assessments-history-update-error', info ) );
					} );
				} )
				.fail( function ( code, info ) {
					self.progressHandler.showError( self.agent.errorMsg( 'toolbox-close-kept-error', info ) );
				} );
			};

			/**
			 * Genera il pulsante per rispondere "no" alla cancellazione della pagina.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			SysopButtons.prototype.getCloseDeleteNoButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-close-delete-no' ),
					flags: [ 'constructive' ]
				} )
				.on( 'click', this.onCloseDeleteNoButtonClick.bind( this ) );
			};

			/**
			 * Genera il pulsante per rispondere "annulla" alla cancellazione della pagina.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			SysopButtons.prototype.getCloseCancelButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-close-cancel' )
				} )
				.on( 'click', this.switchButtons.bind( this, true ) );
			};

			/**
			 * Gestisce il clic sul pulsante "chiudi la procedura".
			 *
			 * @private
			 */
			SysopButtons.prototype.onCloseButtonClick = function () {
				$( '<div>' )
				.append( [
					// Cancellare la pagina?
					this.agent.msg( 'toolbox-close-delete' ),
					'<br>',
					// Sì
					this.getCloseDeleteYesButton().$element,
					// No
					this.getCloseDeleteNoButton().$element,
					// Annulla
					this.getCloseCancelButton().$element
				] )
				.insertAfter( this.switchButtons() );
			};

			/**
			 * Genera il pulsante "chiudi la procedura" con conferma richiesta.
			 *
			 * @private
			 * @return {OO.ui.ButtonWidget}
			 */
			SysopButtons.prototype.getCloseButton = function () {
				return new OO.ui.ButtonWidget( {
					label: this.agent.msg( 'toolbox-close' ),
					flags: [ 'progressive' ]
				} )
				.on( 'click', this.onCloseButtonClick.bind( this ) );
			};
		},

		/**
		 * Aggiunge un link nella barra laterale per avviare una procedura guidata.
		 */
		addPortletLink: function () {
			var self = this;

			$( mw.util.addPortletLink( 'p-tb', '#', self.msg( 'menu-link-label' ), 'PDC-init', self.msg( 'menu-link-tooltip' ) ) )
			.click( function ( event ) {
				event.preventDefault();
				self.proceduraGuidata( mw.config.get( 'wgPageName' ) );
			} );
		},

		/**
		 * Se opportuno, aggiunge un link nella barra laterale ed esegue autoRfd.
		 */
		portletMode: function () {
			var self = this;

			self.agent.inCancellazione( mw.config.get( 'wgPageName' ) )
			.done( function ( val ) {
				if ( val === false ) {
					self.agent.verificaRequisiti()
					.done( function ( accettato ) {
						if ( accettato !== true ) {
							return;
						}
						self.addPortletLink();
						self.autoRfd();
					} );
				}
			} );
		},

		/**
		 * Restituisce il contatore di voti, creandolo se necessario.
		 *
		 * @private
		 * @return {VoteCounter}
		 */
		getVoteCounter: function () {
			if ( this.voteCounter === undefined ) {
				this.voteCounter = new VoteCounter( {
					$content: mw.util.$content,
					pageName: mw.config.get( 'wgPageName' ),
					voteNullItems: this.config.voteNullItems,
					voteSections: this.config.voteSections
				} );
			}
			return this.voteCounter;
		},

		/**
		 * Genera il box "Strumenti PDC" a partire dallo stato della procedura di cancellazione.
		 *
		 * @private
		 * @param {Object} stato - Lo stato della procedura di cancellazione
		 * @return {jQuery}
		 */
		getToolboxFromStatus: function ( stato ) {
			var creazione, apertaDaGiorni, $toolbox, statusMsg, days, user,
				prorogabile = false,
				adesso = new Date();

			if ( stato && stato.creazione && stato.creazione.timestamp ) {
				creazione = new Date( stato.creazione.timestamp );
				apertaDaGiorni = ( adesso - creazione ) / 86400000;
				if (
					stato.terminata === false &&
					!stato.votazione &&
					!stato.prorogata &&
					apertaDaGiorni >= this.config.minDeferrableDays
				) {
					prorogabile = true;
				}
			}
			$toolbox = $( '<div>' )
			.addClass( 'toccolours' )
			.css( 'font-size', '100%' )
			.append(
				$( '<h3>' )
				.text( this.msg( 'toolbox-title', stato.pagina ) )
			);
			statusMsg = this.msg( 'toolbox-open-generic' );
			if ( ProgressHandler === undefined ) {
				this.defineProgressHandler();
			}
			this.progressHandler = new ProgressHandler();
			this.progressHandler.on( 'message', function () {
				$toolbox.find( '.oo-ui-buttonElement' ).hide();
			} );
			$toolbox.prepend( this.progressHandler.$container );
			if ( stato.terminata ) {
				statusMsg = this.msg( 'toolbox-closed' );
			} else if ( apertaDaGiorni ) {
				if ( parseInt( apertaDaGiorni ) > 1 ) {
					days = this.msg( 'toolbox-days-ago', parseInt( apertaDaGiorni ) );
				} else if ( adesso.getDate() === creazione.getDate() ) {
					days = this.msg( 'toolbox-today' );
				} else {
					days = this.msg( 'toolbox-yesterday' );
				}
				if ( stato.creazione && stato.creazione.user ) {
					user = this.agent.linkUtente( stato.creazione.user );
				} else {
					user = this.msg( 'toolbox-user-unknown' );
				}
				statusMsg = this.msg( 'toolbox-open', days, user );
			}
			$( '<p>' )
			.append( statusMsg )
			.appendTo( $toolbox );

			if ( PublicButtons === undefined ) {
				this.definePublicButtons();
			}

			$toolbox
			.append( new PublicButtons( {
				agent: this.agent,
				progressHandler: this.progressHandler,
				prorogabile: prorogabile,
				protectOnCancel: this.sysop,
				stato: stato
			} ).$element );

			if ( stato.votazione && stato.terminata === false ) {
				// Strumenti per votare

				if ( VoteButtons === undefined ) {
					this.defineVoteButtons();
				}

				$toolbox
				.append( [
					$( '<h4>' )
					.text( this.msg( 'toolbox-vote' ) ),
					new VoteButtons( {
						agent: this.agent,
						pageName: mw.config.get( 'wgPageName' ),
						progressHandler: this.progressHandler,
						getVoteOutcome: this.config.getVoteOutcome,
						voteCounter: this.getVoteCounter()
					} ).$element
				] );
			}

			if ( this.sysop && stato.terminata === false ) {
				// Strumenti per amministratori

				if ( SysopButtons === undefined ) {
					this.defineSysopButtons();
				}

				$toolbox
				.append( [
					$( '<h4>' )
					.text( this.msg( 'toolbox-sysops' ) ),
					new SysopButtons( {
						agent: this.agent,
						progressHandler: this.progressHandler,
						stato: stato,
						voteCounter: this.getVoteCounter()
					} ).$element
				] );
			}

			return $toolbox;
		},

		/**
		 * Se opportuno, aggiunge il box "Strumenti PDC".
		 */
		toolboxMode: function () {
			var self = this;

			self.agent.verificaRequisiti()
			.done( function ( accettato ) {
				if ( accettato !== true ) {
					return;
				}
				self.agent.ottieniTitoloPagina( mw.config.get( 'wgPageName' ) )
				.done( function ( titoloPagina ) {
					self.agent.ottieniStatoProcedura( mw.config.get( 'wgPageName' ) )
					.done( function ( stato ) {
						stato.pagina = titoloPagina;
						self.getToolboxFromStatus( stato ).prependTo( '#mw-content-text' );
					} );
				} );
			} );
		},

		/**
		 * Se lo script è utile nella pagina, carica le dipendenze e lo avvia.
		 */
		preInit: function () {
			var self = this;
			if (
				// pagina esistente
				mw.config.get( 'wgArticleId' ) > 0 &&
				// in modalità di visualizzazione
				mw.config.get( 'wgAction' ) === 'view' &&
				// niente redirect
				mw.config.get( 'wgIsRedirect' ) === false &&
				// niente diff
				mw.util.getParamValue( 'diff' ) === null &&
				// solo ultime versioni
				mw.util.getParamValue( 'oldid' ) === null
			) {
				mw.loader.using( 'mediawiki.user' )
				.done( function () {
					var sp, modes, values;

					// solo utenti registrati
					if ( mw.user.isAnon() !== false ) {
						return;
					}

					sp = mw.config.get( 'wgPageName' ).replace( /_/g, ' ' ).split( '/' );
					modes = {
						// visualizzare la casella "Strumenti PDC"?
						toolbox: ( sp.length > 1 && sp[ 0 ] === self.config.base ),
						// aggiungere il link per la finestra di dialogo alla sezione "Strumenti" della barra laterale?
						portlet: (
							mw.config.get( 'wgIsArticle' ) === true &&
							self.config.actInNamespaces.indexOf( mw.config.get( 'wgNamespaceNumber' ) ) !== -1
						)
					};
					values = $.map( modes, function ( val ) {
						return val;
					} );
					if ( values.indexOf( true ) !== -1 ) {
						mw.loader.using( self.dependencies.general )
						.done( function () {
							self.init( modes );
						} );
					}
				} );
			}
		},

		/**
		 * Prepara i messaggi nella lingua dell'utente e nelle lingue alternative.
		 *
		 * @private
		 * @return {mw.Map}
		 */
		getMessagesWithFallback: function () {
			var i, langCode,
				chain = mw.language.getFallbackLanguageChain(),
				messages = new mw.Map();

			for ( i = chain.length - 1; i >= 0; i-- ) {
				langCode = chain[ i ];
				if ( this.i18n.hasOwnProperty( langCode ) ) {
					messages.set( this.i18n[ langCode ] );
				}
			}

			return messages;
		},

		/**
		 * Avvia lo script senza caricare le dipendenze.
		 *
		 * @private
		 * @param {Object} modes - Le modalità da attivare
		 */
		init: function ( modes ) {
			this.i18n = this.getMessagesWithFallback();
			this.agent = new PdcAgent( {
				api: this.api,
				assessmentsHistoryEditor: new AssessmentsHistoryEditor(),
				i18n: this.i18n,
				l10n: this.config,
				namespaces: this.namespaces
			} );
			if ( modes.portlet === true ) {
				this.portletMode();
			}
			if ( modes.toolbox === true ) {
				this.toolboxMode();
			}
		}
	};
	PDC.preInit();
	window.PDC = PDC;
}( mediaWiki, jQuery ) );
// </nowiki>