params = explode('.', $params); foreach ($_POST as $k => $v) { Util::checkNumeric($v); $this->post[$k] = $v; } foreach ($_GET as $k => $v) $this->get[$k] = Util::checkNumeric($v) ? $v : is_string($v) ? trim(urldecode($v)) : $v; } private function get($var) { if (isset($this->get[$var])) return $this->get[$var]; return null; } private function post($var) { if (isset($this->post[$var])) return $this->post[$var]; return null; } public function handle($what) { $f = 'handle'.ucFirst(str_replace(['-', '_'], '', $what)); if (!$what || !method_exists($this, $f)) return null; Util::sendNoCacheHeader(); return $this->$f(); } /* responses header() */ private function handleGotocomment() { if (!$this->get('id')) return; if ($_ = DB::Aowow()->selectRow('SELECT IFNULL(c2.id, c1.id) AS id, IFNULL(c2.type, c1.type) AS type, IFNULL(c2.typeId, c1.typeId) AS typeId FROM ?_comments c1 LEFT JOIN ?_comments c2 ON c1.replyTo = c2.id WHERE c1.id = ?d', $this->get('id'))) header('Location: ?'.Util::$typeStrings[$_['type']].'='.$_['typeId'].'#comments:id='.$_['id'].($_['id'] != $this->get('id') ? ':reply='.$this->get('id') : null), true, 302); } /* responses */ private function handleData() { if (is_numeric($this->get('locale'))) User::useLocale($this->get('locale')); $result = ''; // different data can be strung together foreach ($this->params as $set) { // requires valid token to hinder automated access if ($set != 'item-scaling') if (!$this->get('t') || empty($_SESSION['dataKey']) || $this->get('t') != $_SESSION['dataKey']) continue; switch ($set) { /* issue on no initial data: when we loadOnDemand, the jScript tries to generate the catg-tree before it is initialized it cant be initialized, without loading the data as empty catg are omitted loading the data triggers the generation of the catg-tree */ case 'factions': $result .= $this->data_loadProfilerData($set); break; case 'companions': $result .= $this->data_loadProfilerData($set, '778'); break; case 'mounts': $result .= $this->data_loadProfilerData($set, '777'); break; case 'quests': // &partial: im not doing this right // it expects a full quest dump on first lookup but will query subCats again if clicked..? // for now omiting the detail clicks with empty results and just set catg update $catg = $this->get('catg') ?: 'null'; if ($catg == 'null') $result .= $this->data_loadProfilerData($set); else if ($this->data_isLoadOnDemand()) $result .= "\n\$WowheadProfiler.loadOnDemand('quests', ".$catg.");\n"; break; case 'recipes': if (!$this->data_isLoadOnDemand() || !$this->get('skill')) break; $skills = array_intersect(explode(',', $this->get('skill')), [171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, 185, 129, 356]); if (!$skills) break; foreach ($skills as $s) Util::loadStaticFile('p-recipes-'.$s, $result, true); Util::loadStaticFile('p-recipes-sec', $result, true); $result .= "\n\$WowheadProfiler.loadOnDemand('recipes', null);\n"; break; // locale independant case 'quick-excludes': // generated per character in profiler case 'zones': case 'weight-presets': case 'item-scaling': case 'realms': case 'statistics': if (!Util::loadStaticFile($set, $result) && CFG_DEBUG) $result .= "alert('could not fetch static data: ".$set."');"; $result .= "\n\n"; break; // localized case 'talents': if ($_ = $this->get('class')) $set .= "-".intVal($_); case 'pet-talents': case 'glyphs': case 'gems': case 'enchants': case 'itemsets': case 'pets': if (!Util::loadStaticFile($set, $result, true) && CFG_DEBUG) $result .= "alert('could not fetch static data: ".$set." for locale: ".User::$localeString."');"; $result .= "\n\n"; break; default: break; } } return $result; } private function handleProfile() { if (!$this->params) return null; switch ($this->params[0]) { case 'link': case 'unlink': $this->profile_handleLink(); // always returns null return ''; case 'pin': case 'unpin': $this->profile_handlePin(); // always returns null return ''; case 'public': case 'private': $this->profile_handlePrivacy(); // always returns null return ''; case 'avatar': if ($this->profile_handleAvatar()) // sets an image header die(); // so it has to die here or another header will be set case 'resync': case 'status': return $this->profile_handleResync($this->params[0] == 'resync'); case 'save': return $this->profile_handleSave(); case 'delete': return $this->profile_handleDelete(); case 'purge': return $this->profile_handlePurge(); case 'summary': // page is generated by jScript return ''; // just be empty case 'load': return $this->profile_handleLoad(); default: return null; } } /* responses 0: success $: silent error */ private function handleCookie() { if (User::$id && $this->params && $this->get($this->params[0])) if (DB::Aowow()->query('REPLACE INTO ?_account_cookies VALUES (?d, ?, ?)', User::$id, $this->params[0], $this->get($this->params[0]))) return 0; return null; } /* responses 0: success 1: captcha invalid 2: description too long 3: reason missing 7: already reported $: prints response */ private function handleContactus() { $mode = $this->post('mode'); $rsn = $this->post('reason'); $ua = $this->post('ua'); $app = $this->post('appname'); $url = $this->post('page'); $desc = $this->post('desc'); $subj = intVal($this->post('id')); $contexts = array( [1, 2, 3, 4, 5, 6, 7, 8], [15, 16, 17, 18, 19, 20], [30, 31, 32, 33, 34, 35, 36, 37], [45, 46, 47, 48], [60, 61], [45, 46, 47, 48], [45, 46, 48] ); if ($mode === null || $rsn === null || $ua === null || $app === null || $url === null) return 'required field missing'; if (!isset($contexts[$mode]) || !in_array($rsn, $contexts[$mode])) return 'mode invalid'; if (!$desc) return 3; if (mb_strlen($desc) > 500) return 2; if (!User::$id && !User::$ip) return 'your ip could not be determined'; // check already reported $field = User::$id ? 'userId' : 'ip'; if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $mode, $rsn, $subj, $field, User::$id ?: User::$ip)) return 7; $update = array( 'userId' => User::$id, 'mode' => $mode, 'reason' => $rsn, 'ip' => User::$ip, 'description' => $desc, 'userAgent' => $ua, 'appName' => $app, 'url' => $url ); if ($subj) $update['subject'] = $subj; if ($_ = $this->post('relatedurl')) $update['relatedurl'] = $_; if ($_ = $this->post('email')) $update['email'] = $_; if (DB::Aowow()->query('INSERT INTO ?_reports (?#) VALUES (?a)', array_keys($update), array_values($update))) return 0; return 'save to db unsuccessful'; } private function handleComment() { // post sizes $_minCmt = 10; $_maxCmt = 7500 * (User::isPremium() ? 3 : 1); $_minRpl = 15; $_maxRpl = 600; $result = null; /* note: return values must be formated as STRICT json! */ switch ($this->params[0]) { case 'add': // i .. have problems believing, that everything uses nifty ajax while adding comments requires a brutal header(Loacation: ), yet, thats how it is if (!$this->get('typeid') || !$this->get('type') || !isset(Util::$typeStrings[$this->get('type')])) return; // whatever, we cant even send him back // trim to max length if (!User::isInGroup(U_GROUP_MODERATOR) && mb_strlen($this->post('commentbody')) > $_maxCmt) $this->post['body'] = substr($this->post('body'), 0, $_maxCmt); if (User::canComment() && !empty($this->post('commentbody')) && mb_strlen($this->post('commentbody')) >= $_minCmt) { if ($postIdx = DB::Aowow()->query('INSERT INTO ?_comments (type, typeId, userId, roles, body, date) VALUES (?d, ?d, ?d, ?d, ?, UNIX_TIMESTAMP())', $this->get('type'), $this->get('typeid'), User::$id, User::$groups, $this->post('commentbody'))) { Util::gainSiteReputation(User::$id, SITEREP_ACTION_COMMENT, ['id' => $postIdx]); // every comment starts with a rating of +1 and i guess the simplest thing to do is create a db-entry with the system as owner DB::Aowow()->query('INSERT INTO ?_comments_rates (commentId, userId, value) VALUES (?d, 0, 1)', $postIdx); // flag target with hasComment (if filtrable) if ($tbl = Util::getCCTableParent($this->get('type'))) DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_COMMENT, $this->get('typeid')); } } header('Location: ?'.Util::$typeStrings[$this->get('type')].'='.$this->get('typeid').'#comments', true, 302); break; case 'edit': if ((!User::canComment() && !User::isInGroup(U_GROUP_MODERATOR)) || !$this->get('id') || !$this->post('body')) break; if (mb_strlen($this->post('body')) < $_minCmt) break; // trim to max length if (!User::isInGroup(U_GROUP_MODERATOR) && mb_strlen($this->post('body')) > $_maxCmt) $this->post['body'] = substr($this->post('body'), 0, $_maxCmt); $update = array( 'body' => $this->post('body'), 'editUserId' => User::$id, 'editDate' => time() ); if (User::isInGroup(U_GROUP_MODERATOR)) { $update['responseBody'] = !$this->post('response') ? '' : $this->post('response'); $update['responseUserId'] = !$this->post('response') ? 0 : User::$id; $update['responseRoles'] = !$this->post('response') ? 0 : User::$groups; } DB::Aowow()->query('UPDATE ?_comments SET editCount = editCount + 1, ?a WHERE id = ?d', $update, $this->get('id')); break; case 'delete': if (!$this->post('id')) break; $ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags | ?d, deleteUserId = ?d, deleteDate = UNIX_TIMESTAMP() WHERE id IN (?a){ AND userId = ?d}', CC_FLAG_DELETED, User::$id, (array)$this->post('id'), User::isInGroup(U_GROUP_MODERATOR) ? DBSIMPLE_SKIP : User::$id ); // deflag hasComment (if filtrable) if ($ok) { $coInfo = DB::Aowow()->selectRow('SELECT IF(BIT_OR(~b.flags) & ?d, 1, 0) as hasMore, b.type, b.typeId FROM ?_comments a JOIN ?_comments b ON a.type = b.type AND a.typeId = b.typeId WHERE a.id = ?d', CC_FLAG_DELETED, $this->post('id') ?: $this->get('id') ); if (!$coInfo['hasMore'] && ($tbl = Util::getCCTableParent($coInfo['type']))) DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags & ~?d WHERE id = ?d', CUSTOM_HAS_COMMENT, $coInfo['typeId']); } break; case 'undelete': if (!$this->post('id')) break; $ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~?d WHERE id IN (?a){ AND userId = deleteUserId AND deleteUserId = ?d}', CC_FLAG_DELETED, (array)$this->post('id'), User::isInGroup(U_GROUP_MODERATOR) ? DBSIMPLE_SKIP : User::$id ); // reflag hasComment (if filtrable) if ($ok) { $coInfo = DB::Aowow()->selectRow('SELECT type, typeId FROM ?_comments WHERE id = ?d', $this->post('id') ?: $this->get('id')); if ($tbl = Util::getCCTableParent($coInfo['type'])) DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_COMMENT, $coInfo['typeId']); } break; case 'rating': // up/down - distribution if (!$this->get('id')) { $result = ['success' => 0]; break; } if ($votes = DB::Aowow()->selectRow('SELECT 1 AS success, SUM(IF(value > 0, value, 0)) AS up, SUM(IF(value < 0, -value, 0)) AS down FROM ?_comments_rates WHERE commentId = ?d GROUP BY commentId', $this->get('id'))) $result = $votes; else $result = ['success' => 1, 'up' => 0, 'down' => 0]; break; case 'vote': // up, down and remove if (!User::$id || !$this->get('id') || !$this->get('rating')) { $result = ['error' => 1, 'message' => Lang::main('genericError')]; break; } $target = DB::Aowow()->selectRow('SELECT c.userId AS owner, cr.value FROM ?_comments c LEFT JOIN ?_comments_rates cr ON cr.commentId = c.id AND cr.userId = ?d WHERE c.id = ?d', User::$id, $this->get('id')); $val = User::canSupervote() ? 2 : 1; if ($this->get('rating') < 0) $val *= -1; if (User::getCurDailyVotes() <= 0) $result = ['error' => 1, 'message' => Lang::main('tooManyVotes')]; else if (!$target || $val != $this->get('rating')) $result = ['error' => 1, 'message' => Lang::main('genericError')]; else if (($val > 0 && !User::canUpvote()) || ($val < 0 && !User::canDownvote())) $result = ['error' => 1, 'message' => Lang::main('bannedRating')]; if ($result) break; $ok = false; // old and new have same sign; undo vote (user may have gained/lost access to superVote in the meantime) if ($target['value'] && ($target['value'] < 0) == ($val < 0)) $ok = DB::Aowow()->query('DELETE FROM ?_comments_rates WHERE commentId = ?d AND userId = ?d', $this->get('id'), User::$id); else // replace, because we may be overwriting an old, opposing vote if ($ok = DB::Aowow()->query('REPLACE INTO ?_comments_rates (commentId, userId, value) VALUES (?d, ?d, ?d)', (int)$this->get('id'), User::$id, $val)) User::decrementDailyVotes(); // do not refund retracted votes! if (!$ok) { $result = ['error' => 1, 'message' => Lang::main('genericError')]; break; } if ($val > 0) // gain rep Util::gainSiteReputation($target['owner'], SITEREP_ACTION_UPVOTED, ['id' => $this->get('id'), 'voterId' => User::$id]); else if ($val < 0) Util::gainSiteReputation($target['owner'], SITEREP_ACTION_DOWNVOTED, ['id' => $this->get('id'), 'voterId' => User::$id]); $result = ['error' => 0]; break; case 'sticky': // toggle flag if (!$this->post('id') || !User::isInGroup(U_GROUP_MODERATOR)) break; if ($this->post('sticky')) DB::Aowow()->query('UPDATE ?_comments SET flags = flags | ?d WHERE id = ?d', CC_FLAG_STICKY, $this->post('id')); else DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~?d WHERE id = ?d', CC_FLAG_STICKY, $this->post('id')); break; case 'out-of-date': // toggle flag if (!$this->post('id')) { $result = 'The comment does not exist.'; break; } $ok = false; if (User::isInGroup(U_GROUP_MODERATOR)) // directly mark as outdated { if (!$this->post('remove')) $ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags | 0x4 WHERE id = ?d', $this->post('id')); else $ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~0x4 WHERE id = ?d', $this->post('id')); } else if (User::$id && !$this->post('reason') || mb_strlen($this->post('reason')) < 15) { $result = 'Your message is too short.'; break; } else if (User::$id) // only report as outdated { $ok = DB::Aowow()->query( 'INSERT INTO ?_reports (userId, mode, reason, subject, ip, description, userAgent, appName) VALUES (?d, 1, 17, ?d, ?, "", ?, ?)', User::$id, $this->post('id'), User::$ip, $_SERVER['HTTP_USER_AGENT'], get_browser(null, true)['browser'] ); } if ($ok) // this one is very special; as in: completely retarded return 'ok'; // the script expects the actual characters 'ok' not some string like "ok" $result = Lang::main('genericError'); break; case 'show-replies': $result = !$this->get('id') ? [] : CommunityContent::getCommentReplies($this->get('id')); break; case 'add-reply': // also returns all replies on success if (!User::canComment()) return 'You are not allowed to reply.'; else if (!$this->post('body') || mb_strlen($this->post('body')) < $_minRpl || mb_strlen($this->post('body')) > $_maxRpl) return 'Your reply has '.mb_strlen($this->post('body')).' characters and must have at least '.$_minRpl.' and at most '.$_maxRpl.'.'; else if (!$this->post('commentId') || !DB::Aowow()->selectCell('SELECT 1 FROM ?_comments WHERE id = ?d', $this->post('commentId'))) return Lang::main('genericError'); else if (DB::Aowow()->query('INSERT INTO ?_comments (`userId`, `roles`, `body`, `date`, `replyTo`) VALUES (?d, ?d, ?, UNIX_TIMESTAMP(), ?d)', User::$id, User::$groups, $this->post('body'), $this->post('commentId'))) $result = CommunityContent::getCommentReplies($this->post('commentId')); else return Lang::main('genericError'); break; case 'edit-reply': // also returns all replies on success if (!User::canComment()) return 'You are not allowed to reply.'; else if (!$this->post('replyId') || !$this->post('commentId')) return Lang::main('genericError'); else if (!$this->post('body') || mb_strlen($this->post('body')) < $_minRpl || mb_strlen($this->post('body')) > $_maxRpl) return 'Your reply has '.mb_strlen($this->post('body')).' characters and must have at least '.$_minRpl.' and at most '.$_maxRpl.'.'; if (DB::Aowow()->query('UPDATE ?_comments SET body = ?, editUserId = ?d, editDate = UNIX_TIMESTAMP(), editCount = editCount + 1 WHERE id = ?d AND replyTo = ?d{ AND userId = ?d}', $this->post('body'), User::$id, $this->post('replyId'), $this->post('commentId'), User::isInGroup(U_GROUP_MODERATOR) ? DBSIMPLE_SKIP : User::$id)) $result = CommunityContent::getCommentReplies($this->post('commentId')); else return Lang::main('genericError'); break; case 'detach-reply': if (!User::isInGroup(U_GROUP_MODERATOR) || !$this->post('id')) break; DB::Aowow()->query('UPDATE ?_comments c1, ?_comments c2 SET c1.replyTo = 0, c1.type = c2.type, c1.typeId = c2.typeId WHERE c1.replyTo = c2.id AND c1.id = ?d', $this->post('id')); break; case 'delete-reply': if (!User::$id || !$this->post('id')) break; if (DB::Aowow()->query('DELETE FROM ?_comments WHERE id = ?d{ AND userId = ?d}', $this->post('id'), User::isInGroup(U_GROUP_MODERATOR) ? DBSIMPLE_SKIP : User::$id)) DB::Aowow()->query('DELETE FROM ?_comments_rates WHERE commentId = ?d', $this->post('id')); break; case 'flag-reply': if (!User::$id || !$this->post('id')) break; DB::Aowow()->query( 'INSERT INTO ?_reports (userId, mode, reason, subject, ip, description, userAgent, appName) VALUES (?d, 1, 19, ?d, ?, "", ?, ?)', User::$id, $this->post('id'), User::$ip, $_SERVER['HTTP_USER_AGENT'], get_browser(null, true)['browser'] ); break; case 'upvote-reply': if (!$this->post('id') || !User::canUpvote()) break; $owner = DB::Aowow()->selectCell('SELECT userId FROM ?_comments WHERE id = ?d', $this->post('id')); if (!$owner) break; $ok = DB::Aowow()->query( 'INSERT INTO ?_comments_rates (commentId, userId, value) VALUES (?d, ?d, ?d)', $this->post('id'), User::$id, User::canSupervote() ? 2 : 1 ); if ($ok) { Util::gainSiteReputation($owner, SITEREP_ACTION_UPVOTED, ['id' => $this->post('id'), 'voterId' => User::$id]); User::decrementDailyVotes(); } break; case 'downvote-reply': if (!$this->post('id') || !User::canUpvote()) break; $owner = DB::Aowow()->selectCell('SELECT userId FROM ?_comments WHERE id = ?d', $this->post('id')); if (!$owner) break; $ok = DB::Aowow()->query( 'INSERT INTO ?_comments_rates (commentId, userId, value) VALUES (?d, ?d, ?d)', $this->post('id'), User::$id, User::canSupervote() ? -2 : -1 ); if ($ok) { Util::gainSiteReputation($owner, SITEREP_ACTION_DOWNVOTED, ['id' => $this->post('id'), 'voterId' => User::$id]); User::decrementDailyVotes(); } } return Util::toJSON($result); } private function handleLocale() // not sure if this should be here.. { User::setLocale($this->params[0]); User::save(); header('Location: '.(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '.'), true, 302); } private function handleAccount() { if (!$this->params || !User::$id) return null; switch ($this->params[0]) { case 'exclude': // profiler completion exclude handler // $this->post('groups') = bitMask of excludeGroupIds when using .. excludeGroups .. duh // should probably occur in g_user.excludegroups (dont forget to also set g_users.settings = {}) return ''; case 'weightscales': if ($this->post('save')) { if (!$this->post('id')) { $res = DB::Aowow()->selectRow('SELECT MAX(id) AS max, count(id) AS num FROM ?_account_weightscales WHERE userId = ?d', User::$id); if ($res['num'] < 5) // more or less hard-defined in LANG.message_weightscalesaveerror $this->post['id'] = ++$res['max']; else return 0; } if (DB::Aowow()->query('REPLACE INTO ?_account_weightscales VALUES (?d, ?d, ?, ?)', intVal($this->post('id')), User::$id, $this->post('name'), $this->post('scale'))) return $this->post('id'); else return 0; } else if ($this->post('delete') && $this->post('id')) DB::Aowow()->query('DELETE FROM ?_account_weightscales WHERE id = ?d AND userId = ?d', intVal($this->post('id')), User::$id); else return 0; } return null; } private function handleAdmin() { if (!$this->get('action') || !$this->params) return null; if ($this->params[0] == 'screenshots') { if (!User::isInGroup(U_GROUP_STAFF | U_GROUP_SCREENSHOT)) // comment_mod, handleSSmod, vi_mod ? return null; switch ($this->get('action')) { case 'list': // get all => null (optional) case 'manage': // get: [type => type, typeId => typeId] || [user => username] case 'editalt': // get: id => ssId; post: alt => caption case 'approve': // get: id => ssId || ,-separated id-list case 'sticky': // get: id => ssId || ,-separated id-list case 'delete': // get: id => ssId || ,-separated id-list case 'relocate': // get: id => ssId, typeid => typeId (but not type..?) $fn = 'admin_handleSS'.ucfirst($this->get('action')); return $this->$fn(); break; default: return null; } } else if ($this->params[0] == 'siteconfig') { if (!User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN)) return null; switch ($this->get('action')) { case 'remove': if (!$this->get('id')) return 'invalid configuration option given'; if (DB::Aowow()->query('DELETE FROM ?_config WHERE `key` = ? AND (`flags` & ?d) = 0', $this->get('id'), CON_FLAG_PERSISTENT)) return ''; else return 'option name is either protected or was not found'; case 'add': $key = strtolower(trim($this->get('id'))); $val = trim($this->get('val')); if (!strlen($key)) return 'empty option name given'; if (preg_match('/[^a-z0-9_\.\-]/i', $key, $m)) return 'invalid chars in option name: "'.$m[0].'"'; if (ini_get($key) === false || ini_set($key, $val) === false) return 'this configuration option cannot be set'; if (DB::Aowow()->selectCell('SELECT 1 FROM ?_config WHERE `flags` & ?d AND `key` = ?', CON_FLAG_PHP, $key)) return 'this configuration option is already in use'; DB::Aowow()->query('INSERT IGNORE INTO ?_config (`key`, `value`, `flags`) VALUES (?, ?, ?d)', $key, $val, CON_FLAG_TYPE_STRING | CON_FLAG_PHP); return ''; case 'update': $key = trim($this->get('id')); $val = trim($this->get('val')); if (!strlen($key)) return 'empty option name given'; $flags = DB::Aowow()->selectCell('SELECT `flags` FROM ?_config WHERE `key` = ?', $key); if (!$flags) return 'configuration option not found'; if (!($flags & CON_FLAG_TYPE_STRING) && !strlen($val)) return 'empty value given'; else if ($flags & CON_FLAG_TYPE_INT && !preg_match('/^-?\d+$/i', $val)) return "value must be integer"; else if ($flags & CON_FLAG_TYPE_FLOAT && !preg_match('/^-?\d*(,|.)?\d+$/i', $val)) return "value must be float"; else if ($flags & CON_FLAG_TYPE_BOOL) $val = (int)!!$val; // *snort* bwahahaa DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $val, $key); return ''; default: return null; } } return null; } /**********/ /* Helper */ /**********/ private function data_isLoadOnDemand() { return substr($this->get('callback'), 0, 29) == '$WowheadProfiler.loadOnDemand'; } private function data_loadProfilerData($file, $catg = 'null') { $result = ''; if ($this->data_isLoadOnDemand()) if (Util::loadStaticFile('p-'.$file, $result, true)) $result .= "\n\$WowheadProfiler.loadOnDemand('".$file."', ".$catg.");\n"; return $result; } private function profile_handleAvatar() // image { // something happened in the last years: those textures do not include tiny icons $s = [/* 'tiny' => 15, */'small' => 18, 'medium' => 36, 'large' => 56]; $size = $this->get('size') ?: 'medium'; if (!$this->get('id') || !preg_match('/^([0-9]+)\.(jpg|gif)$/', $this->get('id'), $matches) || !in_array($size, array_keys($s))) return false; header('Content-Type: image/'.$matches[2]); $id = $matches[1]; $dest = imageCreateTruecolor($s[$size], $s[$size]); if (file_exists('uploads/avatars/'.$id.'.jpg')) { $offsetX = $offsetY = 0; switch ($size) { case 'tiny': $offsetX += $s['small']; case 'small': $offsetY += $s['medium']; case 'medium': $offsetX += $s['large']; } $src = imageCreateFromJpeg('uploads/avatars/'.$id.'.jpg'); imagecopymerge($dest, $src, 0, 0, $offsetX, $offsetY, $s[$size], $s[$size], 100); } if ($matches[2] == 'gif') imageGif($dest); else imageJpeg($dest); return true; } private function profile_handlePin($id, $mode) // (un)favorite { /* params id: user: [optional] return: null */ } private function profile_handleLink($id, $mode) // links char with account { /* params id: user: [optional] return: null */ } private function profile_handlePrivacy($id, $mode) // public visibility { /* params id: user: [optional] return: null */ } private function profile_handleResync($initNew = true) // resync init and status requests { /* params id: user: [optional] return null [onOK] int or str [onError] */ if ($initNew) return '1'; else { /* not all fields are required, if zero they are omitted statusCode: 0: end the request 1: waiting 2: working... 3: ready; click to view 4: error / retry errorCode: 0: unk error 1: char does not exist 2: armory gone [ processId, [StatusCode, timeToRefresh, iCount, errorCode, iNResyncs], []... ] */ return '[0, [4, 10000, 1, 2]]'; } } private function profile_handleSave() // unKill a profile { /* params GET id: params POST name, level, class, race, gender, nomodel, talenttree1, talenttree2, talenttree3, activespec, talentbuild1, glyphs1, talentbuild2, glyphs2, gearscore, icon, public [always] description, source, copy, inv { inventory: array containing itemLinks } [optional] } return int > 0 [profileId, if we came from an armoryProfile create a new one] int < 0 [onError] str [onError] */ return 'NYI'; } private function profile_handleDelete() // kill a profile { /* params id: return null */ return 'NYI'; } private function profile_handlePurge() // removes certain saved information but not the entire character { /* params id: data: [string, tabName?] return null */ return 'NYI'; } private function profile_handleLoad() { /* params id: profileId items: string [itemIds.join(':')] unnamed: unixtime [only to force the browser to reload instead of cache] return lots... */ // titles, achievements, characterData, talents (, pets) // and some onLoad-hook to .. load it registerProfile($data) // everything else goes through data.php .. strangely enough $char = new ProfileList(array(['id', $this->get('id')])); // or string or whatever // modify model from auras with profile_getModelForForm $buff = ''; if ($it = array_column($char->getField('inventory'), 0)) { $itemz = new ItemList(array(['id', $it, CFG_SQL_LIMIT_NONE])); $data = $itemz->getListviewData(ITEMINFO_JSON | ITEMINFO_SUBITEMS); // get and apply inventory foreach ($itemz->iterate() as $iId => $__) $buff .= 'g_items.add('.$iId.', {name_'.User::$localeString.":'".Util::jsEscape($itemz->getField('name', true))."', quality:".$itemz->getField('quality').", icon:'".$itemz->getField('iconString')."', jsonequip:".Util::toJSON($data[$iId])."});\n"; $buff .= "\n"; } if ($au = $char->getField('auras')) { $auraz = new SpellList(array(['id', $char->getField('auras')], CFG_SQL_LIMIT_NONE)); $dataz = $auraz->getListviewData(); $modz = $auraz->getProfilerMods(); // get and apply aura-mods foreach ($dataz as $id => $data) { $mods = []; if (!empty($modz[$id])) { foreach ($modz[$id] as $k => $v) { if (is_array($v)) $mods[] = $v; else if ($str = @Util::$itemMods[$k]) $mods[$str] = $v; } } $buff .= 'g_spells.add('.$id.", {id:".$id.", name:'".Util::jsEscape(substr($data['name'], 1))."', icon:'".$data['icon']."', modifier:".Util::toJSON($mods)."});\n"; } $buff .= "\n"; } /* depending on progress-achievements // required by progress in JScript move to handleLoad()? Util::$pageTemplate->extendGlobalIds(TYPE_NPC, [29120, 31134, 29306, 29311, 23980, 27656, 26861, 26723, 28923, 15991]); */ // load available titles Util::loadStaticFile('p-titles-'.$char->getField('gender'), $buff, true); // load available achievements if (!Util::loadStaticFile('p-achievements', $buff, true)) { $buff .= "\n\ng_achievement_catorder = [];"; $buff .= "\n\ng_achievement_points = [0];"; } // excludes; structure UNK type => [maskBit => [typeIds]] ? /* g_user.excludes = [type:[typeIds]] g_user.includes = [type:[typeIds]] g_user.excludegroups = groupMask // requires g_user.settings != null maskBit are matched against fieldId from excludeGroups id: 1, label: LANG.dialog_notavail id: 2, label: LANG.dialog_tcg id: 4, label: LANG.dialog_collector id: 8, label: LANG.dialog_promo id: 16, label: LANG.dialog_nonus id: 96, label: LANG.dialog_faction id: 896, label: LANG.dialog_profession id: 1024, label: LANG.dialog_noexalted */ // $buff .= "\n\ng_excludes = {};"; // add profile to buffer $buff .= "\n\n\$WowheadProfiler.registerProfile(".Util::toJSON($char->getEntry(2)).");"; // can't use JSON_NUMERIC_CHECK or the talent-string becomes a float return $buff."\n"; } private function profile_getModelForForm($form, $char) { switch ($form) { case 1: // FORM_CAT if ($char['race'] == 4) // RACE_NIGHTELF { if ($char['hairColor'] >= 0 && $char['hairColor'] <= 2) return 29407; else if ($char['hairColor'] == 3) return 29406; else if ($char['hairColor'] == 4) return 29408; else if ($char['hairColor'] == 7 || $char['hairColor'] == 8) return 29405; else return 892; } if ($char['race'] == 6) // RACE_TAUREN { if ($char['gender'] == GENDER_MALE) { if ($char['skinColor'] >= 0 && $char['skinColor'] <= 5) return 29412; else if ($char['skinColor'] >= 0 && $char['skinColor'] <= 8) return 29411; else if ($char['skinColor'] >= 0 && $char['skinColor'] <= 11) return 29410; else if (in_array($char['skinColor'], [12, 13, 14, 18])) return 29410; else return 8571; } else // if gender == GENDER_FEMALE { if ($char['skinColor'] >= 0 && $char['skinColor'] <= 3) return 29412; else if ($char['skinColor'] >= 0 && $char['skinColor'] <= 5) return 29411; else if ($char['skinColor'] >= 0 && $char['skinColor'] <= 7) return 29410; else if ($char['skinColor'] == 10) return 29410; else return 8571; } } case 5: // FORM_DIREBEAR case 8: // FORM_BEAR if ($char['race'] == 4) // RACE_NIGHTELF { if ($char['hairColor'] >= 0 && $char['hairColor'] <= 2) return 29413; else if ($char['hairColor'] == 3) return 29417; else if ($char['hairColor'] == 4) return 29416; else if ($char['hairColor'] == 6) return 29414; else return 2281; } if ($char['race'] == 6) // RACE_TAUREN { if ($char['gender'] == GENDER_MALE) { if ($char['skinColor'] >= 0 && $char['skinColor'] <= 2) return 29415; else if (in_array($char['skinColor'], [3, 4, 5, 12, 13, 14])) return 29419; else if (in_array($char['skinColor'], [9, 10, 11, 15, 16, 17])) return 29420; else if ($char['skinColor'] == 18) return 29421; else return 2289; } else // if gender == GENDER_FEMALE { if ($char['skinColor'] == 0 && $char['skinColor'] == 1) return 29418; else if ($char['skinColor'] == 2 && $char['skinColor'] == 3) return 29419; else if ($char['skinColor'] >= 6 && $char['skinColor'] <= 9) return 29420; else if ($char['skinColor'] == 10) return 29421; else return 2289; } } } // hey, still here? you're not a Tauren/Nelf as bear or cat, are you? return DB::Aowow()->selectCell('SELECT IF(?d == 1, IFNULL(displayIdA, displayIdH), IFNULL(displayIdH, displayIdA)) FROM ?_shapeshiftform WHERE id = ?d', Util::sideByRaceMask(1 << ($char['race'] - 1)), $form); } // get all => null (optional) // evaled response .. UNK private function admin_handleSSList() { // ssm_screenshotPages // ssm_numPagesFound $pages = CommunityContent::getScreenshotPagesForManager(isset($this->get['all']), $nPages); $buff = 'ssm_screenshotPages = '.Util::toJSON($pages).";\n"; $buff .= 'ssm_numPagesFound = '.$nPages.';'; return $buff; } // get: [type => type, typeId => typeId] || [user => username] // evaled response .. UNK private function admin_handleSSManage() { $res = []; if ($this->get('type') && intVal($this->get('type')) && $this->get('typeid') && intVal($this->get('typeid'))) $res = CommunityContent::getScreenshotsForManager($this->get('type'), $this->get('typeid')); else if ($this->get('user') && strlen(urldecode($this->get('user'))) > 2) if ($uId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE displayName = ?', strtolower(urldecode($this->get('user'))))) $res = CommunityContent::getScreenshotsForManager(0, 0, $uId); return 'ssm_screenshotData = '.Util::toJSON($res); } // get: id => SSid // resp: '' private function admin_handleSSEditalt() { if (!$this->get('id') || $this->post('alt') === null) return ''; // doesn't need to be htmlEscaped, ths javascript does that DB::Aowow()->query('UPDATE ?_screenshots SET caption = ? WHERE id = ?d', trim($this->post('alt')), $this->get('id')); return ''; } // get: id => comma-separated SSids // resp: '' private function admin_handleSSApprove($override = []) { if (!$this->get('id')) return ''; $ids = $override ?: array_map('intval', explode(',', $this->get('id'))); // create resized and thumb version of screenshot $resized = [772, 618]; $thumb = [150, 150]; $path = 'static/uploads/screenshots/%s/%d.jpg'; foreach ($ids as $id) { // must not be already approved if ($_ = DB::Aowow()->selectRow('SELECT userIdOwner, date FROM ?_screenshots WHERE (status & ?d) = 0 AND id = ?d', CC_FLAG_APPROVED, $id)) { // should also error-log if (!file_exists(sprintf($path, 'pending', $id))) continue; $srcImg = imagecreatefromjpeg(sprintf($path, 'pending', $id)); $srcW = imagesx($srcImg); $srcH = imagesy($srcImg); // write thumb $scale = min(1.0, min($thumb[0] / $srcW, $thumb[1] / $srcH)); $destW = $srcW * $scale; $destH = $srcH * $scale; $destImg = imagecreatetruecolor($destW, $destH); imagefill($destImg, 0, 0, imagecolorallocate($destImg, 255, 255, 255)); imagecopyresampled($destImg, $srcImg, 0, 0, 0, 0, $destW, $destH, $srcW, $srcH); imagejpeg($destImg, sprintf($path, 'thumb', $id), 100); // write resized (only if required) if ($srcW > $resized[0] || $srcH > $resized[1]) { $scale = min(1.0, min($resized[0] / $srcW, $resized[1] / $srcH)); $destW = $srcW * $scale; $destH = $srcH * $scale; $destImg = imagecreatetruecolor($destW, $destH); imagefill($destImg, 0, 0, imagecolorallocate($destImg, 255, 255, 255)); imagecopyresampled($destImg, $srcImg, 0, 0, 0, 0, $destW, $destH, $srcW, $srcH); imagejpeg($destImg, sprintf($path, 'resized', $id), 100); } imagedestroy($srcImg); // move screenshot from pending to normal rename(sprintf($path, 'pending', $id), sprintf($path, 'normal', $id)); // set as approved in DB and gain rep (once!) DB::Aowow()->query('UPDATE ?_screenshots SET status = ?d, userIdApprove = ?d WHERE id = ?d', CC_FLAG_APPROVED, User::$id, $id); Util::gainSiteReputation($_['userIdOwner'], SITEREP_ACTION_UPLOAD, ['id' => $id, 'what' => 1, 'date' => $_['date']]); } } return ''; } // get: id => comma-separated SSids // resp: '' private function admin_handleSSSticky() { if (!$this->get('id')) return ''; // this one is a bit strange: as far as i've seen, the only thing a 'sticky' screenshot does is show up in the infobox // this also means, that only one screenshot per page should be sticky // so, handle it one by one and the last one affecting one particular type/typId-key gets the cake $ids = array_map('intval', explode(',', $this->get('id'))); foreach ($ids as $id) { // reset all others DB::Aowow()->query('UPDATE ?_screenshots a, ?_screenshots b SET a.status = a.status & ~?d WHERE a.type = b.type AND a.typeId = b.typeId AND a.id <> b.id AND b.id = ?d', CC_FLAG_STICKY, $id); // approve this one (if not already) $this->admin_handleSSApprove([$id]); // toggle sticky status DB::Aowow()->query('UPDATE ?_screenshots SET `status` = IF(`status` & ?d, `status` & ~?d, `status` | ?d) WHERE id = ?d AND `status` & ?d', CC_FLAG_STICKY, CC_FLAG_STICKY, CC_FLAG_STICKY, $id, CC_FLAG_APPROVED); } return ''; } // get: id => comma-separated SSids // resp: '' // 2 steps: 1) remove from sight, 2) remove from disk private function admin_handleSSDelete() { if (!$this->get('id')) return ''; $path = 'static/uploads/screenshots/%s/%d.jpg'; $ids = array_map('intval', explode(',', $this->get('id'))); foreach ($ids as $id) { // irrevocably remove already deleted files if (DB::Aowow()->selectCell('SELECT 1 FROM ?_screenshots WHERE status & ?d AND id = ?d', CC_FLAG_DELETED, $id)) { DB::Aowow()->query('DELETE FROM ?_screenshots WHERE id = ?d', $id); if (file_exists(sprintf($path, 'pending', $id))) unlink(sprintf($path, 'pending', $id)); continue; } // move pending or normal to pending if (file_exists(sprintf($path, 'normal', $id))) rename(sprintf($path, 'normal', $id), sprintf($path, 'pending', $id)); // remove resized and thumb if (file_exists(sprintf($path, 'thumb', $id))) unlink(sprintf($path, 'thumb', $id)); if (file_exists(sprintf($path, 'resized', $id))) unlink(sprintf($path, 'resized', $id)); } // flag as deleted if not aready DB::Aowow()->query('UPDATE ?_screenshots SET status = ?d, userIdDelete = ?d WHERE id IN (?a)', CC_FLAG_DELETED, User::$id, $ids); return ''; } // get: id => ssId, typeid => typeId (but not type..?) // resp: '' private function admin_handleSSRelocate() { if (!$this->get('id') || !$this->get('typeid')) return ''; $type = DB::Aowow()->selectCell('SELECT type FROM ?_screenshots WHERE id = ?d', $this->get('id')); $typeId = (int)$this->get('typeid'); if (!(new Util::$typeClasses[$type]([['id', $typeId]]))->error) DB::Aowow()->query('UPDATE ?_screenshots SET typeId = ?d WHERE id = ?d', $typeId, $this->get('id')); return ''; } } ?>