ownerDocument; $node->appendChild($no->createCDATASection($cData)); return $this; } } trait TrRequestData { // const in trait supported in php8.2+ public const PATTERN_TEXT_LINE = '/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Cn}]/ui'; public const PATTERN_TEXT_BLOB = '/[\x00-\x09\x0B-\x1F\p{Cf}\p{Co}\p{Cs}\p{Cn}]/ui'; protected $_get = []; // fill with variables you that are going to be used; eg: protected $_post = []; // 'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdList'] protected $_cookie = []; private $filtered = false; private function initRequestData() : void { if ($this->filtered) return; // php bug? If INPUT_X is empty, filter_input_array returns null/fails // only really relevant for INPUT_POST // manuall set everything null in this case if ($this->_post) { if ($_POST) $this->_post = filter_input_array(INPUT_POST, $this->_post); else $this->_post = array_fill_keys(array_keys($this->_post), null); } if ($this->_get) { if ($_GET) $this->_get = filter_input_array(INPUT_GET, $this->_get); else $this->_get = array_fill_keys(array_keys($this->_get), null); } if ($this->_cookie) { if ($_COOKIE) $this->_cookie = filter_input_array(INPUT_COOKIE, $this->_cookie); else $this->_cookie = array_fill_keys(array_keys($this->_cookie), null); } $this->filtered = true; } private static function checkEmptySet(string $val) : bool { return $val === ''; // parameter is expected to be empty } private static function checkInt(string $val) : int { if (preg_match('/^-?\d+$/', $val)) return intVal($val); return 0; } private static function checkIdList(string $val) : array { if (preg_match('/^-?\d+(,-?\d+)*$/', $val)) return array_map('intVal', explode(',', $val)); return []; } private static function checkIntArray(string $val) : array { if (preg_match('/^-?\d+(:-?\d+)*$/', $val)) return array_map('intVal', explode(':', $val)); return []; } private static function checkIdListUnsigned(string $val) : array { if (preg_match('/^\d+(,\d+)*$/', $val)) return array_map('intVal', explode(',', $val)); return []; } private static function checkTextLine(string $val) : string { // trim non-printable chars return preg_replace(self::PATTERN_TEXT_LINE, '', $val); } private static function checkTextBlob(string $val) : string { // trim non-printable chars return preg_replace(self::PATTERN_TEXT_BLOB, '', $val); } } abstract class Util { /* NOTE! * FILE_ACCESS should be 0755 or less, but CLI and web interface both access the same files. While in CLI php is executed with the current users perms, * while the web interface is always executed by www-data (or whoever runs the web server) who does not own the files previously created via CLI. * And thus web interface actions fail with permission denied, unless the files are flagged +wx for everyone. * This probably has to be solved on the system level by having www-data and the CLI user share a group or something. */ public const FILE_ACCESS = 0777; public const DIR_ACCESS = 0777; private const GEM_SCORE_BASE_WOTLK = 16; // rare quality wotlk gem score private const GEM_SCORE_BASE_BC = 8; // rare quality bc gem score private static $perfectGems = null; public static $regions = array( 'us', 'eu', 'kr', 'tw', 'cn', 'dev' ); public static $ssdMaskFields = array( 'shoulderMultiplier', 'trinketMultiplier', 'weaponMultiplier', 'primBudged', 'rangedMultiplier', 'clothShoulderArmor', 'leatherShoulderArmor', 'mailShoulderArmor', 'plateShoulderArmor', 'weaponDPS1H', 'weaponDPS2H', 'casterDPS1H', 'casterDPS2H', 'rangedDPS', 'wandDPS', 'spellPower', null, null, 'tertBudged', 'clothCloakArmor', 'clothChestArmor', 'leatherChestArmor', 'mailChestArmor', 'plateChestArmor' ); public static $weightScales = array( 'agi', 'int', 'sta', 'spi', 'str', 'health', 'mana', 'healthrgn', 'manargn', 'armor', 'blockrtng', 'block', 'defrtng', 'dodgertng', 'parryrtng', 'resirtng', 'atkpwr', 'feratkpwr', 'armorpenrtng', 'critstrkrtng', 'exprtng', 'hastertng', 'hitrtng', 'splpen', 'splpwr', 'arcsplpwr', 'firsplpwr', 'frosplpwr', 'holsplpwr', 'natsplpwr', 'shasplpwr', 'dmg', 'mledps', 'rgddps', 'mledmgmin', 'rgddmgmin', 'mledmgmax', 'rgddmgmax', 'mlespeed', 'rgdspeed', 'arcres', 'firres', 'frores', 'holres', 'natres', 'shares', 'mleatkpwr', 'mlecritstrkrtng', 'mlehastertng', 'mlehitrtng', 'rgdatkpwr', 'rgdcritstrkrtng', 'rgdhastertng', 'rgdhitrtng', 'splcritstrkrtng', 'splhastertng', 'splhitrtng', 'spldmg', 'splheal', 'nsockets' ); public static $dateFormatInternal = "Y/m/d H:i:s"; public static $changeLevelString = '%s'; public static $setRatingLevelString = '%s'; public static $lvTabNoteString = '%s'; public static $filterResultString = '$$WH.sprintf(LANG.lvnote_filterresults, \'%s\')'; public static $tryFilteringString = '$$WH.sprintf(%s, %s, %s) + LANG.dash + LANG.lvnote_tryfiltering.replace(\'\', \'\')'; public static $tryFilteringEntityString = '$$WH.sprintf(LANG.lvnote_entitiesfound, %s, %s, %s) + LANG.dash + LANG.lvnote_tryfiltering.replace(\'\', \'\')'; public static $tryNarrowingString = '$$WH.sprintf(%s, %s, %s) + LANG.dash + LANG.lvnote_trynarrowing'; public static $dfnString = '%s'; public static $mapSelectorString = '%s (%d)'; public static $guideratingString = " $(document).ready(function() {\n $('#guiderating').append(GetStars(%.10F, %s, %u, %u));\n });"; public static $expansionString = array( // 3 & 4 unused .. obviously null, 'bc', 'wotlk', 'cata', 'mop' ); public static $tcEncoding = '0zMcmVokRsaqbdrfwihuGINALpTjnyxtgevElBCDFHJKOPQSUWXYZ123456789'; private static $notes = []; public static function addNote(string $note, int $uGroupMask = U_GROUP_EMPLOYEE, int $level = LOG_LEVEL_ERROR) : void { self::$notes[] = [$note, $uGroupMask, $level]; } public static function getNotes() : array { $notes = []; $severity = LOG_LEVEL_INFO; foreach (self::$notes as [$note, $uGroup, $level]) { if ($uGroup && !User::isInGroup($uGroup)) continue; if ($level < $severity) $severity = $level; $notes[] = $note; } return [$notes, $severity]; } private static $execTime = 0.0; public static function execTime(bool $set = false) : string { if ($set) { self::$execTime = microTime(true); return ''; } if (!self::$execTime) return ''; $newTime = microTime(true); $tDiff = $newTime - self::$execTime; self::$execTime = $newTime; return self::formatTime($tDiff * 1000, true); } public static function formatMoney(int $qty) : string { $money = ''; if ($qty >= 10000) { $g = floor($qty / 10000); $money .= ''.$g.' '; $qty -= $g * 10000; } if ($qty >= 100) { $s = floor($qty / 100); $money .= ''.$s.' '; $qty -= $s * 100; } if ($qty > 0) $money .= ''.$qty.''; return $money; } public static function parseTime(int $msec) : array { $time = [0, 0, 0, 0, 0]; if ($_ = ($msec % 1000)) $time[0] = $_; $sec = $msec / 1000; if ($sec >= 3600 * 24) { $time[4] = floor($sec / 3600 / 24); $sec -= $time[4] * 3600 * 24; } if ($sec >= 3600) { $time[3] = floor($sec / 3600); $sec -= $time[3] * 3600; } if ($sec >= 60) { $time[2] = floor($sec / 60); $sec -= $time[2] * 60; } if ($sec > 0) { $time[1] = (int)$sec; $sec -= $time[1]; } return $time; } public static function formatTime(int $msec, bool $short = false) : string { [$ms, $s, $m, $h, $d] = self::parseTime(abs($msec)); // \u00A0 is  , but also usable by js if ($short) { if ($_ = round($d / 365)) return $_."\u{00A0}".Lang::timeUnits('ab', 0); if ($_ = round($d / 30)) return $_."\u{00A0}".Lang::timeUnits('ab', 1); if ($_ = round($d / 7)) return $_."\u{00A0}".Lang::timeUnits('ab', 2); if ($_ = round($d)) return $_."\u{00A0}".Lang::timeUnits('ab', 3); if ($_ = round($h)) return $_."\u{00A0}".Lang::timeUnits('ab', 4); if ($_ = round($m)) return $_."\u{00A0}".Lang::timeUnits('ab', 5); if ($_ = round($s + $ms / 1000, 2)) return $_."\u{00A0}".Lang::timeUnits('ab', 6); if ($ms) return $ms."\u{00A0}".Lang::timeUnits('ab', 7); return '0 '.Lang::timeUnits('ab', 6); } else { $_ = $d + $h / 24; if ($_ > 1 && !($_ % 365)) // whole years return round(($d + $h / 24) / 364, 2)."\u{00A0}".Lang::timeUnits($d / 364 == 1 && !$h ? 'sg' : 'pl', 0); if ($_ > 1 && !($_ % 30)) // whole month return round(($d + $h / 24) / 30, 2)."\u{00A0}".Lang::timeUnits($d / 30 == 1 && !$h ? 'sg' : 'pl', 1); if ($_ > 1 && !($_ % 7)) // whole weeks return round(($d + $h / 24) / 7, 2)."\u{00A0}".Lang::timeUnits($d / 7 == 1 && !$h ? 'sg' : 'pl', 2); if ($d) return round($d + $h / 24, 2)."\u{00A0}".Lang::timeUnits($d == 1 && !$h ? 'sg' : 'pl', 3); if ($h) return round($h + $m / 60, 2)."\u{00A0}".Lang::timeUnits($h == 1 && !$m ? 'sg' : 'pl', 4); if ($m) return round($m + $s / 60, 2)."\u{00A0}".Lang::timeUnits($m == 1 && !$s ? 'sg' : 'pl', 5); if ($s) return round($s + $ms / 1000, 2)."\u{00A0}".Lang::timeUnits($s == 1 && !$ms ? 'sg' : 'pl', 6); if ($ms) return $ms." ".Lang::timeUnits($ms == 1 ? 'sg' : 'pl', 7); return '0 '.Lang::timeUnits('pl', 6); } } public static function formatTimeDiff(int $sec) : string { $delta = time() - $sec; [, $s, $m, $h, $d] = self::parseTime($delta * 1000); if ($delta > (1 * MONTH)) // use absolute return date(Lang::main('dateFmtLong'), $sec); else if ($delta > (2 * DAY)) // days ago return Lang::main('timeAgo', [$d . ' ' . Lang::timeUnits('pl', 3)]); else if ($h) // hours, minutes ago return Lang::main('timeAgo', [$h . ' ' . Lang::timeUnits('ab', 4) . ' ' . $m . ' ' . Lang::timeUnits('ab', 5)]); else if ($m) // minutes, seconds ago return Lang::main('timeAgo', [$m . ' ' . Lang::timeUnits('ab', 5) . ' ' . $s . ' ' . Lang::timeUnits('ab', 6)]); else // seconds ago return Lang::main('timeAgo', [$s . ' ' . Lang::timeUnits($s == 1 ? 'sg' : 'pl', 6)]); } // pageTexts, questTexts and mails public static function parseHtmlText(string|array $text, bool $markdown = false) : string|array { if (is_array($text)) { foreach ($text as &$t) $t = self::parseHtmlText($t, $markdown); return $text; } if (stristr($text, '')) // text is basically a html-document with weird linebreak-syntax { $pairs = array( '' => '', '' => '', '
' => '', '' => '', '