';
- $x .= '| '.$this->getField('name').' |
';
- if ($g = $this->getField('name'))
- $x .= '| <'.$g.'> ('.$this->getField('guildrank').') |
';
+ $x .= '| '.$name.' |
';
+ if ($g = $this->getField('guildname'))
+ $x .= '| <'.$g.'> |
';
else if ($d = $this->getField('description'))
$x .= '| '.$d.' |
';
- $x .= '| '.Lang::game('level').' '.$this->getField('level').' '.Lang::game('ra', $this->curTpl['race']).' '.Lang::game('cl', $this->curTpl['classs']).' |
';
+ $x .= '| '.Lang::game('level').' '.$this->getField('level').' '.Lang::game('ra', $this->getField('race')).' '.Lang::game('cl', $this->getField('class')).' |
';
$x .= '
';
return $x;
}
- public function getJSGlobals($addMask = 0) {}
-
- private function getTalentDistribution()
+ public function getJSGlobals($addMask = 0)
{
- if (!empty($this->tDistribution))
- $this->tDistribution[$this->curTpl['classId']] = DB::Aowow()->selectCol('SELECT COUNT(t.id) FROM dbc_talent t JOIN dbc_talenttab tt ON t.tabId = tt.id WHERE tt.classMask & ?d GROUP BY tt.id ORDER BY tt.tabNumber ASC', 1 << ($this->curTpl['classId'] - 1));
+ $data = [];
+ $realms = Profiler::getRealms();
- $result = [];
- $start = 0;
- foreach ($this->tDistribution[$this->curTpl['classId']] as $len)
+ foreach ($this->iterate() as $id => $__)
{
- $result[] = array_sum(str_split(substr($this->curTpl['talentString'], $start, $len)));
- $start += $len;
+ if (($addMask & PROFILEINFO_PROFILE) && ($this->getField('cuFlags') & PROFILER_CU_PROFILE))
+ {
+ $profile = array(
+ 'id' => $this->getField('id'),
+ 'name' => $this->getField('name'),
+ 'race' => $this->getField('race'),
+ 'classs' => $this->getField('class'),
+ 'level' => $this->getField('level'),
+ 'gender' => $this->getField('gender')
+ );
+
+ if ($_ = $this->getField('icon'))
+ $profile['icon'] = $_;
+
+ $data[] = $profile;
+
+ continue;
+ }
+
+ if ($addMask & PROFILEINFO_CHARACTER && !($this->getField('cuFlags') & PROFILER_CU_PROFILE))
+ {
+ if (!isset($realms[$this->getField('realm')]))
+ continue;
+
+ $data[] = array(
+ 'id' => $this->getField('id'),
+ 'name' => $this->getField('name'),
+ 'realmname' => $realms[$this->getField('realm')]['name'],
+ 'region' => $realms[$this->getField('realm')]['region'],
+ 'realm' => Profiler::urlize($realms[$this->getField('realm')]['name']),
+ 'race' => $this->getField('race'),
+ 'classs' => $this->getField('class'),
+ 'level' => $this->getField('level'),
+ 'gender' => $this->getField('gender'),
+ 'pinned' => $this->getField('cuFlags') & PROFILER_CU_PINNED ? 1 : 0
+ );
+ }
}
- return $result;
+ return $data;
+ }
+
+ public function isCustom()
+ {
+ return $this->getField('cuFlags') & PROFILER_CU_PROFILE;
}
}
class ProfileListFilter extends Filter
{
- public $extraOpts = [];
+ public $useLocalList = false;
+ public $extraOpts = [];
- protected $genericFilter = array(
+ private $realms = [];
+
+ protected $enums = array(
+ -1 => array( // arena team sizes
+ // by name by rating by contrib
+ 12 => 2, 13 => 2, 14 => 2,
+ 15 => 3, 16 => 3, 17 => 3,
+ 18 => 5, 19 => 5, 20 => 5
+ )
);
+ protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet
+ 2 => [FILTER_CR_NUMERIC, 'gearscore', NUM_CAST_INT ], // gearscore [num]
+ 3 => [FILTER_CR_NUMERIC, 'achievementpoints', NUM_CAST_INT ], // achievementpoints [num]
+ 5 => [FILTER_CR_NUMERIC, 'talenttree1', NUM_CAST_INT ], // talenttree1 [num]
+ 6 => [FILTER_CR_NUMERIC, 'talenttree2', NUM_CAST_INT ], // talenttree2 [num]
+ 7 => [FILTER_CR_NUMERIC, 'talenttree3', NUM_CAST_INT ], // talenttree3 [num]
+ 9 => [FILTER_CR_STRING, 'g.name', ], // guildname
+ 10 => [FILTER_CR_CALLBACK, 'cbHasGuildRank', null, null], // guildrank
+ 12 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname2v2
+ 15 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname3v3
+ 18 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname5v5
+ 13 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng2v2
+ 16 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng3v3
+ 19 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng5v5
+ 14 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib2v2 [num]
+ 17 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib3v3 [num]
+ 20 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib5v5 [num]
+ 21 => [FILTER_CR_CALLBACK, 'cbWearsItems', null, null], // wearingitem [str]
+ 23 => [FILTER_CR_CALLBACK, 'cbCompletedAcv', null, null], // completedachievement
+ 25 => [FILTER_CR_CALLBACK, 'cbProfession', 171, null], // alchemy [num]
+ 26 => [FILTER_CR_CALLBACK, 'cbProfession', 164, null], // blacksmithing [num]
+ 27 => [FILTER_CR_CALLBACK, 'cbProfession', 333, null], // enchanting [num]
+ 28 => [FILTER_CR_CALLBACK, 'cbProfession', 202, null], // engineering [num]
+ 29 => [FILTER_CR_CALLBACK, 'cbProfession', 182, null], // herbalism [num]
+ 30 => [FILTER_CR_CALLBACK, 'cbProfession', 773, null], // inscription [num]
+ 31 => [FILTER_CR_CALLBACK, 'cbProfession', 755, null], // jewelcrafting [num]
+ 32 => [FILTER_CR_CALLBACK, 'cbProfession', 165, null], // leatherworking [num]
+ 33 => [FILTER_CR_CALLBACK, 'cbProfession', 186, null], // mining [num]
+ 34 => [FILTER_CR_CALLBACK, 'cbProfession', 393, null], // skinning [num]
+ 35 => [FILTER_CR_CALLBACK, 'cbProfession', 197, null], // tailoring [num]
+ 36 => [FILTER_CR_CALLBACK, 'cbHasGuild', null, null] // hasguild [yn]
+ );
+
+
+ // fieldId => [checkType, checkValue[, fieldIsArray]]
+ protected $inputFields = array(
+ 'cr' => [FILTER_V_RANGE, [1, 36], true ], // criteria ids
+ 'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 5000]], true ], // criteria operators
+ 'crv' => [FILTER_V_REGEX, '/[\p{C};]/ui', true ], // criteria values
+ 'na' => [FILTER_V_REGEX, '/[\p{C};]/ui', false], // name - only printable chars, no delimiter
+ 'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
+ 'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact
+ 'si' => [FILTER_V_LIST, [1, 2], false], // side
+ 'ra' => [FILTER_V_LIST, [[1, 8], 10, 11], true ], // race
+ 'cl' => [FILTER_V_LIST, [[1, 9], 11], true ], // class
+ 'minle' => [FILTER_V_RANGE, [1, MAX_LEVEL], false], // min level
+ 'maxle' => [FILTER_V_RANGE, [1, MAX_LEVEL], false], // max level
+ 'rg' => [FILTER_V_CALLBACK, 'cbRegionCheck', false], // region
+ 'sv' => [FILTER_V_CALLBACK, 'cbServerCheck', false], // server
+ );
+
+ /* heads up!
+ a couple of filters are too complex to be run against the characters database
+ if they are selected, force useage of LocalProfileList
+ */
+
+ public function __construct($fromPOST = false, $opts = [])
+ {
+ if (!empty($opts['realms']))
+ $this->realms = $opts['realms'];
+ else
+ $this->realms = array_keys(Profiler::getRealms());
+
+ parent::__construct($fromPOST, $opts);
+
+ if (!empty($this->fiData['c']['cr']))
+ if (array_intersect($this->fiData['c']['cr'], [2, 3, 5, 6, 7, 21]))
+ $this->useLocalList = true;
+ }
+
protected function createSQLForCriterium(&$cr)
{
if (in_array($cr[0], array_keys($this->genericFilter)))
- {
if ($genCR = $this->genericCriterion($cr))
return $genCR;
- unset($cr);
- $this->error = true;
- return [1];
- }
-
- switch ($cr[0])
- {
- default:
- break;
- }
-
unset($cr);
- $this->error = 1;
+ $this->error = true;
return [1];
}
@@ -217,13 +266,446 @@ class ProfileListFilter extends Filter
$parts = [];
$_v = $this->fiData['v'];
- // name
- if (isset($_v['na']))
- if ($_ = $this->modularizeString(['name_loc'.User::$localeId]))
- $parts[] = $_;
+ // region (rg), battlegroup (bg) and server (sv) are passed to ProflieList as miscData and handled there
+
+ // table key differs between remote and local :<
+ $k = $this->useLocalList ? 'p' : 'c';
+
+ // name [str] - the table is case sensitive. Since i down't want to destroy indizes, lets alter the search terms
+ if (!empty($_v['na']))
+ {
+ $lower = $this->modularizeString([$k.'.name'], Util::lower($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on');
+ $proper = $this->modularizeString([$k.'.name'], Util::ucWords($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on');
+
+ $parts[] = ['OR', $lower, $proper];
+ }
+
+ // side [list]
+ if (!empty($_v['si']))
+ {
+ if ($_v['si'] == 1)
+ $parts[] = [$k.'.race', [1, 3, 4, 7, 11]];
+ else if ($_v['si'] == 2)
+ $parts[] = [$k.'.race', [2, 5, 6, 8, 10]];
+ }
+
+ // race [list]
+ if (!empty($_v['ra']))
+ $parts[] = [$k.'.race', $_v['ra']];
+
+ // class [list]
+ if (!empty($_v['cl']))
+ $parts[] = [$k.'.class', $_v['cl']];
+
+ // min level [int]
+ if (isset($_v['minle']))
+ $parts[] = [$k.'.level', $_v['minle'], '>='];
+
+ // max level [int]
+ if (isset($_v['maxle']))
+ $parts[] = [$k.'.level', $_v['maxle'], '<='];
return $parts;
}
+
+ protected function cbRegionCheck(&$v)
+ {
+ if ($v == 'eu' || $v == 'us')
+ {
+ $this->parentCats[0] = $v; // directly redirect onto this region
+ $v = ''; // remove from filter
+
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function cbServerCheck(&$v)
+ {
+ foreach (Profiler::getRealms() as $realm)
+ if ($realm['name'] == $v)
+ {
+ $this->parentCats[1] = Profiler::urlize($v);// directly redirect onto this server
+ $v = ''; // remove from filter
+
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function cbProfession($cr, $skillId)
+ {
+ if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
+ return;
+
+ $k = 'sk_'.Util::createHash(12);
+ $col = 'skill'.$skillId;
+
+ $this->formData['extraCols'][$skillId] = $col;
+
+ if ($this->useLocalList)
+ {
+ $this->extraOpts[$k] = array(
+ 'j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.TYPE_SKILL.' AND '.$k.'.typeId = '.$skillId.' AND '.$k.'.cur '.$cr[1].' '.$cr[2], true],
+ 's' => [', '.$k.'.cur AS '.$col]
+ );
+ return [$k.'.typeId', null, '!'];
+ }
+ else
+ {
+ $this->extraOpts[$k] = array(
+ 'j' => ['character_skills '.$k.' ON '.$k.'.guid = c.guid AND '.$k.'.skill = '.$skillId.' AND '.$k.'.value '.$cr[1].' '.$cr[2], true],
+ 's' => [', '.$k.'.value AS '.$col]
+ );
+ return [$k.'.skill', null, '!'];
+ }
+ }
+
+ protected function cbCompletedAcv($cr)
+ {
+ if (!Util::checkNumeric($cr[2], NUM_CAST_INT))
+ return false;
+
+ if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_achievement WHERE id = ?d', $cr[2]))
+ return false;
+
+ $k = 'acv_'.Util::createHash(12);
+
+ if ($this->useLocalList)
+ {
+ $this->extraOpts[$k] = ['j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.TYPE_ACHIEVEMENT.' AND '.$k.'.typeId = '.$cr[2], true]];
+ return [$k.'.typeId', null, '!'];
+ }
+ else
+ {
+ $this->extraOpts[$k] = ['j' => ['character_achievement '.$k.' ON '.$k.'.guid = c.guid AND '.$k.'.achievement = '.$cr[2], true]];
+ return [$k.'.achievement', null, '!'];
+ }
+ }
+
+ protected function cbWearsItems($cr)
+ {
+ if (!Util::checkNumeric($cr[2], NUM_CAST_INT))
+ return false;
+
+ if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_items WHERE id = ?d', $cr[2]))
+ return false;
+
+ $k = 'i_'.Util::createHash(12);
+
+ $this->extraOpts[$k] = ['j' => ['?_profiler_items '.$k.' ON '.$k.'.id = p.id AND '.$k.'.item = '.$cr[2], true]];
+ return [$k.'.item', null, '!'];
+ }
+
+ protected function cbHasGuild($cr)
+ {
+ if (!$this->int2Bool($cr[1]))
+ return false;
+
+ if ($this->useLocalList)
+ return ['p.guild', null, $cr[1] ? '!' : null];
+ else
+ return ['gm.guildId', null, $cr[1] ? '!' : null];
+ }
+
+ protected function cbHasGuildRank($cr)
+ {
+ if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
+ return false;
+
+ if ($this->useLocalList)
+ return ['p.guildrank', $cr[2], $cr[1]];
+ else
+ return ['gm.rank', $cr[2], $cr[1]];
+ }
+
+ protected function cbTeamName($cr)
+ {
+ if ($_ = $this->modularizeString(['at.name'], $cr[2]))
+ return ['AND', ['at.type', $this->enums[-1][$cr[0]]], $_];
+
+ return false;
+ }
+
+ protected function cbTeamRating($cr)
+ {
+ if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
+ return false;
+
+ return ['AND', ['at.type', $this->enums[-1][$cr[0]]], ['at.rating', $cr[2], $cr[1]]];
+ }
}
+
+class RemoteProfileList extends ProfileList
+{
+ protected $queryBase = 'SELECT `c`.*, `c`.`guid` AS ARRAY_KEY FROM characters c';
+ protected $queryOpts = array(
+ 'c' => [['gm', 'g', 'ca', 'ct'], 'g' => 'ARRAY_KEY', 'o' => 'level DESC, name ASC'],
+ 'ca' => ['j' => ['character_achievement ca ON ca.guid = c.guid', true], 's' => ', GROUP_CONCAT(DISTINCT ca.achievement SEPARATOR " ") AS _acvs'],
+ 'ct' => ['j' => ['character_talent ct ON ct.guid = c.guid AND ct.spec = c.activespec', true], 's' => ', GROUP_CONCAT(DISTINCT ct.spell SEPARATOR " ") AS _talents'],
+ 'gm' => ['j' => ['guild_member gm ON gm.guid = c.guid', true], 's' => ', gm.rank AS guildrank'],
+ 'g' => ['j' => ['guild g ON g.guildid = gm.guildid', true], 's' => ', g.guildid AS guild, g.name AS guildname'],
+ 'atm' => ['j' => ['arena_team_member atm ON atm.guid = c.guid', true], 's' => ', atm.personalRating AS rating'],
+ 'at' => [['atm'], 'j' => 'arena_team at ON atm.arenaTeamId = at.arenaTeamId', 's' => ', at.name AS arenateam, IF(at.captainGuid = c.guid, 1, 0) AS captain']
+ );
+
+ public function __construct($conditions = [], $miscData = null)
+ {
+ // select DB by realm
+ if (!$this->selectRealms($miscData))
+ {
+ trigger_error('no access to auth-db or table realmlist is empty', E_USER_WARNING);
+ return;
+ }
+
+ parent::__construct($conditions, $miscData);
+
+ if ($this->error)
+ return;
+
+ reset($this->dbNames); // only use when querying single realm
+ $realmId = key($this->dbNames);
+ $realms = Profiler::getRealms();
+ $acvCache = [];
+ $talentCache = [];
+ $atCache = [];
+ $distrib = null;
+ $talentData = [];
+ $limit = CFG_SQL_LIMIT_DEFAULT;
+
+ foreach ($conditions as $c)
+ if (is_int($c))
+ $limit = $c;
+
+ // post processing
+ foreach ($this->iterate() as $guid => &$curTpl)
+ {
+ // battlegroup
+ $curTpl['battlegroup'] = CFG_BATTLEGROUP;
+
+ // realm
+ $r = explode(':', $guid)[0];
+ if (!empty($realms[$r]))
+ {
+ $curTpl['realm'] = $r;
+ $curTpl['realmName'] = $realms[$r]['name'];
+ $curTpl['region'] = $realms[$r]['region'];
+ }
+ else
+ {
+ trigger_error('character "'.$curTpl['name'].'" belongs to nonexistant realm #'.$r, E_USER_WARNING);
+ unset($this->templates[$guid]);
+ continue;
+ }
+
+ // temp id
+ $curTpl['id'] = 0;
+
+ // achievement points pre
+ if ($acvs = explode(' ', $curTpl['_acvs']))
+ foreach ($acvs as $a)
+ if ($a && !isset($acvCache[$a]))
+ $acvCache[$a] = $a;
+
+ // talent points pre
+ if ($talents = explode(' ', $curTpl['_talents']))
+ foreach ($talents as $t)
+ if ($t && !isset($talentCache[$t]))
+ $talentCache[$t] = $t;
+
+ // equalize distribution
+ if ($limit != CFG_SQL_LIMIT_NONE)
+ {
+ if (empty($distrib[$curTpl['realm']]))
+ $distrib[$curTpl['realm']] = 1;
+ else
+ $distrib[$curTpl['realm']]++;
+ }
+
+ $curTpl['cuFlags'] = 0;
+ }
+
+ if ($talentCache)
+ $talentData = DB::Aowow()->select('SELECT spell AS ARRAY_KEY, tab, rank FROM ?_talents WHERE spell IN (?a)', $talentCache);
+
+ if ($distrib !== null)
+ {
+ $total = array_sum($distrib);
+ foreach ($distrib as &$d)
+ $d = ceil($limit * $d / $total);
+ }
+
+ if ($acvCache)
+ $acvCache = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, points FROM ?_achievement WHERE id IN (?a)', $acvCache);
+
+ foreach ($this->iterate() as $guid => &$curTpl)
+ {
+ if ($distrib !== null)
+ {
+ if ($limit <= 0 || $distrib[$curTpl['realm']] <= 0)
+ {
+ unset($this->templates[$guid]);
+ continue;
+ }
+
+ $distrib[$curTpl['realm']]--;
+ $limit--;
+ }
+
+
+ $a = explode(' ', $curTpl['_acvs']);
+ $t = explode(' ', $curTpl['_talents']);
+ unset($curTpl['_acvs']);
+ unset($curTpl['_talents']);
+
+ // achievement points post
+ $curTpl['achievementpoints'] = array_sum(array_intersect_key($acvCache, array_combine($a, $a)));
+
+ // talent points post
+ $curTpl['talenttree1'] = 0;
+ $curTpl['talenttree2'] = 0;
+ $curTpl['talenttree3'] = 0;
+ foreach ($talentData as $spell => $data)
+ if (in_array($spell, $t))
+ $curTpl['talenttree'.($data['tab'] + 1)] += $data['rank'];
+ }
+ }
+
+ public function getListviewData($addInfoMask = 0, array $reqCols = [])
+ {
+ $data = parent::getListviewData($addInfoMask, $reqCols);
+
+ // not wanted on server list
+ foreach ($data as &$d)
+ unset($d['published']);
+
+ return $data;
+ }
+
+ public function initializeLocalEntries()
+ {
+ $baseData = $guildData = [];
+ foreach ($this->iterate() as $guid => $__)
+ {
+ $baseData[$guid] = array(
+ 'realm' => $this->getField('realm'),
+ 'realmGUID' => $this->getField('guid'),
+ 'name' => $this->getField('name'),
+ 'race' => $this->getField('race'),
+ 'class' => $this->getField('class'),
+ 'level' => $this->getField('level'),
+ 'gender' => $this->getField('gender'),
+ 'guild' => $this->getField('guild') ?: null,
+ 'guildrank' => $this->getField('guild') ? $this->getField('guildrank') : null,
+ 'cuFlags' => PROFILER_CU_NEEDS_RESYNC
+ );
+
+ if ($this->getField('guild'))
+ $guildData[] = array(
+ 'realm' => $this->getField('realm'),
+ 'realmGUID' => $this->getField('guild'),
+ 'name' => $this->getField('guildname'),
+ 'nameUrl' => Profiler::urlize($this->getField('guildname')),
+ 'cuFlags' => PROFILER_CU_NEEDS_RESYNC
+ );
+ }
+
+ // basic guild data (satisfying table constraints)
+ if ($guildData)
+ {
+ foreach (Util::createSqlBatchInsert($guildData) as $ins)
+ DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES '.$ins, array_keys(reset($guildData)));
+
+ // merge back local ids
+ $localGuilds = DB::Aowow()->selectCol('SELECT realm AS ARRAY_KEY, realmGUID AS ARRAY_KEY2, id FROM ?_profiler_guild WHERE realm IN (?a) AND realmGUID IN (?a)',
+ array_column($guildData, 'realm'), array_column($guildData, 'realmGUID')
+ );
+
+ foreach ($baseData as &$bd)
+ if ($bd['guild'])
+ $bd['guild'] = $localGuilds[$bd['realm']][$bd['guild']];
+ }
+
+ // basic char data (enough for tooltips)
+ if ($baseData)
+ {
+ foreach (Util::createSqlBatchInsert($baseData) as $ins)
+ DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_profiles (?#) VALUES '.$ins, array_keys(reset($baseData)));
+
+ // merge back local ids
+ $localIds = DB::Aowow()->select(
+ 'SELECT CONCAT(realm, ":", realmGUID) AS ARRAY_KEY, id, gearscore FROM ?_profiler_profiles WHERE (cuFlags & ?d) = 0 AND realm IN (?a) AND realmGUID IN (?a)',
+ PROFILER_CU_PROFILE,
+ array_column($baseData, 'realm'),
+ array_column($baseData, 'realmGUID')
+ );
+
+ foreach ($this->iterate() as $guid => &$_curTpl)
+ if (isset($localIds[$guid]))
+ $_curTpl = array_merge($_curTpl, $localIds[$guid]);
+ }
+ }
+}
+
+
+class LocalProfileList extends ProfileList
+{
+ protected $queryBase = 'SELECT p.*, p.id AS ARRAY_KEY FROM ?_profiler_profiles p';
+ protected $queryOpts = array(
+ 'p' => [['g'], 'g' => 'p.id'],
+ 'ap' => ['j' => ['?_account_profiles ap ON ap.profileId = p.id', true], 's' => ', (IFNULL(ap.ExtraFlags, 0) | p.cuFlags) AS cuFlags'],
+ 'atm' => ['j' => ['?_profiler_arena_team_member atm ON atm.profileId = p.id', true], 's' => ', atm.captain, atm.personalRating AS rating, atm.seasonGames, atm.seasonWins'],
+ 'at' => [['atm'], 'j' => ['?_profiler_arena_team at ON at.id = atm.arenaTeamId', true], 's' => ', at.type'],
+ 'g' => ['j' => ['?_profiler_guild g ON g.id = p.guild', true], 's' => ', g.name AS guildname']
+ );
+
+ public function __construct($conditions = [], $miscData = null)
+ {
+ parent::__construct($conditions, $miscData);
+
+ if ($this->error)
+ return;
+
+ $realms = Profiler::getRealms();
+
+ // post processing
+ $acvPoints = DB::Aowow()->selectCol('SELECT pc.id AS ARRAY_KEY, SUM(a.points) FROM ?_profiler_completion pc LEFT JOIN ?_achievement a ON a.id = pc.typeId WHERE pc.`type` = ?d AND pc.id IN (?a) GROUP BY pc.id', TYPE_ACHIEVEMENT, $this->getFoundIDs());
+
+ foreach ($this->iterate() as $id => &$curTpl)
+ {
+ if ($curTpl['realm'] && !isset($realms[$curTpl['realm']]))
+ continue;
+
+ if (isset($realms[$curTpl['realm']]))
+ {
+ $curTpl['realmName'] = $realms[$curTpl['realm']]['name'];
+ $curTpl['region'] = $realms[$curTpl['realm']]['region'];
+ }
+
+ // battlegroup
+ $curTpl['battlegroup'] = CFG_BATTLEGROUP;
+
+ $curTpl['achievementpoints'] = isset($acvPoints[$id]) ? $acvPoints[$id] : 0;
+ }
+ }
+
+ public function getProfileUrl()
+ {
+ $url = '?profile=';
+
+ if ($this->isCustom())
+ return $url.$this->getField('id');
+
+ return $url.implode('.', array(
+ Profiler::urlize($this->getField('region')),
+ Profiler::urlize($this->getField('realmName')),
+ urlencode($this->getField('name'))
+ ));
+ }
+}
+
+
?>
diff --git a/includes/types/spell.class.php b/includes/types/spell.class.php
index 20821201..9bc9cb4a 100644
--- a/includes/types/spell.class.php
+++ b/includes/types/spell.class.php
@@ -340,33 +340,226 @@ class SpellList extends BaseType
public function getProfilerMods()
{
+ // weapon hand check: param: slot, class, subclass, value
+ $whCheck = '$function() { var j, w = _inventory.getInventory()[%d]; if (!w[0] || !g_items[w[0]]) { return 0; } j = g_items[w[0]].jsonequip; return (j.classs == %d && (%d & (1 << (j.subclass)))) ? %d : 0; }';
+
$data = $this->getStatGain(); // flat gains
+ foreach ($data as $id => &$spellData)
+ {
+ foreach ($spellData as $modId => $val)
+ {
+ if (!isset(Game::$itemMods[$modId]))
+ continue;
+
+ if ($modId == ITEM_MOD_EXPERTISE_RATING) // not a rating .. pure expertise
+ $spellData['exp'] = $val;
+ else
+ $spellData[Game::$itemMods[$modId]] = $val;
+
+ unset($spellData[$modId]);
+ }
+
+ // apply weapon restrictions
+ $this->getEntry($id);
+ $class = $this->getField('equippedItemClass');
+ $subClass = $this->getField('equippedItemSubClassMask');
+ $slot = $subClass & 0x5000C ? 18 : 16;
+ if ($class != ITEM_CLASS_WEAPON || !$subClass)
+ continue;
+
+ foreach ($spellData as $json => $pts)
+ $spellData[$json] = [1, 'functionOf', sprintf($whCheck, $slot, $class, $subClass, $pts)];
+ }
+
+ // 4 possible modifiers found
+ //