MediaWiki:Gadget-PermissionOTRS.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/** PermissionOTRS.js
* @description: Adds VRTS permission template via API. Other templates such as {VRTS pending} will be removed.
* @revision: 23:07, 16 August 2018 (UTC)
* @source: https://commons.wikimedia.org/wiki/MediaWiki:Gadget-PermissionOTRS.js
* @license: under the terms of the MIT license
* @authors:
*	Maintainer: [[User:Perhelion]]
*	Created by Bryan Tong Minh
*	Amended by [[:de:User:DerHexer]], [[User:Guandalug]], DieBuche, [[User:Steinsplitter]], [[User:Rillke]]
*	Rewritten by Perhelion, Fordította: :hu:User:Bencemac
*	Structured Data on Commons support added by [[User:Majavah]]
* @required modules: mediawiki.util, oojs-ui-core, oojs-ui-windows, mediawiki.api
* <nowiki>
*/
/* eslint indent:["error","tab",{"outerIIFEBody":0}] */
/* eslint no-underscore-dangle:0 */
/* global mediaWiki, jQuery, OO */
/* jshint curly:false, scripturl:true, forin:false */
( function ( $, mw ) {
'use strict';

var conf = mw.config.get( [
		'wgCategories',
		'wgIsArticle',
		'wgGlobalGroups',
		'wgNamespaceNumber',
		'wgPageName',
		// 'wgRestrictionEdit',
		'wgUserGroups',
		'wgUserLanguage',
		'wgUserName',
		'wgArticleId',
	] ),
	user = conf.wgUserName,
	page = conf.wgPageName,
	sLink = '[[COM:Volunteer Response Team|VRTS]] ',
	oChangeTag = 'PermissionOTRS',
	ticket,
	content,
	// timestamp,
	reason = '',
	deferred = null,
	editform = null,
	received = false,
	ticketWikidataPropertyNumber = 'P6305', 
	i18n = {
		de: {
			apiErrorCode: 'Fehler bei der Verarbeitung der Anfrage.\n\t API-Fehlercode: %CODE%\n\n\tBitte versuche es erneut.',
			confirmDR: 'Melde dies beim Löschantrag?',
			digit: 'Ticket-ID (16-stellige Nummer):',
			Done: 'Fertig!',
			emptyResponse: 'Eine leere Antwort vom Server erhalten.',
			// Error: 'Fehler!',
			fail: 'fehlgeschlagen',
			gotContent: 'Inhalt der Seite erhalten…',
			// INVALIDID: 'INVALIDE ID',
			invalidId: 'Es muss eine gültige 16-stellige Ticket-Nummer eingeben werden.',
			noAuth: 'Du bist für diese Funktion nicht berechtigt!',
			noFound: 'Keine passende Stelle zum Einfügen der Vorlage gefunden! Bitte von Hand hinzufügen.',
			process: '…Anfrage zur Kennzeichnung mit Ticket#: %TICKET%',
			Processing: 'Verarbeitung …',
			reason: 'Gib eine Begründung?',
			sameTicket: 'Gleiches Ticket bereits hinzugefügt.',
			success: 'VRTS-Ticket erfolgreich eingefügt.'
		},
		hu: {
			apiErrorCode: 'Hiba lépett fel a művelet végrehajtása során.\n\t API-hibakód:%CODE%\n\n\tKérlek, próbáld újra!',
			digit: 'Ticket ID (16 jegyű szám):',
			Done: 'Kész!',
			emptyResponse: 'Üres válasz érkezett a szervertől.',
			Error: 'Hiba!',
			fail: 'hiba',
			gotContent: 'Az oldal tartalmának betöltése…',
			invalidId: 'Adj meg egy érvényes 16 számjegyű számot.',
			noAuth: 'Nincs jogosultságod a művelet végrehajtásához!',
			noFound: 'Nem található megfelelő hely a sablon beillesztésére. Írd be kézzel.',
			process: 'Az összekapcsolás a(z) %TICKET% számú jeggyel elkezdődött',
			Processing: 'Folyamatban…',
			sameTicket: 'A jegy már szerepel az oldalon.',
			success: 'VRTS jegy sikeresen hozzáadva.'
		},
		pl: {
			apiErrorCode: 'Wysŧąpił błąd podczas dodawania szablonu z biletem VRTS.\n\t API-error-code: %CODE%\n\n\tProszę spróbować ponownie.',
			confirmDR: 'Powiadamić dyskusję o kasowaniu pliku?',
			digit: 'Numer biletu VRTS (16-cyfrowy):',
			Done: 'Gotowe!',
			emptyResponse: 'Otrzymano pustą odpowiedź z serwera.',
			Error: 'Błąd!',
			fail: 'błąd',
			gotContent: 'Mam zawartość strony…',
			INVALIDID: 'NIEPRAWIDŁOWY NUMER',
			invalidId: 'Musisz podać poprawny 16-cyfrowy numer biletu.',
			noAuth: 'Nie masz uprawnień do tej funkcji!',
			noFound: 'Nie znaleziono odpowiedniego miejsca na dodanie szablonu! Dodaj ręcznie. ',
			process: '…szablon z biletem VRTS Nr: %TICKET%',
			Processing: 'Dodaję…',
			reason: 'Podać powód?',
			sameTicket: 'Ten sam bilet już dodano.',
			sdcAdding: 'Dodaję deklarację SDC ...',
			sdcAdded: 'Deklaracja SDC została dodana, kontynuuję z dodawaniem szablonu',
			success: 'Bilet VRTS został pomyślnie wstawiony.'
		},
        vi: {  // Translation by [[User:Tryvix1509]]
            apiErrorCode: 'Có lỗi khi xử lý yêu cầu. Vui lòng thử lại.\n\t Mã lỗi API: %CODE%',
            confirmDR: 'Đề nghị xóa tập tin này?',
            digit: 'Nhập ID thẻ (gồm 16 chữ số):',
            Done: 'Xong!',
            emptyResponse: 'Máy chủ đã trả về phản hồi rỗng.',
            Error: 'Lỗi!',
            fail: 'thất bại',
            gotContent: 'Đang lấy nội dung trang…',
            INVALIDID: 'ID THẺ KHÔNG HỢP LỆ',
            invalidId: 'Bạn phải nhập số thẻ hợp lệ có 16 chữ số.',
            noAuth: 'Bạn không có quyền thực hiện tác vụ này!',
            noFound: 'Không tìm thấy vị trí phù hợp để chèn bản mẫu! Xin hãy tự thêm vào thủ công. ',
            process: '…đang yêu cầu gắn thẻ Ticket#: %TICKET%',
            Processing: 'Đang xử lý…',
            reason: 'Cung cấp lý do?',
            sameTicket: 'Thẻ này đã được thêm vào rồi.',
            sdcAdding: 'Đang thêm tuyên bố cho Dữ liệu có cấu trúc ...',
            sdcAdded: 'Đã thêm tuyên bố cho Dữ liệu có cấu trúc, đang lưu trang',
            success: 'Thẻ VRTS đã được chèn vào tập tin.'
        },
		apiErrorCode: 'There was an error processing your request.\n\t API-error-code: %CODE%\n\n\tPlease try again.',
		confirmDR: 'Report this to the deletion request?',
		digit: 'Ticket ID (16 digit number):',
		Done: 'Done!',
		emptyResponse: 'Got an empty response from the server.',
		Error: 'Error!',
		fail: 'fail',
		gotContent: 'Got page contents…',
		INVALIDID: 'INVALID ID',
		invalidId: 'You must enter a valid 16-digit ticket number.',
		noAuth: 'You are not authorized for this function!',
		noFound: 'No suitable place found to insert the template! Please add by hand.',
		process: '…request to tag with Ticket#: %TICKET%',
		Processing: 'Processing…',
		reason: 'Give a reason?',
		sameTicket: 'Same ticket already added.',
		sdcAdding: 'Adding SDC claim...',
		sdcAdded: 'SDC claim saved, continuing with saving the page',
		success: 'VRTS ticket successfully inserted.'
	};

if ( conf.wgNamespaceNumber !== 6 || $.inArray( 'autoconfirmed', conf.wgUserGroups ) === -1 || !user ) {
	return;
}
if ( !conf.wgIsArticle ) {
	editform = $( '#wpTextbox1' );
}

function _apiFail( code, r ) {
	if ( code === 'http' ) {
		r = 'HTTP error: '   r.textStatus;
	} else if ( code === 'ok-but-empty' ) {
		r = i18n.emptyResponse;
	} else {
		r = 'API '   i18n.fail   ': '   code;
	}
	mw.log.warn( r );
	mw.notify( i18n.apiErrorCode.replace( '%CODE%', code ), {
		title: i18n.Error,
		autoHide: 0,
		type: 'error'
	} );
}

function doDoneMsg() {
	mw.notify( i18n.success, {
		title: i18n.Done
	} ).done( function () {
		// true if not edit-mode
		if ( deferred ) { location.reload(); }
	} );
}

function addHintOnDR( lastMatch ) {
	mw.loader.using( 'user.options', function () {
		var uSig = ( mw.user.options.get( 'fancysig' ) && mw.user.options.get( 'nickname' ).search( /^[ ']*\[\[/ ) ) ?
				' ' : ' --',
			DR = /subpage ?= ?([^|] )\|/.exec( lastMatch );
		DR = ( DR && ( DR = DR[ 1 ] ) ) || page;
		new mw.Api().edit(
			'Commons:Deletion_requests/'   DR.replace( / /g, '_' ),
			function ( revision ) {
				var text = revision.content;
				// if really open
				if ( text && !/\{\{[Dd]elf\}\}\s*$/.test( text ) ) {
					text  = '\n* [[File:Permission logo 2021.svg|26px|link=|VRTS]] '   ( received ?
						'There is an VRTS email received for “'   page.replace( /_/g, ' ' )   '” but not processed yet, [[ticket:'   ticket   ']].' :
						'I have just accepted permission for “'   page.replace( /_/g, ' ' )   '” under [[ticket:'   ticket   ']].' ); // and tagged it as such
					return {
						text: text   uSig   '~~~~\n', // nowiki
						summary: 'VRTS ticket received',
						watchlist: 'preferences',
						assert: 'user',
						notminor: 1
					};
				} else {
					mw.notify( 'Deletion request already closed.', {
						title: 'Notification failed ',
						type: 'warn'
					} );
					return $.Deferred().fail();
				}
			} )
			.done( function () {
				mw.notify( 'Notification successfully added.', {
					title: 'Notify on deletion request'
				} ).done( function () {
					doDoneMsg();
				} );
			} ).fail( _apiFail );
	} );
}

function doneMsg() {
	// Add message on possible DR
	if ( /\{\{[Dd]elete[^}\n]*\}\}/.test( content ) ) {
		var match = RegExp.lastMatch;
		OO.ui.confirm( i18n.confirmDR ).done( function ( confirmed ) {
			if ( confirmed ) {
				addHintOnDR( match );
			} else { doDoneMsg(); }
		} );
	} else { doDoneMsg(); }
}

function _getPage( VRTS ) {
	if ( !editform ) {
		mw.notify( VRTS.name   ': '   i18n.process.replace( /%TICKET%/, ticket ), {
			title: i18n.Processing
		} );

		new mw.Api().edit(
			page,
			function ( revision ) {
				mw.notify( i18n.gotContent, {
					title: i18n.Processing
				} );
				content = revision.content;
				// timestamp = revision.timestamp;
				deferred = $.Deferred();
				VRTS();
				return deferred.promise();
			} ).done( doneMsg ).fail( _apiFail );
	} else {
		content = editform.val();
		return VRTS();
	}
}

function _prompt( text, VRTS ) {
	var VRTSfield = $( '#field-vrts' ), // there is already a ticket
		name = 'VRTS',
		$select,
		type = 'mw-message-box mw-message-box-error',
		noprompt = false, // whether we expect input
		title = ( VRTS && VRTS.name ) ? VRTS.name : name;

	// try pre-fill the ticket
	if ( VRTSfield.length ) {
		ticket = VRTSfield.attr( 'title' ).replace( /[^\n] (\d{16})$/, '$1' );
	} else if ( editform && /\{\{permission[ _]received\|id=(\d{16}) [^}\n]*\}\}/i.test( editform.val() ) ) {
		ticket = RegExp.$1;
	} else if ( editform && /\{\{permission[ _]received\|year=\d{4}\|month=\w \|day=\d{1,2}\|1=(\d{16})[^}\n]*\}\}/i.test( editform.val() ) ) {
		ticket = RegExp.$1;
	}
	if ( !text ) {
		if ( received ) {
			$select = 1;
		}
		title = i18n.INVALIDID;
		text = i18n.invalidId;

	} else if ( text === 'FAIL' ) {
		title = text;
		text = i18n.noFound;
		noprompt = 1;
	} else {
		type = '';
	}

	if ( type ) {
		text = $( '<span>' )
			.addClass( type )
			.text( text );
	} else if ( received ) {
		// Reason checkbox
		$select = 1;
	}

	mw.loader.using( [ 'oojs-ui-core', 'oojs-ui-windows' ], function () {
		/* Code taken from oojs-ui-windows #OO-ui-method-prompt */
		function OOuiPrompt( text, options ) {
			var instance,
				manager = OO.ui.getWindowManager(),
				textInput = new OO.ui.TextInputWidget( ( options && options.textInput ) || {} ),
				textField = new OO.ui.FieldLayout( textInput, {
					align: 'top',
					label: text
				} ),
				dropDown,
				checkbox = options.textInput.checkbox,
				fieldset = [ textField ];

			if ( checkbox ) {
				dropDown = new OO.ui.DropdownInputWidget( {
					id: name   'combo',
					// label: '',
					disabled: true,
					options: [
						{ data: 'a', label: 'processing' },
						{ data: 'b', label: 'licence' },
						{ data: 'c', label: 'email' }
					]
				} );
				checkbox = new OO.ui.CheckboxInputWidget( {
					id: name   'reason'
				} ).on( 'change', function () {
					reason = dropDown.isDisabled();
					dropDown.setDisabled( !reason );
					// $select.toggle();
				} );

				checkbox = new OO.ui.FieldLayout( checkbox, { label: i18n.reason, align: 'inline' } );
				$select = dropDown.$element;
				fieldset.push( checkbox );
				fieldset.push( dropDown );
			}

			fieldset = new OO.ui.FieldsetLayout( {/* label: 'Checkbox'*/} )
				.addItems( fieldset );

			instance = manager.openWindow( 'message', $.extend( {
				title: textInput.title,
				message: fieldset.$element
			}, options ) );

			instance.opened.then( function () {
				textInput.on( 'enter', function () {
					manager.getCurrentWindow().close( { action: 'accept' } );
				} );
				textInput.focus();
			} );

			return instance.closed.then( function ( data ) {
				return data && data.action === 'accept' ? textInput.getValue() : null;
			} );
		}

		if ( noprompt ) {
			OO.ui.alert( text );
		} else {
			new OOuiPrompt( text, {
				textInput: {
					title: title,
					maxLength: 23, // For possible pre-phrase "Ticket#"
					validate: /^\s*(Ticket)?#?\d{16}\s*$/,
					checkbox: !!$select,
					value: ticket
				}
			} ).done( function ( result ) {
				ticket = $.trim( result ).replace( /^(Ticket)?#?/, '' ); // clean up input
				reason = ( reason && typeof $select === 'object' ) ? $select.find( 'option:selected' ).text() : '';
				if ( ticket ) { // Check ticket validity
					if ( /^\d{16}$/.test( ticket ) ) {
						_getPage( VRTS );
					} else { // Try again
						_prompt( '', VRTS );
					}
				}
			} );
		}
	} );
}

function _savePage( summary ) {
	if ( deferred ) {
		deferred.resolve( {
			text: content,
			summary: summary,
			watchlist: 'preferences',
			tags: oChangeTag,
			notminor: 1
		} );
	} else { // In edit-mode
		$( '#wpMinoredit' ).prop( 'checked', 0 );
		editform.val( content );
		$( '#wpSummary' ).val( function ( i, v ) {
			return $.trim( v.replace( summary, '' )   ' '   summary );
		} );
		doneMsg();
	}
}

function _cleanUp( text ) {
	// ToDo: maybe here could be loaded another general cleanup module!?
	return $.trim( text
		.replace( /== ?Summary ?==/, '=={{int:filedesc}}==' )
		.replace( /== ?Licensing ?==/, '=={{int:license-header}}==' )
		.replace( /\{\{(Permission)[ _-](pending|received)[^}\n]*\}\}\s?/ig, '' )
		.replace( /\{\{[Nn]o[ _](VRTS[ _])?permission[^}\n]*\}\}\s*/g, '\n' )
		.replace( /<(!--|nowiki>)\s*\{\{([Pp]ermission[ _]?)?OTRS ?\| ?id= ?\d \s*[^}\n]*\}\}\s*(--|<\/nowiki)>\s?/g, '' )
		// Remove problem since tags (X-To-DR string)
		.replace( /\{\{\s*[Nn]o[ _](source|permission|license)([ _]since)?\s*(\|[^}\n] )?\}\}\s?/g, '' )
		// Remove Copyvio (X-To-DR string)
		.replace( /\{\{\s*(Copyviol?|Copyright(?!ed)|Copyrighted(?! free use)|Screenshot|Icon|Logo|Logo-Germany|(?:Non-free video |DVD )?Cover|Db-f9|db-copyvio|Vio(?:lation)?)\s*(\|[^\n] |[^}\n]*)\}\}\s?/i, '' )
		// Remove Speedy (X-To-DR string)
		.replace( /\{\{\s*(Speedy(?:[ -]?delet(?:e|ion))?|Speedilydelete|Noncommercial|Nonderivative|löschen|db|spd|qd|Sdelete|SLA|Spdel|Ek|Destruir)\s*(\|[^\n] |[^}\n]*)\}\}\s?/i, '' )
		.replace( /\{\{\s*SD\s*(\|[^\n] |[^}\n]*)\}\}\s?/, '' ) ); // Remove SD (not Sd)
}

function _addPermission( template, summary ) {
	var text = _cleanUp( content ),
		textBefore = text,
		regP = /( )*\|\s*[Pp]ermission( )*=\s*([^\n]*\n)/,
		regA = /( )*\|\s*[Aa]uthor( )*=\s*[^\n]*\n/g; // fallback position, if no permission parameter
	if ( !regP.test( text ) ) {
		if ( !regA.test( text ) ) {
			return _prompt( 'FAIL' );
		}
		var l = regA.lastIndex,
			s1 = RegExp.$1 || '', // leading whitespaces as indent?
			s2 = ( RegExp.$2 ) ? ' ' : '', // whitespace on parameter?
			sr = text.substr( l ).search( /^\s*(\||\}\})/m ); // try to got at template end
		if ( sr >= 0 ) {
			sr  = l; // insert to the end of information template
			text = text.substr( 0, sr )   s1   '|permission'   s2   '='   s2   template   '\n'   text.substr( sr );
		} else {
			return _prompt( 'FAIL' );
		}
	} else {
		if ( text.indexOf( template ) > 0 ) {
			return mw.notify( i18n.sameTicket, {
				title: 'VRTS '   i18n.fail   '…',
				type: 'warn'
			} );
		}
		text = text.replace( regP, '$1|permission$2=$2'   template   '\n$3' );
	}
	if ( textBefore === text ) {
		return _prompt( 'FAIL' );
	}
	content = text;
	_savePage( sLink   summary );
}

var PermissionVRTS = function PermissionVRTS() {
		mw.notify( i18n.sdcAdding, {
			title: i18n.Processing
		} );

		var entityId = 'M'   conf.wgArticleId,
			guidGenerator = new wikibase.utilities.ClaimGuidGenerator( entityId ),
			guid = guidGenerator.newGuid(),
			claim = JSON.stringify( {
				type: 'statement',
				id: guid,
				rank: 'normal',
				mainsnak: {
					snaktype: 'value',
					property: ticketWikidataPropertyNumber,
					datatype: 'external-id',
					datavalue: {
						type: 'string',
						value: ticket,
					},
				},
			} );
		
		new mw.Api().postWithEditToken( {
			action: 'wbsetclaim',
			id: entityId,
			claim: claim,
			summary: 'add [[COM:Volunteer Response Team|VRTS]] ticket number as an [[Commons:Structured data|SDC]] claim',
			assert: 'user',
			tags: oChangeTag,
		} )
			.done( function () {
				mw.notify( i18n.sdcAdded, {
					title: i18n.Processing
				} );

				_addPermission( '{{PermissionTicket|id='   ticket   '|user='   user   '}}', 'permission added' );
			})
			.fail( _apiFail );
	},

	VRTSreceived = function VRTSreceived() {
		_addPermission(
			'{{Permission received|id='   ticket  
			'|year={{subst:'  
			'#time:Y}}|month={{subst:'  
			'#time:F}}|day={{subst:'  
			'#time:j}}'   ( reason ? '|reason='   reason : '' )  
			'|user='   user   '}}',
			'email received but not processed yet' );
	};

function dialogVRTS( e, VRTS ) {
	e.preventDefault();
	// Merge languages
	// TODO: could be improved with mw.language.getFallbackLanguages
	$.extend( true, i18n, i18n[ conf.wgUserLanguage ] );

	function chkuGrp( b ) {
		if ( b ) {
			_prompt( i18n.digit, VRTS );
		} else {
			mw.notify( i18n.noAuth, {
				title: 'VRTS '   i18n.fail   '!',
				type: 'warn'
			} );
		}
	}
	mw.loader.using( [ 'mediawiki.api' ], function () {
		if ( $.inArray( 'vrt-permissions', conf.wgGlobalGroups ) > -1 || $.inArray( 'sysop', conf.wgUserGroups ) > -1 ) {
			chkuGrp( 1 );
		} else { // Fallback?
			new mw.Api().get( {
				meta: 'globaluserinfo',
				guiuser: user,
				guiprop: 'groups'
			} ).done( function ( r ) {
				chkuGrp( ( $.inArray( 'vrt-permissions', r.query.globaluserinfo.groups ) > -1 ) );
			} ).fail( function ( code, r ) {
				_apiFail( code, r );
			} );
		}
	} );
}

mw.loader.using( [ 'mediawiki.util', 'wikibase.utilities.ClaimGuidGenerator' ], function () {
	var bReceive,
		bConfirm; // booleans; add link only if needed

	$.each( conf.wgCategories, function ( i, text ) {
		if ( text && !( bReceive && bConfirm ) ) { // Check Cats
			if ( /^Items with (ticket )?VRTS permission confirmed/.test( text ) ) {
				bConfirm = bReceive = true;
			} else if ( /^Permission received/.test( text ) ) {
				bReceive = true;
			}
		}
	} );

	if ( !bConfirm ) {
		bConfirm = [ $( mw.util.addPortletLink( 'p-tb', '#', 'Permission VRTS' ) )
			.on( 'click', function ( e ) {
				received = 0;
				dialogVRTS( e, PermissionVRTS );
			} ) ];
		if ( !bReceive ) {
			bConfirm[ 1 ] = $( mw.util.addPortletLink( 'p-tb', '#', 'VRTS received' ) )
				.on( 'click', function ( e ) {
					received = 1;
					dialogVRTS( e, VRTSreceived );
				} );
		}
		$.each( bConfirm, function ( i, link ) {
			link.find( 'a' ).css( 'color', '#D82' );
		} );
	}
} );
}( jQuery, mediaWiki ) );
// EOF </nowiki>