Place Harmonizer Beta

Harmonizes, formats, and locks a selected place

Versión del día 31/12/2015. Echa un vistazo a la versión más reciente.

/* global I18n */
/* global OpenLayers */
/* global $ */
/* global W */
/* global unsafeWindow */
/* global Components */
// ==UserScript==
// @name		 Place Harmonizer Beta
// @namespace 	 https://greasyforks.org/en/users/19426-bmtg
// @version	  1.0.09
// @description  Harmonizes, formats, and locks a selected place
// @author	   WMEPH development group
// @include			 https://www.waze.com/editor/*
// @include			 https://www.waze.com/*/editor/*
// @include			 https://editor-beta.waze.com/editor/*
// @include			 https://editor-beta.waze.com/*/editor/*
// @grant	   GM_xmlhttpRequest
// ==/UserScript==
(function () {
	
	var USA_PNH_DATA;
	var USA_PNH_NAMES = [];
	var CAN_PNH_DATA;
	var CAN_PNH_NAMES = [];
			
	function placeHarmonizer_bootstrap() {
		var bGreasemonkeyServiceDefined	= false;
		try { 
			if ("object" === typeof Components.interfaces.gmIGreasemonkeyService) {
				bGreasemonkeyServiceDefined = true;
			}
		}
		catch (err) { //Ignore. 
		}
		if ( "undefined" === typeof unsafeWindow || ! bGreasemonkeyServiceDefined) {
			unsafeWindow = ( function () {
				var dummyElem = document.createElement('p');
				dummyElem.setAttribute ('onclick', 'return window;');
				return dummyElem.onclick ();
			} ) ();
		}
		
		
		/* begin running the code! */
		if (("undefined" !== typeof W.loginManager)) {
			// Pull USA PNH Data
			GM_xmlhttpRequest({
				method: "GET",
				url: "https://docs.google.com/spreadsheets/d/1-f-JTWY5UnBx-rFTa4qhyGMYdHBZWNirUTOgn222zMY/export?format=tsv&id=1-f-JTWY5UnBx-rFTa4qhyGMYdHBZWNirUTOgn222zMY&gid=1061880663",
				onload: function (response) {
					//console.log( response.responseText );
					USA_PNH_DATA = response.responseText.split('\n');
				}
			});
			// Pull CAN PNH Data
			GM_xmlhttpRequest({
				method: "GET",
				url: "http://docs.google.com/spreadsheets/d/1TIxQZVLUbAJ8iH6LPTkJsvqFb_DstrHpKsJbv1W1FZs/export?format=tsv&id=1TIxQZVLUbAJ8iH6LPTkJsvqFb_DstrHpKsJbv1W1FZs&gid=947416380",
				onload: function (response) {
					//console.log( response.responseText );
					CAN_PNH_DATA = response.responseText.split('\n');
				}
			});
			dataReady();  //  Run the code
		} else {
			console.log("WMEPH: Bootstrap failed.  Trying again...");
			setTimeout(function () { placeHarmonizer_bootstrap(); }, 700);
		}
		
		function dataReady() {
			// If the data has returned, then start, otherwise wait a bit longer
			if ("undefined" !== typeof CAN_PNH_DATA && "undefined" !== typeof USA_PNH_DATA) {
				setTimeout(function(){ // Build the name search lists
					USA_PNH_NAMES = makeNameCheckList(USA_PNH_DATA);
					CAN_PNH_NAMES = makeNameCheckList(CAN_PNH_DATA);
				}, 200);
				setTimeout(runPH, 900);  //  start the main code
			} else {
				console.log("WMEPH: Waiting for PNH Data...");
				setTimeout(function () { dataReady(); }, 200);
			}
		}
	}
	
	function runPH() {
		var WMEPHversion = "1.0.09";
		var WMEPHWhatsNew = ['All new version with new features', 'Live integration with PNH data', 'Over 600 chains harmonized', 'Works in the USA and Canada', 'Interactive banner buttons', 'Fixed bugs'];  // New in this version
		var newSep = '\n - ';
		WMEPHWhatsNew = WMEPHWhatsNew.join(newSep);
		// If the editor installs a newer version, pop up an alert with the new elements
		// if ( localStorage.getItem('WMEPHversion') === null ) {
		// 	localStorage.setItem('WMEPHversion', WMEPHversion);
		// } else 
		if ( localStorage.getItem('WMEPHversion') !== WMEPHversion ) {
			alert('WMEPH updated to v. ' + WMEPHversion + '\nUpdates:' + newSep + WMEPHWhatsNew);
			localStorage.setItem('WMEPHversion', WMEPHversion);
			localStorage.setItem(GLinkWarning, '0');
		}
		
		// initialize the KB shortcut and settings tab
		setTimeout(setupKBShort, 100);  // set up KB Shortcut 
		setTimeout(add_PlaceHarmonizationSettingsTab, 150);  // set up settings tab
		
		// function to prime the shortcuts
		function setupKBShort() {
			console.log("WMEPH: Initializing");
			if (isDevVersion) {
				shortcut.add("Shift+Alt+s", function() { harmonizePlace(); });
			} else {
				shortcut.add("Shift+Alt+a", function() { harmonizePlace(); });
			}
		}
		
		var WMEPHurl = 'https://www.waze.com/forum/posting.php?mode=reply&f=819&t=164962';  // WMEPH Forum thread URL
		var USAPNHMasURL = 'https://docs.google.com/spreadsheets/d/1-f-JTWY5UnBx-rFTa4qhyGMYdHBZWNirUTOgn222zMY/edit#gid=0';  // Master USA PNH link
		var placesWikiURL = 'https://wiki.waze.com/wiki/Places';
		var isDevVersion = true;
		var thisUser = W.loginManager.user;
		if (thisUser === null) {
			console.log("WMEPH: Could not determine user.");
			return;
		}
		var WMEPHdevList = "bmtg|vtpearce|cardyin|jtsmith2|joyriding|fjsawicki|coolcanuck".split("|");  
		var WMEPHbetaList = "jwe252|uscwaller|t0cableguy|tonestertm|driving79|nacron|machete808|bz2012|carloslaso|xanderb|ehepner1977|roadtechie|havanaday|nimbus-|itzwolf|jroman7|mrbyte_ontheroad|ggrane|ardan74|red-nax|wambuli|turbomkt|trackmum".split("|");
		var devUser = (WMEPHdevList.indexOf(thisUser.userName.toLowerCase()) > -1);
		var betaUser = (WMEPHbetaList.indexOf(thisUser.userName.toLowerCase()) > -1);
		if (devUser) {betaUser = true;}  // dev users are beta users
		var usrRank = thisUser.normalizedLevel;  // get editor's level
		var GLinkWarning = 'GLinkWarning';  // Warning message for first time users not to use 3rd party sources.
		if (!localStorage.getItem(GLinkWarning)) {  // store settings so the warning is only given once
			localStorage.setItem(GLinkWarning, '0');
		}
		// lock levels are offset by one
		var lockLevel2 = 1;
		var lockLevel3 = 2;
		var lockLevel4 = 3;
		var lockLevel5 = 4;
		// Only lock up to the user's level
		if (lockLevel2 > (usrRank - 1)) {lockLevel2 = (usrRank - 1);}
		if (lockLevel3 > (usrRank - 1)) {lockLevel3 = (usrRank - 1);}
		if (lockLevel4 > (usrRank - 1)) {lockLevel4 = (usrRank - 1);}
		if (lockLevel5 > (usrRank - 1)) {lockLevel5 = (usrRank - 1);}
		
		var PMUserList = { // user names and IDs
			SER: {approvalActive: true, modID: '16941753', modName: 't0cableguy'},
			WMEPH: {approvalActive: true, modID: '17027620', modName: 'bmtg'},
		};
		
		var devVersStr;
		var devVersStrSpace;
		//var forumMsgInputs = { subject: 'Re: WMEPH', message: 'Message:', addbbcode20: '100', preview: 'Preview', attach_sig: 'on', notify: 'on' }; // Default forum post
		//var forumPMInputs = { subject: 'WMEPH message', message: 'Message:', preview: 'Preview', attach_sig: 'on' }; // Default PM post
		var severity;  // error tracking to determine banner color (messages)
		var severityButt;  // error tracking to determine banner color (action buttons)
		var bannButt;  // Banner Buttons object
		var sidebarMessageOld;  //  *** Eventually delete once new method is complete
		var sidebarMessage;  // Holds the banner messages
		var bannMess = {    // banner message array in order of display
			bankType1: { active: false, severity: 3, message: 'Clarify the type of bank: the name has ATM but the primary category is Offices' },
			gasBrandMM: { active: false, severity: 3, message: 'Gas name and brand do not appear to match. Verify which is correct.' },
			gasUnbranded: { active: false, severity: 3, message: '"Unbranded" should not be used for the station brand. Change to the correct brand or use the blank entry at the top of the brand list.' },
			areaNotPoint: { active: false, severity: 3, message: 'This category should be an area place.  Either change it, or manually lock it.' },
			areaStadium: { active: false, severity: 3, message: 'This category should be an area.  Either change it, manually lock it, or consider using "Sports Court" category and a place point for small/local fields.' },
			areaPostOfficeSER: { active: false, severity: 3, message: 'Only use the "Post Office" category for USPS post offices.  If this is a USPS location, please change to an area place and run the script again.  All other mail service places use the "Shopping and Services" Category.' },
			areaHospital: { active: false, severity: 3, message: 'This category should usually be an area.  Either change it, or use the "Office" category for non-emergency medical offices.' },
			unmappedSER: { active: false, severity: 3, message: 'This category is usually not mapped in the SE region.  If it\'s a valid place, please manually lock it.' },
			pointCarDealerSER: { active: false, severity: 3, message: 'This category should be a point place, not an area.' },
			nameMissing: { active: false, severity: 3, message: 'Name is missing.' },
			hnMissing: { active: false, severity: 3, message: 'House number missing.' },
			hnNonStandard: { active: false, severity: 3, message: 'House number is non-standard. Correct and rerun script, or manually lock the place.' },
			streetMissing: { active: false, severity: 3, message: 'Street missing.' },
			cityMissing: { active: false, severity: 3, message: 'City missing.' },
			
			checkDescription: { active: false, severity: 2, message: 'Description field already contained info; PNH description was added in front of existing. Check for consistency or duplicate info.' },
			resiTypeName: { active: false, severity: 2, message: 'The place name suggests a residential place.  Please verify.' },
			phoneInvalid: { active: false, severity: 2, message: 'Phone invalid.' },
			
			subFuel: { active: false, severity: 1, message: 'Make sure this place is for the gas station itself and not the main store building.  Otherwise undo and check the categories.' },
			catHotel: { active: false, severity: 1, message: 'Please check hotel details, as names can often be unique (e.g. Holiday Inn - Tampa North).' },
			catPostOffice: { active: false, severity: 1, message: 'Verify the primary name according to your regional standards. If this is not a USPS post office, change the category, as "Post Office" is only used for USPS locations.' },
			phoneMissing: { active: false, severity: 1, message: 'Phone missing.' },
			urlMissing: { active: false, severity: 1, message: 'URL missing.' },
			gasNoBrand: { active: false, severity: 1, message: 'Verify that gas station has no brand.' },
			
			babiesRUs: { active: false, severity: 0, message: 'If there is a Toys R Us at this location, please make it the primary name and Babies R Us the alt name and rerun the script.' },
			generalStadium: { active: false, severity: 0, message: 'If this is a small/local ballfield or arena, consider using the "Sports Court" category and making it a point place.' },
			placeFormatted: { active: false, severity: 0, message: 'Place formatted.' },
			placeMatched: { active: false, severity: 0, message: 'Place matched from PNH data.' },
			placeLocked: { active: false, severity: 0, message: 'Place locked.' }
		};
		
		var catTransWaze2Lang = I18n.translations['en'].venues.categories;  // pulls the category translations
		var item;
		var newName;
		var newAliases = [];
		var newAliasesTemp = [];
		var newCategories = [];
		var newURL;
		var newPhone;
		var newServices = [];
		
		if (isDevVersion && thisUser.userName !== 'bmtg') {
			debugger;
		}
		
		// CSS setups
		var cssCode = [".PHbutton {background: #ffffff;color: #000;padding: 0px 6px 0px 6px;text-decoration: none;}",
			".PHbutton:hover {background: #e8e5e8;text-decoration: none;}"];
		for (var cssix=0; cssix<cssCode.length; cssix++) {
			insertCss(cssCode[cssix]);
		}
		function insertCss( code ) {
			var style = document.createElement('style');
			style.type = 'text/css';
			style.innerHTML = code;
			document.head.appendChild( style );
		}  // END insertCss funtion
		
		// used for phone reformatting
		if (!String.plFormat) {
			String.plFormat = function(format) {
				var args = Array.prototype.slice.call(arguments, 1);
				return format.replace(/{(\d+)}/g, function(name, number) {
					return typeof args[number] !== "undefined" ? args[number] : null;
				});
			};
		}
		
		// Banner production
		function WMEPH_DispWarn(e) {
			"use strict";
			// var numWords = e.split(' ').length;
			//var r;
			//if ("undefined" === typeof o) {
			//	r = 3e7;  // Don't remove the banner
			// OLD: If no duration is specified, use 30 seconds + 3 seconds per word (disabled)
			//	r = 3e4 + numWords * 3000;
			//} else {r = o;}
			e = "<li>" + e;
			var n = $('<div id="WMEPHlog">').append(e);
			$("#WMEPH_logger_warn").append(n);
			//$("#WMEPH_logger_warn").append(n), n.delay(r).slideUp({
			//	duration: 200,
			//	complete: function() { n.remove(); }
			//})
		}  // END WMEPH_DispWarn function
	
		// Change place.name to title case
		var ignoreWords = ["an", "and", "as", "at", "by", "for", "from", "hhgregg", "in", "into", "of", "on", "or", "the", "to", "with"];
		var capWords = ["3M", "AMC", "AOL", "AT&T", "ATM", "BBC", "BLT", "BMV", "BMW", "BP", "CBS", "CCS", "CGI", "CISCO", "CNN", "CVS", "DHL", "DKNY",
			"DMV", "DSW", "ER", "ESPN", "FCUK", "GNC", "H&M", "HP", "HSBC", "IBM", "IKEA", "IRS", "JBL", "JCPenney", "KFC", "LLC", "MBNA", "MCA", "MCI",
			"NBC", "PNC", "TCBY", "TNT", "UPS", "USA", "USPS", "VW", "ZZZ"
		];
		function toTitleCase(str) {
			if (!str) {
				return str;
			}
			var allCaps = (str === str.toUpperCase());
			// Cap first letter of each word
			str = str.replace(/\b([^\W_\d][^\s-\/]*) */g, function(txt) {
				return ((txt === txt.toUpperCase()) && !allCaps) ? txt : txt.charAt(0).toUpperCase() + txt.substr(1);
			});
			// Cap O'Reilley's, L'Amour, D'Artagnan as long as 5+ letters
			str = str.replace(/[oOlLdD]'[A-Za-z']{3,}/g, function(txt) {
				return ((txt === txt.toUpperCase()) && !allCaps) ? txt : txt.charAt(0).toUpperCase() + txt.charAt(1) + txt.charAt(2).toUpperCase() + txt.substr(3);
			});
			// Cap McFarley's, as long as 5+ letters long
			str = str.replace(/[mM][cC][A-Za-z']{3,}/g, function(txt) {
				return ((txt === txt.toUpperCase()) && !allCaps) ? txt : txt.charAt(0).toUpperCase() + txt.charAt(1).toLowerCase() + txt.charAt(2).toUpperCase() + txt.substr(3);
			});
			// anything with an "&" sign, cap the word after &
			str = str.replace(/&\w+/g, function(txt) {
				return ((txt === txt.toUpperCase()) && !allCaps) ? txt : txt.charAt(0) + txt.charAt(1).toUpperCase() + txt.substr(2);
			});
			// lowercase any from the ignoreWords list
			str = str.replace(/[^ ]+/g, function(txt) {
				var txtLC = txt.toLowerCase();
				return (ignoreWords.indexOf(txtLC) > -1) ? txtLC : txt;
			});
			// uppercase any from the capWords List
			str = str.replace(/[^ ]+/g, function(txt) {
				var txtLC = txt.toUpperCase();
				return (capWords.indexOf(txtLC) > -1) ? txtLC : txt;
			});
			// Cap first letter of entire name
			str = str.charAt(0).toUpperCase() + str.substr(1);
			return str;
		}
	
		// Change place.name to title case
		function toTitleCaseStrong(str) {
			if (!str) {
				return str;
			}
			var allCaps = (str === str.toUpperCase());
			// Cap first letter of each word
			str = str.replace(/\b([^\W_\d][^\s-\/]*) */g, function(txt) {
				return ((txt === txt.toUpperCase()) && !allCaps) ? txt : txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
			});
			// Cap O'Reilley's, L'Amour, D'Artagnan as long as 5+ letters
			str = str.replace(/[oOlLdD]'[A-Za-z']{3,}/g, function(txt) {
				return ((txt === txt.toUpperCase()) && !allCaps) ? txt : txt.charAt(0).toUpperCase() + txt.charAt(1) + txt.charAt(2).toUpperCase() + txt.substr(3).toLowerCase();
			});
			// Cap McFarley's, as long as 5+ letters long
			str = str.replace(/[mM][cC][A-Za-z']{3,}/g, function(txt) {
				return ((txt === txt.toUpperCase()) && !allCaps) ? txt : txt.charAt(0).toUpperCase() + txt.charAt(1).toLowerCase() + txt.charAt(2).toUpperCase() + txt.substr(3).toLowerCase();
			});
			// anything sith an "&" sign, cap the word after &
			str = str.replace(/&\w+/g, function(txt) {
				return ((txt === txt.toUpperCase()) && !allCaps) ? txt : txt.charAt(0) + txt.charAt(1).toUpperCase() + txt.substr(2);
			});
			// lowercase any from the ignoreWords list
			str = str.replace(/[^ ]+/g, function(txt) {
				var txtLC = txt.toLowerCase();
				return (ignoreWords.indexOf(txtLC) > -1) ? txtLC : txt;
			});
			// uppercase any from the capWords List
			str = str.replace(/[^ ]+/g, function(txt) {
				var txtLC = txt.toUpperCase();
				return (capWords.indexOf(txtLC) > -1) ? txtLC : txt;
			});
			// Cap first letter of entire name
			str = str.charAt(0).toUpperCase() + str.substr(1);
			return str;
		}
		
		// Form banner Message
		function formBannMess(bannKey) {
			bannMess[bannKey].active = true;
			severity = Math.max(bannMess[bannKey].severity, severity);
		}
		
		// Form banner Button Message
		function formBannButt(bannKey) {
			bannButt[bannKey].active = true;
			severityButt = Math.max(bannButt[bannKey].severity, severityButt);
		}
		
		// normalize phone
		function normalizePhone(s, outputFormat) {
			if (!s) {
				formBannMess('phoneMissing');
				return s;
			}
			var s1 = s.replace(/\D/g, '');  // remove non-number characters
			var m = s1.match(/^1?([2-9]\d{2})([2-9]\d{2})(\d{4})$/);  // Ignore leading 1, and also don't allow area code or exchange to start with 0 or 1 (***USA/CAN specific)
			if (!m) {
				formBannMess('phoneInvalid');
				return s;
			} else {
				return String.plFormat(outputFormat, m[1], m[2], m[3]);
			}
		}
		
		// Normalize url
		function normalizeURL(placeName,s,addr) {
			if (!s) {  // Notify that url is missing and provide web search to find website and gather data (provided for all editors)
				formBannMess('urlMissing');
				formBannButt('webSearch');
				return s;
			}
			s = s.replace(/ \(.*/g, '');
			s = s.replace(/ /g, '');  // remove any spaces
			
			var m = s.match(/^https?:\/\/(.*)$/i);  // remove http(s):// 
			if (m) { s = m[1]; } 
			
			if ($("#WMEPH-StripWWW" + devVersStr).prop('checked')) {  // if option is checked, remove 'www.' from the url
				m = s.match(/^www\.(.*)$/i);
				if (m) { s = m[1]; } 
			}
			m = s.match(/^(.*)\/$/i);  // remove final slash
			if (m) { s = m[1]; }
			 
			return s;
		}  // END normalizeURL function
	
		// Only run the harmonization if a venue is selected
		function harmonizePlace() {
			// Script is only for R2+ editors
			if (usrRank < 2) {
				alert("Script is currently available for editors of Rank 2 and up.");
				return;
			}
			
			// Only run if a single place is selected
			if (W.selectionManager.selectedItems.length === 1) {
				var item = W.selectionManager.selectedItems[0].model;
				if (item.type === "venue") { 
					blurAll();  // focus away from current cursor position
					harmonizePlaceGo(); 
				}
			}
		}
	
		// Main script
		function harmonizePlaceGo() {
			// Not sure what this does, but it's in all the other scripts that update Waze objects
			var UpdateObject;
			if (typeof(require) !== "undefined") {
				UpdateObject = require("Waze/Action/UpdateObject");
			} else {
				UpdateObject = W.Action.UpdateObject;
			}
			
			var placePL = WMEPH_initialiseFL();  //  set up external post div and pull place PL
			placePL = placePL.replace(/&layers=[\d]+/g, '');  // remove Permalink Layers
			var region;
			var state2L;
			var gFormState = "";
			var newPlaceURL;
			var approveRegionURL;
			var PNHOrderNum = "";
			var PNHNameTemp = "";
			sidebarMessageOld = [];
			sidebarMessage = [];
			// var topSBMess;  // Unused, delete
			severity = 0;
			severityButt = 0;
			
			for (var bannKey in bannMess) {
				bannMess[bannKey].active = false;
			}
			
			bannButt = {  // set up banner action buttons.  Structure:
				// active: false until activated in the script 
				// bannText: The text before the button option
				// id: button id
				// value: button text
				// title: tooltip text
				// cLog: message for console
				// action: The action that happens if the button is pressed
				addAlias: {  // append optional Alias to the name
					active: false, 
					bannText: "Is there a " + newAliasesTemp[0] + " at this location?", 
					severity: 0,  
					id: "addAlias",  
					value: "Yes", 
					title: 'Add ' + newAliasesTemp[0],
					cLog: "WMEPH: added optional alt-name",  
					action: function() {
						newAliases = insertAtIX(newAliases,newAliasesTemp,0);
						if (specCases.indexOf('altName2Desc') > -1) {
							if ( item.attributes.description.toUpperCase().indexOf(newAliasesTemp.toUpperCase()) === -1 ) {
								newDescripion = newAliasesTemp + '\n' + newDescripion;
								W.model.actionManager.add(new UpdateObject(item, { description: newDescripion }));
							}
						}
						W.model.actionManager.add(new UpdateObject(item, { aliases: newAliases }));
						//console.log(bannButt.addAlias.cLog);  
						bannButt.addAlias.active = false;  // reset the display flag
					}
				},  // END addAlias definition
				addCat2: {  // append optional secondary category to the place
					active: false, 
					bannText: "Is there a " + newCategories[0] + " at this location?", 
					severity: 0,  
					id: "addCat2",  
					value: "Yes", 
					title: 'Add ' + newCategories[0],
					cLog: "WMEPH: added optional secondary category",  
					action: function() {
						newCategories.push.apply(newCategories,altCategories);
						W.model.actionManager.add(new UpdateObject(item, { categories: newCategories }));
						//console.log(bannButt.addCat2.cLog);  
						bannButt.addCat2.active = false;  // reset the display flag
					}
				},  // END addCat2 definition
				addPharm: {  // append Pharmacy to the place
					active: false, 
					bannText: "Is there a Pharmacy at this location?", 
					severity: 1,  
					id: "addPharm",  
					value: "Yes", 
					title: 'Add Pharmacy category',
					cLog: "WMEPH: added Pharmacy",  
					action: function() {
						newCategories = insertAtIX(newCategories, 'PHARMACY', 1);
						W.model.actionManager.add(new UpdateObject(item, {
							categories: newCategories
						}));
						//console.log(bannButt.addPharm.cLog);  
						bannButt.addPharm.active = false;  // reset the display flag
					}
				},  // END addPharm definition
				appendAMPM: {  // append AMPM to the name
					active: false, 
					bannText: "Is there an ampm at this location?", 
					severity: 1,  
					id: "appendAMPM",  
					value: "Yes", 
					title: 'Add ampm to the place',
					cLog: "WMEPH: added ampm",  
					action: function() {
						newCategories = insertAtIX(newCategories, 'CONVENIENCE_STORE', 1);
						newName = 'ARCO ampm';
						newURL = 'ampm.com';
						W.model.actionManager.add(new UpdateObject(item, {
							name: newName,
							url: newURL,
							categories: newCategories
						}));
						//console.log(bannButt.appendAMPM.cLog);  
						bannButt.appendAMPM.active = false;  // reset the display flag
						bannButt.addConvStore.active = false;  // also reset the addConvStore display flag
					}
				},  // END appendAMPM definition
				gasMismatch: {  // if the gas brand and name don't match
					active: false, 
					bannText: "Gas name and brand don't match.  Move brand to name?", 
					severity: 3,  
					id: "gasMismatch",  
					value: "Yes", 
					title: 'Change the primary name to the brand and make the current name the alt-name.',
					cLog: "WMEPH: Updated station name from brand",  
					action: function() {
						newAliases = insertAtIX(newAliases, newName, 0);
						W.model.actionManager.add(new UpdateObject(item, {
							name: brand,
							aliases: newAliases
						}));
						console.log(bannButt.gasMismatch.cLog);  
						bannButt.gasMismatch.active = false;  // reset the display flag
						newName = item.attributes.brand;
					}
				},  // END gasMismatch definition
				STC: {  // Force strong title case option
					active: false,  // Activated if Strong Title Case != Normal Title Case (e.g. HomeSpace Company)
					bannText: "Force Title Case: ", 
					severity: 0,  
					id: "toTitleCaseStrong",  
					value: "Yes", 
					title: "Force Title Case to InterNal CaPs",
					cLog: "WMEPH: Applied Strong Title Case",  
					action: function() {
						newName = toTitleCaseStrong(item.attributes.name);  // Get the Strong Title Case name
						if (newName !== item.attributes.name) {  // if they are not equal
							W.model.actionManager.add(new UpdateObject(item, {  //  update the place name
								name: newName
							}));
							console.log(bannButt.STC.cLog);  
							bannButt.STC.active = false;  // reset the display flag
						}
					}
				},  // END Strong Title Case definition
				addATM: {
					active: false,
					bannText: "ATM at location? ",
					severity: 0,  
					id: "addATM",
					value: "Yes",
					title: "Add the ATM category to this place",
					cLog: "WMEPH: Added ATM category",
					action: function() {
						newCategories = insertAtIX(newCategories,"ATM",1);  // Insert ATM category in the second position
						W.model.actionManager.add(new UpdateObject(item, {  //  update the place name
							categories: newCategories
						}));
						bannButt.addATM.active = false;   // reset the display flag
					}
				},  // END addATM definition
				standaloneATM: {
					active: false,
					bannText: "Is this a standalone ATM? ",
					severity: 2,  
					id: "standaloneATM",
					value: "Yes",
					title: "Is this a standalone ATM with no bank branch?",
					cLog: "WMEPH: Changed to standalone ATM",
					action: function() {
						newCategories = ["ATM"];  // Change to ATM only
						if (newName.indexOf("ATM") === -1) {
							newName = newName + ' ATM';	
						}
						W.model.actionManager.add(new UpdateObject(item, { categories: newCategories }));
						bannButt.bankCorporate.active = false;   // reset the bank Branch display flag
						bannButt.bankBranch.active = false;   // reset the bank Branch display flag
						bannButt.standaloneATM.active = false;   // reset the standalone ATM display flag
					}
				},  // END standaloneATM definition
				bankBranch: {
					active: false,
					bannText: "Is this a bank branch office? ",
					severity: 1,  
					id: "bankBranch",
					value: "Yes",
					title: "Is this a bank branch office?",
					cLog: "WMEPH: Changed to bank branch",
					action: function() {
						newCategories = ["BANK_FINANCIAL","ATM"];  // Change to bank and atm cats
						newName = newName.replace(/[\- (]*ATM[\- )]*/g, ' ').replace(/^ /g,'').replace(/ $/g,'');	 // strip ATM from name if present
						W.model.actionManager.add(new UpdateObject(item, { categories: newCategories }));
						W.model.actionManager.add(new UpdateObject(item, { name: newName }));
						bannButt.bankCorporate.active = false;   // reset the bank Branch display flag
						bannButt.bankBranch.active = false;   // reset the bank Branch display flag
						bannButt.standaloneATM.active = false;   // reset the standalone ATM display flag
					}
				},  // END bankBranch definition
				bankCorporate: {
					active: false,
					bannText: "Is this the bank's corporate offices?",
					severity: 1,  
					id: "bankCorporate",
					value: "Yes",
					title: "Is this the bank's corporate offices?",
					cLog: "WMEPH: Changed to bank branch",
					action: function() {
						newCategories = ["OFFICES"];  // Change to offices category
						newName = newName.replace(/[\- (]*ATM[\- )]*/g, ' ').replace(/^ /g,'').replace(/ $/g,'');	 // strip ATM from name if present
						W.model.actionManager.add(new UpdateObject(item, { categories: newCategories }));
						W.model.actionManager.add(new UpdateObject(item, { name: newName }));
						bannButt.bankCorporate.active = false;   // reset the bank Branch display flag
						bannButt.bankBranch.active = false;   // reset the bank Branch display flag
						bannButt.standaloneATM.active = false;   // reset the standalone ATM display flag
					}
				},  // END bankCorporate definition
				addConvStore: {
					active: false,
					bannText: "Add convenience store category? ",
					severity: 1,  
					id: "addConvStore",
					value: "Yes",
					title: "Add the Convenience Store category to this place",
					cLog: "WMEPH: Added Convenience Store category",
					action: function() {
						newCategories = insertAtIX(newCategories,"CONVENIENCE_STORE",1);  // Insert C.S. category in the second position
						W.model.actionManager.add(new UpdateObject(item, {  //  update 
							categories: newCategories
						}));
						bannButt.addConvStore.active = false;   // reset the display flag
					}
				},  // END addConvStore definition
				isitUSPS: {
					active: false,
					bannText: "Is this a USPS location? ",
					severity: 0,  
					id: "isitUSPS",
					value: "Yes",
					title: "Is this a USPS location?",
					cLog: "WMEPH: Fixed USPS",
					action: function() {
						newServices = ["AIR_CONDITIONING", "CREDIT_CARDS", "PARKING_FOR_CUSTOMERS", "WHEELCHAIR_ACCESSIBLE"];
						W.model.actionManager.add(new UpdateObject(item, { url: "usps.com" }));
						if (region === 'SER') {
							W.model.actionManager.add(new UpdateObject(item, { aliases: ["United States Postal Service"] }));
						}
						bannButt.isitUSPS.active = false;
					}
				},  // END isitUSPS definition
				PlaceWebsite: {
					active: false,
					bannText: "",
					severity: 0,  
					id: "PlaceWebsite",
					value: "Open place website",
					title: "Direct link to place website",
					cLog: "WMEPH: Open web search",
					action: function() {
						window.open('http:\/\/' + newURL);
					}
				},  // END PlaceWebsite definition
				webSearch: {
					active: false,
					bannText: "",
					severity: 0,  
					id: "webSearch",
					value: "Web Search",
					title: "Search the web for this place.  Do not copy info from 3rd party sources!",
					cLog: "WMEPH: Open web search",
					action: function() {
						if (localStorage.getItem(GLinkWarning) === '1') {
							window.open(buildGLink(newName,addr));
						} else {
							if (confirm('***Please DO NOT copy info from Google or third party sources.*** This link is to help you find the business webpage.\nClick OK to agree and continue.') ) {  // if the category doesn't translate, then pop an alert that will make a forum post to the thread
								localStorage.setItem(GLinkWarning, '1');
								window.open(buildGLink(newName,addr));
							}
						}
					}
				},  // END webSearch definition
				NewPlaceSubmit: {
					active: false,
					bannText: "No PNH match. If place is a chain: ",
					severity: 0,  
					id: "NewPlaceSubmit",
					value: "Submit new data",
					title: "Submit info for a new chain through the linked form",
					cLog: "WMEPH: Open submit new place form",
					action: function() {
						window.open(newPlaceURL);
					}
				},  // END NewPlaceSubmit definition
				ApprovalSubmit: {
					active: false,
					bannText: "PNH data exists but is not approved for your region: ",
					severity: 0,  
					id: "ApprovalSubmit",
					value: "Request approval",
					title: "Request region/country approval of this place",
					cLog: "WMEPH: Open request approval form",
					action: function() {
						if (PMUserList.hasOwnProperty(region)) {
							if (PMUserList[region].approvalActive) {
								var forumPMInputs = {
									subject: 'PNH place approval for "' + PNHNameTemp + '"',
									message: 'Please approve "' + PNHNameTemp + '" for the ' + region + ' region.  Thanks\n \nPNH order number: ' + PNHOrderNum + '\n \nExample Permalink: ' + placePL + '\n \nPNH Link: ' + USAPNHMasURL,
									preview: 'Preview', attach_sig: 'on' 
								};
								forumPMInputs['address_list[u]['+PMUserList[region].modID+']'] = 'to';  // SER region, sends a PM to t0cableguy = 16941753
								WMEPH_openPostDataInNewTab('https://www.waze.com/forum/ucp.php?i=pm&mode=compose', forumPMInputs);
							} else {
								window.open(approveRegionURL);
							}
						} else {
							window.open(approveRegionURL);
						}
					}
				},  // END ApprovalSubmit definition
				placesWiki: {
					active: true,
					bannText: "",
					severity: 0,  
					id: "placesWiki",
					value: "Places wiki",
					title: "Open the places wiki page",
					cLog: "WMEPH: Opened places wiki",
					action: function() {
						window.open(placesWikiURL);
					}
				},  // END placesWiki definition
				PlaceErrorForumPost: {
					active: true,
					bannText: "",
					severity: 0,  
					id: "PlaceErrorForumPost",
					value: "Report script error",
					title: "Report an error on the forum",
					cLog: "WMEPH: Post initiated",
					action: function() {
						var forumMsgInputs = {
							subject: 'Re: WMEPH Bug report',
							message: 'Permalink: ' + placePL + '\nPlace name: ' + item.attributes.name + '\nCountry: ' + addr.country.name + '\nDescribe the error:\n ',
							addbbcode20: '100', preview: 'Preview', attach_sig: 'on', notify: 'on'
						};
						WMEPH_openPostDataInNewTab(WMEPHurl + '#preview', forumMsgInputs);
					}
				}  // END PlaceErrorForumPost definition
			};  // END bannButt definitions
			
			//Setting switch for the Places Wiki button
			if ( $("#WMEPH-HidePlacesWiki" + devVersStr).prop('checked') ) {
				bannButt.placesWiki.active = false;
			}
			
			// provide Google search link to places
			if (devUser || betaUser || usrRank > 2) {  // enable the link for all places, for R4+ and betas
				formBannButt('webSearch');
			}
			
			
			// Only can select one place at a time in WME, so the loop is superfluous (eg, ix=0 will work), but perhaps we leave it in case we add some sort of looping process like URs.
			for (var ix = 0; ix < W.selectionManager.selectedItems.length; ix++) {
				
				item = W.selectionManager.selectedItems[ix].model;
				
				// get GPS lat/long coords from place
				var itemGPS = OpenLayers.Layer.SphericalMercator.inverseMercator(item.attributes.geometry.bounds.right,item.attributes.geometry.bounds.top);
				// console.log("WMEPH: Place GPS coords: "+itemGPS);
				
				var lockOK = true;  // if nothing goes wrong, then place will be locked
				var customStoreFinder = false;  // switch indicating place-specific custom store finder url
				var customStoreFinderLocal = false;  // switch indicating place-specific custom store finder url with localization option (GPS/addr)
				var customStoreFinderURL = "";  // switch indicating place-specific custom store finder url
				var customStoreFinderLocalURL = "";  // switch indicating place-specific custom store finder url with localization option (GPS/addr)
				var categories = item.attributes.categories;
				newCategories = categories.slice(0);
				newName = item.attributes.name;
				newName = toTitleCase(newName);
				// var nameShort = newName.replace(/[^A-Za-z]/g, '');  // strip non-letters for PNH name searching
				// var nameNumShort = newName.replace(/[^A-Za-z0-9]/g, ''); // strip non-letters/non-numbers for PNH name searching
				newAliases = item.attributes.aliases.slice(0);
				var brand = item.attributes.brand;
				var newDescripion = item.attributes.description;
				newURL = item.attributes.url;
				var newURLSubmit = "";
				if (newURL !== null) {
					newURLSubmit = newURL;
				}
				newPhone = item.attributes.phone;
				newServices = item.attributes.services.slice(0);
				var addServices = [];
				
				var addr = item.getAddress();
				
				/*
				// ### Future use, leave in place
				WMETB_getAddress = function(e) {
					"use strict";
					if ("segment" == e.type) var t = e.model.streets.get(e.attributes.primaryStreetID);
					else if ("venue" == e.type) var o = e.model.streets.get(e.attributes.streetID);
					var r, n, a, i, s = e;
					return t ? (r = e.model.cities.get(t.cityID), n = e.model.states.get(r.stateID), a = e.model.countries.get(r.countryID), i = e.attributes.streetIDs.map(function(e) {
						s.model.streets.get(e)
					}), {
						street: t,
						city: r,
						state: n,
						country: a,
						altStreets: i
					}) : o ? (r = e.model.cities.get(o.cityID), n = e.model.states.get(r.stateID), a = e.model.countries.get(r.countryID), {
						street: o,
						city: r,
						state: n,
						country: a
					}) : {
						street: "Error",
						city: "Error",
						state: "Error",
						country: "Error"
					}
				}
				
				
				
				*/
				
				
				// Some user submitted places have no data in the country, state and address fields.  Need to have that to make the localization work.
				if (!addr.state || !addr.country) {
					alert("Place has no address data. Please set at least a city and rerun the script.");
					return;  //  don't run the script
				}
				// Country restrictions
				var countryCode;
				if (addr.country.name === "United States") {
					countryCode = "USA";
				} else if (addr.country.name === "Canada") {
					countryCode = "CAN";
				} else {
					alert("At present this script is not supported in this country.");
					return;
				}
				
				if (countryCode === "USA") {
					// Setup USA State and Regional vars
					switch (addr.state.name) {
						case "Arkansas": state2L = "AR"; region = "SCR"; gFormState = "&entry.1252443068=AR"; break;
						case "Louisiana": state2L = "LA"; region = "SCR"; gFormState = "&entry.1252443068=LA"; break;
						case "Mississippi": state2L = "MS"; region = "SCR"; gFormState = "&entry.1252443068=MS"; break;
						case "Oklahoma": state2L = "OK"; region = "SCR"; gFormState = "&entry.1252443068=OK"; break;
						case "Texas": state2L = "TX"; region = "TX"; gFormState = "&entry.1252443068=TX"; break;
						case "Alaska": state2L = "AK"; region = "NWR"; gFormState = "&entry.124157720=AK"; break;
						case "Idaho": state2L = "ID"; region = "NWR"; gFormState = "&entry.124157720=ID"; break;
						case "Montana": state2L = "MT"; region = "NWR"; gFormState = "&entry.124157720=MT"; break;
						case "Oregon": state2L = "OR"; region = "NWR"; gFormState = "&entry.124157720=OR"; break;
						case "Washington": state2L = "WA"; region = "NWR"; gFormState = "&entry.124157720=WA"; break;
						case "Wyoming": state2L = "WY"; region = "NWR"; gFormState = "&entry.124157720=WY"; break;
						case "Hawaii": state2L = "HI"; region = "HI"; gFormState = "&entry.124157720=OR"; break;
						case "Arizona": state2L = "AZ"; region = "SWR"; gFormState = "&entry.124157720=AZ"; break;
						case "California": state2L = "CA"; region = "SWR"; gFormState = "&entry.124157720=CA"; break;
						case "Colorado": state2L = "CO"; region = "SWR"; gFormState = "&entry.124157720=CO"; break;
						case "Nevada": state2L = "NV"; region = "SWR"; gFormState = "&entry.124157720=NV"; break;
						case "New Mexico": state2L = "NM"; region = "SWR"; gFormState = "&entry.124157720=NM"; break;
						case "Utah": state2L = "UT"; region = "SWR"; gFormState = "&entry.124157720=UT"; break;
						case "Iowa": state2L = "IA"; region = "PLN"; gFormState = "&entry.124157720=IA"; break;
						case "Kansas": state2L = "KS"; region = "PLN"; gFormState = "&entry.124157720=KS"; break;
						case "Minnesota": state2L = "MN"; region = "PLN"; gFormState = "&entry.124157720=MN"; break;
						case "Missouri": state2L = "MO"; region = "PLN"; gFormState = "&entry.124157720=MO"; break;
						case "Nebraska": state2L = "NE"; region = "PLN"; gFormState = "&entry.124157720=NE"; break;
						case "North Dakota": state2L = "ND"; region = "PLN"; gFormState = "&entry.124157720=ND"; break;
						case "South Dakota": state2L = "SD"; region = "PLN"; gFormState = "&entry.124157720=SD"; break;
						case "Illinois": state2L = "IL"; region = "GLR"; gFormState = "&entry.124157720=IL"; break;
						case "Indiana": state2L = "IN"; region = "GLR"; gFormState = "&entry.124157720=IN"; break;
						case "Michigan": state2L = "MI"; region = "GLR"; gFormState = "&entry.124157720=MI"; break;
						case "Ohio": state2L = "OH"; region = "GLR"; gFormState = "&entry.124157720=OH"; break;
						case "Wisconsin": state2L = "WI"; region = "GLR"; gFormState = "&entry.124157720=WI"; break;
						case "Kentucky": state2L = "KY"; region = "SAT"; break;
						case "North Carolina": state2L = "NC"; region = "SAT"; break;
						case "South Carolina": state2L = "SC"; region = "SAT"; break;
						case "Tennessee": state2L = "TN"; region = "SAT"; break;
						case "Alabama": state2L = "AL"; region = "SER"; gFormState = "&entry.2010899807=AL+-+Alabama"; break;
						case "Florida": state2L = "FL"; region = "SER"; gFormState = "&entry.2010899807=FL+-+Florida"; break;
						case "Georgia": state2L = "GA"; region = "SER"; gFormState = "&entry.2010899807=GA+-+Georgia"; break;
						case "Connecticut": state2L = "CT"; region = "NEW"; gFormState = "&entry.124157720=CT"; break;
						case "Maine": state2L = "ME"; region = "NEW"; gFormState = "&entry.124157720=ME"; break;
						case "Massachusetts": state2L = "MA"; region = "NEW"; gFormState = "&entry.124157720=MA"; break;
						case "New Hampshire": state2L = "NH"; region = "NEW"; gFormState = "&entry.124157720=NH"; break;
						case "Rhode Island": state2L = "RI"; region = "NEW"; gFormState = "&entry.124157720=RI"; break;
						case "Vermont": state2L = "VT"; region = "NEW"; gFormState = "&entry.124157720=VT"; break;
						case "Delaware": state2L = "DE"; region = "NOR"; gFormState = "&entry.124157720=DE"; break;
						case "New Jersey": state2L = "NJ"; region = "NOR"; gFormState = "&entry.124157720=NJ"; break;
						case "New York": state2L = "NY"; region = "NOR"; gFormState = "&entry.124157720=NY"; break;
						case "Pennsylvania": state2L = "PA"; region = "NOR"; gFormState = "&entry.124157720=PA"; break;
						case "District of Columbia": state2L = "DC"; region = "MAR"; gFormState = "&entry.124157720=DC"; break;
						case "Maryland": state2L = "MD"; region = "MAR"; gFormState = "&entry.124157720=MD"; break;
						case "Virginia": state2L = "VA"; region = "MAR"; gFormState = "&entry.124157720=VA"; break;
						case "West Virginia": state2L = "WV"; region = "MAR"; gFormState = "&entry.124157720=WV"; break;
						default: state2L = "Unknown"; region = "Unknown";
					}
					console.log("WMEPH: Place is in region " + region);
				}  // END USA regional/state assigning
				
				if (countryCode === "CAN") {
					// Setup Canadian provinces
					switch (addr.state.name) {
						case "Ontario": state2L = "ON"; region = "CAN"; break;
						case "British Columbia": state2L = "BC"; region = "CAN"; break;
						case "Alberta": state2L = "AB"; region = "CAN"; break;
						case "Saskatchewan": state2L = "SK"; region = "CAN"; break;
						case "Manitoba": state2L = "MB"; region = "CAN"; break;
						case "Ontario": state2L = "ON"; region = "CAN"; break;
						case "Quebec": state2L = "QC"; region = "CAN"; break;
						case "Newfoundland And Labrador": state2L = "NL"; region = "CAN"; break;
						case "New Brunswick": state2L = "NB"; region = "CAN"; break;
						case "Prince Edward Island": state2L = "PE"; region = "CAN"; break;
						case "Nova Scotia": state2L = "NS"; region = "CAN"; break;
						case "Nunavut": state2L = "NU"; region = "CAN"; break;
						case "Northwest Territories": state2L = "NT"; region = "CAN"; break;
						case "Yukon": state2L = "YT"; region = "CAN"; break;
						default: state2L = "Unknown"; region = "Unknown";
					}
					console.log("WMEPH: Place is in province: " + state2L);
				}  // END Canada assignments
				
				// If region or state is unknown, report the error
				if (state2L === "Unknown" || region === "Unknown") {
					if (confirm('WMEPH: Localization Error\nClick OK to report this error') ) {  // if the location is not found, then pop an alert that will make a forum post to the thread
						var forumMsgInputs = {
							subject: 'Re: WMEPH Bug report',
							message: 'Error report: State name "' + addr.state.name + '" is not found.',
							addbbcode20: '100', preview: 'Preview', attach_sig: 'on', notify: 'on'
						};
						WMEPH_openPostDataInNewTab(WMEPHurl + '#preview', forumMsgInputs);
					}
					return;
				}
				
				// Clear attributes from residential places
				if (item.attributes.residential) {   
					newName = item.attributes.houseNumber + " " + addr.street.name;
					if (item.attributes.name !== newName) {  // Set the residential place name to the address (to clear any personal info)
						console.log("WMEPH: Residential Name reset");
						W.model.actionManager.add(new UpdateObject(item, {name: newName}));
					}
					newCategories = ["RESIDENCE_HOME"];
					newDescripion = null;
					if (item.attributes.description !== null && item.attributes.description !== "") {  // remove any description
						console.log("WMEPH: Residential description cleared");
						W.model.actionManager.add(new UpdateObject(item, {description: newDescripion}));
					}
					newPhone = null;
					if (item.attributes.phone !== null && item.attributes.phone !== "") {  // remove any phone info
						console.log("WMEPH: Residential Phone cleared");
						W.model.actionManager.add(new UpdateObject(item, {phone: newPhone}));
					}
					newURL = null;
					if (item.attributes.url !== null && item.attributes.url !== "") {  // remove any url
						console.log("WMEPH: Residential URL cleared");
						W.model.actionManager.add(new UpdateObject(item, {url: newURL}));
					}
					if (item.attributes.services.length > 0) {
						console.log("WMEPH: Residential URL cleared");
						W.model.actionManager.add(new UpdateObject(item, {services: [] }));
					}
				} else {  // for non-residential places
					
					// Place Harmonization 
					var PNHMatchData = harmoList(newName,state2L,region,countryCode,newCategories);  // check against the PNH list
					
					if (PNHMatchData[0] !== "NoMatch" && PNHMatchData[0] !== "ApprovalNeeded" ) { // *** Replace place data with PNH data
						var PNH_DATA_headers;
						if (countryCode === "USA") {
							PNH_DATA_headers = USA_PNH_DATA[0].split("|");
						} else if (countryCode === "CAN") {
							PNH_DATA_headers = CAN_PNH_DATA[0].split("|");
						}
						var ph_name_ix = PNH_DATA_headers.indexOf("ph_name");
						var ph_aliases_ix = PNH_DATA_headers.indexOf("ph_aliases");
						var ph_category1_ix = PNH_DATA_headers.indexOf("ph_category1");
						var ph_category2_ix = PNH_DATA_headers.indexOf("ph_category2");
						var ph_description_ix = PNH_DATA_headers.indexOf("ph_description");
						var ph_url_ix = PNH_DATA_headers.indexOf("ph_url");
						var ph_order_ix = PNH_DATA_headers.indexOf("ph_order");
						// var ph_notes_ix = PNH_DATA_headers.indexOf("ph_notes");
						var ph_speccase_ix = PNH_DATA_headers.indexOf("ph_speccase");
						var ph_sfurl_ix = PNH_DATA_headers.indexOf("ph_sfurl");
						var ph_sfurllocal_ix = PNH_DATA_headers.indexOf("ph_sfurllocal");
						var ph_forcecat_ix = PNH_DATA_headers.indexOf("ph_forcecat");
						var ph_displaynote_ix = PNH_DATA_headers.indexOf("ph_displaynote");
						
						// Check special cases
						var specCases = PNHMatchData[ph_speccase_ix];
						if (specCases !== "0" && specCases !== "") {
							specCases = specCases.replace(/,[^A-Za-z0-9]+/g, ",");  // tighten up commas if more than one specCase flag.
							specCases = specCases.split(",");  // split by comma
						}
						var scFlag;
						for (var scix = 0; scix < specCases.length; scix++) {  // find any button flags in the special case (format: butt_xyzXyz)
							if ( specCases[scix].match(/^buttOn_/g) !== null ) {
								scFlag = specCases[scix].match(/^buttOn_(.+)/i)[1];
								bannButt[scFlag].active = true;
							} else if ( specCases[scix].match(/^buttOff_/g) !== null ) {
								scFlag = specCases[scix].match(/^buttOff_(.+)/i)[1];
								bannButt[scFlag].active = false;
							}
						}
						
						// Display any notes for the specific place
						if (PNHMatchData[ph_displaynote_ix] !== '0' && PNHMatchData[ph_displaynote_ix] !== '' ) {
							if ( containsAny(specCases,['pharmhours']) ) {
								if ( item.attributes.description.toUpperCase().indexOf('PHARMACY') === -1 || item.attributes.description.toUpperCase().indexOf('HOURS') === -1 ) {
									sidebarMessage.push(PNHMatchData[ph_displaynote_ix]);
									severity = Math.max(severity,1);
								}
							} else {
								sidebarMessage.push(PNHMatchData[ph_displaynote_ix]);
							}
						}
						
						//populate the variables from PNH data
						newName = PNHMatchData[ph_name_ix];
						newAliasesTemp = PNHMatchData[ph_aliases_ix];
						newDescripion = PNHMatchData[ph_description_ix];
						newURL = PNHMatchData[ph_url_ix];
						PNHOrderNum = PNHMatchData[ph_order_ix];
						
						var priPlaceCat = catTranslate(PNHMatchData[ph_category1_ix]);  // translate primary category to WME code
						var altCategories = PNHMatchData[ph_category2_ix];
						if (altCategories !== "0" && altCategories !== "") {
							altCategories = altCategories.replace(/,[^A-Za-z0-9]*/g, ",");  // tighten up commas if more than one secondary category.
							altCategories = altCategories.split(",");  // split by comma
							for (var catix = 0; catix<altCategories.length; catix++) {  // translate altCats into WME cat codes
								 var newAltTemp = catTranslate(altCategories[catix]);
								 if (newAltTemp === "ERROR") {  // if no translation, quit the loop
									 console.log('WMEPH: category ' + altCategories[catix] + 'cannot be translated.');
									 return;
								 } else {
									altCategories[catix] = newAltTemp;  // replace with translated element
								 }
							}
						}
						
						if ( ["GAS_STATION"].indexOf(priPlaceCat) > -1 ) {  // for primary categories in the vector, don't replace existing sub-categories
							if ( altCategories !== "0" && altCategories !== "" ) {  // if alts exist
								insertAtIX(newCategories, altCategories, 1);  //  then insert the alts into the existing category array
							}
						} else {  // completely replace categories with PNH categories
							newCategories = [priPlaceCat];
							if (altCategories !== "0" && altCategories !== "") {
								newCategories.push.apply(newCategories,altCategories);
							}
						}
						
						// *** need to add a section above to allow other permissible categories to remain? (optional)
						
						
						if (newAliasesTemp !== "0" && newAliasesTemp !== "") {  // make aliases array
							newAliasesTemp = newAliasesTemp.replace(/,[^A-za-z0-9]*/g, ",");  // tighten up commas if more than one alias.
							newAliasesTemp = newAliasesTemp.split(",");  // split by comma
						}
						
						if (specCases.indexOf('buttOn_addAlias') > -1) {
							bannButt.addAlias.bannText = "Is there a " + newAliasesTemp[0] + " at this location?";
							bannButt.addAlias.title = 'Add ' + newAliasesTemp[0];
						}
						if (specCases.indexOf('buttOn_addCat2') > -1) {
							bannButt.addAlias.bannText = "Is there a " + catTransWaze2Lang[altCategories[0]] + " at this location?";
							bannButt.addAlias.title = 'Add ' + catTransWaze2Lang[altCategories[0]];
						}
						
						if ( specCases.indexOf('bank') > -1 ) {  // PNH banks
							
							// #### Needs work
							// Generic Bank treatment
							/*
							var newNameExt = ' '+newName+' ';
							newNameExt = newNameExt.replace(/[^A-Za-z0-9]/g, ' ');
							var ixBank = item.attributes.categories.indexOf("BANK_FINANCIAL");
							var ixATM = item.attributes.categories.indexOf("ATM");
							var ixOffices = item.attributes.categories.indexOf("OFFICES");
							// if the name contains ATM in it
							if ( newNameExt.toUpperCase().indexOf('ATM ') > -1 ) {
								if ( ixOffices === 0 ) {
									formBannMess('bankType1');
									formBannButt('standaloneATM');
									formBannButt('bankBranch');
									formBannButt('bankCorporate');
								} else if ( ixBank === -1 && ixATM === -1 ) {
									formBannButt('standaloneATM');
									formBannButt('bankBranch');
								} else if ( ixBank === 0 ) {
									formBannButt('standaloneATM');
									formBannButt('bankBranch');
								} else if ( ixATM === 0 && ixBank > 0) {
									formBannButt('bankBranch');
								}
								// Net result: If the place has ATM cat only and ATM in the name, then it will be green
							} else {  // if no ATM in name:
								if ( ixOffices === 0 ) {
									formBannButt('bankBranch');
								} else if ( ixBank === -1 && ixATM === -1 ) {
									formBannButt('standaloneATM');
									formBannButt('bankBranch');
								} else if ( ixBank > -1  && ixATM === -1 ) {
									formBannButt('addATM');
								} else if ( ixATM === 0 ) {
									formBannButt('standaloneATM');
									formBannButt('bankBranch');
								} else if ( ixBank > 0 && ixATM > 0 ) {
									formBannButt('standaloneATM');
									formBannButt('bankBranch');
								}
								// Net result: If the place has Bank category first, then it will be green
							}
							*/
						
						} else if ( specCases.indexOf('hotel') > 1 ) {  // for certain flags, proceed with update
						
						
						} else {  // for certain flags, proceed with update
							if (newName !== item.attributes.name) {
								console.log("WMEPH: Name updated");
								W.model.actionManager.add(new UpdateObject(item, { name: newName }));
							}
							
							if (!containsAll(newAliases,newAliasesTemp) && newAliasesTemp !== "0" && newAliasesTemp !== "" && specCases.indexOf('optionName2') === -1 ) {
								newAliases = insertAtIX(newAliases,newAliasesTemp,0);
								console.log("WMEPH: Alt Names updated");
								W.model.actionManager.add(new UpdateObject(item, { aliases: newAliases }));
							}
							
							if (!matchSets(item.attributes.categories,newCategories) && specCases.indexOf('optionCat2') === -1 ) {
								console.log("WMEPH: Categories updated" + " with " + newCategories);
								W.model.actionManager.add(new UpdateObject(item, { categories: newCategories }));
							}
							if (newDescripion !== null && newDescripion !== "0") {
								if ( item.attributes.description.toUpperCase().indexOf(newDescripion.toUpperCase()) === -1 ) {
									if ( item.attributes.description !== "" || item.attributes.description !== null ) {
										formBannMess('checkDescription');
									}
									console.log("WMEPH: Description updated");
									newDescripion = newDescripion + '\n' + item.attributes.description;
									W.model.actionManager.add(new UpdateObject(item, { description: newDescripion }));
								}
								
							}
							
							
						}
						
						if ( PNHMatchData[ph_speccase_ix] === 'subFuel' ) {
							formBannMess('subFuel');
						}
						
						// *** Add storefinder URL codes
							
						
					} else {  // if no match found
						if (PNHMatchData[0] === "ApprovalNeeded") {
							PNHNameTemp = PNHMatchData[1];
							PNHOrderNum = PNHMatchData[2];
						}
						
						if (newName !== item.attributes.name) {
							console.log("WMEPH: Name updated");
							W.model.actionManager.add(new UpdateObject(item, { name: newName }));
						}
						if (newName !== toTitleCaseStrong(newName)) {
							formBannButt('STC');
						}
						
						
						
						// #### Needs work
						// Generic Bank treatment
						var ixBank = item.attributes.categories.indexOf("BANK_FINANCIAL");
						var ixATM = item.attributes.categories.indexOf("ATM");
						var ixOffices = item.attributes.categories.indexOf("OFFICES");
						var newNameExt = ' '+newName+' ';
						newNameExt = newNameExt.replace(/[^A-Za-z0-9]/g, ' ');
						// if the name contains ATM in it
						if ( newNameExt.toUpperCase().indexOf('ATM ') > -1 ) {
							if ( ixOffices === 0 ) {
								formBannMess('bankType1');
								formBannButt('standaloneATM');
								formBannButt('bankBranch');
								formBannButt('bankCorporate');
							} else if ( ixBank === -1 && ixATM === -1 ) {
								formBannButt('standaloneATM');
								formBannButt('bankBranch');
							} else if ( ixBank === 0 ) {
								formBannButt('standaloneATM');
								formBannButt('bankBranch');
							} else if ( ixATM === 0 && ixBank > 0) {
								formBannButt('bankBranch');
							}
							// Net result: If the place has ATM cat only and ATM in the name, then it will be green
						} else if (ixBank > -1  || ixATM > -1) {  // if no ATM in name:
							if ( ixOffices === 0 ) {
								formBannButt('bankBranch');
							} else if ( ixBank > -1  && ixATM === -1 ) {
								formBannButt('addATM');
							} else if ( ixATM === 0 ) {
								formBannButt('standaloneATM');
								formBannButt('bankBranch');
							} else if ( ixBank > 0 && ixATM > 0 ) {
								formBannButt('standaloneATM');
								formBannButt('bankBranch');
							}
							// Net result: If the place has Bank category first, then it will be green
						}
						
						
						
					}  // END match/no-match updates
					
					// Gas station treatment applies to all
					if (newCategories[0] === 'GAS_STATION') {
						// Brand checking
						if ( item.attributes.brand === null || item.attributes.brand === "" ) {
							formBannMess('gasNoBrand');
						} else if (item.attributes.brand === 'Unbranded' ) {
							formBannMess('gasUnbranded');
							lockOK = false;
						} else if ( newName.toUpperCase().indexOf(item.attributes.brand.toUpperCase()) === -1 ) {
							formBannButt('gasMismatch');
							lockOK = false;
						}
						// Add convenience store category to station
						console.log(newCategories);
						if (newCategories.indexOf("CONVENIENCE_STORE") === -1 && !bannMess.subFuel.active) {
							if ( $("#WMEPH-ConvenienceStoreToGasStations" + devVersStr).prop('checked') ) {  // Automatic if user has the setting checked
								newCategories = insertAtIX(newCategories, "CONVENIENCE_STORE", 1);  // insert the C.S. category
								W.model.actionManager.add(new UpdateObject(item, {  //  update
									categories: newCategories
								}));
							} else {  // If not checked, then it will be a banner button
								formBannButt('addConvStore');
							}
						}
					}
					
					
					// Make submission links
					var regionFormURL = '';
					var newPlaceAddon = '';
					var approvalAddon = '';
					var approvalMessage = 'Please approve this place in the PNH list.  PNH order number: ' + PNHOrderNum;
					switch (region) {
						case "NWR": regionFormURL = 'https://docs.google.com/forms/d/1hv5hXBlGr1pTMmo4n3frUx1DovUODbZodfDBwwTc7HE/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "SWR": regionFormURL = 'https://docs.google.com/forms/d/1Qf2N4fSkNzhVuXJwPBJMQBmW0suNuy8W9itCo1qgJL4/viewform';
							newPlaceAddon = '?entry.1497446659='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.1497446659='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "HI": regionFormURL = 'https://docs.google.com/forms/d/1Qf2N4fSkNzhVuXJwPBJMQBmW0suNuy8W9itCo1qgJL4/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "PLN": regionFormURL = 'https://docs.google.com/forms/d/1ycXtAppoR5eEydFBwnghhu1hkHq26uabjUu8yAlIQuI/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "SCR": regionFormURL = 'https://docs.google.com/forms/d/1KZzLdlX0HLxED5Bv0wFB-rWccxUp2Mclih5QJIQFKSQ/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "TX": regionFormURL = 'https://docs.google.com/forms/d/1KZzLdlX0HLxED5Bv0wFB-rWccxUp2Mclih5QJIQFKSQ/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "GLR": regionFormURL = 'https://docs.google.com/forms/d/19btj-Qt2-_TCRlcS49fl6AeUT95Wnmu7Um53qzjj9BA/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "SAT": regionFormURL = 'https://docs.google.com/forms/d/1bxgK_20Jix2ahbmUvY1qcY0-RmzUBT6KbE5kjDEObF8/viewform';
							newPlaceAddon = '';
							approvalAddon = '';
							break;
						case "SER": regionFormURL = 'https://docs.google.com/forms/d/1jYBcxT3jycrkttK5BxhvPXR240KUHnoFMtkZAXzPg34/viewform';
							newPlaceAddon = '?entry.822075961='+newName+'&entry.1422079728='+newURLSubmit+'&entry.1891389966='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.822075961='+PNHNameTemp+'&entry.607048307='+approvalMessage+'&entry.1891389966='+thisUser.userName+gFormState; 
							break;
						case "TER": regionFormURL = 'https://docs.google.com/forms/d/1v7JhffTfr62aPSOp8qZHA_5ARkBPldWWJwDeDzEioR0/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "NEW": regionFormURL = 'https://docs.google.com/forms/d/1UgFAMdSQuJAySHR0D86frvphp81l7qhEdJXZpyBZU6c/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "NOR": regionFormURL = 'https://docs.google.com/forms/d/1iYq2rd9HRd-RBsKqmbHDIEBGuyWBSyrIHC6QLESfm4c/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "MAR": regionFormURL = 'https://docs.google.com/forms/d/1PhL1iaugbRMc3W-yGdqESoooeOz-TJIbjdLBRScJYOk/viewform';
							newPlaceAddon = '?entry.925969794='+newName+'&entry.1970139752='+newURLSubmit+'&entry.1749047694='+thisUser.userName+gFormState; 
							approvalAddon = '?entry.925969794='+PNHNameTemp+'&entry.50214576='+approvalMessage+'&entry.1749047694='+thisUser.userName+gFormState; 
							break;
						case "CAN": regionFormURL = 'https://docs.google.com/forms/d/13JwXsrWPNmCdfGR5OVr5jnGZw-uNGohwgjim-JYbSws/viewform';
							newPlaceAddon = '?entry_839085807='+newName+'&entry_1067461077='+newURLSubmit; 
							approvalAddon = '?entry_839085807='+PNHNameTemp+'&entry_1125435193='+approvalMessage; 
							break;
					default: regionFormURL = "";
					}
					newPlaceURL = regionFormURL + newPlaceAddon;
					approveRegionURL = regionFormURL + approvalAddon;	
					
					
					// *** filter weak/parent categories from stronger categories (remove food and drink if restaurant, etc.)
					
						
					// Category/Name-based Services, added to any existing services:
					if ( containsAny(newCategories,["BANK_FINANCIAL","ATM"]) ) {
						addServices = ["AIR_CONDITIONING", "PARKING_FOR_CUSTOMERS", "WHEELCHAIR_ACCESSIBLE"];
					}
					if ( containsAny(newCategories,["GAS_STATION"]) ) {
						addServices = ["RESTROOMS", "AIR_CONDITIONING", "PARKING_FOR_CUSTOMERS", "WHEELCHAIR_ACCESSIBLE"];
					}
					if ( containsAny(newCategories,["SHOPPING_CENTER","PARKING_LOT","GARAGE_AUTOMOTIVE_SHOP"]) ) {
						addServices = ["PARKING_FOR_CUSTOMERS", "WHEELCHAIR_ACCESSIBLE"];
					}
					if ( containsAny(newCategories,["HOSPITAL_MEDICAL_CARE","DEPARTMENT_STORE","RESTAURANT","CAFE","CAR_DEALERSHIP","FURNITURE_HOME_STORE","SPORTING_GOODS",
					"CAR_DEALERSHIP","BAR","GYM_FITNESS","CONVENIENCE_STORE","SUPERMARKET_GROCERY","PET_STORE_VETERINARIAN_SERVICES","TOY_STORE","PERSONAL_CARE"]) ) {
						addServices = ["RESTROOMS", "CREDIT_CARDS", "AIR_CONDITIONING", "PARKING_FOR_CUSTOMERS", "WHEELCHAIR_ACCESSIBLE"];
					}
					if ( containsAny(newCategories,["BOOKSTORE","FASHION_AND_CLOTHING","PERSONAL_CARE","BAKERY","HARDWARE_STORE","DESSERT","FAST_FOOD","PHARMACY","ELECTRONICS",
						"FLOWERS","MARKET","JEWELRY","MUSIC_STORE"]) ) {
						addServices = ["CREDIT_CARDS", "AIR_CONDITIONING", "PARKING_FOR_CUSTOMERS", "WHEELCHAIR_ACCESSIBLE"];
					}
					// Push the services onto the existing services array
					newServices = insertAtIX(newServices,addServices,12);
				
					// These categories get their services replaced
					if ( containsAny(newCategories,["COLLEGE_UNIVERSITY","SCHOOL","RELIGIOUS_CENTER","KINDERGARDEN"]) && (newCategories.indexOf("PARKING_LOT") === -1) ) {
						newServices = ["RESTROOMS", "AIR_CONDITIONING", "PARKING_FOR_CUSTOMERS", "WHEELCHAIR_ACCESSIBLE"];
					}
					
					
					// Place Area check
					if (item.isPoint() && containsAny(newCategories,["GAS_STATION","PARKING_LOT","AIRPORT","BRIDGE","CEMETERY","EMBASSY_CONSULATE","FIRE_DEPARTMENT",
						"POLICE_STATION","PRISON_CORRECTIONAL_FACILITY","SCHOOL","SHOPPING_CENTER","RACING_TRACK","THEME_PARK","GOLF_COURSE","PARK"]) ) {
						formBannMess('areaNotPoint');
						lockOK = false;
					}
					if (item.isPoint() && newCategories.indexOf("STADIUM_ARENA") > -1) {
						formBannMess('areaStadium');
						lockOK = false;
					}
					if (region === "SER" && item.isPoint() && newCategories.indexOf("POST_OFFICE") > -1) {
						formBannMess('areaPostOfficeSER');
						lockOK = false;
					}
					if (item.isPoint() && newCategories.indexOf("HOSPITAL_MEDICAL_CARE") > -1) {
						formBannMess('areaHospital');
						lockOK = false;
					}
					if (region === "SER" && containsAny(newCategories,["JUNCTION_INTERCHANGE","SEA_LAKE_POOL","RIVER_STREAM","FOREST_GROVE","CANAL","SWAMP_MARSH","ISLAND","BEACH","TRANSPORTATION"]) ) {
						formBannMess('unmappedSER');
						lockOK = false;
					}
					if (region === "SER" && item.is2D() && (newCategories.indexOf("CAR_DEALERSHIP") > -1)) {
						formBannMess('pointCarDealerSER');
						lockOK = false;
					}
				
					// Address check
					if (!item.attributes.name && !item.attributes.residential) {
						formBannMess('nameMissing');
						lockOK = false;
					}
				}  // END if (not residential)
				
				// House number check
				if (!item.attributes.houseNumber) {
					formBannMess('hnMissing');
					lockOK = false;
				} else {
					var hnOK = false;
					var hnTemp = item.attributes.houseNumber.replace(/[^\d]/g, '');  // Digits only
					var hnTempDash = item.attributes.houseNumber.replace(/[^\d-]/g, '');  // Digits and dashes only
					if (hnTemp === item.attributes.houseNumber && hnTemp < 1000000) {  //  general check that HN is 6 digits or less, & that it is only [0-9]
						hnOK = true;
					}
					if (state2L === "HI") {  // Allowance for XX-XXXX HN format for Hawaii
						if (hnTempDash.match(/^\d{1,2}-\d{1,4}$/g) !== null) {
							if (hnTempDash === hnTempDash.match(/^\d{1,2}-\d{1,4}$/g)[0]) {
								hnOK = true;
							}
						}
					}
					if (!hnOK) {
						formBannMess('hnNonStandard');
						lockOK = false;
					}
				}
	
				if (!addr.street || addr.street.isEmpty) {
					formBannMess('streetMissing');
					lockOK = false;
				}
				if (!addr.city || addr.city.isEmpty) {
					formBannMess('cityMissing');
					lockOK = false;
				}
				
				
				if (!item.attributes.residential) {
					// URL formatting
					newURL = normalizeURL(newName,newURL,addr);
					if (newURL !== item.attributes.url && newURL !== "" && newURL !== "0") {
						console.log("WMEPH: URL updated");
						W.model.actionManager.add(new UpdateObject(item, { url: newURL }));
					}
					
					// Phone formatting		
					var outputFormat = "({0}) {1}-{2}";
					if (region === "SER" && !(/^\(\d{3}\) \d{3}-\d{4}$/.test(item.attributes.phone))) {
						outputFormat = "{0}-{1}-{2}";
					} else if (region === "GLR") {
						outputFormat = "{0}-{1}-{2}";
					} else if (countryCode === "CAN") {
						outputFormat = "+1-{0}-{1}-{2}";
					}
					newPhone = normalizePhone(item.attributes.phone, outputFormat);
					if (newPhone !== item.attributes.phone) {
						console.log("WMEPH: Phone updated");
						W.model.actionManager.add(new UpdateObject(item, {phone: newPhone}));
					}
				}  // END if (not residential)
				
				// Post Office cat check
				if (newCategories.indexOf("POST_OFFICE") > -1) {
					
					
					formBannButt('isitUSPS');
				}
	
				//	Add services to existing, only if they are different than what's there
				if (!item.attributes.residential && !matchSets(item.attributes.services,newServices) && $("#WMEPH-EnableServices" + devVersStr).prop('checked')) {
					console.log("WMEPH: Services updated");
					W.model.actionManager.add(new UpdateObject(item, { services: newServices }));
				}
				// Place locking
				if (lockOK) {
					var levelToLock = lockLevel3;
	
					if (region === "SER") {
						if (newCategories.indexOf("COLLEGE_UNIVERSITY") > -1 && newCategories.indexOf("PARKING_LOT") > -1) {
							levelToLock = lockLevel4;
						} else if ( item.isPoint() && newCategories.indexOf("COLLEGE_UNIVERSITY") > -1 && newCategories.indexOf("HOSPITAL_MEDICAL_CARE") === -1 ) {
							levelToLock = lockLevel4;
						} else if ( containsAny(newCategories,["HOSPITAL_MEDICAL_CARE","COLLEGE_UNIVERSITY","STADIUM_ARENA","SCHOOL","AIRPORT"]) ) {
							levelToLock = lockLevel5;
						}
					}
	
					if (region === "SAT") {
						var SATlevel5Categories = ["HOSPITAL_MEDICAL_CARE", "AIRPORT"];
						if ( containsAny(newCategories,SATlevel5Categories) ) {
							levelToLock = lockLevel5;
						}
					}
	
					if (region === "MAR") {
						var MARlevel4Categories = [ "HOSPITAL_MEDICAL_CARE", "AIRPORT", "FIRE_DEPARTMENT", "POLICE_STATION" ];
						if ( containsAny(newCategories,MARlevel4Categories) ) {
							levelToLock = lockLevel4;
						}
					}
	
					if (item.attributes.lockRank < levelToLock) {
						console.log("WMEPH: Venue locked!");
						W.model.actionManager.add(new UpdateObject(item, {
							lockRank: levelToLock
						}));
					}
					bannMess.placeLocked.active = true;
				}
	
				// User alerts for potentially confusing places
				var hotelCat = 0;
				var walmartFlag = 0;

				

				if (Math.max(severity, severityButt) < 3) {
					var nameShortSpace = newName.replace(/[^A-Za-z ]/g, '');
					if ( ["HOME","MY HOME","HOUSE","MY HOUSE","CASA","MI CASA"].indexOf( nameShortSpace.toUpperCase() ) > -1 ) {
						formBannMess('resiTypeName');
					}
					if (newName === "UPS") {
						sidebarMessageOld.push("If this is a 'UPS Store' location, please change the name to The UPS Store and run the script again.");
						severity = Math.max(1, severity);
					}
					if (newName === "FedEx") {
						sidebarMessageOld.push("If this is a FedEx Office location, please change the name to FedEx Office and run the script again.");
						severity = Math.max(1, severity);
					}
					if (newName === "IBM Southeast EFCU") {
						sidebarMessageOld.push("Please add the suffix ' - LOCATION' to the primary name as found on IBMSEFCU's website");
						severity = Math.max(2, severity);
					}
					if (walmartFlag === 1) {
						sidebarMessageOld.push("If this Walmart sells groceries, please add the Supermarket category to the place.");
						severity = Math.max(1, severity);
					}
					if (newCategories.indexOf("POST_OFFICE") > -1) {
						customStoreFinder = "https://tools.usps.com/go/POLocatorAction.action";
						customStoreFinder = true;
						formBannMess('catPostOffice');
					}
					if (item.is2D() && newCategories.indexOf("STADIUM_ARENA") > -1) {
						formBannMess('generalStadium');
					}
					if (hotelCat === 1 || newCategories.indexOf("HOTEL") > -1) {
						formBannMess('catHotel');
					}
	
				}
	
				// #### Needs work
				if (newURL !== null && newURL !== "") {
					if (customStoreFinder) {
						sidebarMessageOld.push("(<a href=\"" + customStoreFinder + "\" target=\"_blank\" style=\"color: #FFF\" title=\"Open " + newName + " store finder in a new tab\">Website</a>)");
						// *** Storefinder work
					} else {
						bannButt.PlaceWebsite.active = true;
					}
				}
				
				// push together messages from active bannMess objects
				for (var bannKey in bannMess) {
					if (bannMess[bannKey].active) {
						sidebarMessage.push(bannMess[bannKey].message);
					}
				}
				
				
				// Make Messaging banners
				assembleBanner(item);
				
			}  // (End Place 'loop')
			
		}  // END harmonizePlaceGo function
		
		// Set up banner messages
		function assembleBanner(item) {
			var sidebarMessageEXT = sidebarMessage.slice(0);  // pull out message array to add on to if necessary
			severityButt = 0;
			for (var NHix = 0; NHix < Object.keys(bannButt).length; NHix++ ) {
				var tempKey = Object.keys(bannButt)[NHix];
				var strButt1;
				var strButt2 = '';
				if (bannButt[tempKey].active) {
					strButt1 = bannButt[tempKey].bannText + '<input class="PHbutton" id="' + bannButt[tempKey].id + '" title="' + bannButt[tempKey].title + '" type="button" value="' + bannButt[tempKey].value + '">';
					sidebarMessageEXT.push(strButt1 + strButt2);
					severityButt = Math.max(bannButt[tempKey].severity, severityButt);
				}
			}
			if (isDevVersion) {
				sidebarMessageEXT.push('WMEPH Beta');
			} 
			displayBanners(sidebarMessageEXT.join("<li>"), Math.max(severity, severityButt) );
			setupButtons(item);
			// if (EXTOption) {
			// 	sidebarMessageEXT = sidebarMessageEXT.join("<li>");
			// 	displayBanners(sidebarMessageEXT,severity);
			// 	setupButtons();
			// } else {
			// 	displayBanners(sidebarMessageOld,severity);	
			// 	setupButtons();
			// }
		}  // END assemble Banner function
		
		// Button event handlers
		function setupButtons(item) {
			var ixButt = 0;
			var btn = [];
			for (var NHix = 0; NHix < Object.keys(bannButt).length; NHix++ ) {
				var tempKey = Object.keys(bannButt)[NHix];
				if (bannButt[tempKey].active) {
					btn[ixButt] = document.getElementById(bannButt[tempKey].id); 
					btn[ixButt].onclick = (function(buttonId, item){
						return function() {
							//bannButt[buttonId].action(item);
							bannButt[buttonId].action();
							assembleBanner(item);
						};
					})(tempKey, item)
					ixButt++;
				}
			}
			
		}  // END setupButtons function
		
		
		// Display banners with <LI> string and severity
		function displayBanners(sbm,sev) {
			$('#WMEPH_logger_warn').empty();
			if (sev === 0) {
					$('<div id="WMEPH_logger_warn">').css("width", "290").css("background-color", "rgb(36, 172, 36)").css("color", "white").css("font-size", "15px").css("font-weight", "bold").css("margin-left", "auto").css("margin-right", "auto").prependTo(".contents");
				}
				if (sev === 1) {
					$('<div id="WMEPH_logger_warn">').css("width", "290").css("background-color", "rgb(40, 40, 230)").css("color", "white").css("font-size", "15px").css("font-weight", "bold").css("margin-left", "auto").css("margin-right", "auto").prependTo(".contents");
				}
				if (sev === 2) {
					$('<div id="WMEPH_logger_warn">').css("width", "290").css("background-color", "rgb(217, 173, 42)").css("color", "white").css("font-size", "15px").css("font-weight", "bold").css("margin-left", "auto").css("margin-right", "auto").prependTo(".contents");
				}
				if (sev === 3) {
					$('<div id="WMEPH_logger_warn">').css("width", "290").css("background-color", "rgb(211, 48, 48)").css("color", "white").css("font-size", "15px").css("font-weight", "bold").css("margin-left", "auto").css("margin-right", "auto").prependTo(".contents");
				}
				WMEPH_DispWarn(sbm);
				
		}  // END displayBanners funtion
		
		// Build a Google search url based on place name and address
		function buildGLink(searchName,addr) {
			var searchStreet = "";
			var searchCity = "";
			
			searchName = searchName.replace(/&/g, "%26");
			searchName = searchName.replace(/[ \/]/g, "%20");
			
			
			if ("string" === typeof addr.street.name) {
				searchStreet = addr.street.name + ",%20";
			}
			searchStreet = searchStreet.replace(/ /g, "%20");
			
			if ("string" === typeof addr.city.name) {
				searchCity = addr.city.name + ",%20";
			}
			searchCity = searchCity.replace(/ /g, "%20");
			
			return "http://www.google.com/search?q=" + searchName + ",%20" + searchStreet + searchCity + addr.state.name;
		} // END buildGLink function
		
		// WME Category translation from Natural language to object language
		function catTranslate(natCategories) {
			if (natCategories.toUpperCase().replace(/ AND /g, "").replace(/[^A-Z]/g, "").indexOf('PETSTORE') > -1) {
				return "PET_STORE_VETERINARIAN_SERVICES";
			}
			for(var keyCat in catTransWaze2Lang){
				if ( natCategories.toUpperCase().replace(/ AND /g, "").replace(/[^A-Z]/g, "") ===  catTransWaze2Lang[keyCat].toUpperCase().replace(/ AND /g, "").replace(/[^A-Z]/g, "")) {
					return keyCat;
				}
			}
			if (confirm('WMEPH: Category Error!\nClick OK to report this error') ) {  // if the category doesn't translate, then pop an alert that will make a forum post to the thread
				forumMsgInputs = {
					subject: 'Re: WMEPH Bug report',
					message: 'Error report: category "' + natCategories + '" is not translatable.',
					addbbcode20: '100', preview: 'Preview', attach_sig: 'on', notify: 'on'
				};
				WMEPH_openPostDataInNewTab(WMEPHurl + '#preview', forumMsgInputs);
			}
			return "ERROR";
		}  // END catTranslate function
		
		// compares two arrays to see if equal, regardless of order
		function matchSets(array1, array2) {
			if (array1.length !== array2.length) {return false;}  // compare lengths
			for (var i = 0; i < array1.length; i++) {
				if (array2.indexOf(array1[i]) === -1) { 
					return false;   
				}           
			}       
			return true;
		}
		
		// function that checks if all elements of target are in array:source
		function containsAll(source,target) {
			if (typeof(target) === "string") { target = [target]; }  // if a single string, convert to an array
			for (var ixx = 0; ixx < target.length; ixx++) {
				if ( source.indexOf(target[ixx]) === -1 ) {
					return false; 
				}
			}
			return true;  
		}
		
		// function that checks if any element of target are in source
		function containsAny(source,target) {
			if (typeof(source) === "string") { source = [source]; }  // if a single string, convert to an array
			if (typeof(target) === "string") { target = [target]; }  // if a single string, convert to an array
			var result = source.filter(function(tt){ return target.indexOf(tt) > -1; });   
			return (result.length > 0);  
		}
		
		// Function that inserts a string or a string array into another string array at index ix and removes any duplicates
		function insertAtIX(array1, array2, ix) {  // array1 is original string, array2 is the inserted string, at index ix
			var arrayNew = array1.slice(0);  // slice the input array so it doesn't change
			if (typeof(array2) === "string") { array2 = [array2]; }  // if a single string, convert to an array
			if (typeof(array2) === "object") {  // only apply to inserted arrays
				var arrayTemp = arrayNew.splice(ix);  // split and hold the first part
				arrayNew.push.apply(arrayNew, array2);  // add the insert
				arrayNew.push.apply(arrayNew, arrayTemp);  // add the tail end of original
			}
			return uniq(arrayNew);  // remove any duplicates (so the function can be used to move the position of a string)
		}
		
		// settings tab
		function add_PlaceHarmonizationSettingsTab() {
			//Create Settings Tab
			if (isDevVersion) {
				devVersStr = "Beta";
				devVersStrSpace = " " + devVersStr;
			}
			else {
				devVersStr = "";
				devVersStrSpace = "";
			}
			// ' + devVersStr + '
			var phTabHtml = '<li><a href="#sidepanel-ph' + devVersStr + '" data-toggle="tab" id="PlaceHarmonization' + devVersStr + '">WMEPH' + devVersStrSpace + '</a></li>';
			$("#user-tabs ul.nav-tabs:first").append(phTabHtml);
		
			//Create Settings Tab Content
			var phContentHtml = '<div class="tab-pane" id="sidepanel-ph' + devVersStr + '"><div id="PlaceHarmonizer' + devVersStr + '"><p>WMEPH' + devVersStrSpace + ' v. ' + WMEPHversion + '</p><hr align="center" width="90%"><p>Settings:</p></div></div>';
			$("#user-info div.tab-content:first").append(phContentHtml);
			
			//Create Settings Checkboxes and Load Data
			//example condition:  if ( $("#WMEPH-ConvenienceStoreToGasStations" + devVersStr).prop('checked') ) { }
			createSettingsCheckbox("WMEPH-HidePlacesWiki" + devVersStr,"Hide 'Places Wiki' button in results banner");
			if (devUser || betaUser || usrRank > 2) {
				createSettingsCheckbox("WMEPH-EnableServices" + devVersStr,"Enable automatic addition of common services");
			}
			if (devUser || betaUser || usrRank > 2) {
				createSettingsCheckbox("WMEPH-ConvenienceStoreToGasStations" + devVersStr,'Automatically add "Convenience Store" category to gas stations');
			}
			if (devUser || betaUser || usrRank > 2) {
				createSettingsCheckbox("WMEPH-StripWWW" + devVersStr,"Strip 'www.' from all URLs");
			}
			if (devUser || betaUser || usrRank > 2) {
				// createSettingsCheckbox("WMEPH-PreserveLongURLs" + devVersStr,"Preserve existing long URLs for harmonized places");
			}
			
			if (devUser) {  // Override script regionality (devs only)
				var phDevContentHtml = '<hr align="center" width="90%"><p>Dev Only Settings:</p></div></div>';
				$("#PlaceHarmonizer" + devVersStr).append(phDevContentHtml);
				createSettingsCheckbox("WMEPH-RegionOverride" + devVersStr,"Disable Region Specificity");
			}
			var feedbackString = 'Submit script feedback & suggestions';
			var placesWikiStr = 'Open the WME Places Wiki page';
			var phContentHtml2 = '<div class="tab-pane" id="sidepanel-ph' + devVersStr + '"><div id="PlaceHarmonizer' + devVersStr + '"><hr align="center" width="90%"><p><a href="' + placesWikiURL + '" target="_blank" title="'+placesWikiStr+'">'+placesWikiStr+'</a><p><a href="' + WMEPHurl + '" target="_blank" title="'+feedbackString+'">'+feedbackString+'</a></p></div></div>';
			$("#PlaceHarmonizer" + devVersStr).append(phContentHtml2);
			// $("#user-info div.tab-content:first").append(phContentHtml2);
			
		} // END Settings Tab
	
		// This routine will create a checkbox in the #PlaceHarmonizer tab and will load the setting
		//		settingID:  The #id of the checkbox being created.  
		//  textDescription:  The description of the checkbox that will be use
		function createSettingsCheckbox(settingID, textDescription) {
			//Create settings checkbox and append HTML to settings tab
			var phTempHTML = '<input type="checkbox" id="' + settingID + '">'+ textDescription +'</input><br>';
			$("#PlaceHarmonizer" + devVersStr).append(phTempHTML);
			// console.log('WMEPH: ' + settingID + 'checkbox created');
			
			//Associate click event of new checkbox to call saveSettingToLocalStorage with proper ID
			$("#" + settingID).click(function() {saveSettingToLocalStorage(settingID);});
			// console.log('WMEPH: Callback Set');
			
			//Load Setting for Local Storage, if it doesn't exist set it to NOT checked.
			//If previously set to 1, then trigger "click" event.
			if (!localStorage.getItem(settingID))
			{
				console.log('WMEPH: ' + settingID + ' not found.');
			} else if (localStorage.getItem(settingID) === "1") {
				// console.log(settingID + ' = 1 so invoking click');
				$("#" + settingID).trigger('click');
			}
			// console.log('WMEPH: Setting Checked');
		}
	
		// Save settings prefs
		function saveSettingToLocalStorage(settingID) {
			if ($("#" + settingID).prop('checked')) {
				// console.log('WMEPH: ' + settingID + ' to 1');
				localStorage.setItem(settingID, '1');
			} else {
				// console.log('WMEPH: ' + settingID + ' to 0');
				localStorage.setItem(settingID, '0');
			}	
		}
		
		// Focus away from the current cursor focus, to set text box changes
		function blurAll() {
			var tmp = document.createElement("input");
			document.body.appendChild(tmp);
			tmp.focus();
			document.body.removeChild(tmp);
		}
		
		// Sets up a div for submitting forms
		function WMEPH_initialiseFL() {
			var wmephlinks = WMEPH_getId("WMEPH-forumlink");
			var mapFooter = WMEPH_getElementsByClassName("WazeControlPermalink");
			if (mapFooter.length === 0) {
				console.log("WMEPH: error, can't find permalink container");
				setTimeout(WMEPH_initialiseFL, 1000);
				return;
			}
			var WMEPH_divPerma = mapFooter[0];
			var WMEPH_aPerma = null;
			for (var i = 0; i < WMEPH_divPerma.children.length; i++) {
				if (WMEPH_divPerma.children[i].className === 'icon-link') {
					WMEPH_aPerma = WMEPH_divPerma.children[i];
					break;
				}
			}
			//WMEPH_aPerma.style.display = 'none';
			if (wmephlinks !== null) {return WMEPH_aPerma.href;}
			var WMEPH_nodeWMEPH = document.createElement('div');
			WMEPH_nodeWMEPH.id = 'WMEPH-forumlink';
			WMEPH_nodeWMEPH.style.display = 'inline';
			WMEPH_divPerma.appendChild(WMEPH_nodeWMEPH);
			return WMEPH_aPerma.href;
		}  // END WMEPH_initialiseFL function
		
		function WMEPH_getElementsByClassName(classname, node) {  // Get element by class name
			if (!node) {node = document.getElementsByTagName("body")[0];}
			var a = [];
			var re = new RegExp('\\b' + classname + '\\b');
			var els = node.getElementsByTagName("*");
			for (var i = 0, j = els.length; i < j; i++) {
				if (re.test(els[i].className)) { a.push(els[i]); }
			}
			return a;
		}  // END WMEPH_getElementsByClassName function
		
		function WMEPH_getId(node) {  //  getID function
			return document.getElementById(node);
		}
		
		// Make a populated post on a forum thread
		function WMEPH_openPostDataInNewTab(url, data) {
			var form = document.createElement('form');
			form.target = '_blank';
			form.action = url;
			form.method = 'post';
			form.style.display = 'none';
			for (var k in data) {
				if (data.hasOwnProperty(k)) {
					var input;
					if (k === 'message') {
						input = document.createElement('textarea');
					} else if (k === 'username') {
						input = document.createElement('username_list');
					} else {
						input = document.createElement('input');
					}
					input.name = k;
					input.value = data[k];
					input.type = 'hidden';
					form.appendChild(input);
				}
			}
			WMEPH_getId('WMEPH-forumlink').appendChild(form);
			form.submit();
			WMEPH_getId('WMEPH-forumlink').removeChild(form);
			return true;
		}  // END WMEPH_openPostDataInNewTab function

		// Function that checks current place against the Harmonization Data.  Returns place data or "NoMatch"		
		function harmoList(itemName,state2L,region3L,country,itemCats) {
			var PNH_DATA_headers;
			var ixendPNH_NAMES;
			if (country === 'USA') {
				PNH_DATA_headers = USA_PNH_DATA[0].split("|");  // pull the data header names
				ixendPNH_NAMES = USA_PNH_NAMES.length;
			} else if (country === 'CAN') {
				PNH_DATA_headers = CAN_PNH_DATA[0].split("|");  // pull the data header names
				ixendPNH_NAMES = CAN_PNH_NAMES.length;
			} else {
					alert("No PNH data exists for this country.");
					return;
			}
			var ph_name_ix = PNH_DATA_headers.indexOf("ph_name");
			var ph_category1_ix = PNH_DATA_headers.indexOf("ph_category1");
			var ph_forcecat_ix = PNH_DATA_headers.indexOf("ph_forcecat");  // Force the category match
			var ph_region_ix = PNH_DATA_headers.indexOf("ph_region");  // Find the index for regions
			var ph_order_ix = PNH_DATA_headers.indexOf("ph_order");
			var nameComps;  // filled with search names to compare against place name
			var PNHPriCat;  // Primary category of PNH data
			var PNHForceCat;  // Primary category of PNH data
			var approvedRegions;  // filled with the regions that are approved for the place, when match is found
			var matchPNHData = [];  // array of matched data
			var currMatchData;
			var currMatchNum = 0;  // index for multiple matches, currently returns on first match
			var PNHOrderNum;
			var PNHNameTemp;
			var PNHNameMatch = false;  // tracks match status
			var PNHMatchProceed;  // tracks match status
			itemName = itemName.toUpperCase();  // UpperCase the current place name (The Holly And Ivy Pub #23 --> THE HOLLY AND IVY PUB #23 )
			itemName = itemName.replace(/ AND /g, '');  // Clear the word " AND " from the name (THE HOLLY AND IVY PUB #23 --> THE HOLLYIVY PUB #23 )
			itemName = itemName.replace(/^THE /g, '');  // Clear the word "THE " from the start of the name ( THE HOLLYIVY PUB #23 -- > HOLLYIVY PUB #23 )
			itemName = itemName.replace(/[^A-Z0-9]/g, '');  // Clear all non-letter and non-number characters ( HOLLYIVY PUB #23 -- > HOLLYIVYPUB23 )
			var itemNameNoNum = itemName.replace(/[^A-Z]/g, '');  // Clear non-letter characters for alternate match ( HOLLYIVYPUB23 --> HOLLYIVYPUB ) 
			
			// Search performance stats
			var t0; var t1;
			if (devUser) {
				t0 = performance.now();  // Speed check start
			}
			
			// for each place on the PNH list (skipping headers at index 0)
			// console.log(ixendPNH_NAMES);
			for (var phnum=1; phnum<ixendPNH_NAMES; phnum++) { 
				PNHMatchProceed = false; 
				if (country === 'USA') {
					nameComps = USA_PNH_NAMES[phnum].split("|");  // splits all possible search names for the current PNH entry
				} else if (country === 'CAN') {
					nameComps = CAN_PNH_NAMES[phnum].split("|");  // splits all possible search names for the current PNH entry
				}
				
				if ( nameComps.indexOf(itemName) > -1 || nameComps.indexOf(itemNameNoNum) > -1 ) {  // Compare WME place name to PNH search name list
					if (country === 'USA') {
						matchPNHData[currMatchNum] = USA_PNH_DATA[phnum];  // Pull the data line from the PNH data table.  (**Set in array for future multimatch features)
					} else if (country === 'CAN') {
						matchPNHData[currMatchNum] = CAN_PNH_DATA[phnum];  // Pull the data line from the PNH data table.  (**Set in array for future multimatch features)
					}
					currMatchData = matchPNHData[currMatchNum].split("|");  // Split the PNH place data into string array
					PNHPriCat = catTranslate(currMatchData[ph_category1_ix]);
					PNHForceCat = currMatchData[ph_forcecat_ix];
					if (itemCats[0] === "GAS_STATION") {  // Gas stations only harmonized if the WME place category is already gas station (prevents Costco Gas becoming Costco Store) 
						PNHForceCat = "1";
					}
					if ( PNHForceCat === "1" && itemCats.indexOf(PNHPriCat) === 0 ) {  // Name and primary category match
						PNHMatchProceed = true; 
					} else if ( PNHForceCat === "2" && itemCats.indexOf(PNHPriCat) > -1 ) {  // Name and any category match
						PNHMatchProceed = true; 
					} else if ( PNHForceCat === "0" || PNHForceCat === "") {  // Name only match
						PNHMatchProceed = true; 
					}
					
					if (PNHMatchProceed) {
						
						PNHNameMatch = true;  // PNH match found (once true, stays true)
						PNHNameTemp = currMatchData[ph_name_ix];  // temp name for approval return
						PNHOrderNum = currMatchData[ph_order_ix];  // temp order number for approval return
						
						approvedRegions = currMatchData[ph_region_ix].replace(/ /g, '');  // remove spaces from region field
						approvedRegions = approvedRegions.toUpperCase().split(",");  // upper case the approved regions and split by commas
						if (approvedRegions.indexOf(state2L) > -1 || approvedRegions.indexOf(region3L) > -1 ||  // if the WME-selected item matches the region
						approvedRegions.indexOf(country) > -1 ||  //  OR if the country code is in the data then it is approved for all regions therein 
						$("#WMEPH-RegionOverride" + devVersStr).prop('checked')) {  // OR if region override is selected
							if (devUser) {
								t1 = performance.now();  // log search time
								console.log("WMEPH: Found place in " + (t1 - t0) + " milliseconds.");
							}
							bannMess.placeMatched.active = true;
							return currMatchData;  // Return the PNH data string array to the main script
						}
						currMatchNum++;  // *** Multiple matches for future work
					}
				} 
			}  // END loop through PNH places
			
			// If NO (name & region) match was found:
			if (PNHNameMatch) {  // if a name match was found but not for region, prod the user to get it approved
				bannButt.ApprovalSubmit.active = true;
				console.log("WMEPH: PNH data exists but not approved for this area.");	
				if (devUser) {
					t1 = performance.now();  // log search time
					console.log("WMEPH: Searched all PNH entries in " + (t1 - t0) + " milliseconds.");
				}
				return ["ApprovalNeeded", PNHNameTemp, PNHOrderNum];
			} else {  // if no match was found, suggest adding the place to the sheet if it's a chain
				bannButt.NewPlaceSubmit.active = true;
				console.log("WMEPH: Place not found in the " + country + " PNH list.");	
				if (devUser) {
					t1 = performance.now();  // log search time
					console.log("WMEPH: Searched all PNH entries in " + (t1 - t0) + " milliseconds.");
				}
				return ["NoMatch"];
			}
		} // END harmoList function
		
		// KB Shortcut object
		var shortcut = {
			'all_shortcuts': {}, //All the shortcuts are stored in this array
			'add': function(shortcut_combination, callback, opt) {
				//Provide a set of default options
				var default_options = { 'type': 'keydown', 'propagate': false, 'disable_in_input': false, 'target': document, 'keycode': false };
				if (!opt) {opt = default_options;}
				else {
					for (var dfo in default_options) {
						if (typeof opt[dfo] === 'undefined') {opt[dfo] = default_options[dfo];}
					}
				}
				var ele = opt.target;
				if (typeof opt.target === 'string') {ele = document.getElementById(opt.target);}
				// var ths = this;
				shortcut_combination = shortcut_combination.toLowerCase();
				//The function to be called at keypress
				var func = function(e) {
					e = e || window.event;
					if (opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields
						var element;
						if (e.target) {element = e.target;}
						else if (e.srcElement) {element = e.srcElement;}
						if (element.nodeType === 3) {element = element.parentNode;}
						if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {return;}
					}
					//Find Which key is pressed
					var code;
					if (e.keyCode) {code = e.keyCode;}
					else if (e.which) {code = e.which;}
					var character = String.fromCharCode(code).toLowerCase();
					if (code === 188) {character = ",";} //If the user presses , when the type is onkeydown
					if (code === 190) {character = ".";} //If the user presses , when the type is onkeydown
					var keys = shortcut_combination.split("+");
					//Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
					var kp = 0;
					//Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
					var shift_nums = { "`": "~","1": "!","2": "@","3": "#","4": "$","5": "%","6": "^","7": "&",
							"8": "*","9": "(","0": ")","-": "_","=": "+",";": ":","'": "\"",",": "<",".": ">","/": "?","\\": "|" };
						//Special Keys - and their codes
					var special_keys = { 'esc': 27,'escape': 27,'tab': 9,'space': 32,'return': 13,'enter': 13,'backspace': 8,'scrolllock': 145,
						'scroll_lock': 145,'scroll': 145,'capslock': 20,'caps_lock': 20,'caps': 20,'numlock': 144,'num_lock': 144,'num': 144,
						'pause': 19,'break': 19,'insert': 45,'home': 36,'delete': 46,'end': 35,'pageup': 33,'page_up': 33,'pu': 33,'pagedown': 34,
						'page_down': 34,'pd': 34,'left': 37,'up': 38,'right': 39,'down': 40,'f1': 112,'f2': 113,'f3': 114,'f4': 115,'f5': 116,
						'f6': 117,'f7': 118,'f8': 119,'f9': 120,'f10': 121,'f11': 122,'f12': 123 };
					var modifiers = {
						shift: { wanted: false, pressed: false },
						ctrl: { wanted: false, pressed: false },
						alt: { wanted: false, pressed: false },
						meta: { wanted: false, pressed: false } //Meta is Mac specific
					};
					if (e.ctrlKey) {modifiers.ctrl.pressed = true;}
					if (e.shiftKey) {modifiers.shift.pressed = true;}
					if (e.altKey) {modifiers.alt.pressed = true;}
					if (e.metaKey) {modifiers.meta.pressed = true;}
					var k;
					for (var i = 0; k = keys[i], i < keys.length; i++) {
						//Modifiers
						if (k === 'ctrl' || k === 'control') {
							kp++;
							modifiers.ctrl.wanted = true;
						} else if (k === 'shift') {
							kp++;
							modifiers.shift.wanted = true;
						} else if (k === 'alt') {
							kp++;
							modifiers.alt.wanted = true;
						} else if (k === 'meta') {
							kp++;
							modifiers.meta.wanted = true;
						} else if (k.length > 1) { //If it is a special key
							if (special_keys[k] === code) {kp++;}
						} else if (opt['keycode']) {
							if (opt['keycode'] === code) {kp++;}
						} else { //The special keys did not match
							if (character === k) {kp++;}
							else {
								if (shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase
									character = shift_nums[character];
									if (character === k) {kp++;}
								}
							}
						}
					}
	
					if (kp === keys.length && modifiers.ctrl.pressed === modifiers.ctrl.wanted && modifiers.shift.pressed === modifiers.shift.wanted && 
						modifiers.alt.pressed === modifiers.alt.wanted && modifiers.meta.pressed === modifiers.meta.wanted) {
						callback(e);
						if (!opt['propagate']) { //Stop the event
							//e.cancelBubble is supported by IE - this will kill the bubbling process.
							e.cancelBubble = true;
							e.returnValue = false;
							//e.stopPropagation works in Firefox.
							if (e.stopPropagation) {
								e.stopPropagation();
								e.preventDefault();
							}
							return false;
						}
					}
				};
				this.all_shortcuts[shortcut_combination] = { 'callback': func, 'target': ele, 'event': opt['type'] };
				//Attach the function with the event
				if (ele.addEventListener) {ele.addEventListener(opt['type'], func, false);}
				else if (ele.attachEvent) {ele.attachEvent('on' + opt['type'], func);}
				else {ele['on' + opt['type']] = func;}
			},
			//Remove the shortcut - just specify the shortcut and I will remove the binding
			'remove': function(shortcut_combination) {
				shortcut_combination = shortcut_combination.toLowerCase();
				var binding = this.all_shortcuts[shortcut_combination];
				delete(this.all_shortcuts[shortcut_combination]);
				if (!binding) {return;}
				var type = binding['event'];
				var ele = binding['target'];
				var callback = binding['callback'];
				if (ele.detachEvent) {ele.detachEvent('on' + type, callback);}
				else if (ele.removeEventListener) {ele.removeEventListener(type, callback, false);}
				else {ele['on' + type] = false;}
			}
		};  // END Shortcut function
		
		
	} // END runPH Function
		
	
	// This function runs at script load, and builds the search name dataset to compare the WME selected place name to.
	function makeNameCheckList(PNH_DATA) {  // Builds the list of search names to match to the WME place name
		var PNH_NAMES = [];
		var PNH_DATA_headers = PNH_DATA[0].split("|");  // split the data headers out
		var ph_name_ix = PNH_DATA_headers.indexOf("ph_name");  // find the indices needed for the function
		var ph_category1_ix = PNH_DATA_headers.indexOf("ph_category1");
		var ph_searchnamebase_ix = PNH_DATA_headers.indexOf("ph_searchnamebase");
		var ph_searchnamemid_ix = PNH_DATA_headers.indexOf("ph_searchnamemid");
		var ph_searchnameend_ix = PNH_DATA_headers.indexOf("ph_searchnameend");
		var ph_disable_ix = PNH_DATA_headers.indexOf("ph_disable");
		
		var t0 = performance.now();  // Speed check start
		var newNameListLength;  // static list length
		
		for (var pnhix=0; pnhix<PNH_DATA.length; pnhix++) {  // loop through all PNH places
			var pnhEntryTemp = PNH_DATA[pnhix].split("|");  // split the current PNH data line 
			if (pnhEntryTemp[ph_disable_ix] !== "1") {
				var newNameList = pnhEntryTemp[ph_name_ix].toUpperCase();  // pull out the primary PNH name & upper case it
				newNameList = newNameList.replace(/ AND /g, '');  // Clear the word "AND" from the name
				newNameList = newNameList.replace(/^THE /g, '');  // Clear the word "THE" from the start of the name
				newNameList = [newNameList.replace(/[^A-Z0-9]/g, '')];  // Clear non-letter and non-number characters, store in array
				
				// The following code sets up alternate search names as outlined in the PNH dataset.  
				// Formula, with P = primary; B1, B2 = base terms; M1, M2 = mid terms; E1, E2 = end terms
				// Search list will build: P, B, PM, BM, PE, BE, PME, BME.  
				// Multiple M terms are applied singly and in pairs (B1M2M1E2).  Multiple B and E terms are applied singly (e.g B1B2M1 not used).
				// Any doubles like B1E2=P are purged at the end to reduce search times.
				if (pnhEntryTemp[ph_searchnamebase_ix] !== "0") {   // If base terms exist, otherwise only the primary name is matched
					var pnhSearchNameBase = pnhEntryTemp[ph_searchnamebase_ix].replace(/[^A-Za-z0-9,]/g, '');  // clear non-letter and non-number characters (keep commas)
					pnhSearchNameBase = pnhSearchNameBase.toUpperCase().split(",");  // upper case and split the base-name  list
					newNameList.push.apply(newNameList,pnhSearchNameBase);   // add them to the search list
					
					if (pnhEntryTemp[ph_searchnamemid_ix] !== "0") {  // if middle search term add-ons exist
						var pnhSearchNameMid = pnhEntryTemp[ph_searchnamemid_ix].replace(/[^A-Za-z0-9,]/g, '');  // clear non-letter and non-number characters
						pnhSearchNameMid = pnhSearchNameMid.toUpperCase().split(",");  // upper case and split
						if (pnhSearchNameMid.length > 1) {  // if there are more than one mid terms, it adds a permutation of the first 2
							pnhSearchNameMid.push.apply( pnhSearchNameMid,[ pnhSearchNameMid[0]+pnhSearchNameMid[1],pnhSearchNameMid[1]+pnhSearchNameMid[0] ] );
						}
						newNameListLength = newNameList.length;
						for (var extix=1; extix<newNameListLength; extix++) {  // extend the list by adding Mid terms onto the SearchNameBase names
							for (var altix=0; altix<pnhSearchNameMid.length; altix++) {
								newNameList.push(newNameList[extix]+pnhSearchNameMid[altix] );
							}
						}
					}
					
					if (pnhEntryTemp[ph_searchnameend_ix] !== "0") {  // if end search term add-ons exist
						var pnhSearchNameEnd = pnhEntryTemp[ph_searchnameend_ix].replace(/[^A-Za-z0-9,]/g, '');  // clear non-letter and non-number characters
						pnhSearchNameEnd = pnhSearchNameEnd.toUpperCase().split(",");  // upper case and split
						newNameListLength = newNameList.length;
						for (var exetix=1; exetix<newNameListLength; exetix++) {  // extend the list by adding End terms onto all the SearchNameBase & Base+Mid names
							for (var aletix=0; aletix<pnhSearchNameEnd.length; aletix++) {
								newNameList.push(newNameList[exetix]+pnhSearchNameEnd[aletix] );
							}
						}
					}
				}
				
				// Next, add extensions to the search names based on the WME place category
				newNameListLength = newNameList.length;
				var catix;
				if (pnhEntryTemp[ph_category1_ix].toUpperCase().replace(/[^A-Za-z0-9]/g, '') === "HOTEL") {
					for ( catix=0; catix<newNameListLength; catix++) {  // extend the list by adding Hotel to all items
						newNameList.push(newNameList[catix]+"HOTEL");
					}
				} else if (pnhEntryTemp[ph_category1_ix].toUpperCase().replace(/[^A-Za-z0-9]/g, '') === "BANKFINANCIAL") {
					for ( catix=0; catix<newNameListLength; catix++) {  // extend the list by adding Bank and ATM to all items
						newNameList.push(newNameList[catix]+"BANK");
						newNameList.push(newNameList[catix]+"ATM");
					}
				} else if (pnhEntryTemp[ph_category1_ix].toUpperCase().replace(/[^A-Za-z0-9]/g, '') === "SUPERMARKETGROCERY") {
					for ( catix=0; catix<newNameListLength; catix++) {  // extend the list by adding Supermarket to all items
						newNameList.push(newNameList[catix]+"SUPERMARKET");
					}
				} else if (pnhEntryTemp[ph_category1_ix].toUpperCase().replace(/[^A-Za-z0-9]/g, '') === "GYMFITNESS") {
					for ( catix=0; catix<newNameListLength; catix++) {  // extend the list by adding Gym to all items
						newNameList.push(newNameList[catix]+"GYM");
					}
				} else if (pnhEntryTemp[ph_category1_ix].toUpperCase().replace(/[^A-Za-z0-9]/g, '') === "GASSTATION") {
					for ( catix=0; catix<newNameListLength; catix++) {  // extend the list by adding Gas terms to all items
						newNameList.push(newNameList[catix]+"GAS");
						newNameList.push(newNameList[catix]+"GASOLINE");
						newNameList.push(newNameList[catix]+"FUEL");
						newNameList.push(newNameList[catix]+"GASSTATION");
					}
				} else if (pnhEntryTemp[ph_category1_ix].toUpperCase().replace(/[^A-Za-z0-9]/g, '') === "CARRENTAL") {
					for ( catix=0; catix<newNameListLength; catix++) {  // extend the list by adding Car Rental terms to all items
						newNameList.push(newNameList[catix]+"RENTAL");
						newNameList.push(newNameList[catix]+"RENTACAR");
						newNameList.push(newNameList[catix]+"CARRENTAL");
						newNameList.push(newNameList[catix]+"RENTALCAR");
					}
				} 
				newNameList = uniq(newNameList);  // remove any duplicate search names
				newNameList = newNameList.join("|");  // join the list with |
				PNH_NAMES.push(newNameList);  // push the list to the master search list
			} else { // END if valid line
				PNH_NAMES.push('00');
			}
		}
		var t1 = performance.now();  // log search time
		console.log("WMEPH: Built search list of " + PNH_DATA.length + " PNH places in " + (t1 - t0) + " milliseconds.");
		return PNH_NAMES;
	}  // END makeNameCheckList
	
	// Removes duplicate strings from string array
	function uniq(a) {
		"use strict";
		var seen = {};
		return a.filter(function(item) {
			return seen.hasOwnProperty(item) ? false : (seen[item] = true);
		});
	}  // END uniq function
	
	
	
	placeHarmonizer_bootstrap();
})();
// var DLscript = document.createElement("script");
// DLscript.textContent = runPH.toString() + ' \n' + 'runPH();';
// DLscript.setAttribute("type", "application/javascript");
// document.body.appendChild(DLscript);
长期地址
遇到问题?请前往 GitHub 提 Issues。