Revision 263322 of "Wikilivros:Caixa de areia" on ptwikibooks

/**
 * Adds two links on pages like [[Special:AbuseLog/123]] to mark log entries as 'correct' or 'false positive'
 * (workaround for [[bugzilla:28213]]
 * @author: [[User:Helder.wiki]]
 * @tracking: [[Special:GlobalUsage/User:Helder.wiki/Tools/AbuseLogStatus.js]] ([[File:User:Helder.wiki/Tools/AbuseLogStatus.js]])
 */
/*jshint browser: true, camelcase: false, curly: true, eqeqeq: true, immed: true, latedef: true, newcap: true, noarg: true, noempty: true, nonew: true, quotmark: true, undef: true, unused: true, strict: true, trailing: true, maxlen: 130, laxbreak: true, devel: true, evil: true, onevar: true */
/*global jQuery, mediaWiki */
( function ( mw, $ ) {
'use strict';

/* Translatable strings */
mw.messages.set( {
	'al-page-title': 'Wikipédia:Filtro_de_edições/Análise/$1/Registros',
	'al-summary': 'Status do registro [[Special:AbuseLog/$1|$1]]: $2' +
		' (edição feita com [[Special:PermaLink/36666969#Scripts|um script]])',
	'al-correct-template': '*{' + '{Ação|$1}}\n',
	'al-problem-template': '*{' + '{Ação|$1|erro=sim}}\n',
	'al-correct-template-with-note': '*{' + '{Ação|$1|nota=$2}}\n',
	'al-problem-template-with-note': '*{' + '{Ação|$1|erro=sim|nota=$2}}\n',
	// Keep this synced with the regex from [[User:Helder.wiki/Tools/AbuseFilterStats.js]]
	'al-template-regex': '\\* *\\{\\{ *[Aa]ção *\\|(?:.*?\\D)?($1)(?:\\D.*?)?\\}\\} *(?:\\n|$)',
	'al-analysis-page-regex': '^Wikipédia:Filtro de edições\\/Análise\\/(\\d+/\\d+)\\/Registros$',
	'al-page-header': '{' + '{Lista de falsos positivos (cabeçalho)}}\n\n',
	'al-section-title': 'Filtro $1',
	'al-page-edit-success': '<p>A página <a href="$1">foi editada</a>.</p>',
	'al-page-edit-conflict': 'Foi detectado um conflito entre edições. Por favor, tente novamente.',
	'al-page-edit-error': 'Houve um erro ao tentar editar ($1). Por favor, tente novamente.',
	'al-page-edit-error-unknown': 'Houve um erro desconhecido ao tentar editar. Por favor, tente novamente.',
	'al-date-error': 'O script só funciona se o formato das datas não for alterado nas preferências.',
	'al-page-content-error': 'A página de análises ainda não possui uma seção para este filtro ($1). ' +
		'Crie manualmente as seções de cada filtro antes de enviar uma nova análise.',
	'al-filter-count-error': 'Houve um erro ao tentar determinar quantos filtros existem atualmente ($1). ' +
		'Crie manualmente a página de análises com uma seção para cada filtro antes de enviar uma ' +
		'nova análise.',
	'al-log-false-positive': 'Um editor já identificou que este registro foi um falso positivo',
	'al-log-correct': 'Um editor já identificou que este registro estava correto',
	'al-log-false-positive-note': 'Um editor já identificou que este registro foi um falso positivo: $1',
	'al-log-correct-note': 'Um editor já identificou que este registro estava correto: $1',
	'al-header': 'Análise',
	'al-question': 'Este filtro deveria ter detectado esta ação?',
	'al-specific-question': 'Foi correto classificar esta ação como "$1"?',
	'al-correct-description': 'Marcar este registro como correto',
	'al-yes': 'Sim',
	'al-correct': 'Correto',
	'al-incorrect-description': 'Marcar este registro como falso positivo',
	'al-no': 'Não',
	'al-incorrect': 'Falso positivo',
	'al-placeholder': 'Observação sobre esta ação (se precisar)',
	'al-submit': 'Enviar',
	'al-submit-description': 'Enviar a sua análise (editará automaticamente a página apropriada)'
} );

var api, filter, revision, reTemplate, reDetailsPage, reFilterLink,
	curLogs = {};

function getPeriodFromTimestamp( ts ){
	var match, month,
		reDateNoPreference = /((?:jan|fever)eiro|março|abril|maio|ju[nl]ho|agosto|(?:setem|outu|novem|dezem)bro) de (\d{4})/;
	match = ts.match( reDateNoPreference );
	if( match ){
		month = $.inArray( match[1], mw.config.get( 'wgMonthNames' ) );
		if ( month < 10){
			month = '0' + month;
		}
		return match[2] + '/' + month;
	}
	mw.notify( mw.msg( 'al-date-error' ), {
		autoHide: false,
		tag: 'status'
	} );
	return false;
}

function doEdit ( params ){
	api.post( params )
	.done( function( data ) {
		var edit = data.edit,
			link;
		if ( edit && edit.result && edit.result === 'Success' ) {
			link = mw.util.wikiGetlink( edit.title ) +
				'?diff=' + ( edit.newrevid || 0 );
			mw.notify( $( mw.msg( 'al-page-edit-success', link ) ), {
				autoHide: false,
				tag: 'status'
			} );
		} else {
			mw.notify( mw.msg( 'al-page-edit-error-unknown' ), {
				autoHide: false,
				tag: 'status'
			} );
		}
	} )
	.fail( function( code ){
		if( code === 'editconflict' ){
			// TODO: Try again automatically (and no not remove the spinner during the process)!
			mw.notify( mw.msg( 'al-page-edit-conflict' ), {
				autoHide: false,
				tag: 'status'
			} );
			return;
		}
		mw.notify( mw.msg( 'al-page-edit-error', code ), {
			autoHide: false,
			tag: 'status'
		} );
	} )
	.always( function(){
		$.removeSpinner( 'af-status-spinner' );
		$( '#al-submit' ).removeAttr( 'disabled' );
	} );
}

function onClick (){
	var note, period,
		falsePositive = $( 'input[type="radio"]:checked' ).val() !== 'correct',
		defineStatus = function ( data ){
			var template, start, text,
				editParams = {
					action: 'edit',
					title: mw.msg( 'al-page-title', period ),
					section: filter,
					summary: mw.msg(
						'al-summary',
						revision,
						falsePositive ? mw.msg( 'al-incorrect' ) : mw.msg( 'al-correct' )
					),
					minor: true,
					watchlist: 'nochange',
					token: mw.user.tokens.get( 'editToken' )
				},
				page = data.query.pages[ data.query.pageids[0] ],
				isMissing = page.missing === '';
			if ( note ){
				note = note.replace( /\|/g, '{{!}}' );
				template = falsePositive
					? mw.message( 'al-problem-template-with-note', revision, note ).plain()
					: mw.message( 'al-correct-template-with-note', revision, note ).plain();
			} else {
				template = falsePositive
					? mw.message( 'al-problem-template', revision ).plain()
					: mw.message( 'al-correct-template', revision ).plain();
			}
			if ( isMissing ){
				api.get( {
					list: 'abusefilters',
					abfdir: 'older',
					abflimit: 1,
					abfprop: 'id'
				} )
				.done( function( data ){
					var i;
					text = mw.message( 'al-page-header' ).plain();
					for( i = 1; i <= filter; i++ ){
						text += '\n== ' + mw.msg( 'al-section-title', i ) + ' ==\n\n';
					}
					text += '\n' + template;
					for(; i <= data.query.abusefilters[0].id; i++ ){
						text += '\n== ' + mw.msg( 'al-section-title', i ) + ' ==\n\n';
					}
					editParams.text = text;
					doEdit( editParams );
				} )
				.fail( function ( code, data ) {
					mw.notify( mw.msg( 'al-filter-count-error', data.error.info ), {
						autoHide: false,
						tag: 'status'
					} );
					$.removeSpinner( 'af-status-spinner' );
				} );
			} else {
				text = page.revisions[0]['*'];
				text = text.replace( reTemplate, '' ) + '\n' + template;
				start = text.search( /^.*\{\{[Aa]ção/m );
				text = text.substr( 0, start ).replace( /\n+$/g, '\n\n' ) +
					text.substr( start )
						.split( '\n' )
						.sort()
						.join( '\n' )
						.replace( /^\n+/g, '' )
						// TODO: remove these temporary hacks
						.replace( /(^|\n)\*\s+\{\{Ação/g, '$1*{' + '{Ação' )
						.replace( /(\* *\{\{ *Ação *\| *(\d+)\D.+\n)(\* *\{\{ *Ação *\| *\2\D.+\n)+/g, '$1' );
				editParams.basetimestamp = page.revisions[0].timestamp;
				editParams.starttimestamp = page.starttimestamp;
				editParams.text = text;
				doEdit( editParams );
			}
		},
		getPageContent = function (){
			period = getPeriodFromTimestamp(
				$( 'fieldset' ).find( 'p:first span:first' )
					.contents().first()
					.text()
			);
			if( !period ){
				$( '#al-submit' ).removeAttr( 'disabled' );
				return;
			}
			$( '#al-submit' ).injectSpinner( 'af-status-spinner' );
			$( '#mw-content-text' ).find( 'fieldset p > span > a' ).each( function(){
				filter = $( this ).attr( 'href' ).match( /Especial:Filtro_de_abusos\/(\d+)$/ );
				if( filter && filter[1] ){
					filter = filter[1];
					return false;
				}
			} );
			api.get( {
				prop: 'info|revisions',
				rvprop: 'content|timestamp',
				intoken: 'edit',
				rvsection: filter,
				rvlimit: 1,
				indexpageids: true,
				titles: mw.msg( 'al-page-title', period )
			} )
			.done( defineStatus )
			.fail( function ( code, data ) {
				if ( code === 'rvnosuchsection' ){
					mw.notify( mw.msg( 'al-page-content-error', data.error.info ), {
						autoHide: false,
						tag: 'status'
					} );
				}
				$.removeSpinner( 'af-status-spinner' );
			} );
		};
	$( '#al-submit' ).attr( 'disabled', 'disabled' );
	note = $( '#al-note' ).val();
	mw.loader.using( [
		'mediawiki.api.edit',
		'jquery.spinner',
		'mediawiki.notify',
		'mediawiki.notification'
	], getPageContent );
}

function addAbuseFilterStatusLinks(){
	var desc = $( 'fieldset' ).find( 'p:first span:first' )
		.text().match( /Descrição do filtro: (.+?) \(/ );
	reTemplate = new RegExp( mw.message( 'al-template-regex', revision ).plain(), 'g' );
	$( 'fieldset h3' ).first().before(
		$( '<h3>' ).text( mw.msg( 'al-header' ) ),
		$( '<p>' ).text(
				desc && desc[1] ?
					mw.msg( 'al-specific-question', desc[1] ) :
					mw.msg( 'al-question' )
			)
			.append(
				$( '<br />' ),
				$( '<input>' ).attr( {
					'name': 'al-status',
					'id': 'al-status-correct',
					'type': 'radio',
					'value': 'correct'
				} ).prop( 'checked', true ),
				$( '<label>' ).attr( {
					'for': 'al-status-correct',
					'title': mw.msg( 'al-correct-description' )
				} ).text( mw.msg( 'al-yes' ) ),
				$( '<input>' ).attr( {
					'name': 'al-status',
					'id': 'al-status-incorrect',
					'type': 'radio',
					'value': 'incorrect'
				} ),
				$( '<label>' ).attr( {
					'for': 'al-status-incorrect',
					'title': mw.msg( 'al-incorrect-description' )
				} ).text( mw.msg( 'al-no' ) ),
				' ',
				$( '<input>' ).attr( {
					'type': 'text',
					'id': 'al-note',
					'placeholder': mw.msg( 'al-placeholder' ),
					'size': 50
				} ),
				$( '<input>' ).attr( {
					'type': 'submit',
					'value': mw.msg( 'al-submit' ),
					'id': 'al-submit',
					'title': mw.msg( 'al-submit-description' )
				} ).click( onClick )
			)
	);
}
function markAbuseFilterEntriesByStatus( texts ){
	mw.util.addCSS(
		'.af-log-false-positive { background: #FDD; } ' +
		'.af-log-correct { background: #DFD; }'
	);
	$( '#mw-content-text' ).find( 'li' ).each( function(){
		var log, $currentLi = $( this );
		$currentLi.find( 'a' ).each( function(){
			var note, period,
				match = $( this ).attr( 'href' )
					.match( reDetailsPage );
			if( match && match[1] ){
				log = match[1];
				reTemplate = new RegExp( mw.message( 'al-template-regex', log ).plain(), 'g' );
				period = curLogs[ log ].timestamp.substring( 0, 4 ) + '/' +
					curLogs[ log ].timestamp.substring( 5, 7 );
				match = texts[ period ].match( reTemplate );
				if( match ){
					note = match[0].match( /nota *= *(.+?) *(?:\||\}\} *(?:\n|$))/ );
					// Highlight log entries already checked
					if( /\| *erro *= *sim/.test( match[0] ) ){
						// add af-false-positive class
						$currentLi
							.addClass( 'af-log-false-positive' )
							.attr(
								'title',
								note ? mw.msg( 'al-log-false-positive-note', mw.html.escape( note[1] ) )
									: mw.msg( 'al-log-false-positive' )
							);
					} else {
						$currentLi
							.addClass( 'af-log-correct' )
							.attr(
								'title',
								note ? mw.msg( 'al-log-correct-note', mw.html.escape( note[1] ) )
									: mw.msg( 'al-log-correct' )
							);
					}
				}
				return false;
			}
		} );
	} );
}

function getSectionsFromPages( sections ){
	var // key, section,
		// pages = [],
		revParams = {
			action: 'query',
			prop: 'revisions',
			rvprop: 'content'
			// ,indexpageids: true
		};
	/*
	for( key in sections ){
		pages.push( mw.msg( 'al-page-title', key ) );
		if( sections[ key ].length === 1 ){
			// FIXME: Get only one section
		} else {
			// Get the whole page
		}
	}
	*/
	revParams.titles = Object.keys( sections ).join( '|' );
	api.get( revParams )
	.done( function( data ){
		var statusTexts = {},
			reAnalysisPage = new RegExp( mw.message( 'al-analysis-page-regex' ).plain() );
		$.each( data.query.pages, function( id ){
			var period,
				pg = data.query.pages[ id ];
			if ( pg.missing !== '' ){
				period = pg.title.match( reAnalysisPage );
				if( period && period[1] ){
					statusTexts[ period[1] ] = pg.revisions[0]['*'];
				}
			}
		} );
		markAbuseFilterEntriesByStatus( statusTexts );
	} );
}

function getPagesToCheck(){
	var d,
		query = mw.Uri().query,
		// The log with the provided offset is returned
		// by the API but not by the special page
		// Adding +1 or -1 ensures the last item from the
		// special page is also returned by the API
		offset = query.dir === 'prev' ? 1 : -1,
		abuseLogParams = {
			action: 'query',
			list: 'abuselog',
			aflprop: 'ids|timestamp',
			afldir: query.dir === 'prev' ? 'newer' : 'older',
			afllimit: query.limit || 50
		};
	if( query.wpSearchUser ){
		abuseLogParams.afluser = query.wpSearchUser;
	}
	if( query.wpSearchTitle ){
		abuseLogParams.afltitle = query.wpSearchTitle;
	}
	if( query.wpSearchFilter ){
		abuseLogParams.aflfilter = query.wpSearchFilter;
	}
	// FIXME: Is this correct? Are the other values of 'dir'?
	if( query.offset ){
		d = query.offset;
		d = new Date( Date.UTC(
			d.substring( 0, 4 ),
			d.substring( 4, 6 ) - 1,
			d.substring( 6, 8 ),
			d.substring( 8, 10 ),
			d.substring( 10, 12 ),
			// The special page returns logs whose timestamp < T (not <= T)
			parseInt( d.substring( 12, 14 ), 10 ) + offset
		) ).toISOString();
		abuseLogParams.aflstart = d;
	}
	api.get( abuseLogParams )
	.done( function( data ){
		var i, log, filter, period, page,
			sections = {};
		// FIXME: Sort the logs (mainly if query.dir === 'prev')
		for( i = 0; i < data.query.abuselog.length; i++ ) {
			log = data.query.abuselog[i];
			curLogs[ log.id ] = {
				filter: log.filter_id,
				timestamp: log.timestamp
			};
			// YYYY/MM
			period = log.timestamp.substring( 0, 4 ) + '/' +
				log.timestamp.substring( 5, 7 );
			filter = log.filter_id;
			page = mw.msg( 'al-page-title', period );
			if( sections[ page ] ){
				sections[ page ].push( filter );
			} else {
				sections[ page ] = [];
			}
		}
		getSectionsFromPages( sections );
	} );
}

if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'AbuseLog'
	&& mw.config.get( 'wgDBname' ) === 'ptwiki'
) {
	reDetailsPage = /Especial:Registro_de_abusos\/(\d+)$/;
	reFilterLink = /^\/wiki\/Especial:Filtro_de_abusos\/(\d+)$/;
	mw.loader.using( [ 'mediawiki.api', 'mediawiki.Uri' ], function(){
		api = new mw.Api();
		if ( mw.config.get( 'wgTitle' ) === 'Registro de abusos' ){
			$( getPagesToCheck );
		} else {
			revision = mw.config.get( 'wgPageName' ).match( reDetailsPage );
			if( revision && revision[1] ){
				revision = revision[1];
				$( addAbuseFilterStatusLinks );
			}
		}
	} );
}

}( mediaWiki, jQuery ) );