mode, $this->type, $this->typeId, $staff, User::$localeId, '-1', '-1']; // item special: can modify tooltips if (isset($this->enhancedTT)) $key[] = md5(serialize($this->enhancedTT)); return implode('_', $key); } protected function applyCCErrors() : void { if (!empty($_SESSION['error']['co'])) $this->coError = $_SESSION['error']['co']; if (!empty($_SESSION['error']['ss'])) $this->ssError = $_SESSION['error']['ss']; if (!empty($_SESSION['error']['vi'])) $this->viError = $_SESSION['error']['vi']; unset($_SESSION['error']); } } trait TrListPage { protected $category = null; protected $subCat = ''; protected $filter = []; protected $lvTabs = []; // most pages have this protected $redButtons = []; // see template/redButtons.tpl.php private $filterObj = null; protected function generateCacheKey(bool $withStaff = true) : string { $staff = intVal($withStaff && User::isInGroup(U_GROUP_EMPLOYEE)); // mode, type, typeId, employee-flag, localeId, $key = [$this->mode, $this->type, '-1', $staff, User::$localeId]; //category $key[] = $this->category ? implode('.', $this->category) : '-1'; // filter $key[] = $this->filterObj ? md5(serialize($this->filterObj)) : '-1'; return implode('_', $key); } } trait TrProfiler { protected $region = ''; protected $realm = ''; protected $realmId = 0; protected $battlegroup = ''; // not implemented, since no pserver supports it protected $subjectName = ''; protected $subjectGUID = 0; protected $sumSubjects = 0; protected $doResync = null; protected function generateCacheKey(bool $withStaff = true) : string { $staff = intVal($withStaff && User::isInGroup(U_GROUP_EMPLOYEE)); // mode, type, typeId, employee-flag, localeId, category, filter $key = [$this->mode, $this->type, $this->subject->getField('id'), $staff, User::$localeId, '-1', '-1']; return implode('_', $key); } protected function getSubjectFromUrl(string $pageParam) : void { if (!$pageParam) return; // cat[0] is always region // cat[1] is realm or bGroup (must be realm if cat[2] is set) // cat[2] is arena-team, guild or player $cat = explode('.', mb_strtolower($pageParam), 3); $cat = array_map('urldecode', $cat); if (array_search($cat[0], Util::$regions) === false) return; $this->region = $cat[0]; // if ($cat[1] == Profiler::urlize(Cfg::get('BATTLEGROUP'))) // $this->battlegroup = Cfg::get('BATTLEGROUP'); if (isset($cat[1])) { foreach (Profiler::getRealms() as $rId => $r) { if (Profiler::urlize($r['name'], true) == $cat[1]) { $this->realm = $r['name']; $this->realmId = $rId; if (isset($cat[2]) && mb_strlen($cat[2]) >= 2) $this->subjectName = $cat[2]; // cannot reconstruct original name from urlized form; match against special name field break; } } } } protected function initialSync() : void { $this->prepareContent(); $this->notFound = array( 'title' => sprintf(Lang::profiler('firstUseTitle'), $this->subjectName, $this->realm), 'msg' => '' ); if (isset($this->tabId)) $this->pageTemplate['activeTab'] = $this->tabId; $this->sumSQLStats(); $this->display('text-page-generic'); exit(); } protected function generatePath() : void { if ($this->region) { $this->path[] = $this->region; if ($this->realm) $this->path[] = Profiler::urlize($this->realm, true); // else // $this->path[] = Profiler::urlize(Cfg::get('BATTLEGROUP')); } } } class GenericPage { use TrRequestData; protected $tpl = ''; protected $reqUGroup = U_GROUP_NONE; protected $reqAuth = false; protected $mode = CACHE_TYPE_NONE; protected $contribute = CONTRIBUTE_NONE; protected $wowheadLink = 'https://wowhead.com/'; protected $jsGlobals = []; protected $lvData = []; protected $title = []; // for title-Element protected $name = ''; // for h1-Element protected $tabId = null; protected $gDataKey = false; // adds the dataKey to the user vars protected $notFound = []; protected $pageTemplate = []; protected $article = null; protected $articleUrl = ''; protected $editAccess = null; // 0 is valid access value, so null protected $scripts = array( [SC_JS_FILE, 'js/jquery-3.7.0.min.js', SC_FLAG_NO_TIMESTAMP ], [SC_JS_FILE, 'js/basic.js' ], [SC_JS_FILE, 'widgets/power.js', SC_FLAG_NO_TIMESTAMP | SC_FLAG_APPEND_LOCALE], [SC_JS_FILE, 'js/locale_%s.js', SC_FLAG_LOCALIZED ], [SC_JS_FILE, 'js/global.js' ], [SC_JS_FILE, 'js/locale.js' ], [SC_JS_FILE, 'js/Markup.js' ], [SC_CSS_FILE, 'css/basic.css' ], [SC_CSS_FILE, 'css/global.css' ], [SC_CSS_FILE, 'css/aowow.css' ], [SC_CSS_FILE, 'css/locale_%s.css', SC_FLAG_LOCALIZED ] ); // private vars don't get cached private $time = 0; private $cacheDir = 'cache/template/'; private $jsgBuffer = []; private $gPageInfo = []; private $gUser = []; private $gFavorites = []; private $community = ['co' => [], 'sc' => [], 'vi' => []]; private $announcements = []; private $cacheLoaded = []; private $skipCache = 0x0; private $memcached = null; private $mysql = ['time' => 0, 'count' => 0]; private $js = []; private $css = []; private $headerLogo = ''; private $fullParams = ''; private $lvTemplates = array( 'achievement' => ['template' => 'achievement', 'id' => 'achievements', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_achievements' ], 'areatrigger' => ['template' => 'areatrigger', 'id' => 'areatrigger', 'parent' => 'lv-generic', 'data' => [], ], 'calendar' => ['template' => 'holidaycal', 'id' => 'calendar', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_calendar' ], 'class' => ['template' => 'classs', 'id' => 'classes', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_classes' ], 'commentpreview' => ['template' => 'commentpreview', 'id' => 'comments', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_comments' ], 'npc' => ['template' => 'npc', 'id' => 'npcs', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_npcs' ], 'currency' => ['template' => 'currency', 'id' => 'currencies', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_currencies' ], 'emote' => ['template' => 'emote', 'id' => 'emotes', 'parent' => 'lv-generic', 'data' => [] ], 'enchantment' => ['template' => 'enchantment', 'id' => 'enchantments', 'parent' => 'lv-generic', 'data' => [] ], 'event' => ['template' => 'holiday', 'id' => 'holidays', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_holidays' ], 'faction' => ['template' => 'faction', 'id' => 'factions', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_factions' ], 'genericmodel' => ['template' => 'genericmodel', 'id' => 'same-model-as', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_samemodelas' ], 'icongallery' => ['template' => 'icongallery', 'id' => 'icons', 'parent' => 'lv-generic', 'data' => [] ], 'item' => ['template' => 'item', 'id' => 'items', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_items' ], 'itemset' => ['template' => 'itemset', 'id' => 'itemsets', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_itemsets' ], 'mail' => ['template' => 'mail', 'id' => 'mails', 'parent' => 'lv-generic', 'data' => [] ], 'model' => ['template' => 'model', 'id' => 'gallery', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_gallery' ], 'object' => ['template' => 'object', 'id' => 'objects', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_objects' ], 'pet' => ['template' => 'pet', 'id' => 'hunter-pets', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_pets' ], 'profile' => ['template' => 'profile', 'id' => 'profiles', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_profiles' ], 'quest' => ['template' => 'quest', 'id' => 'quests', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_quests' ], 'race' => ['template' => 'race', 'id' => 'races', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_races' ], 'replypreview' => ['template' => 'replypreview', 'id' => 'comment-replies', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_commentreplies'], 'reputationhistory' => ['template' => 'reputationhistory', 'id' => 'reputation', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_reputation' ], 'screenshot' => ['template' => 'screenshot', 'id' => 'screenshots', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_screenshots' ], 'skill' => ['template' => 'skill', 'id' => 'skills', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_skills' ], 'sound' => ['template' => 'sound', 'id' => 'sounds', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.types[19][2]' ], 'spell' => ['template' => 'spell', 'id' => 'spells', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_spells' ], 'title' => ['template' => 'title', 'id' => 'titles', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_titles' ], 'topusers' => ['template' => 'topusers', 'id' => 'topusers', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.topusers' ], 'video' => ['template' => 'video', 'id' => 'videos', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_videos' ], 'zone' => ['template' => 'zone', 'id' => 'zones', 'parent' => 'lv-generic', 'data' => [], 'name' => '$LANG.tab_zones' ], 'guide' => ['template' => 'guide', 'id' => 'guides', 'parent' => 'lv-generic', 'data' => [], ] ); public function __construct(string $pageCall = '', string $pageParam = '') { $this->time = microtime(true); $this->initRequestData(); $this->title[] = Cfg::get('NAME'); $this->fullParams = $pageCall; if ($pageParam) $this->fullParams .= '='.$pageParam; $cacheDir = Cfg::get('CACHE_DIR'); if ($cacheDir && Util::writeDir($cacheDir)) $this->cacheDir = mb_substr($cacheDir, -1) != '/' ? $cacheDir.'/' : $cacheDir; // force page refresh if (isset($_GET['refresh']) && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_DEV)) { if ($_GET['refresh'] == 'filecache') $this->skipCache = CACHE_MODE_FILECACHE; else if ($_GET['refresh'] == 'memcached') $this->skipCache = CACHE_MODE_MEMCACHED; else if ($_GET['refresh'] == '') $this->skipCache = CACHE_MODE_FILECACHE | CACHE_MODE_MEMCACHED; } // prep js+css includes $parentVars = get_class_vars(__CLASS__); if ($parentVars['scripts'] != $this->scripts) // additions set in child class $this->scripts = array_merge($parentVars['scripts'], $this->scripts); $this->addScript(...$this->scripts); if (User::isInGroup(U_GROUP_STAFF | U_GROUP_SCREENSHOT | U_GROUP_VIDEO)) $this->addScript([SC_CSS_FILE, 'css/staff.css'], [SC_JS_FILE, 'js/staff.js']); // display modes if (isset($_GET['power']) && method_exists($this, 'generateTooltip')) $this->mode = CACHE_TYPE_TOOLTIP; else if (isset($_GET['xml']) && method_exists($this, 'generateXML')) $this->mode = CACHE_TYPE_XML; else { // get alt header logo if ($ahl = DB::Aowow()->selectCell('SELECT altHeaderLogo FROM ?_home_featuredbox WHERE ?d BETWEEN startDate AND endDate ORDER BY id DESC', time())) $this->headerLogo = Util::defStatic($ahl); $this->gUser = User::getUserGlobals(); $this->gFavorites = User::getFavorites(); $this->pageTemplate['pageName'] = strtolower($pageCall); $this->wowheadLink = sprintf(WOWHEAD_LINK, Util::$subDomains[User::$localeId], $pageCall, $pageParam); if (!$this->isValidPage()) $this->error(); } // requires authed user if ($this->reqAuth && !User::$id) $this->forwardToSignIn($_SERVER['QUERY_STRING'] ?? ''); // restricted access if ($this->reqUGroup && !User::isInGroup($this->reqUGroup)) { if (User::$id) $this->error(); else $this->forwardToSignIn($_SERVER['QUERY_STRING'] ?? ''); } if (Cfg::get('MAINTENANCE') && !User::isInGroup(U_GROUP_EMPLOYEE)) $this->maintenance(); else if (Cfg::get('MAINTENANCE') && User::isInGroup(U_GROUP_EMPLOYEE)) Util::addNote('Maintenance mode enabled!'); // get errors from previous page from session and apply to template if (method_exists($this, 'applyCCErrors')) $this->applyCCErrors(); } /**********/ /* Checks */ /**********/ // "template_exists" private function isSaneInclude(string $path, string $file) : bool { if (preg_match('/[^\w\-]/i', str_replace('admin/', '', $file))) return false; if (!is_file($path.$file.'.tpl.php')) return false; return true; } // has a valid combination of categories private function isValidPage() : bool { if (!isset($this->category) || empty($this->validCats)) return true; $c = $this->category; // shorthand switch (count($c)) { case 0: // no params works always return true; case 1: // null is valid || value in a 1-dim-array || (key for a n-dim-array && ( has more subcats || no further subCats )) $filtered = array_filter($this->validCats, function ($x) { return is_int($x); }); return $c[0] === null || in_array($c[0], $filtered) || (!empty($this->validCats[$c[0]]) && (is_array($this->validCats[$c[0]]) || $this->validCats[$c[0]] === true)); case 2: // first param has to be a key. otherwise invalid if (!isset($this->validCats[$c[0]])) return false; // check if the sub-array is n-imensional if (is_array($this->validCats[$c[0]]) && count($this->validCats[$c[0]]) == count($this->validCats[$c[0]], COUNT_RECURSIVE)) return in_array($c[1], $this->validCats[$c[0]]); // second param is value in second level array else return isset($this->validCats[$c[0]][$c[1]]); // check if params is key of another array case 3: // 3 params MUST point to a specific value return isset($this->validCats[$c[0]][$c[1]]) && in_array($c[2], $this->validCats[$c[0]][$c[1]]); } return false; } /****************/ /* Prepare Page */ /****************/ // get from cache ?: run generators protected function prepareContent() : void { if (!$this->loadCache()) { $this->generateContent(); $this->generatePath(); $this->generateTitle(); $this->addArticle(); $this->applyGlobals(); $this->saveCache(); } if ($this instanceof GuidePage) { $this->gPageInfo = ['name' => $this->name]; if (isset($this->author)) $this->gPageInfo['author'] = $this->author; } else if (isset($this->type) && isset($this->typeId)) { $this->gPageInfo = array( // varies slightly for special pages like maps, user-dashboard or profiler 'type' => $this->type, 'typeId' => $this->typeId, 'name' => $this->name ); } // only adds edit links to the staff menu: precursor to guides? if (!empty($this->articleUrl) && !($this instanceof GuidePage && $this->show == GuidePage::SHOW_GUIDE)) { $this->gPageInfo = array( 'articleUrl' => $this->fullParams, // is actually be the url-param 'editAccess' => $this->editAccess ?? (U_GROUP_ADMIN | U_GROUP_EDITOR | U_GROUP_BUREAU) ); } if (!empty($this->path)) $this->pageTemplate['breadcrumb'] = $this->path; if (!empty($this->filter)) $this->pageTemplate['filter'] = empty($this->filter['query']) ? 0 : 1; if (method_exists($this, 'postCache')) // e.g. update dates for events and such $this->postCache(); // determine contribute tabs if (isset($this->subject)) { $x = get_class($this->subject); $this->contribute = $x::$contribute; } if ($this->contribute & CONTRIBUTE_CO) $this->community['co'] = CommunityContent::getComments($this->type, $this->typeId); if ($this->contribute & CONTRIBUTE_SS) $this->community['ss'] = CommunityContent::getScreenshots($this->type, $this->typeId); if ($this->contribute & CONTRIBUTE_VI) $this->community['vi'] = CommunityContent::getVideos($this->type, $this->typeId); // as comments are not cached, those globals cant be either if ($this->contribute != CONTRIBUTE_NONE) { $this->extendGlobalData(CommunityContent::getJSGlobals()); $this->applyGlobals(); } $this->time = microtime(true) - $this->time; $this->sumSQLStats(); } public function addScript(array ...$structs) : void { array_walk($structs, function(&$x) { $x = array_pad($x, 3, 0); }); foreach ($structs as [$type, $str, $flags]) { if (empty($str)) { trigger_error('GenericPage::addScript - content empty', E_USER_WARNING); continue; } $dynData = strpos($str, '?data=') === 0; $app = []; // insert locale string if ($flags & SC_FLAG_LOCALIZED) $str = sprintf($str, User::$localeString); if ($dynData) { $app[] = 'locale='.User::$localeId; $app[] = 't='.$_SESSION['dataKey']; } else if (($flags & SC_FLAG_APPEND_LOCALE) && User::$localeId) $app[] = 'lang='.Util::$subDomains[User::$localeId]; // append anti-cache timestamp if (!($flags & SC_FLAG_NO_TIMESTAMP) && !$dynData) if ($type == SC_JS_FILE || $type == SC_CSS_FILE) $app[] = filemtime('static/'.$str) ?: 0; if ($app) $str .= ($dynData ? '&' : '?').implode('&', $app); switch ($type) { case SC_JS_FILE: $str = ($dynData ? Cfg::get('HOST_URL') : Cfg::get('STATIC_URL')).'/'.$str; case SC_JS_STRING: if ($flags & SC_FLAG_PREFIX) array_unshift($this->js, [$type, $str]); else $this->js[] = [$type, $str]; break; case SC_CSS_FILE: $str = Cfg::get('STATIC_URL').'/'.$str; case SC_CSS_STRING: if ($flags & SC_FLAG_PREFIX) array_unshift($this->css, [$type, $str]); else $this->css[] = [$type, $str]; break; default: trigger_error('GenericPage::addScript - unknown script type #'.$type, E_USER_WARNING); } } } // get article & static infobox (run before processing jsGlobals) private function addArticle() :void { if (isset($this->article)) return; $article = []; if (isset($this->guideRevision)) $article = DB::Aowow()->selectRow('SELECT `article`, `quickInfo`, `locale`, `editAccess` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d AND `rev` = ?d', Type::GUIDE, $this->typeId, $this->guideRevision); else if (!empty($this->articleUrl)) $article = DB::Aowow()->selectRow('SELECT `article`, `quickInfo`, `locale`, `editAccess` FROM ?_articles WHERE `url` = ? AND `locale` IN (?a) ORDER BY `locale` DESC, `rev` DESC LIMIT 1', $this->articleUrl, [User::$localeId, LOCALE_EN]); else if (!empty($this->type) && isset($this->typeId)) $article = DB::Aowow()->selectRow('SELECT `article`, `quickInfo`, `locale`, `editAccess` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d AND `locale` IN (?a) ORDER BY `locale` DESC, `rev` DESC LIMIT 1', $this->type, $this->typeId, [User::$localeId, LOCALE_EN]); if ($article) { if ($article['article']) (new Markup($article['article']))->parseGlobalsFromText($this->jsgBuffer); if ($article['quickInfo']) (new Markup($article['quickInfo']))->parseGlobalsFromText($this->jsgBuffer); $this->article = array( 'text' => Util::jsEscape(Util::defStatic($article['article'])), 'params' => [] ); if (!empty($this->type) && isset($this->typeId)) $this->article['params']['dbpage'] = true; // convert U_GROUP_* to MARKUP.CLASS_* (as seen in js-object Markup) if ($article['editAccess'] & (U_GROUP_ADMIN | U_GROUP_VIP | U_GROUP_DEV)) $this->article['params']['allow'] = '$Markup.CLASS_ADMIN'; else if ($article['editAccess'] & U_GROUP_STAFF) $this->article['params']['allow'] = '$Markup.CLASS_STAFF'; else if ($article['editAccess'] & U_GROUP_PREMIUM) $this->article['params']['allow'] = '$Markup.CLASS_PREMIUM'; else if ($article['editAccess'] & U_GROUP_PENDING) $this->article['params']['allow'] = '$Markup.CLASS_PENDING'; else $this->article['params']['allow'] = '$Markup.CLASS_USER'; $this->editAccess = $article['editAccess']; if (empty($this->infobox) && !empty($article['quickInfo'])) $this->infobox = $article['quickInfo']; if ($article['locale'] != User::$localeId) $this->article['params']['prepend'] = '
'.Lang::main('langOnly', [Lang::lang($article['locale'])]).'
'; if (method_exists($this, 'postArticle')) // e.g. update variables in article $this->postArticle($this->article['text']); } } // get announcements and notes for user private function addAnnouncements(bool $pagespecific = true) : void { if (!isset($this->announcements)) $this->announcements = []; // display occured notices if (([$notes, $level] = Util::getNotes()) && $notes) { array_unshift($notes, 'One or more issues occured, while generating this page.'); $colors = array( // [border, text] CLI::LOG_ERROR => ['C50F1F', 'E51223'], CLI::LOG_WARN => ['C19C00', 'E5B700'], CLI::LOG_INFO => ['3A96DD', '42ADFF'] ); $this->announcements[0] = array( 'parent' => 'announcement-0', 'id' => 0, 'mode' => 1, 'status' => 1, 'name' => 'internal error', 'style' => 'color: #'.($colors[$level][1] ?? 'fff').'; font-weight: bold; font-size: 14px; padding-left: 40px; background-image: url('.Cfg::get('STATIC_URL').'/images/announcements/warn-small.png); background-size: 15px 15px; background-position: 12px center; border: dashed 2px #'.($colors[$level][0] ?? 'fff').';', 'text' => '[span]'.implode("[br]", $notes).'[/span]' ); } // fetch announcements if ($this->pageTemplate['pageName']) { $ann = DB::Aowow()->Select('SELECT ABS(id) AS ARRAY_KEY, a.* FROM ?_announcements a WHERE status = 1 AND (page = ? OR page = "*") AND (groupMask = 0 OR groupMask & ?d)', $pagespecific ? $this->pageTemplate['pageName'] : '', User::$groups); foreach ($ann as $k => $v) { if ($t = Util::localizedString($v, 'text')) { $_ = array( 'parent' => 'announcement-'.$k, 'id' => $v['id'], 'mode' => $v['mode'], 'status' => $v['status'], 'name' => $v['name'], 'text' => Util::defStatic($t) ); if ($v['style']) // may be empty $_['style'] = Util::defStatic($v['style']); $this->announcements[$k] = $_; } } } } protected function getCategoryFromUrl(string $urlParam) : void { $arr = explode('.', $urlParam); $params = []; foreach ($arr as $v) if (is_numeric($v)) $params[] = (int)$v; $this->category = $params; } protected function forwardToSignIn(string $next = '') : void { $next = $next ? '&next='.$next : ''; header('Location: ?account=signin'.$next, true, 302); } protected function sumSQLStats() : void { Util::arraySumByKey($this->mysql, DB::Aowow()->getStatistics(), DB::World()->getStatistics()); } /*******************/ /* Special Display */ /*******************/ // unknown entry public function notFound(string $title = '', string $msg = '') : void { if ($this->mode == CACHE_TYPE_TOOLTIP && method_exists($this, 'generateTooltip')) { header(MIME_TYPE_JSON); echo $this->generateTooltip(); } else if ($this->mode == CACHE_TYPE_XML && method_exists($this, 'generateXML')) { header(MIME_TYPE_XML); echo $this->generateXML(); } else { header('HTTP/1.0 404 Not Found', true, 404); array_unshift($this->title, Lang::main('nfPageTitle')); $this->contribute = CONTRIBUTE_NONE; $this->notFound = array( 'title' => isset($this->typeId) ? Util::ucFirst($title).' #'.$this->typeId : $title, 'msg' => !$msg && isset($this->typeId) ? sprintf(Lang::main('pageNotFound'), $title) : $msg ); if (isset($this->tabId)) $this->pageTemplate['activeTab'] = $this->tabId; $this->sumSQLStats(); $this->display('list-page-generic'); } exit(); } // unknown page public function error() : void { // $this->path = null; // $this->tabId = null; $this->articleUrl = 'page-not-found'; $this->title[] = Lang::main('errPageTitle'); $this->name = Lang::main('errPageTitle'); // $this->lvTabs = []; $this->addArticle(); $this->sumSQLStats(); header('HTTP/1.0 404 Not Found', true, 404); $this->display('list-page-generic'); exit(); } // display brb gnomes public function maintenance() : void { header('HTTP/1.0 503 Service Temporarily Unavailable', true, 503); header('Retry-After: '.(3 * HOUR)); $this->display('maintenance'); exit(); } /*******************/ /* General Display */ /*******************/ // load given template string or GenericPage::$tpl public function display(string $override = '') : void { // Heisenbug: IE11 and FF32 will sometimes (under unknown circumstances) cache 302 redirects and stop // re-requesting them from the server but load them from local cache, thus breaking menu features. Util::sendNoCacheHeader(); if ($this->mode == CACHE_TYPE_TOOLTIP && method_exists($this, 'generateTooltip')) $this->displayExtra([$this, 'generateTooltip']); else if ($this->mode == CACHE_TYPE_XML && method_exists($this, 'generateXML')) $this->displayExtra([$this, 'generateXML'], MIME_TYPE_XML); else { if (isset($this->tabId)) $this->pageTemplate['activeTab'] = $this->tabId; if ($override) { $this->addAnnouncements(false); include('template/pages/'.$override.'.tpl.php'); die(); } else if ($this->tpl) { $this->prepareContent(); if (!$this->isSaneInclude('template/pages/', $this->tpl)) { trigger_error('Error: nonexistant template requested: template/pages/'.$this->tpl.'.tpl.php', E_USER_ERROR); $this->error(); } $this->addAnnouncements(); if (isset($this->lvTabs)) { $this->lvTabs = array_filter($this->lvTabs); array_walk($this->lvTabs, function (&$x) { $x = array_pad($x, 3, null); }); } include('template/pages/'.$this->tpl.'.tpl.php'); die(); } else $this->error(); } } // generate and cache public function displayExtra(callable $generator, string $mime = MIME_TYPE_JSON) : void { $outString = ''; if (!$this->loadCache($outString)) { $outString = $generator(); $this->saveCache($outString); } header($mime); if (method_exists($this, 'postCache') && ($pc = $this->postCache())) die(sprintf($outString, ...$pc)); else die($outString); } // load jsGlobal public function writeGlobalVars() : string { $buff = ''; if (!empty($this->guideRating)) $buff .= sprintf(Util::$guideratingString, ...$this->guideRating); foreach ($this->jsGlobals as $type => $struct) { $buff .= " var _ = ".$struct[0].';'; foreach ($struct[1] as $key => $data) { foreach ($data as $k => $v) { // localizes expected fields .. except for icons .. icons are special if (in_array($k, ['name', 'namefemale']) && $struct[0] != Type::getJSGlobalString(Type::ICON)) { $data[$k.'_'.User::$localeString] = $v; unset($data[$k]); } } $buff .= ' _['.(is_numeric($key) ? $key : "'".$key."'")."]=".Util::toJSON($data).';'; } $buff .= "\n"; if (!empty($this->typeId) && !empty($struct[2][$this->typeId])) { $x = $struct[2][$this->typeId]; // spell if (!empty($x['tooltip'])) // spell + item $buff .= "\n _[".$x['id'].'].tooltip_'.User::$localeString.' = '.Util::toJSON($x['tooltip']).";\n"; if (!empty($x['buff'])) // spell $buff .= " _[".$x['id'].'].buff_'.User::$localeString.' = '.Util::toJSON($x['buff']).";\n"; if (!empty($x['spells'])) // spell + item $buff .= " _[".$x['id'].'].spells_'.User::$localeString.' = '.Util::toJSON($x['spells']).";\n"; if (!empty($x['buffspells'])) // spell $buff .= " _[".$x['id'].'].buffspells_'.User::$localeString.' = '.Util::toJSON($x['buffspells']).";\n"; $buff .= "\n"; } } return $buff; } protected function fmtCreateIcon(int $iconIdx, int $type, int $typeId, int $pad = 0, string $element = 'icontab-icon', int $size = 1, string $num = '', string $qty = '') : string { // $element, $iconTabIdx, [typeId, size, num, qty] $createIconString = "\$WH.ge('%s%d').appendChild(%s.createIcon(%s));\n"; if ($size < 0 || $size > 3) { trigger_error('GenericPage::fmtCreateIcon - invalid icon size '.$size.'. Normalied to 1 [small]', E_USER_WARNING); $size = 1; } $jsg = Type::getJSGlobalString($type); if (!$jsg) { trigger_error('GenericPage::fmtCreateIcon - invalid type '.$type.'. Assumed '.Type::SPELL.' [spell]', E_USER_WARNING); $jsg = Type::getJSGlobalString(Type::SPELL); } $params = [$typeId, $size]; if ($num || $qty) $params[] = is_numeric($num) ? $num : "'".$num."'"; if ($qty) $params[] = is_numeric($qty) ? $qty : "'".$qty."'"; // $WH.ge('icontab-icon1').appendChild(g_spells.createIcon(40120, 1, '1-4', 0)); return str_repeat(' ', $pad) . sprintf($createIconString, $element, $iconIdx, $jsg, implode(', ', $params)); } protected function fmtStaffTip(string $text, string $tip) : string { if (User::isInGroup(U_GROUP_EMPLOYEE)) return sprintf(Util::$dfnString, $tip, $text); else return $text; } // load brick protected function brick(string $file, array $localVars = []) : void { foreach ($localVars as $n => $v) $$n = $v; if (!$this->isSaneInclude('template/bricks/', $file)) trigger_error('Nonexistant template requested: template/bricks/'.$file.'.tpl.php', E_USER_ERROR); else include('template/bricks/'.$file.'.tpl.php'); } // load listview addIns protected function lvBrick(string $file) : void { if (!$this->isSaneInclude('template/listviews/', $file)) trigger_error('Nonexistant Listview addin requested: template/listviews/'.$file.'.tpl.php', E_USER_ERROR); else include('template/listviews/'.$file.'.tpl.php'); } // load brick with more text then vars protected function localizedBrick(string $file, int $loc = LOCALE_EN) : void { if (!$this->isSaneInclude('template/localized/', $file.'_'.$loc)) { if ($loc == LOCALE_EN || !$this->isSaneInclude('template/localized/', $file.'_'.LOCALE_EN)) trigger_error('Nonexistant template requested: template/localized/'.$file.'_'.$loc.'.tpl.php', E_USER_ERROR); else include('template/localized/'.$file.'_'.LOCALE_EN.'.tpl.php'); } else include('template/localized/'.$file.'_'.$loc.'.tpl.php'); } /**********************/ /* Prepare js-Globals */ /**********************/ // add typeIds that should be displayed as jsGlobal on the page public function extendGlobalIds(int $type, int ...$ids) : void { if (!$type || !$ids) return; if (!isset($this->jsgBuffer[$type])) $this->jsgBuffer[$type] = []; foreach ($ids as $id) $this->jsgBuffer[$type][] = $id; } // add jsGlobals or typeIds (can be mixed in one array: TYPE => [mixeddata]) to display on the page public function extendGlobalData(array $data, ?array $extra = null) : void { foreach ($data as $type => $globals) { if (!is_array($globals) || !$globals) continue; $this->initJSGlobal($type); // can be id => data // or idx => id // and may be mixed foreach ($globals as $k => $v) { if (is_array($v)) $this->jsGlobals[$type][1][$k] = $v; else if (is_numeric($v)) $this->extendGlobalIds($type, $v); } } if (is_array($extra) && $extra) $this->jsGlobals[$type][2] = $extra; } // init store for type private function initJSGlobal(int $type) : void { $jsg = &$this->jsGlobals; // shortcut if (isset($jsg[$type])) return; if ($tpl = Type::getJSGlobalTemplate($type)) $jsg[$type] = $tpl; } // lookup jsGlobals from collected typeIds private function applyGlobals() : void { foreach ($this->jsgBuffer as $type => $ids) { foreach ($ids as $k => $id) // filter already generated data, maybe we can save a lookup or two if (isset($this->jsGlobals[$type][1][$id])) unset($ids[$k]); if (!$ids) continue; $this->initJSGlobal($type); $obj = Type::newList($type, [Cfg::get('SQL_LIMIT_NONE'), ['id', array_unique($ids, SORT_NUMERIC)]]); if (!$obj) continue; $this->extendGlobalData($obj->getJSGlobals(GLOBALINFO_SELF)); // delete processed ids $this->jsgBuffer[$type] = []; } } /*********/ /* Cache */ /*********/ // visible properties or given strings are cached private function saveCache(string $saveString = '') : void { if ($this->mode == CACHE_TYPE_NONE) return; if (!Cfg::get('CACHE_MODE') || Cfg::get('DEBUG')) return; $noCache = ['coError', 'ssError', 'viError']; $cKey = $this->generateCacheKey(); $cache = []; if (!$saveString) { foreach ($this as $key => $val) { try { $rp = new ReflectionProperty($this, $key); if ($rp && ($rp->isPublic() || $rp->isProtected())) if (!in_array($key, $noCache)) $cache[$key] = $val; } catch (ReflectionException $e) { } // shut up! } } else $cache = $saveString; if (Cfg::get('CACHE_MODE') & CACHE_MODE_MEMCACHED) { // on &refresh also clear related if ($this->skipCache == CACHE_MODE_MEMCACHED) { $oldMode = $this->mode; for ($i = 1; $i < 5; $i++) // page (1), tooltips (2), searches (3) and xml (4) { $this->mode = $i; for ($j = 0; $j < 2; $j++) // staff / normal $this->memcached()->delete($this->generateCacheKey($j)); } $this->mode = $oldMode; } $data = array( 'timestamp' => time(), 'revision' => AOWOW_REVISION, 'isString' => $saveString ? 1 : 0, 'data' => $cache ); $this->memcached()->set($cKey, $data); } if (Cfg::get('CACHE_MODE') & CACHE_MODE_FILECACHE) { $data = time()." ".AOWOW_REVISION." ".($saveString ? '1' : '0')."\n"; $data .= gzcompress($saveString ? $cache : serialize($cache), 9); // on &refresh also clear related if ($this->skipCache == CACHE_MODE_FILECACHE) { $oldMode = $this->mode; for ($i = 1; $i < 5; $i++) // page (1), tooltips (2), searches (3) and xml (4) { $this->mode = $i; for ($j = 0; $j < 2; $j++) // staff / normal { $key = $this->generateCacheKey($j); if (file_exists($this->cacheDir.$key)) unlink($this->cacheDir.$key); } } $this->mode = $oldMode; } file_put_contents($this->cacheDir.$cKey, $data); } } private function loadCache(string &$saveString = '') : bool { if ($this->mode == CACHE_TYPE_NONE) return false; if (!Cfg::get('CACHE_MODE') || Cfg::get('DEBUG')) return false; $cKey = $this->generateCacheKey(); $rev = $type = $cache = $data = null; if ((Cfg::get('CACHE_MODE') & CACHE_MODE_MEMCACHED) && !($this->skipCache & CACHE_MODE_MEMCACHED)) { if ($cache = $this->memcached()->get($cKey)) { $type = $cache['isString']; $data = $cache['data']; if ($cache['timestamp'] + Cfg::get('CACHE_DECAY') <= time() || $cache['revision'] != AOWOW_REVISION) $cache = null; else $this->cacheLoaded = [CACHE_MODE_MEMCACHED, $cache['timestamp']]; } } if (!$cache && (Cfg::get('CACHE_MODE') & CACHE_MODE_FILECACHE) && !($this->skipCache & CACHE_MODE_FILECACHE)) { if (!file_exists($this->cacheDir.$cKey)) return false; $cache = file_get_contents($this->cacheDir.$cKey); if (!$cache) return false; $cache = explode("\n", $cache, 2); $data = $cache[1]; if (substr_count($cache[0], ' ') < 2) return false; [$time, $rev, $type] = explode(' ', $cache[0]); if ($time + Cfg::get('CACHE_DECAY') <= time() || $rev != AOWOW_REVISION) $cache = null; else { $this->cacheLoaded = [CACHE_MODE_FILECACHE, $time]; $data = gzuncompress($data); } } if (!$cache) return false; if ($type == '0') { if (is_string($data)) $data = unserialize($data); foreach ($data as $k => $v) $this->$k = $v; return true; } else if ($type == '1') { $saveString = $data; return true; } return false; } private function memcached() : Memcached { if (!$this->memcached && (Cfg::get('CACHE_MODE') & CACHE_MODE_MEMCACHED)) { $this->memcached = new Memcached(); $this->memcached->addServer('localhost', 11211); } return $this->memcached; } } ?>