Jump to content

User:Sir Sputnik/spihelper update.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Sir Sputnik (talk | contribs) at 21:07, 9 July 2020 (reduced ddl width). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//Tim's SPI helper script
//v.1.6.9a (GeneralNotability mods)
//Adapted from [[User:Mr.Z-man/closeAFD]]
importScript('User:Timotheus Canens/displaymessage.js');

if (!Array.prototype.indexOf) {  
	Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {  
		"use strict";  
		if (this === void 0 || this === null) {  
			throw new TypeError();
		}  
		var t = Object(this);  
		var len = t.length >>> 0;  
		if (len === 0) {  
			return -1;  
		}  
		var n = 0;
		if (arguments.length > 0) {  
			n = Number(arguments[1]);  
			if (n !== n) { // shortcut for verifying if it's NaN  
				n = 0;  
			} else if (n !== 0 && n !== Infinity && n !== -Infinity) {  
				n = (n > 0 || -1) * Math.floor(Math.abs(n));  
			}  
		}  
		if (n >= len) {  
			return -1;  
		}  
		var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);  
		for (; k < len; k++) {  
			if (k in t && t[k] === searchElement) {  
				return k;  
			}  
		}  
		return -1;  
	};  
}

if(!String.prototype.trim)
{
	String.prototype.trim = function(){
		return this.replace(/^\s+/, '').replace(/\s+$/, '');
	};
}

if (mw.config.get('wgPageName').indexOf('Wikipedia:Sockpuppet_investigations/') != -1
		&& mw.config.get('wgPageName').indexOf('Wikipedia:Sockpuppet_investigations/SPI/') == -1
		&& mw.config.get('wgPageName').indexOf('/Archive') == -1) {

	// Name of the SPI page in wiki title form (e.g. Wikipedia:Sockpuppet investigations/Test)
	var spiHelper_PageName = mw.config.get('wgPageName').replace(/_/g, ' ');
	// Grab the page ID on load - that way we know if it's been changed from the version the user is looking at
	var spiHelper_StartingRevID = spiHelper_getPageRev(spiHelper_PageName);
	// Just the username part of the case
	var spiHelper_CaseName = spiHelper_PageName.replace(/Wikipedia:Sockpuppet investigations\//g, '');
	// Name of the archive page
	const spiHelper_archiveName = spiHelper_PageName + "/Archive";
	var spiHelper_AJAXnumber = 0;
	// Whether the current user has admin permissions, used to determine whether to show block options
	var spiHelper_isAdmin = (mw.config.get('wgUserGroups').toString().indexOf('sysop') != -1);
	// Whether the current user has checkuser permissions, used to determine whether to show checkuser options
	var spiHelper_isCheckuser = (mw.config.get('wgUserGroups').toString().indexOf('checkuser') != -1);
	// Map of top-level actions the user has selected
	var spiHelper_ActionsSelected = 
	{ CU_act : false, CU_new : false, Block : false, Note: false, Close: false, Archive: false };
	
	if(typeof(spiHelper_watchArchive) == 'undefined') spiHelper_watchArchive = "preferences";
	if(typeof(spiHelper_watchCase) == 'undefined') spiHelper_watchCase = "preferences";
	
	// Count of unique users in the case (anything with a checkuser, checkip, user, ip, or vandal template on the page)
	var spiHelper_usercount = 0;
	mw.loader.load('mediawiki.user');
	var spiHelper_section_re = /^(?:===[^=]*===|=====[^=]*=====)\s*$/m;
	
	// define a few constants
	// List of possible selections for tagging a user in the block/tag interface
	const spiHelper_tagValues = '<option value="" selected="selected"> None </option>' +
		'<option value="blocked"> Suspected sock </option>' +
		'<option value="proven"> Proven sock </option>' +
		'<option value="confirmed"> CU confirmed sock </option>' +
		'<option value="master"> Blocked master </option>' +
		'<option value="sockmasterchecked"> CU confirmed master </option>' +
		'<option value="bannedmaster"> 3X banned master </option>';

	// List of possible selections for tagging a user's altmaster in the block/tag interface
	const spiHelper_altmasterTagValues = '<option value="" selected="selected"> None </option>' +
		'<option value="suspected"> Suspected alt master </option>' +
		'<option value="proven"> Proven alt master </option>';
		
	const spiHelper_CASESTATUS_RE = /\{\{\s*SPI case status\s*\|?\s*(\S*?)\s*\}\}/i;
	const spiHelper_CASESTATUS_CLOSED_RE = /^(?:close|closed)$/i;


	function spiHelper_init(){
		var pagetext = spiHelper_getPageText(spiHelper_PageName, false);
		if(!spiHelper_section_re.test(pagetext)) return; // Nothing to do here.
		var result = spiHelper_CASESTATUS_RE.exec(pagetext);
		var casestatus = '';
		if(result !== null){
			casestatus = result[1];
		}

		var hasCURequest = (casestatus !== '' && !(/^(?:close|closed|admin|moreinfo|CUdecline|hold|CUdeclined|clerk)$/i.test(casestatus)));
		var canAddCURequest = (casestatus === '' || /^(?:admin|moreinfo|hold|clerk)$/i.test(casestatus));
		var CUActiondefault = (/^(?:CU|checkuser|request|CUrequest)$/i.test(casestatus));
		var isClosed = spiHelper_CASESTATUS_CLOSED_RE.test(casestatus);

		var text = '<h3>Handling SPI case</h3><ul>';
		if(hasCURequest)
			text += '<li><input type="checkbox"' + (CUActiondefault? ' checked="checked"' : '') + ' name="spiHelper_CU_Action" id="spiHelper_CU_Action" />' +
			'<label for="spiHelper_CU_Action">Act on CU request</label></li>';
		else if(canAddCURequest)
			text += '<li><input type="checkbox" checked="checked" name="spiHelper_Case_Action" id="spiHelper_Case_Action" />' +
			'<label for="spiHelper_Case_Action">Request CU or other action</label></li>';
		text += '<li><input type="checkbox" name="spiHelper_BlockTag" id="spiHelper_BlockTag" />' +
		'<label for="spiHelper_BlockTag">';
		text += spiHelper_isAdmin ? "Block/tag socks" : "Tag socks";
		text += '</label></li><li><input type="checkbox" name="spiHelper_Comment" id="spiHelper_Comment" />' +
		'<label for="spiHelper_Comment">Note/comment</label></li>';
		if(!isClosed)
			text += '<li><input type="checkbox" name="spiHelper_Close" id="spiHelper_Close" onchange="spiHelper_toggleArchive()" />' +
			'<label for="spiHelper_Close">Close case</label></li>';
		else
			text += '<li><input type="checkbox" disabled="true" name="spiHelper_Close" id="spiHelper_Close" />' +
			'<label for="spiHelper_Close">Close case</label></li>';
		if(isClosed)
			text += '<li><input type="checkbox" checked="checked" name="spiHelper_Archive" id="spiHelper_Archive" />' +
			'<label for="spiHelper_Archive">Archive case</label></li></ul>';
		else
			text += '<li><input type="checkbox" disabled="true" name="spiHelper_Archive" id="spiHelper_Archive" />' +
			'<label for="spiHelper_Archive">Archive case</label></li></ul>';
		text += '<input type="button" id="spiHelper_GenerateForm" name="spiHelper_GenerateForm" value="Continue" onclick="spiHelper_generateform()" />';
		displayMessage(text);
	}

	function spiHelper_toggleArchive(){
		document.getElementById("spiHelper_Archive").disabled = !document.getElementById("spiHelper_Close").checked;
		if(document.getElementById("spiHelper_Archive").disabled)
			document.getElementById("spiHelper_Archive").checked = false;
	}

	function spiHelper_generateform(){
		spiHelper_usercount = 0;
		if (document.getElementById("spiHelper_CU_Action") != null)
			spiHelper_ActionsSelected.CU_act = document.getElementById("spiHelper_CU_Action").checked;
		else if(document.getElementById("spiHelper_Case_Action") != null)
			spiHelper_ActionsSelected.Case_act = document.getElementById("spiHelper_Case_Action").checked;
		spiHelper_ActionsSelected.Block = document.getElementById("spiHelper_BlockTag").checked;
		spiHelper_ActionsSelected.Note = document.getElementById("spiHelper_Comment").checked;
		spiHelper_ActionsSelected.Close = document.getElementById("spiHelper_Close").checked;
		spiHelper_ActionsSelected.Archive = document.getElementById("spiHelper_Archive").checked;
		var pagetext = spiHelper_getPageText(spiHelper_PageName, false);
		if(!(spiHelper_ActionsSelected.CU_act||spiHelper_ActionsSelected.Case_act||spiHelper_ActionsSelected.Note
				||spiHelper_ActionsSelected.Close||spiHelper_ActionsSelected.Archive
				||spiHelper_ActionsSelected.Block)){
			displayMessage("");
			return;
		}
		var text = '<h3>Handling SPI case</h3>';
		if(spiHelper_ActionsSelected.CU_act){
			text += '<h4>Handling Checkuser request</h4>'
				+ '<ul><li><label for="spiHelper_CUAction">Checkuser request:</label>';
			if(spiHelper_isCheckuser){
				text += spiHelper_generateSelect('spiHelper_CUAction',
						[
						 { label: 'No action', selected : true, value: 'noaction' },
						 { label: 'Endorse for CU attention', value: 'endorse' },
						 { label: 'Decline CU', value: 'cudecline' },
						 { label: 'Place case on hold', value: 'cuhold' },
						 { label: 'Relist for another check', value: 'relist' },
						 { label: 'Request more information', value: 'cumoreinfo' },
						 { label: 'Mark as in progress', value: 'inprogress' },
						 { label: 'Mark as checked', value: 'checked' },
						 { label: 'Request clerk action', value: 'clerk' }
						 ]);
			}
			else {
				if(spiHelper_isAdmin){
					text += spiHelper_generateSelect('spiHelper_CUAction',
						[
						 { label: 'No action', selected : true, value: 'noaction' },
						 { label: 'Endorse for CU attention', value: 'endorse' },
						 { label: 'Decline CU', value: 'decline' },
						 { label: 'Place case on hold', value: 'hold' },
						 { label: 'Relist for another check', value: 'relist' },
						 { label: 'Request more information', value: 'cumoreinfo' },
						 { label: 'Mark as checked', value: 'checked' },
						 { label: 'Request clerk action', value: 'clerk'}
						 ]);
				}
				else {
					text += spiHelper_generateSelect('spiHelper_CUAction',
						[
						 { label: 'No action', selected : true, value: 'noaction' },
						 { label: 'Endorse for CU attention', value: 'endorse' },
						 { label: 'Decline CU', value: 'decline' },
						 { label: 'Place case on hold', value: 'hold' },
						 { label: 'Relist for another check', value: 'relist' },
						 { label: 'Request more information', value: 'cumoreinfo' },
						 { label: 'Mark as checked', value: 'checked' },
						 { label: 'Request admin action', value: 'admin' },
						 { label: 'Request clerk action', value: 'clerk' }						 
						 ]);
				}
			}
			text +=  '</li> <li><label for="spiHelper_CUComment">Comment:</label>'
				+ '<textarea rows="3" cols="80" name="spiHelper_CUComment" id="spiHelper_CUComment"></textarea></li></ul>';
		}
		if(spiHelper_ActionsSelected.Case_act){
			text += '<h4>Request CU or other action</h4>'
				+ '<ul><li><label for="spiHelper_Request">Action to take:</label>';
			if(spiHelper_isAdmin){
				text += spiHelper_generateSelect('spiHelper_Request',
					[
					 { label: 'No action', selected : true, value: 'noaction' },
					 { label: 'Request CU', value: 'CUrequest' },
					 { label: 'Request CU and self-endorse (clerk only)', value: 'selfendorse' },
					 { label: 'Request more information (non-CU)', value: 'moreinfo' },
					 { label: 'Put case on hold', value: 'hold' },
					 { label: 'Request clerk action', value: 'clerk' }
					]);
			}
			else {
				text += spiHelper_generateSelect('spiHelper_Request',
					[
					 { label: 'No action', selected : true, value: 'noaction' },
					 { label: 'Request CU', value: 'CUrequest' },
					 { label: 'Request CU and self-endorse (clerk only)', value: 'selfendorse' },
					 { label: 'Request more information (non-CU)', value: 'moreinfo' },
					 { label: 'Put case on hold', value: 'hold' },
					 { label: 'Request admin action', value: 'admin' },
					 { label: 'Request clerk action', value: 'clerk' }
					]);
			}
			text += '</li><li><label for="spiHelper_RequestComment">Comment:</label>'
				+ '<textarea rows="3" cols="80" name="spiHelper_RequestComment" id="spiHelper_RequestComment"></textarea></li>'
				+'</ul>';
		}
		if(spiHelper_ActionsSelected.Block ){
			if(spiHelper_isAdmin)
				text += '<h4>Blocking and tagging socks</h4>';
			else
				text += '<h4>Tagging socks</h4>';
			var checkuser_re = /\{\{\s*check(user|ip)\s*\|\s*(?:1=)?\s*([^\|\}]*?)\s*\}\}/gi;
			var results = pagetext.match(checkuser_re);
			var likelyusers = new Array(), likelyips = new Array(), possibleusers = new Array(), possibleips = new Array();
			likelyusers.push(spiHelper_CaseName);
			for(var i = 0; results != null && i < results.length; i++){
				var username = results[i].replace(checkuser_re, "$2");
				var isIP = mw.util.isIPAddress(username, true);
				if(!isIP && likelyusers.indexOf(username) == -1)
					likelyusers.push(username);
				else if(isIP && likelyips.indexOf(username) == -1)
					likelyips.push(username);
			}
			var user_re = /\{\{\s*(?:user|vandal|IP)[^\|\}\{]*?\s*\|\s*(?:1=)?\s*([^\|\}]*?)\s*\}\}/gi;
			var userresults = pagetext.match(user_re);
			for(var i = 0; userresults != null && i < userresults.length; i++){
				var username = userresults[i].replace(user_re, "$1");
				if(mw.util.isIPAddress(username, true) && possibleips.indexOf(username) == -1
						&& likelyips.indexOf(username) == -1)
					possibleips.push(username);
				else if(possibleusers.indexOf(username) == -1 && likelyusers.indexOf(username) == -1)
					possibleusers.push(username);

			}
			if(spiHelper_isAdmin){
				text += '<ul><li><input type="checkbox" name="spiHelper_noblock" id="spiHelper_noblock" />' 
					 + '<label for="spiHelper_noblock">Do not make any blocks (this overrides the individual "Blk" boxes below)</label></li>'
					 + '<li><input type="checkbox" checked="checked" name="spiHelper_override" id="spiHelper_override" />' 
					 + '<label for="spiHelper_override">Override any existing blocks</label></li>';
				if(spiHelper_isCheckuser) {
					text += '<li><input type="checkbox" name="spiHelper_cublock" id="spiHelper_cublock" />'
						 + '<label for="spiHelper_cublock">Mark blocks as Checkuser blocks.</label></li>'
						 + '<li><input type="checkbox" name="spiHelper_cublockonly" id="spiHelper_cublockonly" />'
						 + '<label for="spiHelper_cublockonly">Suppress the usual block summary and only use {'
						 + '{checkuserblock-account}} and {' + '{checkuserblock}} (no effect if "mark blocks as CU blocks" is not checked).</label></li>';
				}
				text +='<li><input type="checkbox" checked="checked" name="spiHelper_blocknoticemaster" id="spiHelper_blocknoticemaster" />'
					 + '<label for="spiHelper_blocknoticemaster">Add talk page notice when (re)blocking the sockmaster.</label></li>'
					 + '<li><input type="checkbox" name="spiHelper_blocknoticesocks" id="spiHelper_blocknoticesocks" />' 
					 + '<label for="spiHelper_blocknoticesocks">Add talk page notice when blocking socks.</label></li>' 
					 + '<li><input type="checkbox" name="spiHelper_blanktalk" id="spiHelper_blanktalk" />' 
					 + '<label for="spiHelper_blanktalk">Blank the talk page when adding talk notices.</label></li>'
					 + '<li><input type="checkbox" name="spiHelper_hidelocknames" id="spiHelper_hidelocknames" />' 
					 + '<label for="spiHelper_hidelocknames">Hide usernames when requesting global locks.</label></li></ul>';
				
			}
			text += '<table> <tr><th>Username</th>' + (spiHelper_isAdmin? '<th>Blk?</th><th>Dur</th><th>ACB</th><th>AB/AO</th><th>NTP</th><th>NEM</th>' : '' ) + '<th>Tag</th><th>Alt Master Tag</th><th>Req Lock?</th></tr>';
			// "Select all" options
			var row = '<tr><td><b>(All)</b></td>';
			if(spiHelper_isAdmin){
				row += '<td><input type="checkbox" name="spiHelper_block_doblock_all" id="spiHelper_block_doblock" onclick="spiHelper_checkAllBlockOpts(this)"/></td>';
				row += '<td></td>';  // Empty (block time)
				row += '<td><input type="checkbox" name="spiHelper_block_acb_all" id="spiHelper_block_acb" onclick="spiHelper_checkAllBlockOpts(this)" checked="checked"/></td>';
				row += '<td><input type="checkbox" name="spiHelper_block_ab_all" id="spiHelper_block_ab" onclick="spiHelper_checkAllBlockOpts(this)" checked="checked"/></td>';
				row += '<td><input type="checkbox" name="spiHelper_block_tp_all" id="spiHelper_block_tp" onclick="spiHelper_checkAllBlockOpts(this)" /></td>';
				row += '<td><input type="checkbox" name="spiHelper_block_email_all" id="spiHelper_block_email" onclick="spiHelper_checkAllBlockOpts(this)"/></td>';
			}
			row += '<td><select name="spiHelper_block_tag_all" id="spiHelper_block_tag" onchange="spiHelper_checkAllBlockOpts(this)">' + spiHelper_tagValues + '</select></td>';
			row += '<td><select name="spiHelper_block_tag_altmaster_all" id="spiHelper_block_tag_altmaster" onchange="spiHelper_checkAllBlockOpts(this)">' + spiHelper_altmasterTagValues + '</select></td>';
			row += '<td><input type="checkbox" name="spiHelper_block_lock_all" id="spiHelper_block_lock" onclick="spiHelper_checkAllBlockOpts(this)"/></td></tr>';
			text += row;
			for(var i = 0; i < likelyusers.length; i++){
				spiHelper_usercount++;
				text += spiHelper_generateBlockTableLine(likelyusers[i], true, spiHelper_usercount, spiHelper_isAdmin);
			}
			for(var i = 0; i < likelyips.length; i++){
				spiHelper_usercount++;
				text += spiHelper_generateBlockTableLine(likelyips[i], true, spiHelper_usercount, spiHelper_isAdmin);
			}
			for(var i = 0; i < possibleusers.length; i++){
				spiHelper_usercount++;
				text += spiHelper_generateBlockTableLine(possibleusers[i], false, spiHelper_usercount, spiHelper_isAdmin);
			}
			for(var i = 0; i < possibleips.length; i++){
				spiHelper_usercount++;
				text += spiHelper_generateBlockTableLine(possibleips[i], false, spiHelper_usercount, spiHelper_isAdmin);
			}
			text += '</table>';
		}
		if(spiHelper_ActionsSelected.Note){
			text += '<h4>Commenting on case</h4>'
				+ '<ul><li><label for="spiHelper_CommentType">Comment label:</label>'
				+ spiHelper_generateSelect('spiHelper_CommentType',
						[
						 { label: 'None', selected : true, value: 'none' },
						 { label: 'Clerk note', value: 'clerknote' },
						 { label: 'Administrator note', value: 'adminnote' },
						 { label: 'Note', value: 'takenote' }
						 ]) + '</li>'
						 + '<li><label for="spiHelper_CommentText">Comment:</label>'
						 + '<textarea rows="3" cols="80" name="spiHelper_CommentText" id="spiHelper_CommentText"></textarea></li></ul>';
		}
		if(spiHelper_ActionsSelected.Close){
			text += '<h4>Marking case as closed</h4>'
				+ '<ul><li><input type="checkbox" checked="checked" name="spiHelper_CloseCase" id="spiHelper_CloseCase" />'
				+ '<label for="spiHelper_CloseCase">Close this SPI case</label></li>'
				+ '<li><label for="spiHelper_Close_Comment">Closing comment: </label>'
				+ '<textarea rows="3" cols="80" name="spiHelper_Close_Comment" id="spiHelper_Close_Comment"></textarea></li></ul>';
		}
		if(spiHelper_ActionsSelected.Archive){
			text += '<h4>Archiving case</h4>'
				+ '<ul><li><input type="checkbox" checked="checked" name="spiHelper_ArchiveCase" id="spiHelper_ArchiveCase" />'
				+ '<label for="spiHelper_ArchiveCase">Archive this SPI case</label></li>';
			text += '</ul>';
		}
		text += '<input type="button" id="spiHelper_performActions" name="spiHelper_performActions" value="Done" onclick="spiHelper_performActions()" />';
		displayMessage(text);
	}

	function spiHelper_performActions(){
		if(spiHelper_ActionsSelected.CU_act){
			spiHelper_ActionsSelected.CUAction = document.getElementById('spiHelper_CUAction').value;
			spiHelper_ActionsSelected.CUComment = document.getElementById('spiHelper_CUComment').value;
		}
		else if(spiHelper_ActionsSelected.Case_act){
			spiHelper_ActionsSelected.RequestComment = document.getElementById('spiHelper_RequestComment').value;
			spiHelper_ActionsSelected.RequestAction = document.getElementById('spiHelper_Request').value;
		}
		if(spiHelper_ActionsSelected.Note){
			spiHelper_ActionsSelected.CommentType = document.getElementById('spiHelper_CommentType').value;
			spiHelper_ActionsSelected.Comment = document.getElementById('spiHelper_CommentText').value;
		}
		if(spiHelper_ActionsSelected.Block){
			spiHelper_ActionsSelected.Blocks = new Array();
			spiHelper_ActionsSelected.Tags = new Array();
			spiHelper_ActionsSelected.GlobalLocks = new Array();
			if(spiHelper_isAdmin && !document.getElementById('spiHelper_noblock').checked){
				var tagmaster = document.getElementById('spiHelper_blocknoticemaster').checked;
				var tagsocks = document.getElementById('spiHelper_blocknoticesocks').checked;
				spiHelper_ActionsSelected.BlankTalk = document.getElementById('spiHelper_blanktalk').checked;
				spiHelper_ActionsSelected.TagMaster = document.getElementById('spiHelper_blocknoticemaster').checked;
				spiHelper_ActionsSelected.OverrideExisting = document.getElementById('spiHelper_override').checked;
				spiHelper_ActionsSelected.HideLockNames = document.getElementById('spiHelper_hidelocknames').checked;
				if(spiHelper_isCheckuser){
					spiHelper_ActionsSelected.CUBlock = document.getElementById('spiHelper_cublock').checked;
					spiHelper_ActionsSelected.CUBlockOnly = document.getElementById('spiHelper_cublockonly').checked;
				}
				for(var i = 1; i <= spiHelper_usercount; i++){
					if(document.getElementById('spiHelper_block_doblock' + i).checked){
						var noticetype = false;
						
						if(tagmaster && document.getElementById('spiHelper_block_tag' + i).value.indexOf("master") != -1){
							noticetype = "master";
						}
						else if(tagsocks && document.getElementById('spiHelper_block_tag' + i).value == "blocked"){
							noticetype = "suspectsock";
						}
						else if(tagsocks && document.getElementById('spiHelper_block_tag' + i).value != ""){
							noticetype = "sock";
						}
						
						var item = {
								username : document.getElementById('spiHelper_block_username' + i).value,
								duration : document.getElementById('spiHelper_block_duration' + i).value,
								acb : document.getElementById('spiHelper_block_acb' + i).checked,
								ab  : document.getElementById('spiHelper_block_ab' + i).checked,
								ntp : document.getElementById('spiHelper_block_tp' + i).checked,
								nem : document.getElementById('spiHelper_block_email' + i).checked,
								tpn : noticetype
						};
						
						spiHelper_ActionsSelected.Blocks.push(item);
					}
					if(document.getElementById('spiHelper_block_lock' + i).checked){
						spiHelper_ActionsSelected.GlobalLocks.push(document.getElementById('spiHelper_block_username' + i).value);
					}
					if(document.getElementById('spiHelper_block_tag' + i).value != ""){
						var item = {
								username : document.getElementById('spiHelper_block_username' + i).value,
								tag : document.getElementById('spiHelper_block_tag' + i).value,
								altmasterstatus : document.getElementById('spiHelper_block_tag_altmaster' + i).value,
						};
						spiHelper_ActionsSelected.Tags.push(item);
					}
				}
			}
			else {
				for(var i = 1; i <= spiHelper_usercount; i++){
					if(document.getElementById('spiHelper_block_tag' + i).value != ""){
						var item = {
								username : document.getElementById('spiHelper_block_username' + i).value,
								tag : document.getElementById('spiHelper_block_tag' + i).value,
						};
						spiHelper_ActionsSelected.Tags.push(item);
					}
					if(document.getElementById('spiHelper_block_lock' + i).checked){
						spiHelper_ActionsSelected.GlobalLocks.push(document.getElementById('spiHelper_block_username' + i).value);
					}
				}
			}
		}
		if(spiHelper_ActionsSelected.Close){
			spiHelper_ActionsSelected.Close = document.getElementById('spiHelper_CloseCase').checked;
			spiHelper_ActionsSelected.CloseComment = document.getElementById('spiHelper_Close_Comment').value;
		}
		if(spiHelper_ActionsSelected.Archive){
			spiHelper_ActionsSelected.Archive = document.getElementById('spiHelper_ArchiveCase').checked;
		}
		displayMessage('<ul id="spiHelper_status"></ul><ul id="spiHelper_finish"></ul>');
		document.getElementById('spiHelper_finish').innerHTML += '<span id="spiHelper_finished_wrapper"><span id="spiHelper_finished_main" style="display:none"><li id="spiHelper_done"><b>Done (<a href="/wiki/'+encodeURI(spiHelper_PageName)+'?action=purge" title="'+spiHelper_PageName+'">Reload page</a>)</b></li></span></span>';
		var pagetext = spiHelper_getPageText(spiHelper_PageName, true);
		var editsummary = "";

		var result = spiHelper_CASESTATUS_RE.exec(pagetext);
		if(result == null){
			pagetext = pagetext.replace("===", "\{\{SPI case status\}\}\n===");
			result = spiHelper_CASESTATUS_RE.exec(pagetext);
		}
		var casestatustext = result[0], casestatus = result[1];

		if(spiHelper_ActionsSelected.CU_act){
			var CUAction = spiHelper_ActionsSelected.CUAction;
			var CUComment = spiHelper_ActionsSelected.CUComment;
			if(CUAction != 'noaction')
				casestatus = CUAction;
			if(CUAction == "checked"){
				editsummary = "Marking request as checked";
			}
			else if(CUAction == "inprogress"){
				if(!/\{\{Inprogress\}\}/i.test(CUComment))
					CUComment = "\{\{Inprogress\}\} - " + CUComment;
				editsummary = "Marking request in progress";
			}
			else if(CUAction == "decline"){
				if(!/\{\{(?:Decline|Decline-IP)\}\}/i.test(CUComment))
					CUComment = "\{\{Decline\}\} - " + CUComment;
				editsummary = "Declining checkuser";
			}
			else if(CUAction == "cudecline"){
				if(!/\{\{(?:CUdeclined|Cudecline|declined)\}\}/i.test(CUComment))
					CUComment = "\{\{Cudecline\}\} - " + CUComment;
				editsummary = "Declining checkuser";
			}
			else if(CUAction == "endorse"){
				if(!/\{\{(?:Endorse|Selfendorse|Requestandendorse)\}\}/i.test(CUComment))
					CUComment = "\{\{Endorse\}\} - " + CUComment;
				editsummary = "Endorsing for checkuser attention";
			}
			else if(CUAction == "cumoreinfo"){
				if(!/\{\{moreinfo\}\}/i.test(CUComment))
					CUComment = "\{\{moreinfo\}\} - " + CUComment;
				editsummary = "Requesting additional information";
			}
			else if(CUAction == "relist"){
				if(!/\{\{relisted\}\}/i.test(CUComment))
					CUComment = "\{\{relisted\}\} - " + CUComment;
				editsummary = "Relisting case for another check";
			}
			else if(CUAction == "hold" || CUAction == "cuhold"){
				if(!/\{\{onhold\}\}/i.test(CUComment))
					CUComment = "\{\{onhold\}\} - " + CUComment;
				editsummary = "Placing checkuser request on hold";
			}
			else if(CUAction == 'admin'){
				CUComment = "\{\{awaitingadmin\}\} - " + CUComment;
				editsummary = "Requesting admin action";
			}
			else if(CUAction == "clerk"){
				CUComment = "\{\{Clerk Request\}\} - " + CUComment;
				editsummary = "Requesting clerk action";
			}
			if(CUComment != ''){
				CUComment += " \~\~\~\~";
				if(pagetext.indexOf("\n----") == -1)
					pagetext += "\n----";
				pagetext = pagetext.replace(/\s*\n----/, '\n*' + CUComment + '\n----');
			}
		}

		if(spiHelper_ActionsSelected.Case_act){
			var RequestComment = spiHelper_ActionsSelected.RequestComment;
			var RequestAction = spiHelper_ActionsSelected.RequestAction;
			var newtext = '\n*';
			if(RequestAction == "selfendorse"){
				casestatus = "endorse";
				editsummary = "Adding checkuser request (self-endorsed for checkuser attention)";
				newtext += "{" + "{Requestandendorse}} ";
			}
			else if(RequestAction != "noaction"){
				casestatus = RequestAction;
				if(RequestAction == "CUrequest"){
					editsummary = "Adding checkuser request";
					if(!/\{\{CURequest\}\}/i.test(RequestComment))
						newtext += "{" + "{CURequest}} ";
				}
				else if(RequestAction == "moreinfo"){
					editsummary = "Requesting additional information";
					if(!/\{\{moreinfo\}\}/i.test(RequestComment))
						newtext += "{" + "{moreinfo}} ";
				}
				else if(RequestAction == "hold"){
					editsummary = "Putting case on hold";
					if(!/\{\{onhold\}\}/i.test(RequestComment))
						newtext += "{" + "{onhold}} ";
				}
				else if(RequestAction == "admin"){
					editsummary = "Requesting admin action";
					newtext += "\{\{awaitingadmin\}\} ";
				}
				else if(RequestAction == "clerk"){
					editsummary = "Requesting clerk action";
					newtext += "\{\{Clerk Request\}\} ";
				}
			}
			else {
				// noaction
				if(RequestComment != '')
					editsummary = "Comment";
			}
			if(RequestAction != "noaction" && RequestComment != '')
				newtext += "- ";
			newtext += RequestComment + " \~\~\~\~";
			if(pagetext.indexOf("\n----") == -1)
				pagetext += "\n----";
			if(RequestAction != "noaction" || RequestComment != '')
				pagetext = pagetext.replace(/\s*\n----/, newtext + '\n----');
		}
		if(spiHelper_ActionsSelected.Block){
			var sockmaster = "";
			var altmaster = "";
			var sockcount = 0;
			var needsAltmaster = false;
			for(var i = 0; i < spiHelper_ActionsSelected.Tags.length; i++){
				var isIP = mw.util.isIPAddress(spiHelper_ActionsSelected.Tags[i].username, true);
				if(isIP) continue; // do not support tagging IPs
				if(spiHelper_ActionsSelected.Tags[i].tag.indexOf("master") != -1){
					sockmaster = spiHelper_ActionsSelected.Tags[i].username;
					continue;
				}
				if(spiHelper_ActionsSelected.Tags[i].altmasterstatus !== "") {
					needsAltmaster = true;
				}
				sockcount ++;
			}
			if(sockcount > 0 && sockmaster == "") sockmaster = prompt("Please enter the name of the sockmaster: ", spiHelper_CaseName);
			if(sockcount > 0 && needsAltmaster) altmaster = prompt("Please enter the name of the alternate sockmaster: ", spiHelper_CaseName);
			
			if(spiHelper_isAdmin){
				for(var i = 0; i < spiHelper_ActionsSelected.Blocks.length; i++){
					var isIP = mw.util.isIPAddress(spiHelper_ActionsSelected.Blocks[i].username, true);
					var block_summary = "Abusing [[WP:SOCK|multiple accounts]]: Please see: [[w:en:" + spiHelper_PageName + "]]";
					if(spiHelper_isCheckuser){
						if(spiHelper_ActionsSelected.CUBlock){
							var cublock_template = isIP ? ('{' + '{checkuserblock}}') : ('{' + '{checkuserblock-account}}');
							if(spiHelper_ActionsSelected.CUBlockOnly)
								block_summary = cublock_template;
							else
								block_summary = cublock_template + ': ' + block_summary;
						}
					}
					spiHelper_blockUser(
							spiHelper_ActionsSelected.Blocks[i].username,
							spiHelper_ActionsSelected.Blocks[i].duration,
							block_summary,
							spiHelper_ActionsSelected.OverrideExisting,
							(isIP ? spiHelper_ActionsSelected.Blocks[i].ab : false),
							spiHelper_ActionsSelected.Blocks[i].acb,
							(isIP ? false : spiHelper_ActionsSelected.Blocks[i].ab),
							spiHelper_ActionsSelected.Blocks[i].ntp,
							spiHelper_ActionsSelected.Blocks[i].nem);
					if(spiHelper_ActionsSelected.TagMaster && spiHelper_ActionsSelected.Blocks[i].username == sockmaster)
						spiHelper_ActionsSelected.Blocks[i].tpn = "master";
					if(sockmaster != null && sockmaster != "" && spiHelper_ActionsSelected.Blocks[i].tpn){
						var newtext = '';
						if(spiHelper_ActionsSelected.Blocks[i].tpn.indexOf("sock") != -1){
							newtext = '== Blocked as a sockpuppet ==\n{' + '{subst:sockblock|master=' + sockmaster;
							if(spiHelper_ActionsSelected.Blocks[i].ntp)
								newtext += '|notalk=yes';
							if(spiHelper_ActionsSelected.Blocks[i].tpn.indexOf("suspect") != -1)
								newtext += '|suspected=yes';
							newtext += '|sig=yes}}';
						}
						else {
							newtext = '== Blocked for sockpuppetry ==\n{' + '{subst:sockblock|masterblock=yes|period=' + spiHelper_ActionsSelected.Blocks[i].duration;
							if(spiHelper_ActionsSelected.Blocks[i].ntp)
								newtext += '|notalk=yes';
							newtext += '|evidence= [[' + spiHelper_PageName + ']]|sig=yes}}';
						}
						if(!spiHelper_ActionsSelected.BlankTalk){
							var oldtext = spiHelper_getPageText("User talk:" + spiHelper_ActionsSelected.Blocks[i].username, true);
							if(oldtext != "")
								newtext = oldtext + '\n' + newtext;	
						}
						spiHelper_editPage("User talk:" + spiHelper_ActionsSelected.Blocks[i].username,
								newtext, "Adding sockpuppetry block notice per [[" + spiHelper_PageName+ "]]", false, null, "nochange");
					}
				}
			}
			if(sockmaster != null && sockmaster != ""){
				var checkConfirmedCat = false;
				var checkSuspectedCat = false;
				var checkAltSuspectedCat = false;
				var checkAltConfirmedCat = false;
				for(var i = 0; i < spiHelper_ActionsSelected.Tags.length; i++){
					var isIP = mw.util.isIPAddress(spiHelper_ActionsSelected.Tags[i].username, true);
					if(isIP) continue; // do not support tagging IPs
					var tagtext = "";
					var altmasterText = "";
					var altmasterSockTag = ""; // Lets us put both a sockmaster and sock tag on a page if we're CU-confirmed but suspect a different master
					if(altmaster !== null && altmaster !== "" && spiHelper_ActionsSelected.Tags[i].altmasterstatus !== "") {
						altmasterText = "|altmaster=" + altmaster + "|altmaster-status=" + spiHelper_ActionsSelected.Tags[i].altmasterstatus;
						// Create a sock tag which we throw on the page if we think a sockmaster is actually someone else's sock
						if(spiHelper_ActionsSelected.Tags[i].altmasterstatus == "suspected")
						{
							altmasterSockTag = "\n{" + "{sockpuppet|1=" + altmaster + "|2=blocked}}";
							checkAltSuspectedCat = true;
						}
						else if(spiHelper_ActionsSelected.Tags[i].altmasterstatus == "proven")
						{
							altmasterSockTag = "\n{" + "{sockpuppet|1=" + altmaster + "|2=proven}}";
							checkAltConfirmedCat = true;						
						}
					}
					if(spiHelper_ActionsSelected.Tags[i].tag == "blocked")
					{
						tagtext = "{" + "{sockpuppet|1=" + sockmaster + "|2=blocked" + altmasterText + "}}";
						checkSuspectedCat = true;
					}
					else if(spiHelper_ActionsSelected.Tags[i].tag == "proven")
					{
						tagtext = "{" + "{sockpuppet|1=" + sockmaster + "|2=proven" + altmasterText + "}}";
						checkConfirmedCat = true;						
					}
					else if(spiHelper_ActionsSelected.Tags[i].tag == "confirmed")
					{
						tagtext = "{" + "{sockpuppet|1=" + sockmaster + "|2=confirmed" + altmasterText + "}}";
						checkConfirmedCat = true;
					}
					else if(spiHelper_ActionsSelected.Tags[i].tag == "master")
						tagtext = "{" + "{sockmaster|blocked}}" + altmasterSockTag;
					else if(spiHelper_ActionsSelected.Tags[i].tag == "sockmasterchecked")
						tagtext = "{" + "{sockmaster|blocked|checked=yes}}"  + altmasterSockTag;
					else if(spiHelper_ActionsSelected.Tags[i].tag == "bannedmaster")
						tagtext = "{" + "{sockmaster|banned|checked=yes}}"  + altmasterSockTag;
					spiHelper_editPage("User:" + spiHelper_ActionsSelected.Tags[i].username, tagtext, "Adding sockpuppetry tag per [[" + spiHelper_PageName+ "]]", false, null, "nochange");
				}
				
				if(checkAltConfirmedCat)
				{
					var catname = "Category:Wikipedia sockpuppets of " + altmaster;
					var cattext = spiHelper_getPageText(catname, false);
					if(cattext == "")
					{
						spiHelper_editPage(catname,"{" + "{sockpuppet category}}", "Creating sockpuppet category per [[" + spiHelper_PageName + "]]", true, null, "nochange");
					}
				}
				if(checkAltSuspectedCat)
				{
					var catname = "Category:Suspected Wikipedia sockpuppets of " + altmaster;
					var cattext = spiHelper_getPageText(catname, false);
					if(cattext == "")
					{
						spiHelper_editPage(catname,"{" + "{sockpuppet category}}", "Creating sockpuppet category per [[" + spiHelper_PageName + "]]", true, null, "nochange");
					}	
				}
				if(checkConfirmedCat)
				{
					var catname = "Category:Wikipedia sockpuppets of " + sockmaster;
					var cattext = spiHelper_getPageText(catname, false);
					if(cattext == "")
					{
						spiHelper_editPage(catname,"{" + "{sockpuppet category}}", "Creating sockpuppet category per [[" + spiHelper_PageName + "]]", true, null, "nochange");
					}
				}
				if(checkSuspectedCat)
				{
					var catname = "Category:Suspected Wikipedia sockpuppets of " + sockmaster;
					var cattext = spiHelper_getPageText(catname, false);
					if(cattext == "")
					{
						spiHelper_editPage(catname,"{" + "{sockpuppet category}}", "Creating sockpuppet category per [[" + spiHelper_PageName + "]]", true, null, "nochange");
					}	
				}
			}
			if (spiHelper_ActionsSelected.GlobalLocks.length > 0){
				var templateContent = "";
				var matchCount = 0;
				for(var i = 0; i < spiHelper_ActionsSelected.GlobalLocks.length; i++){
					var isIP = mw.util.isIPAddress(spiHelper_ActionsSelected.GlobalLocks[i], true);
					if(isIP) continue; // do not support locking IPs (those are global blocks, not locks, and are handled a bit differently)
					templateContent += "|" + spiHelper_ActionsSelected.GlobalLocks[i];
					matchCount++;
				}
				if (matchCount > 0) {
					if (spiHelper_ActionsSelected.HideLockNames) {
						// If requested, hide locked names
						templateContent += "|hidename=1"
					}
					// Parts of this code were adapted from https://github.com/Xi-Plus/twinkle-global
					var metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php');
					var lockTemplate = "";
					if (matchCount == 0) {
						lockTemplate = "{" + "{LockHide" + templateContent + "}}"
					}
					else {
						lockTemplate = "{" + "{MultiLock" + templateContent + "}}"
					}
					if(sockmaster == "") sockmaster = prompt("Please enter the name of the sockmaster: ", spiHelper_CaseName);
					var comment = prompt("Please enter a comment for the global lock request (optional):", "");
					var heading = spiHelper_ActionsSelected.HideLockNames ? "sockpuppet(s)" : sockmaster + " sock(s)";
					message = "=== Global lock for " + heading + " ===";
					message += "\n{{status}}";
					message += "\n" + lockTemplate;
					message += "\n* Sockpuppet(s) found in enwiki sockpuppet investigation, see [[:w:en:" + spiHelper_PageName + "]]. " + comment + " \~\~\~\~";

					metaApi.edit('Steward requests/Global', function(revision) {
						var text = revision.content;
						text = text.replace(/\n+(== See also == *\n)/, '\n\n' + message + '\n\n$1');
						return {
							text: text,
							summary: "global lock request for " + heading + " (via spihelper)",
							assert: "user",
							watchlist: "nochange"
						}
					});
					document.getElementById('spiHelper_status').innerHTML += '<li id="spiHelper_lock">Filing global lock request</li>';
				}
			}
		}
		if(spiHelper_ActionsSelected.Note){
			if(pagetext.indexOf("\n----") == -1)
				pagetext += "\n----";
			if(spiHelper_ActionsSelected.Comment != ''){
				var newtext = '\n*';
				if(spiHelper_ActionsSelected.CommentType != 'none')
					newtext += "\{\{" + spiHelper_ActionsSelected.CommentType + "\}\} ";
				newtext += spiHelper_ActionsSelected.Comment + " \~\~\~\~\n";
				pagetext = pagetext.replace(/\s*\n----/, newtext + '----');
				if(!/comment/i.test(editsummary)){
					if(editsummary != "")
						editsummary += ", comment";
					else
						editsummary = "Comment";
				}
			}

		}
		if(spiHelper_ActionsSelected.Close){
			if(pagetext.indexOf("\n----") == -1)
				pagetext += "\n----";
			var newtext = '\n';
			if(spiHelper_ActionsSelected.CloseComment != '')
				newtext += '*' + spiHelper_ActionsSelected.CloseComment + " \~\~\~\~\n";
			casestatus = 'close';
			pagetext = pagetext.replace(/\s*\n----/, newtext + '----');
			if(editsummary != "")
				editsummary += ", marking case as closed";
			else
				editsummary = "Marking case as closed";
		}

		pagetext = pagetext.replace(casestatustext, "\{\{SPI case status|" + casestatus + "\}\}");
		casestatustext = "\{\{SPI case status|" + casestatus + "\}\}";

		if(spiHelper_ActionsSelected.Archive){
			var onComplete = function () {
				spiHelper_archiveCase('');
				document.getElementById('spiHelper_finished_main').style.display = '';
			};
			spiHelper_editPage(spiHelper_PageName, pagetext, editsummary, false, onComplete, spiHelper_watchCase);
		}
		else
			spiHelper_editPage(spiHelper_PageName, pagetext, editsummary, false, null, spiHelper_watchCase);
		if(!spiHelper_ActionsSelected.Archive)
			document.getElementById('spiHelper_finished_main').style.display = '';
	}

	function spiHelper_archiveCase(editSummary) {
		const caseSections = spiHelper_getInvestigationSectionIDs();
		for (let i = 0; i < caseSections.length; i++) {
			const sectionText = spiHelper_getPageText(spiHelper_PageName, false, caseSections[i]);
			const result = spiHelper_CASESTATUS_RE.exec(pagetext);
			if(result === null){
				// Bail out - can't find the case status template in this section
				return;
			}
			if (spiHelper_CASESTATUS_CLOSED_RE.test(result[1])) {
				const sectionId = caseSections[i];
				// A running concern with the SPI archives is whether they exceed the post-expand include size
				// Calculate what percent of that size the archive will be if we add the current page to it - if >1,
				// we need to archive the archive
				const postExpandPercent = (spiHelper_getPostExpandSize(spiHelper_PageName, sectionId) + spiHelper_getPostExpandSize(spiHelper_archiveName)) / spiHelper_getMaxPostExpandSize();
				if (postExpandPercent >= 1) {
					// We'd overflow the archive, so move it and then archive the current page
					spiHelper_moveArchive(() => {
						spiHelper_archiveCaseSection(editSummary, sectionId);
					});
				}
				else {
					// No issues, go ahead and archive
					spiHelper_archiveCaseSection(editSummary, sectionId);
				}
			} 
		}
	}
	
	function spiHelper_archiveCaseSection(editSummary, sectionId) {
		let sectionText = spiHelper_getPageText(spiHelper_PageName, true, sectionId);
		sectionText = sectionText.replace(spiHelper_CASESTATUS_RE, '');
		let newarchivetext = sectionText.substring(sectionText.search(spiHelper_section_re));

		if(editSummary != "")
			editSummary += ", archiving case to [[" + spiHelper_archiveName + "]]";
		else
			editSummary = "Archiving case to [[" + spiHelper_archiveName + "]]";
			
		// Blank the section we archived
		spiHelper_editPage(spiHelper_PageName, "", editSummary, false, null, spiHelper_watchCase, sectionId);
		let archivetext = spiHelper_getPageText(spiHelper_archiveName, true);
		if(archivetext == "")
			archivetext = "__" + "TOC__\n\{\{SPIarchive notice|1=" + spiHelper_CaseName + "\}\}\n\{\{SPIpriorcases\}\}";
		else
			archivetext = archivetext.replace(/<br\s*\/>\s*\{\{SPIpriorcases\}\}/gi, "\n\{\{SPIpriorcases}}"); // fmt fix whenever needed.
		archivetext += "\n" + newarchivetext;
		spiHelper_editPage(spiHelper_archiveName, archivetext, "Archiving case from [[" + spiHelper_PageName + "]]", false, null, spiHelper_watchArchive);
	}
	
	function spiHelper_moveArchive(onComplete) {
		// Find the first empty archive page
		var archiveId = 1;
		while (spiHelper_getPageText(spiHelper_archiveName + "/" + i) !== "") {
			archiveId++;
		}
		const newarchivename = spiHelper_archiveName + "/" + archiveId;
		document.getElementById('spiHelper_finished_wrapper').innerHTML = '<span id="spiHelper_AJAX_finished_'+spiHelper_AJAXnumber+'" style="display:none">' + document.getElementById('spiHelper_finished_wrapper').innerHTML + '</span>';
		const func_id = spiHelper_AJAXnumber;
		spiHelper_AJAXnumber++;
		document.getElementById('spiHelper_status').innerHTML += '<li id="spiHelper_move'+func_id+'">Moving <a href="/wiki/'+encodeURI(spiHelper_archiveName)+'" title="'+ spiHelper_archiveName +'">' + spiHelper_archiveName +'</a> to <a href="/wiki/' + encodeURI(newarchivename) + 'title="' + newarchivename + '">' +  newarchivename + '</a></li>';
			api.postWithToken( "csrf", {
			action: "move",
			from: spiHelper_archiveName,
			to: newarchivename,
			reason: "Moving archive to avoid exceeding post expand size limit",
			noredirect: true
		}).done(function(data) {
			document.getElementById('spiHelper_move'+func_id).innerHTML = 'Moved to <a href="/wiki/'+encodeURI(newarchivename)+'" title="'+newarchivename+'">'+newarchivename+'</a>';
			if (onComplete) {
				onComplete();
			}
		}).fail(function(error) {
			document.getElementById('spiHelper_move'+func_id).innerHTML = '<div style="color:red"><b>Move failed on <a href="/wiki/'+encodeURI(spiHelper_archiveName)+'" title="' + spiHelper_archiveName +'">' + spiHelper_archiveName + '</a></b>: ' + error + '</div>';
		}).always(function() {
			document.getElementById('spiHelper_AJAX_finished_'+func_id).style.display = '';
		});
	}

	function spiHelper_editPage(title, newtext, summary, createonly, onComplete, watch, sectionId = null) {
		document.getElementById('spiHelper_finished_wrapper').innerHTML = '<span id="spiHelper_AJAX_finished_'+spiHelper_AJAXnumber+'" style="display:none">' + document.getElementById('spiHelper_finished_wrapper').innerHTML + '</span>';
		var func_id = spiHelper_AJAXnumber;
		spiHelper_AJAXnumber++;
		document.getElementById('spiHelper_status').innerHTML += '<li id="spiHelper_edit'+func_id+'">Editing <a href="/wiki/'+encodeURI(title)+'" title="'+title+'">'+title+'</a></li>';
		var baserevid = 0;
		if (title === spiHelper_PageName) {
			baserevid = spiHelper_StartingRevID;
		}
		else {
			baserevid = spiHelper_getPageRev(title);
		}
		var api = new mw.Api();
		const request = {
			action: "edit",
			watchlist: watch,
			summary: summary,
			text: newtext,
			title: title,
			createonly: createonly,
			baserevid: baserevid
		}
		if (sectionId) {
			request['section'] = sectionId;
		}
		api.postWithToken("csrf", request).done(function(data) {
			document.getElementById('spiHelper_edit'+func_id).innerHTML = 'Saved <a href="/wiki/'+encodeURI(title)+'" title="'+title+'">'+title+'</a>';
			// Update revid
			baserevid = data["newrevid"]
			if (onComplete != null) {
				onComplete();
			}
		}).fail(function(error) {
			document.getElementById('spiHelper_edit'+func_id).innerHTML = '<div style="color:red"><b>Edit failed on <a href="/wiki/'+encodeURI(title)+'" title="'+title+'">'+title+'</a></b>: ' + error + '</div>';
		}).always(function() {
			document.getElementById('spiHelper_AJAX_finished_'+func_id).style.display = '';
		});
	}

	function spiHelper_blockUser(user, duration, reason, reblock, anononly, accountcreation, autoblock, talkpage, email ) {
		document.getElementById('spiHelper_finished_wrapper').innerHTML = '<span id="spiHelper_AJAX_finished_'+spiHelper_AJAXnumber+'" style="display:none">' + document.getElementById('spiHelper_finished_wrapper').innerHTML + '</span>';
		var func_id = spiHelper_AJAXnumber;
		spiHelper_AJAXnumber++;
		document.getElementById('spiHelper_status').innerHTML += '<li id="spiHelper_block'+escape(user)+'">Blocking <a href="/wiki/User:'+encodeURI(user)+'" title="User:'+user+'">'+user+'</a></li>';
		var api = new mw.Api();
		api.postWithToken("csrf", {
			action: "block",
			expiry: duration,
			reason: reason,
			reblock: reblock,
			anononly: anononly,
			nocreate: accountcreation,
			autoblock: autoblock,
			allowusertalk: !talkpage,
			noemail: email,
			watchuser: "nochange",
			user: user
		}).done(function(blockData) {
			document.getElementById('spiHelper_block'+escape(user)).innerHTML = 'Blocked <a href="/wiki/User:'+encodeURI(user)+'" title="User:'+user+'">'+user+'</a>';
		}).fail(function(error) {
			document.getElementById('spiHelper_block'+escape(user)).innerHTML = '<div style="color:red"><b>Block failed on <a href="/wiki/User:'+encodeURI(user)+'" title="User:'+user+'">'+user+'</a></b>: ' + error + '</div>';
		}).always(function() {
			document.getElementById('spiHelper_AJAX_finished_'+func_id).style.display = '';
		});
	}

	function escapeHtmlChars(original){
		return original
		.replace(/&/g, "&amp;")
		.replace(/</g, "&lt;")
		.replace(/>/g, "&gt;")
		.replace(/"/g, "&quot;")
		.replace(/'/g, "&#039;");
	}
	function spiHelper_generateBlockTableLine(name, defaultblock, id, admin){
		var row = '<tr>';
		row += '<td><input type="text" name="spiHelper_block_username' + id + '" id="spiHelper_block_username' + id + 
		'" value="' + escapeHtmlChars(name)  + '" style="width:10vw"/></td>';
		if(admin){
			row += '<td><input type="checkbox" name="spiHelper_block_doblock' + id + '" id="spiHelper_block_doblock' + id + 
			'" ' + (defaultblock ? 'checked="checked" ' : '') + '/></td>';
			row += '<td><input type="text" name="spiHelper_block_duration' + id + '" id="spiHelper_block_duration' + id + 
			'" value="indefinite" style="width:10vw"/></td>';
			row += '<td><input type="checkbox" name="spiHelper_block_acb' + id + '" id="spiHelper_block_acb' + id + '" checked="checked" /></td>';
			row += '<td><input type="checkbox" name="spiHelper_block_ab' + id + '" id="spiHelper_block_ab' + id + '" checked="checked" /></td>';
			row += '<td><input type="checkbox" name="spiHelper_block_tp' + id + '" id="spiHelper_block_tp' + id + '" /></td>';
			row += '<td><input type="checkbox" name="spiHelper_block_email' + id + '" id="spiHelper_block_email' + id + '" /></td>';
		}
		row += '<td><select name="spiHelper_block_tag' + id + '" id="spiHelper_block_tag' + id + '" >' + spiHelper_tagValues + '</select></td>';
		row += '<td><select name="spiHelper_block_tag_altmaster' + id + '" id="spiHelper_block_tag_altmaster' + id + '" >' + spiHelper_altmasterTagValues + '</select></td>';

		row += '<td><input type="checkbox" name="spiHelper_block_lock' + id + '" id="spiHelper_block_lock' + id + '"/></td></tr>';
		return row;
	}

	function spiHelper_getPageText(title, show, sectionId = null) {
		const func_id = spiHelper_AJAXnumber;
		spiHelper_AJAXnumber++;
		if(show){
			document.getElementById('spiHelper_status').innerHTML += '<li id="spiHelper_get'+func_id+'">Getting <a href="/wiki/'+encodeURI(title)+'" title="'+title+'">'+title+'</a></li>';
		}
		const request = {
			action: 'query',
			format: 'json',
			prop: 'revisions',
			rvprop: 'content',
			rvslots: 'main',
			indexpageids: true,
			titles: title
		};
		if (sectionId) {
			request['rvsection'] = sectionId;
		}
		const response = JSON.parse(
			$.ajax({
				url: mw.util.wikiScript('api'),
				data: request,
				async: false
			})
			.responseText
		);
		const pageid = response['query']['pageids'][0];
		if (pageid == "-1") {
			if(show){
				document.getElementById('spiHelper_get'+func_id).innerHTML = '<a class="new" href="/wiki/'+encodeURI(title)+'" title="'+title+'">'+title+'</a> does not exist';
			}
			return '';
		}
		pagetext = response['query']['pages'][pageid]['revisions'][0]['slots']['main']['*'];
		if(show){
			document.getElementById('spiHelper_get'+func_id).innerHTML = 'Got <a href="/wiki/'+encodeURI(title)+'" title="'+title+'">'+title+'</a>';
		}
		return pagetext;
	}
	
	function spiHelper_getInvestigationSectionIDs() {
		// Uses the parse API to get page sections, then find the investigation
		// sections (should all be level-3 headers)
		const request = {
			action: 'parse',
			format: 'json',
			prop: 'sections',
			page: spiHelper_PageName,
		};
		const response = JSON.parse(
			$.ajax({
				url: mw.util.wikiScript('api'),
				data: request,
				async: false
			})
			.responseText
		);
		const dateSections = []
		for (var i = 0; i < response['parse']['sections'].length; i++) {
			if (response['parse']['sections'][i]["level"] === "3") {
				dateSections.push(response['parse']['sections'][i]["index"])
			}
		}
		return dateSections;
	}

	function spiHelper_getPageRev(title) {
		var request = {
			format: 'json',
			action: 'query',
			prop: 'revisions',
			indexpageids: true,
			titles : title
		};
		var response = JSON.parse(
			$.ajax({
				url: mw.util.wikiScript('api'),
				data: request,
				async: false
			})
			.responseText
		);
		pageid = response['query']['pageids'][0];
		if (pageid == "-1") {
			return 0;
		}
		return response['query']['pages'][pageid]['revisions'][0]['revid'];
	}
	
	function spiHelper_getPostExpandSize(title, sectionId = null) {
		// Synchronous method to get a page's post-expand include size given its title
		const request = {
			action: 'parse',
			format: 'json',
			prop: 'limitreportdata',
			page: title,
		};
		if(sectionId) {
			request['section'] = sectionId;
		}
		const response = JSON.parse(
			$.ajax({
				url: mw.util.wikiScript('api'),
				data: request,
				async: false
			})
			.responseText
		);
		// The page might not exist, so we need to handle that smartly - only get the parse
		// if the page actually parsed
		if ('parse' in response) {
			// Iterate over all properties to find the PEIS
			for(var i = 0; i < response['parse']['limitreportdata'].length; i ++) {
				if(response['parse']['limitreportdata'][i]['name'] == "limitreport-postexpandincludesize") {
					return response['parse']['limitreportdata'][i][0];
				}
			}
		}
		else {
			// Fallback - most likely the page doesn't exist
			return 0;
		}
	}
	
	function spiHelper_getMaxPostExpandSize() {
		return mw.config.get('wgPageParseReport')['limitreport']['postexpandincludesize']['limit'];
	}

	function spiHelper_generateSelect(title, options, onchange){
		var text = '<select name="' + title + '" id="' + title +'" ';
		if(onchange != null)
			text += 'onchange = "' + onchange + '" ';
		text+= '>';
		for(var i = 0; i < options.length; i ++){
			var o = options[i];
			text += '<option value="' + o.value + '" ';
			if(o.selected)
				text += 'selected="selected" ';
			text += '>' + o.label + '</option>';
		}
		text += "</select>";
		return text;
	}

	function spiHelper_oneClickArchive(){
		var pagetext = spiHelper_getPageText(spiHelper_PageName, false);
		if(!spiHelper_section_re.test(pagetext)){
			alert("Looks like the page has been archived already.");
			return;
		}
		displayMessage('<ul id="spiHelper_status"></ul><ul id="spiHelper_finish"></ul>');
		document.getElementById('spiHelper_finish').innerHTML += '<span id="spiHelper_finished_wrapper"><span id="spiHelper_finished_main" style="display:none"><li id="spiHelper_done"><b>Done (<a href="/wiki/'+encodeURI(spiHelper_PageName)+'?action=purge" title="'+spiHelper_PageName+'">Reload page</a>)</b></li></span></span>';
		spiHelper_archiveCase('');
		document.getElementById('spiHelper_finished_main').style.display = '';
	}
	
	function spiHelper_checkAllBlockOpts(source) {
		for(var i = 1; i <= spiHelper_usercount; i++){
			if (source.type == "checkbox") {
				document.getElementById(source.id + i).checked = source.checked;
			}
			else {
				document.getElementById(source.id + i).value = source.value;
			}
		}
	}

	function spiHelper_addLink() {
		// fires on page load, adds the SPI portlet and (if the page is categorized as "awaiting archive," meaning that at least one closed template
		// is on the page) the SPI-Archive portlet
		mw.loader.using('mediawiki.util').then(function() {
			mw.util.addPortletLink("p-cactions", "javascript:spiHelper_init()", "SPI", "ca-spiHelper", "SPI");
			if(mw.config.get('wgCategories').toString().indexOf('SPI cases awaiting archive') != -1)
				mw.util.addPortletLink("p-cactions", "javascript:spiHelper_oneClickArchive()", "SPI-Archive", "ca-spiHelperArchive", "SPI-Archive");
		});
	}
	$(spiHelper_addLink);
}