Jump to content

User:TheDJ/RemoveGPS.js

From Wikimedia Commons, the free media repository
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.
/**
 * RemoveGPS.js - upload a new version of an image file that has GPS (coordinate)
 * data removed from its EXIF metadata, then remove that data from the file page's wikitext
 * and structured data as well.
 *
 * @author Yaron Koren
 * @license MIT
 */

( function( mw, $ ) {

	const removeGPSMessages = {
		en: {
			"remove-gps-upload-summary": "Uploaded new version of image, with GPS metadata removed, via $1",
			"remove-gps-edit-summary": "Removed {{location}} template from page via $1",
			"remove-gps-tasks-header": "Doing the following tasks:",
			"remove-gps-upload-task": "Uploading new version of the file with GPS EXIF data removed",
			"remove-gps-edit-task": "Replacing {{location}} template in the page text with {{location withheld}}",
			"remove-gps-structured-data-task": "Removing the \"coordinates of the point of view\" (P1259) value from this page's structured data",
			"remove-gps-notify-admins-task": "Notifying Commons administrators to hide old revisions of the file and wiki page",
			"remove-gps-page-will-reload": "The page will reload on completion.",
			"remove-gps-notify-admins":
				"Request Wikimedia Commons administrators to delete previous revisions of the file and wiki page that still contain the GPS data",
			"remove-gps-notify-admins-warning": "(note that this will allow administrators to see your email address, if you have one set)",
			"remove-gps-button-label": "Upload a new version of this file with GPS metadata removed"
		},
	};

	const rgpsUserLang = mw.config.get("wgUserLanguage") || "en";
	mw.messages.set(removeGPSMessages[rgpsUserLang] || removeGPSMessages.en);

	// Make sure OOUI is loaded, because we are using its CSS.
	mw.loader.using( [ 'oojs-ui-core', 'oojs-ui-widgets' ] ).then( function () {

		// There is a chained set of commands once the button is pressed, in the following order:
		// 1. Get the new, GPS-less version of the file (and possibly notify the Commons admins).
		// 2. Upload the new version of the file.
		// 3. Get the current wikitext of the page.
		// 4. Save the page, with the {{location}} call in the wikitext replaced with {{location withheld}}.
		// 5. Get the claim ID(s) for the "coordinates of the point of view" value in the page's structured data.
		// 6. Remove the claim for each such ID.
		// 7. Reload the page.

		const api = new mw.Api({ userAgent: 'RemoveGPS', parameters: { formatversion: 2 } });
		const removeGPS = {};
		removeGPS.namespaceName = mw.config.get('wgCanonicalNamespace');
		removeGPS.pageName = mw.config.get('wgTitle');
		removeGPS.gpsRemovalToolURL = "https://exif-gps-removal.toolforge.org/?file=" + encodeURIComponent(removeGPS.pageName);
		removeGPS.toolLink = '[[Commons:RemoveGPS|RemoveGPS]]'

		// Step 1
		removeGPS.getNewVersionOfFile = async function() {
			const notifyAdmins = $('#gps-removal-checkbox').is(':checked');
			if ( notifyAdmins ) {
				removeGPS.gpsRemovalToolURL += '&notify=yes&user=' + encodeURIComponent(mw.config.get('wgUserName'));
				const response = await api.get({
					action: 'query',
					meta: 'userinfo',
					uiprop: 'email'
				});
				const emailAddress = response.query.userinfo.email;
				removeGPS.gpsRemovalToolURL += '&email=' + encodeURIComponent(emailAddress);
			}
			removeGPS.replaceButtonWithText( notifyAdmins );
			$.ajax({
				url: removeGPS.gpsRemovalToolURL,
				method: "GET",
				xhrFields: {
					responseType: 'blob'
				},
				success: function(result) {
					removeGPS.uploadNewVersionOfFile(result);
				}
			});
		}

		// Step 2
		removeGPS.uploadNewVersionOfFile = function(newFileContent) {
			api.upload( newFileContent, {
				filename: removeGPS.pageName,
				comment: mw.msg("remove-gps-upload-summary", removeGPS.toolLink),
				ignorewarnings: true // Ignore warnings like duplicate filename
			}).done(function(data) {
				removeGPS.getPageText();
			}).fail(function(error, details) {
				if ( details.upload && details.upload.result == 'Success') {
					// The upload "failed", but actually succeeded, so keep going.
					// The "failure" is presumably because of the "duplicate filename" warning -
					// why isn't "ignorewarnings" taking effect here?
					removeGPS.getPageText();
				} else {
					console.error('Upload failed:', error);
					console.log(details);
				}
			});
		}

		// Step 3
		removeGPS.getPageText = function() {
			$.get('https://commons.wikimedia.org/wiki/File:' + removeGPS.pageName + '?action=raw', function(data) {
				const textWithoutLocationTemplate = data.replace( /\{\{[Ll]ocation\b[^}]*\}\}/g, '{{location withheld}}');
				if ( data != textWithoutLocationTemplate ) {
					removeGPS.replaceLocationTemplate(textWithoutLocationTemplate);
				} else {
					removeGPS.getClaimIDForGPSData();
				}
			});
		}

		// Step 4
		removeGPS.replaceLocationTemplate = function(textWithoutLocationTemplate) {
			api.postWithToken('csrf', {
				action: 'edit',
				title: 'File:' + removeGPS.pageName,
				summary: mw.msg("remove-gps-edit-summary", removeGPS.toolLink),
				text: textWithoutLocationTemplate
			}).then(function(data) {
				removeGPS.getClaimIDForGPSData();
			});
		}

		// Step 5
		removeGPS.getClaimIDForGPSData = function() {
			const entityId = mw.config.get( 'wbEntityId' );
			if ( !entityId ) {
				// No Wikibase entity on this page, so there's no structured data to remove.
				location.reload();
				return;
			}
			// P1259 = "coordinates of the point of view"
			api.get({
				action: 'wbgetclaims',
				entity: entityId,
				property: 'P1259'
			}).then(function(data) {
				if ( data.claims.P1259 != null ) {
					for (const claim of data.claims.P1259) {
						removeGPS.removeGPSFromStructuredData(claim.id);
					}
				} else {
					location.reload();
				}
			});
		}

		// Step 6
		removeGPS.removeGPSFromStructuredData = function(claimID) {
			api.postWithToken('csrf', {
				action: 'wbremoveclaims',
				claim: claimID,
				summary: 'via ' + removeGPS.toolLink
			}).then(function(data) {
				// Step 7
				// Reload the page so the user can see the new version of the metadata.
				location.reload();
			});
		}

		removeGPS.replaceButtonWithText = function( notifyAdmins ) {
			// Inject CSS for spinner animation
			// Copied from https://commons.wikimedia.org/wiki/User:TechieNK/Cat2Data.js
			const style = document.createElement("style");
			style.textContent = `
				.spinner-ring {
				  display: inline-block;
				  position: relative;
				  width: 24px;
				  height: 24px;
				  margin-left: 0.5rem;
				  vertical-align: middle;
				}
				.spinner-ring div {
				  transform-origin: 12px 12px;
				  animation: spinner-ring 1.2s linear infinite;
				}
				.spinner-ring div:after {
				  content: " ";
				  display: block;
				  position: absolute;
				  top: 1px;
				  left: 11px;
				  width: 2px;
				  height: 6px;
				  border-radius: 20%;
				  background: #1f1f1f;
				}
				${[...Array(12)]
				.map(
					(_, i) =>
						`.spinner-ring div:nth-child(${i + 1}) {
					transform: rotate(${i * 30}deg);
					animation-delay: -${(11 - i) * 0.1}s;
				  }`
				)
				.join("\n")}
				@keyframes spinner-ring {
				  0% { opacity: 1; }
				  100% { opacity: 0; }
				}
				.cdx-menu-item__text__description {
				  font-size: 0.85em;
				  white-space: normal;
				  overflow: hidden;
				  text-overflow: ellipsis;
				}
			  `;
			document.head.appendChild(style);
			let newText = '<div class="wbmi-link-notice oo-ui-widget oo-ui-widget-enabled oo-ui-labelElement oo-ui-flaggedElement-warning oo-ui-iconElement oo-ui-messageWidget-block oo-ui-messageWidget">' +
				mw.msg('remove-gps-tasks-header') +
				'<ul>' +
				'<li>' + mw.msg('remove-gps-upload-task') +
				'<li>' + mw.msg('remove-gps-edit-task') +
				'<li>' + mw.msg('remove-gps-structured-data-task');
			if ( notifyAdmins ) {
				newText += '<li>' + mw.msg('remove-gps-notify-admins-task');
			}
			newText += '</ul>';
			newText += '<p>' + mw.msg('remove-gps-page-will-reload') + '</p>';
			newText += `
				  <div class="spinner-ring" style="width: 40px; height: 40px;">
					<div></div><div></div><div></div><div></div><div></div><div></div>
					<div></div><div></div><div></div><div></div><div></div><div></div>
				  </div>
			</div>
			  `;
			$('#gps-removal-button-wrapper').html(newText);
		}

		removeGPS.injectWrapperStyle = function() {
			const style = document.createElement("style");
			style.textContent = `
				#overall-wrapper {
				  background-color: var( --background-color-neutral, #eaecf0 );
				  color: var( --color-base, #202122 );
				  border: 1px solid var( --border-color-subtle, #c8ccd1 );
				  border-radius: 5px;
				  padding: 1.5em;
				  margin: 2em 5em;
				}
				@media ( max-width: 640px ) {
				  #overall-wrapper {
					margin: 1em 0.5em;
					padding: 1em;
				  }
				}
			  `;
			document.head.appendChild(style);
		}

		removeGPS.displayGPSRemovalButton = function() {
			removeGPS.injectWrapperStyle();
			$('.mw-imagepage-section-metadata').after('<div id="overall-wrapper">' +
				'<span id="gps-removal-checkbox-wrapper">' +
				'<input type="checkbox" id="gps-removal-checkbox" checked /><span id="gps-removal-post-checkbox-span"></span>' +
				'<label for="gps-removal-checkbox" style="white-space: normal;"> ' +
				mw.msg('remove-gps-notify-admins') + ' ' + mw.msg('remove-gps-notify-admins-warning') +  '</label> ' +
				'</span>' +
				'<span id="gps-removal-button-wrapper">' +
				'<input type="button" value="' + mw.msg('remove-gps-button-label') + '" id="gps-removal-button">' +
				'</span></div>');

			$('#gps-removal-checkbox-wrapper')
				.addClass("oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-checkboxInputWidget")
				.css('padding-bottom', '20px');
			$('#gps-removal-checkbox')
				.addClass("oo-ui-inputWidget-input");
			$('#gps-removal-post-checkbox-span')
				.addClass("oo-ui-checkboxInputWidget-checkIcon oo-ui-widget oo-ui-widget-enabled oo-ui-iconElement-icon oo-ui-icon-check oo-ui-iconElement oo-ui-labelElement-invisible oo-ui-iconWidget oo-ui-image-invert");

			$('#gps-removal-button-wrapper')
				.addClass( "oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-labelElement oo-ui-flaggedElement-progressive oo-ui-flaggedElement-primary oo-ui-buttonInputWidget");
			$('#gps-removal-button')
				.addClass("oo-ui-inputWidget-input oo-ui-buttonElement-button")
				.css('white-space', 'normal')
				.click(removeGPS.getNewVersionOfFile);
		}

		// Only show this button if it's a file, and if it holds GPS metadata, and the user uploaded the photo (or is
		// an administrator).
		if (removeGPS.namespaceName === 'File' &&
			($('#mw_metadata .exif-gpslatitude').length > 0 || $('#mw_metadata .exif-gpslongitude').length > 0 || $('#mw_metadata .exif-altitude').length > 0)) {
			$.ajax({
				url: removeGPS.gpsRemovalToolURL + "&check_user=" + mw.config.get('wgUserId'),
				method: "GET",
				success: function(result) {
					if ( result === '1' ) {
						removeGPS.displayGPSRemovalButton();
					}
				}
			});
		}

	});

}( mediaWiki, jQuery ) );