<?php
/**
 * WebEngine CMS
 * https://webenginecms.org/
 * 
 * @version 1.2.6
 * @author Lautaro Angelico <http://lautaroangelico.com/>
 * @copyright (c) 2013-2025 Lautaro Angelico, All Rights Reserved
 * 
 * Licensed under the MIT license
 * http://opensource.org/licenses/MIT
 */

class Account extends common {
	
	private $_defaultAccountSerial = '1111111111111';
	private $_countryChangeCooldown = 1296000;
	
	protected $_account;
	protected $_country;
	
	public function setAccount($account) {
		$this->_account = $account;
	}
	
	public function setCountry($country) {
		$this->_country = $country;
	}
	
	public function registerAccount($username, $password, $cpassword, $email) {
		
		if(!check_value($username)) throw new Exception(lang('error_4',true));
		if(!check_value($password)) throw new Exception(lang('error_4',true));
		if(!check_value($cpassword)) throw new Exception(lang('error_4',true));
		if(!check_value($email)) throw new Exception(lang('error_4',true));

		// Filters
		if(!Validator::UsernameLength($username)) throw new Exception(lang('error_5',true));
		if(!Validator::AlphaNumeric($username)) throw new Exception(lang('error_6',true));
		if(!Validator::PasswordLength($password)) throw new Exception(lang('error_7',true));
		if($password != $cpassword) throw new Exception(lang('error_8',true));
		if(!Validator::Email($email)) throw new Exception(lang('error_9',true));
		
		# load registration configs
		$regCfg = loadConfigurations('register');

		# check username / email in Legado and Seasonal
		$provisioner = new NexoraAccountProvisioner(
			$this->_passwordEncryption,
			$this->_sha256salt,
			$this->_defaultAccountSerial
		);
		$provisioner->assertAvailable($username, $email);
		
		# The legacy EVS stores pending game passwords in plaintext. It is intentionally disabled.
		if($regCfg['verify_email']) {
			throw new RuntimeException('La verificación de registro heredada está deshabilitada por seguridad. Usa registro directo o implementa identidad Nexora antes de activarla.');
			# check if username / email exists
			if($this->checkUsernameEVS($username)) throw new Exception(lang('error_10',true));
			if($this->checkEmailEVS($email)) throw new Exception(lang('error_11',true));
			
			# generate verification key
			$verificationKey = $this->createRegistrationVerification($username,$password,$email);
			if(!check_value($verificationKey)) throw new Exception(lang('error_23',true));
			
			# send verification email
			$this->sendRegistrationVerificationEmail($username,$email,$verificationKey);
			message('success', lang('success_18',true));
			return;
		}
		
		# create the same credentials in Legado and Seasonal atomically
		$provisioner->createAccounts($username, $password, $email);
		
		# send welcome email
		if($regCfg['send_welcome_email']) {
			$this->sendWelcomeEmail($username, $email);
		}
		
		# success message
		message('success', lang('success_1',true));
		
		
		if($regCfg['automatic_login']) {
			// automatic log-in
			try {
				$userLogin = new login($this->_realm);
				$userLogin->validateLogin($username, $password);
			} catch(Exception $ex) {
				redirect(1,'login/');
			}
		} else {
			// redirect to log-in module
			redirect(2, 'login/', 5);
		}
		
	}
	
	public function changePasswordProcess($userid, $username, $password, $new_password, $confirm_new_password) {
		if(!check_value($userid)) throw new Exception(lang('error_4',true));
		if(!check_value($username)) throw new Exception(lang('error_4',true));
		if(!check_value($password)) throw new Exception(lang('error_4',true));
		if(!check_value($new_password)) throw new Exception(lang('error_4',true));
		if(!check_value($confirm_new_password)) throw new Exception(lang('error_4',true));
		if(!Validator::PasswordLength($new_password)) throw new Exception(lang('error_7',true));
		if($new_password != $confirm_new_password) throw new Exception(lang('error_8',true));
		
		# check user credentials
		if(!$this->validateUser($username, $password)) throw new Exception(lang('error_13',true));
		
		# check online status
		if($this->accountOnline($username)) throw new Exception(lang('error_14',true));
		
		# change password
		if(!$this->changePassword($userid, $username, $new_password)) throw new Exception(lang('error_23',true));
		
		# send email with new password
		$accountData = $this->accountInformation($userid);
		try {
			$email = new Email();
			$email->setTemplate('CHANGE_PASSWORD');
			$email->addVariable('{USERNAME}', $username);
			$email->addAddress($accountData[_CLMN_EMAIL_]);
			$email->send();
		} catch (Exception $ex) {}
		
		# success message
		message('success', lang('success_2',true));
	}
	
	public function changePasswordProcess_verifyEmail($userid, $username, $password, $new_password, $confirm_new_password, $ip_address) {
		throw new RuntimeException('La verificación de contraseña heredada está deshabilitada porque almacenaba la contraseña pendiente en texto claro. Usa el flujo directo seguro.');
	}

	public function changePasswordVerificationProcess($user_id, $auth_code) {
		throw new RuntimeException('Los enlaces de cambio de contraseña del sistema anterior están deshabilitados. Inicia un cambio nuevo desde tu cuenta.');
	}

	public function passwordRecoveryProcess($user_email, $ip_address) {
		$service = new NexoraPasswordReset($this->_realm);
		$service->request($user_email, $ip_address);
		message('success', 'Si el correo corresponde a una cuenta válida, recibirás un enlace de recuperación de un solo uso.');
	}

	/** Legacy recovery URLs were deterministic and are intentionally rejected. */
	public function passwordRecoveryVerificationProcess($ui, $ue, $key) {
		throw new RuntimeException('Este enlace pertenece al sistema de recuperación anterior. Solicita un enlace nuevo.');
	}

	public function changeEmailAddress($accountId, $newEmail, $ipAddress, $currentPassword=null) {
		$info = $this->accountInformation($accountId);
		if(!is_array($info)) throw new Exception(lang('error_21',true));
		if(!check_value($currentPassword)) throw new Exception(lang('error_4',true));
		$service = new NexoraEmailChange($this->_realm);
		$service->request($accountId, $info[_CLMN_USERNM_], $currentPassword, $newEmail, $ipAddress);
	}

	public function changeEmailVerificationProcess($token) {
		$service = new NexoraEmailChange($this->_realm);
		return $service->complete($token);
	}

	public function verifyRegistrationProcess($username, $key) {
		throw new RuntimeException('La verificación de registro heredada está deshabilitada porque almacenaba contraseñas pendientes en texto claro.');
	}

	public function getAccountCountry() {
		if(!check_value($this->_account)) return;
		$result = $this->platform->query_fetch_single("SELECT * FROM ".WEBENGINE_ACCOUNT_COUNTRY." WHERE account = ?", array($this->_account));
		if(!is_array($result)) return;
		return $result;
	}
	
	public function updateAccountCountry() {
		if(!check_value($this->_account)) return;
		if(!check_value($this->_country)) return;
		
		$data = $this->getAccountCountry();
		if(!is_array($data)) return;
		if(time() < strtotime($data['lastchange'])+$this->_countryChangeCooldown) return;
		
		$result = $this->platform->query("UPDATE ".WEBENGINE_ACCOUNT_COUNTRY." SET country = ? WHERE account = ?", array($this->_country, $this->_account));
		if(!$result) return;
		return true;
	}
	
	public function insertAccountCountry() {
		if(!check_value($this->_account)) return;
		if(!check_value($this->_country)) return;
		$result = $this->platform->query("INSERT INTO ".WEBENGINE_ACCOUNT_COUNTRY." (account, country) VALUES (?, ?)", array($this->_account, $this->_country));
		if(!$result) return;
		return true;
	}
	
	public function getServerList() {
		$result = $this->memuonline->query_fetch("SELECT DISTINCT("._CLMN_MS_GS_.") FROM "._TBL_MS_."");
		if(!is_array($result)) return;
		foreach($result as $row) {
			$servers[] = $row[_CLMN_MS_GS_];
		}
		return $servers;
	}
	
	public function getOnlineAccountCount($server=null) {
		if(check_value($server)) {
			$result = $this->memuonline->query_fetch_single("SELECT COUNT(*) as online FROM "._TBL_MS_." WHERE "._CLMN_CONNSTAT_." = 1 AND "._CLMN_MS_GS_." = ?", array($server));
			if(!is_array($result)) return 0;
			return $result['online'];
		}
		$result = $this->memuonline->query_fetch_single("SELECT COUNT(*) as online FROM "._TBL_MS_." WHERE "._CLMN_CONNSTAT_." = 1");
		if(!is_array($result)) return 0;
		return $result['online'];
	}
	
	public function getOnlineAccountList($server=null) {
		if(check_value($server)) {
			$result = $this->memuonline->query_fetch("SELECT "._CLMN_MS_MEMBID_.", "._CLMN_MS_GS_.", "._CLMN_MS_IP_." FROM "._TBL_MS_." WHERE "._CLMN_CONNSTAT_." = 1 AND "._CLMN_MS_GS_." = ?", array($server));
			if(!is_array($result)) return;
			return $result;
		}
		$result = $this->memuonline->query_fetch("SELECT "._CLMN_MS_MEMBID_.", "._CLMN_MS_GS_.", "._CLMN_MS_IP_." FROM "._TBL_MS_." WHERE "._CLMN_CONNSTAT_." = 1");
		if(!is_array($result)) return;
		return $result;
	}
	
	private function sendRegistrationVerificationEmail($username, $account_email, $key) {
			$verificationLink = __BASE_URL__.'verifyemail/?op=2&user='.rawurlencode($username).'&key='.$key.'&realm='.rawurlencode($this->_realm);
		try {
			$email = new Email();
			$email->setTemplate('WELCOME_EMAIL_VERIFICATION');
			$email->addVariable('{USERNAME}', $username);
			$email->addVariable('{LINK}', $verificationLink);
			$email->addAddress($account_email);
			$email->send();
		} catch (Exception $ex) {
			if($this->_debug) {
				throw new Exception($ex->getMessage());
			}
		}
	}
	
	private function sendWelcomeEmail($username,$address) {
		try {
			$email = new Email();
			$email->setTemplate('WELCOME_EMAIL');
			$email->addVariable('{USERNAME}', $username);
			$email->addAddress($address);
			$email->send();
		} catch (Exception $ex) {
			if($this->_debug) {
				throw new Exception($ex->getMessage());
			}
		}
	}
	
	private function createRegistrationVerification($username,$password,$email) {
		if(!check_value($username)) return;
		if(!check_value($password)) return;
		if(!check_value($email)) return;
		
		$key = uniqid();
		$data = array(
			$username,
			$password,
			$email,
			time(),
			$_SERVER['REMOTE_ADDR'],
			$key
		);
		
		$query = "INSERT INTO ".WEBENGINE_REGISTER_ACCOUNT." (registration_account,registration_password,registration_email,registration_date,registration_ip,registration_key) VALUES (?,?,?,?,?,?)";
		
		$result = $this->platform->query($query, $data);
		if(!$result) return;
		return $key;
	}
	
	private function deleteRegistrationVerification($username) {
		if(!check_value($username)) return;
		$delete = $this->platform->query("DELETE FROM ".WEBENGINE_REGISTER_ACCOUNT." WHERE registration_account = ?", array($username));
		if($delete) return true;
		return;
	}

	private function checkUsernameEVS($username) {
		if(!check_value($username)) return;
		$result = $this->platform->query_fetch_single("SELECT * FROM ".WEBENGINE_REGISTER_ACCOUNT." WHERE registration_account = ?", array($username));
		if(!is_array($result)) return;
		
		$configs = loadConfigurations('register');
		if(!is_array($configs)) return;
		
		$timelimit = $result['registration_date']+$configs['verification_timelimit']*60*60;
		if($timelimit > time()) return true;
		
		$this->deleteRegistrationVerification($username);
		return false;
	}

	private function checkEmailEVS($email) {
		if(!check_value($email)) return;
		$result = $this->platform->query_fetch_single("SELECT * FROM ".WEBENGINE_REGISTER_ACCOUNT." WHERE registration_email = ?", array($email));
		if(!is_array($result)) return;
		
		$configs = loadConfigurations('register');
		if(!is_array($configs)) return;
		
		$timelimit = $result['registration_date']+$configs['verification_timelimit']*60*60;
		if($timelimit > time()) return true;
		
		$this->deleteRegistrationVerification($result['registration_account']);
		return false;
	}
	
	private function changeEmailVerificationMail($userName, $emailAddress, $newEmail, $verificationLink, $ipAddress) {
		try {
			$email = new Email();
			$email->setTemplate('CHANGE_EMAIL_VERIFICATION');
			$email->addVariable('{USERNAME}', $userName);
			$email->addVariable('{IP_ADDRESS}', $ipAddress);
			$email->addVariable('{NEW_EMAIL}', $newEmail);
			$email->addVariable('{LINK}', $verificationLink);
			$email->addAddress($emailAddress);
			$email->send();
			
			return true;
		} catch (Exception $ex) {
			if($this->_debug) {
				throw new Exception($ex->getMessage());
			}
			return;
		}
	}
	
	private function generateAccountRecoveryLink($userid,$email,$recovery_code) {
		if(!check_value($userid)) return;
		if(!check_value($recovery_code)) return;
		
		$build_url = __BASE_URL__;
		$build_url .= 'forgotpassword/';
		$build_url .= '?ui=';
		$build_url .= $userid;
		$build_url .= '&ue=';
		$build_url .= rawurlencode($email);
		$build_url .= '&key=';
		$build_url .= $recovery_code;
		$build_url .= '&realm=';
		$build_url .= rawurlencode($this->_realm);
		return $build_url;
	}
	
}
