<commit_before>/**
 * Filters
 * Gold Server - http://gold.psim.us/
 *
 * Manually sets filters for chatting and names.
 * In this, we also handle proxy connections with a blacklist feature.
 * Credits: jd, panpawn
 *
 * @license MIT license
 */
'use strict';

const fs = require('fs');

let adWhitelist = Config.adWhitelist ? Config.adWhitelist : [];
let adRegex = new RegExp("(play.pokemonshowdown.com\\/~~)(?!(" + adWhitelist.join('|') + "))", "g");

let bannedMessages = Config.bannedMessages ? Config.bannedMessages : [];

let watchPhrases = Config.watchPhrases ? Config.watchPhrases : [];
let watchUsers = Config.watchUsers ? Config.watchUsers : [];

let proxyWhitelist = Config.proxyWhitelist || false;

/*********************
 * Chatfilter Magic *
 * ******************/

Config.chatfilter = function (message, user, room, connection, targetUser) {
	user.lastActiveTime = Date.now();
	if (!room && !Users(targetUser)) targetUser = {name: 'unknown user'};

	// watch phrases and watch users
	let watchRoom = Rooms('watchroom') ? Rooms('watchroom') : false;
	if (watchRoom) {
		let watchWords = watchPhrases.filter(phrase => { return ~toId(message).indexOf(phrase); }).length;
		let watchUserslist = watchUsers.filter(name => { return ~user.userid.indexOf(name); }).length;
		if (!user.can('hotpatch') && watchWords >= 1 || watchUserslist >= 1) {
			watchRoom.add('|c|' + user.getIdentity() + '| __(' + (room ? "To " + room.id : "Private message to " + targetUser.name) + ")__ " + message).update();
		}
	}

	// global banned messages
	for (let x in bannedMessages) {
		if (message.toLowerCase().indexOf(bannedMessages[x]) > -1 && bannedMessages[x] !== '' && message.substr(0, 1) !== '/') {
			if (user.locked) return false;
			Punishments.lock(user, Date.now() + 7 * 24 * 60 * 60 * 1000, null, "Said a banned word: " + bannedMessages[x]);
			user.popup('You have been automatically locked for sending a message containing a banned word.');
			Monitor.log('[PornMonitor] ' + user.name + ' __(' + (room ? 'In ' + room.id : 'Private message to ' + targetUser.name) + ')__ was automatically locked (and shadow banned) for trying to say "' + message + '"');
			fs.appendFile('logs/modlog/modlog_staff.txt', '[' + (new Date().toJSON()) + '] (staff) ' + user.name + ' was locked from talking by the Server (' +
			bannedMessages[x] + ') (' + connection.ip + ')\n');
			Gold.pmUpperStaff(user.name + ' has been automatically locked for sending a message containing a banned word' +
			(room ? ". **Room:**" + room.id : " in a private message to " + targetUser.name + ".") + ' **Message:** ' + message, '~Server');
			Users.ShadowBan.addUser(user);
		}
	}

	// advertising
	let pre_matches = (message.match(/psim|psim.us|psim us|psm.us|psm us/g) || []).length;
	let final_check = (pre_matches >= 1 ? adWhitelist.filter(server => { return ~message.indexOf(server); }).length : 0);
	if (!user.can('hotpatch') && (pre_matches >= 1 && final_check === 0 || pre_matches >= 2 && final_check >= 1 || message.match(adRegex))) {
		if (user.locked) return false;
		if (!Users.ShadowBan.checkBanned(user)) {
			Users.ShadowBan.addUser(user);
			fs.appendFile('logs/modlog/modlog_staff.txt', '[' + (new Date().toJSON()) + '] (staff) ' + user.name + ' was shadow banned by the Server. (Advertising) (' + connection.ip + ')\n');
			Gold.pmUpperStaff(user.name + " has been sbanned for attempting to advertise" + (room ? ". **Room:**" + room.id : " in a private message to " + targetUser.name + ".") + " **Message:** " + message, "~Server");
			Monitor.log("[AdvMonitor] " + user.name + " has been sbanned for attempting to advertise" + (room ? ". **Room:** " + room.id : " in a private message to " + targetUser.name + ".") + " **Message:** " + message);
		}
	}
	return message;
};

/*********************
 * Namefilter Magic *
 * ******************/
try {
	Config.bannedNames = fs.readFileSync('config/bannednames.txt', 'utf8').toLowerCase().split('\n');
} catch (e) {
	Config.bannedNames = [];
}

function loadBannedNames() {
	try {
		Config.bannedNames = fs.readFileSync('config/bannednames.txt', 'utf8').toLowerCase().split('\n');
	} catch (e) {
		Config.bannedNames = [];
	}
}
loadBannedNames();

Config.namefilter = function (name, user) {
	let badNames = Config.bannedNames, badHosts = Object.keys(Gold.lockedHosts), nameId = toId(name);
	for (let x in badNames) {
		if (nameId.indexOf(badNames[x]) > -1 && badNames[x] !== '') {
			Monitor.log('[NameFilter] should probably FR: ' + name);
		}
	}

	// Hostfilter stuff
	let ip = user.connections[Object.keys(user.connections).length - 1].ip;

	// deal with global ranked user's manually...
	let userSymbol = (Users.usergroups[nameId] ? Users.usergroups[nameId].substr(0, 1) : ' ');
	let rankIndex = (Config.groupsranking.includes(userSymbol) ? Config.groupsranking.indexOf(userSymbol) : false);

	Dnsbl.reverse(ip).then(host => {
		if (!host) return;
		if (badHosts.length < 0) return; // there are no blacklisted hosts (yet)

		// handling "trusted" users...
		if (rankIndex && rankIndex > 0) return;
		if (Gold.userData[toId(name)] && Gold.userData[toId(name)].proxywhitelist) return;
		if (proxyWhitelist && proxyWhitelist.includes(nameId)) return;

		badHosts.forEach(badHost => {
			if (host.includes(badHost)) {
				user.locked = '#hostfilter';
				user.updateIdentity();
				user.popup("|modal|You have been automatically locked due to being on a proxy known for spam and abuse.\n\nLog off PS! and try reconnecting without a proxy to be unlocked.");
				// Monitor.log("[ProxyMonitor] " + name + " (" + ip + ") has been automatically locked. (" + host + ")");
				return;
			}
		});
	});

	Gold.evadeMonitor(user, name);

	return name;
};

/*********************
 * Hostfilter Magic *
 * ******************/
Gold.lockedHosts = Object.create(null);

function loadHostBlacklist() {
	fs.readFile('config/lockedhosts.json', 'utf8', function (err, file) {
		if (err) return;
		Gold.lockedHosts = JSON.parse(file);
	});
}
loadHostBlacklist();

function saveHost() {
	fs.writeFileSync('config/lockedhosts.json', JSON.stringify(Gold.lockedHosts));
}

Gold.evadeMonitor = function (user, name, punished) {
	let punishments = this.punishments;
	if (punished && punished.alts) {
		punished.alts.forEach(alt => {
			if (Gold.punishments[toId(alt)]) delete Gold.punishments[toId(alt)];
		});
		Gold.savePunishments();
		return;
	}
	let points = 0;
	let num = Object.keys(user.connections).length - 1;
	let userAgent = user.connections[num].headers ? user.connections[num].headers['user-agent'] : '';
	let ip = user.connections[num].ip;

	if (punished) {
		punishments[user.userid] = {
			'useragent': userAgent,
			'ip': ip,
			'iprange': Gold.getIpRange(ip)[0],
			'ipclass': Gold.getIpRange(ip)[1],
			'type': punished.type,
			'exires': punished.expires,
		};
		Gold.savePunishments();
	} else {
		if (user.locked || Users.ShadowBan.checkBanned(user)) return;

		let ipRange = Gold.getIpRange(ip)[0];
		let reasons = [];
		let evader = '';
		let defaultAvatars = [1, 2, 101, 102, 169, 170, 265, 266];
		Object.keys(punishments).forEach(offender => {
			if (punishments[offender].exires < Date.now()) return;
			if (punishments[offender].useragent === userAgent) {
				points++;
				reasons.push(`have the same user agent`);
				evader = punishments[offender].type + ' user: ' + offender;
			}
			if (punishments[offender].iprange && ip.startsWith(punishments[offender].iprange)) {
				points++;
				reasons.push(`have the IPv4 class ${punishments[offender].ipclass} range (${ipRange}.*)`);
				evader = punishments[offender].type + ' user: ' + offender;
			}
			if (points >= 1 && defaultAvatars.includes(user.avatar)) {
				points++;
				reasons.push(`have a default avatar`);
			}
		});
		let staff = Rooms('staff');
		if (points >= 2) {
			Users.ShadowBan.addUser(name);
			if (staff) staff.add(`[EvadeMonitor] SHADOWBANNED: ${name}, evading alt of ${evader} because they ${reasons.join(' and ')}`).update();
		} else if (points === 1) {
			if (staff) staff.add(`[EvadeMonitor] SUSPECTED EVADER: ${name} is possibly an evading alt of ${evader} because they ${reasons.join(' and ')}.`).update();
		}
	}
};

exports.commands = {
	lockhost: function (target, room, user) {
		if (!this.can('pban')) return false;
		if (!target) return this.parse('/help lockhost');
		if (Gold.lockedHosts[target]) return this.errorReply("The host '" + target + "' is was already locked by " + Gold.lockedHosts[target].by + ".");

		Gold.lockedHosts[target] = {
			by: user.name,
			on: Date.now(),
		};
		saveHost();

		this.privateModCommand("(" + user.name + " has blacklisted host: " + target + ")");
	},
	lockhosthelp: ["/lockhost [host] - Adds host to server blacklist.  Users connecting with these hosts will be automatically locked from connection, so use this carefully! Requires & ~"],

	unlockhost: function (target, room, user) {
		if (!this.can('pban')) return false;
		if (!target) return this.parse('/help unlockhost');
		if (!Gold.lockedHosts[target]) return this.errorReply("The host '" + target + "' is not currently blacklisted.");

		delete Gold.lockedHosts[target];
		saveHost();

		this.privateModCommand("(" + user.name + " has unblacklisted host: " + target + ")");
	},
	unlockhosthelp: ["/unlockhost [host] - Removes a host from the server's blacklist.  Requires & ~"],

	proxylist: function (target, room, user) {
		if (!this.can('pban')) return false;
		let badHosts = Object.keys(Gold.lockedHosts);
		if (badHosts.length < 0) return this.errorReply("Weird, no proxies have been blacklisted (yet).");

		let buff = '

Proxy:	Blacklisted By:	Blacklisted:
';
		badHosts.forEach(proxy => {
			buff += '
' + proxy + '	' + Gold.nameColor(Gold.lockedHosts[proxy].by, false) + '	' + Chat.toDurationString(Date.now() - Gold.lockedHosts[proxy].on) + ' ago
';
		});
		buff += '
';

		return this.sendReplyBox(buff);
	},
};
<commit_msg>Slight change to filter declaration<commit_after>/**
 * Filters
 * Gold Server - http://gold.psim.us/
 *
 * Manually sets filters for chatting and names.
 * In this, we also handle proxy connections with a blacklist feature.
 * Credits: jd, panpawn
 *
 * @license MIT license
 */
'use strict';

const fs = require('fs');

let adWhitelist = Config.adWhitelist ? Config.adWhitelist : [];
let adRegex = new RegExp("(play.pokemonshowdown.com\\/~~)(?!(" + adWhitelist.join('|') + "))", "g");

let bannedMessages = Config.bannedMessages ? Config.bannedMessages : [];

let watchPhrases = Config.watchPhrases ? Config.watchPhrases : [];
let watchUsers = Config.watchUsers ? Config.watchUsers : [];

let proxyWhitelist = Config.proxyWhitelist || false;

/*********************
 * Chatfilter Magic *
 * ******************/

exports.chatfilter = function (message, user, room, connection, targetUser) {
	user.lastActiveTime = Date.now();
	if (!room && !Users(targetUser)) targetUser = {name: 'unknown user'};

	// watch phrases and watch users
	let watchRoom = Rooms('watchroom') ? Rooms('watchroom') : false;
	if (watchRoom) {
		let watchWords = watchPhrases.filter(phrase => { return ~toId(message).indexOf(phrase); }).length;
		let watchUserslist = watchUsers.filter(name => { return ~user.userid.indexOf(name); }).length;
		if (!user.can('hotpatch') && watchWords >= 1 || watchUserslist >= 1) {
			watchRoom.add('|c|' + user.getIdentity() + '| __(' + (room ? "To " + room.id : "Private message to " + targetUser.name) + ")__ " + message).update();
		}
	}

	// global banned messages
	for (let x in bannedMessages) {
		if (message.toLowerCase().indexOf(bannedMessages[x]) > -1 && bannedMessages[x] !== '' && message.substr(0, 1) !== '/') {
			if (user.locked) return false;
			Punishments.lock(user, Date.now() + 7 * 24 * 60 * 60 * 1000, null, "Said a banned word: " + bannedMessages[x]);
			user.popup('You have been automatically locked for sending a message containing a banned word.');
			Monitor.log('[PornMonitor] ' + user.name + ' __(' + (room ? 'In ' + room.id : 'Private message to ' + targetUser.name) + ')__ was automatically locked (and shadow banned) for trying to say "' + message + '"');
			fs.appendFile('logs/modlog/modlog_staff.txt', '[' + (new Date().toJSON()) + '] (staff) ' + user.name + ' was locked from talking by the Server (' +
			bannedMessages[x] + ') (' + connection.ip + ')\n');
			Gold.pmUpperStaff(user.name + ' has been automatically locked for sending a message containing a banned word' +
			(room ? ". **Room:**" + room.id : " in a private message to " + targetUser.name + ".") + ' **Message:** ' + message, '~Server');
			Users.ShadowBan.addUser(user);
		}
	}

	// advertising
	let pre_matches = (message.match(/psim|psim.us|psim us|psm.us|psm us/g) || []).length;
	let final_check = (pre_matches >= 1 ? adWhitelist.filter(server => { return ~message.indexOf(server); }).length : 0);
	if (!user.can('hotpatch') && (pre_matches >= 1 && final_check === 0 || pre_matches >= 2 && final_check >= 1 || message.match(adRegex))) {
		if (user.locked) return false;
		if (!Users.ShadowBan.checkBanned(user)) {
			Users.ShadowBan.addUser(user);
			fs.appendFile('logs/modlog/modlog_staff.txt', '[' + (new Date().toJSON()) + '] (staff) ' + user.name + ' was shadow banned by the Server. (Advertising) (' + connection.ip + ')\n');
			Gold.pmUpperStaff(user.name + " has been sbanned for attempting to advertise" + (room ? ". **Room:**" + room.id : " in a private message to " + targetUser.name + ".") + " **Message:** " + message, "~Server");
			Monitor.log("[AdvMonitor] " + user.name + " has been sbanned for attempting to advertise" + (room ? ". **Room:** " + room.id : " in a private message to " + targetUser.name + ".") + " **Message:** " + message);
		}
	}
	return message;
};
Config.chatfilter = exports.chatfilter;

/*********************
 * Namefilter Magic *
 * ******************/
try {
	Config.bannedNames = fs.readFileSync('config/bannednames.txt', 'utf8').toLowerCase().split('\n');
} catch (e) {
	Config.bannedNames = [];
}

function loadBannedNames() {
	try {
		Config.bannedNames = fs.readFileSync('config/bannednames.txt', 'utf8').toLowerCase().split('\n');
	} catch (e) {
		Config.bannedNames = [];
	}
}
loadBannedNames();

Config.namefilter = function (name, user) {
	let badNames = Config.bannedNames, badHosts = Object.keys(Gold.lockedHosts), nameId = toId(name);
	for (let x in badNames) {
		if (nameId.indexOf(badNames[x]) > -1 && badNames[x] !== '') {
			Monitor.log('[NameFilter] should probably FR: ' + name);
		}
	}

	// Hostfilter stuff
	let ip = user.connections[Object.keys(user.connections).length - 1].ip;

	// deal with global ranked user's manually...
	let userSymbol = (Users.usergroups[nameId] ? Users.usergroups[nameId].substr(0, 1) : ' ');
	let rankIndex = (Config.groupsranking.includes(userSymbol) ? Config.groupsranking.indexOf(userSymbol) : false);

	Dnsbl.reverse(ip).then(host => {
		if (!host) return;
		if (badHosts.length < 0) return; // there are no blacklisted hosts (yet)

		// handling "trusted" users...
		if (rankIndex && rankIndex > 0) return;
		if (Gold.userData[toId(name)] && Gold.userData[toId(name)].proxywhitelist) return;
		if (proxyWhitelist && proxyWhitelist.includes(nameId)) return;

		badHosts.forEach(badHost => {
			if (host.includes(badHost)) {
				user.locked = '#hostfilter';
				user.updateIdentity();
				user.popup("|modal|You have been automatically locked due to being on a proxy known for spam and abuse.\n\nLog off PS! and try reconnecting without a proxy to be unlocked.");
				// Monitor.log("[ProxyMonitor] " + name + " (" + ip + ") has been automatically locked. (" + host + ")");
				return;
			}
		});
	});

	Gold.evadeMonitor(user, name);

	return name;
};

/*********************
 * Hostfilter Magic *
 * ******************/
Gold.lockedHosts = Object.create(null);

function loadHostBlacklist() {
	fs.readFile('config/lockedhosts.json', 'utf8', function (err, file) {
		if (err) return;
		Gold.lockedHosts = JSON.parse(file);
	});
}
loadHostBlacklist();

function saveHost() {
	fs.writeFileSync('config/lockedhosts.json', JSON.stringify(Gold.lockedHosts));
}

Gold.evadeMonitor = function (user, name, punished) {
	let punishments = this.punishments;
	if (punished && punished.alts) {
		punished.alts.forEach(alt => {
			if (Gold.punishments[toId(alt)]) delete Gold.punishments[toId(alt)];
		});
		Gold.savePunishments();
		return;
	}
	let points = 0;
	let num = Object.keys(user.connections).length - 1;
	let userAgent = user.connections[num].headers ? user.connections[num].headers['user-agent'] : '';
	let ip = user.connections[num].ip;

	if (punished) {
		punishments[user.userid] = {
			'useragent': userAgent,
			'ip': ip,
			'iprange': Gold.getIpRange(ip)[0],
			'ipclass': Gold.getIpRange(ip)[1],
			'type': punished.type,
			'exires': punished.expires,
		};
		Gold.savePunishments();
	} else {
		if (user.locked || Users.ShadowBan.checkBanned(user)) return;

		let ipRange = Gold.getIpRange(ip)[0];
		let reasons = [];
		let evader = '';
		let defaultAvatars = [1, 2, 101, 102, 169, 170, 265, 266];
		Object.keys(punishments).forEach(offender => {
			if (punishments[offender].exires < Date.now()) return;
			if (punishments[offender].useragent === userAgent) {
				points++;
				reasons.push(`have the same user agent`);
				evader = punishments[offender].type + ' user: ' + offender;
			}
			if (punishments[offender].iprange && ip.startsWith(punishments[offender].iprange)) {
				points++;
				reasons.push(`have the IPv4 class ${punishments[offender].ipclass} range (${ipRange}.*)`);
				evader = punishments[offender].type + ' user: ' + offender;
			}
			if (points >= 1 && defaultAvatars.includes(user.avatar)) {
				points++;
				reasons.push(`have a default avatar`);
			}
		});
		let staff = Rooms('staff');
		if (points >= 2) {
			Users.ShadowBan.addUser(name);
			if (staff) staff.add(`[EvadeMonitor] SHADOWBANNED: ${name}, evading alt of ${evader} because they ${reasons.join(' and ')}`).update();
		} else if (points === 1) {
			if (staff) staff.add(`[EvadeMonitor] SUSPECTED EVADER: ${name} is possibly an evading alt of ${evader} because they ${reasons.join(' and ')}.`).update();
		}
	}
};

exports.commands = {
	lockhost: function (target, room, user) {
		if (!this.can('pban')) return false;
		if (!target) return this.parse('/help lockhost');
		if (Gold.lockedHosts[target]) return this.errorReply("The host '" + target + "' is was already locked by " + Gold.lockedHosts[target].by + ".");

		Gold.lockedHosts[target] = {
			by: user.name,
			on: Date.now(),
		};
		saveHost();

		this.privateModCommand("(" + user.name + " has blacklisted host: " + target + ")");
	},
	lockhosthelp: ["/lockhost [host] - Adds host to server blacklist.  Users connecting with these hosts will be automatically locked from connection, so use this carefully! Requires & ~"],

	unlockhost: function (target, room, user) {
		if (!this.can('pban')) return false;
		if (!target) return this.parse('/help unlockhost');
		if (!Gold.lockedHosts[target]) return this.errorReply("The host '" + target + "' is not currently blacklisted.");

		delete Gold.lockedHosts[target];
		saveHost();

		this.privateModCommand("(" + user.name + " has unblacklisted host: " + target + ")");
	},
	unlockhosthelp: ["/unlockhost [host] - Removes a host from the server's blacklist.  Requires & ~"],

	proxylist: function (target, room, user) {
		if (!this.can('pban')) return false;
		let badHosts = Object.keys(Gold.lockedHosts);
		if (badHosts.length < 0) return this.errorReply("Weird, no proxies have been blacklisted (yet).");

		let buff = '

Proxy:	Blacklisted By:	Blacklisted:
';
		badHosts.forEach(proxy => {
			buff += '
' + proxy + '	' + Gold.nameColor(Gold.lockedHosts[proxy].by, false) + '	' + Chat.toDurationString(Date.now() - Gold.lockedHosts[proxy].on) + ' ago
';
		});
		buff += '
';

		return this.sendReplyBox(buff);
	},
};
<|endoftext|>


<commit_before>/**
 * Filters
 * Gold Server - http://gold.psim.us/
 *
 * Manually sets filters for chatting and names.
 * In this, we also handle proxy connections with a blacklist feature.
 * Credits: jd, panpawn
 *
 * @license MIT license
 */
'use strict';

const fs = require('fs');

let adWhitelist = Config.adWhitelist ? Config.adWhitelist : [];
let adRegex = new RegExp("(play.pokemonshowdown.com\\/~~)(?!(" + adWhitelist.join('|') + "))", "g");

let bannedMessages = Config.bannedMessages ? Config.bannedMessages : [];

let watchPhrases = Config.watchPhrases ? Config.watchPhrases : [];
let watchUsers = Config.watchUsers ? Config.watchUsers : [];

let proxyWhitelist = Config.proxyWhitelist || false;

/*********************
 * Chatfilter Magic *
 * ******************/

exports.chatfilter = function (message, user, room, connection, targetUser) {
	user.lastActiveTime = Date.now();
	if (!room && !Users(targetUser)) targetUser = {name: 'unknown user'};

	// watch phrases and watch users
	let watchRoom = Rooms('watchroom') ? Rooms('watchroom') : false;
	if (watchRoom) {
		let watchWords = watchPhrases.filter(phrase => { return ~toId(message).indexOf(phrase); }).length;
		let watchUserslist = watchUsers.filter(name => { return ~user.userid.indexOf(name); }).length;
		if (!user.can('hotpatch') && watchWords >= 1 || watchUserslist >= 1) {
			watchRoom.add('|c|' + user.getIdentity() + '| __(' + (room ? "To " + room.id : "Private message to " + targetUser.name) + ")__ " + message).update();
		}
	}

	// global banned messages
	for (let x in bannedMessages) {
		if (message.toLowerCase().indexOf(bannedMessages[x]) > -1 && bannedMessages[x] !== '' && message.substr(0, 1) !== '/') {
			if (user.locked) return false;
			Punishments.lock(user, Date.now() + 7 * 24 * 60 * 60 * 1000, null, "Said a banned word: " + bannedMessages[x]);
			user.popup('You have been automatically locked for sending a message containing a banned word.');
			Monitor.log('[PornMonitor] ' + user.name + ' __(' + (room ? 'In ' + room.id : 'Private message to ' + targetUser.name) + ')__ was automatically locked (and shadow banned) for trying to say "' + message + '"');
			fs.appendFile('logs/modlog/modlog_staff.txt', '[' + (new Date().toJSON()) + '] (staff) ' + user.name + ' was locked from talking by the Server (' +
			bannedMessages[x] + ') (' + connection.ip + ')\n');
			Gold.pmUpperStaff(user.name + ' has been automatically locked for sending a message containing a banned word' +
			(room ? ". **Room:**" + room.id : " in a private message to " + targetUser.name + ".") + ' **Message:** ' + message, '~Server');
			Users.ShadowBan.addUser(user);
		}
	}

	// advertising
	let pre_matches = (message.match(/psim|psim.us|psim us|psm.us|psm us/g) || []).length;
	let final_check = (pre_matches >= 1 ? adWhitelist.filter(server => { return ~message.indexOf(server); }).length : 0);
	if (!user.can('hotpatch') && (pre_matches >= 1 && final_check === 0 || pre_matches >= 2 && final_check >= 1 || message.match(adRegex))) {
		if (user.locked) return false;
		if (!Users.ShadowBan.checkBanned(user)) {
			Users.ShadowBan.addUser(user);
			fs.appendFile('logs/modlog/modlog_staff.txt', '[' + (new Date().toJSON()) + '] (staff) ' + user.name + ' was shadow banned by the Server. (Advertising) (' + connection.ip + ')\n');
			Gold.pmUpperStaff(user.name + " has been sbanned for attempting to advertise" + (room ? ". **Room:**" + room.id : " in a private message to " + targetUser.name + ".") + " **Message:** " + message, "~Server");
			Monitor.log("[AdvMonitor] " + user.name + " has been sbanned for attempting to advertise" + (room ? ". **Room:** " + room.id : " in a private message to " + targetUser.name + ".") + " **Message:** " + message);
		}
	}
	return message;
};
Config.chatfilter = exports.chatfilter;

/*********************
 * Namefilter Magic *
 * ******************/
try {
	Config.bannedNames = fs.readFileSync('config/bannednames.txt', 'utf8').toLowerCase().split('\n');
} catch (e) {
	Config.bannedNames = [];
}

function loadBannedNames() {
	try {
		Config.bannedNames = fs.readFileSync('config/bannednames.txt', 'utf8').toLowerCase().split('\n');
	} catch (e) {
		Config.bannedNames = [];
	}
}
loadBannedNames();

Config.namefilter = function (name, user) {
	let badNames = Config.bannedNames, badHosts = Object.keys(Gold.lockedHosts), nameId = toId(name);
	for (let x in badNames) {
		if (nameId.indexOf(badNames[x]) > -1 && badNames[x] !== '') {
			Monitor.log('[NameFilter] should probably FR: ' + name);
		}
	}

	// Hostfilter stuff
	let conNum = Object.keys(user.connections).length - 1;
	let ip = user.connections[conNum].ip;
	let trusted = trustedHack(nameId);

	Dnsbl.reverse(ip).then(host => {
		if (!host) return;
		if (badHosts.length < 0) return; // there are no blacklisted hosts (yet)

		// handling "trusted" users...
		if (trusted) return;
		if (Gold.userData[toId(name)] && Gold.userData[toId(name)].proxywhitelist) return;
		if (proxyWhitelist && proxyWhitelist.includes(nameId)) return;

		badHosts.forEach(badHost => {
			if (host.includes(badHost)) {
				user.locked = '#hostfilter';
				user.updateIdentity();
				user.popup("|modal|You have been automatically locked due to being on a proxy known for spam and abuse.\n\nLog off PS! and try reconnecting without a proxy to be unlocked.");
				// Monitor.log("[ProxyMonitor] " + name + " (" + ip + ") has been automatically locked. (" + host + ")");
				return;
			}
		});
	});

	if (user.connections[conNum].headers && user.connections[conNum].headers['user-agent']) {
		user.useragent = user.connections[conNum].headers['user-agent'];
	}

	Gold.evadeMonitor(user, name);

	return name;
};

// deal with global ranked user's manually...
function trustedHack(name) {
	let nameId = toId(name);
	let userSymbol = (Users.usergroups[nameId] ? Users.usergroups[nameId].substr(0, 1) : ' ');
	let rankIndex = (Config.groupsranking.includes(userSymbol) ? Config.groupsranking.indexOf(userSymbol) : false);
	if (rankIndex && rankIndex > 0) return true;
	return false;
}

/*********************
 * Hostfilter Magic *
 * ******************/
Gold.lockedHosts = Object.create(null);

function loadHostBlacklist() {
	fs.readFile('config/lockedhosts.json', 'utf8', function (err, file) {
		if (err) return;
		Gold.lockedHosts = JSON.parse(file);
	});
}
loadHostBlacklist();

function saveHost() {
	fs.writeFileSync('config/lockedhosts.json', JSON.stringify(Gold.lockedHosts));
}

Gold.evadeMonitor = function (user, name, punished) {
	if (punished && punished.alts) { // handles when user is unlocked
		punished.alts.forEach(alt => {
			if (Gold.punishments[toId(alt)]) delete Gold.punishments[toId(alt)];
		});
		Gold.savePunishments();
		return;
	}
	let points = 0;
	let matched = false;
	let userAgent = user.useragent ? user.useragent : '';
	let ip = user.latestIp;

	if (punished) {
		let tarId = toId(name); // since user.userid can be a guest number here...
		Object.keys(Gold.punishments).forEach(punished => {
			if (Gold.punishments[punished].ip === ip) matched = true;
		});
		if (!matched && !Gold.punishments[tarId]) {
			Gold.punishments[tarId] = {
				'useragent': userAgent,
				'ip': ip,
				'iprange': Gold.getIpRange(ip)[0],
				'ipclass': Gold.getIpRange(ip)[1],
				'type': punished.type,
				'exires': punished.expires,
			};
			Gold.savePunishments();
		}
	} else {
		if (user.locked || Users.ShadowBan.checkBanned(user) || trustedHack(name)) return;

		let reasons = [];
		let evader = '', offender = '';
		let alertStaff = false;
		let defaultAvatars = [1, 2, 101, 102, 169, 170, 265, 266];
		let punishedUsers = Object.keys(Gold.punishments);

		for (let i = 0; i < punishedUsers.length; i++) {
			offender = Gold.punishments[punishedUsers[i]];
			if (offender.ip === ip) break;
			if (reasons.length >= 3) break; // this should never happen
			if (offender.exires < Date.now()) {
				delete Gold.punishments[punishedUsers[i]];
				Gold.savePunishments();
			}
			if (offender.useragent && offender.useragent === userAgent) {
				points++;
				reasons.push(`have the same user agent`);
				evader = `${offender.type} user: ${punishedUsers[i]}`;
				alertStaff = true;
			}
			if (offender.iprange && ip.startsWith(offender.iprange)) {
				points++;
				reasons.push(`have the IPv4 class ${offender.ipclass} range (${offender.iprange}.*)`);
				evader = `${punishments[offender].type} user: ${punishedUsers[i]}`;
			}
			// this does not count AS a reason (points), but merely to add to the list of reasons
			if (defaultAvatars.includes(user.avatar)) {
				reasons.push(`have a default avatar`);
			}
		}
		let staff = Rooms('staff');
		if (staff) {
			if (points >= 2) {
				Users.ShadowBan.addUser(name);
				staff.add(`[EvadeMonitor] SHADOWBANNED: ${name}, evading alt of ${evader} because they ${reasons.join(' and ')}`).update();
			} else if (alertStaff) {
				staff.add(`[EvadeMonitor] SUSPECTED EVADER: ${name} is possibly an evading alt of ${evader} because they ${reasons.join(' and ')}.`).update();
			}
		}
	}
};

exports.commands = {
	lockhost: function (target, room, user) {
		if (!this.can('pban')) return false;
		if (!target) return this.parse('/help lockhost');
		if (Gold.lockedHosts[target]) return this.errorReply("The host '" + target + "' is was already locked by " + Gold.lockedHosts[target].by + ".");

		Gold.lockedHosts[target] = {
			by: user.name,
			on: Date.now(),
		};
		saveHost();

		this.privateModCommand("(" + user.name + " has blacklisted host: " + target + ")");
	},
	lockhosthelp: ["/lockhost [host] - Adds host to server blacklist.  Users connecting with these hosts will be automatically locked from connection, so use this carefully! Requires & ~"],

	unlockhost: function (target, room, user) {
		if (!this.can('pban')) return false;
		if (!target) return this.parse('/help unlockhost');
		if (!Gold.lockedHosts[target]) return this.errorReply("The host '" + target + "' is not currently blacklisted.");

		delete Gold.lockedHosts[target];
		saveHost();

		this.privateModCommand("(" + user.name + " has unblacklisted host: " + target + ")");
	},
	unlockhosthelp: ["/unlockhost [host] - Removes a host from the server's blacklist.  Requires & ~"],

	proxylist: function (target, room, user) {
		if (!this.can('pban')) return false;
		let badHosts = Object.keys(Gold.lockedHosts);
		if (badHosts.length < 0) return this.errorReply("Weird, no proxies have been blacklisted (yet).");

		let buff = '

Proxy:	Blacklisted By:	Blacklisted:
';
		badHosts.forEach(proxy => {
			buff += '
' + proxy + '	' + Gold.nameColor(Gold.lockedHosts[proxy].by, false) + '	' + Chat.toDurationString(Date.now() - Gold.lockedHosts[proxy].on) + ' ago
';
		});
		buff += '
';

		return this.sendReplyBox(buff);
	},
};
<commit_msg>Disable evade monitor for now<commit_after>/**
 * Filters
 * Gold Server - http://gold.psim.us/
 *
 * Manually sets filters for chatting and names.
 * In this, we also handle proxy connections with a blacklist feature.
 * Credits: jd, panpawn
 *
 * @license MIT license
 */
'use strict';

const fs = require('fs');

let adWhitelist = Config.adWhitelist ? Config.adWhitelist : [];
let adRegex = new RegExp("(play.pokemonshowdown.com\\/~~)(?!(" + adWhitelist.join('|') + "))", "g");

let bannedMessages = Config.bannedMessages ? Config.bannedMessages : [];

let watchPhrases = Config.watchPhrases ? Config.watchPhrases : [];
let watchUsers = Config.watchUsers ? Config.watchUsers : [];

let proxyWhitelist = Config.proxyWhitelist || false;

/*********************
 * Chatfilter Magic *
 * ******************/

exports.chatfilter = function (message, user, room, connection, targetUser) {
	user.lastActiveTime = Date.now();
	if (!room && !Users(targetUser)) targetUser = {name: 'unknown user'};

	// watch phrases and watch users
	let watchRoom = Rooms('watchroom') ? Rooms('watchroom') : false;
	if (watchRoom) {
		let watchWords = watchPhrases.filter(phrase => { return ~toId(message).indexOf(phrase); }).length;
		let watchUserslist = watchUsers.filter(name => { return ~user.userid.indexOf(name); }).length;
		if (!user.can('hotpatch') && watchWords >= 1 || watchUserslist >= 1) {
			watchRoom.add('|c|' + user.getIdentity() + '| __(' + (room ? "To " + room.id : "Private message to " + targetUser.name) + ")__ " + message).update();
		}
	}

	// global banned messages
	for (let x in bannedMessages) {
		if (message.toLowerCase().indexOf(bannedMessages[x]) > -1 && bannedMessages[x] !== '' && message.substr(0, 1) !== '/') {
			if (user.locked) return false;
			Punishments.lock(user, Date.now() + 7 * 24 * 60 * 60 * 1000, null, "Said a banned word: " + bannedMessages[x]);
			user.popup('You have been automatically locked for sending a message containing a banned word.');
			Monitor.log('[PornMonitor] ' + user.name + ' __(' + (room ? 'In ' + room.id : 'Private message to ' + targetUser.name) + ')__ was automatically locked (and shadow banned) for trying to say "' + message + '"');
			fs.appendFile('logs/modlog/modlog_staff.txt', '[' + (new Date().toJSON()) + '] (staff) ' + user.name + ' was locked from talking by the Server (' +
			bannedMessages[x] + ') (' + connection.ip + ')\n');
			Gold.pmUpperStaff(user.name + ' has been automatically locked for sending a message containing a banned word' +
			(room ? ". **Room:**" + room.id : " in a private message to " + targetUser.name + ".") + ' **Message:** ' + message, '~Server');
			Users.ShadowBan.addUser(user);
		}
	}

	// advertising
	let pre_matches = (message.match(/psim|psim.us|psim us|psm.us|psm us/g) || []).length;
	let final_check = (pre_matches >= 1 ? adWhitelist.filter(server => { return ~message.indexOf(server); }).length : 0);
	if (!user.can('hotpatch') && (pre_matches >= 1 && final_check === 0 || pre_matches >= 2 && final_check >= 1 || message.match(adRegex))) {
		if (user.locked) return false;
		if (!Users.ShadowBan.checkBanned(user)) {
			Users.ShadowBan.addUser(user);
			fs.appendFile('logs/modlog/modlog_staff.txt', '[' + (new Date().toJSON()) + '] (staff) ' + user.name + ' was shadow banned by the Server. (Advertising) (' + connection.ip + ')\n');
			Gold.pmUpperStaff(user.name + " has been sbanned for attempting to advertise" + (room ? ". **Room:**" + room.id : " in a private message to " + targetUser.name + ".") + " **Message:** " + message, "~Server");
			Monitor.log("[AdvMonitor] " + user.name + " has been sbanned for attempting to advertise" + (room ? ". **Room:** " + room.id : " in a private message to " + targetUser.name + ".") + " **Message:** " + message);
		}
	}
	return message;
};
Config.chatfilter = exports.chatfilter;

/*********************
 * Namefilter Magic *
 * ******************/
try {
	Config.bannedNames = fs.readFileSync('config/bannednames.txt', 'utf8').toLowerCase().split('\n');
} catch (e) {
	Config.bannedNames = [];
}

function loadBannedNames() {
	try {
		Config.bannedNames = fs.readFileSync('config/bannednames.txt', 'utf8').toLowerCase().split('\n');
	} catch (e) {
		Config.bannedNames = [];
	}
}
loadBannedNames();

Config.namefilter = function (name, user) {
	let badNames = Config.bannedNames, badHosts = Object.keys(Gold.lockedHosts), nameId = toId(name);
	for (let x in badNames) {
		if (nameId.indexOf(badNames[x]) > -1 && badNames[x] !== '') {
			Monitor.log('[NameFilter] should probably FR: ' + name);
		}
	}

	// Hostfilter stuff
	let conNum = Object.keys(user.connections).length - 1;
	let ip = user.connections[conNum].ip;
	let trusted = trustedHack(nameId);

	Dnsbl.reverse(ip).then(host => {
		if (!host) return;
		if (badHosts.length < 0) return; // there are no blacklisted hosts (yet)

		// handling "trusted" users...
		if (trusted) return;
		if (Gold.userData[toId(name)] && Gold.userData[toId(name)].proxywhitelist) return;
		if (proxyWhitelist && proxyWhitelist.includes(nameId)) return;

		badHosts.forEach(badHost => {
			if (host.includes(badHost)) {
				user.locked = '#hostfilter';
				user.updateIdentity();
				user.popup("|modal|You have been automatically locked due to being on a proxy known for spam and abuse.\n\nLog off PS! and try reconnecting without a proxy to be unlocked.");
				// Monitor.log("[ProxyMonitor] " + name + " (" + ip + ") has been automatically locked. (" + host + ")");
				return;
			}
		});
	});

	if (user.connections[conNum].headers && user.connections[conNum].headers['user-agent']) {
		user.useragent = user.connections[conNum].headers['user-agent'];
	}

	// Gold.evadeMonitor(user, name);

	return name;
};

// deal with global ranked user's manually...
function trustedHack(name) {
	let nameId = toId(name);
	let userSymbol = (Users.usergroups[nameId] ? Users.usergroups[nameId].substr(0, 1) : ' ');
	let rankIndex = (Config.groupsranking.includes(userSymbol) ? Config.groupsranking.indexOf(userSymbol) : false);
	if (rankIndex && rankIndex > 0) return true;
	return false;
}

/*********************
 * Hostfilter Magic *
 * ******************/
Gold.lockedHosts = Object.create(null);

function loadHostBlacklist() {
	fs.readFile('config/lockedhosts.json', 'utf8', function (err, file) {
		if (err) return;
		Gold.lockedHosts = JSON.parse(file);
	});
}
loadHostBlacklist();

function saveHost() {
	fs.writeFileSync('config/lockedhosts.json', JSON.stringify(Gold.lockedHosts));
}

Gold.evadeMonitor = function (user, name, punished) {
	if (punished && punished.alts) { // handles when user is unlocked
		punished.alts.forEach(alt => {
			if (Gold.punishments[toId(alt)]) delete Gold.punishments[toId(alt)];
		});
		Gold.savePunishments();
		return;
	}
	let points = 0;
	let matched = false;
	let userAgent = user.useragent ? user.useragent : '';
	let ip = user.latestIp;

	if (punished) {
		let tarId = toId(name); // since user.userid can be a guest number here...
		Object.keys(Gold.punishments).forEach(punished => {
			if (Gold.punishments[punished].ip === ip) matched = true;
		});
		if (!matched && !Gold.punishments[tarId]) {
			Gold.punishments[tarId] = {
				'useragent': userAgent,
				'ip': ip,
				'iprange': Gold.getIpRange(ip)[0],
				'ipclass': Gold.getIpRange(ip)[1],
				'type': punished.type,
				'exires': punished.expires,
			};
			Gold.savePunishments();
		}
	} else {
		if (user.locked || Users.ShadowBan.checkBanned(user) || trustedHack(name)) return;

		let reasons = [];
		let evader = '', offender = '';
		let alertStaff = false;
		let defaultAvatars = [1, 2, 101, 102, 169, 170, 265, 266];
		let punishedUsers = Object.keys(Gold.punishments);

		for (let i = 0; i < punishedUsers.length; i++) {
			offender = Gold.punishments[punishedUsers[i]];
			if (offender.ip === ip) break;
			if (reasons.length >= 3) break; // this should never happen
			if (offender.exires < Date.now()) {
				delete Gold.punishments[punishedUsers[i]];
				Gold.savePunishments();
			}
			if (offender.useragent && offender.useragent === userAgent) {
				points++;
				reasons.push(`have the same user agent`);
				evader = `${offender.type} user: ${punishedUsers[i]}`;
				alertStaff = true;
			}
			if (offender.iprange && ip.startsWith(offender.iprange)) {
				points++;
				reasons.push(`have the IPv4 class ${offender.ipclass} range (${offender.iprange}.*)`);
				evader = `${punishments[offender].type} user: ${punishedUsers[i]}`;
			}
			// this does not count AS a reason (points), but merely to add to the list of reasons
			if (defaultAvatars.includes(user.avatar)) {
				reasons.push(`have a default avatar`);
			}
		}
		let staff = Rooms('staff');
		if (staff) {
			if (points >= 2) {
				Users.ShadowBan.addUser(name);
				staff.add(`[EvadeMonitor] SHADOWBANNED: ${name}, evading alt of ${evader} because they ${reasons.join(' and ')}`).update();
			} else if (alertStaff) {
				staff.add(`[EvadeMonitor] SUSPECTED EVADER: ${name} is possibly an evading alt of ${evader} because they ${reasons.join(' and ')}.`).update();
			}
		}
	}
};

exports.commands = {
	lockhost: function (target, room, user) {
		if (!this.can('pban')) return false;
		if (!target) return this.parse('/help lockhost');
		if (Gold.lockedHosts[target]) return this.errorReply("The host '" + target + "' is was already locked by " + Gold.lockedHosts[target].by + ".");

		Gold.lockedHosts[target] = {
			by: user.name,
			on: Date.now(),
		};
		saveHost();

		this.privateModCommand("(" + user.name + " has blacklisted host: " + target + ")");
	},
	lockhosthelp: ["/lockhost [host] - Adds host to server blacklist.  Users connecting with these hosts will be automatically locked from connection, so use this carefully! Requires & ~"],

	unlockhost: function (target, room, user) {
		if (!this.can('pban')) return false;
		if (!target) return this.parse('/help unlockhost');
		if (!Gold.lockedHosts[target]) return this.errorReply("The host '" + target + "' is not currently blacklisted.");

		delete Gold.lockedHosts[target];
		saveHost();

		this.privateModCommand("(" + user.name + " has unblacklisted host: " + target + ")");
	},
	unlockhosthelp: ["/unlockhost [host] - Removes a host from the server's blacklist.  Requires & ~"],

	proxylist: function (target, room, user) {
		if (!this.can('pban')) return false;
		let badHosts = Object.keys(Gold.lockedHosts);
		if (badHosts.length < 0) return this.errorReply("Weird, no proxies have been blacklisted (yet).");

		let buff = '

Proxy:	Blacklisted By:	Blacklisted:
';
		badHosts.forEach(proxy => {
			buff += '
' + proxy + '	' + Gold.nameColor(Gold.lockedHosts[proxy].by, false) + '	' + Chat.toDurationString(Date.now() - Gold.lockedHosts[proxy].on) + ' ago
';
		});
		buff += '
';

		return this.sendReplyBox(buff);
	},
};
<|endoftext|>


<commit_before>import { updateChildNodes } from "../core/dom.js";
import { fragmentFrom } from "../core/htmlLiterals.js";
import { replace, transmute } from "../core/template.js";
import CursorAPIMixin from "./CursorAPIMixin.js";
import DelegateInputLabelMixin from "./DelegateInputLabelMixin.js";
import FormElementMixin from "./FormElementMixin.js";
import {
  defaultState,
  ids,
  inputDelegate,
  render,
  rendered,
  setState,
  shadowRoot,
  state,
  stateEffects,
  template,
} from "./internal.js";
import ItemsAPIMixin from "./ItemsAPIMixin.js";
import ItemsCursorMixin from "./ItemsCursorMixin.js";
import PopupButton from "./PopupButton.js";
import PopupSelectMixin from "./PopupSelectMixin.js";
import SelectedTextAPIMixin from "./SelectedTextAPIMixin.js";
import SelectedValueAPIMixin from "./SelectedValueAPIMixin.js";
import SingleSelectAPIMixin from "./SingleSelectAPIMixin.js";
import SlotItemsMixin from "./SlotItemsMixin.js";

const Base = CursorAPIMixin(
  DelegateInputLabelMixin(
    FormElementMixin(
      ItemsAPIMixin(
        ItemsCursorMixin(
          PopupSelectMixin(
            SelectedTextAPIMixin(
              SelectedValueAPIMixin(
                SingleSelectAPIMixin(SlotItemsMixin(PopupButton))
              )
            )
          )
        )
      )
    )
  )
);

/**
 * Shows a single choice made from a pop-up list of choices
 *
 * @inherits PopupButton
 * @mixes CursorAPIMixin
 * @mixes DelegateInputLabelMixin
 * @mixes FormElementMixin
 * @mixes ItemsAPIMixin
 * @mixes ItemsCursorMixin
 * @mixes PopupSelectMixin
 * @mixes SelectedTextAPIMixin
 * @mixes SelectedValueAPIMixin
 * @mixes SingleSelectAPIMixin
 * @mixes SlotItemsMixin
 *
 * @part {div} list - the list shown in the popup
 * @part down-icon - the icon shown in the toggle if the popup will open or close in the down direction
 * @part up-icon - the icon shown in the toggle if the popup will open or close in the up direction
 * @part {div} value - region inside the toggle button showing the value of the current selection
 */
class DropdownList extends Base {
  get [defaultState]() {
    return Object.assign(super[defaultState], {
      accessibleOptions: null,
      ariaHasPopup: "listbox",
      currentItemRequired: true,
      listPartType: "div",
      popupList: null,
      selectedIndex: -1,
      selectedItem: null,
      valuePartType: "div",
    });
  }

  get [inputDelegate]() {
    return this[ids].source;
  }

  get items() {
    /** @type {any} */
    const list = this[ids] && this[ids].list;
    return list ? list.items : null;
  }

  /**
   * The class or tag used to define the `list` part – the element
   * presenting the list items and handling navigation between them.
   *
   * @type {PartDescriptor}
   * @default List
   */
  get listPartType() {
    return this[state].listPartType;
  }
  set listPartType(listPartType) {
    this[setState]({ listPartType });
  }

  [render](/** @type {ChangedFlags} */ changed) {
    super[render](changed);

    renderParts(this[shadowRoot], this[state], changed);

    // Update selection.
    if (changed.items || changed.selectedIndex) {
      const { items, selectedIndex } = this[state];
      const selectedItem = items ? items[selectedIndex] : null;

      // Show selection in value part.
      const clone = selectedItem ? selectedItem.cloneNode(true) : null;
      const childNodes = clone ? clone.childNodes : [];
      updateChildNodes(this[ids].value, childNodes);

      // Mark only the selected item as selected.
      if (items) {
        items.forEach((/** @type {any} */ item) => {
          if ("selected" in item) {
            item.selected = item === selectedItem;
          }
        });
      }
    }

    // The popup's current item is represented in the visible list.
    if (changed.popupCurrentIndex) {
      const { popupCurrentIndex } = this[state];
      const list = /** @type {any} */ (this[ids].list);
      if ("currentIndex" in list) {
        list.currentIndex = popupCurrentIndex;
      }
    }
  }

  [rendered](changed) {
    super[rendered](changed);

    // Indicate which component is the popup's list.
    if (changed.listPartType) {
      this[setState]({
        popupList: this[ids].list,
      });
    }
  }

  [stateEffects](state, changed) {
    const effects = super[stateEffects](state, changed);

    // Create accessible items for dropdown list.
    if (changed.items) {
      const items = state.items || [];
      const accessibleOptions = items.map((item) => {
        const option = document.createElement("option");
        option.textContent = item.textContent; // TODO getItemText
        return option;
      });
      Object.assign(effects, { accessibleOptions });
    }

    // When opening the popup, by default (re)select the current item.
    if (changed.opened && state.opened) {
      Object.assign(effects, {
        popupCurrentIndex: state.selectedIndex,
      });
    }

    // When the popup closes, update our selection from the list selection.
    if (changed.opened && !state.opened) {
      const { closeResult, items } = state;
      if (items && closeResult !== undefined) {
        const selectedIndex = items.indexOf(closeResult);
        Object.assign(effects, {
          selectedIndex,
        });
      }
    }

    // If we get items and don't have a selection, select the first item.
    if (changed.items || changed.selectedIndex) {
      const { items, selectedIndex } = state;
      if (selectedIndex < 0 && items && items.length > 0) {
        Object.assign(effects, {
          selectedIndex: 0,
        });
      }
    }

    return effects;
  }

  get [template]() {
    const result = super[template];

    // Replace the source slot with an element to show the value.
    const sourceSlot = result.content.querySelector('slot[name="source"]');
    if (sourceSlot) {
      replace(
        sourceSlot,
        fragmentFrom.html` <div id="value" part="value"></div> `
      );
    }

    // Wrap default slot with a list.
    const defaultSlot = result.content.querySelector("slot:not([name])");
    if (defaultSlot) {
      defaultSlot.replaceWith(fragmentFrom.html`

          <slot></slot>

      `);
    }

    // Connect the source to the accessible list.
    const source = result.content.querySelector('[part~="source"]');
    if (source) {
      source.setAttribute("aria-activedescendant", "value");
      source.setAttribute("aria-autocomplete", "none");
      source.setAttribute("aria-controls", "accessibleListPlaceholder");
      source.role = "combobox";
    }

    renderParts(result.content, this[state]);

    // Add styling, plus an invisible div that can serve as a placeholder for an
    // accessible listbox that the combo box can use in "aria-controls".
    result.content.append(fragmentFrom.html`

    `);

    return result;
  }

  /**
   * The class or tag used to create the `value` part - the region
   * showing the dropdown list's current value.
   *
   * @type {PartDescriptor}
   * @default 'div'
   */
  get valuePartType() {
    return this[state].valuePartType;
  }
  set valuePartType(valuePartType) {
    this[setState]({ valuePartType });
  }
}

/**
 * Render parts for the template or an instance.
 *
 * @private
 * @param {DocumentFragment} root
 * @param {PlainObject} state
 * @param {ChangedFlags} [changed]
 */
function renderParts(root, state, changed) {
  if (!changed || changed.listPartType) {
    const { listPartType } = state;
    const list = root.getElementById("list");
    if (list) {
      transmute(list, listPartType);
    }
  }
  if (!changed || changed.valuePartType) {
    const { valuePartType } = state;
    const value = root.getElementById("value");
    if (value) {
      transmute(value, valuePartType);
    }
  }
}

export default DropdownList;
<commit_msg>Use actual list as target of aria-controls.<commit_after>import { updateChildNodes } from "../core/dom.js";
import { fragmentFrom } from "../core/htmlLiterals.js";
import { replace, transmute } from "../core/template.js";
import CursorAPIMixin from "./CursorAPIMixin.js";
import DelegateInputLabelMixin from "./DelegateInputLabelMixin.js";
import FormElementMixin from "./FormElementMixin.js";
import {
  defaultState,
  ids,
  inputDelegate,
  render,
  rendered,
  setState,
  shadowRoot,
  state,
  stateEffects,
  template,
} from "./internal.js";
import ItemsAPIMixin from "./ItemsAPIMixin.js";
import ItemsCursorMixin from "./ItemsCursorMixin.js";
import PopupButton from "./PopupButton.js";
import PopupSelectMixin from "./PopupSelectMixin.js";
import SelectedTextAPIMixin from "./SelectedTextAPIMixin.js";
import SelectedValueAPIMixin from "./SelectedValueAPIMixin.js";
import SingleSelectAPIMixin from "./SingleSelectAPIMixin.js";
import SlotItemsMixin from "./SlotItemsMixin.js";

const Base = CursorAPIMixin(
  DelegateInputLabelMixin(
    FormElementMixin(
      ItemsAPIMixin(
        ItemsCursorMixin(
          PopupSelectMixin(
            SelectedTextAPIMixin(
              SelectedValueAPIMixin(
                SingleSelectAPIMixin(SlotItemsMixin(PopupButton))
              )
            )
          )
        )
      )
    )
  )
);

/**
 * Shows a single choice made from a pop-up list of choices
 *
 * @inherits PopupButton
 * @mixes CursorAPIMixin
 * @mixes DelegateInputLabelMixin
 * @mixes FormElementMixin
 * @mixes ItemsAPIMixin
 * @mixes ItemsCursorMixin
 * @mixes PopupSelectMixin
 * @mixes SelectedTextAPIMixin
 * @mixes SelectedValueAPIMixin
 * @mixes SingleSelectAPIMixin
 * @mixes SlotItemsMixin
 *
 * @part {div} list - the list shown in the popup
 * @part down-icon - the icon shown in the toggle if the popup will open or close in the down direction
 * @part up-icon - the icon shown in the toggle if the popup will open or close in the up direction
 * @part {div} value - region inside the toggle button showing the value of the current selection
 */
class DropdownList extends Base {
  get [defaultState]() {
    return Object.assign(super[defaultState], {
      accessibleOptions: null,
      ariaHasPopup: "listbox",
      currentItemRequired: true,
      listPartType: "div",
      popupList: null,
      selectedIndex: -1,
      selectedItem: null,
      valuePartType: "div",
    });
  }

  get [inputDelegate]() {
    return this[ids].source;
  }

  get items() {
    /** @type {any} */
    const list = this[ids] && this[ids].list;
    return list ? list.items : null;
  }

  /**
   * The class or tag used to define the `list` part – the element
   * presenting the list items and handling navigation between them.
   *
   * @type {PartDescriptor}
   * @default List
   */
  get listPartType() {
    return this[state].listPartType;
  }
  set listPartType(listPartType) {
    this[setState]({ listPartType });
  }

  [render](/** @type {ChangedFlags} */ changed) {
    super[render](changed);

    renderParts(this[shadowRoot], this[state], changed);

    // Update selection.
    if (changed.items || changed.selectedIndex) {
      const { items, selectedIndex } = this[state];
      const selectedItem = items ? items[selectedIndex] : null;

      // Show selection in value part.
      const clone = selectedItem ? selectedItem.cloneNode(true) : null;
      const childNodes = clone ? clone.childNodes : [];
      updateChildNodes(this[ids].value, childNodes);

      // Mark only the selected item as selected.
      if (items) {
        items.forEach((/** @type {any} */ item) => {
          if ("selected" in item) {
            item.selected = item === selectedItem;
          }
        });
      }
    }

    // The popup's current item is represented in the visible list.
    if (changed.popupCurrentIndex) {
      const { popupCurrentIndex } = this[state];
      const list = /** @type {any} */ (this[ids].list);
      if ("currentIndex" in list) {
        list.currentIndex = popupCurrentIndex;
      }
    }
  }

  [rendered](changed) {
    super[rendered](changed);

    // Indicate which component is the popup's list.
    if (changed.listPartType) {
      this[setState]({
        popupList: this[ids].list,
      });
    }
  }

  [stateEffects](state, changed) {
    const effects = super[stateEffects](state, changed);

    // Create accessible items for dropdown list.
    if (changed.items) {
      const items = state.items || [];
      const accessibleOptions = items.map((item) => {
        const option = document.createElement("option");
        option.textContent = item.textContent; // TODO getItemText
        return option;
      });
      Object.assign(effects, { accessibleOptions });
    }

    // When opening the popup, by default (re)select the current item.
    if (changed.opened && state.opened) {
      Object.assign(effects, {
        popupCurrentIndex: state.selectedIndex,
      });
    }

    // When the popup closes, update our selection from the list selection.
    if (changed.opened && !state.opened) {
      const { closeResult, items } = state;
      if (items && closeResult !== undefined) {
        const selectedIndex = items.indexOf(closeResult);
        Object.assign(effects, {
          selectedIndex,
        });
      }
    }

    // If we get items and don't have a selection, select the first item.
    if (changed.items || changed.selectedIndex) {
      const { items, selectedIndex } = state;
      if (selectedIndex < 0 && items && items.length > 0) {
        Object.assign(effects, {
          selectedIndex: 0,
        });
      }
    }

    return effects;
  }

  get [template]() {
    const result = super[template];

    // Replace the source slot with an element to show the value.
    const sourceSlot = result.content.querySelector('slot[name="source"]');
    if (sourceSlot) {
      replace(
        sourceSlot,
        fragmentFrom.html` <div id="value" part="value"></div> `
      );
    }

    // Wrap default slot with a list.
    const defaultSlot = result.content.querySelector("slot:not([name])");
    if (defaultSlot) {
      defaultSlot.replaceWith(fragmentFrom.html`

          <slot></slot>

      `);
    }

    // Apply combobox semantics to the source button. Because focus moves to the
    // list itself when the popup opens, the aria-controls attribute has no
    // effect other than convincing the browser to announce the button as a
    // combobox.
    const source = result.content.querySelector('[part~="source"]');
    if (source) {
      source.setAttribute("aria-activedescendant", "value");
      source.setAttribute("aria-autocomplete", "none");
      source.setAttribute("aria-controls", "list");
      source.role = "combobox";
    }

    renderParts(result.content, this[state]);

    result.content.append(fragmentFrom.html`

    `);

    return result;
  }

  /**
   * The class or tag used to create the `value` part - the region
   * showing the dropdown list's current value.
   *
   * @type {PartDescriptor}
   * @default 'div'
   */
  get valuePartType() {
    return this[state].valuePartType;
  }
  set valuePartType(valuePartType) {
    this[setState]({ valuePartType });
  }
}

/**
 * Render parts for the template or an instance.
 *
 * @private
 * @param {DocumentFragment} root
 * @param {PlainObject} state
 * @param {ChangedFlags} [changed]
 */
function renderParts(root, state, changed) {
  if (!changed || changed.listPartType) {
    const { listPartType } = state;
    const list = root.getElementById("list");
    if (list) {
      transmute(list, listPartType);
    }
  }
  if (!changed || changed.valuePartType) {
    const { valuePartType } = state;
    const value = root.getElementById("value");
    if (value) {
      transmute(value, valuePartType);
    }
  }
}

export default DropdownList;
<|endoftext|>


<commit_before>class MCProjectSidenavComponentController {
    /*@ngInject*/
    constructor($state, mcprojstore, mcprojectstore2, $timeout, ProjectModel, projectFileTreeAPI, $mdDialog, mcRouteState, $q, User) {
        this.$state = $state;
        this.mcprojstore = mcprojstore;
        this.mcprojectstore2 = mcprojectstore2;
        this.experiment = null;
        this.$timeout = $timeout;
        this.ProjectModel = ProjectModel;
        this.projectFileTreeAPI = projectFileTreeAPI;
        this.$mdDialog = $mdDialog;
        this.mcRouteState = mcRouteState;
        this.User = User;
        this.$q = $q;
    }

    $onInit() {
        this.unsubscribe = this.mcprojstore.subscribe(this.mcprojstore.OTEXPERIMENT, this.mcprojstore.EVSET, (e) => {
            this.$timeout(() => {
                if (!e) {
                    this.experiment = null;
                    return;
                }

                if (!this.experiment) {
                    this.experiment = angular.copy(e);
                } else if (this.experiment.id !== e.id) {
                    this.experiment = angular.copy(e);
                }
            });
        });

        this.project = this.mcprojstore.currentProject;
        this.isBetaUser = this.User.isBetaUser();
    }

    loadProjectFiles() {
        if (!this.project.files) {
            return this.projectFileTreeAPI.getProjectRoot(this.project.id).then((files) => {
                return this.mcprojstore.updateCurrentProject(currentProject => {
                    this.project.files = files;
                    currentProject.files = this.project.files;
                    return currentProject;
                });
            });
        } else {
            return this.$q.resolve(null);
        }
    }

    $onDestroy() {
        this.unsubscribe();
    }

    refreshProject() {
        this.mcprojectstore2.reloadProject(this.project.id);
        this.ProjectModel.getProjectForCurrentUser(this.project.id).then((p) => this._updateProject(p));
    }

    _updateProject(project) {
        this.mcprojstore.updateCurrentProject((currentProject, transformers) => {
            let transformedExperiments = project.experiments.map(e => transformers.transformExperiment(e));
            project.experiments = _.indexBy(transformedExperiments, 'id');
            project.experimentsFullyLoaded = true;
            this.project = project;
            return project;
        }).then(
            () => {
                if (!this.project.files) {
                    this.projectFileTreeAPI.getProjectRoot(this.project.id).then((files) => {
                        this.mcprojstore.updateCurrentProject(currentProject => {
                            this.project.files = files;
                            currentProject.files = this.project.files;
                            return currentProject;
                        });
                    });
                }
            }
        );
    }

    setGlobusDownloadTransfer() {
        console.log("Reached setGlobusDownloadTransfer");
        this.$mdDialog.show({
            templateUrl: 'app/modals/globus-download-transfer-dialog.html',
            controller: GlobusDownloadTrasferDialogController,
            controllerAs: '$ctrl',
            bindToController: true,
            locals: {
                project: this.project,
            }}
        ).then(((results) =>  console.log("done")));
    }

    modifyShortcuts() {
        this.loadProjectFiles().then(
            () => {
                let dirs = [];
                if (this.project.files[0]) {
                    dirs = this.project.files[0].children.filter(d => d.data.otype === 'directory').map(d => d.data);
                }

                this.$mdDialog.show({
                    templateUrl: 'app/modals/modify-project-shortcuts-dialog.html',
                    controller: ModifyProjectShortcutsDialogController,
                    controllerAs: '$ctrl',
                    bindToController: true,
                    locals: {
                        project: this.project,
                        dirs: dirs
                    }
                }).then(
                    () => this.refreshProject()
                );
            });
    }

    isDatasetsRoute() {
        return this.mcRouteState.getRouteName().startsWith('project.experiment.datasets');
    }

    isProjectDatasetsRoute() {
        return this.mcRouteState.getRouteName().startsWith('project.datasets');
    }
}

class GlobusDownloadTrasferDialogController {
    /*@ngInject*/
    constructor($mdDialog, etlServerAPI) {
        this.$mdDialog = $mdDialog;
        this.etlServerAPI = etlServerAPI;
        this.requestComplete = false;
        this.globusUser = "username@globus.com";
        this.url = "";
    }

    submitToServer() {
        console.log("Submitting request to server: ", this.project.id, this.globusUser);
        this.etlServerAPI.
            setupGlobusDownloadTransfer(this.project.id, this.globusUser).
            then(globusResults => {
                console.log("Results returned from server: ", globusResults);
                if (globusResults && globusResults.url) {
                    this.url = globusResults.url;
                    this.requestComplete = true;
                }
        });
    }

    cancel() {
        this.$mdDialog.cancel();
    }
}

class ModifyProjectShortcutsDialogController {
    /*@ngInject*/
    constructor($mdDialog, projectShortcuts, projectsAPI, projectFileTreeAPI) {
        this.$mdDialog = $mdDialog;
        this.projectsAPI = projectsAPI;
        this.projectFileTreeAPI = projectFileTreeAPI;
        this.defaultShortcuts = projectShortcuts.defaultShortcutPaths(this.project.name, this.dirs);
        this.otherDirs = projectShortcuts.filterExistingDefaults(angular.copy(this.dirs));
        this.dirsMap = _.indexBy(this.dirs, 'path');
    }

    done() {
        this.defaultShortcuts.forEach(s => {
            if (s.path in this.dirsMap) {
                const d = this.dirsMap[s.path];
                if (s.shortcut) {
                    this.projectsAPI.createShortcut(this.project.id, d.id);
                } else if (!s.shortcut) {
                    this.projectsAPI.deleteShortcut(this.project.id, d.id);
                }
            } else if (s.shortcut) {
                this.projectFileTreeAPI.createProjectDir(this.project.id, this.project.files[0].data.id, s.name).then(
                    (dir) => {
                        this.projectsAPI.createShortcut(this.project.id, dir.id);
                    }
                );
            }
        });
        this.otherDirs.forEach(dir => {
            if (dir.shortcut) {
                this.projectsAPI.createShortcut(this.project.id, dir.id);
            } else if (!dir.shortcut) {
                this.projectsAPI.deleteShortcut(this.project.id, dir.id);
            }
        });
        let defaultShortcuts = this.defaultShortcuts.filter(s => s.shortcut);
        let dirShortcuts = this.otherDirs.filter(d => d.shortcut);
        let allShortcuts = defaultShortcuts.concat(dirShortcuts);
        this.$mdDialog.hide(allShortcuts);
    }

    cancel() {
        this.$mdDialog.cancel();
    }
}

angular.module('materialscommons').component('mcProjectSidenav', {
    template: require('./mc-project-sidenav.html'),
    controller: MCProjectSidenavComponentController
});
<commit_msg>Minor fix to remote 'defined but not used' error<commit_after>class MCProjectSidenavComponentController {
    /*@ngInject*/
    constructor($state, mcprojstore, mcprojectstore2, $timeout, ProjectModel, projectFileTreeAPI, $mdDialog, mcRouteState, $q, User) {
        this.$state = $state;
        this.mcprojstore = mcprojstore;
        this.mcprojectstore2 = mcprojectstore2;
        this.experiment = null;
        this.$timeout = $timeout;
        this.ProjectModel = ProjectModel;
        this.projectFileTreeAPI = projectFileTreeAPI;
        this.$mdDialog = $mdDialog;
        this.mcRouteState = mcRouteState;
        this.User = User;
        this.$q = $q;
    }

    $onInit() {
        this.unsubscribe = this.mcprojstore.subscribe(this.mcprojstore.OTEXPERIMENT, this.mcprojstore.EVSET, (e) => {
            this.$timeout(() => {
                if (!e) {
                    this.experiment = null;
                    return;
                }

                if (!this.experiment) {
                    this.experiment = angular.copy(e);
                } else if (this.experiment.id !== e.id) {
                    this.experiment = angular.copy(e);
                }
            });
        });

        this.project = this.mcprojstore.currentProject;
        this.isBetaUser = this.User.isBetaUser();
    }

    loadProjectFiles() {
        if (!this.project.files) {
            return this.projectFileTreeAPI.getProjectRoot(this.project.id).then((files) => {
                return this.mcprojstore.updateCurrentProject(currentProject => {
                    this.project.files = files;
                    currentProject.files = this.project.files;
                    return currentProject;
                });
            });
        } else {
            return this.$q.resolve(null);
        }
    }

    $onDestroy() {
        this.unsubscribe();
    }

    refreshProject() {
        this.mcprojectstore2.reloadProject(this.project.id);
        this.ProjectModel.getProjectForCurrentUser(this.project.id).then((p) => this._updateProject(p));
    }

    _updateProject(project) {
        this.mcprojstore.updateCurrentProject((currentProject, transformers) => {
            let transformedExperiments = project.experiments.map(e => transformers.transformExperiment(e));
            project.experiments = _.indexBy(transformedExperiments, 'id');
            project.experimentsFullyLoaded = true;
            this.project = project;
            return project;
        }).then(
            () => {
                if (!this.project.files) {
                    this.projectFileTreeAPI.getProjectRoot(this.project.id).then((files) => {
                        this.mcprojstore.updateCurrentProject(currentProject => {
                            this.project.files = files;
                            currentProject.files = this.project.files;
                            return currentProject;
                        });
                    });
                }
            }
        );
    }

    setGlobusDownloadTransfer() {
        console.log("Reached setGlobusDownloadTransfer");
        this.$mdDialog.show({
            templateUrl: 'app/modals/globus-download-transfer-dialog.html',
            controller: GlobusDownloadTrasferDialogController,
            controllerAs: '$ctrl',
            bindToController: true,
            locals: {
                project: this.project,
            }}
        ).then(((results) =>  console.log("done", results)));
    }

    modifyShortcuts() {
        this.loadProjectFiles().then(
            () => {
                let dirs = [];
                if (this.project.files[0]) {
                    dirs = this.project.files[0].children.filter(d => d.data.otype === 'directory').map(d => d.data);
                }

                this.$mdDialog.show({
                    templateUrl: 'app/modals/modify-project-shortcuts-dialog.html',
                    controller: ModifyProjectShortcutsDialogController,
                    controllerAs: '$ctrl',
                    bindToController: true,
                    locals: {
                        project: this.project,
                        dirs: dirs
                    }
                }).then(
                    () => this.refreshProject()
                );
            });
    }

    isDatasetsRoute() {
        return this.mcRouteState.getRouteName().startsWith('project.experiment.datasets');
    }

    isProjectDatasetsRoute() {
        return this.mcRouteState.getRouteName().startsWith('project.datasets');
    }
}

class GlobusDownloadTrasferDialogController {
    /*@ngInject*/
    constructor($mdDialog, etlServerAPI) {
        this.$mdDialog = $mdDialog;
        this.etlServerAPI = etlServerAPI;
        this.requestComplete = false;
        this.globusUser = "username@globus.com";
        this.url = "";
    }

    submitToServer() {
        console.log("Submitting request to server: ", this.project.id, this.globusUser);
        this.etlServerAPI.
            setupGlobusDownloadTransfer(this.project.id, this.globusUser).
            then(globusResults => {
                console.log("Results returned from server: ", globusResults);
                if (globusResults && globusResults.url) {
                    this.url = globusResults.url;
                    this.requestComplete = true;
                }
        });
    }

    cancel() {
        this.$mdDialog.cancel();
    }
}

class ModifyProjectShortcutsDialogController {
    /*@ngInject*/
    constructor($mdDialog, projectShortcuts, projectsAPI, projectFileTreeAPI) {
        this.$mdDialog = $mdDialog;
        this.projectsAPI = projectsAPI;
        this.projectFileTreeAPI = projectFileTreeAPI;
        this.defaultShortcuts = projectShortcuts.defaultShortcutPaths(this.project.name, this.dirs);
        this.otherDirs = projectShortcuts.filterExistingDefaults(angular.copy(this.dirs));
        this.dirsMap = _.indexBy(this.dirs, 'path');
    }

    done() {
        this.defaultShortcuts.forEach(s => {
            if (s.path in this.dirsMap) {
                const d = this.dirsMap[s.path];
                if (s.shortcut) {
                    this.projectsAPI.createShortcut(this.project.id, d.id);
                } else if (!s.shortcut) {
                    this.projectsAPI.deleteShortcut(this.project.id, d.id);
                }
            } else if (s.shortcut) {
                this.projectFileTreeAPI.createProjectDir(this.project.id, this.project.files[0].data.id, s.name).then(
                    (dir) => {
                        this.projectsAPI.createShortcut(this.project.id, dir.id);
                    }
                );
            }
        });
        this.otherDirs.forEach(dir => {
            if (dir.shortcut) {
                this.projectsAPI.createShortcut(this.project.id, dir.id);
            } else if (!dir.shortcut) {
                this.projectsAPI.deleteShortcut(this.project.id, dir.id);
            }
        });
        let defaultShortcuts = this.defaultShortcuts.filter(s => s.shortcut);
        let dirShortcuts = this.otherDirs.filter(d => d.shortcut);
        let allShortcuts = defaultShortcuts.concat(dirShortcuts);
        this.$mdDialog.hide(allShortcuts);
    }

    cancel() {
        this.$mdDialog.cancel();
    }
}

angular.module('materialscommons').component('mcProjectSidenav', {
    template: require('./mc-project-sidenav.html'),
    controller: MCProjectSidenavComponentController
});
<|endoftext|>


<commit_before>(function () {
  'use strict';

  angular
    .module('app')
    .controller('StratSectionTabController', StratSectionTabController);

  StratSectionTabController.$inject = ['$ionicModal', '$ionicPopup', '$log', '$scope', '$state', 'FormFactory',
    'HelpersFactory', 'SpotFactory', 'StratSectionFactory'];

  function StratSectionTabController($ionicModal, $ionicPopup, $log, $scope, $state, FormFactory, HelpersFactory,
                                     SpotFactory, StratSectionFactory) {
    var vm = this;
    var vmParent = $scope.vm;

    var isEdit = false;
    var thisTabName = 'strat-section';

    vm.addOverlayImageModal = {};
    vm.data = {};
    vm.showStratSection = false;

    vm.addOverlayImage = addOverlayImage;
    vm.changeColumnProfile = changeColumnProfile;
    vm.changeUnits = changeUnits;
    vm.deleteOverlayImage = deleteOverlayImage;
    vm.editOverlayImage = editOverlayImage;
    vm.getImageLabel = getImageLabel;
    vm.resizeImage = resizeImage;
    vm.saveOverlayImage = saveOverlayImage;
    vm.toggleStratSection = toggleStratSection;
    vm.viewStratSection = viewStratSection;

    activate();

    /**
     * Private Functions
     */

    function activate() {
      $log.log('In StratSectionTabController');

      // Loading tab from Spots list
      if ($state.current.name === 'app.spotTab.' + thisTabName) loadTab($state);
      // Loading tab in Map side panel
      $scope.$on('load-tab', function (event, args) {
        if (args.tabName === thisTabName) {
          vmParent.saveSpot().finally(function () {
            vmParent.spotChanged = false;
            loadTab({
              'current': {'name': 'app.spotTab.' + thisTabName},
              'params': {'spotId': args.spotId}
            });
          });
        }
      });
      createModals();
    }

    function createModals() {
      $ionicModal.fromTemplateUrl('app/spot/sed/strat-section/add-overlay-image-modal.html', {
        'scope': $scope,
        'animation': 'slide-in-up',
        'backdropClickToClose': false,
        'hardwareBackButtonClose': false
      }).then(function (modal) {
        vm.addOverlayImageModal = modal;
      });

      // Cleanup the modals when we're done with it!
      $scope.$on('$destroy', function () {
        vm.addOverlayImageModal.remove();
      });
    }

    function loadTab(state) {
      vmParent.loadTab(state);     // Need to load current state into parent
      vmParent.child = vm;
      FormFactory.setForm('sed', 'strat_section');
      if (vmParent.spot.properties.sed && vmParent.spot.properties.sed.strat_section) {
        $log.log('Strat Section:', vmParent.spot.properties.sed.strat_section);
        vmParent.data = vmParent.spot.properties.sed.strat_section;
        vm.showStratSection = true;
      }
      else vmParent.data = {};
    }

    /**
     * Public Functions
     */

    function addOverlayImage() {
      vm.data = {};
      isEdit = false;
      vm.addOverlayImageModal.show();
    }

    function changeColumnProfile(newProfile, oldProfile) {
      var stratSectionId = vmParent.spot.properties.sed.strat_section.strat_section_id;
      if (!newProfile) vmParent.spot.properties.sed.strat_section.column_profile = oldProfile;
      else if (((oldProfile === 'clastic' || oldProfile === 'carbonate' || oldProfile === 'mixed_clastic') &&
        newProfile === 'weathering_pro') || (oldProfile === 'weathering_pro' &&
        (newProfile === 'clastic' || newProfile === 'carbonate' || newProfile === 'mixed_clastic'))) {
        var confirmPopup = $ionicPopup.confirm({
          'title': 'Column Profile Change Warning!',
          'template': 'Changing the column profile will redraw all intervals as rectangles so any drawing edits to ' +
          'current interval shapes will be lost. Continue anyway?'
        });
        confirmPopup.then(function (res) {
          if (res) {
            SpotFactory.save(vmParent.spot).then(function () {
              StratSectionFactory.changedColumnProfile(stratSectionId);
            });
          }
          else vmParent.spot.properties.sed.strat_section.column_profile = oldProfile;
        });

      }
    }

    function changeUnits(newUnits, oldUnits) {
      var stratSectionId = vmParent.spot.properties.sed.strat_section.strat_section_id;
      var stratSectionIntervals = StratSectionFactory.getStratSectionIntervals(stratSectionId);
      if (!_.isEmpty(stratSectionIntervals)) {
        $ionicPopup.alert({
          'title': 'Units Change Error!',
          'template': 'Units for this Section have already been set at ' + oldUnits + '. Please start a new' +
          ' Section and set your desired units before adding intervals or delete the intervals in this Section.'
        });
        vmParent.spot.properties.sed.strat_section.column_y_axis_units = oldUnits;
      }

      /*var confirmPopup = $ionicPopup.confirm({
        'title': 'Units Change!',
        'template': 'Changing the units for this Strat Section will also convert the interval thickness ' +
        'for all intervals in this Strat Section to use the same units. Are you sure you want to continue?'
      });
      confirmPopup.then(function (res) {
        if (res) {
          StratSectionFactory.gatherStratSectionSpots(vmParent.spot.properties.sed.strat_section.strat_section_id);
          var spots = StratSectionFactory.getStratSectionSpots();
          // Separate the Strat Section Spots into the Interval Spots and other Spots
          var stratSectionSpotsPartitioned = _.partition(spots, function (spot) {
            return spot.properties.surface_feature &&
              spot.properties.surface_feature.surface_feature_type === 'strat_interval';
          });
          var stratSectionIntervals = stratSectionSpotsPartitioned[0];
          _.each(stratSectionIntervals, function (stratSectionInterval) {
            if (stratSectionInterval.properties.sed && stratSectionInterval.properties.sed.lithologies) {
              var thickness = stratSectionInterval.properties.sed.lithologies.interval_thickness;
              if (oldUnits === 'cm' && newUnits === 'm') thickness = thickness / 100;             // cm to m
              else if (oldUnits === 'cm' && newUnits === 'in') thickness = thickness / 2.54;      // cm to in
              else if (oldUnits === 'cm' && newUnits === 'ft') thickness = thickness / 30.48;     // cm to ft
              else if (oldUnits === 'm' && newUnits === 'cm') thickness = thickness * 100;        // m to cm
              else if (oldUnits === 'm' && newUnits === 'in') thickness = thickness / 0.0254;     // m to in
              else if (oldUnits === 'm' && newUnits === 'ft') thickness = thickness / 0.3048;     // m to ft
              else if (oldUnits === 'in' && newUnits === 'cm') thickness = thickness * 2.54;      // in to cm
              else if (oldUnits === 'in' && newUnits === 'm') thickness = thickness * 0.0254;     // in to m
              else if (oldUnits === 'in' && newUnits === 'ft') thickness = thickness / 12;        // in to ft
              else if (oldUnits === 'ft' && newUnits === 'cm') thickness = thickness * 30.48;     // ft to cm
              else if (oldUnits === 'ft' && newUnits === 'm') thickness = thickness * 0.3048;     // ft to m
              else if (oldUnits === 'ft' && newUnits === 'in') thickness = thickness * 12;        // ft to in
              thickness = HelpersFactory.roundToDecimalPlaces(thickness, 2);
              stratSectionInterval.properties.sed.lithologies.interval_thickness = thickness;
              stratSectionInterval.properties.sed.lithologies.thickness_units = newUnits;
              stratSectionInterval = StratSectionFactory.checkForIntervalUpdates('app.spotTab.sed-lithologies', stratSectionInterval);
              SpotFactory.save(stratSectionInterval);
            }
          });
        }
        else vmParent.spot.properties.sed.strat_section.column_y_axis_units = oldUnits;
      });*/
    }

    function deleteOverlayImage(imageToDelete) {
      var confirmPopup = $ionicPopup.confirm({
        'title': 'Remove Overlay',
        'template': 'Are you sure you want to remove this image as an overlay on this Strat Section? ' +
        'This will not delete the image itself.'
      });
      confirmPopup.then(function (res) {
        if (res) {
          vmParent.spot.properties.sed.strat_section.images = _.reject(
            vmParent.spot.properties.sed.strat_section.images,
            function (image) {
              return image.id === imageToDelete.id;
            });
          if (vmParent.spot.properties.sed.strat_section.images.length === 0) {
            delete vmParent.spot.properties.sed.strat_section.images;
          }
        }
      });
    }

    function editOverlayImage(image) {
      vm.data = image;
      isEdit = true;
      vm.addOverlayImageModal.show();
    }

    function getImageLabel(id) {
      var image = _.find(vmParent.spot.properties.images, function (image) {
        return id === image.id;
      });
      return image && image.title ? image.title : 'Untitled';
    }

    // Resize image preserving image ratio
    function resizeImage(dim) {
      var image = _.find(vmParent.spot.properties.images, function (image) {
        return vm.data.id === image.id;
      });
      if (dim === 'w' && vm.data.image_width) {
        vm.data.image_height =
          Math.round(image.height / image.width * vm.data.image_width);
      }
      else if (dim === 'h' && vm.data.image_height) {
        vm.data.image_width =
          Math.round(image.width / image.height * vm.data.image_height);
      }
      else {
        delete vm.data.image_height;
        delete vm.data.image_width;
      }
    }

    function saveOverlayImage() {
      vm.data = HelpersFactory.cleanObj(vm.data);
      if (!_.isEmpty(vm.data)) {
        var imageUsedAlready = _.find(vmParent.spot.properties.sed.strat_section.images, function (image) {
          return vm.data.id === image.id;
        });
        if (!isEdit && imageUsedAlready) {
          $ionicPopup.alert({
            'title': 'Image Used Already!',
            'template': 'This image has already been used as an overlay in this Strat Section. ' +
            'Select another image or modify the existing overlay with this image.'
          });
          return 0;
        }
        if (!vmParent.spot.properties.sed.strat_section.images) vmParent.spot.properties.sed.strat_section.images = [];
        else {
          vmParent.spot.properties.sed.strat_section.images = _.reject(vmParent.spot.properties.sed.strat_section.images,
            function (image) {
              return vm.data.id === image.id;
            });
        }
        vmParent.spot.properties.sed.strat_section.images.push(vm.data);
        vm.data = {};
        vmParent.saveSpot();
      }
      vm.addOverlayImageModal.hide();
    }

    function toggleStratSection() {
      if (vm.showStratSection) {
        vmParent.spot.properties.sed = {};
        vmParent.spot.properties.sed.strat_section = {};
        vmParent.spot.properties.sed.strat_section.strat_section_id = HelpersFactory.getNewId();
        vmParent.spot.properties.sed.strat_section.column_profile = 'clastic';
        vmParent.spot.properties.sed.strat_section.column_y_axis_units = 'm';
        vmParent.data = vmParent.spot.properties.sed.strat_section;
      }
      if (!vm.showStratSection && !_.isEmpty(vmParent.spot.properties.sed.strat_section)) {
        // ToDo: Do a check here if there are any Spots mapped on the Strat Section - if so can't delete
        var confirmPopup = $ionicPopup.confirm({
          'title': 'Delete Strat Section?',
          'template': 'By toggling off the Strat Section option you will be deleting assoicated Spots. Continue?'
        });
        confirmPopup.then(function (res) {
          if (res) delete vmParent.spot.properties.sed;
          else vm.showStratSection = !vm.showStratSection;
        });
      }
    }

    function viewStratSection() {
      vmParent.submit('/app/strat-sections/' + vmParent.spot.properties.sed.strat_section.strat_section_id);
    }
  }
}());
<commit_msg>Sed: Check for use before toggling strat section off<commit_after>(function () {
  'use strict';

  angular
    .module('app')
    .controller('StratSectionTabController', StratSectionTabController);

  StratSectionTabController.$inject = ['$ionicModal', '$ionicPopup', '$log', '$scope', '$state', 'FormFactory',
    'HelpersFactory', 'SpotFactory', 'StratSectionFactory'];

  function StratSectionTabController($ionicModal, $ionicPopup, $log, $scope, $state, FormFactory, HelpersFactory,
                                     SpotFactory, StratSectionFactory) {
    var vm = this;
    var vmParent = $scope.vm;

    var isEdit = false;
    var thisTabName = 'strat-section';

    vm.addOverlayImageModal = {};
    vm.data = {};
    vm.showStratSection = false;

    vm.addOverlayImage = addOverlayImage;
    vm.changeColumnProfile = changeColumnProfile;
    vm.changeUnits = changeUnits;
    vm.deleteOverlayImage = deleteOverlayImage;
    vm.editOverlayImage = editOverlayImage;
    vm.getImageLabel = getImageLabel;
    vm.resizeImage = resizeImage;
    vm.saveOverlayImage = saveOverlayImage;
    vm.toggleStratSection = toggleStratSection;
    vm.viewStratSection = viewStratSection;

    activate();

    /**
     * Private Functions
     */

    function activate() {
      $log.log('In StratSectionTabController');

      // Loading tab from Spots list
      if ($state.current.name === 'app.spotTab.' + thisTabName) loadTab($state);
      // Loading tab in Map side panel
      $scope.$on('load-tab', function (event, args) {
        if (args.tabName === thisTabName) {
          vmParent.saveSpot().finally(function () {
            vmParent.spotChanged = false;
            loadTab({
              'current': {'name': 'app.spotTab.' + thisTabName},
              'params': {'spotId': args.spotId}
            });
          });
        }
      });
      createModals();
    }

    function createModals() {
      $ionicModal.fromTemplateUrl('app/spot/sed/strat-section/add-overlay-image-modal.html', {
        'scope': $scope,
        'animation': 'slide-in-up',
        'backdropClickToClose': false,
        'hardwareBackButtonClose': false
      }).then(function (modal) {
        vm.addOverlayImageModal = modal;
      });

      // Cleanup the modals when we're done with it!
      $scope.$on('$destroy', function () {
        vm.addOverlayImageModal.remove();
      });
    }

    function loadTab(state) {
      vmParent.loadTab(state);     // Need to load current state into parent
      vmParent.child = vm;
      FormFactory.setForm('sed', 'strat_section');
      if (vmParent.spot.properties.sed && vmParent.spot.properties.sed.strat_section) {
        $log.log('Strat Section:', vmParent.spot.properties.sed.strat_section);
        vmParent.data = vmParent.spot.properties.sed.strat_section;
        vm.showStratSection = true;
      }
      else vmParent.data = {};
    }

    /**
     * Public Functions
     */

    function addOverlayImage() {
      vm.data = {};
      isEdit = false;
      vm.addOverlayImageModal.show();
    }

    function changeColumnProfile(newProfile, oldProfile) {
      var stratSectionId = vmParent.spot.properties.sed.strat_section.strat_section_id;
      if (!newProfile) vmParent.spot.properties.sed.strat_section.column_profile = oldProfile;
      else if (((oldProfile === 'clastic' || oldProfile === 'carbonate' || oldProfile === 'mixed_clastic') &&
        newProfile === 'weathering_pro') || (oldProfile === 'weathering_pro' &&
        (newProfile === 'clastic' || newProfile === 'carbonate' || newProfile === 'mixed_clastic'))) {
        var confirmPopup = $ionicPopup.confirm({
          'title': 'Column Profile Change Warning!',
          'template': 'Changing the column profile will redraw all intervals as rectangles so any drawing edits to ' +
            'current interval shapes will be lost. Continue anyway?'
        });
        confirmPopup.then(function (res) {
          if (res) {
            SpotFactory.save(vmParent.spot).then(function () {
              StratSectionFactory.changedColumnProfile(stratSectionId);
            });
          }
          else vmParent.spot.properties.sed.strat_section.column_profile = oldProfile;
        });

      }
    }

    function changeUnits(newUnits, oldUnits) {
      var stratSectionId = vmParent.spot.properties.sed.strat_section.strat_section_id;
      var stratSectionIntervals = StratSectionFactory.getStratSectionIntervals(stratSectionId);
      if (!_.isEmpty(stratSectionIntervals)) {
        $ionicPopup.alert({
          'title': 'Units Change Error!',
          'template': 'Units for this Section have already been set at ' + oldUnits + '. Please start a new' +
            ' Section and set your desired units before adding intervals or delete the intervals in this Section.'
        });
        vmParent.spot.properties.sed.strat_section.column_y_axis_units = oldUnits;
      }

      /*var confirmPopup = $ionicPopup.confirm({
        'title': 'Units Change!',
        'template': 'Changing the units for this Strat Section will also convert the interval thickness ' +
        'for all intervals in this Strat Section to use the same units. Are you sure you want to continue?'
      });
      confirmPopup.then(function (res) {
        if (res) {
          StratSectionFactory.gatherStratSectionSpots(vmParent.spot.properties.sed.strat_section.strat_section_id);
          var spots = StratSectionFactory.getStratSectionSpots();
          // Separate the Strat Section Spots into the Interval Spots and other Spots
          var stratSectionSpotsPartitioned = _.partition(spots, function (spot) {
            return spot.properties.surface_feature &&
              spot.properties.surface_feature.surface_feature_type === 'strat_interval';
          });
          var stratSectionIntervals = stratSectionSpotsPartitioned[0];
          _.each(stratSectionIntervals, function (stratSectionInterval) {
            if (stratSectionInterval.properties.sed && stratSectionInterval.properties.sed.lithologies) {
              var thickness = stratSectionInterval.properties.sed.lithologies.interval_thickness;
              if (oldUnits === 'cm' && newUnits === 'm') thickness = thickness / 100;             // cm to m
              else if (oldUnits === 'cm' && newUnits === 'in') thickness = thickness / 2.54;      // cm to in
              else if (oldUnits === 'cm' && newUnits === 'ft') thickness = thickness / 30.48;     // cm to ft
              else if (oldUnits === 'm' && newUnits === 'cm') thickness = thickness * 100;        // m to cm
              else if (oldUnits === 'm' && newUnits === 'in') thickness = thickness / 0.0254;     // m to in
              else if (oldUnits === 'm' && newUnits === 'ft') thickness = thickness / 0.3048;     // m to ft
              else if (oldUnits === 'in' && newUnits === 'cm') thickness = thickness * 2.54;      // in to cm
              else if (oldUnits === 'in' && newUnits === 'm') thickness = thickness * 0.0254;     // in to m
              else if (oldUnits === 'in' && newUnits === 'ft') thickness = thickness / 12;        // in to ft
              else if (oldUnits === 'ft' && newUnits === 'cm') thickness = thickness * 30.48;     // ft to cm
              else if (oldUnits === 'ft' && newUnits === 'm') thickness = thickness * 0.3048;     // ft to m
              else if (oldUnits === 'ft' && newUnits === 'in') thickness = thickness * 12;        // ft to in
              thickness = HelpersFactory.roundToDecimalPlaces(thickness, 2);
              stratSectionInterval.properties.sed.lithologies.interval_thickness = thickness;
              stratSectionInterval.properties.sed.lithologies.thickness_units = newUnits;
              stratSectionInterval = StratSectionFactory.checkForIntervalUpdates('app.spotTab.sed-lithologies', stratSectionInterval);
              SpotFactory.save(stratSectionInterval);
            }
          });
        }
        else vmParent.spot.properties.sed.strat_section.column_y_axis_units = oldUnits;
      });*/
    }

    function deleteOverlayImage(imageToDelete) {
      var confirmPopup = $ionicPopup.confirm({
        'title': 'Remove Overlay',
        'template': 'Are you sure you want to remove this image as an overlay on this Strat Section? ' +
          'This will not delete the image itself.'
      });
      confirmPopup.then(function (res) {
        if (res) {
          vmParent.spot.properties.sed.strat_section.images = _.reject(
            vmParent.spot.properties.sed.strat_section.images,
            function (image) {
              return image.id === imageToDelete.id;
            });
          if (vmParent.spot.properties.sed.strat_section.images.length === 0) {
            delete vmParent.spot.properties.sed.strat_section.images;
          }
        }
      });
    }

    function editOverlayImage(image) {
      vm.data = image;
      isEdit = true;
      vm.addOverlayImageModal.show();
    }

    function getImageLabel(id) {
      var image = _.find(vmParent.spot.properties.images, function (image) {
        return id === image.id;
      });
      return image && image.title ? image.title : 'Untitled';
    }

    // Resize image preserving image ratio
    function resizeImage(dim) {
      var image = _.find(vmParent.spot.properties.images, function (image) {
        return vm.data.id === image.id;
      });
      if (dim === 'w' && vm.data.image_width) {
        vm.data.image_height =
          Math.round(image.height / image.width * vm.data.image_width);
      }
      else if (dim === 'h' && vm.data.image_height) {
        vm.data.image_width =
          Math.round(image.width / image.height * vm.data.image_height);
      }
      else {
        delete vm.data.image_height;
        delete vm.data.image_width;
      }
    }

    function saveOverlayImage() {
      vm.data = HelpersFactory.cleanObj(vm.data);
      if (!_.isEmpty(vm.data)) {
        var imageUsedAlready = _.find(vmParent.spot.properties.sed.strat_section.images, function (image) {
          return vm.data.id === image.id;
        });
        if (!isEdit && imageUsedAlready) {
          $ionicPopup.alert({
            'title': 'Image Used Already!',
            'template': 'This image has already been used as an overlay in this Strat Section. ' +
              'Select another image or modify the existing overlay with this image.'
          });
          return 0;
        }
        if (!vmParent.spot.properties.sed.strat_section.images) vmParent.spot.properties.sed.strat_section.images = [];
        else {
          vmParent.spot.properties.sed.strat_section.images = _.reject(
            vmParent.spot.properties.sed.strat_section.images,
            function (image) {
              return vm.data.id === image.id;
            });
        }
        vmParent.spot.properties.sed.strat_section.images.push(vm.data);
        vm.data = {};
        vmParent.saveSpot().then(function () {
          vmParent.spotChanged = false;
        });
      }
      vm.addOverlayImageModal.hide();
    }

    function toggleStratSection() {
      if (vm.showStratSection) {
        vmParent.spot.properties.sed = {};
        vmParent.spot.properties.sed.strat_section = {};
        vmParent.spot.properties.sed.strat_section.strat_section_id = HelpersFactory.getNewId();
        vmParent.spot.properties.sed.strat_section.column_profile = 'clastic';
        vmParent.spot.properties.sed.strat_section.column_y_axis_units = 'm';
        vmParent.data = vmParent.spot.properties.sed.strat_section;
      }
      if (!vm.showStratSection && !_.isEmpty(vmParent.spot.properties.sed.strat_section)) {
        // Check if there are any Spots mapped on this Strat Section. If so, don't allow toggle off.
        var numSpotsMappedOnThisStratSection = 0;
        _.each(SpotFactory.getSpots(), function (spot) {
          if (spot.properties.strat_section_id === vmParent.spot.properties.sed.strat_section.strat_section_id) {
            numSpotsMappedOnThisStratSection++;
          }
        });
        if (numSpotsMappedOnThisStratSection > 0) {
          $ionicPopup.alert({
            'title': 'Strat Section In Use!',
            'template': 'There are ' + numSpotsMappedOnThisStratSection + ' Spot(s) mapped on this Spot. Delete these' +
              ' Spots before removing the strat section.'
          }).then(function () {
            vm.showStratSection = !vm.showStratSection;
          });
        }
        else {
          var confirmPopup = $ionicPopup.confirm({
            'title': 'Delete Strat Section?',
            'template': 'Are you sure you want to delete this strat section?'
          });
          confirmPopup.then(function (res) {
            if (res) {
              delete vmParent.spot.properties.sed;
              vmParent.saveSpot().then(function () {
                vmParent.spotChanged = false;
              });
            }
            else vm.showStratSection = !vm.showStratSection;
          });
        }
      }
    }

    function viewStratSection() {
      vmParent.submit('/app/strat-sections/' + vmParent.spot.properties.sed.strat_section.strat_section_id);
    }
  }
}());
<|endoftext|>