User:Yaron Koren/RemoveGPS.js

/**
 * 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, $ ) {
	
	// 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. Get the CSRF token.
	// 3. Upload the new version of the file.
	// 4. Get the current wikitext of the page.
	// 5. Save the page, with the {{location}} call in the wikitext replaced with {{location withheld}}.
	// 6. Get the claim ID(s) for the "coordinates of the point of view" value in the page's structured data.
	// 7. Remove the claim for each such ID.
	// 8. Reload the page.

	var removeGPS = {};
	removeGPS.namespaceName = mw.config.get('wgCanonicalNamespace');
	removeGPS.pageName = mw.config.get('wgTitle');
	removeGPS.gpsRemovalToolURL = "https://exif-gps-removal.toolforge.org/?file=" + removeGPS.pageName;
	removeGPS.apiURL = 'https://commons.wikimedia.org/w/api.php';
	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=' + mw.config.get('wgUserName');
        	const response = await $.ajax({
            	url: removeGPS.apiURL + '?action=query&meta=userinfo&uiprop=email&format=json',
            	method: 'GET'
        	});
        	const emailAddress = response.query.userinfo.email;
        	removeGPS.gpsRemovalToolURL += '&email=' + emailAddress;
		}
		removeGPS.replaceButtonWithText( notifyAdmins );
		$.ajax({
			url: removeGPS.gpsRemovalToolURL,
			method: "GET",
			xhrFields: {
				responseType: 'blob'
			},
			success: function(result) {
				removeGPS.getCSRFToken(result);
			}
		});
	}

	// Step 2
	removeGPS.getCSRFToken = function(newFileContent) {
		$.get( removeGPS.apiURL + '?action=query&meta=tokens&type=csrf&format=json', function(data) {
			const csrfToken = data.query.tokens.csrftoken;
			removeGPS.uploadNewVersionOfFile(newFileContent, csrfToken);
		});
	}

	// Step 3
	removeGPS.uploadNewVersionOfFile = function(newFileContent, csrfToken) {	
		const formData = new FormData();
    	formData.append('action', 'upload');
    	formData.append('filename', removeGPS.pageName);
    	formData.append('file', newFileContent);
    	formData.append('token', csrfToken);
    	formData.append('comment', 'Uploaded new version of image, with GPS metadata removed, via ' + removeGPS.toolLink)
    	formData.append('ignorewarnings', 1); // Optional: ignore some warnings like duplicate filename
	     $.ajax({
	     	url: removeGPS.apiURL,
    	    method: 'POST',
   			data: formData,
   			processData: false, // Prevents jQuery from trying to stringify the data
    		contentType: false, // Allows the browser to set the correct multipart/form-data header
        	success: function(result) {
        			removeGPS.getPageText(csrfToken);
        	}
    	});
	}
	
	// Step 4
	removeGPS.getPageText = function(csrfToken) {
		$.get('https://commons.wikimedia.org/wiki/File:' + removeGPS.pageName + '?action=raw', function(data) {
			console.log(data);
			const textWithoutLocationTemplate = data.replace( /{{[L|l]ocation.*}}/g, '{{location withheld}}');
			if ( data != textWithoutLocationTemplate ) {
				removeGPS.replaceLocationTemplate(textWithoutLocationTemplate, csrfToken);
			} else {
       			removeGPS.getClaimIDForGPSData(csrfToken);
			}
		});
	}
	
	// Step 5
	removeGPS.replaceLocationTemplate = function(textWithoutLocationTemplate, csrfToken) {
		$.ajax({
		    url: removeGPS.apiURL,
   			method: 'POST',
   			data: {
       			action: 'edit',
		        title: 'File:' + removeGPS.pageName,
   			    summary: 'Removed {{location}} template from page via ' + removeGPS.toolLink,
       			text: textWithoutLocationTemplate,
       			token: csrfToken
   			},
   			success: function(data) {
       			removeGPS.getClaimIDForGPSData(csrfToken);
   			}
		});
	}
	
	// Step 6
	removeGPS.getClaimIDForGPSData = function(csrfToken) {
		// P1259 = "coordinates of the point of view"
		$.get( removeGPS.apiURL + '?action=wbgetclaims&entity=' + mw.config.get( 'wbEntityId' ) +
		'&property=P1259&format=json', function(data) {
			if ( data.claims.P1259 != null ) {
				for (const claim of data.claims.P1259) {
					removeGPS.removeGPSFromStructuredData(claim.id, csrfToken);
				}
			} else {
				location.reload();
			}
		});
	}

	// Step 7
	removeGPS.removeGPSFromStructuredData = function(claimID, csrfToken) {
		$.ajax({
		    url: removeGPS.apiURL,
   			method: 'POST',
   			data: {
       			action: 'wbremoveclaims',
		        claim: claimID,
   			    summary: 'via ' + removeGPS.toolLink,
       			token: csrfToken
   			},
   			success: function(data) {
       			// Step 8
   				// 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">
				Doing the following tasks:
				<ul>
					<li>Uploading new version of the file with GPS EXIF data removed
					<li>Replacing {{location}} template in the page text with {{location withheld}}
					<li>Removing the "coordinates of the point of view" (P1259) value from this page's structured data
              `;
		if ( notifyAdmins ) {
			newText += '<li>Notifying Commons administrators to hide old revisions of the file and wiki page';
		}
		newText += `
				</ul>
				<p>The page will reload on completion.</p>
                  <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.displayGPSRemovalButton = function() {
		$('.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;"> ' +
		'Request Wikimedia Commons administrators to delete previous revisions of the file and wiki page that still contain the GPS data</label> ' +
		'</span>' +
		'<span id="gps-removal-button-wrapper">' +
		'<input type="button" value="Upload a new version of this file with GPS metadata removed" id="gps-removal-button">' +
		'</span></div>');
		
		$('#overall-wrapper')
		.css('background-color', '#eaecf0')
		.css('border', '1px #c8ccd1 solid')
		.css('border-radius', '5px')
		.css('padding', '1.5em')
		.css('margin', '2em 5em')
		$('#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 ) );
Category:Pages with coordinates