<?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
 */

function check_value($value) {
	if((@count((array)$value)>0 and !@empty($value) and @isset($value)) || $value=='0') {
		return true;
	}
}

function redirect($type = 1, $location = null, $delay = 0) {
	if(!check_value($location)) {
		$to = __BASE_URL__;
	} else {
		$to = __BASE_URL__ . $location;
		
		if($location == 'login') {
			$_SESSION['login_last_location'] = $_REQUEST['page'].'/';
			if(isset($_REQUEST['subpage'])) {
				$_SESSION['login_last_location'] .= $_REQUEST['subpage'].'/';
			}
		}
	}

	switch($type) {
		default:
			header('Location: '.$to.'');
			die();
		break;
		case 1:
			header('Location: '.$to.'');
			die();
		break;
		case 2:
			echo '<meta http-equiv="REFRESH" content="'.$delay.';url='.$to.'">';
		break;
		case 3:
			header('Location: '.$location.'');
			die();
		break;
	}
}

function isLoggedIn() {
	if(!isset($_SESSION['valid'])) return;
	if(!isset($_SESSION['userid'])) return;
	if(!isset($_SESSION['username'])) return;
	if(!isset($_SESSION['timeout'])) return;
	if(class_exists('NexoraRealm') && !NexoraRealm::hasAccountRealm()) return;
	
	$loginConfigs = loadConfigurations('login');
	if(is_array($loginConfigs)) {
		if($loginConfigs['enable_session_timeout']) {
			if(time()-$_SESSION['timeout'] > $loginConfigs['session_timeout']) {
				logOutUser();
			}
		}
	}
	
	$_SESSION['timeout'] = time();
	return true;
}

function logOutUser() {
	if(class_exists('NexoraRealm')) NexoraRealm::clearAccountRealm();
	$_SESSION = array();
	if(session_status() === PHP_SESSION_ACTIVE) session_destroy();
	redirect();
}

function renderNexoraRealmSelect($fieldName='nexora_realm') {
	if(!class_exists('NexoraRealm') || !NexoraRealm::enabled()) return;
	$selected = null;
	if(isset($_POST[$fieldName]) && NexoraRealm::exists($_POST[$fieldName])) {
		$selected = strtolower($_POST[$fieldName]);
	} elseif(NexoraRealm::hasExplicitPublicRealm()) {
		$selected = NexoraRealm::publicRealm();
	}

	echo '<div class="form-group">';
	echo '<label class="col-sm-4 control-label" for="nexoraRealmSelect">Reino</label>';
	echo '<div class="col-sm-8"><select class="form-control" id="nexoraRealmSelect" name="'.htmlspecialchars($fieldName, ENT_QUOTES, 'UTF-8').'" required>';
	echo '<option value="">Selecciona Legado o Seasonal</option>';
	foreach(NexoraRealm::publicList() as $id => $realm) {
		echo '<option value="'.htmlspecialchars($id, ENT_QUOTES, 'UTF-8').'"'.($selected === $id ? ' selected' : '').'>'.htmlspecialchars($realm['label'], ENT_QUOTES, 'UTF-8').'</option>';
	}
	echo '</select></div></div>';
}

/** Campo de registro: la identidad se crea en ambos reinos sin pedir destino. */
function renderNexoraDualRegistrationNotice($fieldName='nexora_realm') {
	if(!class_exists('NexoraRealm') || !NexoraRealm::enabled()) return;
	$preferredRealm = NexoraRealm::active();
	if(isset($_POST[$fieldName]) && NexoraRealm::exists($_POST[$fieldName])) {
		$preferredRealm = strtolower($_POST[$fieldName]);
	}

	echo '<input type="hidden" name="'.htmlspecialchars($fieldName, ENT_QUOTES, 'UTF-8').'" value="'.htmlspecialchars($preferredRealm, ENT_QUOTES, 'UTF-8').'">';
	echo '<div class="alert alert-info nexora-dual-registration-notice" role="status">';
	echo '<strong>Una cuenta para ambos servidores.</strong><br>';
	echo 'El mismo usuario y contraseña se crearán en Legado y Seasonal. Al iniciar sesión eliges el servidor al que deseas entrar.';
	echo '</div>';
}

function nexoraPaypalCustomSignature($userId, $site, $realm, $packId, $ipAddress, $orderId) {
	$secret = class_exists('NexoraSecurity')
		? NexoraSecurity::requireStrongSecret('NEXORA_PAYMENT_SIGNING_KEY')
		: '';
	if(!check_value($secret)) throw new RuntimeException('No se ha configurado la clave privada de firma de pagos.');
	$payload = implode('|', array(
		(int)$userId,
		strtoupper((string)$site),
		strtolower((string)$realm),
		(string)$packId,
		(string)$ipAddress,
		(string)$orderId,
	));
	return hash_hmac('sha256', $payload, $secret);
}

function message($type='info', $message="", $title="") {
	switch($type) {
		case 'error':
			$class = ' alert-danger';
		break;
		case 'success':
			$class = ' alert-success';
		break;
		case 'warning':
			$class = ' alert-warning';
		break;
		default:
			$class = ' alert-info';
		break;
	}
	
	if(check_value($title)) {
		echo '<div class="alert'.$class.'" role="alert"><strong>'.$title.'</strong><br />'.$message.'</div>';
	} else {
		echo '<div class="alert'.$class.'" role="alert">'.$message.'</div>';
	}
}

function lang($phrase, $return=true) {
	global $lang;
	if(!array_key_exists($phrase, $lang)) {
		$result = 'ERROR';
	} else {
		$result = $lang[$phrase];
	}
	
	if(config('language_debug',true)) {
		if($return) {
			return '<span title="'.$phrase.'" alt="'.$phrase.'">'.$result.'</span>';
		} else {
			echo '<span title="'.$phrase.'" alt="'.$phrase.'">'.$result.'</span>';
		}
	} else {
		if($return) {
			return $result;
		} else {
			echo $result;
		}
	}
}

function langf($phrase, $args=array(), $print=false) {
	global $lang;
	$result = @vsprintf($lang[$phrase], $args);
	if(!$result) $result = 'ERROR';
	
	if(config('language_debug',true)) {
		if($print) {
			echo '<span title="'.$phrase.'" alt="'.$phrase.'">'.$result.'</span>';
		} else {
			return '<span title="'.$phrase.'" alt="'.$phrase.'">'.$result.'</span>';
		}
	} else {
		if($print) {
			echo $result;
		} else {
			return $result;
		}
	}
}

function debug($value) {
	echo '<pre>';
		print_r($value);
	echo '</pre>';
}

function canAccessAdminCP($username) {
	if(!check_value($username)) return false;
	if(class_exists('NexoraRbac')) return NexoraRbac::canAccess($username);
	$admins = config('admins',true);
	return is_array($admins) && array_key_exists($username, $admins);
}

function BuildCacheData($data_array) {
	$result = null;
	if(is_array($data_array)) {
		foreach($data_array as $row) {
			$count = count($row);
			$i = 1;
			foreach($row as $data) {
				$result .= $data;
				if($i < $count) {
					$result .= '¦';
				}
				$i++;
			}
			$result .= "\n";
		}
		return $result;
	} else {
		return null;
	}
}

function nexoraEnsureDirectory($directory) {
	$directory = rtrim((string)$directory, '/\\');
	if($directory === '') return false;
	if(is_dir($directory)) return is_writable($directory);
	if(@mkdir($directory, 0775, true) || is_dir($directory)) return is_writable($directory);
	@error_log('[Nexora cache] No se pudo crear el directorio: '.$directory);
	return false;
}

function nexoraAtomicWrite($file, $data) {
	$file = (string)$file;
	$directory = dirname($file);
	if(!nexoraEnsureDirectory($directory)) {
		@error_log('[Nexora cache] Directorio no escribible: '.$directory);
		return false;
	}
	if(file_exists($file) && !is_writable($file)) {
		@error_log('[Nexora cache] Archivo no escribible: '.$file);
		return false;
	}
	$tmp = $file.'.tmp.'.getmypid().'.'.mt_rand(1000,999999);
	$written = @file_put_contents($tmp, $data, LOCK_EX);
	if($written === false) {
		@unlink($tmp);
		@error_log('[Nexora cache] No se pudo escribir temporal: '.$file);
		return false;
	}
	if(!@rename($tmp, $file)) {
		@unlink($tmp);
		@error_log('[Nexora cache] No se pudo publicar el archivo: '.$file);
		return false;
	}
	@chmod($file, 0664);
	return true;
}

function UpdateCache($file_name, $data) {
	$file = class_exists('NexoraRealm') ? NexoraRealm::cachePath($file_name) : __PATH_CACHE__.$file_name;
	return nexoraAtomicWrite($file, time()."\n".$data);
}

function LoadCacheData($file_name, $realm=null) {
	$file = class_exists('NexoraRealm') ? NexoraRealm::cachePath($file_name, $realm) : __PATH_CACHE__.$file_name;
	if(!file_exists($file)) return;
	if(!is_readable($file)) return;
	
	$cache_file = file_get_contents($file);
	if(empty($cache_file)) return;
	$file_lanes = explode("\n",$cache_file);
	$nlines = count($file_lanes);
	$line_data = array();
	for($i=0; $i<$nlines; $i++) {
		if(check_value($file_lanes[$i])) {
			$line_data[$i] = explode("¦",$file_lanes[$i]);
		}
	}
	return count($line_data) > 0 ? $line_data : null;
}

function sec_to_hms($input_seconds=0) {
	$result = sec_to_dhms($input_seconds);
	if(!is_array($result)) return array(0,0,0);
	return array((($result[0]*24)+$result[1]), $result[2], $result[3]);
}

function sec_to_dhms($input_seconds=0) {
	if($input_seconds < 1) return array(0,0,0,0);
	$days_module = $input_seconds % 86400;
	$days = ($input_seconds-$days_module)/86400;
	$hours_module = $days_module % 3600;
	$hours = ($days_module-$hours_module)/3600;
	$minutes_module = $hours_module % 60;
	$minutes = ($hours_module-$minutes_module)/60;
	$seconds = $minutes_module;
	return array($days,$hours,$minutes,$seconds);
}

# to be removed
function cs_CalculateTimeLeft() {
	return 0;
}

function nexoraCronStatePath($realm=null) {
	if(class_exists('NexoraRealm') && NexoraRealm::enabled()) {
		$realm = NexoraRealm::validate($realm === null ? NexoraRealm::active() : $realm);
		return NexoraRealm::realmCacheDirectory($realm).'cron_state.json';
	}
	return __PATH_CACHE__.'cron_state.json';
}

function nexoraCronStateLoad($realm=null) {
	$file = nexoraCronStatePath($realm);
	if(!is_file($file) || !is_readable($file)) return array();
	$raw = @file_get_contents($file);
	if($raw === false || trim($raw) === '') return array();
	$data = json_decode($raw, true);
	return is_array($data) ? $data : array();
}

function nexoraCronLastRun($file, $realm=null) {
	$file = basename((string)$file);
	$state = nexoraCronStateLoad($realm);
	return isset($state[$file]) && is_numeric($state[$file]) ? (int)$state[$file] : null;
}

function nexoraCronMarkRun($file, $realm=null, $timestamp=null) {
	$file = basename((string)$file);
	$timestamp = $timestamp === null ? time() : (int)$timestamp;
	$stateFile = nexoraCronStatePath($realm);
	$directory = dirname($stateFile);
	if(!nexoraEnsureDirectory($directory)) return false;
	$lockFile = $stateFile.'.lock';
	$lock = @fopen($lockFile, 'c+');
	if(!$lock) return false;
	$ok = false;
	if(@flock($lock, LOCK_EX)) {
		$state = nexoraCronStateLoad($realm);
		$state[$file] = $timestamp;
		$encoded = json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
		if($encoded !== false) $ok = nexoraAtomicWrite($stateFile, $encoded."\n");
		@flock($lock, LOCK_UN);
	}
	@fclose($lock);
	return $ok;
}

function nexoraCronResetState($file=null, $realm=null) {
	$stateFile = nexoraCronStatePath($realm);
	if($file === null || $file === '') {
		if(is_file($stateFile)) return nexoraAtomicWrite($stateFile, "{}\n");
		return true;
	}
	$state = nexoraCronStateLoad($realm);
	$file = basename((string)$file);
	if(isset($state[$file])) unset($state[$file]);
	$encoded = json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
	if($encoded === false) return false;
	return nexoraAtomicWrite($stateFile, $encoded."\n");
}

function nexoraCronIsDue($cron, $realm=null, $now=null) {
	if(!is_array($cron) || empty($cron['cron_file_run'])) return false;
	$now = $now === null ? time() : (int)$now;
	$interval = isset($cron['cron_run_time']) ? max(1, (int)$cron['cron_run_time']) : 60;
	$lastRun = nexoraCronLastRun($cron['cron_file_run'], $realm);
	if($lastRun === null) return true;
	return $now > ($lastRun + $interval);
}

function updateCronLastRun($file) {
	$realm = class_exists('NexoraRealm') && NexoraRealm::enabled() ? NexoraRealm::active() : null;
	$timestamp = time();
	$stateUpdated = nexoraCronMarkRun($file, $realm, $timestamp);
	try {
		$database = Connection::Database('Platform', $realm);
		$database->query("UPDATE ".WEBENGINE_CRON." SET cron_last_run = ? WHERE cron_file_run = ?", array($timestamp, basename((string)$file)));
	} catch(Throwable $ex) {
		@error_log('[Nexora cron]['.($realm === null ? 'default' : $realm).'] '.$ex->getMessage());
	}
	return $stateUpdated;
}

function returnGuildLogo($binaryData="", $size=40) {
	$imgSize = Validator::UnsignedNumber($size) ? $size : 40;
	return '<img src="'.__PATH_API__.'guildmark.php?data='.$binaryData.'&size='.urlencode($size).'" width="'.$imgSize.'" height="'.$imgSize.'">';
}

function getGensRank($contributionPoints) {
	global $custom;
	foreach($custom['gens_ranks'] as $points => $title) {
		if($contributionPoints >= $points) return $title;
	}
	return $title;
}

function getGensLeadershipRank($rankPosition) {
	global $custom;
	foreach($custom['gens_ranks_leadership'] as $title => $range) {
		if($rankPosition >= $range[0] && $rankPosition <= $range[1]) return $title;
	}
	return;
}

function nexoraLocalConfig() {
	static $local = null;
	if(is_array($local)) return $local;
	$local = array();
	if(defined('__PATH_CONFIGS__')) {
		$file = __PATH_CONFIGS__ . 'nexora.local.php';
		if(is_file($file) && is_readable($file)) {
			$loaded = include($file);
			if(is_array($loaded)) $local = $loaded;
		}
	}
	return $local;
}

function webengineConfigs() {
	if(!file_exists(__PATH_CONFIGS__ . 'webengine.json')) throw new Exception('WebEngine\'s configuration file doesn\'t exist, please reupload the website files.');

	$raw = file_get_contents(__PATH_CONFIGS__ . 'webengine.json');
	if(!check_value($raw)) throw new Exception('WebEngine\'s configuration file is empty, please run the installation script.');
	$config = json_decode($raw, true);
	if(!is_array($config)) throw new Exception('WebEngine configuration is not valid JSON.');

	// Local file and environment variables override public JSON values.
	$local = nexoraLocalConfig();
	$allowed = array('SQL_DB_HOST','SQL_DB_PORT','SQL_DB_USER','SQL_DB_PASS','SQL_PDO_DRIVER','SQL_PASSWORD_ENCRYPTION','SQL_SHA256_SALT','cron_api_key');
	foreach($allowed as $key) {
		if(array_key_exists($key, $local) && $local[$key] !== '') $config[$key] = $local[$key];
		$env = getenv('NEXORA_'.$key);
		if($env !== false && $env !== '') $config[$key] = $env;
	}
	// Friendly aliases used by the sample local configuration.
	$aliases = array(
		'NEXORA_SQL_DB_HOST'=>'SQL_DB_HOST', 'NEXORA_SQL_DB_PORT'=>'SQL_DB_PORT',
		'NEXORA_SQL_DB_USER'=>'SQL_DB_USER', 'NEXORA_SQL_DB_PASS'=>'SQL_DB_PASS',
		'NEXORA_SQL_PDO_DRIVER'=>'SQL_PDO_DRIVER'
	);
	foreach($aliases as $envName=>$configKey) {
		$env = getenv($envName);
		if($env !== false && $env !== '') $config[$configKey] = $env;
	}
	return $config;
}

function config($config_name, $return = false) {
	$config = webengineConfigs();
	if(!array_key_exists($config_name, $config)) return;
	if($return) {
		return $config[$config_name];
	} else {
		echo $config[$config_name];
	}
}

function convertXML($object) {
	return json_decode(json_encode($object), true);
}

function nexoraModuleConfigPath($module, $realm=null) {
	$module = basename((string)$module);
	$realm = ($realm === null && class_exists('NexoraRealm')) ? NexoraRealm::active() : $realm;
	if($realm && class_exists('NexoraRealm') && NexoraRealm::exists($realm)) {
		$realmPath = __PATH_MODULE_CONFIGS__.$module.'.'.NexoraRealm::validate($realm).'.xml';
		if(is_file($realmPath)) return $realmPath;
	}
	$defaultPath = __PATH_MODULE_CONFIGS__.$module.'.xml';
	return is_file($defaultPath) ? $defaultPath : null;
}

function loadModuleConfigs($module) {
	global $mconfig;
	$path = nexoraModuleConfigPath($module);
	if($path) {
		$xml = simplexml_load_file($path);
		$mconfig = array();
		if($xml) $mconfig = convertXML($xml->children());
	}
}

function moduleConfigExists($module) {
	return nexoraModuleConfigPath($module) !== null;
}

function globalConfigExists($config_file) {
	if(file_exists(__PATH_CONFIGS__.$config_file.'.xml')) {
		return true;
	}
}

function mconfig($configuration) {
	global $mconfig;
	if(@array_key_exists($configuration, $mconfig)) {
		return $mconfig[$configuration];
	} else {
		return null;
	}
}

function gconfig($config_file,$return=true) {
	global $gconfig;
	if(globalConfigExists($config_file)) {
		$xml = simplexml_load_file(__PATH_CONFIGS__.$config_file.'.xml');
		$gconfig = array();
		if($xml) {
			$globalCONFIGS = convertXML($xml->children());
			if($return) {
				return $globalCONFIGS;
			} else {
				$gconfig = $globalCONFIGS;
			}
		}
	}
}

function loadConfigurations($file) {
	if(!check_value($file)) return;
	$path = nexoraModuleConfigPath($file);
	if(!$path) return;
	$xml = simplexml_load_file($path);
	if($xml) return convertXML($xml->children());
	return;
}

function loadConfig($name="webengine") {
	if(!check_value($name)) return;
	if(!file_exists(__PATH_CONFIGS__ . $name . '.json')) return;
	$cfg = file_get_contents(__PATH_CONFIGS__ . $name . '.json');
	if(!check_value($cfg)) return;
	return json_decode($cfg, true);
}

/* Nexora News Studio: metadata visual sin alterar la tabla original de WebEngine. */
function nexoraNewsStudioLoad() {
	$cfg = loadConfig('news_studio');
	return is_array($cfg) ? $cfg : array();
}
function nexoraNewsStudioGet($newsId) {
	$cfg = nexoraNewsStudioLoad();
	$key = (string)(int)$newsId;
	return isset($cfg[$key]) && is_array($cfg[$key]) ? $cfg[$key] : array();
}
function nexoraNewsStudioSaveMeta($newsId, $meta) {
	$path = __PATH_CONFIGS__ . 'news_studio.json';
	$cfg = nexoraNewsStudioLoad();
	$key = (string)(int)$newsId;
	$allowedTypes = array('actualizacion','evento','mantenimiento','anuncio','comunidad');
	$allowedRealms = array('legacy','seasonal','both');
	$type = isset($meta['type']) ? strtolower(trim((string)$meta['type'])) : 'anuncio';
	$realm = isset($meta['realm']) ? strtolower(trim((string)$meta['realm'])) : 'both';
	$cover = isset($meta['cover']) ? trim((string)$meta['cover']) : '';
	if ($cover !== '' && !filter_var($cover, FILTER_VALIDATE_URL) && !preg_match('#^[a-zA-Z0-9/_\.\-]+$#', $cover)) $cover = '';
	$excerpt = trim(strip_tags((string)($meta['excerpt'] ?? '')));
	$excerpt = function_exists('mb_substr') ? mb_substr($excerpt, 0, 300, 'UTF-8') : substr($excerpt, 0, 300);
	$cfg[$key] = array(
		'type' => in_array($type, $allowedTypes, true) ? $type : 'anuncio',
		'realm' => in_array($realm, $allowedRealms, true) ? $realm : 'both',
		'excerpt' => $excerpt,
		'cover' => $cover,
		'featured' => !empty($meta['featured'])
	);
	$json = json_encode($cfg, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
	return $json !== false && file_put_contents($path, $json . "\n", LOCK_EX) !== false;
}
function nexoraNewsStudioDeleteMeta($newsId) {
	$path = __PATH_CONFIGS__ . 'news_studio.json';
	$cfg = nexoraNewsStudioLoad(); unset($cfg[(string)(int)$newsId]);
	return file_put_contents($path, json_encode($cfg, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n", LOCK_EX) !== false;
}

function getPlayerClassAvatar($code=0, $htmlImageTag=true, $tooltip=true, $cssClass=null) {
	global $custom;
	$imageFileName = array_key_exists($code, $custom['character_class']) ? $custom['character_class'][$code][2] : 'avatar.jpg';
	$imageFullPath = __PATH_TEMPLATE_IMG__ . config('character_avatars_dir', true) . '/' . $imageFileName;
	$className = array_key_exists($code, $custom['character_class']) ? $custom['character_class'][$code][0] : '';
	if(!$htmlImageTag) return $imageFullPath;
	$result = '<img';
	if(check_value($cssClass)) $result .= ' class="'.$cssClass.'"';
	if($tooltip) $result .= ' data-toggle="tooltip" data-placement="top" title="'.$className.'" alt="'.$className.'"';
	$result .= ' src="'.$imageFullPath.'" />';
	return $result;
}

function playerProfile($playerName, $returnLinkOnly=false) {
	if(!config('player_profiles',true)) return $playerName;
	
	$profileConfig = loadConfigurations('profiles');
	if(is_array($profileConfig) && array_key_exists('encode', $profileConfig) && $profileConfig['encode'] == 1) {
		$link = __BASE_URL__.'profile/player/req/'.base64url_encode($playerName).'/';
		if(class_exists('NexoraRealm')) $link = NexoraRealm::appendToUrl($link);
		if($returnLinkOnly) return $link;
		return '<a href="'.$link.'" target="_blank">'.$playerName.'</a>';
	}
	$link = __BASE_URL__.'profile/player/req/'.urlencode($playerName).'/';
	if(class_exists('NexoraRealm')) $link = NexoraRealm::appendToUrl($link);
	if($returnLinkOnly) return $link;
	return '<a href="'.$link.'" target="_blank">'.$playerName.'</a>';
}

function guildProfile($guildName, $returnLinkOnly=false) {
	if(!config('guild_profiles',true)) return $guildName;
	
	$profileConfig = loadConfigurations('profiles');
	if(is_array($profileConfig) && array_key_exists('encode', $profileConfig) && $profileConfig['encode'] == 1) {
		$link = __BASE_URL__.'profile/guild/req/'.base64url_encode($guildName).'/';
		if(class_exists('NexoraRealm')) $link = NexoraRealm::appendToUrl($link);
		if($returnLinkOnly) return $link;
		return '<a href="'.$link.'" target="_blank">'.$guildName.'</a>';
	}
	$link = __BASE_URL__.'profile/guild/req/'.urlencode($guildName).'/';
	if(class_exists('NexoraRealm')) $link = NexoraRealm::appendToUrl($link);
	if($returnLinkOnly) return $link;
	return '<a href="'.$link.'" target="_blank">'.$guildName.'</a>';
}

function encodeCache($data, $pretty=false) {
	if($pretty) return json_encode($data, JSON_PRETTY_PRINT);
	return json_encode($data);
}

function decodeCache($data) {
	return json_decode($data, true);
}

function updateCacheFile($fileName, $data) {
	$file = class_exists('NexoraRealm') ? NexoraRealm::cachePath($fileName) : __PATH_CACHE__ . $fileName;
	return nexoraAtomicWrite($file, $data);
}

function loadCache($fileName, $realm=null) {
	$file = class_exists('NexoraRealm') ? NexoraRealm::cachePath($fileName, $realm) : __PATH_CACHE__ . $fileName;
	if(!file_exists($file)) return;
	if(!is_readable($file)) return;
	
	$cacheDataRaw = file_get_contents($file);
	if(!check_value($cacheDataRaw)) return;
	
	$cacheData = decodeCache($cacheDataRaw);
	if(!is_array($cacheData)) return;
	
	return $cacheData;
}

function checkBlockedIp() {
	if(in_array(access, array('cron'))) return;
	$ip = class_exists('NexoraSecurity') ? NexoraSecurity::clientIp() : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '');
	if(!$ip || !Validator::Ip($ip)) return true;
	$blockedIpCache = loadCache('blocked_ip.cache');
	if(!is_array($blockedIpCache)) return;
	if(in_array($ip, $blockedIpCache, true)) return true;
}

function getCronList() {
	$db = Connection::Database('Platform');
	$result = $db->query_fetch("SELECT * FROM ".WEBENGINE_CRON." ORDER BY cron_id ASC");
	if(!is_array($result)) return;
	return $result;
}

function addRankingMenuLink($phrase, $module, $filesExclusivity=null) {
	global $rankingMenuLinks;
	if(!check_value($phrase)) return;
	if(!check_value($module)) return;
	
	if(is_array($filesExclusivity)) {
		if(!in_array(strtolower(config('server_files',true)), array_map('strtolower', $filesExclusivity))) return;
	}
	
	if(lang($phrase) != 'ERROR') {
		$phrase = lang($phrase);
	}
	
	$rankingMenuLinks[] = array($phrase, $module);
}

function getRankingMenuLinks() {
	global $rankingMenuLinks;
	if(!is_array($rankingMenuLinks)) return;
	return $rankingMenuLinks;
}

function loadJsonFile($filePath) {
	if(!file_exists($filePath)) return;
	if(!is_readable($filePath)) return;
	$jsonData = file_get_contents($filePath);
	if($jsonData == false) return;
	$result = json_decode($jsonData, true);
	if(!is_array($result)) return;	
	return $result;
}

function getCountryCodeFromIp($ip) {
	$api = 'http://ip-api.com/json/'.$ip.'?fields=status,countryCode';
    $handle = curl_init();
    curl_setopt($handle, CURLOPT_URL, $api);
    curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
    $json = curl_exec($handle);
    curl_close($handle);
    if(!check_value($json)) return;
    $result = json_decode($json, true);
	if(!is_array($result)) return;
	if($result['status'] == 'fail') return;
	if(!check_value($result['countryCode'])) return;
	return $result['countryCode'];
}

function getCountryFlag($countryCode='default') {
	if(!check_value($countryCode)) $countryCode = 'default';
	return __PATH_COUNTRY_FLAGS__ . strtolower($countryCode) . '.gif';
}

function returnMapName($id=0) {
	global $custom;
	if(!is_array($custom['map_list'])) return 'Lorencia Bar';
	if(!array_key_exists($id, $custom['map_list'])) {
		if(config('error_reporting',true)) return 'Map Number ('.$id.')';
		return 'Lorencia Bar';
	}
	return $custom['map_list'][$id];
}

function returnPkLevel($id) {
	global $custom;
	if(!is_array($custom['pk_level'])) return;
	if(!array_key_exists($id, $custom['pk_level'])) return;
	return $custom['pk_level'][$id];
}

function getDirectoryListFromPath($path) {
	if(!file_exists($path)) return;
	$files = scandir($path);
	foreach($files as $row) {
		if(in_array($row, array('.','..'))) continue;
		if(!is_dir($path.$row)) continue;
		$result[] = $row;
	}
	if(!is_array($result)) return;
	return $result;
}

function getInstalledLanguagesList() {
	$languageDir = getDirectoryListFromPath(__PATH_LANGUAGES__);
	if(!is_array($languageDir)) return;
	foreach($languageDir as $language) {
		if(!file_exists(__PATH_LANGUAGES__.$language.'/language.php')) continue;
		$result[] = $language;
	}
	if(!is_array($result)) return;
	return $result;
}

// https://www.php.net/manual/en/function.filesize.php#106569
function readableFileSize($bytes, $decimals = 2) {
	$sz = 'BKMGTP';
	$factor = floor((strlen($bytes) - 1) / 3);
	return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
}

function getPlayerClass($class=0) {
	global $custom;
	if(!array_key_exists($class, $custom['character_class'])) return 'Unknown';
	return $custom['character_class'][$class][0];
}

function custom($index) {
	global $custom;
	if(!is_array($custom)) return;
	if(!array_key_exists($index, $custom)) return;
	return $custom[$index];
}

//https://base64.guru/developers/php/examples/base64url
function base64url_encode($data) {
	$b64 = base64_encode($data . '!we');
	if ($b64 === false) return false;
	$url = strtr($b64, '+/', '-_');
	return rtrim($url, '=');
}

function base64url_decode($data, $strict=false) {
	$b64 = strtr($data, '-_', '+/');
	$decoded = base64_decode($b64, $strict);
	$end = substr($decoded, -3);
	if($end !== '!we') return;
	return substr($decoded, 0, -3);
}
