From 360f2e6878981aa3919801baa874ae1c470ddc1c Mon Sep 17 00:00:00 2001 From: Sarjuuk Date: Sun, 8 Sep 2013 21:41:49 +0200 Subject: [PATCH] - work agains items + some random fixes .. use own item-table - do not display serverside events in calendar - include gems in item comparison .. also parse their stats in setup - filters use conditions and are more restrictive - changed DBSimple version so it uses mysqli (mysql is deprecated as of php 5.5) - moved each filter class to matching type; file for baseType and BaseFilter - baseType querys are somewhat modular, trying to avoid ridiculous joins that WILL occur sometimes (especially with items) as far as possible --- .htaccess | 2 + config/config.php.in | 1 + datasets/weight-presets | 4 +- includes/DbSimple/Connect.php | 273 ++++ includes/DbSimple/Database.php | 1390 +++++++++++++++++ includes/DbSimple/Generic.php | 1275 +-------------- includes/DbSimple/Ibase.php | 58 +- includes/DbSimple/Mysql.php | 171 +- includes/DbSimple/Mysqli.php | 183 +++ includes/class.achievement.php | 135 +- includes/class.basetype.php | 865 ++++++++++ includes/class.charclass.php | 4 +- includes/class.charrace.php | 4 +- includes/class.creature.php | 10 +- includes/class.currency.php | 4 +- includes/class.database.php | 2 +- includes/class.filter.php | 530 ------- includes/class.item.php | 1228 +++++++++++---- includes/class.itemset.php | 120 +- includes/class.pet.php | 4 +- includes/class.quest.php | 8 +- includes/class.skill.php | 4 +- includes/class.spell.php | 221 ++- includes/class.title.php | 6 +- includes/class.user.php | 4 +- includes/class.worldevent.php | 5 +- includes/class.zone.php | 4 +- includes/defines.php | 32 +- includes/kernel.php | 13 +- includes/utilities.php | 507 +----- index.php | 3 + localization/locale_dede.php | 22 + localization/locale_enus.php | 91 +- localization/locale_eses.php | 21 + localization/locale_frfr.php | 21 + localization/locale_ruru.php | 21 + pages/account.php | 27 +- pages/achievement.php | 49 +- pages/achievements.php | 11 +- pages/class.php | 10 +- pages/compare.php | 6 +- pages/events.php | 2 +- pages/item.php | 791 +--------- pages/items.php | 302 ++++ pages/itemset.php | 12 +- pages/itemsets.php | 15 +- pages/pet.php | 4 +- pages/race.php | 6 +- pages/skill.php | 6 +- pages/spell.php | 24 +- pages/spells.php | 14 +- pages/talent.php | 2 +- search.php | 46 +- setup/tools/dataset-assembler/enchants.php | 18 +- setup/tools/dataset-assembler/gems.php | 4 +- setup/tools/dataset-assembler/realms.php | 2 +- setup/tools/sql/_item.php | 319 +++- setup/tools/sql/_itemset.php | 9 +- template/achievement.tpl | 26 +- template/achievements.tpl | 95 +- template/brb.tpl | 14 + template/bricks/contrib_0.tpl | 8 +- template/bricks/contrib_2.tpl | 8 +- template/bricks/contrib_3.tpl | 10 +- template/bricks/contrib_6.tpl | 8 +- template/bricks/contrib_8.tpl | 8 +- template/bricks/listviews/item.tpl | 44 +- template/class.tpl | 2 +- template/css/global.css | 132 +- template/images/maintenance/brbgnomes.jpg | Bin 0 -> 38348 bytes .../images/maintenance/brbmaintenance.jpg | Bin 0 -> 124529 bytes template/item.tpl | 67 + template/items.tpl | 184 +++ template/itemset.tpl | 8 +- template/itemsets.tpl | 87 +- template/js/filters.js | 32 +- template/js/global.js | 16 +- template/js/locale_dede.js | 2 +- template/js/locale_enus.js | 2 +- template/pet.tpl | 4 +- template/race.tpl | 2 +- template/skill.tpl | 4 +- template/spell.tpl | 16 +- template/spells.tpl | 59 +- template/talent.tpl | 2 +- template/title.tpl | 4 +- 86 files changed, 5976 insertions(+), 3793 deletions(-) create mode 100644 includes/DbSimple/Connect.php create mode 100644 includes/DbSimple/Database.php create mode 100644 includes/DbSimple/Mysqli.php create mode 100644 includes/class.basetype.php delete mode 100644 includes/class.filter.php create mode 100644 pages/items.php create mode 100644 template/brb.tpl create mode 100644 template/images/maintenance/brbgnomes.jpg create mode 100644 template/images/maintenance/brbmaintenance.jpg create mode 100644 template/item.tpl create mode 100644 template/items.tpl diff --git a/.htaccess b/.htaccess index de110ba1..3a5ed06c 100644 --- a/.htaccess +++ b/.htaccess @@ -28,3 +28,5 @@ RewriteEngine on RewriteRule ^([a-z0-9\-]+)$ ?$1 [NC] # /items => ?items RewriteRule ^([a-z0-9\-]+)=([^?&]*)$ ?$1=$2 [NC] # /items=4.1 => ?items=4.1 RewriteRule ^([a-z0-9\-]+)=([^?&]*)[&?](.*)$ ?$1=$2&$3 [NC] # /items=4.1?filter=sl=7 => ?items=4.1&filter=sl=7 + + diff --git a/config/config.php.in b/config/config.php.in index a17368e3..40c8daa2 100644 --- a/config/config.php.in +++ b/config/config.php.in @@ -60,5 +60,6 @@ $AoWoWconf['limit'] = 300; // Limit of $AoWoWconf['debug'] = true; // Disable cache, show smarty console panel, enable sql-errors $AoWoWconf['map_grouping'] = 0; // Map object grouping factor. Meters = 10^param. 0:disabled; 1|2|3:enabled (1:recommended) $AoWoWconf['battlegroup'] = 'Pure Pwnage'; // pretend, we belong to a battlegroup to satisfy profiler-related Jscripts; region can be determined from realmlist.timezone +$AoWoWconf['maintainance'] = false; // brb gnomes say hi ?> diff --git a/datasets/weight-presets b/datasets/weight-presets index 42e263d7..04fa6472 100644 --- a/datasets/weight-presets +++ b/datasets/weight-presets @@ -15,9 +15,9 @@ var wt_presets = { }, 3: { pve: { - beast: {__icon:'ability_hunter_bestialdiscipline',rgddps:213,hitrtng:100,agi:58,critstrkrtng:40,int:37,atkpwr:30,armorpenrtng:28,hastertng:21}, + beast: {__icon:'ability_hunter_beasttaming',rgddps:213,hitrtng:100,agi:58,critstrkrtng:40,int:37,atkpwr:30,armorpenrtng:28,hastertng:21}, marks: {__icon:'ability_hunter_focusedaim',rgddps:379,hitrtng:100,agi:74,critstrkrtng:57,armorpenrtng:40,int:39,atkpwr:32,hastertng:24}, - surv: {__icon:'ability_hunter_camouflage',rgddps:181,hitrtng:100,agi:76,critstrkrtng:42,int:35,hastertng:31,atkpwr:29,armorpenrtng:26} + surv: {__icon:'inv_spear_02',rgddps:181,hitrtng:100,agi:76,critstrkrtng:42,int:35,hastertng:31,atkpwr:29,armorpenrtng:26} } }, 4: { diff --git a/includes/DbSimple/Connect.php b/includes/DbSimple/Connect.php new file mode 100644 index 00000000..4706069a --- /dev/null +++ b/includes/DbSimple/Connect.php @@ -0,0 +1,273 @@ +нужен для ленивой инициализации коннекта к базе + * + * @package DbSimple + * @method mixed transaction(string $mode=null) + * @method mixed commit() + * @method mixed rollback() + * @method mixed select(string $query [, $arg1] [,$arg2] ...) + * @method mixed selectRow(string $query [, $arg1] [,$arg2] ...) + * @method array selectCol(string $query [, $arg1] [,$arg2] ...) + * @method string selectCell(string $query [, $arg1] [,$arg2] ...) + * @method mixed query(string $query [, $arg1] [,$arg2] ...) + * @method string escape(mixed $s, bool $isIdent=false) + * @method DbSimple_SubQuery subquery(string $query [, $arg1] [,$arg2] ...) + * @method callback setLogger(callback $logger) + * @method callback setCacher(callback $cacher) + * @method string setIdentPrefix($prx) + * @method string setCachePrefix($prx) + */ +class DbSimple_Connect +{ + /** @var DbSimple_Generic_Database База данных */ + protected $DbSimple; + /** @var string DSN подключения */ + protected $DSN; + /** @var string Тип базы данных */ + protected $shema; + /** @var array Что выставить при коннекте */ + protected $init; + /** @var integer код ошибки */ + public $error = null; + /** @var string сообщение об ошибке */ + public $errmsg = null; + + /** + * Конструктор только запоминает переданный DSN + * создание класса и коннект происходит позже + * + * @param string $dsn DSN строка БД + */ + public function __construct($dsn) + { + $this->DbSimple = null; + $this->DSN = $dsn; + $this->init = array(); + $this->shema = ucfirst(substr($dsn, 0, strpos($dsn, ':'))); + } + + /** + * Взять базу из пула коннектов + * + * @param string $dsn DSN строка БД + * @return DbSimple_Connect + */ + public static function get($dsn) + { + static $pool = array(); + return isset($pool[$dsn]) ? $pool[$dsn] : $pool[$dsn] = new self($dsn); + } + + /** + * Возвращает тип базы данных + * + * @return string имя типа БД + */ + public function getShema() + { + return $this->shema; + } + + /** + * Коннект при первом запросе к базе данных + */ + public function __call($method, $params) + { + if ($this->DbSimple === null) + $this->connect($this->DSN); + return call_user_func_array(array(&$this->DbSimple, $method), $params); + } + + /** + * mixed selectPage(int &$total, string $query [, $arg1] [,$arg2] ...) + * Функцию нужно вызвать отдельно из-за передачи по ссылке + */ + public function selectPage(&$total, $query) + { + if ($this->DbSimple === null) + $this->connect($this->DSN); + $args = func_get_args(); + $args[0] = &$total; + return call_user_func_array(array(&$this->DbSimple, 'selectPage'), $args); + } + + /** + * Подключение к базе данных + * @param string $dsn DSN строка БД + */ + protected function connect($dsn) + { + $parsed = $this->parseDSN($dsn); + if (!$parsed) + $this->errorHandler('Ошибка разбора строки DSN', $dsn); + if (!isset($parsed['scheme'])) + $this->errorHandler('Невозможно загрузить драйвер базы данных', $parsed); + $this->shema = ucfirst($parsed['scheme']); + require_once dirname(__FILE__).'/'.$this->shema.'.php'; + $class = 'DbSimple_'.$this->shema; + $this->DbSimple = new $class($parsed); + $this->errmsg = &$this->DbSimple->errmsg; + $this->error = &$this->DbSimple->error; + $prefix = isset($parsed['prefix']) ? $parsed['prefix'] : ($this->_identPrefix ? $this->_identPrefix : false); + if ($prefix) + $this->DbSimple->setIdentPrefix($prefix); + if ($this->_cachePrefix) $this->DbSimple->setCachePrefix($this->_cachePrefix); + if ($this->_cacher) $this->DbSimple->setCacher($this->_cacher); + if ($this->_logger) $this->DbSimple->setLogger($this->_logger); + $this->DbSimple->setErrorHandler($this->errorHandler!==null ? $this->errorHandler : array(&$this, 'errorHandler')); + //выставление переменных + foreach($this->init as $query) + call_user_func_array(array(&$this->DbSimple, 'query'), $query); + $this->init = array(); + } + + /** + * Функция обработки ошибок - стандартный обработчик + * Все вызовы без @ прекращают выполнение скрипта + * + * @param string $msg Сообщение об ошибке + * @param array $info Подробная информация о контексте ошибки + */ + public function errorHandler($msg, $info) + { + // Если использовалась @, ничего не делать. + if (!error_reporting()) return; + // Выводим подробную информацию об ошибке. + echo "SQL Error: $msg
";
+		print_r($info);
+		echo "
"; + exit(); + } + + /** + * Выставляет запрос для инициализации + * + * @param string $query запрос + */ + public function addInit($query) + { + $args = func_get_args(); + if ($this->DbSimple !== null) + return call_user_func_array(array(&$this->DbSimple, 'query'), $args); + $this->init[] = $args; + } + + /** + * Устанавливает новый обработчик ошибок + * Обработчик получает 2 аргумента: + * - сообщение об ошибке + * - массив (код, сообщение, запрос, контекст) + * + * @param callback|null|false $handler обработчик ошибок + *
null - по умолчанию + *
false - отключен + * @return callback|null|false предыдущий обработчик + */ + public function setErrorHandler($handler) + { + $prev = $this->errorHandler; + $this->errorHandler = $handler; + if ($this->DbSimple) + $this->DbSimple->setErrorHandler($handler); + return $prev; + } + + /** @var callback обработчик ошибок */ + private $errorHandler = null; + private $_cachePrefix = ''; + private $_identPrefix = null; + private $_logger = null; + private $_cacher = null; + + /** + * callback setLogger(callback $logger) + * Set query logger called before each query is executed. + * Returns previous logger. + */ + public function setLogger($logger) + { + $prev = $this->_logger; + $this->_logger = $logger; + if ($this->DbSimple) + $this->DbSimple->setLogger($logger); + return $prev; + } + + /** + * callback setCacher(callback $cacher) + * Set cache mechanism called during each query if specified. + * Returns previous handler. + */ + public function setCacher(Zend_Cache_Backend_Interface $cacher=null) + { + $prev = $this->_cacher; + $this->_cacher = $cacher; + if ($this->DbSimple) + $this->DbSimple->setCacher($cacher); + return $prev; + } + + /** + * string setIdentPrefix($prx) + * Set identifier prefix used for $_ placeholder. + */ + public function setIdentPrefix($prx) + { + $old = $this->_identPrefix; + if ($prx !== null) $this->_identPrefix = $prx; + if ($this->DbSimple) + $this->DbSimple->setIdentPrefix($prx); + return $old; + } + + /** + * string setCachePrefix($prx) + * Set cache prefix used in key caclulation. + */ + public function setCachePrefix($prx) + { + $old = $this->_cachePrefix; + if ($prx !== null) $this->_cachePrefix = $prx; + if ($this->DbSimple) + $this->DbSimple->setCachePrefix($prx); + return $old; + } + + /** + * Разбирает строку DSN в массив параметров подключения к базе + * + * @param string $dsn строка DSN для разбора + * @return array Параметры коннекта + */ + protected function parseDSN($dsn) + { + $parsed = parse_url($dsn); + if (!$parsed) + return null; + $params = null; + if (!empty($parsed['query'])) + { + parse_str($parsed['query'], $params); + $parsed += $params; + } + $parsed['dsn'] = $dsn; + return $parsed; + } +} + +?> diff --git a/includes/DbSimple/Database.php b/includes/DbSimple/Database.php new file mode 100644 index 00000000..1a3a6ad9 --- /dev/null +++ b/includes/DbSimple/Database.php @@ -0,0 +1,1390 @@ +. + * + * Contains 3 classes: + * - DbSimple_Database: common database methods + * - DbSimple_Blob: common BLOB support + * - DbSimple_LastError: error reporting and tracking + * + * Special result-set fields: + * - ARRAY_KEY* ("*" means "anything") + * - PARENT_KEY + * + * Transforms: + * - GET_ATTRIBUTES + * - CALC_TOTAL + * - GET_TOTAL + * - UNIQ_KEY + * + * Query attributes: + * - BLOB_OBJ + * - CACHE + * + * @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/ + * @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/ + * @author Ivan Borzenkov, http://forum.dklab.ru/users/Ivan1986/ + * + * @version 2.x $Id$ + */ + +/** + * Use this constant as placeholder value to skip optional SQL block [...]. + */ +if (!defined('DBSIMPLE_SKIP')) + define('DBSIMPLE_SKIP', log(0)); + +/** + * Names of special columns in result-set which is used + * as array key (or karent key in forest-based resultsets) in + * resulting hash. + */ +if (!defined('DBSIMPLE_ARRAY_KEY')) + define('DBSIMPLE_ARRAY_KEY', 'ARRAY_KEY'); // hash-based resultset support +if (!defined('DBSIMPLE_PARENT_KEY')) + define('DBSIMPLE_PARENT_KEY', 'PARENT_KEY'); // forrest-based resultset support + +/** + * + * Base class for all databases. + * Can create transactions and new BLOBs, parse DSNs. + * + * Logger is COMMON for multiple transactions. + * Error handler is private for each transaction and database. + */ +abstract class DbSimple_Database extends DbSimple_LastError +{ + /** + * Public methods. + */ + + /** + * object blob($blob_id) + * Create new blob + */ + public function blob($blob_id = null) + { + $this->_resetLastError(); + return $this->_performNewBlob($blob_id); + } + + /** + * void transaction($mode) + * Create new transaction. + */ + public function transaction($mode=null) + { + $this->_resetLastError(); + $this->_logQuery('-- START TRANSACTION '.$mode); + return $this->_performTransaction($mode); + } + + /** + * mixed commit() + * Commit the transaction. + */ + public function commit() + { + $this->_resetLastError(); + $this->_logQuery('-- COMMIT'); + return $this->_performCommit(); + } + + /** + * mixed rollback() + * Rollback the transaction. + */ + public function rollback() + { + $this->_resetLastError(); + $this->_logQuery('-- ROLLBACK'); + return $this->_performRollback(); + } + + /** + * mixed select(string $query [, $arg1] [,$arg2] ...) + * Execute query and return the result. + */ + public function select($query) + { + $args = func_get_args(); + $total = false; + return $this->_query($args, $total); + } + + /** + * mixed selectPage(int &$total, string $query [, $arg1] [,$arg2] ...) + * Execute query and return the result. + * Total number of found rows (independent to LIMIT) is returned in $total + * (in most cases second query is performed to calculate $total). + */ + public function selectPage(&$total, $query) + { + $args = func_get_args(); + array_shift($args); + $total = true; + return $this->_query($args, $total); + } + + /** + * hash selectRow(string $query [, $arg1] [,$arg2] ...) + * Return the first row of query result. + * On errors return false and set last error. + * If no one row found, return array()! It is useful while debugging, + * because PHP DOES NOT generates notice on $row['abc'] if $row === null + * or $row === false (but, if $row is empty array, notice is generated). + */ + public function selectRow() + { + $args = func_get_args(); + $total = false; + $rows = $this->_query($args, $total); + if (!is_array($rows)) return $rows; + if (!count($rows)) return array(); + reset($rows); + return current($rows); + } + + /** + * array selectCol(string $query [, $arg1] [,$arg2] ...) + * Return the first column of query result as array. + */ + public function selectCol() + { + $args = func_get_args(); + $total = false; + $rows = $this->_query($args, $total); + if (!is_array($rows)) return $rows; + $this->_shrinkLastArrayDimensionCallback($rows); + return $rows; + } + + /** + * scalar selectCell(string $query [, $arg1] [,$arg2] ...) + * Return the first cell of the first column of query result. + * If no one row selected, return null. + */ + public function selectCell() + { + $args = func_get_args(); + $total = false; + $rows = $this->_query($args, $total); + if (!is_array($rows)) return $rows; + if (!count($rows)) return null; + reset($rows); + $row = current($rows); + if (!is_array($row)) return $row; + reset($row); + return current($row); + } + + /** + * mixed query(string $query [, $arg1] [,$arg2] ...) + * Alias for select(). May be used for INSERT or UPDATE queries. + */ + public function query() + { + $args = func_get_args(); + $total = false; + return $this->_query($args, $total); + } + + /** + * string escape(mixed $s, bool $isIdent=false) + * Enclose the string into database quotes correctly escaping + * special characters. If $isIdent is true, value quoted as identifier + * (e.g.: `value` in MySQL, "value" in Firebird, [value] in MSSQL). + */ + public function escape($s, $isIdent=false) + { + return $this->_performEscape($s, $isIdent); + } + + + /** + * DbSimple_SubQuery subquery(string $query [, $arg1] [,$arg2] ...) + * Выполняет разворачивание плейсхолдеров без коннекта к базе + * Нужно для сложных запросов, состоящих из кусков, которые полезно сохранить + * + */ + public function subquery() + { + $args = func_get_args(); + $this->_expandPlaceholders($args,$this->_placeholderNativeArgs !== null); + return new DbSimple_SubQuery($args); + } + + + /** + * callback setLogger(callback $logger) + * Set query logger called before each query is executed. + * Returns previous logger. + */ + public function setLogger($logger) + { + $prev = $this->_logger; + $this->_logger = $logger; + return $prev; + } + + /** + * callback setCacher(callback $cacher) + * Set cache mechanism called during each query if specified. + * Returns previous handler. + */ + public function setCacher(Zend_Cache_Backend_Interface $cacher=null) + { + $prev = $this->_cacher; + $this->_cacher = $cacher; + return $prev; + } + + /** + * string setIdentPrefix($prx) + * Set identifier prefix used for $_ placeholder. + */ + public function setIdentPrefix($prx) + { + $old = $this->_identPrefix; + if ($prx !== null) $this->_identPrefix = $prx; + return $old; + } + + /** + * string setCachePrefix($prx) + * Set cache prefix used in key caclulation. + */ + public function setCachePrefix($prx) + { + $old = $this->_cachePrefix; + if ($prx !== null) $this->_cachePrefix = $prx; + return $old; + } + + /** + * Задает имя класса строки + * + *
для следующего запроса каждая строка будет + * заменена классом, конструктору которого передается + * массив поле=>значение для этой строки + * + * @param string $name имя класса + * @return DbSimple_Generic_Database указатель на себя + */ + public function setClassName($name) + { + $this->_className = $name; + return $this; + } + + /** + * array getStatistics() + * Returns various statistical information. + */ + public function getStatistics() + { + return $this->_statistics; + } + + + /** + * string _performEscape(mixed $s, bool $isIdent=false) + */ + abstract protected function _performEscape($s, $isIdent=false); + + /** + * object _performNewBlob($id) + * + * Returns new blob object. + */ + abstract protected function _performNewBlob($id=null); + + /** + * list _performGetBlobFieldNames($resultResource) + * Get list of all BLOB field names in result-set. + */ + abstract protected function _performGetBlobFieldNames($result); + + /** + * mixed _performTransformQuery(array &$query, string $how) + * + * Transform query different way specified by $how. + * May return some information about performed transform. + */ + abstract protected function _performTransformQuery(&$queryMain, $how); + + + /** + * resource _performQuery($arrayQuery) + * Must return: + * - For SELECT queries: ID of result-set (PHP resource). + * - For other queries: query status (scalar). + * - For error queries: false (and call _setLastError()). + */ + abstract protected function _performQuery($arrayQuery); + + /** + * mixed _performFetch($resultResource) + * Fetch ONE NEXT row from result-set. + * Must return: + * - For SELECT queries: all the rows of the query (2d arrray). + * - For INSERT queries: ID of inserted row. + * - For UPDATE queries: number of updated rows. + * - For other queries: query status (scalar). + * - For error queries: false (and call _setLastError()). + */ + abstract protected function _performFetch($result); + + /** + * mixed _performTransaction($mode) + * Start new transaction. + */ + abstract protected function _performTransaction($mode=null); + + /** + * mixed _performCommit() + * Commit the transaction. + */ + abstract protected function _performCommit(); + + /** + * mixed _performRollback() + * Rollback the transaction. + */ + abstract protected function _performRollback(); + + /** + * string _performGetPlaceholderIgnoreRe() + * Return regular expression which matches ignored query parts. + * This is needed to skip placeholder replacement inside comments, constants etc. + */ + protected function _performGetPlaceholderIgnoreRe() + { + return ''; + } + + /** + * Returns marker for native database placeholder. E.g. in FireBird it is '?', + * in PostgreSQL - '$1', '$2' etc. + * + * @param int $n Number of native placeholder from the beginning of the query (begins from 0!). + * @return string String representation of native placeholder marker (by default - '?'). + */ + protected function _performGetNativePlaceholderMarker($n) + { + return '?'; + } + + + /** + * array parseDSN(mixed $dsn) + * Parse a data source name. + * See parse_url() for details. + */ + protected function parseDSN($dsn) + { + if (is_array($dsn)) return $dsn; + $parsed = @parse_url($dsn); + if (!$parsed) return null; + $params = null; + if (!empty($parsed['query'])) { + parse_str($parsed['query'], $params); + $parsed += $params; + } + $parsed['dsn'] = $dsn; + return $parsed; + } + + + /** + * array _query($query, &$total) + * See _performQuery(). + */ + private function _query($query, &$total) + { + $this->_resetLastError(); + + // Fetch query attributes. + $this->attributes = $this->_transformQuery($query, 'GET_ATTRIBUTES'); + + // Modify query if needed for total counting. + if ($total) + $this->_transformQuery($query, 'CALC_TOTAL'); + + $rows = false; + $cache_it = false; + // Кешер у нас либо null либо соответствует Zend интерфейсу + if (!empty($this->attributes['CACHE']) && $this->_cacher) + { + + $hash = $this->_cachePrefix . md5(serialize($query)); + // Getting data from cache if possible + $fetchTime = $firstFetchTime = 0; + $qStart = microtime(true); + $cacheData = unserialize($this->_cacher->load($hash)); + $queryTime = microtime(true) - $qStart; + + $invalCache = isset($cacheData['invalCache']) ? $cacheData['invalCache'] : null; + $result = isset($cacheData['result']) ? $cacheData['result'] : null; + $rows = isset($cacheData['rows']) ? $cacheData['rows'] : null; + + + $cache_params = $this->attributes['CACHE']; + + // Calculating cache time to live + $re = '/ + (?> + ([0-9]+) #1 - hours + h)? [ \t]* + (?> + ([0-9]+) #2 - minutes + m)? [ \t]* + (?> + ([0-9]+) #3 - seconds + s?)? (,)? + /sx'; + $m = null; + preg_match($re, $cache_params, $m); + $ttl = (isset($m[3])?$m[3]:0) + + (isset($m[2])?$m[2]:0) * 60 + + (isset($m[1])?$m[1]:0) * 3600; + // Cutting out time param - now there are just fields for uniqKey or nothing + $cache_params = trim(preg_replace($re, '', $cache_params, 1)); + + $uniq_key = null; + + // UNIQ_KEY calculation + if (!empty($cache_params)) { + $dummy = null; + // There is no need in query, cos' needle in $this->attributes['CACHE'] + $this->_transformQuery($dummy, 'UNIQ_KEY'); + $uniq_key = call_user_func(array(&$this, 'select'), $dummy); + $uniq_key = md5(serialize($uniq_key)); + } + // Check TTL? + $ok = empty($ttl) || $cacheData; + + // Invalidate cache? + if ($ok && $uniq_key == $invalCache) { + $this->_logQuery($query); + $this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows); + + } + else $cache_it = true; + } + + if (false === $rows || true === $cache_it) { + $this->_logQuery($query); + + // Run the query (counting time). + $qStart = microtime(true); + $result = $this->_performQuery($query); + $fetchTime = $firstFetchTime = 0; + + if (is_resource($result) || is_object($result)) { + $rows = array(); + // Fetch result row by row. + $fStart = microtime(true); + $row = $this->_performFetch($result); + $firstFetchTime = microtime(true) - $fStart; + if (!empty($row)) { + $rows[] = $row; + while ($row=$this->_performFetch($result)) { + $rows[] = $row; + } + } + $fetchTime = microtime(true) - $fStart; + } else { + $rows = $result; + } + $queryTime = microtime(true) - $qStart; + + // Log query statistics. + $this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows); + + // Prepare BLOB objects if needed. + if (is_array($rows) && !empty($this->attributes['BLOB_OBJ'])) { + $blobFieldNames = $this->_performGetBlobFieldNames($result); + foreach ($blobFieldNames as $name) { + for ($r = count($rows)-1; $r>=0; $r--) { + $rows[$r][$name] =& $this->_performNewBlob($rows[$r][$name]); + } + } + } + + // Transform resulting rows. + $result = $this->_transformResult($rows); + + // Storing data in cache + if ($cache_it && $this->_cacher) + { + $this->_cacher->save( + serialize(array( + 'invalCache' => $uniq_key, + 'result' => $result, + 'rows' => $rows + )), + $hash, + array(), + $ttl==0?false:$ttl + ); + } + + } + // Count total number of rows if needed. + if (is_array($result) && $total) { + $this->_transformQuery($query, 'GET_TOTAL'); + $total = call_user_func_array(array(&$this, 'selectCell'), $query); + } + + if ($this->_className) + { + foreach($result as $k=>$v) + $result[$k] = new $this->_className($v); + $this->_className = ''; + } + + return $result; + } + + + /** + * mixed _transformQuery(array &$query, string $how) + * + * Transform query different way specified by $how. + * May return some information about performed transform. + */ + private function _transformQuery(&$query, $how) + { + // Do overriden transformation. + $result = $this->_performTransformQuery($query, $how); + if ($result === true) return $result; + // Common transformations. + switch ($how) { + case 'GET_ATTRIBUTES': + // Extract query attributes. + $options = array(); + $q = $query[0]; + $m = null; + while (preg_match('/^ \s* -- [ \t]+ (\w+): ([^\r\n]+) [\r\n]* /sx', $q, $m)) { + $options[$m[1]] = trim($m[2]); + $q = substr($q, strlen($m[0])); + } + return $options; + case 'UNIQ_KEY': + $q = $this->attributes['CACHE']; + $query = array(); + while(preg_match('/(\w+)\.\w+/sx', $q, $m)) { + $query[] = 'SELECT MAX('.$m[0].') AS M, COUNT(*) AS C FROM '.$m[1]; + $q = substr($q, strlen($m[0])); + } + $query = " -- UNIQ_KEY\n". + join("\nUNION\n", $query); + return true; + } + // No such transform. + $this->_setLastError(-1, "No such transform type: $how", $query); + } + + + /** + * void _expandPlaceholders(array &$queryAndArgs, bool $useNative=false) + * Replace placeholders by quoted values. + * Modify $queryAndArgs. + */ + protected function _expandPlaceholders(&$queryAndArgs, $useNative=false) + { + $cacheCode = null; + if ($this->_logger) { + // Serialize is much faster than placeholder expansion. So use caching. + $cacheCode = md5(serialize($queryAndArgs) . '|' . $useNative . '|' . $this->_identPrefix); + if (isset($this->_placeholderCache[$cacheCode])) { + $queryAndArgs = $this->_placeholderCache[$cacheCode]; + return; + } + } + + if (!is_array($queryAndArgs)) { + $queryAndArgs = array($queryAndArgs); + } + + $this->_placeholderNativeArgs = $useNative? array() : null; + $this->_placeholderArgs = array_reverse($queryAndArgs); + + $query = array_pop($this->_placeholderArgs); // array_pop is faster than array_shift + + // Do all the work. + $this->_placeholderNoValueFound = false; + $query = $this->_expandPlaceholdersFlow($query); + + if ($useNative) { + array_unshift($this->_placeholderNativeArgs, $query); + $queryAndArgs = $this->_placeholderNativeArgs; + } else { + $queryAndArgs = array($query); + } + + if ($cacheCode) { + $this->_placeholderCache[$cacheCode] = $queryAndArgs; + } + } + + + /** + * Do real placeholder processing. + * Imply that all interval variables (_placeholder_*) already prepared. + * May be called recurrent! + */ + private function _expandPlaceholdersFlow($query) + { + $re = '{ + (?> + # Ignored chunks. + (?> + # Comment. + -- [^\r\n]* + ) + | + (?> + # DB-specifics. + ' . trim($this->_performGetPlaceholderIgnoreRe()) . ' + ) + ) + | + (?> + # Optional blocks + \{ + # Use "+" here, not "*"! Else nested blocks are not processed well. + ( (?> (?>(\??)[^{}]+) | (?R) )* ) #1 + \} + ) + | + (?> + # Placeholder + (\?) ( [_dsafn&|\#]? ) #2 #3 + ) + }sx'; + $query = preg_replace_callback( + $re, + array(&$this, '_expandPlaceholdersCallback'), + $query + ); + return $query; + } + + static $join = array( + '|' => array('inner' => ' AND ', 'outer' => ') OR (',), + '&' => array('inner' => ' OR ', 'outer' => ') AND (',), + 'a' => array('inner' => ', ', 'outer' => '), (',), + ); + + /** + * string _expandPlaceholdersCallback(list $m) + * Internal function to replace placeholders (see preg_replace_callback). + */ + private function _expandPlaceholdersCallback($m) + { + // Placeholder. + if (!empty($m[3])) { + $type = $m[4]; + + // Idenifier prefix. + if ($type == '_') { + return $this->_identPrefix; + } + + // Value-based placeholder. + if (!$this->_placeholderArgs) return 'DBSIMPLE_ERROR_NO_VALUE'; + $value = array_pop($this->_placeholderArgs); + + // Skip this value? + if ($value === DBSIMPLE_SKIP) { + $this->_placeholderNoValueFound = true; + return ''; + } + + // First process guaranteed non-native placeholders. + switch ($type) { + case 's': + if (!($value instanceof DbSimple_SubQuery)) + return 'DBSIMPLE_ERROR_VALUE_NOT_SUBQUERY'; + return $value->get($this->_placeholderNativeArgs); + case '|': + case '&': + case 'a': + if (!$value) $this->_placeholderNoValueFound = true; + if (!is_array($value)) return 'DBSIMPLE_ERROR_VALUE_NOT_ARRAY'; + $parts = array(); + $multi = array(); //массив для двойной вложенности + $mult = $type!='a' || is_int(key($value)) && is_array(current($value)); + foreach ($value as $prefix => $field) { + //превращаем $value в двумерный нуменованный массив + if (!is_array($field)) { + $field = array($prefix => $field); + $prefix = 0; + } + $prefix = is_int($prefix) ? '' : + $this->escape($this->_addPrefix2Table($prefix), true) . '.'; + //для мультиинсерта очищаем ключи - их быть не может по синтаксису + if ($mult && $type=='a') + $field = array_values($field); + foreach ($field as $k => $v) + { + if ($v instanceof DbSimple_SubQuery) + $v = $v->get($this->_placeholderNativeArgs); + else + $v = $v === null? 'NULL' : $this->escape($v); + if (!is_int($k)) { + $k = $this->escape($k, true); + $parts[] = "$prefix$k=$v"; + } else { + $parts[] = $v; + } + } + if ($mult) + { + $multi[] = join(self::$join[$type]['inner'], $parts); + $parts = array(); + } + } + return $mult ? join(self::$join[$type]['outer'], $multi) : join(', ', $parts); + case '#': + // Identifier. + if (!is_array($value)) + { + if ($value instanceof DbSimple_SubQuery) + return $value->get($this->_placeholderNativeArgs); + return $this->escape($this->_addPrefix2Table($value), true); + } + $parts = array(); + foreach ($value as $table => $identifiers) + { + if (!is_array($identifiers)) + $identifiers = array($identifiers); + $prefix = ''; + if (!is_int($table)) + $prefix = $this->escape($this->_addPrefix2Table($table), true) . '.'; + foreach ($identifiers as $identifier) + if ($identifier instanceof DbSimple_SubQuery) + $parts[] = $identifier->get($this->_placeholderNativeArgs); + elseif (!is_string($identifier)) + return 'DBSIMPLE_ERROR_ARRAY_VALUE_NOT_STRING'; + else + $parts[] = $prefix . ($identifier=='*' ? '*' : + $this->escape($this->_addPrefix2Table($identifier), true)); + } + return join(', ', $parts); + case 'n': + // NULL-based placeholder. + return empty($value)? 'NULL' : intval($value); + } + + // Native arguments are not processed. + if ($this->_placeholderNativeArgs !== null) { + $this->_placeholderNativeArgs[] = $value; + return $this->_performGetNativePlaceholderMarker(count($this->_placeholderNativeArgs) - 1); + } + + // In non-native mode arguments are quoted. + if ($value === null) return 'NULL'; + switch ($type) { + case '': + if (!is_scalar($value)) return 'DBSIMPLE_ERROR_VALUE_NOT_SCALAR'; + return $this->escape($value); + case 'd': + return intval($value); + case 'f': + return str_replace(',', '.', floatval($value)); + } + // By default - escape as string. + return $this->escape($value); + } + + // Optional block. + if (isset($m[1]) && strlen($block=$m[1])) + { + $prev = $this->_placeholderNoValueFound; + if ($this->_placeholderNativeArgs !== null) + $prevPh = $this->_placeholderNativeArgs; + + // Проверка на {? } - условный блок + $skip = false; + if ($m[2]=='?') + { + $skip = array_pop($this->_placeholderArgs) === DBSIMPLE_SKIP; + $block[0] = ' '; + } + + $block = $this->_expandOptionalBlock($block); + + if ($skip) + $block = ''; + + if ($this->_placeholderNativeArgs !== null) + if ($this->_placeholderNoValueFound) + $this->_placeholderNativeArgs = $prevPh; + $this->_placeholderNoValueFound = $prev; // recurrent-safe + return $block; + } + + // Default: skipped part of the string. + return $m[0]; + } + + + /** + * Заменяет ?_ на текущий префикс + * + * @param string $table имя таблицы + * @return string имя таблицы + */ + private function _addPrefix2Table($table) + { + if (substr($table, 0, 2) == '?_') + $table = $this->_identPrefix . substr($table, 2); + return $table; + } + + + /** + * Разбирает опциональный блок - условие | + * + * @param string $block блок, который нужно разобрать + * @return string что получается в результате разбора блока + */ + private function _expandOptionalBlock($block) + { + $alts = array(); + $alt = ''; + $sub=0; + $exp = explode('|',$block); + // Оптимизация, так как в большинстве случаев | не используется + if (count($exp)==1) + $alts=$exp; + else + foreach ($exp as $v) + { + // Реализуем автоматный магазин для нахождения нужной скобки + // На суммарную парность скобок проверять нет необходимости - об этом заботится регулярка + $sub+=substr_count($v,'{'); + $sub-=substr_count($v,'}'); + if ($sub>0) + $alt.=$v.'|'; + else + { + $alts[]=$alt.$v; + $alt=''; + } + } + $r=''; + foreach ($alts as $block) + { + $this->_placeholderNoValueFound = false; + $block = $this->_expandPlaceholdersFlow($block); + // Необходимо пройти все блоки, так как если пропустить оставшиесь, + // то это нарушит порядок подставляемых значений + if ($this->_placeholderNoValueFound == false && $r=='') + $r = ' '.$block.' '; + } + return $r; + } + + + /** + * void _setLastError($code, $msg, $query) + * Set last database error context. + * Aditionally expand placeholders. + */ + protected function _setLastError($code, $msg, $query) + { + if (is_array($query)) { + $this->_expandPlaceholders($query, false); + $query = $query[0]; + } + return parent::_setLastError($code, $msg, $query); + } + + + /** + * Convert SQL field-list to COUNT(...) clause + * (e.g. 'DISTINCT a AS aa, b AS bb' -> 'COUNT(DISTINCT a, b)'). + */ + private function _fieldList2Count($fields) + { + $m = null; + if (preg_match('/^\s* DISTINCT \s* (.*)/sx', $fields, $m)) { + $fields = $m[1]; + $fields = preg_replace('/\s+ AS \s+ .*? (?=,|$)/sx', '', $fields); + return "COUNT(DISTINCT $fields)"; + } else { + return 'COUNT(*)'; + } + } + + + /** + * array _transformResult(list $rows) + * Transform resulting rows to various formats. + */ + private function _transformResult($rows) + { + // is not array + if (!is_array($rows) || !$rows) + return $rows; + + // Find ARRAY_KEY* AND PARENT_KEY fields in field list. + $pk = null; + $ak = array(); + foreach (array_keys(current($rows)) as $fieldName) + if (0 == strncasecmp($fieldName, DBSIMPLE_ARRAY_KEY, strlen(DBSIMPLE_ARRAY_KEY))) + $ak[] = $fieldName; + elseif (0 == strncasecmp($fieldName, DBSIMPLE_PARENT_KEY, strlen(DBSIMPLE_PARENT_KEY))) + $pk = $fieldName; + + if (!$ak) + return $rows; + + natsort($ak); // sort ARRAY_KEY* using natural comparision + // Tree-based array? Fields: ARRAY_KEY, PARENT_KEY + if ($pk !== null) + return $this->_transformResultToForest($rows, $ak[0], $pk); + // Key-based array? Fields: ARRAY_KEY. + return $this->_transformResultToHash($rows, $ak); + } + + + /** + * Converts rowset to key-based array. + * + * @param array $rows Two-dimensional array of resulting rows. + * @param array $ak List of ARRAY_KEY* field names. + * @return array Transformed array. + */ + private function _transformResultToHash(array $rows, array $arrayKeys) + { + $result = array(); + foreach ($rows as $row) { + // Iterate over all of ARRAY_KEY* fields and build array dimensions. + $current =& $result; + foreach ($arrayKeys as $ak) { + $key = $row[$ak]; + unset($row[$ak]); // remove ARRAY_KEY* field from result row + if ($key !== null) { + $current =& $current[$key]; + } else { + // IF ARRAY_KEY field === null, use array auto-indices. + $tmp = array(); + $current[] =& $tmp; + $current =& $tmp; + unset($tmp); // we use $tmp, because don't know the value of auto-index + } + } + $current = $row; // save the row in last dimension + } + return $result; + } + + + /** + * Converts rowset to the forest. + * + * @param array $rows Two-dimensional array of resulting rows. + * @param string $idName Name of ID field. + * @param string $pidName Name of PARENT_ID field. + * @return array Transformed array (tree). + */ + private function _transformResultToForest(array $rows, $idName, $pidName) + { + $children = array(); // children of each ID + $ids = array(); + // Collect who are children of whom. + foreach ($rows as $i=>$r) { + $row =& $rows[$i]; + $id = $row[$idName]; + if ($id === null) { + // Rows without an ID are totally invalid and makes the result tree to + // be empty (because PARENT_ID = null means "a root of the tree"). So + // skip them totally. + continue; + } + $pid = $row[$pidName]; + if ($id == $pid) $pid = null; + $children[$pid][$id] =& $row; + if (!isset($children[$id])) $children[$id] = array(); + $row['childNodes'] =& $children[$id]; + $ids[$id] = true; + } + // Root elements are elements with non-found PIDs. + $forest = array(); + foreach ($rows as $i=>$r) { + $row =& $rows[$i]; + $id = $row[$idName]; + $pid = $row[$pidName]; + if ($pid == $id) $pid = null; + if (!isset($ids[$pid])) { + $forest[$row[$idName]] =& $row; + } + unset($row[$idName]); + unset($row[$pidName]); + } + return $forest; + } + + + /** + * Replaces the last array in a multi-dimensional array $V by its first value. + * Used for selectCol(), when we need to transform (N+1)d resulting array + * to Nd array (column). + */ + private function _shrinkLastArrayDimensionCallback(&$v) + { + if (!$v) return; + reset($v); + if (!is_array($firstCell = current($v))) { + $v = $firstCell; + } else { + array_walk($v, array(&$this, '_shrinkLastArrayDimensionCallback')); + } + } + + + /** + * void _logQuery($query, $noTrace=false) + * Must be called on each query. + * If $noTrace is true, library caller is not solved (speed improvement). + */ + protected function _logQuery($query, $noTrace=false) + { + if (!$this->_logger) return; + $this->_expandPlaceholders($query, false); + $args = array(); + $args[] =& $this; + $args[] = $query[0]; + $args[] = $noTrace? null : $this->findLibraryCaller(); + return call_user_func_array($this->_logger, $args); + } + + + /** + * void _logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows) + * Log information about performed query statistics. + */ + private function _logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows) + { + // Always increment counters. + $this->_statistics['time'] += $queryTime; + $this->_statistics['count']++; + + // If no logger, economize CPU resources and actually log nothing. + if (!$this->_logger) return; + + $dt = round($queryTime * 1000); + $firstFetchTime = round($firstFetchTime*1000); + $tailFetchTime = round($fetchTime * 1000) - $firstFetchTime; + $log = " -- "; + if ($firstFetchTime + $tailFetchTime) { + $log = sprintf(" -- %d ms = %d+%d".($tailFetchTime? "+%d" : ""), $dt, $dt-$firstFetchTime-$tailFetchTime, $firstFetchTime, $tailFetchTime); + } else { + $log = sprintf(" -- %d ms", $dt); + } + $log .= "; returned "; + + if (!is_array($rows)) { + $log .= $this->escape($rows); + } else { + $detailed = null; + if (count($rows) == 1) { + $len = 0; + $values = array(); + foreach ($rows[0] as $k=>$v) { + $len += strlen($v); + if ($len > $this->MAX_LOG_ROW_LEN) { + break; + } + $values[] = $v === null? 'NULL' : $this->escape($v); + } + if ($len <= $this->MAX_LOG_ROW_LEN) { + $detailed = "(" . preg_replace("/\r?\n/", "\\n", join(', ', $values)) . ")"; + } + } + if ($detailed) { + $log .= $detailed; + } else { + $log .= count($rows). " row(s)"; + } + } + + $this->_logQuery($log, true); + } + + + // Identifiers prefix (used for ?_ placeholder). + private $_identPrefix = ''; + + // Queries statistics. + private $_statistics = array( + 'time' => 0, + 'count' => 0, + ); + + private $_cachePrefix = ''; + private $_className = ''; + + private $_logger = null; + private $_cacher = null; + private $_placeholderArgs, $_placeholderNativeArgs, $_placeholderCache=array(); + private $_placeholderNoValueFound; + + /** + * When string representation of row (in characters) is greater than this, + * row data will not be logged. + */ + private $MAX_LOG_ROW_LEN = 128; +} + + +/** + * Database BLOB. + * Can read blob chunk by chunk, write data to BLOB. + */ +interface DbSimple_Blob +{ + /** + * string read(int $length) + * Returns following $length bytes from the blob. + */ + public function read($len); + + /** + * string write($data) + * Appends data to blob. + */ + public function write($data); + + /** + * int length() + * Returns length of the blob. + */ + public function length(); + + /** + * blobid close() + * Closes the blob. Return its ID. No other way to obtain this ID! + */ + public function close(); +} + + +/** + * Класс для хранения подзапроса - результата выполнения функции + * DbSimple_Generic_Database::subquery + * + */ +class DbSimple_SubQuery +{ + private $query=array(); + + public function __construct(array $q) + { + $this->query = $q; + } + + /** + * Возвращает сам запрос и добавляет плейсхолдеры в массив переданный по ссылке + * + * @param &array|null - ссылка на массив плейсхолдеров + * @return string + */ + public function get(&$ph) + { + if ($ph !== null) + $ph = array_merge($ph, array_slice($this->query,1,null,true)); + return $this->query[0]; + } +} + + +/** + * Support for error tracking. + * Can hold error messages, error queries and build proper stacktraces. + */ +abstract class DbSimple_LastError +{ + public $error = null; + public $errmsg = null; + private $errorHandler = null; + private $ignoresInTraceRe = 'DbSimple_.*::.* | call_user_func.*'; + + /** + * abstract void _logQuery($query) + * Must be overriden in derived class. + */ + abstract protected function _logQuery($query); + + /** + * void _resetLastError() + * Reset the last error. Must be called on correct queries. + */ + protected function _resetLastError() + { + $this->error = $this->errmsg = null; + } + + /** + * void _setLastError(int $code, string $message, string $query) + * Fill $this->error property with error information. Error context + * (code initiated the query outside DbSimple) is assigned automatically. + */ + protected function _setLastError($code, $msg, $query) + { + $context = "unknown"; + if ($t = $this->findLibraryCaller()) { + $context = (isset($t['file'])? $t['file'] : '?') . ' line ' . (isset($t['line'])? $t['line'] : '?'); + } + $this->error = array( + 'code' => $code, + 'message' => rtrim($msg), + 'query' => $query, + 'context' => $context, + ); + $this->errmsg = rtrim($msg) . ($context? " at $context" : ""); + + $this->_logQuery(" -- error #".$code.": ".preg_replace('/(\r?\n)+/s', ' ', $this->errmsg)); + + if (is_callable($this->errorHandler)) { + call_user_func($this->errorHandler, $this->errmsg, $this->error); + } + + return false; + } + + + /** + * callback setErrorHandler(callback $handler) + * Set new error handler called on database errors. + * Handler gets 3 arguments: + * - error message + * - full error context information (last query etc.) + */ + public function setErrorHandler($handler) + { + $prev = $this->errorHandler; + $this->errorHandler = $handler; + // In case of setting first error handler for already existed + // error - call the handler now (usual after connect()). + if (!$prev && $this->error && $this->errorHandler) { + call_user_func($this->errorHandler, $this->errmsg, $this->error); + } + return $prev; + } + + /** + * void addIgnoreInTrace($reName) + * Add regular expression matching ClassName::functionName or functionName. + * Matched stack frames will be ignored in stack traces passed to query logger. + */ + public function addIgnoreInTrace($name) + { + $this->ignoresInTraceRe .= "|" . $name; + } + + /** + * array of array findLibraryCaller() + * Return part of stacktrace before calling first library method. + * Used in debug purposes (query logging etc.). + */ + protected function findLibraryCaller() + { + $caller = call_user_func( + array(&$this, 'debug_backtrace_smart'), + $this->ignoresInTraceRe, + true + ); + return $caller; + } + + /** + * array debug_backtrace_smart($ignoresRe=null, $returnCaller=false) + * + * Return stacktrace. Correctly work with call_user_func* + * (totally skip them correcting caller references). + * If $returnCaller is true, return only first matched caller, + * not all stacktrace. + * + * @version 2.03 + */ + private function debug_backtrace_smart($ignoresRe=null, $returnCaller=false) + { + $trace = debug_backtrace(); + + if ($ignoresRe !== null) + $ignoresRe = "/^(?>{$ignoresRe})$/six"; + $smart = array(); + $framesSeen = 0; + for ($i=0, $n=count($trace); $i<$n; $i++) { + $t = $trace[$i]; + if (!$t) continue; + + // Next frame. + $next = isset($trace[$i+1])? $trace[$i+1] : null; + + // Dummy frame before call_user_func* frames. + if (!isset($t['file'])) { + $t['over_function'] = $trace[$i+1]['function']; + $t = $t + $trace[$i+1]; + $trace[$i+1] = null; // skip call_user_func on next iteration + $next = isset($trace[$i+2])? $trace[$i+2] : null; // Correct Next frame. + } + + // Skip myself frame. + if (++$framesSeen < 2) continue; + + // 'class' and 'function' field of next frame define where + // this frame function situated. Skip frames for functions + // situated in ignored places. + if ($ignoresRe && $next) { + // Name of function "inside which" frame was generated. + $frameCaller = (isset($next['class'])? $next['class'].'::' : '') . (isset($next['function'])? $next['function'] : ''); + if (preg_match($ignoresRe, $frameCaller)) continue; + } + + // On each iteration we consider ability to add PREVIOUS frame + // to $smart stack. + if ($returnCaller) return $t; + $smart[] = $t; + } + return $smart; + } + +} +?> diff --git a/includes/DbSimple/Generic.php b/includes/DbSimple/Generic.php index 0475db11..919cd63e 100644 --- a/includes/DbSimple/Generic.php +++ b/includes/DbSimple/Generic.php @@ -11,7 +11,7 @@ * * Use static DbSimple_Generic::connect($dsn) call if you don't know * database type and parameters, but have its DSN. - * + * * Additional keys can be added by appending a URI query string to the * end of the DSN. * @@ -30,13 +30,13 @@ * * Parsing code is partially grabbed from PEAR DB class, * initial author: Tomas V.V.Cox . - * - * ontains 3 classes: + * + * Contains 3 classes: * - DbSimple_Generic: database factory class * - DbSimple_Generic_Database: common database methods * - DbSimple_Generic_Blob: common BLOB support * - DbSimple_Generic_LastError: error reporting and tracking - * + * * Special result-set fields: * - ARRAY_KEY* ("*" means "anything") * - PARENT_KEY @@ -46,29 +46,32 @@ * - CALC_TOTAL * - GET_TOTAL * - UNIQ_KEY - * + * * Query attributes: * - BLOB_OBJ * - CACHE * * @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/ * @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/ - * - * @version 2.x $Id: Generic.php 226 2007-09-17 21:00:15Z dk $ + * + * @version 2.x $Id$ */ /** * Use this constant as placeholder value to skip optional SQL block [...]. */ -define('DBSIMPLE_SKIP', log(0)); +if (!defined('DBSIMPLE_SKIP')) + define('DBSIMPLE_SKIP', log(0)); /** * Names of special columns in result-set which is used - * as array key (or karent key in forest-based resultsets) in + * as array key (or karent key in forest-based resultsets) in * resulting hash. */ -define('DBSIMPLE_ARRAY_KEY', 'ARRAY_KEY'); // hash-based resultset support -define('DBSIMPLE_PARENT_KEY', 'PARENT_KEY'); // forrest-based resultset support +if (!defined('DBSIMPLE_ARRAY_KEY')) + define('DBSIMPLE_ARRAY_KEY', 'ARRAY_KEY'); // hash-based resultset support +if (!defined('DBSIMPLE_PARENT_KEY')) + define('DBSIMPLE_PARENT_KEY', 'PARENT_KEY'); // forrest-based resultset support /** @@ -78,12 +81,12 @@ class DbSimple_Generic { /** * DbSimple_Generic connect(mixed $dsn) - * + * * Universal static function to connect ANY database using DSN syntax. * Choose database driver according to DSN. Return new instance * of this driver. */ - function& connect($dsn) + function connect($dsn) { // Load database driver and create its instance. $parsed = DbSimple_Generic::parseDSN($dsn); @@ -93,63 +96,32 @@ class DbSimple_Generic } $class = 'DbSimple_'.ucfirst($parsed['scheme']); if (!class_exists($class)) { - $file = str_replace('_', '/', $class) . ".php"; - // Try to load library file from standard include_path. - if ($f = @fopen($file, "r", true)) { - fclose($f); + $file = dirname(__FILE__).'/'.ucfirst($parsed['scheme']). ".php"; + if (is_file($file)) { require_once($file); } else { - // Wrong include_path; try to load from current directory. - $base = basename($file); - $dir = dirname(__FILE__); - if (@is_file($path = "$dir/$base")) { - require_once($path); - } else { - trigger_error("Error loading database driver: no file $file in include_path; no file $base in $dir", E_USER_ERROR); - return null; - } + trigger_error("Error loading database driver: no file $file", E_USER_ERROR); + return null; } } - $object =& new $class($parsed); + $object = new $class($parsed); if (isset($parsed['ident_prefix'])) { $object->setIdentPrefix($parsed['ident_prefix']); } $object->setCachePrefix(md5(serialize($parsed['dsn']))); - if (@fopen('Cache/Lite.php', 'r', true)) { - $tmp_dirs = array( - ini_get('session.save_path'), - getenv("TEMP"), - getenv("TMP"), - getenv("TMPDIR"), - '/tmp' - ); - foreach ($tmp_dirs as $dir) { - if (!$dir) continue; - $fp = @fopen($testFile = $dir . '/DbSimple_' . md5(getmypid() . microtime()), 'w'); - if ($fp) { - fclose($fp); - unlink($testFile); - require_once 'Cache' . '/Lite.php'; // "." -> no phpEclipse notice - $t =& new Cache_Lite(array('cacheDir' => $dir.'/', 'lifeTime' => null, 'automaticSerialization' => true)); - $object->_cacher =& $t; - break; - } - - } - } return $object; } - + /** * array parseDSN(mixed $dsn) * Parse a data source name. - * See parse_url() for details. + * See parse_url() for details. */ function parseDSN($dsn) { if (is_array($dsn)) return $dsn; - $parsed = @parse_url($dsn); + $parsed = parse_url($dsn); if (!$parsed) return null; $params = null; if (!empty($parsed['query'])) { @@ -158,1204 +130,7 @@ class DbSimple_Generic } $parsed['dsn'] = $dsn; return $parsed; - } -} - - -/** - * Base class for all databases. - * Can create transactions and new BLOBs, parse DSNs. - * - * Logger is COMMON for multiple transactions. - * Error handler is private for each transaction and database. - */ -class DbSimple_Generic_Database extends DbSimple_Generic_LastError -{ - /** - * Public methods. - */ - - /** - * object blob($blob_id) - * Create new blob - */ - function blob($blob_id = null) - { - $this->_resetLastError(); - return $this->_performNewBlob($blob_id); - } - - /** - * void transaction($mode) - * Create new transaction. - */ - function transaction($mode=null) - { - $this->_resetLastError(); - $this->_logQuery('-- START TRANSACTION '.$mode); - return $this->_performTransaction($mode); - } - - /** - * mixed commit() - * Commit the transaction. - */ - function commit() - { - $this->_resetLastError(); - $this->_logQuery('-- COMMIT'); - return $this->_performCommit(); - } - - /** - * mixed rollback() - * Rollback the transaction. - */ - function rollback() - { - $this->_resetLastError(); - $this->_logQuery('-- ROLLBACK'); - return $this->_performRollback(); - } - - /** - * mixed select(string $query [, $arg1] [,$arg2] ...) - * Execute query and return the result. - */ - function select($query) - { - $args = func_get_args(); - $total = false; - return $this->_query($args, $total); - } - - /** - * mixed selectPage(int &$total, string $query [, $arg1] [,$arg2] ...) - * Execute query and return the result. - * Total number of found rows (independent to LIMIT) is returned in $total - * (in most cases second query is performed to calculate $total). - */ - function selectPage(&$total, $query) - { - $args = func_get_args(); - array_shift($args); - $total = true; - return $this->_query($args, $total); - } - - /** - * hash selectRow(string $query [, $arg1] [,$arg2] ...) - * Return the first row of query result. - * On errors return null and set last error. - * If no one row found, return array()! It is useful while debugging, - * because PHP DOES NOT generates notice on $row['abc'] if $row === null - * or $row === false (but, if $row is empty array, notice is generated). - */ - function selectRow() - { - $args = func_get_args(); - $total = false; - $rows = $this->_query($args, $total); - if (!is_array($rows)) return $rows; - if (!count($rows)) return array(); - reset($rows); - return current($rows); - } - - /** - * array selectCol(string $query [, $arg1] [,$arg2] ...) - * Return the first column of query result as array. - */ - function selectCol() - { - $args = func_get_args(); - $total = false; - $rows = $this->_query($args, $total); - if (!is_array($rows)) return $rows; - $this->_shrinkLastArrayDimensionCallback($rows); - return $rows; - } - - /** - * scalar selectCell(string $query [, $arg1] [,$arg2] ...) - * Return the first cell of the first column of query result. - * If no one row selected, return null. - */ - function selectCell() - { - $args = func_get_args(); - $total = false; - $rows = $this->_query($args, $total); - if (!is_array($rows)) return $rows; - if (!count($rows)) return null; - reset($rows); - $row = current($rows); - if (!is_array($row)) return $row; - reset($row); - return current($row); - } - - /** - * mixed query(string $query [, $arg1] [,$arg2] ...) - * Alias for select(). May be used for INSERT or UPDATE queries. - */ - function query() - { - $args = func_get_args(); - $total = false; - return $this->_query($args, $total); - } - - /** - * string escape(mixed $s, bool $isIdent=false) - * Enclose the string into database quotes correctly escaping - * special characters. If $isIdent is true, value quoted as identifier - * (e.g.: `value` in MySQL, "value" in Firebird, [value] in MSSQL). - */ - function escape($s, $isIdent=false) - { - return $this->_performEscape($s, $isIdent); - } - - - /** - * callback setLogger(callback $logger) - * Set query logger called before each query is executed. - * Returns previous logger. - */ - function setLogger($logger) - { - $prev = $this->_logger; - $this->_logger = $logger; - return $prev; - } - - /** - * callback setCacher(callback $cacher) - * Set cache mechanism called during each query if specified. - * Returns previous handler. - */ - function setCacher($cacher) - { - $prev = $this->_cacher; - $this->_cacher = $cacher; - return $prev; - } - - /** - * string setIdentPrefix($prx) - * Set identifier prefix used for $_ placeholder. - */ - function setIdentPrefix($prx) - { - $old = $this->_identPrefix; - if ($prx !== null) $this->_identPrefix = $prx; - return $old; - } - - /** - * string setIdentPrefix($prx) - * Set cache prefix used in key caclulation. - */ - function setCachePrefix($prx) - { - $old = $this->_cachePrefix; - if ($prx !== null) $this->_cachePrefix = $prx; - return $old; - } - - /** - * array getStatistics() - * Returns various statistical information. - */ - function getStatistics() - { - return $this->_statistics; - } - - - /** - * Virtual protected methods - */ - function ____________PROTECTED() {} // for phpEclipse outline - - - /** - * string _performEscape(mixed $s, bool $isIdent=false) - */ - function _performEscape($s, $isIdent) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * object _performNewBlob($id) - * - * Returns new blob object. - */ - function& _performNewBlob($id) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * list _performGetBlobFieldNames($resultResource) - * Get list of all BLOB field names in result-set. - */ - function _performGetBlobFieldNames($result) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * mixed _performTransformQuery(array &$query, string $how) - * - * Transform query different way specified by $how. - * May return some information about performed transform. - */ - function _performTransformQuery(&$queryMain, $how) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - - /** - * resource _performQuery($arrayQuery) - * Must return: - * - For SELECT queries: ID of result-set (PHP resource). - * - For other queries: query status (scalar). - * - For error queries: null (and call _setLastError()). - */ - function _performQuery($arrayQuery) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * mixed _performFetch($resultResource) - * Fetch ONE NEXT row from result-set. - * Must return: - * - For SELECT queries: all the rows of the query (2d arrray). - * - For INSERT queries: ID of inserted row. - * - For UPDATE queries: number of updated rows. - * - For other queries: query status (scalar). - * - For error queries: null (and call _setLastError()). - */ - function _performFetch($result) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * array _performTotal($arrayQuery) - */ - function _performTotal($arrayQuery) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * mixed _performTransaction($mode) - * Start new transaction. - */ - function _performTransaction($mode=null) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * mixed _performCommit() - * Commit the transaction. - */ - function _performCommit() - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * mixed _performRollback() - * Rollback the transaction. - */ - function _performRollback() - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * string _performGetPlaceholderIgnoreRe() - * Return regular expression which matches ignored query parts. - * This is needed to skip placeholder replacement inside comments, constants etc. - */ - function _performGetPlaceholderIgnoreRe() - { - return ''; - } - - /** - * Returns marker for native database placeholder. E.g. in FireBird it is '?', - * in PostgreSQL - '$1', '$2' etc. - * - * @param int $n Number of native placeholder from the beginning of the query (begins from 0!). - * @return string String representation of native placeholder marker (by default - '?'). - */ - function _performGetNativePlaceholderMarker($n) - { - return '?'; - } - - - /** - * Private methods. - */ - function ____________PRIVATE() {} // for phpEclipse outline - - - /** - * array _query($query, &$total) - * See _performQuery(). - */ - function _query($query, &$total) - { - $this->_resetLastError(); - - // Fetch query attributes. - $this->attributes = $this->_transformQuery($query, 'GET_ATTRIBUTES'); - - // Modify query if needed for total counting. - if ($total) { - $this->_transformQuery($query, 'CALC_TOTAL'); - } - $is_cacher_callable = (is_callable($this->_cacher) || (method_exists($this->_cacher, 'get') && method_exists($this->_cacher, 'save'))); - $rows = null; - $cache_it = false; - if (!empty($this->attributes['CACHE']) && $is_cacher_callable) { - - $hash = $this->_cachePrefix . md5(serialize($query)); - // Getting data from cache if possible - $fetchTime = $firstFetchTime = 0; - $qStart = $this->_microtime(); - $cacheData = $this->_cache($hash); - $queryTime = $this->_microtime() - $qStart; - - $storeTime = isset($cacheData['storeTime']) ? $cacheData['storeTime'] : null; - $invalCache = isset($cacheData['invalCache']) ? $cacheData['invalCache'] : null; - $result = isset($cacheData['result']) ? $cacheData['result'] : null; - $rows = isset($cacheData['rows']) ? $cacheData['rows'] : null; - - - $cache_params = $this->attributes['CACHE']; - - // Calculating cache time to live - $re = '/ - ( - ([0-9]+) #2 - hours - h)? [ \t]* - ( - ([0-9]+) #4 - minutes - m)? [ \t]* - ( - ([0-9]+) #6 - seconds - s?)? (,)? - /sx'; - $m = null; - preg_match($re, $cache_params, $m); - $ttl = @$m[6] + @$m[4] * 60 + @$m[2] * 3600; - // Cutting out time param - now there are just fields for uniqKey or nothing - $cache_params = trim(preg_replace($re, '', $cache_params, 1)); - - $uniq_key = null; - - // UNIQ_KEY calculation - if (!empty($cache_params)) { - $dummy = null; - // There is no need in query, cos' needle in $this->attributes['CACHE'] - $this->_transformQuery($dummy, 'UNIQ_KEY'); - $uniq_key = call_user_func_array(array(&$this, 'select'), $dummy); - $uniq_key = md5(serialize($uniq_key)); - } - // Check TTL? - $ttl = empty($ttl) ? true : (int)$storeTime > (time() - $ttl); - - // Invalidate cache? - if ($ttl && $uniq_key == $invalCache) { - $this->_logQuery($query); - $this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows); - - } - else $cache_it = true; - } - - if (null === $rows || true === $cache_it) { - $this->_logQuery($query); - - // Run the query (counting time). - $qStart = $this->_microtime(); - $result = $this->_performQuery($query); - $fetchTime = $firstFetchTime = 0; - - if (is_resource($result)) { - $rows = array(); - // Fetch result row by row. - $fStart = $this->_microtime(); - $row = $this->_performFetch($result); - $firstFetchTime = $this->_microtime() - $fStart; - if ($row !== null) { - $rows[] = $row; - while ($row=$this->_performFetch($result)) { - $rows[] = $row; - } - } - $fetchTime = $this->_microtime() - $fStart; - } else { - $rows = $result; - } - $queryTime = $this->_microtime() - $qStart; - - // Log query statistics. - $this->_logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows); - - // Prepare BLOB objects if needed. - if (is_array($rows) && !empty($this->attributes['BLOB_OBJ'])) { - $blobFieldNames = $this->_performGetBlobFieldNames($result); - foreach ($blobFieldNames as $name) { - for ($r = count($rows)-1; $r>=0; $r--) { - $rows[$r][$name] =& $this->_performNewBlob($rows[$r][$name]); - } - } - } - - // Transform resulting rows. - $result = $this->_transformResult($rows); - - // Storing data in cache - if ($cache_it && $is_cacher_callable) { - $this->_cache( - $hash, - array( - 'storeTime' => time(), - 'invalCache' => $uniq_key, - 'result' => $result, - 'rows' => $rows - ) - ); - } - - } - // Count total number of rows if needed. - if (is_array($result) && $total) { - $this->_transformQuery($query, 'GET_TOTAL'); - $total = call_user_func_array(array(&$this, 'selectCell'), $query); - } - - return $result; - } - - - /** - * mixed _transformQuery(array &$query, string $how) - * - * Transform query different way specified by $how. - * May return some information about performed transform. - */ - function _transformQuery(&$query, $how) - { - // Do overriden transformation. - $result = $this->_performTransformQuery($query, $how); - if ($result === true) return $result; - // Common transformations. - switch ($how) { - case 'GET_ATTRIBUTES': - // Extract query attributes. - $options = array(); - $q = $query[0]; - $m = null; - while (preg_match('/^ \s* -- [ \t]+ (\w+): ([^\r\n]+) [\r\n]* /sx', $q, $m)) { - $options[$m[1]] = trim($m[2]); - $q = substr($q, strlen($m[0])); - } - return $options; - case 'UNIQ_KEY': - $q = $this->attributes['CACHE']; - $i = 0; - $query = " -- UNIQ_KEY\n"; - while(preg_match('/(\w+)\.\w+/sx', $q, $m)) { - if($i > 0)$query .= "\nUNION\n"; - $query .= 'SELECT MAX('.$m[0].') AS M, COUNT(*) AS C FROM '.$m[1]; - $q = substr($q, strlen($m[0])); - $i++; - } - return true; - } - // No such transform. - $this->_setLastError(-1, "No such transform type: $how", $query); - } - - - /** - * void _expandPlaceholders(array &$queryAndArgs, bool $useNative=false) - * Replace placeholders by quoted values. - * Modify $queryAndArgs. - */ - function _expandPlaceholders(&$queryAndArgs, $useNative=false) - { - $cacheCode = null; - if ($this->_logger) { - // Serialize is much faster than placeholder expansion. So use caching. - $cacheCode = md5(serialize($queryAndArgs) . '|' . $useNative . '|' . $this->_identPrefix); - if (isset($this->_placeholderCache[$cacheCode])) { - $queryAndArgs = $this->_placeholderCache[$cacheCode]; - return; - } - } - - if (!is_array($queryAndArgs)) { - $queryAndArgs = array($queryAndArgs); - } - - $this->_placeholderNativeArgs = $useNative? array() : null; - $this->_placeholderArgs = array_reverse($queryAndArgs); - - $query = array_pop($this->_placeholderArgs); // array_pop is faster than array_shift - - // Do all the work. - $this->_placeholderNoValueFound = false; - $query = $this->_expandPlaceholdersFlow($query); - - if ($useNative) { - array_unshift($this->_placeholderNativeArgs, $query); - $queryAndArgs = $this->_placeholderNativeArgs; - } else { - $queryAndArgs = array($query); - } - - if ($cacheCode) { - $this->_placeholderCache[$cacheCode] = $queryAndArgs; - } - } - - - /** - * Do real placeholder processing. - * Imply that all interval variables (_placeholder_*) already prepared. - * May be called recurrent! - */ - function _expandPlaceholdersFlow($query) - { - $re = '{ - (?> - # Ignored chunks. - (?> - # Comment. - -- [^\r\n]* - ) - | - (?> - # DB-specifics. - ' . trim($this->_performGetPlaceholderIgnoreRe()) . ' - ) - ) - | - (?> - # Optional blocks - \{ - # Use "+" here, not "*"! Else nested blocks are not processed well. - ( (?> (?>[^{}]+) | (?R) )* ) #1 - \} - ) - | - (?> - # Placeholder - (\?) ( [_dsafn\#]? ) #2 #3 - ) - }sx'; - $query = preg_replace_callback( - $re, - array(&$this, '_expandPlaceholdersCallback'), - $query - ); - return $query; - } - - - /** - * string _expandPlaceholdersCallback(list $m) - * Internal function to replace placeholders (see preg_replace_callback). - */ - function _expandPlaceholdersCallback($m) - { - // Placeholder. - if (!empty($m[2])) { - $type = $m[3]; - - // Idenifier prefix. - if ($type == '_') { - return $this->_identPrefix; - } - - // Value-based placeholder. - if (!$this->_placeholderArgs) return 'DBSIMPLE_ERROR_NO_VALUE'; - $value = array_pop($this->_placeholderArgs); - - // Skip this value? - if ($value === DBSIMPLE_SKIP) { - $this->_placeholderNoValueFound = true; - return ''; - } - - // First process guaranteed non-native placeholders. - switch ($type) { - case 'a': - if (!$value) $this->_placeholderNoValueFound = true; - if (!is_array($value)) return 'DBSIMPLE_ERROR_VALUE_NOT_ARRAY'; - $parts = array(); - foreach ($value as $k=>$v) { - $v = $v === null? 'NULL' : $this->escape($v); - if (!is_int($k)) { - $k = $this->escape($k, true); - $parts[] = "$k=$v"; - } else { - $parts[] = $v; - } - } - return join(', ', $parts); - case "#": - // Identifier. - if (!is_array($value)) return $this->escape($value, true); - $parts = array(); - foreach ($value as $table => $identifier) { - if (!is_string($identifier)) return 'DBSIMPLE_ERROR_ARRAY_VALUE_NOT_STRING'; - $parts[] = (!is_int($table)? $this->escape($table, true) . '.' : '') . $this->escape($identifier, true); - } - return join(', ', $parts); - case 'n': - // NULL-based placeholder. - return empty($value)? 'NULL' : intval($value); - } - - // Native arguments are not processed. - if ($this->_placeholderNativeArgs !== null) { - $this->_placeholderNativeArgs[] = $value; - return $this->_performGetNativePlaceholderMarker(count($this->_placeholderNativeArgs) - 1); - } - - // In non-native mode arguments are quoted. - if ($value === null) return 'NULL'; - switch ($type) { - case '': - if (!is_scalar($value)) return 'DBSIMPLE_ERROR_VALUE_NOT_SCALAR'; - return $this->escape($value); - case 'd': - return intval($value); - case 'f': - return str_replace(',', '.', floatval($value)); - } - // By default - escape as string. - return $this->escape($value); - } - - // Optional block. - if (isset($m[1]) && strlen($block=$m[1])) { - $prev = @$this->_placeholderNoValueFound; - $block = $this->_expandPlaceholdersFlow($block); - $block = $this->_placeholderNoValueFound? '' : ' ' . $block . ' '; - $this->_placeholderNoValueFound = $prev; // recurrent-safe - return $block; - } - - // Default: skipped part of the string. - return $m[0]; - } - - - /** - * void _setLastError($code, $msg, $query) - * Set last database error context. - * Aditionally expand placeholders. - */ - function _setLastError($code, $msg, $query) - { - if (is_array($query)) { - $this->_expandPlaceholders($query, false); - $query = $query[0]; - } - return DbSimple_Generic_LastError::_setLastError($code, $msg, $query); - } - - - /** - * Return microtime as float value. - */ - function _microtime() - { - $t = explode(" ", microtime()); - return $t[0] + $t[1]; - } - - - /** - * Convert SQL field-list to COUNT(...) clause - * (e.g. 'DISTINCT a AS aa, b AS bb' -> 'COUNT(DISTINCT a, b)'). - */ - function _fieldList2Count($fields) - { - $m = null; - if (preg_match('/^\s* DISTINCT \s* (.*)/sx', $fields, $m)) { - $fields = $m[1]; - $fields = preg_replace('/\s+ AS \s+ .*? (?=,|$)/sx', '', $fields); - return "COUNT(DISTINCT $fields)"; - } else { - return 'COUNT(*)'; - } - } - - - /** - * array _transformResult(list $rows) - * Transform resulting rows to various formats. - */ - function _transformResult($rows) - { - // Process ARRAY_KEY feature. - if (is_array($rows) && $rows) { - // Find ARRAY_KEY* AND PARENT_KEY fields in field list. - $pk = null; - $ak = array(); - foreach (current($rows) as $fieldName => $dummy) { - if (0 == strncasecmp($fieldName, DBSIMPLE_ARRAY_KEY, strlen(DBSIMPLE_ARRAY_KEY))) { - $ak[] = $fieldName; - } else if (0 == strncasecmp($fieldName, DBSIMPLE_PARENT_KEY, strlen(DBSIMPLE_PARENT_KEY))) { - $pk = $fieldName; - } - } - natsort($ak); // sort ARRAY_KEY* using natural comparision - - if ($ak) { - // Tree-based array? Fields: ARRAY_KEY, PARENT_KEY - if ($pk !== null) { - return $this->_transformResultToForest($rows, $ak[0], $pk); - } - // Key-based array? Fields: ARRAY_KEY. - return $this->_transformResultToHash($rows, $ak); - } - } - return $rows; - } - - - /** - * Converts rowset to key-based array. - * - * @param array $rows Two-dimensional array of resulting rows. - * @param array $ak List of ARRAY_KEY* field names. - * @return array Transformed array. - */ - function _transformResultToHash($rows, $arrayKeys) - { - $arrayKeys = (array)$arrayKeys; - $result = array(); - foreach ($rows as $row) { - // Iterate over all of ARRAY_KEY* fields and build array dimensions. - $current =& $result; - foreach ($arrayKeys as $ak) { - $key = $row[$ak]; - unset($row[$ak]); // remove ARRAY_KEY* field from result row - if ($key !== null) { - $current =& $current[$key]; - } else { - // IF ARRAY_KEY field === null, use array auto-indices. - $tmp = array(); - $current[] =& $tmp; - $current =& $tmp; - unset($tmp); // we use tmp, because don't know the value of auto-index - } - } - $current = $row; // save the row in last dimension - } - return $result; - } - - - /** - * Converts rowset to the forest. - * - * @param array $rows Two-dimensional array of resulting rows. - * @param string $idName Name of ID field. - * @param string $pidName Name of PARENT_ID field. - * @return array Transformed array (tree). - */ - function _transformResultToForest($rows, $idName, $pidName) - { - $children = array(); // children of each ID - $ids = array(); - // Collect who are children of whom. - foreach ($rows as $i=>$r) { - $row =& $rows[$i]; - $id = $row[$idName]; - if ($id === null) { - // Rows without an ID are totally invalid and makes the result tree to - // be empty (because PARENT_ID = null means "a root of the tree"). So - // skip them totally. - continue; - } - $pid = $row[$pidName]; - if ($id == $pid) $pid = null; - $children[$pid][$id] =& $row; - if (!isset($children[$id])) $children[$id] = array(); - $row['childNodes'] =& $children[$id]; - $ids[$id] = true; - } - // Root elements are elements with non-found PIDs. - $forest = array(); - foreach ($rows as $i=>$r) { - $row =& $rows[$i]; - $id = $row[$idName]; - $pid = $row[$pidName]; - if ($pid == $id) $pid = null; - if (!isset($ids[$pid])) { - $forest[$row[$idName]] =& $row; - } - unset($row[$idName]); - unset($row[$pidName]); - } - return $forest; - } - - - /** - * Replaces the last array in a multi-dimensional array $V by its first value. - * Used for selectCol(), when we need to transform (N+1)d resulting array - * to Nd array (column). - */ - function _shrinkLastArrayDimensionCallback(&$v) - { - if (!$v) return; - reset($v); - if (!is_array($firstCell = current($v))) { - $v = $firstCell; - } else { - array_walk($v, array(&$this, '_shrinkLastArrayDimensionCallback')); - } - } - - - /** - * void _logQuery($query, $noTrace=false) - * Must be called on each query. - * If $noTrace is true, library caller is not solved (speed improvement). - */ - function _logQuery($query, $noTrace=false) - { - if (!$this->_logger) return; - $this->_expandPlaceholders($query, false); - $args = array(); - $args[] =& $this; - $args[] = $query[0]; - $args[] = $noTrace? null : $this->findLibraryCaller(); - return call_user_func_array($this->_logger, $args); - } - - - /** - * void _logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows) - * Log information about performed query statistics. - */ - function _logQueryStat($queryTime, $fetchTime, $firstFetchTime, $rows) - { - // Always increment counters. - $this->_statistics['time'] += $queryTime; - $this->_statistics['count']++; - - // If no logger, economize CPU resources and actually log nothing. - if (!$this->_logger) return; - - $dt = round($queryTime * 1000); - $firstFetchTime = round($firstFetchTime*1000); - $tailFetchTime = round($fetchTime * 1000) - $firstFetchTime; - $log = " -- "; - if ($firstFetchTime + $tailFetchTime) { - $log = sprintf(" -- %d ms = %d+%d".($tailFetchTime? "+%d" : ""), $dt, $dt-$firstFetchTime-$tailFetchTime, $firstFetchTime, $tailFetchTime); - } else { - $log = sprintf(" -- %d ms", $dt); - } - $log .= "; returned "; - - if (!is_array($rows)) { - $log .= $this->escape($rows); - } else { - $detailed = null; - if (count($rows) == 1) { - $len = 0; - $values = array(); - foreach ($rows[0] as $k=>$v) { - $len += strlen($v); - if ($len > $this->MAX_LOG_ROW_LEN) { - break; - } - $values[] = $v === null? 'NULL' : $this->escape($v); - } - if ($len <= $this->MAX_LOG_ROW_LEN) { - $detailed = "(" . preg_replace("/\r?\n/", "\\n", join(', ', $values)) . ")"; - } - } - if ($detailed) { - $log .= $detailed; - } else { - $log .= count($rows). " row(s)"; - } - } - - $this->_logQuery($log, true); - } - - /** - * mixed _cache($hash, $result=null) - * Calls cache mechanism if possible. - */ - function _cache($hash, $result=null) - { - if (is_callable($this->_cacher)) { - return call_user_func($this->_cacher, $hash, $result); - } else if (is_object($this->_cacher) && method_exists($this->_cacher, 'get') && method_exists($this->_cacher, 'save')) { - if (null === $result) - return $this->_cacher->get($hash); - else - $this->_cacher->save($result, $hash); - } - else return false; - } - - - /** - * protected constructor(string $dsn) - * - * Prevent from direct creation of this object. - */ - function DbSimple_Generic_Database() - { - die("This is protected constructor! Do not instantiate directly at ".__FILE__." line ".__LINE__); - } - - // Identifiers prefix (used for ?_ placeholder). - var $_identPrefix = ''; - - // Queries statistics. - var $_statistics = array( - 'time' => 0, - 'count' => 0, - ); - - var $_cachePrefix = ''; - - var $_logger = null; - var $_cacher = null; - var $_placeholderArgs, $_placeholderNativeArgs, $_placeholderCache=array(); - var $_placeholderNoValueFound; - - /** - * When string representation of row (in characters) is greater than this, - * row data will not be logged. - */ - var $MAX_LOG_ROW_LEN = 128; -} - - -/** - * Database BLOB. - * Can read blob chunk by chunk, write data to BLOB. - */ -class DbSimple_Generic_Blob extends DbSimple_Generic_LastError -{ - /** - * string read(int $length) - * Returns following $length bytes from the blob. - */ - function read($len) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * string write($data) - * Appends data to blob. - */ - function write($data) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * int length() - * Returns length of the blob. - */ - function length() - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); - } - - /** - * blobid close() - * Closes the blob. Return its ID. No other way to obtain this ID! - */ - function close() - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__); } } - -/** - * Support for error tracking. - * Can hold error messages, error queries and build proper stacktraces. - */ -class DbSimple_Generic_LastError -{ - var $error = null; - var $errmsg = null; - var $errorHandler = null; - var $ignoresInTraceRe = 'DbSimple_.*::.* | call_user_func.*'; - - /** - * abstract void _logQuery($query) - * Must be overriden in derived class. - */ - function _logQuery($query) - { - die("Method must be defined in derived class. Abstract function called at ".__FILE__." line ".__LINE__);; - } - - /** - * void _resetLastError() - * Reset the last error. Must be called on correct queries. - */ - function _resetLastError() - { - $this->error = $this->errmsg = null; - } - - /** - * void _setLastError(int $code, string $message, string $query) - * Fill $this->error property with error information. Error context - * (code initiated the query outside DbSimple) is assigned automatically. - */ - function _setLastError($code, $msg, $query) - { - $context = "unknown"; - if ($t = $this->findLibraryCaller()) { - $context = (isset($t['file'])? $t['file'] : '?') . ' line ' . (isset($t['line'])? $t['line'] : '?'); - } - $this->error = array( - 'code' => $code, - 'message' => rtrim($msg), - 'query' => $query, - 'context' => $context, - ); - $this->errmsg = rtrim($msg) . ($context? " at $context" : ""); - - $this->_logQuery(" -- error #".$code.": ".preg_replace('/(\r?\n)+/s', ' ', $this->errmsg)); - - if (is_callable($this->errorHandler)) { - call_user_func($this->errorHandler, $this->errmsg, $this->error); - } - - return null; - } - - - /** - * callback setErrorHandler(callback $handler) - * Set new error handler called on database errors. - * Handler gets 3 arguments: - * - error message - * - full error context information (last query etc.) - */ - function setErrorHandler($handler) - { - $prev = $this->errorHandler; - $this->errorHandler = $handler; - // In case of setting first error handler for already existed - // error - call the handler now (usual after connect()). - if (!$prev && $this->error) { - call_user_func($this->errorHandler, $this->errmsg, $this->error); - } - return $prev; - } - - /** - * void addIgnoreInTrace($reName) - * Add regular expression matching ClassName::functionName or functionName. - * Matched stack frames will be ignored in stack traces passed to query logger. - */ - function addIgnoreInTrace($name) - { - $this->ignoresInTraceRe .= "|" . $name; - } - - /** - * array of array findLibraryCaller() - * Return part of stacktrace before calling first library method. - * Used in debug purposes (query logging etc.). - */ - function findLibraryCaller() - { - $caller = call_user_func( - array(&$this, 'debug_backtrace_smart'), - $this->ignoresInTraceRe, - true - ); - return $caller; - } - - /** - * array debug_backtrace_smart($ignoresRe=null, $returnCaller=false) - * - * Return stacktrace. Correctly work with call_user_func* - * (totally skip them correcting caller references). - * If $returnCaller is true, return only first matched caller, - * not all stacktrace. - * - * @version 2.03 - */ - function debug_backtrace_smart($ignoresRe=null, $returnCaller=false) - { - if (!is_callable($tracer='debug_backtrace')) return array(); - $trace = $tracer(); - - if ($ignoresRe !== null) $ignoresRe = "/^(?>{$ignoresRe})$/six"; - $smart = array(); - $framesSeen = 0; - for ($i=0, $n=count($trace); $i<$n; $i++) { - $t = $trace[$i]; - if (!$t) continue; - - // Next frame. - $next = isset($trace[$i+1])? $trace[$i+1] : null; - - // Dummy frame before call_user_func* frames. - if (!isset($t['file'])) { - $t['over_function'] = $trace[$i+1]['function']; - $t = $t + $trace[$i+1]; - $trace[$i+1] = null; // skip call_user_func on next iteration - } - - // Skip myself frame. - if (++$framesSeen < 2) continue; - - // 'class' and 'function' field of next frame define where - // this frame function situated. Skip frames for functions - // situated in ignored places. - if ($ignoresRe && $next) { - // Name of function "inside which" frame was generated. - $frameCaller = (isset($next['class'])? $next['class'].'::' : '') . (isset($next['function'])? $next['function'] : ''); - if (preg_match($ignoresRe, $frameCaller)) continue; - } - - // On each iteration we consider ability to add PREVIOUS frame - // to $smart stack. - if ($returnCaller) return $t; - $smart[] = $t; - } - return $smart; - } - -} -?> \ No newline at end of file +?> diff --git a/includes/DbSimple/Ibase.php b/includes/DbSimple/Ibase.php index 985d3162..839feef9 100644 --- a/includes/DbSimple/Ibase.php +++ b/includes/DbSimple/Ibase.php @@ -2,7 +2,7 @@ /** * DbSimple_Ibase: Interbase/Firebird database. * (C) Dk Lab, http://en.dklab.ru - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -13,10 +13,10 @@ * * @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/ * @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/ - * - * @version 2.x $Id: Ibase.php 221 2007-07-27 23:24:35Z dk $ + * + * @version 2.x $Id$ */ -require_once dirname(__FILE__) . '/Generic.php'; +require_once dirname(__FILE__) . '/Database.php'; /** * Best transaction parameters for script queries. @@ -30,7 +30,7 @@ define('IBASE_BEST_FETCH', IBASE_UNIXTIME); * Database class for Interbase/Firebird. */ -class DbSimple_Ibase extends DbSimple_Generic_Database +class DbSimple_Ibase extends DbSimple_Database { var $DbSimple_Ibase_BEST_TRANSACTION = IBASE_BEST_TRANSACTION; var $DbSimple_Ibase_USE_NATIVE_PHOLDERS = true; @@ -45,7 +45,7 @@ class DbSimple_Ibase extends DbSimple_Generic_Database */ function DbSimple_Ibase($dsn) { - $p = DbSimple_Generic::parseDSN($dsn); + $p = DbSimple_Database::parseDSN($dsn); if (!is_callable('ibase_connect')) { return $this->_setLastError("-1", "Interbase/Firebird extension is not loaded", "ibase_connect"); } @@ -77,17 +77,16 @@ class DbSimple_Ibase extends DbSimple_Generic_Database $this->trans = @ibase_trans($parameters, $this->link); } - function& _performNewBlob($blobid=null) + function _performNewBlob($blobid=null) { - $obj =& new DbSimple_Ibase_Blob($this, $blobid); - return $obj; + return new DbSimple_Ibase_Blob($this, $blobid); } function _performGetBlobFieldNames($result) { $blobFields = array(); for ($i=ibase_num_fields($result)-1; $i>=0; $i--) { - $info = ibase_field_info($result, $i); + $info = ibase_field_info($result, $i); if ($info['type'] === "BLOB") $blobFields[] = $info['name']; } return $blobFields; @@ -132,14 +131,14 @@ class DbSimple_Ibase extends DbSimple_Generic_Database case 'CALC_TOTAL': // Not possible return true; - + // Perform total calculation. case 'GET_TOTAL': // TODO: GROUP BY ... -> COUNT(DISTINCT ...) $re = '/^ (?> -- [^\r\n]* | \s+)* (\s* SELECT \s+) #1 - ((?:FIRST \s+ \S+ \s+ (?:SKIP \s+ \S+ \s+)? )?) #2 + ((?:FIRST \s+ \S+ \s+ (?:SKIP \s+ \S+ \s+)? )?) #2 (.*?) #3 (\s+ FROM \s+ .*?) #4 ((?:\s+ ORDER \s+ BY \s+ .*)?) #5 @@ -149,12 +148,12 @@ class DbSimple_Ibase extends DbSimple_Generic_Database $queryMain[0] = $m[1] . $this->_fieldList2Count($m[3]) . " AS C" . $m[4]; $skipHead = substr_count($m[2], '?'); if ($skipHead) array_splice($queryMain, 1, $skipHead); - $skipTail = substr_count($m[5], '?'); + $skipTail = substr_count($m[5], '?'); if ($skipTail) array_splice($queryMain, -$skipTail); } return true; } - + return false; } @@ -164,7 +163,7 @@ class DbSimple_Ibase extends DbSimple_Generic_Database $this->_expandPlaceholders($queryMain, $this->DbSimple_Ibase_USE_NATIVE_PHOLDERS); $hash = $queryMain[0]; - + if (!isset($this->prepareCache[$hash])) { $this->prepareCache[$hash] = @ibase_prepare((is_resource($this->trans) ? $this->trans : $this->link), $queryMain[0]); } else { @@ -177,11 +176,11 @@ class DbSimple_Ibase extends DbSimple_Generic_Database $result = @call_user_func_array('ibase_execute', $queryMain); // ATTENTION!!! // WE MUST save prepared ID (stored in $prepared variable) somewhere - // before returning $result because of ibase destructor. Now it is done - // by $this->prepareCache. When variable $prepared goes out of scope, it - // is destroyed, and memory for result also freed by PHP. Totally we - // got "Invalud statement handle" error message. - + // before returning $result because of ibase destructor. Now it is done + // by $this->prepareCache. When variable $prepared goes out of scope, it + // is destroyed, and memory for result also freed by PHP. Totally we + // got "Invalud statement handle" error message. + if ($result === false) return $this->_setDbError($queryMain[0]); if (!is_resource($result)) { // Non-SELECT queries return number of affected rows, SELECT - resource. @@ -194,24 +193,23 @@ class DbSimple_Ibase extends DbSimple_Generic_Database { // Select fetch mode. $flags = $this->fetchFlags; - if (empty($this->attributes['BLOB_OBJ'])) $flags = $flags | IBASE_TEXT; + if (empty($this->attributes['BLOB_OBJ'])) $flags = $flags | IBASE_TEXT; else $flags = $flags & ~IBASE_TEXT; $row = @ibase_fetch_assoc($result, $flags); if (ibase_errmsg()) return $this->_setDbError($this->_lastQuery); - if ($row === false) return null; return $row; } - - + + function _setDbError($query) { return $this->_setLastError(ibase_errcode(), ibase_errmsg(), $query); } - + } -class DbSimple_Ibase_Blob extends DbSimple_Generic_Blob +class DbSimple_Ibase_Blob implements DbSimple_Blob { var $blob; // resourse link var $id; @@ -230,7 +228,7 @@ class DbSimple_Ibase_Blob extends DbSimple_Generic_Blob if (!($e=$this->_firstUse())) return $e; $data = @ibase_blob_get($this->blob, $len); if ($data === false) return $this->_setDbError('read'); - return $data; + return $data; } function write($data) @@ -266,8 +264,8 @@ class DbSimple_Ibase_Blob extends DbSimple_Generic_Blob function _setDbError($query) { $hId = $this->id === null ? "null" : ($this->id === false ? "false" : $this->id); - $query = "-- $query BLOB $hId"; - $this->database->_setDbError($query); + $query = "-- $query BLOB $hId"; + $this->database->_setDbError($query); } // Called on each blob use (reading or writing). @@ -278,7 +276,7 @@ class DbSimple_Ibase_Blob extends DbSimple_Generic_Blob // Open or create blob. if ($this->id !== null) { $this->blob = @ibase_blob_open($this->id); - if ($this->blob === false) return $this->_setDbError('open'); + if ($this->blob === false) return $this->_setDbError('open'); } else { $this->blob = @ibase_blob_create($this->database->link); if ($this->blob === false) return $this->_setDbError('create'); diff --git a/includes/DbSimple/Mysql.php b/includes/DbSimple/Mysql.php index 9d75fec4..171ae62c 100644 --- a/includes/DbSimple/Mysql.php +++ b/includes/DbSimple/Mysql.php @@ -13,16 +13,16 @@ * * @author Dmitry Koterov, http://forum.dklab.ru/users/DmitryKoterov/ * @author Konstantin Zhinko, http://forum.dklab.ru/users/KonstantinGinkoTit/ - * - * @version 2.x $Id: Mysql.php 163 2007-01-10 09:47:49Z dk $ + * + * @version 2.x $Id: Mysql.php 247 2008-08-18 21:17:08Z dk $ */ -require_once dirname(__FILE__) . '/Generic.php'; +require_once dirname(__FILE__).'/Database.php'; /** * Database class for MySQL. */ -class DbSimple_Mysql extends DbSimple_Generic_Database +class DbSimple_Mysql extends DbSimple_Database { var $link; @@ -32,58 +32,54 @@ class DbSimple_Mysql extends DbSimple_Generic_Database */ function DbSimple_Mysql($dsn) { - $p = DbSimple_Generic::parseDSN($dsn); - if (!is_callable('mysql_connect')) { - return $this->_setLastError("-1", "MySQL extension is not loaded", "mysql_connect"); - } - $ok = $this->link = @mysql_connect( - $p['host'] . (empty($p['port'])? "" : ":".$p['port']), - $p['user'], - $p['pass'], + $connect = 'mysql_'.((isset($dsn['persist']) && $dsn['persist'])?'p':'').'connect'; + if (!is_callable($connect)) + return $this->_setLastError("-1", "MySQL extension is not loaded", $connect); + $ok = $this->link = @call_user_func($connect, + $dsn['host'] . (empty($dsn['port'])? "" : ":".$dsn['port']), + empty($dsn['user'])?'':$dsn['user'], + empty($dsn['pass'])?'':$dsn['pass'], true ); $this->_resetLastError(); - if (!$ok) return $this->_setDbError('mysql_connect()'); - $ok = @mysql_select_db(preg_replace('{^/}s', '', $p['path']), $this->link); - if (!$ok) return $this->_setDbError('mysql_select_db()'); + if (!$ok) + if (!$ok) return $this->_setDbError('mysql_connect("' . $str . '", "' . $p['user'] . '")'); + $ok = @mysql_select_db(preg_replace('{^/}s', '', $dsn['path']), $this->link); + if (!$ok) + return $this->_setDbError('mysql_select_db()'); + mysql_query('SET NAMES '.(isset($dsn['enc'])?$dsn['enc']:'UTF8')); } - function _performEscape($s, $isIdent=false) + protected function _performEscape($s, $isIdent=false) { - if (!$isIdent) { + if (!$isIdent) return "'" . mysql_real_escape_string($s, $this->link) . "'"; - } else { + else return "`" . str_replace('`', '``', $s) . "`"; - } } - function _performTransaction($parameters=null) + protected function _performNewBlob($blobid=null) { - return $this->query('BEGIN'); + return new DbSimple_Mysql_Blob($this, $blobid); } - function& _performNewBlob($blobid=null) - { - $obj =& new DbSimple_Mysql_Blob($this, $blobid); - return $obj; - } - - - function _performGetBlobFieldNames($result) + protected function _performGetBlobFieldNames($result) { $blobFields = array(); - for ($i=mysql_num_fields($result)-1; $i>=0; $i--) { - $type = mysql_field_type($result, $i); - if (strpos($type, "BLOB") !== false) $blobFields[] = mysql_field_name($result, $i); + for ($i=mysql_num_fields($result)-1; $i>=0; $i--) + { + $type = mysql_field_type($result, $i); + if (stripos($type, "BLOB") !== false) + $blobFields[] = mysql_field_name($result, $i); } return $blobFields; } - function _performGetPlaceholderIgnoreRe() + protected function _performGetPlaceholderIgnoreRe() { return ' " (?> [^"\\\\]+|\\\\"|\\\\)* " | @@ -94,134 +90,119 @@ class DbSimple_Mysql extends DbSimple_Generic_Database } - function _performCommit() + protected function _performTransaction($parameters=null) + { + return $this->query('BEGIN'); + } + + + protected function _performCommit() { return $this->query('COMMIT'); } - function _performRollback() + protected function _performRollback() { return $this->query('ROLLBACK'); } - function _performTransformQuery(&$queryMain, $how) + protected function _performTransformQuery(&$queryMain, $how) { // If we also need to calculate total number of found rows... - switch ($how) { + switch ($how) + { // Prepare total calculation (if possible) case 'CALC_TOTAL': $m = null; - if (preg_match('/^(\s* SELECT)(.*)/six', $queryMain[0], $m)) { - if ($this->_calcFoundRowsAvailable()) { - $queryMain[0] = $m[1] . ' SQL_CALC_FOUND_ROWS' . $m[2]; - } - } + if (preg_match('/^(\s* SELECT)(.*)/six', $queryMain[0], $m)) + $queryMain[0] = $m[1] . ' SQL_CALC_FOUND_ROWS' . $m[2]; return true; - + // Perform total calculation. case 'GET_TOTAL': // Built-in calculation available? - if ($this->_calcFoundRowsAvailable()) { - $queryMain = array('SELECT FOUND_ROWS()'); - } - // Else use manual calculation. - // TODO: GROUP BY ... -> COUNT(DISTINCT ...) - $re = '/^ - (?> -- [^\r\n]* | \s+)* - (\s* SELECT \s+) #1 - (.*?) #2 - (\s+ FROM \s+ .*?) #3 - ((?:\s+ ORDER \s+ BY \s+ .*?)?) #4 - ((?:\s+ LIMIT \s+ \S+ \s* (?:, \s* \S+ \s*)? )?) #5 - $/six'; - $m = null; - if (preg_match($re, $queryMain[0], $m)) { - $query[0] = $m[1] . $this->_fieldList2Count($m[2]) . " AS C" . $m[3]; - $skipTail = substr_count($m[4] . $m[5], '?'); - if ($skipTail) array_splice($query, -$skipTail); - } + $queryMain = array('SELECT FOUND_ROWS()'); return true; } - + return false; } - function _performQuery($queryMain) + protected function _performQuery($queryMain) { $this->_lastQuery = $queryMain; $this->_expandPlaceholders($queryMain, false); - $result = @mysql_query($queryMain[0], $this->link); - if ($result === false) return $this->_setDbError($queryMain[0]); + $result = mysql_query($queryMain[0], $this->link); + if ($result === false) + return $this->_setDbError($queryMain[0]); if (!is_resource($result)) { - if (preg_match('/^\s* INSERT \s+/six', $queryMain[0])) { + if (preg_match('/^\s* INSERT \s+/six', $queryMain[0])) + { // INSERT queries return generated ID. - return @mysql_insert_id($this->link); + return mysql_insert_id($this->link); } // Non-SELECT queries return number of affected rows, SELECT - resource. - return @mysql_affected_rows($this->link); + return mysql_affected_rows($this->link); } return $result; } - - function _performFetch($result) + + protected function _performFetch($result) { - $row = @mysql_fetch_assoc($result); + $row = mysql_fetch_assoc($result); if (mysql_error()) return $this->_setDbError($this->_lastQuery); - if ($row === false) return null; + if ($row === false) return null; return $row; } - - - function _setDbError($query) + + + protected function _setDbError($query) { - return $this->_setLastError(mysql_errno($this->link), mysql_error($this->link), $query); - } - - - function _calcFoundRowsAvailable() - { - $ok = version_compare(mysql_get_server_info($this->link), '4.0') >= 0; - return $ok; + if ($this->link) { + return $this->_setLastError(mysql_errno($this->link), mysql_error($this->link), $query); + } else { + return $this->_setLastError(mysql_errno(), mysql_error(), $query); + } } } -class DbSimple_Mysql_Blob extends DbSimple_Generic_Blob +class DbSimple_Mysql_Blob implements DbSimple_Blob { - // MySQL does not support separate BLOB fetching. - var $blobdata = null; - var $curSeek = 0; + // MySQL does not support separate BLOB fetching. + private $blobdata = null; + private $curSeek = 0; - function DbSimple_Mysql_Blob(&$database, $blobdata=null) + public function __construct(&$database, $blobdata=null) { $this->blobdata = $blobdata; $this->curSeek = 0; } - function read($len) + public function read($len) { $p = $this->curSeek; $this->curSeek = min($this->curSeek + $len, strlen($this->blobdata)); - return substr($this->blobdata, $this->curSeek, $len); + return substr($this->blobdata, $p, $len); } - function write($data) + public function write($data) { $this->blobdata .= $data; } - function close() + public function close() { return $this->blobdata; } - function length() + public function length() { return strlen($this->blobdata); } } -?> +?> \ No newline at end of file diff --git a/includes/DbSimple/Mysqli.php b/includes/DbSimple/Mysqli.php new file mode 100644 index 00000000..f19edcd9 --- /dev/null +++ b/includes/DbSimple/Mysqli.php @@ -0,0 +1,183 @@ +_setLastError('-1', 'mysqli extension is not loaded', 'mysqli'); + + try + { + $this->link = mysqli_init(); + + $this->link->options(MYSQLI_OPT_CONNECT_TIMEOUT, + isset($dsn['timeout']) && $dsn['timeout'] ? $dsn['timeout'] : 0); + + $this->link->real_connect((isset($dsn['persist']) && $dsn['persist'])?'p:'.$dsn['host']:$dsn['host'], + $dsn['user'], isset($dsn['pass'])?$dsn['pass']:'', $base, + empty($dsn['port'])?NULL:$dsn['port'], NULL, + (isset($dsn['compression']) && $dsn['compression']) + ? MYSQLI_CLIENT_COMPRESS : NULL); + + $this->link->set_charset((isset($dsn['enc']) ? $dsn['enc'] : 'UTF8')); + + $this->isMySQLnd = method_exists('mysqli_result', 'fetch_all'); + } + catch (mysqli_sql_exception $e) + { + $this->_setLastError($e->getCode() , $e->getMessage(), 'new mysqli'); + } + } + + protected function _performGetPlaceholderIgnoreRe() + { + return ' + " (?> [^"\\\\]+|\\\\"|\\\\)* " | + \' (?> [^\'\\\\]+|\\\\\'|\\\\)* \' | + ` (?> [^`]+ | ``)* ` | # backticks + /\* .*? \*/ # comments + '; + } + + protected function _performEscape($s, $isIdent=false) + { + if (!$isIdent) + { + return "'" .$this->link->escape_string($s). "'"; + } + else + { + return "`" . str_replace('`', '``', $s) . "`"; + } + } + + protected function _performTransaction($parameters=null) + { + return $this->link->query('BEGIN'); + } + + protected function _performCommit() + { + return $this->link->query('COMMIT'); + } + + protected function _performRollback() + { + return $this->link->query('ROLLBACK'); + } + + protected function _performQuery($queryMain) + { + $this->_lastQuery = $queryMain; + + $this->_expandPlaceholders($queryMain, false); + + $result = $this->link->query($queryMain[0]); + + if (!$result) + return $this->_setDbError($this->link, $queryMain[0]); + + if ($this->link->errno!=0) + return $this->_setDbError($this->link, $queryMain[0]); + + if (preg_match('/^\s* INSERT \s+/six', $queryMain[0])) + return $this->link->insert_id; + + if ($this->link->field_count == 0) + return $this->link->affected_rows; + + if ($this->isMySQLnd) + { + $res = $result->fetch_all(MYSQLI_ASSOC); + $result->close(); + } + else + { + $res = $result; + } + + return $res; + } + + protected function _performTransformQuery(&$queryMain, $how) + { + // If we also need to calculate total number of found rows... + switch ($how) + { + // Prepare total calculation (if possible) + case 'CALC_TOTAL': + $m = null; + if (preg_match('/^(\s* SELECT)(.*)/six', $queryMain[0], $m)) + $queryMain[0] = $m[1] . ' SQL_CALC_FOUND_ROWS' . $m[2]; + return true; + + // Perform total calculation. + case 'GET_TOTAL': + // Built-in calculation available? + $queryMain = array('SELECT FOUND_ROWS()'); + return true; + } + return false; + } + + protected function _setDbError($obj,$q) + { + $info=$obj?$obj:$this->link; + return $this->_setLastError($info->errno, $info->error, $q); + } + + protected function _performNewBlob($id=null) + { + } + + protected function _performGetBlobFieldNames($result) + { + return array(); + } + + protected function _performFetch($result) + { + if ($this->isMySQLnd) + return $result; + + $row = $result->fetch_assoc(); + + if ($this->link->error) + return $this->_setDbError($this->_lastQuery); + + if ($row === false) + { + $result->close(); + return null; + } + + return $row; + } +} + +?> \ No newline at end of file diff --git a/includes/class.achievement.php b/includes/class.achievement.php index c10a1a4b..611cd080 100644 --- a/includes/class.achievement.php +++ b/includes/class.achievement.php @@ -7,12 +7,13 @@ class AchievementList extends BaseType { use listviewHelper; - public static $type = TYPE_ACHIEVEMENT; + public static $type = TYPE_ACHIEVEMENT; - public $criteria = []; - public $tooltip = []; + public $criteria = []; + public $tooltip = []; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_achievement WHERE [filter] [cond] GROUP BY Id ORDER BY `orderInGroup` ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_achievement a'; + protected $queryOpts = ['a' => ['o' => 'orderInGroup ASC']]; public function __construct($conditions = [], $applyFilter = false) { @@ -95,27 +96,6 @@ class AchievementList extends BaseType return $data; } - // hmm, really needed? .. probably .. needs rename? .. also probably - public function getDetailedData() - { - $data = []; - - foreach ($this->iterate() as $__) - { - $data[$this->id] = array( - 'id' => $this->id, - 'name' => $this->getField('name', true), - 'description' => $this->getField('description', true), - 'points' => $this->curTpl['points'], - 'iconname' => $this->curTpl['iconString'], - 'count' => $this->curTpl['reqCriteriaCount'], - 'reward' => $this->getField('reward', true) - ); - } - - return $data; - } - // only for current template public function getCriteria($idx = -1) { @@ -242,4 +222,109 @@ class AchievementList extends BaseType } } + +class AchievementListFilter extends Filter +{ + // cr => [type, field, misc, extraCol] + protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet + 2 => [FILTER_CR_BOOLEAN, 'reward_loc0', true ], // givesreward + 3 => [FILTER_CR_STRING, 'reward', true ], // rewardtext + 7 => [FILTER_CR_BOOLEAN, 'series', ], // givesreward + 9 => [FILTER_CR_NUMERIC, 'id', null, true], // prcntbasemanarequired + 10 => [FILTER_CR_STRING, 'iconString', ], // icon + 18 => [FILTER_CR_STAFFFLAG, 'flags', ], // lastrank + ); + + 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]) + { + case 4: // location [enum] +/* todo */ return [1]; // no plausible locations parsed yet + case 5: // first in series [yn] + return $this->int2Bool($cr[1]) ? ['AND', ['series', 0, '!'], [['series', 0xFFFF0000, '&'], 0]] : [['series', 0xFFFF0000, '&'], 0, '!']; + case 6: // last in series [yn] + return $this->int2Bool($cr[1]) ? ['AND', ['series', 0, '!'], [['series', 0xFFFF, '&'], 0]] : [['series', 0xFFFF, '&'], 0, '!']; + case 11: // Related Event [enum] +/* todo */ return [1]; // >0:holidayId; -2323:any; -2324:none .. not quite like the subcategories + case 14: // hascomments [yn] +/* todo */ return [1]; + case 15: // hasscreenshots [yn] +/* todo */ return [1]; + case 16: // hasvideos [yn] +/* todo */ return [1]; + } + + unset($cr); + $this->error = 1; + return [1]; + } + + protected function createSQLForValues() + { + $parts = []; + $_v = &$this->fiData['v']; + + // name ex: +description, +rewards + if (isset($_v['na'])) + { + if (isset($_v['ex']) && $_v['ex'] == 'on') + $parts[] = ['OR', ['name_loc'.User::$localeId, $_v['na']], ['description_loc'.User::$localeId, $_v['na']], ['reward_loc'.User::$localeId, $_v['na']]]; + else + $parts[] = ['name_loc'.User::$localeId, $_v['na']]; + } + + // points min + if (isset($_v['minpt'])) + { + if ($this->isSaneNumeric($_v['minpt'])) + $parts[] = ['points', $_v['minpt'], '>=']; + else + unset($_v['minpt']); + } + + // points max + if (isset($_v['maxpt'])) + { + if ($this->isSaneNumeric($_v['maxpt'])) + $parts[] = ['points', $_v['maxpt'], '<=']; + else + unset($_v['maxpt']); + } + + // faction (side) + if (isset($_v['si'])) + { + switch ($_v['si']) + { + case 3: // both + $parts[] = ['faction', 0]; + break; + case -1: // faction, exclusive both + case -2: + $parts[] = ['faction', -$_v['si']]; + break; + case 1: // faction, inclusive both + case 2: + $parts[] = ['OR', ['faction', 0], ['faction', $_v['si']]]; + break; + default: + unset($_v['si']); + } + } + + return $parts; + } +} + ?> diff --git a/includes/class.basetype.php b/includes/class.basetype.php new file mode 100644 index 00000000..9a8a83c6 --- /dev/null +++ b/includes/class.basetype.php @@ -0,0 +1,865 @@ +% + * int - operator defaults to: = + * array - operator defaults to: IN () + * operator: modifies/overrides default + * ! - negated default value (NOT LIKE; <>; NOT IN) + * condition as str + * defines linking (AND || OR) + * condition as int + * defines LIMIT + * + * example: + * array( + * ['id', 45], + * ['name', 'test', '!'], + * [ + * 'AND', + * ['flags', 0xFF, '&'], + * ['flags2', 0xF, '&'], + * ] + * [['mask', 0x3, '&'], 0] + * 'OR', + * 5 + * ) + * results in + * WHERE ((`id` = 45) OR (`name` NOT LIKE "%test%") OR ((`flags` & 255) AND (`flags2` & 15)) OR ((`mask` & 3) = 0)) LIMIT 5 + */ + public function __construct($conditions = [], $applyFilter = false) + { + global $AoWoWconf; // yes i hate myself.. + + $where = []; + $linking = ' AND '; + $limit = $AoWoWconf['sqlLimit']; + $className = get_class($this); + + if (!$this->queryBase || $conditions === null) + return; + + // may be called without filtering + if ($applyFilter && class_exists($className.'Filter')) + { + $fiName = $className.'Filter'; + $this->filter = new $fiName($this); + } + + $prefixes = []; + if (preg_match('/FROM \??[\w\_]+( AS)?\s?`?(\w+)`?$/i', $this->queryBase, $match)) + $prefixes['base'] = $match[2]; + else + $prefixes['base'] = ''; + + // add conditions from filter + // to consider - conditions from filters are already escaped and contain sql-wildcards reescaping those sucks + $conditions[] = $this->filter ? $this->filter->getConditions() : null; + + $resolveCondition = function ($c, $supLink) use (&$resolveCondition, &$prefixes) + { + $subLink = ''; + + if (!$c) + return null; + + foreach ($c as $foo) + { + if ($foo === 'AND') + $subLink = ' AND '; + else if ($foo === 'OR') // nessi-bug: if (0 == 'OR') was true once... w/e + $subLink = ' OR '; + } + + // need to manually set link for subgroups to be recognized as condition set + if ($subLink) + { + $sql = []; + + foreach ($c as $foo) + if (is_array($foo)) + if ($x = $resolveCondition($foo, $supLink)) + $sql[] = $x; + + return '('.implode($subLink, $sql).')'; + } + else + { + if ($c[0] == '1') + return '1'; + else if ($c[0] == '0') + return '(0)'; // trick if ($x = 0) into true... + else if (is_array($c[0]) && isset($c[1])) + $field = $resolveCondition($c[0], $supLink); + else if ($c[0]) + { + $setPrefix = function($f) use(&$prefixes) + { + if (is_array($f)) + $f = $f[0]; + + if (is_numeric($f)) + return $f; + + $f = explode('.', Util::sqlEscape($f)); + + switch (count($f)) + { + case 2: + if (!in_array($f[0], $prefixes)) + { + // choose table to join or return null if prefix not existant + if (!in_array($f[0], array_keys($this->queryOpts))) + return null; + + $prefixes[] = $f[0]; + } + + return '`'.$f[0].'`.`'.$f[1].'`'; + case 1: + return '`'.$prefixes['base'].'`.`'.$f[0].'`'; + default: + return null; + } + }; + + // basic formulas + if (preg_match('/^\([\s\+\-\*\/\w\(\)\.]+\)$/i', strtr($c[0], ['`' => '', '´' => '', '--' => '']))) + $field = preg_replace_callback('/[\w\]*\.?[\w]+/i', $setPrefix, $c[0]); + else + $field = $setPrefix($c[0]); + + if (!$field) + return null; + } + else + return null; + + if (is_array($c[1])) + { + $val = implode(',', Util::sqlEscape($c[1], true)); + if ($val === '') + return null; + + $op = (isset($c[2]) && $c[2] == '!') ? 'NOT IN' : 'IN'; + $val = '('.$val.')'; + } + else if (Util::checkNumeric($c[1])) + { + $op = (isset($c[2]) && $c[2] == '!') ? '<>' : '='; + $val = $c[1]; + } + else if (is_string($c[1])) + { + $val = Util::sqlEscape($c[1], true); + + $op = (isset($c[2]) && $c[2] == '!') ? 'NOT LIKE' : 'LIKE'; + $val = $val === '' ? '""' : '"%'.$val.'%"'; + } + else // null for example + return null; + + if (isset($c[2]) && $c[2] != '!') + $op = $c[2]; + + return '('.$field.' '.$op.' '.$val.')'; + } + }; + + foreach ($conditions as $i => $c) + { + switch(getType($c)) + { + case 'array': + break; + case 'string': + case 'integer': + if (is_string($c)) + $linking = $c == 'AND' ? ' AND ' : ' OR '; + else + $limit = $c > 0 ? $c : 0; + default: + unset($conditions[$i]); + } + } + + foreach ($conditions as $c) + if ($x = $resolveCondition($c, $linking)) + $where[] = $x; + + // optional query parts may require other optional parts to work + foreach ($prefixes as $pre) + if (isset($this->queryOpts[$pre][0])) + foreach ($this->queryOpts[$pre][0] as $req) + if (!in_array($req, $prefixes)) + $prefixes[] = $req; + + // remove optional query parts, that are not required + foreach ($this->queryOpts as $k => $arr) + if (!in_array($k, $prefixes)) + unset($this->queryOpts[$k]); + + // insert additional selected fields + if ($s = array_column($this->queryOpts, 's')) + $this->queryBase = str_replace(' FROM', implode('', $s).' FROM', $this->queryBase); + + // append joins + if ($j = array_column($this->queryOpts, 'j')) + foreach ($j as $_) + $this->queryBase .= is_array($_) ? (empty($_[1]) ? ' JOIN ' : ' LEFT JOIN ') . $_[0] : ' JOIN ' . $_; + + // append conditions + if ($where) + $this->queryBase .= ' WHERE (' . implode($linking, $where).')'; + + // append grouping + if ($g = array_column($this->queryOpts, 'g')) + $this->queryBase .= ' GROUP BY ' . implode(', ', $g); + + // append post filtering + if ($h = array_column($this->queryOpts, 'h')) + $this->queryBase .= ' HAVING ' . implode(' AND ', $h); + + // append ordering + if ($o = array_column($this->queryOpts, 'o')) + $this->queryBase .= ' ORDER BY ' . implode(', ', $o); + + // apply limit + if ($limit) + $this->queryBase .= ' LIMIT '.$limit; + +// Util::execTime(true); +// var_dump($this->queryBase); +// echo "

\n\n"; + // execure query (finally) + $rows = DB::Aowow()->SelectPage($this->matches, $this->queryBase); + if (!$rows) + return; +// var_dump(Util::execTime()); +// echo "


\n\n"; + + // assign query results to template + foreach ($rows as $k => $tpl) + $this->templates[$k] = $tpl; + + // push first element for instant use + $this->reset(); + + // all clear + $this->error = false; + } + + public function &iterate() + { + // reset on __construct + $this->reset(); + + while (list($id, $_) = each($this->templates)) + { + $this->id = $id; + $this->curTpl = &$this->templates[$id]; // do not use $tpl from each(), as we want to be referenceable + + yield $id => $this->curTpl; + + unset($this->curTpl); // kill reference or it will 'bleed' into the next iteration + } + + // reset on __destruct .. Generator, Y U NO HAVE __destruct ?! + $this->reset(); + } + + protected function reset() + { + unset($this->curTpl); // kill reference or strange stuff will happen + $this->curTpl = reset($this->templates); + $this->id = key($this->templates); + } + + // read-access to templates + public function getField($field, $localized = false) + { + if (!$this->curTpl || (!$localized && !isset($this->curTpl[$field]))) + return ''; + + if ($localized) + return Util::localizedString($this->curTpl, $field); + + $value = $this->curTpl[$field]; + return Util::checkNumeric($value) ? floatVal($value) : $value; + } + + public function getRandomId() + { + $pattern = '/SELECT .* (-?[\w_]*\.?(id|entry)) AS ARRAY_KEY,?.* FROM (.*) WHERE .*/i'; + $replace = 'SELECT $1 FROM $3 ORDER BY RAND() ASC LIMIT 1'; + $query = preg_replace($pattern, $replace, $this->queryBase); + + return DB::Aowow()->selectCell($query); + } + + public function getFoundIDs() + { + return array_keys($this->templates); + } + + public function getMatches() + { + return $this->matches; + } + + public function filterGetForm($key = null, $raw = false) + { + $form = []; + + if (!$this->filter) + return $form; + + foreach ($this->filter->getForm() as $name => $data) + { + if (!$data || ($key && $name != $key)) + continue; + + switch ($name) + { + case 'setCriteria': + $form[$name] = $raw ? $data : 'fi_setCriteria('.$data['cr'].', '.$data['crs'].', '.$data['crv'].');'; + break; + case 'extraCols': + $form[$name] = $raw ? $data : 'fi_extraCols = '.json_encode(array_unique($data), JSON_NUMERIC_CHECK).';'; + break; + case 'setWeights': + $form[$name] = $raw ? $data : 'fi_setWeights('.json_encode($data, JSON_NUMERIC_CHECK).', 0, 1, 1);'; + break; + case 'form': + if ($key == $name) // only if explicitely specifies + $form[$name] = $data; + break; + default: + break; + } + } + + return $key ? (empty($form[$key]) ? [] : $form[$key]) : $form; + } + + public function filterGetError() + { + if ($this->filter) + return $this->filter->error; + else + return false; + } + + // should return data required to display a listview of any kind + // this is a rudimentary example, that will not suffice for most Types + abstract public function getListviewData(); + + // should return data to extend global js variables for a certain type (e.g. g_items) + abstract public function addGlobalsToJScript(&$smarty, $addMask = GLOBALINFO_ANY); + + // NPC, GO, Item, Quest, Spell, Achievement, Profile would require this + abstract public function renderTooltip(); +} + +trait listviewHelper +{ + public function hasSetFields($fields) + { + if (!is_array($fields)) + return 0x0; + + $result = 0x0; + + foreach ($this->iterate() as $__) + { + foreach ($fields as $k => $str) + { + if ($this->getField($str)) + { + $result |= 1 << $k; + unset($fields[$k]); + } + } + + if (empty($fields)) // all set .. return early + { + $this->reset(); // Generators have no __destruct, reset manually, when not doing a full iteration + return $result; + } + } + + return $result; + } + + public function hasDiffFields($fields) + { + if (!is_array($fields)) + return 0x0; + + $base = []; + $result = 0x0; + + foreach ($fields as $k => $str) + $base[$str] = $this->getField($str); + + foreach ($this->iterate() as $__) + { + foreach ($fields as $k => $str) + { + if ($base[$str] != $this->getField($str)) + { + $result |= 1 << $k; + unset($fields[$k]); + } + } + + if (empty($fields)) // all fields diff .. return early + { + $this->reset(); // Generators have no __destruct, reset manually, when not doing a full iteration + return $result; + } + } + + return $result; + } + + public function hasAnySource() + { + if (!isset($this->sources)) + return false; + + foreach ($this->sources as $src) + { + if (!is_array($src)) + continue; + + if (!empty($src)) + return true; + } + + return false; + } + +} + +trait spawnHelper +{ + private static $spawnQuery = " SELECT a.guid AS ARRAY_KEY, map, position_x, position_y, spawnMask, phaseMask, spawntimesecs, eventEntry, pool_entry AS pool FROM ?# a LEFT JOIN ?# b ON a.guid = b.guid LEFT JOIN ?# c ON a.guid = c.guid WHERE id = ?d"; + + private function fetch() + { + if (!$this->id) + return false; + + switch (get_class($this)) + { + case 'CreatureList': + return DB::Aowow()->select(self::$spawnQuery, 'creature', 'game_event_creature', 'pool_creature', $this->id); + case 'GameObjectList': + return DB::Aowow()->select(self::$spawnQuery, 'gameobject', 'game_event_gameobject', 'pool_gameobject', $this->id); + default: + return false; + } + } + + /* + todo (med): implement this alpha-map-check-virtual-map-transform-wahey! + note: map in tooltips is activated by either '#map' as anchor (will automatic open mapviewer, when clicking link) in the href or as parameterless rel-parameter e.g. rel="map" in the anchor + */ + public function getSpawns($spawnInfo) + { + // SPAWNINFO_SHORT: true => only the most populated area and only coordinates + $data = []; + + // $raw = $this->fetch(); + // if (!$raw) + // return []; + + /* + SPAWNINFO_FULL: + $data = array( + areaId => array( + floorNo => array ( + posX => + posY => + respawn => + phaseMask => + spawnMask => + eventId => + poolId => + ) + ) + ) + + SPAWNINFO_SHORT: [zoneId, [[x1, y1], [x2, y2], ..]] // only the most populated zone + + SPAWNINFO_ZONES: [zoneId1, zoneId2, ..] // only zones + */ + + return $data; + } +} + + +/* + roight! + just noticed, that the filters on pages originally pointed to ?filter= + wich probably checked for correctness of inputs and redirected the correct values as a get-request + .. + well, as it is now, its working .. and you never change a running system .. +*/ + +abstract class Filter +{ + private static $pattern = "/[^\p{L}0-9\s_\-\'\.\?\*]/ui";// delete any char not in unicode, number, space, underscore, hyphen, single quote, dot or common wildcard + private static $wCards = ['*' => '%', '?' => '_']; + private static $criteria = ['cr', 'crs', 'crv']; // [cr]iterium, [cr].[s]ign, [cr].[v]alue + + public $error = false; // erronous search fields + + private $cndSet = []; + + protected $parent = null; // itemFilter requires this + protected $fiData = ['c' => [], 'v' =>[]]; + protected $formData = array( // data to fill form fields + 'form' => [], // base form - unsanitized + 'setCriteria' => [], // dynamic criteria list - index checked + 'setWeights' => [], // dynamic weights list - index checked + 'extraCols' => [] // extra columns for LV - added as required + ); + + // parse the provided request into a usable format; recall self with GET-params if nessecary + public function __construct($parent) + { + $this->parent = $parent; + + // prefer POST over GET, translate to url + if (!empty($_POST)) + { + foreach ($_POST as $k => $v) + { + if (is_array($v)) // array -> max depths:1 + { + if (in_array($k, ['cr', 'wt']) && empty($v[0])) + continue; + + $sub = []; + foreach ($v as $sk => $sv) + $sub[$sk] = Util::checkNumeric($sv) ? $sv : urlencode($sv); + + if (!empty($sub) && in_array($k, self::$criteria)) + $this->fiData['c'][$k] = $sub; + else if (!empty($sub)) + $this->fiData['v'][$k] = $sub; + } + else // stings and integer + { + if (in_array($k, self::$criteria)) + $this->fiData['c'][$k] = Util::checkNumeric($v) ? $v : urlencode($v); + else + $this->fiData['v'][$k] = Util::checkNumeric($v) ? $v : urlencode($v); + } + } + + // create get-data + $tmp = []; + foreach (array_merge($this->fiData['c'], $this->fiData['v']) as $k => $v) + { + if ($v == '') + continue; + else if (is_array($v)) + $tmp[$k] = $k."=".implode(':', $v); + else + $tmp[$k] = $k."=".$v; + } + + // do get request + header('Location: '.STATIC_URL.'?'.$_SERVER['QUERY_STRING'].'='.implode(';', $tmp)); + } + // sanitize input and build sql + else if (!empty($_GET['filter'])) + { + $tmp = explode(';', $_GET['filter']); + $cr = $crs = $crv = []; + + foreach (self::$criteria as $c) + { + foreach ($tmp as $i => $term) + { + if (strpos($term, $c.'=') === 0) + { + $$c = explode(':', explode('=', $term)[1]); + $this->formData['setCriteria'][$c] = json_encode($$c, JSON_NUMERIC_CHECK); // todo (high): move to checks + unset($tmp[$i]); + } + } + } + + for ($i = 0; $i < max(count($cr), count($crv), count($crs)); $i++) + { + if (!isset($cr[$i]) || !isset($crs[$i]) || !isset($crv[$i]) || + !intVal($cr[$i]) || $crs[$i] === '' || $crv[$i] === '') + { + $this->error = true; + continue; + } + + $this->sanitize($crv[$i]); + + if ($crv[$i] !== '') + { + $this->fiData['c']['cr'][] = intVal($cr[$i]); + $this->fiData['c']['crs'][] = intVal($crs[$i]); + $this->fiData['c']['crv'][] = $crv[$i]; + } + else + $this->error = true; + + } + + foreach ($tmp as $v) + { + if (!strstr($v, '=')) + continue; + + $w = explode('=', $v); + + if (strstr($w[1], ':')) + { + $tmp2 = explode(':', $w[1]); + + $this->formData['form'][$w[0]] = $tmp2; + + array_walk($tmp2, function(&$v) { $v = intVal($v); }); + $this->fiData['v'][$w[0]] = $tmp2; + + } + else + { + $this->formData['form'][$w[0]] = $w[1]; + + $this->sanitize($w[1]); + + if ($w[1] !== '') + { + Util::checkNumeric($w[1]); + $this->fiData['v'][$w[0]] = $w[1]; + } + else + $this->error = true; + } + } + + $this->evaluateFilter(); + } + } + + // todo: kill data, that is unexpected or points to wrong indizes + private function evaluateFilter() + { + // values + $this->cndSet = $this->createSQLForValues(); + + // criteria + foreach ($this->criteriaIterator() as &$_cr) + $this->cndSet[] = $this->createSQLForCriterium($_cr); + + if ($this->cndSet) + array_unshift($this->cndSet, empty($this->fiData['v']['ma']) ? 'AND' : 'OR'); + } + + public function getForm() + { + return $this->formData; + } + + public function getConditions() + { + return $this->cndSet; + } + + // santas little helper.. + private function &criteriaIterator() + { + if (!$this->fiData['c']) + return; + + for ($i = 0; $i < count($this->fiData['c']['cr']); $i++) + { + // throws a notice if yielded directly "Only variable references should be yielded by reference" + $v = [&$this->fiData['c']['cr'][$i], &$this->fiData['c']['crs'][$i], &$this->fiData['c']['crv'][$i]]; + yield $i => $v; + } + } + + protected function int2Op(&$op) + { + switch ($op) + { + case 1: $op = '>'; return true; + case 2: $op = '>='; return true; + case 3: $op = '='; return true; + case 4: $op = '<='; return true; + case 5: $op = '<'; return true; + default: return false; + } + } + + protected function int2Bool(&$op) + { + switch ($op) + { + case 1: $op = true; return true; + case 2: $op = false; return true; + default: return false; + } + } + + protected function list2Mask($list, $noOffset = false) + { + $mask = 0x0; + $o = $noOffset ? 0 : 1; // schoolMask requires this..? + + if (!is_array($list)) + $mask = (1 << (intVal($list) - $o)); + else + foreach ($list as $itm) + $mask += (1 << (intVal($itm) - $o)); + + return $mask; + } + + protected function isSaneNumeric(&$val, $castInt = true) + { + if ($castInt && is_float($val)) + $val = intVal($val); + + if (is_int($val) || (is_float($val) && $val >= 0.0)) + return true; + + return false; + } + + private function sanitize(&$str) + { + $str = preg_replace(Filter::$pattern, '', trim($str)); + $str = Util::checkNumeric($str) ? $str : strtr($str, Filter::$wCards); + } + + private function genericBoolean($field, $op, $isString) + { + if ($this->int2Bool($op)) + { + if ($isString) + return [$field, '', $op ? '!' : null]; + else + return [$field, 0, $op ? '>' : '<=']; + } + + return null; + } + + private function genericBooleanFlags($field, $value, $op) + { + if ($this->int2Bool($op)) + return [[$field, $value, '&'], $op ? $value : 0]; + + return null; + } + + private function genericString($field, $value, $localized) + { + $field .= $localized ? '_loc' . User::$localeId : null; + + return [$field, (string)$value]; + } + + private function genericNumeric($field, &$value, $op, $castInt) + { + if (!$this->isSaneNumeric($value, $castInt)) + return null; + + if ($this->int2Op($op)) + return [$field, $value, $op]; + + return null; + } + + private function genericEnum($field, $value) + { + if (is_bool($value)) + return [$field, 0, ($value ? '>' : '<=')]; + else if ($value == -2323) // any + return [$field, 0, '>']; + else if ($value == -2324) // none + return [$field, 0, '<=']; + else if ($value !== null) + return [$field, $value]; + + return null; + } + + protected function genericCriterion(&$cr) + { + $gen = $this->genericFilter[$cr[0]]; + $result = null; + + switch ($gen[0]) + { + case FILTER_CR_NUMERIC: + $result = $this->genericNumeric($gen[1], $cr[2], $cr[1], empty($gen[2])); + break; + case FILTER_CR_FLAG: + $result = $this->genericBooleanFlags($gen[1], $gen[2], $cr[1]); + break; + case FILTER_CR_STAFFFLAG: + if (User::isInGroup(U_GROUP_STAFF) && $cr[1] >= 0) + $result = $this->genericBooleanFlags($gen[1], (1 << $cr[1]), true); + break; + case FILTER_CR_BOOLEAN: + $result = $this->genericBoolean($gen[1], $cr[1], !empty($gen[2])); + break; + case FILTER_CR_STRING: + $result = $this->genericString($gen[1], $cr[2], !empty($gen[2])); + break; + case FILTER_CR_ENUM: + if (isset($this->enums[$cr[0]][$cr[1]])) + $result = $this->genericEnum($gen[1], $this->enums[$cr[0]][$cr[1]]); + else if (intVal($cr[1]) != 0) + $result = $this->genericEnum($gen[1], intVal($cr[1])); + break; + } + + if ($result && !empty($gen[3])) + $this->formData['extraCols'][] = $cr[0]; + + return $result; + } + + // apply Util::sqlEscape() and intVal() in the implementation of these + abstract protected function createSQLForCriterium(&$cr); + abstract protected function createSQLForValues(); +} + +?> diff --git a/includes/class.charclass.php b/includes/class.charclass.php index 921a3c77..9650aa1b 100644 --- a/includes/class.charclass.php +++ b/includes/class.charclass.php @@ -5,9 +5,9 @@ if (!defined('AOWOW_REVISION')) class CharClassList extends BaseType { - public static $type = TYPE_CLASS; + public static $type = TYPE_CLASS; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_classes WHERE [cond] ORDER BY Id ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_classes c'; public function __construct($conditions = []) { diff --git a/includes/class.charrace.php b/includes/class.charrace.php index 81044064..ab3992dc 100644 --- a/includes/class.charrace.php +++ b/includes/class.charrace.php @@ -5,9 +5,9 @@ if (!defined('AOWOW_REVISION')) class CharRaceList extends BaseType { - public static $type = TYPE_RACE; + public static $type = TYPE_RACE; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_races WHERE [cond] ORDER BY Id ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_races r'; public function getListviewData() { diff --git a/includes/class.creature.php b/includes/class.creature.php index 626ba680..80007c59 100644 --- a/includes/class.creature.php +++ b/includes/class.creature.php @@ -8,11 +8,15 @@ class CreatureList extends BaseType { use spawnHelper; - public static $type = TYPE_NPC; + public static $type = TYPE_NPC; - public $tooltips = []; + public $tooltips = []; - protected $setupQuery = 'SELECT ct.*, ct.id AS ARRAY_KEY, ft.A, ft.H, ft.factionId FROM ?_creature ct LEFT JOIN ?_factiontemplate ft ON ft.id = ct.faction_A WHERE [filter] [cond]'; + protected $queryBase = 'SELECT ct.*, ct.id AS ARRAY_KEY FROM ?_creature ct'; + protected $queryOpts = array( + 'ct' => [['ft']], + 'ft' => ['j' => '?_factiontemplate ft ON ft.id = ct.faction_A', 's' => ', ft.A, ft.H, ft.factionId'] + ); public static function getName($id) { diff --git a/includes/class.currency.php b/includes/class.currency.php index 69855f9e..d6ccee1a 100644 --- a/includes/class.currency.php +++ b/includes/class.currency.php @@ -5,9 +5,9 @@ if (!defined('AOWOW_REVISION')) class CurrencyList extends BaseType { - public static $type = TYPE_CURRENCY; + public static $type = TYPE_CURRENCY; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_currencies WHERE [cond] ORDER BY Id ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_currencies c'; public function getListviewData() { diff --git a/includes/class.database.php b/includes/class.database.php index 150b66cf..aa573307 100644 --- a/includes/class.database.php +++ b/includes/class.database.php @@ -17,7 +17,7 @@ class DB private static function createConnectSyntax(&$options) { - return 'mysql://'.$options['user'].':'.$options['pass'].'@'.$options['host'].'/'.$options['db']; + return 'mysqli://'.$options['user'].':'.$options['pass'].'@'.$options['host'].'/'.$options['db']; } public static function connect($idx) diff --git a/includes/class.filter.php b/includes/class.filter.php deleted file mode 100644 index ce95bd8d..00000000 --- a/includes/class.filter.php +++ /dev/null @@ -1,530 +0,0 @@ - '%', '?' => '_']; - private static $criteria = ['cr', 'crs', 'crv']; // [cr]iterium, [cr].[s]ign, [cr].[v]alue - - private $fiData = ['c' => [], 'v' =>[]]; - private $query = ''; - private $form = []; // unsanitized: needed to preselect form-fields - private $setCr = []; // unsanitized: needed to preselect criteria - - public $error = false; // erronous search fields - - // parse the provided request into a usable format; recall self with GET-params if nessecary - public function init() - { - // prefer POST over GET, translate to url - if (!empty($_POST)) - { - foreach ($_POST as $k => $v) - { - if (is_array($v)) // array -> max depths:1 - { - if ($k == 'cr' && empty($v[0])) - continue; - - $sub = []; - foreach ($v as $sk => $sv) - $sub[$sk] = is_numeric($sv) ? (int)$sv : urlencode($sv); - - if (!empty($sub) && in_array($k, Filter::$criteria)) - $this->fiData['c'][$k] = $sub; - else if (!empty($sub)) - $this->fiData['v'][$k] = $sub; - } - else // stings and integer - { - if (in_array($k, Filter::$criteria)) - $this->fiData['c'][$k] = is_numeric($v) ? (int)$v : urlencode($v); - else - $this->fiData['v'][$k] = is_numeric($v) ? (int)$v : urlencode($v); - } - } - - // create get-data - $tmp = []; - foreach (array_merge($this->fiData['c'], $this->fiData['v']) as $k => $v) - { - if ($v == '') - continue; - else if (is_array($v)) - $tmp[$k] = $k."=".implode(':', $v); - else - $tmp[$k] = $k."=".$v; - } - - // do get request - $this->redirect(implode(';', $tmp)); - } - // sanitize input and build sql - else if (!empty($_GET['filter'])) - { - $tmp = explode(';', $_GET['filter']); - $cr = $crs = $crv = []; - - foreach (Filter::$criteria as $c) - { - foreach ($tmp as $i => $term) - { - if (strpos($term, $c.'=') === 0) - { - $$c = explode(':', explode('=', $term)[1]); - $this->setCr[$c] = json_encode($$c, JSON_NUMERIC_CHECK); - unset($tmp[$i]); - } - } - } - - for ($i = 0; $i < max(count($cr), count($crv), count($crs)); $i++) - { - if (!isset($cr[$i]) || !isset($crs[$i]) || !isset($crv[$i]) || - !intVal($cr[$i]) || $crs[$i] == '' || $crv[$i] == '') - { - $this->error = true; - continue; - } - - $this->sanitize($crv[$i]); - - if ($crv[$i] != '') - { - $this->fiData['c']['cr'][] = intVal($cr[$i]); - $this->fiData['c']['crs'][] = $crs[$i]; - $this->fiData['c']['crv'][] = $crv[$i]; - } - else - $this->error = true; - - } - - foreach ($tmp as $v) - { - $w = explode('=', $v); - - if (strstr($w[1], ':')) - { - $tmp2 = explode(':', $w[1]); - - $this->form[$w[0]] = $tmp2; - - array_walk($tmp2, function(&$v) { $v = intVal($v); }); - $this->fiData['v'][$w[0]] = $tmp2; - - } - else - { - $this->form[$w[0]] = $w[1]; - - $this->sanitize($w[1]); - - if ($w[1] != '') - $this->fiData['v'][$w[0]] = is_numeric($w[1]) ? (int)$w[1] : $w[1]; - else - $this->error = true; - } - } - - return $this->fiData; - } - } - - public function buildQuery() - { - if (!empty($this->query)) - return $this->query; - - $parts = []; - - // values - $parts = $this->createSQLForValues($this->fiData['v']); - - // criteria - $c = &$this->fiData['c']; - if (!empty($c)) - { - if (is_array($c['cr'])) - { - for ($i = 0; $i < count($c['cr']); $i++) - $parts[] = $this->createSQLForCriterium(array($c['cr'][$i], $c['crs'][$i], $c['crv'][$i])); - } - else - $parts[] = $this->createSQLForCriterium(array($c['cr'], $c['crs'], $c['crv'])); - } - - $this->query = empty($parts) ? '' : '('.implode(empty($this->fiData['v']['ma']) ? ' AND ' : ' OR ', $parts).')'; - return $this->query; - } - - public function getQuery() - { - return $this->query; - } - - public function getForm() - { - return $this->form; - } - - public function getSetCriteria() - { - if (!empty($this->setCr['cr'])) - return sprintf(Util::$setCriteriaString, $this->setCr['cr'], $this->setCr['crs'], $this->setCr['crv']); - else - return null; - } - - // santas little helper.. - protected function int2Op($int) - { - switch ($int) - { - case 1: return '>'; - case 2: return '>='; - case 3: return '='; - case 4: return '<='; - case 5: return '<'; - default: die('invalid op'); - } - } - - protected function int2Bool($int) - { - switch ($int) - { - case 1: return true; - case 2: return false; - default: die('invalid op'); - } - } - - protected function list2Mask($list, $noOffset = false) - { - $mask = 0x0; - $o = $noOffset ? 0 : 1; // schoolMask requires this..? - - if (is_array($list)) - { - foreach ($list as $itm) - $mask += (1 << (intVal($itm) - $o)); - } - else - $mask = (1 << (intVal($list) - $o)); - - return $mask; - } - - private function sanitize(&$str) - { - $str = preg_replace(Filter::$pattern, '', trim($str)); - $str = strtr($str, Filter::$wildcards); - } - - // if called with POST-data, convert to GET request and call self - private function redirect($get) - { - header('Location: '.STATIC_URL.'?'.$_SERVER['QUERY_STRING'].'='.$get); - } - - // TODO: wrong wrong wrong!! - // 1) filter-Ids are different for each type; 2) (NOT) IN - subqueries will eat the mysql-server alive! - protected function createSQLForCommunity($cr) - { - switch ($cr[0]) - { - case 14: // has Comments [y|n] - return ''; // IN / NOT IN (select Ids FROM aowow_comments ON type = X AND id Y and flags = valid) - case 15: // has Screenshots [y|n] - return ''; // IN / NOT IN (select Ids FROM aowow_screenshots ON type = X AND id Y and flags = valid) - case 16: // has Videos [y|n] - return ''; // IN / NOT IN (select Ids FROM aowow_videos ON type = X AND id Y and flags = valid) - } - } - - // apply Util::sqlEscape() and intVal() in the implementation of these - abstract protected function createSQLForCriterium($cr); - abstract protected function createSQLForValues($vl); -} - - -class AchievementListFilter extends Filter -{ - protected function createSQLForCriterium($cr) - { - if ($r = $this->createSQLForCommunity($cr)) - return $r; - - switch ($cr[0]) - { - case 2: // gives a reward [y|n] - return 'reward_loc0 '.($this->int2Bool($cr[1]) ? '<>' : '=').' \'\''; - case 3: // reward text [str] - return 'reward_loc'.User::$localeId.' LIKE \'%'.Util::sqlEscape($cr[2]).'%\''; - // case 4: // location [int] - // return ''; // no plausible locations parsed yet - case 5: // first in series [y|n] - return $this->int2Bool($cr[1]) ? '(series <> 0 AND (series & 0xFFFF0000) = 0)' : '(series & 0xFFFF0000) <> 0'; - case 6: // last in series [y|n] - return $this->int2Bool($cr[1]) ? '(series <> 0 AND (series & 0xFFFF) = 0)' : '(series & 0xFFFF) <> 0'; - case 7: // part of series [y|n] - return 'series '.($this->int2Bool($cr[1]) ? '<>' : '=').' 0'; - case 9: // Id [op] [int] - return 'id '.$this->int2Op($cr[1]).' '.intVal($cr[2]); - case 10: // Icon [str] - return 'iconString LIKE \'%'.Util::sqlEscape($cr[2]).'%\''; - // case 11: // Related Event [int] - // return ''; // >0:holidayId; -2323:any; -2324:none - case 18: // flags (staff only) - if (User::isInGroup(U_GROUP_STAFF)) - return 'flags &'.(1 << max($cr[1] - 1, 0)) ; - default: - return '1'; - } - } - - protected function createSQLForValues($vl) - { - $parts = []; - - // name ex: +description, +rewards - if (isset($vl['na'])) - { - if (isset($vl['ex']) && $vl['ex'] == 'on') - $parts[] = '(name_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%" OR description_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%" OR reward_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%")'; - else - $parts[] = 'name_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%"'; - } - - // points min - if (isset($vl['minpt'])) - $parts[] = 'points >= '.intVal($vl['minpt']); - - // points max - if (isset($vl['maxpt'])) - $parts[] = 'points <= '.intVal($vl['maxpt']); - - // faction (side) - if (isset($vl['si'])) - { - if ($vl['si'] == 3) // both - $parts[] = 'faction = '.intVal($vl['si']); - else if ($vl['si'] > 0) // faction, inclusive both - $parts[] = '(faction = 3 OR faction = '.intVal($vl['si']).')'; - else if ($vl['si'] < 0) // faction, exclusive both - $parts[] = 'faction = '.intVal(-$vl['si']); - - } - - return $parts; - } -} - - -class SpellListFilter extends Filter -{ - // sources in filter and general use different indizes - private static $fiSource = array( - 1 => -2, // Any - 2 => -1, // None - 3 => 1, // Crafted - 4 => 2, // Drop - 6 => 4, // Quest - 7 => 5, // Vendor - 8 => 6, // Trainer - 9 => 7, // Discovery - 10 => 9 // Talent - ); - - protected function createSQLForCriterium($cr) - { - if ($r = $this->createSQLForCommunity($cr)) - return $r; - - switch ($cr[0]) - { - case 1: // costAbs [op] [int] - return 's.powerCost '.$this->int2Op($cr[1]).' IF(s.powerType IN (1, 6), 10 * '.intVal($cr[2]).', '.intVal($cr[2]).')'; - case 2: // costPct [op] [int] - return 's.powerCostPercent '.$this->int2Op($cr[1]).' '.intVal($cr[2]); - case 3: // requires FocusGO [y|n] - return $this->int2Bool($cr[1]) ? 's.spellFocusObject > 0' : 's.spellFocusObject = 0'; - case 4: // trainingcost [op] [int] - return 's.trainingcost '.$this->int2Op($cr[1]).' '.intVal($cr[2]); - case 5: // Profession Specialitation [y|n] - return 's.reqSpellId '.($this->int2Bool($cr[1]) ? ' > ' : ' = ').' 0'; - case 9: // Source [int] - if ($foo = self::$fiSource[$cr[1]]) - { - if ($foo > 0) // specific - return 's.source LIKE "%'.$foo.':%"'; - else if ($foo == -2) // any - return 's.source <> ""'; - else if ($foo == -1) // none - return 's.source = ""'; - } - - return '1'; - case 10: // First Rank [y|n] - return $this->int2Bool($cr[1]) ? 's.cuFlags & '.SPELL_CU_FIRST_RANK : '(s.cuFlags & '.SPELL_CU_FIRST_RANK.') = 0' ; - case 12: // Last Rank [y|n] - return $this->int2Bool($cr[1]) ? 's.cuFlags & '.SPELL_CU_LAST_RANK : '(s.cuFlags & '.SPELL_CU_LAST_RANK.') = 0' ; - case 13: // Rank# [op] [int] - return 's.rankId '.$this->int2Op($cr[1]).' '.intVal($cr[2]); - case 14: // spellId [op] [int] - return 's.id '.$this->int2Op($cr[1]).' '.intVal($cr[2]); - case 15: // iconString [string] - return 's.iconString LIKE "%'.Util::sqlEscape($cr[2]).'%"'; - case 19: // scales /W level [y|n] 0x80000 = SPELL_ATTR0_LEVEL_DAMAGE_CALCULATION - return $this->int2Bool($cr[1]) ? 's.attributes0 & 0x80000' : '(s.attributes0 & 0x80000) = 0'; - case 20: // has Reagents [y|n] - return $this->int2Bool($cr[1]) ? 's.reagent1 > 0 OR s.reagent2 > 0 OR s.reagent3 > 0 OR s.reagent4 > 0 OR s.reagent5 > 0 OR s.reagent6 > 0 OR s.reagent7 > 0 OR s.reagent8 > 0' : 's.reagents1 = 0 AND s.reagents2 = 0 AND s.reagents3 = 0 AND s.reagents4 = 0 AND s.reagents5 = 0 AND s.reagents6 = 0 AND s.reagents7 = 0 AND s.reagents8 = 0'; - case 25: // rewards skill points [y|n] - return $this->int2Bool($cr[1]) ? 's.skillLevelYellow > 1' : 's.skillLevelYellow <= 1'; - default: - return '1'; - } - } - - protected function createSQLForValues($vl) - { - $parts = []; - - //string (extended) - if (isset($vl['na'])) - { - if (isset($vl['ex']) && $vl['ex'] == 'on') - $parts[] = '(s.name_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%" OR buff_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%" OR description_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%")'; - else - $parts[] = 's.name_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%"'; - } - - // spellLevel min - if (isset($vl['minle'])) - $parts[] = 's.spellLevel >= '.intVal($vl['minle']); - - // spellLevel max - if (isset($vl['maxle'])) - $parts[] = 's.spellLevel <= '.intVal($vl['maxle']); - - // skillLevel min - if (isset($vl['minrs'])) - $parts[] = 's.learnedAt >= '.intVal($vl['minrs']); - - // skillLevel max - if (isset($vl['maxrs'])) - $parts[] = 's.learnedAt <= '.intVal($vl['maxrs']); - - // race - if (isset($vl['ra'])) - $parts[] = 's.reqRaceMask & '.$this->list2Mask($vl['ra']); - - // class [list] - if (isset($vl['cl'])) - $parts[] = 's.reqClassMask & '.$this->list2Mask($vl['cl']); - - // school [list] - if (isset($vl['sc'])) - $parts[] = 's.schoolMask & '.$this->list2Mask($vl['sc'], true); - - // glyph type [list] wonky, admittedly, but consult SPELL_CU_* in defines and it makes sense - if (isset($vl['gl'])) - $parts[] = 's.cuFlags & '.($this->list2Mask($vl['gl']) << 6); - - // dispel type - if (isset($vl['dt'])) - $parts[] = 's.dispelType = '.intVal($vl['dt']); - - // mechanic - if (isset($vl['me'])) - $parts[] = 's.mechanic = '.intVal($vl['me']).' OR s.effect1Mechanic = '.intVal($vl['me']).' OR s.effect2Mechanic = '.intVal($vl['me']).' OR s.effect3Mechanic = '.intVal($vl['me']); - - return $parts; - } -} - - -// missing filter: "Available to Players" -class ItemsetListFilter extends Filter -{ - protected function createSQLForCriterium($cr) - { - if ($r = $this->createSQLForCommunity($cr)) - return $r; - - switch ($cr[0]) - { - case 2: // Id - return 'id '.$this->int2Op($cr[1]).' '.intVal($cr[2]); - case 3: // pieces [int] - return '(IF(item1 > 0, 1, 0) + IF(item2 > 0, 1, 0) + IF(item3 > 0, 1, 0) + IF(item4 > 0, 1, 0) + IF(item5 > 0, 1, 0) + - IF(item6 > 0, 1, 0) + IF(item7 > 0, 1, 0) + IF(item8 > 0, 1, 0) + IF(item9 > 0, 1, 0) + IF(item10 > 0, 1, 0)) ' .$this->int2Op($cr[1]).' '.intVal($cr[2]); - case 4: // bonustext [str] - return 'bonusText_loc'.User::$localeId.' LIKE \'%'.Util::sqlEscape($cr[2]).'%\''; - case 5: // heroic [y|n] - return 'heroic = '.($cr[1] == 1 ? '1' : '0'); - case 6: // related event [int] - $hId = intVal($cr[1]); // >0:holidayId; -2323:any; -2324:none - if ($hId == -2323) - return 'holidayId <> 0'; - - if ($hId == -2324) - $hId = 0; - - return 'holidayId = '.$hId; - // case 12: // available to players [y|n] - // return ''; // ugh .. scan loot, quest and vendor templates and write to ?_itemset - default: - return '1'; - } - } - - protected function createSQLForValues($vl) - { - $parts = []; - - //string (extended) - if (isset($vl['na'])) - $parts[] = 'name_loc'.User::$localeId.' LIKE "%'.Util::sqlEscape($vl['na']).'%"'; - - // quality [list] - if (isset($vl['qu'])) - $parts[] = 'quality IN ('.implode(', ', (array)$vl['qu']).')'; - - // type - if (isset($vl['ty'])) - $parts[] = 'type IN ('.implode(', ', (array)$vl['ty']).')'; - - // itemLevel min - if (isset($vl['minle'])) - $parts[] = 'minLevel >= '.intVal($vl['minle']); - - // itemLevel max - if (isset($vl['maxle'])) - $parts[] = 'maxLevel <= '.intVal($vl['maxle']); - - // reqLevel min - if (isset($vl['minrl'])) - $parts[] = 'reqLevel <= '.intVal($vl['minrl']); - - // reqLevel max - if (isset($vl['maxrl'])) - $parts[] = 'reqLevel <= '.intVal($vl['maxrl']); - - // class - if (isset($vl['cl'])) - $parts[] = 'classMask & '.$this->list2Mask($vl['cl']); - - // tag - if (isset($vl['ta'])) - $parts[] = 'contentGroup = '.$vl['ta']; - - return $parts; - } -} - -?> diff --git a/includes/class.item.php b/includes/class.item.php index 798fc938..aa994d97 100644 --- a/includes/class.item.php +++ b/includes/class.item.php @@ -7,51 +7,37 @@ class ItemList extends BaseType { use ListviewHelper; - public static $type = TYPE_ITEM; + public static $type = TYPE_ITEM; - public $tooltip = ''; - public $json = []; - public $itemMods = []; + public $tooltip = ''; + public $json = []; + public $itemMods = []; - public $rndEnchIds = []; - public $subItems = []; + public $rndEnchIds = []; + public $subItems = []; - private $ssd = []; - private $weightQuery = 'SELECT i.*, iX.*, l.*, i.entry AS ARRAY_KEY, [weightsA] AS sum FROM item_template i LEFT JOIN ?_item_template_addon iX ON i.entry = iX.id LEFT JOIN locales_item l ON i.entry = l.entry JOIN ?_item_stats ais ON ais.id = i.entry WHERE [weightsB] AND [cond] ORDER BY sum DESC'; + private $ssd = []; - protected $setupQuery = 'SELECT *, i.entry AS ARRAY_KEY FROM item_template i LEFT JOIN ?_item_template_addon iX ON i.entry = iX.id LEFT JOIN locales_item l ON i.entry = l.entry WHERE [filter] [cond] ORDER BY i.Quality DESC'; + protected $queryBase = 'SELECT i.*, i.id AS ARRAY_KEY FROM ?_items i'; + protected $queryOpts = array( + 'is' => [ 's' => ', 1 as score', 'j' => '?_item_stats AS `is` ON `is`.`id` = `i`.`id`', 'h' => 'score > 0', 'o' => 'score DESC'], + 'iet' => [['ire'], 'j' => 'item_enchantment_template AS `iet` ON IF (`ire`.`id` > 0, `iet`.`entry` = `i`.`randomProperty`, `iet`.`entry` = `i`.`randomSuffix`)'], + 'ire' => [['iet'], 'j' => '?_itemrandomenchant AS `ire` ON ABS(ire.id) = iet.ench'], + 'i' => [ 'o' => 'i.quality DESC, i.itemLevel DESC'] + ); - public function __construct($conditions = [], $miscData = null) + public function __construct($conditions = [], $applyFilter = false, $miscData = null) { // search by statweight - if ($miscData && isset($miscData['wt']) && isset($miscData['wtv']) && count($miscData['wt']) == count($miscData['wtv'])) - { - $wtA = $wtB = []; + if ($miscData && isset($miscData['wt']) && isset($miscData['wtv'])) + $conditions[] = $this->createConditionsForWeights($miscData, $this->queryOpts); - foreach ($miscData['wt'] as $k => $v) - { - if (@$str = Util::$itemFilter[$v]) - { - if ($str == 'rgdspeed') // dont need no duplicate column - $str = 'speed'; - - $wtA[] = '(`ais`.`'.$str.'` * '.intVal($miscData['wtv'][$k]).')'; - $wtB[] = '`ais`.`'.$str.'` <> 0'; - } - } - - $wtA = $wtA ? '('.implode(' + ', $wtA).')' : 1; - $wtB = $wtB ? '('.implode(' AND ', $wtB).')' : 1; - - $this->setupQuery = strtr($this->weightQuery, ['[weightsA]' => $wtA, '[weightsB]' => $wtB]); - } - - parent::__construct($conditions); + parent::__construct($conditions, $applyFilter); foreach ($this->iterate() as &$_curTpl) { // item is scaling; overwrite other values - if ($_curTpl['ScalingStatDistribution'] > 0 && $_curTpl['ScalingStatValue'] > 0) + if ($_curTpl['scalingStatDistribution'] > 0 && $_curTpl['scalingStatValue'] > 0) $this->initScalingStats(); $this->initJsonStats(); @@ -61,11 +47,11 @@ class ItemList extends BaseType $this->json[$this->id]['itemset'] = $miscData['pcsToSet'][$this->id]; // unify those pesky masks - $_ = &$_curTpl['AllowableClass']; + $_ = &$_curTpl['requiredClass']; if ($_ < 0 || ($_ & CLASS_MASK_ALL) == CLASS_MASK_ALL) $_ = 0; - $_ = &$_curTpl['AllowableRace']; + $_ = &$_curTpl['requiredRace']; if ($_ < 0 || ($_ & RACE_MASK_ALL) == RACE_MASK_ALL) $_ = 0; } @@ -76,14 +62,11 @@ class ItemList extends BaseType { $n = DB::Aowow()->selectRow(' SELECT - t.name, - l.* + name_loc0, name_loc2, name_loc3, name_loc6, name_loc8 FROM - item_template t, - locales_item l + ?_items WHERE - t.entry = ?d AND - t.entry = l.entry', + id = ?d', $id ); return Util::localizedString($n, 'name'); @@ -93,87 +76,117 @@ class ItemList extends BaseType { return DB::Aowow()->selectCol(' SELECT - a.entry + a.id FROM - item_template a, - item_template b + ?_items a, + ?_items b WHERE - b.entry = ?d AND - a.InventoryType = b.InventoryType AND + b.id = ?d AND + a.slotBak = b.slotBak AND a.itemset = b.itemset', $id ); } // end static use + public function getDetailPageData() + { + $data = array( + 'color' => Util::$rarityColorStings[$this->getField('quality')], + 'icon' => $this->getField('iconString'), + 'name' => $this->getField('name', true), + 'displayId' => $this->getField('displayId'), + 'slot' => $this->getField('slot'), + 'stack' => $this->getField('stackable'), + 'class' => $this->getField('class'), + ); + + return $data; + } + public function getListviewData($addInfoMask = 0x0) { - /* looks like this data differs per occasion - * - * maybe split in groups, like: - * ITEMINFO_JSON (0x1): itemMods (including spells) and subitems parsed - * ITEMINFO_SUBITEMS (0x2): searched by comparison - * ITEMINFO_VENDOR (0x4): costs-obj, when displayed as vendor - * ITEMINFO_LOOT (0x8): count, stack, pctstack, modes when displaying loot + /* + * ITEMINFO_JSON (0x01): itemMods (including spells) and subitems parsed + * ITEMINFO_SUBITEMS (0x02): searched by comparison + * ITEMINFO_VENDOR (0x04): costs-obj, when displayed as vendor + * ITEMINFO_LOOT (0x08): count, stack, pctstack, modes when displaying loot + * ITEMINFO_GEM (0x10): gem infos and score */ $data = []; foreach ($this->iterate() as $__) { // random item is random - if ($this->curTpl['RandomProperty'] > 0 || $this->curTpl['RandomSuffix'] > 0) + if ($this->curTpl['randomProperty'] > 0 || $this->curTpl['randomSuffix'] > 0) if ($addInfoMask & ITEMINFO_SUBITEMS) $this->initSubItems(); if ($addInfoMask & ITEMINFO_JSON) $this->extendJsonStats(); - $data[$this->id] = $this->json[$this->id]; + foreach ($this->json[$this->id] as $k => $v) + $data[$this->id][$k] = $v; // json vs listview quirk $data[$this->id]['name'] = $data[$this->id]['quality'].$data[$this->id]['name']; unset($data[$this->id]['quality']); - if (isset($this->itemMods[$this->id])) // due to ITEMINFO_JSON + if ($addInfoMask & ITEMINFO_JSON) + { foreach ($this->itemMods[$this->id] as $k => $v) $data[$this->id][Util::$itemMods[$k]] = $v; + if ($_ = intVal(($this->curTpl['minMoneyLoot'] + $this->curTpl['maxMoneyLoot']) /2)) + $data[$this->id]['avgmoney'] = $_; + } + + if ($addInfoMask & (ITEMINFO_JSON |ITEMINFO_GEM)) + if (isset($this->curTpl['score'])) + $data[$this->id]['score'] = $this->curTpl['score']; + + if ($addInfoMask & ITEMINFO_GEM) + { + $data[$this->id]['uniqEquip'] = ($this->curTpl['flags'] & ITEM_FLAG_UNIQUEEQUIPPED) ? 1 : 0; + $data[$this->id]['socketLevel'] = 0; // not used with wotlk + } + if ($addInfoMask & ITEMINFO_VENDOR) { - if ($x = $this->curTpl['BuyPrice']) + if ($x = $this->curTpl['buyPrice']) $data[$this->id]['buyprice'] = $x; - if ($x = $this->curTpl['SellPrice']) + if ($x = $this->curTpl['sellPrice']) $data[$this->id]['sellprice'] = $x; } // complicated data - if ($x = $this->curTpl['RequiredSkill']) + if ($x = $this->curTpl['requiredSkill']) $data[$this->id]['reqskill'] = $x; - if ($x = $this->curTpl['RequiredSkillRank']) + if ($x = $this->curTpl['requiredSkillRank']) $data[$this->id]['reqskillrank'] = $x; - if ($x = $this->curTpl['requiredspell']) + if ($x = $this->curTpl['requiredSpell']) $data[$this->id]['reqspell'] = $x; - if ($x = $this->curTpl['RequiredReputationFaction']) + if ($x = $this->curTpl['requiredFaction']) $data[$this->id]['reqfaction'] = $x; - if ($x = $this->curTpl['RequiredReputationRank']) + if ($x = $this->curTpl['requiredFactionRank']) $data[$this->id]['reqrep'] = $x; - if ($x = $this->curTpl['ContainerSlots']) + if ($x = $this->curTpl['slots']) $data[$this->id]['nslots'] = $x; - $_ = $this->curTpl['AllowableRace']; + $_ = $this->curTpl['requiredRace']; if ($_ && $_ & RACE_MASK_ALLIANCE != RACE_MASK_ALLIANCE && $_ & RACE_MASK_HORDE != RACE_MASK_HORDE) $data[$this->id]['reqrace'] = $_; - if ($_ = $this->curTpl['AllowableClass']) + if ($_ = $this->curTpl['requiredClass']) $data[$this->id]['reqclass'] = $_; // $data[$this->id]['classes'] ?? - if ($this->curTpl['Flags'] & ITEM_FLAG_HEROIC) + if ($this->curTpl['flags'] & ITEM_FLAG_HEROIC) $data[$this->id]['heroic'] = true; } @@ -201,8 +214,8 @@ class ItemList extends BaseType { $template->extendGlobalData(self::$type, [$this->id => array( 'name' => $this->getField('name', true), - 'quality' => $this->curTpl['Quality'], - 'icon' => $this->curTpl['icon'] + 'quality' => $this->curTpl['quality'], + 'icon' => $this->curTpl['iconString'] )]); } } @@ -221,15 +234,20 @@ class ItemList extends BaseType if ($this->error) return; - $name = $this->getField('name', true); - if (!empty($this->tooltip[$this->id])) return $this->tooltip[$this->id]; + $_name = $this->getField('name', true); + $_reqLvl = $this->curTpl['requiredLevel']; + $_quality = $this->curTpl['quality']; + $_flags = $this->curTpl['flags']; + $_class = $this->curTpl['class']; + $_subClass = $this->curTpl['subClass']; + if (!empty($enhance['rand'])) { $rndEnch = DB::Aowow()->selectRow('SELECT * FROM ?_itemrandomenchant WHERE Id = ?d', $enhance['rand']); - $name .= ' '.Util::localizedString($rndEnch, 'name'); + $_name .= ' '.Util::localizedString($rndEnch, 'name'); $randEnchant['stats'] = ''; for ($i = 1; $i < 6; $i++) @@ -257,92 +275,93 @@ class ItemList extends BaseType // name; quality if ($subT) - $x .= ''.$name.''; + $x .= ''.$_name.''; else - $x .= ''.$name.''; + $x .= ''.$_name.''; // heroic tag - if (($this->curTpl['Flags'] & ITEM_FLAG_HEROIC) && $this->curTpl['Quality'] == ITEM_QUALITY_EPIC) + if (($_flags & ITEM_FLAG_HEROIC) && $_quality == ITEM_QUALITY_EPIC) $x .= '
'.Lang::$item['heroic'].''; // requires map (todo: reparse ?_zones for non-conflicting data; generate Link to zone) - if ($this->curTpl['Map']) + if ($this->curTpl['map']) { - $map = DB::Aowow()->selectRow('SELECT * FROM ?_zones WHERE mapId=?d LIMIT 1', $this->curTpl['Map']); + $map = DB::Aowow()->selectRow('SELECT * FROM ?_zones WHERE mapId=?d LIMIT 1', $this->curTpl['map']); $x .= '
'.Util::localizedString($map, 'name'); } // requires area if ($this->curTpl['area']) { - $area = DB::Aowow()->selectRow('SELECT * FROM ?_areatable WHERE Id=?d LIMIT 1', $this->curTpl['area']); + $area = DB::Aowow()->selectRow('SELECT * FROM ?_zones WHERE Id=?d LIMIT 1', $this->curTpl['area']); $x .= '
'.Util::localizedString($area, 'name'); } // conjured - if ($this->curTpl['Flags'] & ITEM_FLAG_CONJURED) + if ($_flags & ITEM_FLAG_CONJURED) $x .= '
'.Lang::$item['conjured']; // bonding - if (($this->curTpl['Flags'] & ITEM_FLAG_ACCOUNTBOUND) && $this->curTpl['Quality'] == ITEM_QUALITY_HEIRLOOM) + if (($_flags & ITEM_FLAG_ACCOUNTBOUND) && $_quality == ITEM_QUALITY_HEIRLOOM) $x .= '
'.Lang::$item['bonding'][0]; else if ($this->curTpl['bonding']) $x .= '
'.Lang::$item['bonding'][$this->curTpl['bonding']]; // unique || unique-equipped || unique-limited - if ($this->curTpl['maxcount'] == 1) + if ($this->curTpl['maxCount'] == 1) $x .= '
'.Lang::$item['unique']; - else if ($this->curTpl['Flags'] & ITEM_FLAG_UNIQUEEQUIPPED) + else if ($_flags & ITEM_FLAG_UNIQUEEQUIPPED) $x .= '
'.Lang::$item['uniqueEquipped']; - else if ($this->curTpl['ItemLimitCategory']) + else if ($this->curTpl['itemLimitCategory']) { - $limit = DB::Aowow()->selectRow("SELECT * FROM ?_itemlimitcategory WHERE id = ?", $this->curTpl['ItemLimitCategory']); + $limit = DB::Aowow()->selectRow("SELECT * FROM ?_itemlimitcategory WHERE id = ?", $this->curTpl['itemLimitCategory']); $x .= '
'.($limit['isGem'] ? Lang::$item['uniqueEquipped'] : Lang::$item['unique']).Lang::$colon.Util::localizedString($limit, 'name').' ('.$limit['count'].')'; } // max duration - if ($this->curTpl['duration'] > 0) - $x .= "
".Lang::$game['duration'] . ' '. Util::formatTime($this->curTpl['duration'] * 1000) . ($this->curTpl['duration'] < 0 ? ' ('.Lang::$game['realTime'].')' : null); + if ($dur = $this->curTpl['duration']) + $x .= "
".Lang::$game['duration'] . ' '. Util::formatTime(abs($dur) * 1000) . ($dur < 0 ? ' ('.Lang::$game['realTime'].')' : null); // required holiday - if ($this->curTpl['HolidayId']) + if ($hId = $this->curTpl['holidayId']) { - $hDay = DB::Aowow()->selectRow("SELECT * FROM ?_holidays WHERE id = ?", $this->curTpl['HolidayId']); - $x .= '
'.sprintf(Lang::$game['requires'], ''.Util::localizedString($hDay, 'name').''); + $hDay = DB::Aowow()->selectRow("SELECT * FROM ?_holidays WHERE id = ?", $hId); + $x .= '
'.sprintf(Lang::$game['requires'], ''.Util::localizedString($hDay, 'name').''); } - // maxcount - if ($this->curTpl['maxcount'] > 1) - $x .= ' ('.$this->curTpl['maxcount'].')'; + // maxCount + if ($this->curTpl['maxCount'] > 1) + $x .= ' ('.$this->curTpl['maxCount'].')'; // item begins a quest - if ($this->curTpl['startquest']) - $x .= '
'.Lang::$item['startQuest'].''; + if ($this->curTpl['startQuest']) + $x .= '
'.Lang::$item['startQuest'].''; // containerType (slotCount) - if ($this->curTpl['ContainerSlots'] > 1) + if ($this->curTpl['slots'] > 1) { + $fam = log($this->curTpl['bagFamily'], 2) + 1; // word order differs <_< if (in_array(User::$localeId, [LOCALE_FR, LOCALE_ES, LOCALE_RU])) - $x .= '
'.sprintf(Lang::$item['bagSlotString'], Lang::$item['bagFamily'][$this->curTpl['BagFamily']], $this->curTpl['ContainerSlots']); + $x .= '
'.sprintf(Lang::$item['bagSlotString'], Lang::$item['bagFamily'][$fam], $this->curTpl['slots']); else - $x .= '
'.sprintf(Lang::$item['bagSlotString'], $this->curTpl['ContainerSlots'], Lang::$item['bagFamily'][$this->curTpl['BagFamily']]); + $x .= '
'.sprintf(Lang::$item['bagSlotString'], $this->curTpl['slots'], Lang::$item['bagFamily'][$fam]); } - if (in_array($this->curTpl['class'], [ITEM_CLASS_ARMOR, ITEM_CLASS_WEAPON, ITEM_CLASS_AMMUNITION])) + if (in_array($_class, [ITEM_CLASS_ARMOR, ITEM_CLASS_WEAPON, ITEM_CLASS_AMMUNITION])) { $x .= ''; // Class - $x .= ''; + $x .= ''; // Subclass - if ($this->curTpl['class'] == ITEM_CLASS_ARMOR && $this->curTpl['subclass'] > 0) - $x .= ''; - else if ($this->curTpl['class'] == ITEM_CLASS_WEAPON) - $x .= ''; - else if ($this->curTpl['class'] == ITEM_CLASS_AMMUNITION) - $x .= ''; + if ($_class == ITEM_CLASS_ARMOR && $_subClass > 0) + $x .= ''; + else if ($_class == ITEM_CLASS_WEAPON) + $x .= ''; + else if ($_class == ITEM_CLASS_AMMUNITION) + $x .= ''; $x .= '
'.Lang::$item['inventoryType'][$this->curTpl['InventoryType']].''.Lang::$item['inventoryType'][$this->curTpl['slot']].''.Lang::$item['armorSubClass'][$this->curTpl['subclass']].''.Lang::$item['weaponSubClass'][$this->curTpl['subclass']].''.Lang::$item['projectileSubClass'][$this->curTpl['subclass']].''.Lang::$item['armorSubClass'][$_subClass].''.Lang::$item['weaponSubClass'][$_subClass].''.Lang::$item['projectileSubClass'][$_subClass].'
'; } @@ -350,29 +369,29 @@ class ItemList extends BaseType $x .= '
'; // Weapon/Ammunition Stats - if (in_array($this->curTpl['class'], [ITEM_CLASS_WEAPON, ITEM_CLASS_AMMUNITION])) + if (in_array($_class, [ITEM_CLASS_WEAPON, ITEM_CLASS_AMMUNITION])) { $speed = $this->curTpl['delay'] / 1000; - $dmgmin1 = $this->curTpl['dmg_min1'] + $this->curTpl['dmg_min2']; - $dmgmax1 = $this->curTpl['dmg_max1'] + $this->curTpl['dmg_max2']; + $dmgmin1 = $this->curTpl['dmgMin1'] + $this->curTpl['dmgMin2']; + $dmgmax1 = $this->curTpl['dmgMax1'] + $this->curTpl['dmgMax2']; $dps = $speed ? ($dmgmin1 + $dmgmax1) / (2 * $speed) : 0; // regular weapon - if ($this->curTpl['class'] == ITEM_CLASS_WEAPON) + if ($_class == ITEM_CLASS_WEAPON) { $x .= ''; - $x .= ''; + $x .= ''; $x .= ''; $x .= '
'.sprintf($this->curTpl['dmg_type1'] ? Lang::$item['damageMagic'] : Lang::$item['damagePhys'], $this->curTpl['dmg_min1'].' - '.$this->curTpl['dmg_max1'], Lang::$game['sc'][$this->curTpl['dmg_type1']]).''.sprintf($this->curTpl['dmgType1'] ? Lang::$item['damageMagic'] : Lang::$item['damagePhys'], $this->curTpl['dmgMin1'].' - '.$this->curTpl['dmgMax1'], Lang::$game['sc'][$this->curTpl['dmgType1']]).''.Lang::$item['speed'].' '.number_format($speed, 2).'
'; // secondary damage is set - if ($this->curTpl['dmg_min2']) - $x .= '+'.sprintf($this->curTpl['dmg_type2'] ? Lang::$item['damageMagic'] : Lang::$item['damagePhys'], $this->curTpl['dmg_min2'].' - '.$this->curTpl['dmg_max2'], Lang::$game['sc'][$this->curTpl['dmg_type2']]).'
'; + if ($this->curTpl['dmgMin2']) + $x .= '+'.sprintf($this->curTpl['dmgType2'] ? Lang::$item['damageMagic'] : Lang::$item['damagePhys'], $this->curTpl['dmgMin2'].' - '.$this->curTpl['dmgMax2'], Lang::$game['sc'][$this->curTpl['dmgType2']]).'
'; $x .= '('.number_format($dps, 1).' '.Lang::$item['dps'].')
'; // display FeralAttackPower if set - if (in_array($this->curTpl['subclass'], [5, 6, 10]) && $dps > 54.8) + if (in_array($_subClass, [5, 6, 10]) && $dps > 54.8) $x .= '('.round(($dps - 54.8) * 14, 0).' '.Lang::$item['fap'].')
'; } // ammunition @@ -381,8 +400,8 @@ class ItemList extends BaseType } // Armor - if ($this->curTpl['class'] == ITEM_CLASS_ARMOR && $this->curTpl['ArmorDamageModifier'] > 0) - $x .= ''.sprintf(Lang::$item['armor'], $this->curTpl['armor'] + $this->curTpl['ArmorDamageModifier']).'
'; + if ($_class == ITEM_CLASS_ARMOR && ($this->curTpl['armor'] + $this->curTpl['armorDamageModifier']) > 0) + $x .= ''.sprintf(Lang::$item['armor'], $this->curTpl['armor'] + $this->curTpl['armorDamageModifier']).'
'; else if ($this->curTpl['armor']) $x .= ''.sprintf(Lang::$item['armor'], $this->curTpl['armor']).'
'; @@ -391,14 +410,14 @@ class ItemList extends BaseType $x .= ''.sprintf(Lang::$item['block'], $this->curTpl['block']).'
'; // Item is a gem (don't mix with sockets) - if ($this->curTpl['GemProperties']) + if ($geId = $this->curTpl['gemEnchantmentId']) { - $gemText = DB::Aowow()->selectRow('SELECT e.* FROM ?_itemenchantment e, ?_gemproperties p WHERE (p.Id = ?d and e.Id = p.itemenchantmentID)', $this->curTpl['GemProperties']); + $gemText = DB::Aowow()->selectRow('SELECT * FROM ?_itemEnchantment WHERE id = ?d', $geId); $x .= Util::localizedString($gemText, 'text').'
'; } // Random Enchantment - if random enchantment is set, prepend stats from it - if (($this->curTpl['RandomProperty'] || $this->curTpl['RandomSuffix']) && !isset($enhance['rand'])) + if (($this->curTpl['randomProperty'] || $this->curTpl['randomSuffix']) && !isset($enhance['rand'])) $x .= ''.Lang::$item['randEnchant'].'
'; else if (isset($enhance['rand'])) $x .= $randEnchant['stats']; @@ -406,15 +425,15 @@ class ItemList extends BaseType // itemMods (display stats and save ratings for later use) for ($j = 1; $j <= 10; $j++) { - $type = $this->curTpl['stat_type'.$j]; - $qty = $this->curTpl['stat_value'.$j]; + $type = $this->curTpl['statType'.$j]; + $qty = $this->curTpl['statValue'.$j]; if (!$qty || $type <= 0) continue; // base stat if ($type >= ITEM_MOD_AGILITY && $type <= ITEM_MOD_STAMINA) - $x .= '+'.$qty.' '.Lang::$item['statType'][$type].'
'; + $x .= ''.($qty > 0 ? '+' : '-').abs($qty).' '.Lang::$item['statType'][$type].'
'; else // rating with % for reqLevel $green[] = $this->parseRating($type, $qty, $interactive); } @@ -434,32 +453,29 @@ class ItemList extends BaseType $x .= ''; // Sockets w/ Gems - if (isset($enhance['gems'])) + if (!empty($enhance['gems'])) { $gems = DB::Aowow()->select(' SELECT - it.entry AS ARRAY_KEY, - ia.icon, + i.id AS ARRAY_KEY, + i.iconString, ae.*, - colorMask + i.gemColorMask AS colorMask FROM - item_template it + ?_items i JOIN - ?_item_template_addon ia ON ia.id = it.entry - JOIN - ?_gemproperties ag ON ag.Id = it.GemProperties - JOIN - ?_itemenchantment ae ON ae.Id = ag.itemEnchantmentID + ?_itemenchantment ae ON ae.id = i.gemEnchantmentId WHERE - it.entry IN (?a)', + i.id IN (?a)', $enhance['gems'] ); + echo "ho"; } else $enhance['gems'] = []; // zero fill empty sockets - $sockCount = $this->curTpl['socketColor_1'] + $this->curTpl['socketColor_2'] + $this->curTpl['socketColor_3'] + (isset($enhance['sock']) ? 1 : 0); + $sockCount = $this->curTpl['socketColor1'] + $this->curTpl['socketColor2'] + $this->curTpl['socketColor3'] + (isset($enhance['sock']) ? 1 : 0); while ($sockCount > count($enhance['gems'])) $enhance['gems'][] = 0; @@ -469,17 +485,17 @@ class ItemList extends BaseType // fill native sockets for ($j = 1; $j <= 3; $j++) { - if (!$this->curTpl['socketColor_'.$j]) + if (!$this->curTpl['socketColor'.$j]) continue; for ($i = 0; $i < 4; $i++) - if (($this->curTpl['socketColor_'.$j] & (1 << $i))) + if (($this->curTpl['socketColor'.$j] & (1 << $i))) $colorId = $i; $pop = array_pop($enhance['gems']); $col = $pop ? 1 : 0; $hasMatch &= $pop ? (($gems[$pop]['colorMask'] & (1 << $colorId)) ? 1 : 0) : 0; - $icon = $pop ? sprintf(Util::$bgImagePath['tiny'], STATIC_URL, strtolower($gems[$pop]['icon'])) : null; + $icon = $pop ? sprintf(Util::$bgImagePath['tiny'], STATIC_URL, strtolower($gems[$pop]['iconString'])) : null; $text = $pop ? Util::localizedString($gems[$pop], 'text') : Lang::$item['socket'][$colorId]; if ($interactive) @@ -493,7 +509,7 @@ class ItemList extends BaseType { $pop = array_pop($enhance['gems']); $col = $pop ? 1 : 0; - $icon = $pop ? sprintf(Util::$bgImagePath['tiny'], STATIC_URL, strtolower($gems[$pop]['icon'])) : null; + $icon = $pop ? sprintf(Util::$bgImagePath['tiny'], STATIC_URL, strtolower($gems[$pop]['iconString'])) : null; $text = $pop ? Util::localizedString($gems[$pop], 'text') : Lang::$item['socket'][-1]; if ($interactive) @@ -511,54 +527,54 @@ class ItemList extends BaseType } // durability - if ($this->curTpl['MaxDurability']) - $x .= Lang::$item['durability'].' '.$this->curTpl['MaxDurability'].' / '.$this->curTpl['MaxDurability'].'
'; + if ($dur = $this->curTpl['durability']) + $x .= Lang::$item['durability'].' '.$dur.' / '.$dur.'
'; // required classes - if ($classes = Lang::getClassString($this->curTpl['AllowableClass'])) + if ($classes = Lang::getClassString($this->curTpl['requiredClass'])) $x .= Lang::$game['classes'].Lang::$colon.$classes.'
'; // required races - if ($races = Lang::getRaceString($this->curTpl['AllowableRace'])) - if ($races['side'] != SIDE_BOTH) + if ($races = Lang::getRaceString($this->curTpl['requiredRace'])) + if ($races['name'] != Lang::$game['ra'][0]) // not "both" $x .= Lang::$game['races'].Lang::$colon.$races['name'].'
'; // required honorRank (not used anymore) - if ($this->curTpl['requiredhonorrank']) - $x .= sprintf(Lang::$game['requires'], Lang::$game['pvpRank'][$this->curTpl['requiredhonorrank']]).'
'; + if ($rhr = $this->curTpl['requiredHonorRank']) + $x .= sprintf(Lang::$game['requires'], Lang::$game['pvpRank'][$rhr]).'
'; // required CityRank..? // what the f.. // required level - if (($this->curTpl['Flags'] & ITEM_FLAG_ACCOUNTBOUND) && $this->curTpl['Quality'] == ITEM_QUALITY_HEIRLOOM) + if (($_flags & ITEM_FLAG_ACCOUNTBOUND) && $_quality == ITEM_QUALITY_HEIRLOOM) $x .= sprintf(Lang::$game['reqLevelHlm'], ' 1'.Lang::$game['valueDelim'].MAX_LEVEL.' ('.($interactive ? printf(Util::$changeLevelString, MAX_LEVEL) : ''.MAX_LEVEL).')').'
'; - else if ($this->curTpl['RequiredLevel'] > 1) - $x .= sprintf(Lang::$game['reqLevel'], $this->curTpl['RequiredLevel']).'
'; + else if ($_reqLvl > 1) + $x .= sprintf(Lang::$game['reqLevel'], $_reqLvl).'
'; // item level - $x .= Lang::$item['itemLevel'].' '.$this->curTpl['ItemLevel']; + $x .= Lang::$item['itemLevel'].' '.$this->curTpl['itemLevel']; // required skill - if ($this->curTpl['RequiredSkill']) + if ($reqSkill = $this->curTpl['requiredSkill']) { - $_ = ''.SkillList::getName($this->curTpl['RequiredSkill']).''; - if ($this->curTpl['RequiredSkillRank']) - $_ .= ' ('.$this->curTpl['RequiredSkillRank'].')'; + $_ = ''.SkillList::getName($reqSkill).''; + if ($this->curTpl['requiredSkillRank'] > 0) + $_ .= ' ('.$this->curTpl['requiredSkillRank'].')'; $x .= '
'.sprintf(Lang::$game['requires'], $_); } // required spell - if ($this->curTpl['requiredspell']) - $x .= '
'.Lang::$game['requires2'].' '.SpellList::getName($this->curTpl['requiredspell']).''; + if ($reqSpell = $this->curTpl['requiredSpell']) + $x .= '
'.Lang::$game['requires2'].' '.SpellList::getName($reqSpell).''; // required reputation w/ faction - if ($this->curTpl['RequiredReputationFaction']) - $x .= '
'.sprintf(Lang::$game['requires'], 'curTpl['RequiredReputationFaction'].'">'.Faction::getName($this->curTpl['RequiredReputationFaction']).' - '.Lang::$game['rep'][$this->curTpl['RequiredReputationRank']]); + if ($reqFac = $this->curTpl['requiredFaction']) + $x .= '
'.sprintf(Lang::$game['requires'], ''.FactionList::getName($reqFac).' - '.Lang::$game['rep'][$this->curTpl['requiredFactionRank']]); // locked - if ($lId = $this->curTpl['lockid']) + if ($lId = $this->curTpl['lockId']) if ($locks = Lang::getLocks($lId)) $x .= '
'.Lang::$item['locked'].'
'.implode('
', $locks).'
'; @@ -569,17 +585,32 @@ class ItemList extends BaseType $x .= '
'; // spells on item - $itemSpellsAndTrigger = []; - for ($j = 1; $j <= 5; $j++) - if ($this->curTpl['spellid_'.$j] > 0) - $itemSpellsAndTrigger[$this->curTpl['spellid_'.$j]] = $this->curTpl['spelltrigger_'.$j]; - - if ($itemSpellsAndTrigger) + if (!$this->canTeachSpell()) { - $itemSpells = new SpellList(array(['s.id', array_keys($itemSpellsAndTrigger)])); - foreach ($itemSpells->iterate() as $__) - if ($parsed = $itemSpells->parseText('description', $this->curTpl['RequiredLevel'])[0]) - $green[] = Lang::$item['trigger'][$itemSpellsAndTrigger[$itemSpells->id]] . ($interactive ? ''.$parsed.'' : $parsed); + $itemSpellsAndTrigger = []; + for ($j = 1; $j <= 5; $j++) + { + if ($this->curTpl['spellId'.$j] > 0) + { + $cd = $this->curTpl['spellCooldown'.$j]; + if ($cd < $this->curTpl['spellCategoryCooldown'.$j]) + $cd = $this->curTpl['spellCategoryCooldown'.$j]; + + $cd = $cd < 5000 ? null : ' ('.sprintf(Lang::$game['cooldown'], Util::formatTime($cd, true)).')'; + + $itemSpellsAndTrigger[$this->curTpl['spellId'.$j]] = [$this->curTpl['spellTrigger'.$j], $cd]; + } + } + + if ($itemSpellsAndTrigger) + { + $cooldown = ''; + + $itemSpells = new SpellList(array(['s.id', array_keys($itemSpellsAndTrigger)])); + foreach ($itemSpells->iterate() as $__) + if ($parsed = $itemSpells->parseText('description', $_reqLvl > 1 ? $_reqLvl : MAX_LEVEL)[0]) + $green[] = Lang::$item['trigger'][$itemSpellsAndTrigger[$itemSpells->id][0]] . ($interactive ? ''.$parsed.'' : $parsed) . $itemSpellsAndTrigger[$itemSpells->id][1]; + } } // lower table (ratings, spells, ect) @@ -645,7 +676,7 @@ class ItemList extends BaseType foreach ($boni->iterate() as $__) { $itemset['spells'][] = array( - 'tooltip' => $boni->parseText('description', $this->curTpl['RequiredLevel'])[0], + 'tooltip' => $boni->parseText('description', $_reqLvl > 1 ? $_reqLvl : MAX_LEVEL)[0], 'entry' => $itemset['spell'.$setSpellsAndIdx[$boni->id]], 'bonus' => $itemset['bonus'.$setSpellsAndIdx[$boni->id]] ); @@ -672,35 +703,45 @@ class ItemList extends BaseType $xSet .= ''; } - // recipe handling (some stray Techniques have subclass == 0), place at bottom of tooltipp - if ($this->curTpl['class'] == ITEM_CLASS_RECIPE && ($this->curTpl['subclass'] || $this->curTpl['BagFamily'] == 16)) + // recipes, vanity pets, mounts + if ($this->canTeachSpell()) { - $spell = $this->curTpl['spellid_1'] == 483 ? (int)$this->curTpl['spellid_2'] : (int)$this->curTpl['spellid_1']; - $craftSpell = new SpellList(array(['s.id', $spell])); - $craftItem = new ItemList(array(['i.entry', (int)$craftSpell->curTpl['effect1CreateItemId']])); - - if ($desc = $this->getField('description', true)) - $x .= ''.Lang::$item['trigger'][0].' '.$desc.'
'; - - if ($itemTT = $craftItem->renderTooltip(null, $interactive)) - $xCraft = '

'.$itemTT.'
'; - - $reagentItems = []; - for ($i = 1; $i <= 8; $i++) - if ($rId = $craftSpell->getField('reagent'.$i)) - $reagentItems[$rId] = $craftSpell->getField('reagentCount'.$i); - - if (isset($xCraft) && $reagentItems) + $craftSpell = new SpellList(array(['s.id', intVal($this->curTpl['spellId2'])])); + if (!$craftSpell->error) { - $reagents = new ItemList(array(['i.entry', array_keys($reagentItems)])); - $reqReag = []; + if ($desc = $this->getField('description', true)) + { + $xCraft = ''; + $x .= ''.Lang::$item['trigger'][0].' '.$desc.'
'; + } - foreach ($reagents->iterate() as $__) - $reqReag[] = ''.$reagents->getField('name', true).' ('.$reagentItems[$reagents->id].')'; + // recipe handling (some stray Techniques have subclass == 0), place at bottom of tooltipp + if ($_class == ITEM_CLASS_RECIPE || $this->curTpl['bagFamily'] == 16) + { + $craftItem = new ItemList(array(['i.id', (int)$craftSpell->curTpl['effect1CreateItemId']])); + if (!$craftItem->error) + { + if ($itemTT = $craftItem->renderTooltip(null, $interactive)) + $xCraft .= '

'.$itemTT.'
'; - $xCraft .= '
'.Lang::$game['requires2'].' '.implode(', ', $reqReag).''; + $reagentItems = []; + for ($i = 1; $i <= 8; $i++) + if ($rId = $craftSpell->getField('reagent'.$i)) + $reagentItems[$rId] = $craftSpell->getField('reagentCount'.$i); + + if (isset($xCraft) && $reagentItems) + { + $reagents = new ItemList(array(['i.id', array_keys($reagentItems)])); + $reqReag = []; + + foreach ($reagents->iterate() as $__) + $reqReag[] = ''.$reagents->getField('name', true).' ('.$reagentItems[$reagents->id].')'; + + $xCraft .= '
'.Lang::$game['requires2'].' '.implode(', ', $reqReag).''; + } + } + } } - } // misc (no idea, how to organize the
better) @@ -711,19 +752,19 @@ class ItemList extends BaseType $xMisc[] = $xSet; // funny, yellow text at the bottom, omit if we have a recipe - if ($this->curTpl['description'] && !isset($xCraft)) + if ($this->curTpl['description_loc0'] && !isset($xCraft)) $xMisc[] = '"'.$this->getField('description', true).'"'; // readable - if ($this->curTpl['PageText']) + if ($this->curTpl['pageTextId']) $xMisc[] = ''.Lang::$item['readClick'].''; // charges (i guess checking first spell is enough (single charges not shown)) - if ($this->curTpl['spellcharges_1'] > 1) - $xMisc[] = ''.$this->curTpl['spellcharges_1'].' '.Lang::$item['charges'].''; + if ($this->curTpl['spellCharges1'] > 1) + $xMisc[] = ''.$this->curTpl['spellCharges1'].' '.Lang::$item['charges'].''; - if ($this->curTpl['SellPrice']) - $xMisc[] = ''.Lang::$item['sellPrice'].Lang::$colon.Util::formatMoney($this->curTpl['SellPrice']).''; + if ($sp = $this->curTpl['sellPrice']) + $xMisc[] = ''.Lang::$item['sellPrice'].Lang::$colon.Util::formatMoney($sp).''; // list required reagents if (isset($xCraft)) @@ -743,8 +784,8 @@ class ItemList extends BaseType 1, // scaleMinLevel $this->ssd[$this->id]['maxLevel'], // scaleMaxLevel $this->ssd[$this->id]['maxLevel'], // scaleCurLevel - $this->curTpl['ScalingStatDistribution'], // scaleDist - $this->curTpl['ScalingStatValue'], // scaleFlags + $this->curTpl['scalingStatDistribution'], // scaleDist + $this->curTpl['scalingStatValue'], // scaleFlags ); $x .= ''; } @@ -759,11 +800,11 @@ class ItemList extends BaseType // from Trinity public function generateEnchSuffixFactor() { - $rpp = DB::Aowow()->selectRow('SELECT * FROM ?_itemRandomPropPoints WHERE Id = ?', $this->curTpl['ItemLevel']); + $rpp = DB::Aowow()->selectRow('SELECT * FROM ?_itemRandomPropPoints WHERE Id = ?', $this->curTpl['itemLevel']); if (!$rpp) return 0; - switch ($this->curTpl['InventoryType']) + switch ($this->curTpl['slot']) { // Items of that type don`t have points case INVTYPE_NON_EQUIP: @@ -812,7 +853,7 @@ class ItemList extends BaseType } // Select rare/epic modifier - switch ($this->curTpl['Quality']) + switch ($this->curTpl['quality']) { case ITEM_QUALITY_UNCOMMON: return $rpp['uncommon'.$suffixFactor]; @@ -832,10 +873,11 @@ class ItemList extends BaseType public function extendJsonStats() { // convert ItemMods + $this->itemMods[$this->id] = []; for ($h = 1; $h <= 10; $h++) { - $mod = $this->curTpl['stat_type'.$h]; - $val = $this->curTpl['stat_value'.$h]; + $mod = $this->curTpl['statType'.$h]; + $val = $this->curTpl['statValue'.$h]; if (!$mod ||!$val) continue; @@ -850,13 +892,13 @@ class ItemList extends BaseType for ($h = 1; $h <= 5; $h++) { // only onEquip - if ($this->curTpl['spelltrigger_'.$h] != 1) + if ($this->curTpl['spellTrigger'.$h] != 1) continue; - if ($this->curTpl['spellid_'.$h] <= 0) + if ($this->curTpl['spellId'.$h] <= 0) continue; - $equipSpells[] = $this->curTpl['spellid_'.$h]; + $equipSpells[] = $this->curTpl['spellId'.$h]; } if ($equipSpells) @@ -874,6 +916,15 @@ class ItemList extends BaseType if ($enh = DB::Aowow()->selectRow('SELECT * FROM ?_itemenchantment WHERE Id = ?;', $this->json[$this->id]['socketbonus'])) $this->json[$this->id]['socketbonusstat'] = Util::parseItemEnchantment($enh); + // Item is a gem (don't mix with sockets) + if ($geId = $this->curTpl['gemEnchantmentId']) + { + $gemStats = Util::parseItemEnchantment(DB::Aowow()->selectRow('SELECT * FROM ?_itemEnchantment WHERE id = ?d', $geId)); + + foreach ($gemStats as $mod => $qty) + @$this->json[$this->id][$mod] += $qty; + } + // gather random Enchantments // todo (high): extremly high sql-load if (@$this->json[$this->id]['commondrop'] && isset($this->subItems[$this->id])) @@ -921,11 +972,65 @@ class ItemList extends BaseType unset($this->json[$this->id][$k]); } + public function createConditionsForWeights(&$data) + { + if (count($data['wt']) != count($data['wtv'])) + return null; + + $select = $cnd = []; + $wtSum = 0; + + foreach ($data['wt'] as $k => $v) + { + @$str = Util::$itemFilter[$v]; + $qty = intVal($data['wtv'][$k]); + + if ($str && $qty) + { + if ($str == 'rgdspeed') // dont need no duplicate column + $str = 'speed'; + + if ($str == 'mledps') // todo (med): unify rngdps and mledps to dps + $str = 'dps'; + + $select[] = '(`is`.`'.$str.'` * '.$qty.')'; + $cnd[] = ['is.'.$str, 0, '>']; + $wtSum += $qty; + } + else // well look at that.. erronous indizes or zero-weights + { + unset($data['wt'][$k]); + unset($data['wtv'][$k]); + } + } + + if (count($cnd) > 1) + array_unshift($cnd, 'OR'); + else if (count($cnd) == 1) + $cnd = $cnd[0]; + + $this->queryOpts['is']['s'] = $select ? ', ('.implode(' + ', $select).') / '.$wtSum.' AS score' : null; + return $cnd; + } + + private function canTeachSpell() + { + // 483: learn recipe; 55884: learn mount/pet + if (!in_array($this->curTpl['spellId1'], [483, 55884])) + return false; + + if (!$this->curTpl['spellId2']) + return false; + + return true; + } + private function parseRating($type, $value, $interactive = false) { // clamp level range $ssdLvl = isset($this->ssd[$this->id]) ? $this->ssd[$this->id]['maxLevel'] : 1; - $level = min(max($this->curTpl['RequiredLevel'], $ssdLvl), MAX_LEVEL); + $reqLvl = $this->curTpl['requiredLevel'] > 1 ? $this->curTpl['requiredLevel'] : MAX_LEVEL; + $level = min(max($reqLvl, $ssdLvl), MAX_LEVEL); if (!Lang::$item['statType'][$type]) // unknown rating return sprintf(Lang::$item['statType'][count(Lang::$item['statType']) - 1], $type, $value); @@ -944,7 +1049,7 @@ class ItemList extends BaseType private function getSSDMod($type) { - $mask = $this->curTpl['ScalingStatValue']; + $mask = $this->curTpl['scalingStatValue']; switch ($type) { @@ -965,20 +1070,20 @@ class ItemList extends BaseType private function initScalingStats() { - $this->ssd[$this->id] = DB::Aowow()->selectRow("SELECT * FROM ?_scalingstatdistribution WHERE id = ?", $this->curTpl['ScalingStatDistribution']); + $this->ssd[$this->id] = DB::Aowow()->selectRow("SELECT * FROM ?_scalingstatdistribution WHERE id = ?", $this->curTpl['scalingStatDistribution']); // stats and ratings for ($i = 1; $i <= 10; $i++) { if ($this->ssd[$this->id]['statMod'.$i] <= 0) { - $this->templates[$this->id]['stat_type'.$i] = 0; - $this->templates[$this->id]['stat_value'.$i] = 0; + $this->templates[$this->id]['statType'.$i] = 0; + $this->templates[$this->id]['statValue'.$i] = 0; } else { - $this->templates[$this->id]['stat_type'.$i] = $this->ssd[$this->id]['statMod'.$i]; - $this->templates[$this->id]['stat_value'.$i] = intVal(($this->getSSDMod('stats') * $this->ssd[$this->id]['modifier'.$i]) / 10000); + $this->templates[$this->id]['statType'.$i] = $this->ssd[$this->id]['statMod'.$i]; + $this->templates[$this->id]['statValue'.$i] = intVal(($this->getSSDMod('stats') * $this->ssd[$this->id]['modifier'.$i]) / 10000); } } @@ -990,24 +1095,24 @@ class ItemList extends BaseType if ($extraDPS = $this->getSSDMod('dps')) // dmg_x2 not used for heirlooms { $average = $extraDPS * $this->curTpl['delay'] / 1000; - $this->templates[$this->id]['dmg_min1'] = number_format(0.7 * $average); - $this->templates[$this->id]['dmg_max1'] = number_format(1.3 * $average); + $this->templates[$this->id]['dmgMin1'] = number_format(0.7 * $average); + $this->templates[$this->id]['dmgMax1'] = number_format(1.3 * $average); } // apply Spell Power from ScalingStatValue if set if ($spellBonus = $this->getSSDMod('spell')) { - $this->templates[$this->id]['stat_type10'] = ITEM_MOD_SPELL_POWER; - $this->templates[$this->id]['stat_value10'] = $spellBonus; + $this->templates[$this->id]['statType10'] = ITEM_MOD_SPELL_POWER; + $this->templates[$this->id]['statValue10'] = $spellBonus; } } private function initSubItems() { - $randId = $this->curTpl['RandomProperty'] > 0 ? $this->curTpl['RandomProperty'] : $this->curTpl['RandomSuffix']; + $randId = $this->curTpl['randomProperty'] > 0 ? $this->curTpl['randomProperty'] : $this->curTpl['randomSuffix']; if ($randomIds = DB::Aowow()->selectCol('SELECT ench FROM item_enchantment_template WHERE entry = ?d', $randId)) { - if ($this->curTpl['RandomSuffix'] > 0) + if ($this->curTpl['randomSuffix'] > 0) array_walk($randomIds, function($val, $key) use(&$randomIds) { $randomIds[$key] = -$val; }); @@ -1028,44 +1133,44 @@ class ItemList extends BaseType $json = array( 'id' => $this->id, 'name' => $this->getField('name', true), - 'quality' => ITEM_QUALITY_HEIRLOOM - $this->curTpl['Quality'], - 'icon' => $this->curTpl['icon'], + 'quality' => ITEM_QUALITY_HEIRLOOM - $this->curTpl['quality'], + 'icon' => $this->curTpl['iconString'], 'classs' => $this->curTpl['class'], - 'subclass' => $this->curTpl['subclass'], - // 'subsubclass' => $this->curTpl['subsubclass'], - 'heroic' => (string)($this->curTpl['Flags'] & 0x8), - 'side' => Util::sideByRaceMask($this->curTpl['AllowableRace']), // check for FlagsExtra? 0:both; 1: Horde; 2:Alliance - 'slot' => $this->curTpl['InventoryType'] == 26 ? 15 : $this->curTpl['InventoryType'] == 20 ? 5 : $this->curTpl['InventoryType'], - 'slotbak' => $this->curTpl['InventoryType'], - 'level' => $this->curTpl['ItemLevel'], - 'reqlevel' => $this->curTpl['RequiredLevel'], - 'displayid' => $this->curTpl['displayid'], - 'commondrop' => ($this->curTpl['RandomProperty'] > 0 || $this->curTpl['RandomSuffix'] > 0) ? 'true' : null, // string required :( - 'holres' => $this->curTpl['holy_res'], - 'firres' => $this->curTpl['fire_res'], - 'natres' => $this->curTpl['nature_res'], - 'frores' => $this->curTpl['frost_res'], - 'shares' => $this->curTpl['shadow_res'], - 'arcres' => $this->curTpl['arcane_res'], - 'armorbonus' => $this->curTpl['ArmorDamageModifier'], + 'subclass' => $this->curTpl['subClass'], + 'subsubclass' => $this->curTpl['subSubClass'], + 'heroic' => (string)($this->curTpl['flags'] & 0x8), + 'side' => Util::sideByRaceMask($this->curTpl['requiredRace']), // check for FlagsExtra? 0:both; 1: Horde; 2:Alliance + 'slot' => $this->curTpl['slot'], + 'slotbak' => $this->curTpl['slotBak'], + 'level' => $this->curTpl['itemLevel'], + 'reqlevel' => $this->curTpl['requiredLevel'], + 'displayid' => $this->curTpl['displayId'], + 'commondrop' => ($this->curTpl['randomProperty'] > 0 || $this->curTpl['randomSuffix'] > 0) ? 'true' : null, // string required :( + 'holres' => $this->curTpl['resHoly'], + 'firres' => $this->curTpl['resFire'], + 'natres' => $this->curTpl['resNature'], + 'frores' => $this->curTpl['resFrost'], + 'shares' => $this->curTpl['resShadow'], + 'arcres' => $this->curTpl['resArcane'], + 'armorbonus' => $this->curTpl['armorDamageModifier'], 'armor' => $this->curTpl['armor'], - 'dura' => $this->curTpl['MaxDurability'], + 'dura' => $this->curTpl['durability'], 'itemset' => $this->curTpl['itemset'], - 'socket1' => $this->curTpl['socketColor_1'], - 'socket2' => $this->curTpl['socketColor_2'], - 'socket3' => $this->curTpl['socketColor_3'], - 'nsockets' => ($this->curTpl['socketColor_1'] > 0 ? 1 : 0) + ($this->curTpl['socketColor_2'] > 0 ? 1 : 0) + ($this->curTpl['socketColor_3'] > 0 ? 1 : 0), + 'socket1' => $this->curTpl['socketColor1'], + 'socket2' => $this->curTpl['socketColor2'], + 'socket3' => $this->curTpl['socketColor3'], + 'nsockets' => ($this->curTpl['socketColor1'] > 0 ? 1 : 0) + ($this->curTpl['socketColor2'] > 0 ? 1 : 0) + ($this->curTpl['socketColor3'] > 0 ? 1 : 0), 'socketbonus' => $this->curTpl['socketBonus'], - 'scadist' => $this->curTpl['ScalingStatDistribution'], - 'scaflags' => $this->curTpl['ScalingStatValue'] + 'scadist' => $this->curTpl['scalingStatDistribution'], + 'scaflags' => $this->curTpl['scalingStatValue'] ); if ($this->curTpl['class'] == ITEM_CLASS_WEAPON || $this->curTpl['class'] == ITEM_CLASS_AMMUNITION) { - $json['dmgtype1'] = $this->curTpl['dmg_type1']; - $json['dmgmin1'] = $this->curTpl['dmg_min1'] + $this->curTpl['dmg_min2']; - $json['dmgmax1'] = $this->curTpl['dmg_max1'] + $this->curTpl['dmg_max2']; + $json['dmgtype1'] = $this->curTpl['dmgType1']; + $json['dmgmin1'] = $this->curTpl['dmgMin1'] + $this->curTpl['dmgMin2']; + $json['dmgmax1'] = $this->curTpl['dmgMax1'] + $this->curTpl['dmgMax2']; $json['dps'] = !$this->curTpl['delay'] ? 0 : number_format(($json['dmgmin1'] + $json['dmgmax1']) / (2 * $this->curTpl['delay'] / 1000), 1); $json['speed'] = number_format($this->curTpl['delay'] / 1000, 2); @@ -1088,8 +1193,8 @@ class ItemList extends BaseType $json['feratkpwr'] = max(0, round((($json['dmgmin1'] + $json['dmgmax1']) / (2 * $this->curTpl['delay'] / 1000) - 54.8) * 14, 0)); } - if ($this->curTpl['ArmorDamageModifier'] > 0) - $json['armor'] += $this->curTpl['ArmorDamageModifier']; + if ($this->curTpl['armorDamageModifier'] > 0) + $json['armor'] += $this->curTpl['armorDamageModifier']; // clear zero-values afterwards foreach ($json as $k => $v) @@ -1103,53 +1208,586 @@ class ItemList extends BaseType } -/* -teaches - $teaches = array(); - for($j=1;$j<=4;$j++) - if($Row['spellid_'.$j]==483) - $teaches[] = spellinfo($Row['spellid_'.($j+1)]); - if($teaches) +// missing each and everything +class ItemListFilter extends Filter +{ + // usable-by - limit weapon/armor selection per CharClass - itemClass => available itemsubclasses + private $ubFilter = []; + protected $enums = array( + 99 => array( // profession + null, 171, 164, 185, 333, 202, 129, 755, 165, 186, 197, true, false, 356, 182, 773 + ), + 66 => array( // profession specialization + 1 => -1, + 2 => [ 9788, 9787, 17041, 17040, 17039 ], + 3 => -1, + 4 => -1, + 5 => [20219, 20222 ], + 6 => -1, + 7 => -1, + 8 => [10656, 10658, 10660 ], + 9 => -1, + 10 => [26798, 26801, 26797 ], + 11 => [ 9788, 9787, 17041, 17040, 17039, 20219, 20222, 10656, 10658, 10660, 26798, 26801, 26797], // i know, i know .. lazy as fuck + 12 => false, + 13 => -1, + 14 => -1, + 15 => -1 + ), + 152 => array( // class-specific + null, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, 11, true, false + ), + 153 => array( // race-specific + null, 1, 2, 3, 4, 5, 6, 7, 8, null, 10, 11, true, false + ) + ); + // cr => [type, field, misc, extraCol] + protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet + 146 => [FILTER_CR_FLAG, 'flags', ITEM_FLAG_HEROIC ], // heroic + 9 => [FILTER_CR_FLAG, 'flags', ITEM_FLAG_CONJURED ], // conjureditem + 83 => [FILTER_CR_FLAG, 'flags', ITEM_FLAG_UNIQUEEQUIPPED ], // uniqueequipped + 151 => [FILTER_CR_NUMERIC, 'id', null, true], // id + 100 => [FILTER_CR_NUMERIC, 'is.nsockets' ], // nsockets + 111 => [FILTER_CR_NUMERIC, 'requiredSkillRank', null, true], // reqskillrank + 99 => [FILTER_CR_ENUM, 'requiredSkill' ], // requiresprof + 66 => [FILTER_CR_ENUM, 'requiredSpell' ], // requiresprofspec + 17 => [FILTER_CR_ENUM, 'requiredFaction' ], // requiresrepwith + 169 => [FILTER_CR_ENUM, 'holidayId' ], // requiresevent + 21 => [FILTER_CR_NUMERIC, 'is.agi', null, true], // agi + 23 => [FILTER_CR_NUMERIC, 'is.int', null, true], // int + 22 => [FILTER_CR_NUMERIC, 'is.sta', null, true], // sta + 24 => [FILTER_CR_NUMERIC, 'is.spi', null, true], // spi + 20 => [FILTER_CR_NUMERIC, 'is.str', null, true], // str + 115 => [FILTER_CR_NUMERIC, 'is.health', null, true], // health + 116 => [FILTER_CR_NUMERIC, 'is.mana', null, true], // mana + 60 => [FILTER_CR_NUMERIC, 'is.healthrgn', null, true], // healthrgn + 61 => [FILTER_CR_NUMERIC, 'is.manargn', null, true], // manargn + 41 => [FILTER_CR_NUMERIC, 'is.armor' , null, true], // armor + 44 => [FILTER_CR_NUMERIC, 'is.blockrtng', null, true], // blockrtng + 43 => [FILTER_CR_NUMERIC, 'is.block', null, true], // block + 42 => [FILTER_CR_NUMERIC, 'is.defrtng', null, true], // defrtng + 45 => [FILTER_CR_NUMERIC, 'is.dodgertng', null, true], // dodgertng + 46 => [FILTER_CR_NUMERIC, 'is.parryrtng', null, true], // parryrtng + 79 => [FILTER_CR_NUMERIC, 'is.resirtng', null, true], // resirtng + 77 => [FILTER_CR_NUMERIC, 'is.atkpwr', null, true], // atkpwr + 97 => [FILTER_CR_NUMERIC, 'is.feratkpwr', null, true], // feratkpwr + 114 => [FILTER_CR_NUMERIC, 'is.armorpenrtng', null, true], // armorpenrtng + 96 => [FILTER_CR_NUMERIC, 'is.critstrkrtng', null, true], // critstrkrtng + 117 => [FILTER_CR_NUMERIC, 'is.exprtng', null, true], // exprtng + 103 => [FILTER_CR_NUMERIC, 'is.hastertng', null, true], // hastertng + 119 => [FILTER_CR_NUMERIC, 'is.hitrtng', null, true], // hitrtng + 94 => [FILTER_CR_NUMERIC, 'is.splpen', null, true], // splpen + 123 => [FILTER_CR_NUMERIC, 'is.splpwr', null, true], // splpwr + 52 => [FILTER_CR_NUMERIC, 'is.arcsplpwr', null, true], // arcsplpwr + 53 => [FILTER_CR_NUMERIC, 'is.firsplpwr', null, true], // firsplpwr + 54 => [FILTER_CR_NUMERIC, 'is.frosplpwr', null, true], // frosplpwr + 55 => [FILTER_CR_NUMERIC, 'is.holsplpwr', null, true], // holsplpwr + 56 => [FILTER_CR_NUMERIC, 'is.natsplpwr', null, true], // natsplpwr + 57 => [FILTER_CR_NUMERIC, 'is.shasplpwr', null, true], // shasplpwr + 32 => [FILTER_CR_NUMERIC, 'is.dps', true, true], // dps + 33 => [FILTER_CR_NUMERIC, 'is.dmgmin1', null, true], // dmgmin1 + 34 => [FILTER_CR_NUMERIC, 'is.dmgmax1', null, true], // dmgmax1 + 36 => [FILTER_CR_NUMERIC, 'is.speed', true, true], // speed + 134 => [FILTER_CR_NUMERIC, 'is.mledps', true, true], // mledps + 135 => [FILTER_CR_NUMERIC, 'is.mledmgmin', null, true], // mledmgmin + 136 => [FILTER_CR_NUMERIC, 'is.mledmgmax', null, true], // mledmgmax + 137 => [FILTER_CR_NUMERIC, 'is.mlespeed', true, true], // mlespeed + 138 => [FILTER_CR_NUMERIC, 'is.rgddps', true, true], // rgddps + 139 => [FILTER_CR_NUMERIC, 'is.rgddmgmin', null, true], // rgddmgmin + 140 => [FILTER_CR_NUMERIC, 'is.rgddmgmax', null, true], // rgddmgmax + 141 => [FILTER_CR_NUMERIC, 'is.rgdspeed', true, true], // rgdspeed + 25 => [FILTER_CR_NUMERIC, 'is.arcres', null, true], // arcres + 26 => [FILTER_CR_NUMERIC, 'is.firres', null, true], // firres + 28 => [FILTER_CR_NUMERIC, 'is.frores', null, true], // frores + 30 => [FILTER_CR_NUMERIC, 'is.holres', null, true], // holres + 27 => [FILTER_CR_NUMERIC, 'is.natres', null, true], // natres + 29 => [FILTER_CR_NUMERIC, 'is.shares', null, true], // shares + 37 => [FILTER_CR_NUMERIC, 'is.mleatkpwr', null, true], // mleatkpwr + 84 => [FILTER_CR_NUMERIC, 'is.mlecritstrkrtng', null, true], // mlecritstrkrtng + 78 => [FILTER_CR_NUMERIC, 'is.mlehastertng', null, true], // mlehastertng + 95 => [FILTER_CR_NUMERIC, 'is.mlehitrtng', null, true], // mlehitrtng + 38 => [FILTER_CR_NUMERIC, 'is.rgdatkpwr', null, true], // rgdatkpwr + 40 => [FILTER_CR_NUMERIC, 'is.rgdcritstrkrtng', null, true], // rgdcritstrkrtng + 101 => [FILTER_CR_NUMERIC, 'is.rgdhastertng', null, true], // rgdhastertng + 39 => [FILTER_CR_NUMERIC, 'is.rgdhitrtng', null, true], // rgdhitrtng + 49 => [FILTER_CR_NUMERIC, 'is.splcritstrkrtng', null, true], // splcritstrkrtng + 102 => [FILTER_CR_NUMERIC, 'is.splhastertng', null, true], // splhastertng + 48 => [FILTER_CR_NUMERIC, 'is.splhitrtng', null, true], // splhitrtng + 51 => [FILTER_CR_NUMERIC, 'is.spldmg', null, true], // spldmg + 50 => [FILTER_CR_NUMERIC, 'is.splheal', null, true], // splheal + 8 => [FILTER_CR_BOOLEAN, 'requiredDisenchantSkill' ], // disenchantable + 59 => [FILTER_CR_NUMERIC, 'durability', null, true], // dura + 104 => [FILTER_CR_STRING, 'description', true ], // flavortext + 7 => [FILTER_CR_BOOLEAN, 'description_loc0', true ], // hasflavortext + 142 => [FILTER_CR_STRING, 'iconString', ], // icon + + 176 => [FILTER_CR_STAFFFLAG, 'flags' ], // flags + 177 => [FILTER_CR_STAFFFLAG, 'flagsExtra' ], // flags2 + ); + + + // $field = Util::$itemFilter[$cr[0]]; + + public function __construct($parent) { - $item['teaches'] = $teaches; - unset($teaches); - unset($spellrow); + $classes = new CharClassList(); + foreach ($classes->iterate() as $cId => $_tpl) + { + // preselect misc subclasses + $this->ubFilter[$cId] = [ITEM_CLASS_WEAPON => [14], ITEM_CLASS_ARMOR => [0]]; + + for ($i = 0; $i < 21; $i++) + if ($_tpl['weaponTypeMask'] & (1 << $i)) + $this->ubFilter[$cId][ITEM_CLASS_WEAPON][] = $i; + + for ($i = 0; $i < 11; $i++) + if ($_tpl['armorTypeMask'] & (1 << $i)) + $this->ubFilter[$cId][ITEM_CLASS_ARMOR][] = $i; + } + + parent::__construct($parent); } -unlocks - $locks_row = $DB->selectCol(' - SELECT lockID - FROM ?_lock - WHERE - (type1=1 AND lockproperties1=?d) OR - (type2=1 AND lockproperties2=?d) OR - (type3=1 AND lockproperties3=?d) OR - (type4=1 AND lockproperties4=?d) OR - (type5=1 AND lockproperties5=?d) - ', - $item['entry'], $item['entry'], $item['entry'], $item['entry'], $item['entry'] - ); - if($locks_row) + protected function createSQLForCriterium(&$cr) { - // ??????? ??????? ? ????? ????? ?????: - $item['unlocks'] = $DB->select(' - SELECT ?# - FROM gameobject_template - WHERE - ( - ((type IN (?a)) AND (data0 IN (?a))) - OR - ((type IN (?a)) AND (data0 IN (?a))) + 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]) + { + case 2: // bindonpickup [yn] + if ($this->int2Bool($cr[1])) + return ['bonding', 1, $cr[1] ? null : '!']; + break; + case 3: // bindonequip [yn] + if ($this->int2Bool($cr[1])) + return ['bonding', 2, $cr[1] ? null : '!']; + break; + case 4: + if ($this->int2Bool($cr[1])) // bindonuse [yn] + return ['bonding', 3, $cr[1] ? null : '!']; + break; + case 168: // teachesspell [yn] 483: learn recipe; 55884: learn mount/pet + if ($this->int2Bool($cr[1])) + return ['spellId1', [483, 55884], $cr[1] ? null : '!']; + break; + case 15: // unique [yn] + if ($this->int2Bool($cr[1])) + return ['maxCount', 1, $cr[1] ? null : '!']; + break; + case 80: // has sockets [enum] + switch ($cr[1]) + { + case 5: // Yes + return ['is.nsockets', 0, '!']; + case 6: // No + return ['is.nsockets', 0]; + case 1: // Meta + case 2: // Red + case 3: // Yellow + case 4: // Blue + $mask = 1 << ($cr[1] - 1); + return ['OR', ['socketColor1', $mask], ['socketColor2', $mask], ['socketColor3', $mask]]; + } + break; + case 81: // fits gem slot [enum] + switch ($cr[1]) + { + case 5: // Yes + return ['gemEnchantmentId', 0, '!']; + case 6: // No + return ['gemEnchantmentId', 0]; + case 1: // Meta + case 2: // Red + case 3: // Yellow + case 4: // Blue + $mask = 1 << ($cr[1] - 1); + return ['AND', ['gemEnchantmentId', 0, '!'], ['gemColorMask', $mask, '&']]; + } + break; + case 133: // acc bound [yn] + if ($this->int2Bool($cr[1])) + { + if ($cr[1]) + return ['AND', ['flags', ITEM_FLAG_ACCOUNTBOUND, '&'], ['quality', 7]]; + else + return ['OR', [['flags', ITEM_FLAG_ACCOUNTBOUND, '&'], 0], ['quality', 7, '!']]; + } + break; + case 107: // effecttext [str] not yet parsed ['effectsParsed_loc'.User::$localeId, $cr[2]] +/* todo */ return [1]; + case 132: // glyphtype [enum] susubclass not yet set + switch ($cr[1]) + { + case 1: // Major + case 2: // Minor + return ['AND', ['class', 16], ['subSubClass', $cr[1]]]; + } + break; + case 124: // randomenchants [str] + // joining this in one step results in hell .. so .. two steps + $tmp = DB::Aowow()->selectCol('SELECT IF (ire.id > 0, iet.entry, -iet.entry) FROM item_enchantment_template iet JOIN ?_itemrandomenchant ire ON ABS(ire.id) = iet.ench WHERE ire.name_loc'.User::$localeId.' LIKE ?', '%'.$cr[2].'%'); + $suffix = $prop = []; + foreach ($tmp as $t) + { + if ($t > 0) + $prop[] = intVal($t); + else if ($t < 0) + $suffix[] = -intVal($t); + } + + if ($suffix && $prop) + return ['OR', ['randomProperty', $prop], ['randomSuffix', $suffix]]; + else if ($suffix || $prop) + return $prop ? ['randomProperty', $prop] : ['randomSuffix', $suffix]; + else + return [0]; // no results aren't really input errors + case 125: // reqarenartng [op] [int] JOIN npc_vendor, game_event_npc_vendor, itemextendedcost.dbc + if (!$this->isSaneNumeric($cr[2])) + break; + + $this->formData['extraCols'][] = $cr[0]; +/* todo */ return [1]; + case 160: // relatedevent [enum] like 169 .. crawl though npc_vendor and loot_templates of event-related spawns +/* todo */ return [1]; + case 152: // classspecific [enum] + $_ = @$this->enums[$cr[0]][$cr[1]]; + if ($_ !== null) + { + if (is_bool($_)) + return $_ ? ['AND', [['requiredClass', CLASS_MASK_ALL, '&'], CLASS_MASK_ALL, '!'], ['requiredClass', 0, '>']] : ['OR', [['requiredClass', CLASS_MASK_ALL, '&'], CLASS_MASK_ALL], ['requiredClass', 0]]; + else if (is_int($_)) + return ['AND', [['requiredClass', CLASS_MASK_ALL, '&'], CLASS_MASK_ALL, '!'], ['requiredClass', 1 << ($_ - 1), '&']]; + } + break; + case 153: // racespecific [enum] + $_ = @$this->enums[$cr[0]][$cr[1]]; + if ($_ !== null) + { + if (is_bool($_)) + return $_ ? ['AND', [['requiredRace', RACE_MASK_ALL, '&'], RACE_MASK_ALL, '!'], ['requiredRace', 0, '>']] : ['OR', [['requiredRace', RACE_MASK_ALL, '&'], RACE_MASK_ALL], ['requiredRace', 0]]; + else if (is_int($_)) + return ['AND', [['requiredRace', RACE_MASK_ALL, '&'], RACE_MASK_ALL, '!'], ['requiredRace', 1 << ($_ - 1), '&']]; + } + break; + case 35: // damagetype [enum] + if (!$this->isSaneNumeric($cr[1]) || $cr[1] > 6 || $cr[1] < 0) + break; + + return ['OR', ['dmgType1', $cr[1]], ['dmgType2', $cr[1]]]; + case 109: // armorbonus [op] [int] + if (!$this->isSaneNumeric($cr[2], false)) + break; + + $this->formData['extraCols'][] = $cr[0]; + return ['AND', ['armordamagemodifier', $cr[2], $this->int2Op($cr[1])], ['class', ITEM_CLASS_ARMOR]]; + case 86: // craftedprof [profession] +/* todo */ return [1]; + case 16: // dropsin [zone] +/* todo */ return [1]; + case 105: // dropsinnormal [heroicdungeon-any] +/* todo */ return [1]; + case 106: // dropsinheroic [heroicdungeon-any] +/* todo */ return [1]; + case 147: // dropsinnormal10 [multimoderaid-any] +/* todo */ return [1]; + case 148: // dropsinnormal25 [multimoderaid-any] +/* todo */ return [1]; + case 149: // dropsinheroic10 [heroicraid-any] +/* todo */ return [1]; + case 150: // dropsinheroic25 [heroicraid-any] +/* todo */ return [1]; + case 68: // otdisenchanting [yn] +/* todo */ return [1]; + case 69: // otfishing [yn] +/* todo */ return [1]; + case 70: // otherbgathering [yn] +/* todo */ return [1]; + case 71: // otitemopening [yn] +/* todo */ return [1]; + case 72: // otlooting [yn] +/* todo */ return [1]; + case 143: // otmilling [yn] +/* todo */ return [1]; + case 73: // otmining [yn] +/* todo */ return [1]; + case 74: // otobjectopening [yn] +/* todo */ return [1]; + case 75: // otpickpocketing [yn] +/* todo */ return [1]; + case 88: // otprospecting [yn] +/* todo */ return [1]; + case 93: // otpvp [pvp] +/* todo */ return [1]; + case 171: // otredemption [yn] +/* todo */ return [1]; + case 76: // otskinning [yn] +/* todo */ return [1]; + case 158: // purchasablewithcurrency [currency-any] +/* todo */ return [1]; + case 118: // purchasablewithitem [itemcurrency-any] +/* todo */ return [1]; + case 144: // purchasablewithhonor [yn] +/* todo */ return [1]; + case 145: // purchasablewitharena [yn] +/* todo */ return [1]; + case 18: // rewardedbyfactionquest [side] +/* todo */ return [1]; + case 126: // rewardedbyquestin [zone-any] +/* todo */ return [1]; + case 172: // rewardedbyachievement [yn] +/* todo */ return [1]; + case 92: // soldbyvendor [yn] +/* todo */ return [1]; + case 129: // soldbynpc [str-small] +/* todo */ return [1]; + case 161: // availabletoplayers [yn] +/* todo */ return [1]; + case 90: // avgbuyout [op] [int] +/* todo */ return [1]; + case 65: // avgmoney [op] [int] + if (!$this->isSaneNumeric($cr[2]) || $cr[1] < 0) + break; + + $this->formData['extraCols'][] = $cr[0]; + return ['AND', ['flags', ITEM_FLAG_OPENABLE, '&'], ['((minMoneyLoot + maxMoneyLoot) / 2)', $cr[2], $this->int2Op($cr[1])]]; + case 62: // cooldown [op] [int] fuck it .. too complex atm + if (!$this->isSaneNumeric($cr[2]) || $cr[1] < 0) + break; + + $this->formData['extraCols'][] = $cr[0]; + /* 5x + if (id > 0 AND trigger = 0 AND + (((cd > 0 OR (cat = 0 AND cd = 0)) AND cdMatch) OR + ((cat > 0 OR (cat = 0 AND cd = 0)) AND catMatch)) ) - ', - $object_cols[0], - array(GAMEOBJECT_TYPE_QUESTGIVER, GAMEOBJECT_TYPE_CHEST, GAMEOBJECT_TYPE_TRAP, GAMEOBJECT_TYPE_GOOBER, GAMEOBJECT_TYPE_CAMERA, GAMEOBJECT_TYPE_FLAGSTAND, GAMEOBJECT_TYPE_FLAGDROP), - $locks_row, - array(GAMEOBJECT_TYPE_DOOR, GAMEOBJECT_TYPE_BUTTON), - $locks_row - ); - if(!$item['unlocks']) - unset($item['unlocks']); -*/ + */ +/* todo */ return [1]; + case 162: // deprecated [yn] +/* todo */ return [1]; + case 163: // disenchantsinto [disenchanting] + break; + case 10: // locked [yn] + break; + case 159: // millable [yn] + break; + case 127: // notavailable [yn] + break; + case 85: // objectivequest [side] + break; + case 11: // openable [yn] + break; + case 12: // partofset [yn] + break; + case 98: // partyloot [yn] + break; + case 89: // prospectable [yn] + break; + case 5: // questitem [yn] + break; + case 13: // randomlyenchanted [yn] + break; + case 14: // readable [yn] + break; + case 87: // reagentforability [profession] + break; + case 63: // buyprice [op] [int] + break; + case 154: // refundable [yn] + break; + case 165: // repaircost [op] [int] + break; + case 64: // sellprice [op] [int] + break; + case 157: // smartloot [yn] + break; + case 6: // startsquest [side] + break; + case 91: // tool [totemcategory] + break; + case 155: // usableinarenas [yn] + break; + case 156: // usablewhenshapeshifted [yn] + break; + case 130: // hascomments [yn] + break; + case 113: // hasscreenshots [yn] + break; + case 167: // hasvideos [yn] + break; + } + + unset($cr); + $this->error = 1; + return [1]; + } + + protected function createSQLForValues() + { + $parts = []; + $_v = $this->fiData['v']; + + // weights + if (!empty($_v['wt']) && !empty($_v['wtv'])) + { + // gm - gem quality (qualityId) + // jc - jc-gems included (bool) + + // they MAY be strings if only one weight is set + $_v['wt'] = (array)$_v['wt']; + $_v['wtv'] = (array)$_v['wtv']; + + $parts[] = $this->parent->createConditionsForWeights($_v); + + foreach ($_v['wt'] as $_) + $this->formData['extraCols'][] = $_; + + $this->formData['setWeights'] = [$_v['wt'], $_v['wtv']]; + } + + // upgrade for [form only] + if (isset($_v['upg'])) + { + // valid item? + if (is_int($_v['upg']) && DB::Aowow()->selectCell('SELECT 1 FROM ?_items WHERE class IN (2, 3, 4) AND id = ?d', $_v['upg'])) + $this->formData['form']['upg'] = $_v['upg']; + else + unset($_v['upg']); + } + + // group by [form only] + if (isset($_v['gb'])) + { + // valid item? + if (is_int($_v['gb']) && $_v['gb'] >= 0 && $_v['gb'] < 4) + $this->formData['form']['gb'] = $_v['gb']; + else + unset($_v['gb']); + } + + // name + if (isset($_v['na'])) + $parts[] = ['name_loc'.User::$localeId, $_v['na']]; + + // usable-by (not excluded by requiredClass && armor or weapons match mask from ?_classes) + if (isset($_v['ub'])) + { + if (in_array($_v['ub'], [1, 2, 3, 4, 5, 6, 7, 8, 9, 11])) + { + $parts[] = array( + 'AND', + ['OR', ['requiredClass', 0], ['requiredClass', $this->list2Mask($_v['ub']), '&']], + [ + 'OR', + ['class', [2, 4], '!'], + ['AND', ['class', 2], ['subclassbak', $this->ubFilter[$_v['ub']][ITEM_CLASS_WEAPON]]], + ['AND', ['class', 4], ['subclassbak', $this->ubFilter[$_v['ub']][ITEM_CLASS_ARMOR]]] + ] + ); + } + else + unset($_v['ub']); + } + + // quality [list] + if (isset($_v['qu'])) + { + $_ = (array)$_v['qu']; + if (!array_diff($_, array_keys(Util::$rarityColorStings))) + $parts[] = ['quality', $_]; + else + unset($_v['qu']); + } + + // type + if (isset($_v['ty'])) + { + // should be contextual to 'class' + $_ = (array)$_v['ty']; + $parts[] = ['subclass', $_]; + } + + // slot + if (isset($_v['sl'])) + { + // should be contextual + $_ = (array)$_v['sl']; + $parts[] = ['slot', $_]; + } + + // side + if (isset($_v['si'])) + { + $ex = [['requiredRace', RACE_MASK_ALL, '&'], RACE_MASK_ALL, '!']; + $notEx = ['OR', ['requiredRace', 0], [['requiredRace', RACE_MASK_ALL, '&'], RACE_MASK_ALL]]; + + switch ($_v['si']) + { + case 3: + $parts[] = $notEx; + break; + case 2: + $parts[] = ['OR', $notEx, ['requiredRace', RACE_MASK_HORDE, '&']]; + break; + case -2: + $parts[] = ['AND', $ex, ['requiredRace', RACE_MASK_HORDE, '&']]; + break; + case 1: + $parts[] = ['OR', $notEx, ['requiredRace', RACE_MASK_ALLIANCE, '&']]; + break; + case -1: + $parts[] = ['AND', $ex, ['requiredRace', RACE_MASK_ALLIANCE, '&']]; + break; + default: + unset($_v['si']); + } + } + + // itemLevel min + if (isset($_v['minle'])) + { + if (is_int($_v['minle']) && $_v['minle'] > 0) + $parts[] = ['itemLevel', $_v['minle'], '>=']; + else + unset($_v['minle']); + } + + // itemLevel max + if (isset($_v['maxle'])) + { + if (is_int($_v['maxle']) && $_v['maxle'] > 0) + $parts[] = ['itemLevel', $_v['maxle'], '<=']; + else + unset($_v['maxle']); + } + + // reqLevel min + if (isset($_v['minrl'])) + { + if (is_int($_v['minrl']) && $_v['minrl'] > 0) + $parts[] = ['requiredLevel', $_v['minrl'], '>=']; + else + unset($_v['minrl']); + } + + // reqLevel max + if (isset($_v['maxrl'])) + { + if (is_int($_v['maxrl']) && $_v['maxrl'] > 0) + $parts[] = ['requiredLevel', $_v['maxrl'], '<=']; + else + unset($_v['maxrl']); + } + + return $parts; + } +} ?> diff --git a/includes/class.itemset.php b/includes/class.itemset.php index b772627a..c6d78b88 100644 --- a/includes/class.itemset.php +++ b/includes/class.itemset.php @@ -12,7 +12,8 @@ class ItemsetList extends BaseType public $pieceToSet = []; // used to build g_items and search private $classes = []; // used to build g_classes - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_itemset WHERE [filter] [cond] ORDER BY maxlevel DESC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_itemset `set`'; + protected $queryOpts = ['set' => ['o' => 'maxlevel DESC']]; public function __construct($conditions = [], $applyFilter = false) { @@ -82,4 +83,121 @@ class ItemsetList extends BaseType public function renderTooltip() { } } + +// missing filter: "Available to Players" +class ItemsetListFilter extends Filter +{ + // cr => [type, field, misc, extraCol] + protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet + 2 => [FILTER_CR_NUMERIC, 'id', null, true], // id + 3 => [FILTER_CR_NUMERIC, 'npieces', ], // pieces + 4 => [FILTER_CR_STRING, 'bonusText', true ], // bonustext + 5 => [FILTER_CR_BOOLEAN, 'heroic', ], // heroic + 6 => [FILTER_CR_ENUM, 'holidayId', ], // relatedevent + ); + + 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]) + { + case 12: // available to players [yn] ugh .. scan loot, quest and vendor templates and write to ?_itemset +/* todo */ return [1]; + case 8: // hascomments [yn] +/* todo */ return [1]; + case 9: // hasscreenshots [yn] +/* todo */ return [1]; + case 10: // hasvideos [yn] +/* todo */ return [1]; + } + + unset($cr); + $this->error = 1; + return [1]; + } + + protected function createSQLForValues() + { + $parts = []; + $_v = &$this->fiData['v']; + + // name [str] + if (isset($_v['na'])) + $parts[] = ['name_loc'.User::$localeId, $_v['na']]; + + // quality [enum] + if (isset($_v['qu'])) + $parts[] = ['quality', (array)$_v['qu']]; + + // type [enum] + if (isset($_v['ty'])) + $parts[] = ['type', (array)$_v['ty']]; + + // itemLevel min [int] + if (isset($_v['minle'])) + { + if (is_int($_v['minle']) && $_v['minle'] > 0) + $parts[] = ['minLevel', $_v['minle'], '>=']; + else + unset($_v['minle']); + } + + // itemLevel max [int] + if (isset($_v['maxle'])) + { + if (is_int($_v['maxle']) && $_v['maxle'] > 0) + $parts[] = ['maxLevel', $_v['maxle'], '<=']; + else + unset($_v['maxle']); + } + + // reqLevel min [int] + if (isset($_v['minrl'])) + { + if (is_int($_v['minrl']) && $_v['minrl'] > 0) + $parts[] = ['reqLevel', $_v['minrl'], '>=']; + else + unset($_v['minrl']); + } + + // reqLevel max [int] + if (isset($_v['maxrl'])) + { + if (is_int($_v['maxrl']) && $_v['maxrl'] > 0) + $parts[] = ['reqLevel', $_v['maxrl'], '<=']; + else + unset($_v['maxrl']); + } + + // class [enum] + if (isset($_v['cl'])) + { + if (in_array($_v['cl'], [1, 2, 3, 4, 5, 6, 7, 8, 9, 11])) + $parts[] = ['classMask', $this->list2Mask($_v['cl']), '&']; + else + unset($_v['cl']); + } + + // tag [enum] + if (isset($_v['ta'])) + { + if ($_v['ta'] > 0 && $_v['ta'] < 31) + $parts[] = ['contentGroup', intVal($_v['ta'])]; + else + unset($_v['ta']); + } + + return $parts; + } +} + ?> diff --git a/includes/class.pet.php b/includes/class.pet.php index e74b14f5..a5b66fb9 100644 --- a/includes/class.pet.php +++ b/includes/class.pet.php @@ -7,9 +7,9 @@ class PetList extends BaseType { use ListviewHelper; - public static $type = TYPE_PET; + public static $type = TYPE_PET; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_pet WHERE [cond] ORDER BY Id ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_pet p'; public function getListviewData() { diff --git a/includes/class.quest.php b/includes/class.quest.php index 9e67657a..57ac949e 100644 --- a/includes/class.quest.php +++ b/includes/class.quest.php @@ -5,9 +5,13 @@ if (!defined('AOWOW_REVISION')) class QuestList extends BaseType { - public static $type = TYPE_QUEST; + public static $type = TYPE_QUEST; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM quest_template qt LEFT JOIN locales_quest lq ON qt.Id = lq.entry WHERE [filter] [cond] ORDER BY Id ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM quest_template qt'; + protected $queryOpts = array( + 'qt' => [['lq']], + 'lq' => ['j' => ['locales_quest lq ON qt.id = lq.entry', true]] + ); public function __construct($conditions = []) { diff --git a/includes/class.skill.php b/includes/class.skill.php index 1b302cc2..f82ffe2c 100644 --- a/includes/class.skill.php +++ b/includes/class.skill.php @@ -6,9 +6,9 @@ if (!defined('AOWOW_REVISION')) class SkillList extends BaseType { - public static $type = TYPE_SKILL; + public static $type = TYPE_SKILL; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_skillLine sl WHERE [cond] ORDER BY id ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_skillLine sl'; public function __construct($conditions = []) { diff --git a/includes/class.spell.php b/includes/class.spell.php index b6fa3541..9b093c31 100644 --- a/includes/class.spell.php +++ b/includes/class.spell.php @@ -34,7 +34,7 @@ class SpellList extends BaseType private $interactive = false; private $charLevel = MAX_LEVEL; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_spell s WHERE [filter] [cond]'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_spell s'; public function __construct($conditions = [], $applyFilter = false) { @@ -107,7 +107,7 @@ class SpellList extends BaseType } if ($foo) - $this->relItems = new ItemList(array(['i.entry', array_unique($foo)], 0)); + $this->relItems = new ItemList(array(['i.id', array_unique($foo)], 0)); } // use if you JUST need the name @@ -1345,7 +1345,7 @@ Lasts 5 min. $?$gte($pl,68)[][Cannot be used on items level 138 and higher.] if ($cId = $this->curTpl['effect'.$i.'CreateItemId']) { - $createItem = (new ItemList(array(['i.entry', (int)$cId])))->renderTooltip([], true, true); + $createItem = (new ItemList(array(['i.id', (int)$cId])))->renderTooltip([], true, true); break; } } @@ -1622,15 +1622,18 @@ Lasts 5 min. $?$gte($pl,68)[][Cannot be used on items level 138 and higher.] if ($invType = $this->getField('equippedItemInventoryTypeMask')) { - // remove some duplicated strings if both are used - if (($invType & 0x100020) == 0x100020) // Chest and Robe set - $invType &= ~0x20; + // remap some duplicated strings 'Off Hand' and 'Shield' are never used simultaneously + if ($invType & (1 << INVTYPE_ROBE)) // Robe => Chest + { + $invType &= ~(1 << INVTYPE_ROBE); + $invType &= (1 << INVTYPE_CHEST); + } - if (($invType & 0x404000) == 0x404000) // Off-hand and Shield set - $invType &= ~0x4000; - - if (($invType & 0x4008000) == 0x4008000) // Ranged and Ranged (right) set - $invType &= ~0x8000; + if ($invType & (1 << INVTYPE_RANGEDRIGHT)) // Ranged2 => Ranged + { + $invType &= ~(1 << INVTYPE_RANGEDRIGHT); + $invType &= (1 << INVTYPE_RANGED); + } $_ = []; $strs = Lang::$item['inventoryType']; @@ -1681,4 +1684,200 @@ Lasts 5 min. $?$gte($pl,68)[][Cannot be used on items level 138 and higher.] } } + +class SpellListFilter extends Filter +{ + // sources in filter and general use different indizes + private $enums = array( + 9 => array( + 1 => true, // Any + 2 => false, // None + 3 => 1, // Crafted + 4 => 2, // Drop + 6 => 4, // Quest + 7 => 5, // Vendor + 8 => 6, // Trainer + 9 => 7, // Discovery + 10 => 9 // Talent + ) + ); + + // cr => [type, field, misc, extraCol] + protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet + 2 => [FILTER_CR_NUMERIC, 'powerCostPercent', ], // prcntbasemanarequired + 3 => [FILTER_CR_BOOLEAN, 'spellFocusObject' ], // requiresnearbyobject + 4 => [FILTER_CR_NUMERIC, 'trainingcost' ], // trainingcost + 5 => [FILTER_CR_BOOLEAN, 'reqSpellId' ], // requiresprofspec + 10 => [FILTER_CR_FLAG, 'cuFlags', SPELL_CU_FIRST_RANK ], // firstrank + 12 => [FILTER_CR_FLAG, 'cuFlags', SPELL_CU_LAST_RANK ], // lastrank + 13 => [FILTER_CR_NUMERIC, 'rankId', ], // rankno + 14 => [FILTER_CR_NUMERIC, 'id', null, true], // id + 15 => [FILTER_CR_STRING, 'iconString', ], // icon + 19 => [FILTER_CR_FLAG, 'attributes0', 0x80000 ], // scaling + 25 => [FILTER_CR_BOOLEAN, 'skillLevelYellow' ] // rewardsskillups + ); + + 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]) + { + case 1: // costAbs [op] [int] + if (!$this->isSaneNumeric($cr[2])) + break; + + if (!$this->int2Op($cr[1])) + break; + + return ['OR', ['AND', ['powerType', [1, 6]], ['powerCost', (10 * $cr[2]), $cr[1]]], ['AND', ['powerType', [1, 6], '!'], ['powerCost', $cr[2], $cr[1]]]]; + case 9: // Source [enum] + $_ = @$this->enums[$cr[0]][$cr[1]]; + if ($_ !== null) + { + if (is_bool($_)) + return ['source', 0, ($_ ? '!' : null)]; + else if (is_int($_)) + return ['source', $_.':']; + } + break; + case 20: // has Reagents [yn] + if ($this->int2Bool($cr[1])) + { + if ($cr[1]) + return ['OR', ['reagent1', 0, '>'], ['reagent2', 0, '>'], ['reagent3', 0, '>'], ['reagent4', 0, '>'], ['reagent5', 0, '>'], ['reagent6', 0, '>'], ['reagent7', 0, '>'], ['reagent8', 0, '>']]; + else + return ['AND', ['reagent1', 0], ['reagent2', 0], ['reagent3', 0], ['reagent4', 0], ['reagent5', 0], ['reagent6', 0], ['reagent7', 0], ['reagent8', 0]]; + } + case 11: // hascomments [yn] +/* todo */ return [1]; + case 8: // hasscreenshots [yn] +/* todo */ return [1]; + case 17: // hasvideos [yn] +/* todo */ return [1]; + } + + unset($cr); + $this->error = 1; + return [1]; + } + + protected function createSQLForValues() + { + $parts = []; + $_v = &$this->fiData['v']; + + //string (extended) + if (isset($_v['na'])) + { + if (isset($_v['ex']) && $_v['ex'] == 'on') + $parts[] = ['OR', ['name_loc'.User::$localeId, $_v['na']], ['buff_loc'.User::$localeId, $_v['na']], ['description_loc'.User::$localeId, $_v['na']]]; + else + $parts[] = ['name_loc'.User::$localeId, $_v['na']]; + } + + // spellLevel min + if (isset($_v['minle'])) + { + if (is_int($_v['minle']) && $_v['minle'] > 0) + $parts[] = ['spellLevel', $_v['minle'], '>=']; + else + unset($_v['minle']); + } + + // spellLevel max + if (isset($_v['maxle'])) + { + if (is_int($_v['maxle']) && $_v['maxle'] > 0) + $parts[] = ['spellLevel', $_v['maxle'], '<=']; + else + unset($_v['maxle']); + } + + // skillLevel min + if (isset($_v['minrs'])) + { + if (is_int($_v['minrs']) && $_v['minrs'] > 0) + $parts[] = ['learnedAt', $_v['minrs'], '>=']; + else + unset($_v['minrs']); + } + + // skillLevel max + if (isset($_v['maxrs'])) + { + if (is_int($_v['maxrs']) && $_v['maxrs'] > 0) + $parts[] = ['learnedAt', $_v['maxrs'], '<=']; + else + unset($_v['maxrs']); + } + + // race + if (isset($_v['ra'])) + { + if (in_array($_v['ra'], [1, 2, 3, 4, 5, 6, 7, 8, 10, 11])) + $parts[] = ['AND', [['reqRaceMask', RACE_MASK_ALL, '&'], RACE_MASK_ALL, '!'], ['reqRaceMask', $this->list2Mask($_v['ra']), '&']]; + else + unset($_v['ra']); + } + + // class [list] + if (isset($_v['cl'])) + { + $_ = (array)$_v['cl']; + if (!array_diff($_, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11])) + $parts[] = ['reqClassMask', $this->list2Mask($_), '&']; + else + unset($_v['cl']); + } + + // school [list] + if (isset($_v['sc'])) + { + $_ = (array)$_v['sc']; + if (!array_diff($_, [0, 1, 2, 3, 4, 5, 6])) + $parts[] = ['schoolMask', $this->list2Mask($_, true), '&']; + else + unset($_v['sc']); + } + + // glyph type [list] wonky, admittedly, but consult SPELL_CU_* in defines and it makes sense + if (isset($_v['gl'])) + { + if (in_array($_v['gl'], [1, 2])) + $parts[] = ['cuFlags', ($this->list2Mask($_v['gl']) << 6), '&']; + else + unset($_v['gl']); + } + + // dispel type + if (isset($_v['dt'])) + { + if (in_array($_v['dt'], [1, 2, 3, 4, 5, 6, 9])) + $parts[] = ['dispelType', $_v['dt']]; + else + unset($_v['dt']); + } + + // mechanic + if (isset($_v['me'])) + { + if ($_v['me'] > 0 && $_v['me'] < 32) + $parts[] = ['OR', ['mechanic', $_v['me']], ['effect1Mechanic', $_v['me']], ['effect2Mechanic', $_v['me']], ['effect3Mechanic', $_v['me']]]; + else + unset($_v['me']); + } + + return $parts; + } +} + ?> diff --git a/includes/class.title.php b/includes/class.title.php index cdcf0bba..ae941c9a 100644 --- a/includes/class.title.php +++ b/includes/class.title.php @@ -7,11 +7,11 @@ class TitleList extends BaseType { use listviewHelper; - public static $type = TYPE_TITLE; + public static $type = TYPE_TITLE; - public $sources = []; + public $sources = []; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_titles WHERE [cond] ORDER BY Id ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_titles t'; public function __construct($conditions = []) { diff --git a/includes/class.user.php b/includes/class.user.php index e4a82fc5..99618a1e 100644 --- a/includes/class.user.php +++ b/includes/class.user.php @@ -50,7 +50,7 @@ class User { self::$id = $userId; - $ipBan = DB::Aowow()->SelectRow('SELECT count, unbanDate AS unbanDateIP FROM ?_account_bannedIPs WHERE ip = ?s AND type = 0', + $ipBan = DB::Aowow()->SelectRow('SELECT count, unbanDate AS unbanDateIP FROM ?_account_bannedIPs WHERE ip = ? AND type = 0', $_SERVER['REMOTE_ADDR'] ); // explicit " > "; incremented first, checked after @@ -247,7 +247,7 @@ class User { self::$passHash = self::hashCrypt($pass); - DB::Aowow()->query('UPDATE ?_account SET passHash = ?s WHERE id = ?d', + DB::Aowow()->query('UPDATE ?_account SET passHash = ? WHERE id = ?d', self::$passHash, self::$id ); diff --git a/includes/class.worldevent.php b/includes/class.worldevent.php index d2aa9b48..52365e34 100644 --- a/includes/class.worldevent.php +++ b/includes/class.worldevent.php @@ -5,9 +5,10 @@ if (!defined('AOWOW_REVISION')) class WorldEventList extends BaseType { - public static $type = TYPE_WORLDEVENT; + public static $type = TYPE_WORLDEVENT; - protected $setupQuery = 'SELECT *, -e.id AS ARRAY_KEY, -e.id as id FROM ?_events e LEFT JOIN ?_holidays h ON e.holidayId = h.id WHERE [cond] ORDER BY -e.id ASC'; + protected $queryBase = 'SELECT *, -e.id AS ARRAY_KEY, -e.id as id FROM ?_events e LEFT JOIN ?_holidays h ON e.holidayId = h.id'; + protected $queryOpts = ['e' => ['o' => '-e.id ASC']]; public function __construct($conditions = []) { diff --git a/includes/class.zone.php b/includes/class.zone.php index 82e7b71d..eba4590b 100644 --- a/includes/class.zone.php +++ b/includes/class.zone.php @@ -11,9 +11,9 @@ if (!defined('AOWOW_REVISION')) class ZoneList extends BaseType { - public static $type = TYPE_ZONE; + public static $type = TYPE_ZONE; - protected $setupQuery = 'SELECT *, id AS ARRAY_KEY FROM ?_zones z WHERE [cond] ORDER BY Id ASC'; + protected $queryBase = 'SELECT *, id AS ARRAY_KEY FROM ?_zones z'; public function getListviewData() { diff --git a/includes/defines.php b/includes/defines.php index e0822230..78b06cf6 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -87,16 +87,24 @@ define('LOCALE_DE', 3); define('LOCALE_ES', 6); define('LOCALE_RU', 8); +define('FILTER_CR_BOOLEAN', 1); +define('FILTER_CR_FLAG', 2); +define('FILTER_CR_NUMERIC', 3); +define('FILTER_CR_STRING', 4); +define('FILTER_CR_ENUM', 5); +define('FILTER_CR_STAFFFLAG', 6); + // conditional information in template define('GLOBALINFO_SELF', 0x1); // id, name, icon define('GLOBALINFO_RELATED', 0x2); // spells used by pet, classes/races required by spell, ect define('GLOBALINFO_REWARDS', 0x4); // items rewarded by achievement/quest, ect define('GLOBALINFO_ANY', 0xF); -define('ITEMINFO_JSON', 0x1); -define('ITEMINFO_SUBITEMS', 0x2); -define('ITEMINFO_VENDOR', 0x4); -define('ITEMINFO_LOOT', 0x8); +define('ITEMINFO_JSON', 0x01); +define('ITEMINFO_SUBITEMS', 0x02); +define('ITEMINFO_VENDOR', 0x04); +define('ITEMINFO_LOOT', 0x08); +define('ITEMINFO_GEM', 0x10); define('NPCINFO_TAMEABLE', 0x1); define('NPCINFO_MODEL', 0x2); @@ -324,7 +332,7 @@ define('OBJECT_DESTRUCTIBLE_BUILDING', 33); define('OBJECT_GUILD_BANK', 34); define('OBJECT_TRAPDOOR', 35); -// InventoryType +// InventoryType [slot] define('INVTYPE_NON_EQUIP', 0); define('INVTYPE_HEAD', 1); define('INVTYPE_NECK', 2); @@ -373,13 +381,27 @@ define('ITEM_QUALITY_ARTIFACT', 6); // LIGHT YELLOW define('ITEM_QUALITY_HEIRLOOM', 7); // GOLD // ItemClass +define('ITEM_CLASS_CONSUMABLE', 0); +define('ITEM_CLASS_CONTAINER', 1); define('ITEM_CLASS_WEAPON', 2); +define('ITEM_CLASS_GEM', 3); define('ITEM_CLASS_ARMOR', 4); +define('ITEM_CLASS_REAGENT', 5); define('ITEM_CLASS_AMMUNITION', 6); +define('ITEM_CLASS_TRADEGOOD', 7); +// define('ITEM_CLASS_GENERIC', 8); define('ITEM_CLASS_RECIPE', 9); +// define('ITEM_CLASS_MONEY', 10); +define('ITEM_CLASS_QUIVER', 11); +define('ITEM_CLASS_QUEST', 12); +define('ITEM_CLASS_KEY', 13); +// define('ITEM_CLASS_PERMANENT', 14); +define('ITEM_CLASS_MISC', 15); +define('ITEM_CLASS_GLYPH', 16); // ItemFlags define('ITEM_FLAG_CONJURED', 0x0000002); +define('ITEM_FLAG_OPENABLE', 0x0000004); define('ITEM_FLAG_HEROIC', 0x0000008); define('ITEM_FLAG_DEPRECATED', 0x0000010); define('ITEM_FLAG_PARTYLOOT', 0x0000800); diff --git a/includes/kernel.php b/includes/kernel.php index 398e92d2..c721fb20 100644 --- a/includes/kernel.php +++ b/includes/kernel.php @@ -15,15 +15,21 @@ error_reporting($e); define('STATIC_URL', substr('http://'.$_SERVER['SERVER_NAME'].strtr($_SERVER['SCRIPT_NAME'], ['index.php' => '']), 0, -1)); require 'includes/Smarty-2.6.26/libs/Smarty.class.php'; // Libraray: http://www.smarty.net/ -require 'includes/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple +// require 'includes/Smarty-3.1.14/libs/Smarty.class.php'; // Libraray: http://www.smarty.net/ +require 'includes/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (mysqli: https://bitbucket.org/brainreaver/dbsimple/src) require 'includes/utilities.php'; require 'includes/class.user.php'; require 'includes/class.database.php'; -// autoload any List-Classes +// autoload List-Classes and Associated Filters spl_autoload_register(function ($class) { - if (strpos($class, 'List') && !strpos($class, 'Filter')) + if (strpos($class, 'List') && !class_exists($class)) + { + if (!class_exists('BaseType')) + require 'includes/class.basetype.php'; + require 'includes/class.'.strtr($class, ['List' => '']).'.php'; + } }); // debug: measure execution times @@ -82,5 +88,6 @@ User::assignUserToTemplate($smarty, true); @list($str, $trash) = explode('&', $_SERVER['QUERY_STRING'], 2); @list($pageCall, $pageParam) = explode('=', $str, 2); $smarty->assign('query', [$pageCall, $pageParam]); +$smarty->assign('wowhead', 'http://'.Util::$subDomains[User::$localeId].'.wowhead.com/'.$str); ?> diff --git a/includes/utilities.php b/includes/utilities.php index 07eb6ab3..6660cc49 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -3,410 +3,6 @@ if (!defined('AOWOW_REVISION')) die('invalid access'); -abstract class BaseType -{ - public $id = 0; - public $error = true; - - protected $templates = []; - protected $curTpl = []; // lets iterate! - protected $filter = null; - protected $matches = null; // total matches unaffected by sqlLimit in config - - protected $setupQuery = ''; - - /* - * condition as array [expression, value, operator] - * expression: str - must match fieldname; - * int - impl. 1: select everything - * array - another condition array - * value: str - operator defaults to: LIKE %% - * int - operator defaults to: = - * array - operator defaults to: IN () - * operator: modifies/overrides default - * ! - negated default value (NOT LIKE; <>; NOT IN) - * condition as str - * defines linking (AND || OR) - * condition as int - * defines LIMIT - * - * example: - * array( - * ['id', 45], - * ['name', 'test', '!'], - * [ - * 'AND', - * ['flags', 0xFF, '&'], - * ['flags2', 0xF, '&'], - * ] - * [['mask', 0x3, '&'], 0] - * 'OR', - * 5 - * ) - * results in - * WHERE ((`id` = 45) OR (`name` NOT LIKE "%test%") OR ((`flags` & 255) AND (`flags2` & 15)) OR ((`mask` & 3) = 0)) LIMIT 5 - */ - public function __construct($conditions = [], $applyFilter = false) - { - global $AoWoWconf; // yes i hate myself.. - - $sql = []; - $linking = ' AND '; - $limit = ' LIMIT '.$AoWoWconf['sqlLimit']; - $className = get_class($this); - - if (!$this->setupQuery || $conditions === null) - return; - - // may be called without filtering - if ($applyFilter && class_exists($className.'Filter')) - { - $fiName = $className.'Filter'; - $this->filter = new $fiName(); - if ($this->filter->init() === false) - return; - } - - $resolveCondition = function ($c, $supLink) use (&$resolveCondition) - { - $subLink = ''; - - foreach ($c as $foo) - { - if ($foo === 'AND') - $subLink = ' AND '; - else if ($foo === 'OR') // nessi-bug: if (0 == 'OR') was true once... w/e - $subLink = ' OR '; - } - - // need to manually set link for subgroups to be recognized as condition set - if ($subLink) - { - $sql = []; - - foreach ($c as $foo) - if (is_array($foo)) - if ($x = $resolveCondition($foo, $supLink)) - $sql[] = $x; - - return '('.implode($subLink, $sql).')'; - } - else - { - if ($c[0] == '1') - return '1'; - else if (is_array($c[0])) - $field = $resolveCondition($c[0], $supLink); - else if ($c[0]) - $field = '`'.implode('`.`', explode('.', Util::sqlEscape($c[0]))).'`'; - else - return null; - - if (is_array($c[1])) - { - $val = implode(',', Util::sqlEscape($c[1])); - if ($val === '') - return null; - - $op = (isset($c[2]) && $c[2] == '!') ? 'NOT IN' : 'IN'; - $val = '('.$val.')'; - } - else if (is_string($c[1])) - { - $val = Util::sqlEscape($c[1]); - if ($val === '') - return null; - - $op = (isset($c[2]) && $c[2] == '!') ? 'NOT LIKE' : 'LIKE'; - $val = '"%'.$val.'%"'; - } - else if (is_numeric($c[1])) - { - $op = (isset($c[2]) && $c[2] == '!') ? '<>' : '='; - $val = Util::sqlEscape($c[1]); - } - else // null for example - return null; - - if (isset($c[2]) && $c[2] != '!') - $op = $c[2]; - - return '('.$field.' '.$op.' '.$val.')'; - } - }; - - foreach ($conditions as $i => $c) - { - switch(getType($c)) - { - case 'array': - break; - case 'string': - case 'integer': - case 'double': - if (is_string($c)) - $linking = $c == 'AND' ? ' AND ' : ' OR '; - else - $limit = $c > 0 ? ' LIMIT '.$c : ''; - default: - unset($conditions[$i]); - } - } - - foreach ($conditions as $c) - if ($c) - if ($x = $resolveCondition($c, $linking)) - $sql[] = $x; - - // todo: add strings propperly without them being escaped by simpleDB..? - $this->setupQuery = str_replace('[filter]', $this->filter && $this->filter->buildQuery() ? $this->filter->getQuery().' AND ' : NULL, $this->setupQuery); - $this->setupQuery = str_replace('[cond]', empty($sql) ? '1' : '('.implode($linking, $sql).')', $this->setupQuery); - $this->setupQuery .= $limit; - - $rows = DB::Aowow()->SelectPage($this->matches, $this->setupQuery); - if (!$rows) - return; - - foreach ($rows as $k => $tpl) - $this->templates[$k] = $tpl; - - $this->reset(); // push first element for instant use - $this->error = false; - } - - public function &iterate() - { - // reset on __construct - $this->reset(); - - while (list($id, $tpl) = each($this->templates)) - { - $this->id = $id; - $this->curTpl = &$this->templates[$id]; // do not use $tpl as we want to be referenceable - - yield $id => $this->curTpl; - - unset($this->curTpl); // kill reference or it will 'bleed' into the next iteration - } - - // reset on __destruct .. Generator, Y U NO HAVE __destruct ?! - $this->reset(); - } - - protected function reset() - { - unset($this->curTpl); // kill reference or strange stuff will happen - $this->curTpl = reset($this->templates); - $this->id = key($this->templates); - } - - // read-access to templates - public function getField($field, $localized = false) - { - if (!$this->curTpl || (!$localized && !isset($this->curTpl[$field]))) - return ''; - - if ($localized) - return Util::localizedString($this->curTpl, $field); - - $value = $this->curTpl[$field]; - return is_numeric($value) ? floatVal($value) : $value; - } - - public function getRandomId() - { - $pattern = '/SELECT .* (-?[\w_]*\.?(id|entry)) AS ARRAY_KEY,?.* FROM (.*) WHERE .*/i'; - $replace = 'SELECT $1 FROM $3 ORDER BY RAND() ASC LIMIT 1'; - $query = preg_replace($pattern, $replace, $this->setupQuery); - - return DB::Aowow()->selectCell($query); - } - - public function getFoundIDs() - { - return array_keys($this->templates); - } - - public function getMatches() - { - return $this->matches; - } - - public function filterGetSetCriteria() - { - if ($this->filter) - return $this->filter->getSetCriteria(); - else - return null; - } - - public function filterGetForm() - { - if ($this->filter) - return $this->filter->getForm(); - else - return []; - } - - public function filterGetError() - { - if ($this->filter) - return $this->filter->error; - else - return false; - } - - // should return data required to display a listview of any kind - // this is a rudimentary example, that will not suffice for most Types - abstract public function getListviewData(); - - // should return data to extend global js variables for a certain type (e.g. g_items) - abstract public function addGlobalsToJScript(&$smarty, $addMask = GLOBALINFO_ANY); - - // NPC, GO, Item, Quest, Spell, Achievement, Profile would require this - abstract public function renderTooltip(); -} - -trait listviewHelper -{ - public function hasSetFields($fields) - { - if (!is_array($fields)) - return 0x0; - - $result = 0x0; - - foreach ($this->iterate() as $__) - { - foreach ($fields as $k => $str) - { - if ($this->getField($str)) - { - $result |= 1 << $k; - unset($fields[$k]); - } - } - - if (empty($fields)) // all set .. return early - { - $this->reset(); // Generators have no __destruct, reset manually, when not doing a full iteration - return $result; - } - } - - return $result; - } - - public function hasDiffFields($fields) - { - if (!is_array($fields)) - return 0x0; - - $base = []; - $result = 0x0; - - foreach ($fields as $k => $str) - $base[$str] = $this->getField($str); - - foreach ($this->iterate() as $__) - { - foreach ($fields as $k => $str) - { - if ($base[$str] != $this->getField($str)) - { - $result |= 1 << $k; - unset($fields[$k]); - } - } - - if (empty($fields)) // all fields diff .. return early - { - $this->reset(); // Generators have no __destruct, reset manually, when not doing a full iteration - return $result; - } - } - - return $result; - } - - public function hasAnySource() - { - if (!isset($this->sources)) - return false; - - foreach ($this->sources as $src) - { - if (!is_array($src)) - continue; - - if (!empty($src)) - return true; - } - - return false; - } - -} - -trait spawnHelper -{ - private static $spawnQuery = " SELECT a.guid AS ARRAY_KEY, map, position_x, position_y, spawnMask, phaseMask, spawntimesecs, eventEntry, pool_entry AS pool FROM ?# a LEFT JOIN ?# b ON a.guid = b.guid LEFT JOIN ?# c ON a.guid = c.guid WHERE id = ?d"; - - private function fetch() - { - if (!$this->id) - return false; - - switch (get_class($this)) - { - case 'CreatureList': - return DB::Aowow()->select(self::$spawnQuery, 'creature', 'game_event_creature', 'pool_creature', $this->id); - case 'GameObjectList': - return DB::Aowow()->select(self::$spawnQuery, 'gameobject', 'game_event_gameobject', 'pool_gameobject', $this->id); - default: - return false; - } - } - - /* - todo (med): implement this alpha-map-check-virtual-map-transform-wahey! - note: map in tooltips is activated by either '#map' as anchor (will automatic open mapviewer, when clicking link) in the href or as parameterless rel-parameter e.g. rel="map" in the anchor - */ - public function getSpawns($spawnInfo) - { - // SPAWNINFO_SHORT: true => only the most populated area and only coordinates - $data = []; - - // $raw = $this->fetch(); - // if (!$raw) - // return []; - - /* - SPAWNINFO_FULL: - $data = array( - areaId => array( - floorNo => array ( - posX => - posY => - respawn => - phaseMask => - spawnMask => - eventId => - poolId => - ) - ) - ) - - SPAWNINFO_SHORT: [zoneId, [[x1, y1], [x2, y2], ..]] // only the most populated zone - - SPAWNINFO_ZONES: [zoneId1, zoneId2, ..] // only zones - */ - - return $data; - } -} - - class Lang { public static $main; @@ -436,11 +32,20 @@ class Lang public static function load($loc) { - if ((require 'localization/locale_'.$loc.'.php') !== 1) - die('File for localization '.$loc.' not found.'); + if (!file_exists('localization/locale_'.$loc.'.php')) + die('File for localization '.strToUpper($loc).' not found.'); + else + require 'localization/locale_'.$loc.'.php'; foreach ($lang as $k => $v) self::$$k = $v; + + // *cough* .. temp-hack + if (User::$localeId == LOCALE_EN) + { + self::$item['cat'][2] = [self::$item['cat'][2], self::$spell['weaponSubClass']]; + self::$item['cat'][2][1][14] .= ' ('.self::$item['cat'][2][0].')'; + } } // todo: expand @@ -460,12 +65,14 @@ class Lang public static function getLocks($lockId, $interactive = false) { $locks = []; - $lock = DB::Aowow()->selectRow('SELECT * FROM ?_lock WHERE id = ?d', $this->curTpl['lockid']); + $lock = DB::Aowow()->selectRow('SELECT * FROM ?_lock WHERE id = ?d', $lockId); + if (!$lock) + return ''; for ($i = 1; $i <= 5; $i++) { - $prop = $lock['lockproperties'.$i]; - $rnk = $lock['requiredskill'.$i]; + $prop = $lock['properties'.$i]; + $rnk = $lock['reqSkill'.$i]; $name = ''; if ($lock['type'.$i] == 1) // opened by item @@ -479,17 +86,19 @@ class Lang } else if ($lock['type'.$i] == 2) // opened by skill { - if (in_array($prop, [6, 7, 15, 19])) // dnd stuff + if (!in_array($prop, [1, 2, 3, 4, 9, 16, 20])) // exclude unusual stuff continue; $txt = DB::Aowow()->selectRow('SELECT * FROM ?_locktype WHERE id = ?d', $prop); // todo (low): convert to static text - $name = Util::localizedString($txts, 'name'); + $name = Util::localizedString($txt, 'name'); if (!$name) continue; if ($rnk > 0) $name .= ' ('.$rnk.')'; } + else + continue; $locks[$lock['type'.$i] == 1 ? $i : -$i] = sprintf(Lang::$game['requires'], $name); } @@ -648,9 +257,10 @@ class SmartyAoWoW extends Smarty public function __construct($config) { + parent::__construct(); + $cwd = str_replace("\\", "/", getcwd()); - $this->Smarty(); $this->assign('appName', $config['page']['name']); $this->assign('AOWOW_REVISION', AOWOW_REVISION); $this->config = $config; @@ -723,7 +333,7 @@ class SmartyAoWoW extends Smarty // fetch announcements if ($tv['query'][0] && !preg_match('/[^a-z]/i', $tv['query'][0])) { - $ann = DB::Aowow()->Select('SELECT * FROM ?_announcements WHERE status = 1 AND (page = ?s OR page = "*")', $tv['query'][0]); + $ann = DB::Aowow()->Select('SELECT * FROM ?_announcements WHERE status = 1 AND (page = ? OR page = "*")', $tv['query'][0]); foreach ($ann as $k => $v) { if ($t = Util::localizedString($v, 'text')) @@ -815,7 +425,7 @@ class SmartyAoWoW extends Smarty { case TYPE_NPC: (new CreatureList(array(['ct.id', $ids], 0)))->addGlobalsToJscript($this, GLOBALINFO_SELF); break; case TYPE_OBJECT: (new GameobjectList(array(['gt.entry', $ids], 0)))->addGlobalsToJscript($this, GLOBALINFO_SELF); break; - case TYPE_ITEM: (new ItemList(array(['i.entry', $ids], 0)))->addGlobalsToJscript($this, GLOBALINFO_SELF); break; + case TYPE_ITEM: (new ItemList(array(['i.id', $ids], 0)))->addGlobalsToJscript($this, GLOBALINFO_SELF); break; case TYPE_QUEST: (new QuestList(array(['qt.entry', $ids], 0)))->addGlobalsToJscript($this, GLOBALINFO_SELF); break; case TYPE_SPELL: (new SpellList(array(['s.id', $ids], 0)))->addGlobalsToJscript($this, GLOBALINFO_SELF); break; case TYPE_ZONE: (new ZoneList(array(['z.id', $ids], 0)))->addGlobalsToJscript($this, GLOBALINFO_SELF); break; @@ -856,6 +466,14 @@ class SmartyAoWoW extends Smarty exit(); } + public function brb() + { + $this->assign('lang', array_merge(Lang::$main, Lang::$error)); + + $this->display('brb.tpl'); + exit(); + } + // creates the cache file public function saveCache($key, $data, $filter = null) { @@ -901,10 +519,10 @@ class SmartyAoWoW extends Smarty class Util { public static $resistanceFields = array( - null, 'holy_res', 'fire_res', 'nature_res', 'frost_res', 'shadow_res', 'arcane_res' + null, 'resHoly', 'resFire', 'resNature', 'resFrost', 'resShadow', 'resArcane' ); - private static $rarityColorStings = array( // zero-indexed + public static $rarityColorStings = array( // zero-indexed '9d9d9d', 'ffffff', '1eff00', '0070dd', 'a335ee', 'ff8000', 'e5cc80', 'e6cc80' ); @@ -912,6 +530,10 @@ class Util 'enus', null, 'frfr', 'dede', null, null, 'eses', null, 'ruru' ); + public static $subDomains = array( + 'www', null, 'fr', 'de', null, null, 'es', null, 'ru' + ); + public static $typeStrings = array( // zero-indexed null, 'npc', 'object', 'item', 'itemset', 'quest', 'spell', 'zone', 'faction', 'pet', 'achievement', 'title', 'event', 'class', 'race', 'skill', null, 'currency' @@ -1675,14 +1297,6 @@ class Util return self::formatTime($tDiff * 1000, true); } - public static function colorByRarity($idx) - { - if (!isset(self::$rarityColorStings)) - $idx = 1; - - return self::$rarityColorStings($idx); - } - public static function formatMoney($qty) { $money = ''; @@ -1827,13 +1441,16 @@ class Util return 'b'.strToUpper($_); } - public static function sqlEscape($data) + public static function sqlEscape($data, $relaxed = false) { - if (!is_array($data)) - return mysql_real_escape_string(trim($data)); + // relaxed: expecting strings for fulltext search + $pattern = $relaxed ? ['/[;`´"\/\\\]/ui', '--'] : ['/[^\p{L}0-9\s_\-\.]/ui', '--']; - array_walk($data, function(&$item, $key) { - $item = Util::sqlEscape($item); + if (!is_array($data)) + return preg_replace($pattern, '', trim($data)); + + array_walk($data, function(&$item, $key) use (&$relaxed) { + $item = self::sqlEscape($item, $relaxed); }); return $data; @@ -2054,6 +1671,38 @@ class Util return mb_strtoupper($first, 'UTF-8') . $rest; } + + public static function checkNumeric(&$data) + { + if ($data === null) + return false; + else if (!is_array($data)) + { + $data = trim($data); + + if (is_numeric($data)) + { + $_int = intVal($data); + $_float = floatVal($data); + + $data = ($_int == $_float) ? $_int : $_float; + return true; + } + else if (preg_match('/^\d*,\d+$/', $data)) + { + $data = floatVal(strtr($data, ',', '.')); + return true; + } + + return false; + } + + array_walk($data, function(&$item, $key) { + self::checkNumeric($item); + }); + + return false; // always false for passed arrays + } } ?> diff --git a/index.php b/index.php index 7449fa68..83a77240 100644 --- a/index.php +++ b/index.php @@ -12,6 +12,9 @@ if (!file_exists('config/config.php')) // include all necessities, set up basics require 'includes/kernel.php'; +if ($AoWoWconf['maintenance'] && !User::isInGroup(U_GROUP_EMPLOYEE)) + $smarty->brb(); + switch ($pageCall) { /* called by user */ diff --git a/localization/locale_dede.php b/localization/locale_dede.php index f0e86ea2..23a6a874 100644 --- a/localization/locale_dede.php +++ b/localization/locale_dede.php @@ -12,6 +12,7 @@ if (!defined('AOWOW_REVISION')) $lang = array( // page variables 'main' => array( + 'help' => "Hilfe", 'name' => "Name", 'link' => "Link", 'signIn' => "Anmelden", @@ -82,6 +83,7 @@ $lang = array( 'links' => "Links", 'compare' => "Vergleichen", 'view3D' => "3D-Ansicht", + 'findUpgrades' => "Bessere Gegenstände finden...", // miscTools 'subscribe' => "Abonnieren", @@ -95,6 +97,15 @@ $lang = array( 'englishOnly' => "Diese Seite ist nur in Englisch verfügbar.", // calculators + 'preset' => "Vorlage", + 'addWeight' => "Weitere Gewichtung hinzufügen", + 'createWS' => "Gewichtungsverteilung erstellen", + 'jcGemsOnly' => "JS-exklusive Edelsteine einschließen", + 'cappedHint' => 'Tipp: Entfernt Gewichtungen für gedeckte Werte wie Trefferwertung.', + 'groupBy' => "Ordnen nach", + 'gb' => array( + ['Nichts', 'none'], ['Platz', 'slot'], ['Stufe', 'level'], ['Quelle', 'source'] + ), 'compareTool' => "Gegenstandsvergleichswerkzeug", 'talentCalc' => "Talentrechner", 'petCalc' => "Begleiterrechner", @@ -130,6 +141,8 @@ $lang = array( 'faction' => "Fraktion", 'factions' => "Fraktionen", 'cooldown' => "%s Abklingzeit", + 'item' => "Gegenstand", + 'items' => "Gegenstände", 'itemset' => "Ausrüstungsset", 'itemsets' => "Ausrüstungssets", 'mechanic' => "Auswirkung", @@ -484,6 +497,11 @@ $lang = array( 'randEnchant' => "<Zufällige Verzauberung>", 'readClick' => "<Zum Lesen rechtsklicken>", 'set' => "Set", + '_reqLevel' => "Mindeststufe", + 'slot' => "Platz", + '_quality' => "Qualität", + 'usableBy' => "Benutzbar von", + 'gems' => "Edelsteine", 'socketBonus' => "Sockelbonus", 'socket' => array ( "Metasockel", "Roter Sockel", "Gelber Sockel", "Blauer Sockel", -1 => "Prismatischer Sockel" @@ -528,6 +546,10 @@ $lang = array( 'projectileSubClass' => array( null, null, "Pfeil", "Kugel", null ), + 'elixirType' => [null, "Kampf", "Wächter"], +'cat' => array( + // locale_dede.js zerparsen.. +), 'statType' => array( "Erhöht Euer Mana um %d.", "Erhöht Eure Gesundheit um %d.", diff --git a/localization/locale_enus.php b/localization/locale_enus.php index 4a4e0caf..7f07e283 100644 --- a/localization/locale_enus.php +++ b/localization/locale_enus.php @@ -7,6 +7,7 @@ if (!defined('AOWOW_REVISION')) $lang = array( // page variables 'main' => array( +'help' => "Help", 'name' => "Name", 'link' => "Link", 'signIn' => "Sign in", @@ -77,6 +78,7 @@ $lang = array( 'links' => "Links", 'compare' => "Compare", 'view3D' => "View in 3D", +'findUpgrades' => "Find upgrades...", // misc Tools 'subscribe' => "Subscribe", @@ -90,6 +92,15 @@ $lang = array( 'englishOnly' => "This page is only available in English.", // calculators +'preset' => "Preset", +'addWeight' => "Add another weight", +'createWS' => "Create a weight scale", +'jcGemsOnly' => "Include JC-only gems", +'cappedHint' => 'Tip: Remove weights for capped statistics such as Hit rating.', +'groupBy' => "Group By", +'gb' => array( + ['None', 'none'], ['Slot', 'slot'], ['Level', 'level'], ['Source', 'source'] +), 'compareTool' => "Item Comparison Tool", 'talentCalc' => "Talent Calculator", 'petCalc' => "Hunter Pet Calculator", @@ -125,6 +136,8 @@ $lang = array( 'faction' => "faction", 'factions' => "Factions", 'cooldown' => "%s cooldown", +'item' => "item", +'items' => "Items", 'itemset' => "item Set", 'itemsets' => "Item Sets", 'mechanic' => "Mechanic", @@ -164,7 +177,7 @@ $lang = array( 11 => "Draconic", 12 => "Kalimag", 13 => "Gnomish", 14 => "Troll", 33 => "Gutterspeak", 35 => "Draenei", 36 => "Zombie", 37 => "Gnomish Binary", 38 => "Goblin Binary" ), 'gl' => array(null, "Major", "Minor"), - 'si' => array(-2 => "Horde only", -1 => "Alliance only", null, "Alliance", "Horde", "Both"), + 'si' => array(1 => "Alliance", -1 => "Alliance only", 2 => "Horde", -2 => "Horde only", 3 => "Both"), 'resistances' => array(null, 'Holy Resistance', 'Fire Resistance', 'Nature Resistance', 'Frost Resistance', 'Shadow Resistance', 'Arcane Resistance'), 'dt' => array(null, "Magic", "Curse", "Disease", "Poison", "Stealth", "Invisibility", null, null, "Enrage"), 'sc' => array("Physical", "Holy", "Fire", "Nature", "Frost", "Shadow", "Arcane"), @@ -431,17 +444,16 @@ $lang = array( ), 'armorSubClass' => array( "Miscellaneous", "Cloth Armor", "Leather Armor", "Mail Armor", "Plate Armor", - null, "Shilds", "Librams", "Idols", "Totems", + null, "Shields", "Librams", "Idols", "Totems", "Sigils" ), 'weaponSubClass' => array( - "One-Handed Axes", "Two-Handed Axes", "Bows", "Guns", "One-Handed Maces", - "Two-Handed Maces", "Polearms", "One-Handed Swords", "Two-Handed Swords", null, - "Staves", null, null, "Fist Weapons", "Miscellaneous", - "Daggers", "Thrown", null, "Crossbows", "Wands", - "Fishing Poles" + 15 => "Daggers", 13 => "Fist Weapons", 0 => "One-Handed Axes", 4 => "One-Handed Maces", 7 => "One-Handed Swords", + 6 => "Polearms", 10 => "Staves", 1 => "Two-Handed Axes", 5 => "Two-Handed Maces", 8 => "Two-Handed Swords", + 2 => "Bows", 18 => "Crossbows", 3 => "Guns", 16 => "Thrown", 19 => "Wands", + 10 => "Fishing Poles", 14 => "Miscellaneous" ), - 'subClassMasks' => array( + 'subClassMasks' => array( 0x02A5F3 => 'Melee Weapon', 0x0060 => 'Shield', 0x04000C => 'Ranged Weapon', 0xA091 => 'One-Handed Melee Weapon' ), 'traitShort' => array( @@ -478,6 +490,11 @@ $lang = array( 'randEnchant' => "<Random enchantment>", 'readClick' => "<Right Click To Read>", 'set' => "Set", +'_reqLevel' => "Required level", +'slot' => "Slot", +'_quality' => "Quality", +'usableBy' => "Usable by", +'gems' => "Gems", 'socketBonus' => "Socket Bonus", 'socket' => array( "Meta Socket", "Red Socket", "Yellow Socket", "Blue Socket", -1 => "Prismatic Socket" @@ -496,20 +513,20 @@ $lang = array( ), "bagFamily" => array( "Bag", "Quiver", "Ammo Pouch", "Soul Bag", "Leatherworking Bag", - "Inscription Bag", "Herb Bag", "Enchanting Bag", "Engineering Bag", "Key", + "Inscription Bag", "Herb Bag", "Enchanting Bag", "Engineering Bag", null, /*Key*/ "Gem Bag", "Mining Bag" ), 'inventoryType' => array( null, "Head", "Neck", "Shoulder", "Shirt", "Chest", "Waist", "Legs", "Feet", "Wrist", - "Hands", "Finger", "Trinket", "One-Hand", "Off Hand", + "Hands", "Finger", "Trinket", "One-Hand", "Off Hand", /*Shield*/ "Ranged", "Back", "Two-Hand", "Bag", "Tabard", - "Chest", "Main Hand", "Off Hand", "Held In Off-Hand", "Projectile", - "Thrown", "Ranged", "Quiver", "Relic" + null, /*Robe*/ "Main Hand", "Off Hand", "Held In Off-Hand", "Projectile", + "Thrown", null, /*Ranged2*/ "Quiver", "Relic" ), 'armorSubClass' => array( "Miscellaneous", "Cloth", "Leather", "Mail", "Plate", - null, "Shild", "Libram", "Idol", "Totem", + null, "Shield", "Libram", "Idol", "Totem", "Sigil" ), 'weaponSubClass' => array( @@ -522,7 +539,51 @@ $lang = array( 'projectileSubClass' => array( null, null, "Arrow", "Bullet", null ), - 'statType' => array( +'elixirType' => [null, "Battle", "Guardian"], +'cat' => array( + 2 => "Weapons", // self::$spell['weaponSubClass'] + 4 => array("Armor", array( + 1 => "Cloth Armor", 2 => "Leather Armor", 3 => "Mail Armor", 4 => "Plate Armor", 6 => "Shields", 7 => "Librams", + 8 => "Idols", 9 => "Totems", 10 => "Sigils", -6 => "Cloaks", -5 => "Off-hand Frills", -8 => "Shirts", + -7 => "Tabards", -3 => "Amulets", -2 => "Rings", -4 => "Trinkets", 0 => "Miscellaneous (Armor)", + )), + 1 => array("Containers", array( + 0 => "Bags", 3 => "Enchanting Bags", 4 => "Engineering Bags", 5 => "Gem Bags", 2 => "Herb Bags", 8 => "Inscription Bags", + 7 => "Leatherworking Bags", 6 => "Mining Bags", 1 => "Soul Bags" + )), + 0 => array("Consumables", array( + 7 => "Bandages", 0 => "Consumables", 2 => "Elixirs", 3 => "Flasks", 5 => "Food & Drinks", 6 => "Item Enhancements (Permanent)", + -3 => "Item Enhancements (Temporary)", 1 => "Potions", 4 => "Scrolls", 8 => "Other (Consumables)" + )), + 16 => array("Glyphs", array( + 1 => "Warrior Glyphs", 2 => "Paladin Glyphs", 3 => "Hunter Glyphs", 4 => "Rogue Glyphs", 5 => "Priest Glyphs", 6 => "Death Knight Glyphs", + 7 => "Shaman Glyphs", 8 => "Mage Glyphs", 9 => "Warlock Glyphs", 11 => "Druid Glyphs" + )), + 7 => array("Trade Goods", array( + 14 => "Armor Enchantments", 5 => "Cloth", 3 => "Devices", 10 => "Elemental", 12 => "Enchanting", 2 => "Explosives", + 9 => "Herbs", 4 => "Jewelcrafting", 6 => "Leather", 13 => "Materials", 8 => "Meat", 7 => "Metal & Stone", + 1 => "Parts", 15 => "Weapon Enchantments", 11 => "Other (Trade Goods)" + )), + 6 => ["Projectiles", [ 2 => "Arrows", 3 => "Bullets" ]], + 11 => ["Quivers", [ 2 => "Quivers", 3 => "Ammo Pouches"]], + 9 => array("Recipes", array( + 0 => "Books", 6 => "Alchemy Recipes", 4 => "Blacksmithing Plans", 5 => "Cooking Recipes", 8 => "Enchanting Formulae", 3 => "Engineering Schematics", + 7 => "First Aid Books", 9 => "Fishing Books", 11 => "Inscription Techniques", 10 => "Jewelcrafting Designs", 1 => "Leatherworking Patterns",12 => "Mining Guides", + 2 => "Tailoring Patterns" + )), + 3 => array("Gems", array( + 6 => "Meta Gems", 0 => "Red Gems", 1 => "Blue Gems", 2 => "Yellow Gems", 3 => "Purple Gems", 4 => "Green Gems", + 5 => "Orange Gems", 8 => "Prismatic Gems", 7 => "Simple Gems" + )), + 15 => array("Miscellaneous", array( + -2 => "Armor Tokens", 3 => "Holiday", 0 => "Junk", 1 => "Reagents", 5 => "Mounts", -7 => "Flying Mounts", + 2 => "Small Pets", 4 => "Other (Miscellaneous)" + )), + 10 => "Currency", + 12 => "Quest", + 13 => "Keys", + ), + 'statType' => array( "Increases your Mana by %d.", "Increases your Health by %d.", null, @@ -572,7 +633,7 @@ $lang = array( "Unknown Bonus #%d (%d)", ) ), - 'colon' => ': ' + 'colon' => ': ' ); ?> diff --git a/localization/locale_eses.php b/localization/locale_eses.php index ffa1840a..6cf088a9 100644 --- a/localization/locale_eses.php +++ b/localization/locale_eses.php @@ -12,6 +12,7 @@ if (!defined('AOWOW_REVISION')) $lang = array( // page variables 'main' => array( + 'help' => "Ayuda", 'name' => "Nombre", 'link' => "Enlace", 'signIn' => "Iniciar sesión", @@ -74,6 +75,7 @@ $lang = array( 'links' => "Enlaces", 'compare' => "Comparar", 'view3D' => "Ver en 3D", + 'findUpgrades' => "Buscar mejoras...", // misc Tools 'subscribe' => "Suscribirme", @@ -87,6 +89,15 @@ $lang = array( 'englishOnly' => "Esta página sólo está disponible en inglés.", // calculators + 'preset' => "Predet.", + 'addWeight' => "Añadir otro factor", + 'createWS' => "Crear escala de valores", + 'jcGemsOnly' => "Incluir solo gemas de joyería", + 'cappedHint' => 'Consejo: Elimina escalas para atributos al máximo como el Índice de Golpe.', + 'groupBy' => "Agrupar por", + 'gb' => array( + ['Ninguno', 'none'], ['Casilla', 'slot'], ['Nivel', 'level'], ['Fuente', 'source'] + ), 'compareTool' => "Herramienta de comparación de objetos", 'talentCalc' => "Calculadora de talentos", 'petCalc' => "Calculadora de mascotas", @@ -122,6 +133,8 @@ $lang = array( 'faction' => "facción", 'factions' => "Facciones", 'cooldown' => "%s de reutilización", + 'item' => "objeto", + 'items' => "Objetos", 'itemset' => "conjunto de objetos", 'itemsets' => "Conjuntos de objetos", 'mechanic' => "Mecanica", @@ -437,6 +450,11 @@ $lang = array( 'randEnchant' => "<Encantamiento aleatorio>", 'readClick' => "<Click derecho para leer>", 'set' => "Conjunto", + '_reqLevel' => "Nivel requerido", + 'slot' => "Casilla", + '_quality' => "Calidad", + 'usableBy' => "Usable por", + 'gems' => "Gemas", 'socketBonus' => "Bono de ranura", 'socket' => array( "Ranura meta", "Ranura roja", "Ranura amarilla", "Ranura azul", -1 => "Ranura prismática " @@ -481,6 +499,9 @@ $lang = array( 'projectileSubClass' => array( null, null, "Flecha", "Bala", null ), + 'elixirType' => [null, "Batalla", "Guardián"], +'cat' => array( +), 'statType' => array( "Aumenta tu maná %d p.", "Aumenta tu salud %d p.", diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php index af1ffa58..46fb1336 100644 --- a/localization/locale_frfr.php +++ b/localization/locale_frfr.php @@ -12,6 +12,7 @@ if (!defined('AOWOW_REVISION')) $lang = array( // page variables 'main' => array( + 'help' => "Aide", 'name' => "Nom", 'link' => "Lien", 'signIn' => "S'enregistrer", @@ -74,6 +75,7 @@ $lang = array( 'links' => "Liens", 'compare' => "Comparer", 'view3D' => "Voir en 3D", + 'findUpgrades' => "Trouver des améliorations...", // misc Tools 'subscribe' => "S'abonner", @@ -87,6 +89,15 @@ $lang = array( 'englishOnly' => "Cette page n'est disponible qu'en anglais pour le moment.", // calculators + 'preset' => "Prédéterminée", + 'addWeight' => "Ajouter un autre facteur", + 'createWS' => "Créer une échelle de valeurs", + 'jcGemsOnly' => "Inclure les gemmes de joaillier", + 'cappedHint' => 'Conseil: Enlever un facteur pour les statistiques au maximum tel que le score de touche.', + 'groupBy' => "Groupé par", + 'gb' => array( + ['Aucun', 'none'], ['Emplacement', 'slot'], ['Niveau', 'level'], ['Source', 'source'] + ), 'compareTool' => "Outil de comparaison d'objets", 'talentCalc' => "Calculateur de Talents", 'petCalc' => "Calculateur de familiers", @@ -122,6 +133,8 @@ $lang = array( 'faction' => "faction", 'factions' => "Factions", 'cooldown' => "%s de recharge", + 'item' => "objet", + 'items' => "Objets", 'itemset' => "ensemble d'objets", 'itemsets' => "Ensembles d'objets", 'mechanic' => "Mécanique", @@ -436,6 +449,11 @@ $lang = array( 'randEnchant' => "<Enchantement aléatoire>", 'readClick' => "<Clique Droit pour Lire>", 'set' => "Set", + '_reqLevel' => "Niveau requis", + 'slot' => "Emplacement", + '_quality' => "Qualité", + 'usableBy' => "Utilisable par", + 'gems' => "Gemmes", 'socketBonus' => "Bonus de châsse", 'socket' => array( "Méta-châsse", "Châsse rouge", "Châsse jaune", "Châsse bleue", -1 => "Châsse prismatique" @@ -480,6 +498,9 @@ $lang = array( 'projectileSubClass' => array( null, null, "Flèche", "Balle", null ), + 'elixirType' => [null, "De bataille", "De gardien"], +'cat' => array( +), 'statType' => array( "Augmente vos points de mana de %d.", "Augmente vos points de vie de %d.", diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php index 06e79793..46533e9a 100644 --- a/localization/locale_ruru.php +++ b/localization/locale_ruru.php @@ -12,6 +12,7 @@ if (!defined('AOWOW_REVISION')) $lang = array( // page variables 'main' => array( + 'help' => "Справка", 'name' => "Название", 'link' => "Ссылка", 'signIn' => "Войти", @@ -74,6 +75,7 @@ $lang = array( 'links' => "Ссылки", 'compare' => "Сравнить", 'view3D' => "Посмотреть в 3D", + 'findUpgrades' => "Найти лучше...", // misc Tools 'subscribe' => "Подписаться", @@ -87,6 +89,15 @@ $lang = array( 'englishOnly' => "Эта страница доступна только на английском языке.", // calculators + 'preset' => "Готовая таблица", + 'addWeight' => "Добавить фильтр значимости", + 'createWS' => "Отсортировать по значимости", + 'jcGemsOnly' => "Использовать ювелирские", + 'cappedHint' => 'Подсказка: Удалите характеристики с капом (например, меткость).', + 'groupBy' => "Группировать", + 'gb' => array( + ['Нет', 'none'], ['Слот', 'slot'], ['Уровень', 'level'], ['Источник', 'source'] + ), 'compareTool' => "Инструмент сравнения предметов", 'talentCalc' => "Расчёт талантов", 'petCalc' => "Расчёт умений питомцев", @@ -122,6 +133,8 @@ $lang = array( 'faction' => "фракция", 'factions' => "Фракции", 'cooldown' => "Восстановление: %s", + 'item' => "предмет", + 'items' => "Предметы", 'itemset' => "комплект", 'itemsets' => "Комплекты", 'mechanic' => "Механика", @@ -436,6 +449,11 @@ $lang = array( 'randEnchant' => "<Случайное зачарование>", 'readClick' => "<Щелкните правой кнопкой мыши, чтобы прочитать.>", 'set' => "Набор", + '_reqLevel' => "Требуется уровень", + 'slot' => "Слот", + '_quality' => "Качество", + 'usableBy' => "Используется (кем)", + 'gems' => "Самоцветы", 'socketBonus' => "При соответствии цвета", 'socket' => array( "Особое гнездо", "Красное гнездо", "Желтое гнездо", "Синее гнездо", -1 => "Бесцветное гнездо" @@ -480,6 +498,9 @@ $lang = array( 'projectileSubClass' => array( null, null, "Стрелы", "Пули", null ), + 'elixirType' => [null, "Бой", "Охранный"], +'cat' => array( +), 'statType' => array( "Увеличение запаса маны на %d ед.", "Увеличение максимального запаса здоровья на %d ед.", diff --git a/pages/account.php b/pages/account.php index 5f92411a..07dbd686 100644 --- a/pages/account.php +++ b/pages/account.php @@ -39,22 +39,22 @@ function signin() $remember = $_POST['remember_me'] == 'yes'; // handle login try limitation - $ipBan = DB::Aowow()->selectRow('SELECT ip, count, UNIX_TIMESTAMP(unbanDate) as unbanDate FROM ?_account_bannedIPs WHERE type = 0 AND ip = ?s', + $ipBan = DB::Aowow()->selectRow('SELECT ip, count, UNIX_TIMESTAMP(unbanDate) as unbanDate FROM ?_account_bannedIPs WHERE type = 0 AND ip = ?', $_SERVER['REMOTE_ADDR'] ); if (!$ipBan) // no entry exists; set count to 1 - DB::Aowow()->query('INSERT INTO ?_account_bannedIPs VALUES (?s, 0, 1, FROM_UNIXTIME(?))', + DB::Aowow()->query('INSERT INTO ?_account_bannedIPs VALUES (?, 0, 1, FROM_UNIXTIME(?))', $_SERVER['REMOTE_ADDR'], time() + $GLOBALS['AoWoWconf']['loginFailTime'] ); else if ($ipBan['unbanDate'] < time()) // ip has accumulated counts but time expired; reset count to 1 - DB::Aowow()->query('INSERT IGNORE INTO ?_account_bannedIPs VALUES (?s, 0, 1, ?)', + DB::Aowow()->query('INSERT IGNORE INTO ?_account_bannedIPs VALUES (?, 0, 1, ?)', $_SERVER['REMOTE_ADDR'], time() + $GLOBALS['AoWoWconf']['loginFailTime'] ); else // entry already exists; increment count - DB::Aowow()->query('UPDATE ?_account_bannedIPs SET count = count + 1, unbanDate = FROM_UNIXTIME(?) WHERE ip = ?s', + DB::Aowow()->query('UPDATE ?_account_bannedIPs SET count = count + 1, unbanDate = FROM_UNIXTIME(?) WHERE ip = ?', time() + $GLOBALS['AoWoWconf']['loginFailTime'], $_SERVER['REMOTE_ADDR'] ); @@ -71,7 +71,7 @@ function signin() switch (User::Auth($password)) { case AUTH_OK: - DB::Aowow()->query('DELETE FROM ?_account_bannedIPs WHERE type = 0 AND ip = ?s', + DB::Aowow()->query('DELETE FROM ?_account_bannedIPs WHERE type = 0 AND ip = ?', $_SERVER['REMOTE_ADDR'] ); DB::Aowow()->query('UPDATE ?_account SET lastLogin = FROM_UNIXTIME(?), timeout = FROM_UNIXTIME(?) WHERE id = ?', @@ -97,11 +97,13 @@ function signin() function signup() { + global $AoWoWconf, $smarty; + /* $username = Get(GET_STRING, 'username', 'POST'); $password = Get(GET_STRING, 'password', 'POST'); - $pwd2 = Get(GET_STRING, 'password2', 'POST'); - $email = Get(GET_STRING, 'email', 'POST'); + $pwd2 = Get(GET_STRING, 'password2', 'POST'); + $email = Get(GET_STRING, 'email', 'POST'); $remember = Get(GET_BOOL, 'remember_me', 'POST'); if($password != $pwd2) @@ -203,8 +205,9 @@ function signup() return; } */ + // Account creation - if ($_REQUEST['account'] == 'signup' && isset($_POST['username']) && isset($_POST['password']) && isset($_POST['c_password']) && $AoWoWconf['register'] == true) + if (isset($_POST['username']) && isset($_POST['password']) && isset($_POST['c_password']) && $AoWoWconf['register'] == true) { // password mismatch if ($_POST['password'] != $_POST['c_password']) @@ -212,7 +215,7 @@ function signup() else { // AccName already in use - if ($rDB->selectCell('SELECT Count(id) FROM aowow_account WHERE username=? LIMIT 1', $_POST['username']) >= 1) + if (DB::Aowow()->selectCell('SELECT 1 FROM aowow_account WHERE user = ? LIMIT 1', $_POST['username'])) $smarty->assign('signup_error', Lang::$account['nameInUse']); else { @@ -257,7 +260,7 @@ $smarty->updatePageVars(array( ), )); -$smarty->assign('lang', array_merge(Lang::$main, Lang::$account)); +$smarty->assign('lang', array_merge(Lang::$main, Lang::$account, ['colon' => Lang::$colon])); if (User::$id) { @@ -269,7 +272,7 @@ if (User::$id) $next = !empty($next[1]) ? '?'.$next[1] : '.'; header('Location: '.$next); case 'weightscales': - $post = Util::sqlEscape($_POST); + $post = Util::sqlEscape($_POST, true); if (isset($post['save'])) { @@ -282,7 +285,7 @@ if (User::$id) die('0'); } - if (DB::Aowow()->query('REPLACE INTO ?_account_weightscales VALUES (?d, ?d, ?s, ?s)', $post['id'], User::$id, $post['name'], $post['scale'])) + if (DB::Aowow()->query('REPLACE INTO ?_account_weightscales VALUES (?d, ?d, ?, ?)', intVal($post['id']), User::$id, $post['name'], $post['scale'])) die((string)$post['id']); else die('0'); diff --git a/pages/achievement.php b/pages/achievement.php index 61f156c3..fbebcd5b 100644 --- a/pages/achievement.php +++ b/pages/achievement.php @@ -92,7 +92,19 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) 'path' => $tmpPath, 'infobox' => array_merge($infobox, Lang::getInfoBoxForFlags($acv->getField('cuFlags'))), 'relTabs' => [], - 'page' => $acv->getDetailedData()[$_id] + 'page' => array( + 'name' => $acv->getField('name', true), + 'description' => $acv->getField('description', true), + 'points' => $acv->getField('points'), + 'iconname' => $acv->getField('iconString'), + 'count' => $acv->getField('reqCriteriaCount'), + 'reward' => $acv->getField('reward', true), + 'nCriteria' => count($acv->getCriteria()), + 'titleReward' => [], + 'itemReward' => [], + 'criteria' => [], + 'icons' => [] + ) ); // listview: "see also" @@ -137,17 +149,14 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) } // create rewards - $pageData['page']['titleReward'] = []; - $pageData['page']['itemReward'] = []; - if ($foo = $acv->getField('rewards')[TYPE_ITEM]) { - $bar = new ItemList(array(['i.entry', $foo])); + $bar = new ItemList(array(['i.id', $foo])); foreach ($bar->iterate() as $__) { $pageData['page']['itemReward'][$bar->id] = array( 'name' => $bar->getField('name', true), - 'quality' => $bar->getField('Quality') + 'quality' => $bar->getField('quality') ); } } @@ -163,14 +172,10 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) // ACHIEVEMENT CRITERIA // ***** - $pageData['page']['criteria'] = []; - $iconId = 1; - $tmp_arr = []; - $pageData['page']['icons'] = []; - $pageData['page']['total_criteria'] = count($acv->getCriteria()); + $iconId = 1; + $rightCol = []; - $i = 0; // stupid uninitialized iterator..... - foreach ($acv->getCriteria() as $crt) + foreach ($acv->getCriteria() as $i => $crt) { // hide hidden criteria for regular users (really do..?) // if (($crt['complete_flags'] & ACHIEVEMENT_CRITERIA_FLAG_HIDDEN) && User::$perms > 0) @@ -296,12 +301,12 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) case ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM: case ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM: case ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM: - $crtItm = new ItemList(array(['id', $obj])); + $crtItm = new ItemList(array(['i.id', $obj])); $text = $crtName ? $crtName : $crtItm->getField('name', true); $tmp['link'] = array( 'href' => '?item='.$obj, 'text' => $text, - 'quality' => $crtItm->getField('Quality'), + 'quality' => $crtItm->getField('quality'), 'count' => $qty, ); $crtItm->addGlobalsToJscript($smarty); @@ -337,15 +342,15 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) break; } // If the right column - if ($i++ % 2) - $tmp_arr[] = $tmp; - else + if ($i % 2) $pageData['page']['criteria'][] = $tmp; - + else + $rightCol[] = $tmp; } + // If you found the second column - merge data from it to the end of the main body - if ($tmp_arr) - $pageData['page']['criteria'] = array_merge($pageData['page']['criteria'], $tmp_arr); + if ($rightCol) + $pageData['page']['criteria'] = array_merge($pageData['page']['criteria'], $rightCol); // ***** // ACHIEVEMENT CHAIN @@ -403,7 +408,7 @@ $smarty->updatePageVars(array( 'typeId' => $_id )); $smarty->assign('community', CommunityContent::getAll(TYPE_ACHIEVEMENT, $_id)); // comments, screenshots, videos -$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$achievement)); +$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$achievement, ['colon' => Lang::$colon])); $smarty->assign('lvData', $pageData); // load the page diff --git a/pages/achievements.php b/pages/achievements.php index 60a44516..56245248 100644 --- a/pages/achievements.php +++ b/pages/achievements.php @@ -4,8 +4,6 @@ if (!defined('AOWOW_REVISION')) die('illegal access'); -require 'includes/class.filter.php'; - $cats = Util::extractURLParams($pageParam); $path = [0, 9]; $title = []; @@ -56,9 +54,9 @@ if (!$smarty->loadCache($cacheKey, $pageData, $filter)) } // recreate form selection + $filter = array_merge($acvList->filterGetForm('form'), $filter); $filter['query'] = isset($_GET['filter']) ? $_GET['filter'] : NULL; - $filter['setCr'] = $acvList->filterGetSetCriteria(); - $filter = array_merge($acvList->filterGetForm(), $filter); + $filter['fi'] = $acvList->filterGetForm(); // create page title and path if (is_array($cats)) @@ -88,6 +86,9 @@ if (!$smarty->loadCache($cacheKey, $pageData, $filter)) if ($acvList->hasDiffFields(['category'])) $pageData['params']['visibleCols'] = "$['category']"; + if (!empty($filter['fi']['extraCols'])) + $pageData['params']['extraCols'] = '$fi_getExtraCols(fi_extraCols, 0, 0)'; + // create note if search limit was exceeded if ($acvList->getMatches() > $AoWoWconf['sqlLimit']) { @@ -117,7 +118,7 @@ $smarty->updatePageVars(array( ) )); $smarty->assign('filter', $filter); -$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$achievement)); +$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$achievement, ['colon' => Lang::$colon])); $smarty->assign('lvData', $pageData); // load the page diff --git a/pages/class.php b/pages/class.php index 7a41b49c..865e9a70 100644 --- a/pages/class.php +++ b/pages/class.php @@ -104,9 +104,9 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) // Items $conditions = array( - ['allowableClass', 0, '>'], - ['allowableClass', $_mask, '&'], - [['allowableClass', CLASS_MASK_ALL, '&'], CLASS_MASK_ALL, '!'], + ['requiredClass', 0, '>'], + ['requiredClass', $_mask, '&'], + [['requiredClass', CLASS_MASK_ALL, '&'], CLASS_MASK_ALL, '!'], ['itemset', 0], // hmm, do or dont..? 0 ); @@ -114,7 +114,7 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) $items = new ItemList($conditions); $items->addGlobalsToJscript($smarty); - if (!$items->hasDiffFields(['AllowableRace'])) + if (!$items->hasDiffFields(['requiredRace'])) $hidden = "$['side']"; $pageData['relTabs'][] = array( @@ -334,7 +334,7 @@ $smarty->updatePageVars(array( ) )); $smarty->assign('community', CommunityContent::getAll(TYPE_CLASS, $_id)); // comments, screenshots, videos -$smarty->assign('lang', array_merge(Lang::$main, Lang::$talent)); +$smarty->assign('lang', Lang::$main); $smarty->assign('lvData', $pageData); // load the page diff --git a/pages/compare.php b/pages/compare.php index 792786b7..5c0131b6 100644 --- a/pages/compare.php +++ b/pages/compare.php @@ -35,7 +35,7 @@ if ($compareString) } $pageData['summary'] = json_encode($outSet, JSON_NUMERIC_CHECK); - $iList = new ItemList(array(['i.entry', $items])); + $iList = new ItemList(array(['i.id', $items])); $data = $iList->getListviewData(ITEMINFO_SUBITEMS | ITEMINFO_JSON); foreach ($iList->iterate() as $itemId => $__) @@ -46,8 +46,8 @@ if ($compareString) $pageData['items'][] = [ $itemId, Util::jsEscape($iList->getField('name', true)), - $iList->getField('Quality'), - $iList->getField('icon'), + $iList->getField('quality'), + $iList->getField('iconString'), json_encode($data[$itemId], JSON_NUMERIC_CHECK) ]; } diff --git a/pages/events.php b/pages/events.php index 48e74bef..4a3d64a1 100644 --- a/pages/events.php +++ b/pages/events.php @@ -60,7 +60,7 @@ if (!$smarty->loadCache($cacheKey, $pageData)) $pageData['listviews'][] = array( 'file' => 'calendar', - 'data' => $events->getListviewData(), + 'data' => array_filter($events->getListviewData(), function($x) {return $x['id'] > 0;}), 'params' => array( 'tabs' => '$myTabs', 'hideCount' => 1 diff --git a/pages/item.php b/pages/item.php index c471ea30..0a37daec 100644 --- a/pages/item.php +++ b/pages/item.php @@ -49,15 +49,15 @@ if (isset($_GET['power'])) // output json for tooltips if (!$smarty->loadCache($cacheKeyTooltip, $x)) { - $item = new ItemList(array(['i.entry', $_id])); + $item = new ItemList(array(['i.id', $_id])); if ($item->error) die('$WowheadPower.registerItem(\''.$itemString.'\', '.User::$localeId.', {})'); $item->renderTooltip($enh); $x .= '$WowheadPower.registerItem(\''.$itemString.'\', '.User::$localeId.", {\n"; $x .= "\tname_".User::$localeString.": '".Util::jsEscape($item->getField('name', true))."',\n"; - $x .= "\tquality: ".$item->getField('Quality').",\n"; - $x .= "\ticon: '".urlencode($item->getField('icon'))."',\n"; + $x .= "\tquality: ".$item->getField('quality').",\n"; + $x .= "\ticon: '".urlencode($item->getField('iconString'))."',\n"; $x .= "\ttooltip_".User::$localeString.": '".Util::jsEscape($item->tooltip[$_id])."'\n"; $x .= "});"; @@ -69,15 +69,22 @@ if (isset($_GET['power'])) // regular page if (!$smarty->loadCache($cacheKeyPage, $item)) { - $item = new ItemList(array(['i.entry', $_id])); + $item = new ItemList(array(['i.id', $_id])); if ($item->error) $smarty->notFound(Lang::$game['item']); + $pageData = array( + 'infobox' => [], + 'relTabs' => [], + 'tooltip' => $item->renderTooltip([], false), + 'page' => $item->getDetailPageData(), + 'path' => [0, 0, $item->getField('classs'), $item->getField('subClass')], + 'title' => [Lang::$game['item'], $item->getField('name', true)], + 'pagetext' => false, // Books + 'buttons' => in_array($item->getField('class'), [ITEM_CLASS_WEAPON, ITEM_CLASS_GEM, ITEM_CLASS_ARMOR]), + ); - // not yet implemented -> chicken out - $smarty->error(); - /* @@ -110,731 +117,85 @@ if (!$smarty->loadCache($cacheKeyPage, $item))
{#Quick_Facts#}
*/ - unset($item); + /********/ + /* TABS */ + /********/ - // Информация о вещи... - $item = iteminfo($_id, 1); - $path = [0, 0, $item['classs'], $item['subclass'], $item['type']]; + // dropped by creature - // Поиск мобов с которых эта вещь лутится - $drops_cr = drop('creature_loot_template', $item['entry']); - if($drops_cr) - { - $item['droppedby'] = []; - foreach($drops_cr as $lootid => $drop) - { - $rows = $DB->select(' - SELECT c.?#, c.entry - { - , l.name_loc?d AS name_loc - , l.subname_loc?d AS subname_loc - } - FROM ?_factiontemplate, creature_template c - { LEFT JOIN (locales_creature l) ON l.entry=c.entry AND ? } - WHERE - lootid=?d - AND factiontemplateID=faction_A - ', - $npc_cols[0], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $lootid - ); - foreach($rows as $row) - $item['droppedby'][] = array_merge(creatureinfo2($row), $drop); - } - unset($rows); - unset($lootid); - unset($drop); - } - unset($drops_cr); + // GO-loot + // diff between gathered from / mined from / contained in + // foreach($rows as $row) + // { + // // Залежи руды + // if($row['lockproperties1'] == LOCK_PROPERTIES_MINING) + // $item['minedfromobject'][] = array_merge(objectinfo2($row), $drop); + // // Собирается с трав + // elseif($row['lockproperties1'] == LOCK_PROPERTIES_HERBALISM) + // $item['gatheredfromobject'][] = array_merge(objectinfo2($row), $drop); + // // Сундуки + // else + // $item['containedinobject'][] = array_merge(objectinfo2($row), $drop); + // } - // Поиск объектов, из которых лутится эта вещь - $drops_go = drop('gameobject_loot_template', $item['entry']); - if($drops_go) - { - $item['containedinobject'] = []; - $item['minedfromobject'] = []; - $item['gatheredfromobject'] = []; - foreach($drops_go as $lootid => $drop) - { - // Сундуки - $rows = $DB->select(' - SELECT g.entry, g.name, g.type, a.lockproperties1 {, l.name_loc?d AS name_loc} - FROM gameobject_template g LEFT JOIN ?_lock a ON a.lockID=g.data0 - { LEFT JOIN (locales_gameobject l) ON l.entry=g.entry AND ? } - WHERE - g.data1=?d - AND g.type IN (?d, ?d) - ', - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $lootid, - GAMEOBJECT_TYPE_CHEST, - GAMEOBJECT_TYPE_FISHINGHOLE - ); - foreach($rows as $row) - { - // Залежи руды - if($row['lockproperties1'] == LOCK_PROPERTIES_MINING) - $item['minedfromobject'][] = array_merge(objectinfo2($row), $drop); - // Собирается с трав - elseif($row['lockproperties1'] == LOCK_PROPERTIES_HERBALISM) - $item['gatheredfromobject'][] = array_merge(objectinfo2($row), $drop); - // Сундуки - else - $item['containedinobject'][] = array_merge(objectinfo2($row), $drop); - } - } + // sold by [consult itemExtendedCost] - if(!$item['containedinobject']) - unset($item['containedinobject']); - if(!$item['minedfromobject']) - unset($item['minedfromobject']); - if(!$item['gatheredfromobject']) - unset($item['gatheredfromobject']); + // Objective of (quest) - unset($rows); - } - unset($drops_go); + // provided for (quest) - // Поиск вендеров, которые эту вещь продают - $rows_soldby = $DB->select(' - SELECT ?#, c.entry, v.ExtendedCost, v.maxcount AS stock - { - , l.name_loc?d AS name_loc - , l.subname_loc?d AS subname_loc - } - FROM npc_vendor v, ?_factiontemplate, creature_template c - { LEFT JOIN (locales_creature l) ON l.entry=c.entry AND ? } - WHERE - v.item=?d - AND c.entry=v.entry - AND factiontemplateID=faction_A - ORDER BY 1 DESC, 2 DESC - ', - $npc_cols['0'], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $item['entry'] - ); - if($rows_soldby) - { - $item['soldby'] = []; - foreach($rows_soldby as $i => $row) - { - $item['soldby'][$i] = []; - $item['soldby'][$i] = creatureinfo2($row); - $item['soldby'][$i]['stock'] = ($row['stock'] == 0 ? -1 : $row['stock']); - if($row['ExtendedCost']) - { - $item['soldby'][$i]['cost'] = []; - $extcost = $DB->selectRow('SELECT * FROM ?_item_extended_cost WHERE extendedcostID=?d LIMIT 1', abs($row['ExtendedCost'])); - if($extcost['reqhonorpoints']) - $item['soldby'][$i]['cost']['honor'] = ($row['A'] == 1 ? 1 : -1) * $extcost['reqhonorpoints']; - if($extcost['reqarenapoints']) - $item['soldby'][$i]['cost']['arena'] = $extcost['reqarenapoints']; - $item['soldby'][$i]['cost']['items'] = []; - for ($j=1;$j<=5;$j++) - if(($extcost['reqitem'.$j]>0) and ($extcost['reqitemcount'.$j]>0)) - { - allitemsinfo($extcost['reqitem'.$j], 0); - $item['soldby'][$i]['cost']['items'][] = array('item' => $extcost['reqitem'.$j], 'count' => $extcost['reqitemcount'.$j]); - } - } - else - $item['soldby'][$i]['cost']['money'] = $item['BuyPrice']; - } - unset($extcost); - } - unset($rows_soldby); + // reward of (quest) - // Поиск квестов, для выполнения которых нужен этот предмет - $rows_qr = $DB->select(' - SELECT q.?# {, l.Title_loc?d AS Title_loc} - FROM quest_template q - { LEFT JOIN (locales_quest l) ON l.entry=q.entry AND ? } - WHERE - ReqItemId1=?d - OR ReqItemId2=?d - OR ReqItemId3=?d - OR ReqItemId4=?d - ', - $quest_cols[2], - $_SESSION['locale'] > 0 ? $_SESSION['locale'] : DBSIMPLE_SKIP, - $_SESSION['locale'] > 0 ? 1 : DBSIMPLE_SKIP, - $item['entry'], $item['entry'], $item['entry'], $item['entry'] - ); - if($rows_qr) - { - $item['objectiveof'] = []; - foreach($rows_qr as $row) - $item['objectiveof'][] = GetQuestInfo($row, 0xFFFFFF); - } - unset($rows_qr); + // reward of (quest) [from mail-loot] - // Поиск квестов, при взятии которых выдается этот предмет - $rows_qp = $DB->select(' - SELECT q.?# {, l.Title_loc?d AS Title_loc} - FROM quest_template q - { LEFT JOIN (locales_quest l) ON l.entry=q.entry AND ? } - WHERE SrcItemId=?d - ', - $quest_cols[2], - $_SESSION['locale'] > 0 ? $_SESSION['locale'] : DBSIMPLE_SKIP, - $_SESSION['locale'] > 0 ? 1 : DBSIMPLE_SKIP, - $item['entry'] - ); - if($rows_qp) - { - $item['providedfor'] = []; - foreach($rows_qp as $row) - $item['providedfor'][] = GetQuestInfo($row, 0xFFFFFF); - } - unset($rows_qp); + // contained in (item) [item_loot] - // Поиск квестов, наградой за выполнение которых, является этот предмет - $rows_qrw = $DB->select(' - SELECT q.?# {, l.Title_loc?d AS Title_loc} - FROM quest_template q - { LEFT JOIN (locales_quest l) ON l.entry=q.entry AND ? } - WHERE - RewItemId1=?d - OR RewItemId2=?d - OR RewItemId3=?d - OR RewItemId4=?d - OR RewChoiceItemId1=?d - OR RewChoiceItemId2=?d - OR RewChoiceItemId3=?d - OR RewChoiceItemId4=?d - OR RewChoiceItemId5=?d - OR RewChoiceItemId6=?d - ', - $quest_cols[2], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $item['entry'], $item['entry'], $item['entry'], $item['entry'], $item['entry'], - $item['entry'], $item['entry'], $item['entry'], $item['entry'], $item['entry'] - ); - if($rows_qrw) - { - $item['rewardof'] = []; - foreach($rows_qrw as $row) - $item['rewardof'][] = GetQuestInfo($row, 0xFFFFFF); - } - unset($rows_qrw); + // contains [item_loot] - // Поиск квестов, в награду за выполнение которых итем присылают почтой - $drops_qm = drop('mail_loot_template', $item['entry']); - if($drops_qm) - { - foreach($drops_qm as $lootid => $row) - { - $rows_qm = $DB->select(' - SELECT q.?# {, l.Title_loc?d AS Title_loc} - FROM quest_template q - { LEFT JOIN (locales_quest l) ON l.entry=q.entry AND ? } - WHERE RewMailTemplateId=?d - ', - $quest_cols[2], - $_SESSION['locale'] > 0 ? $_SESSION['locale'] : DBSIMPLE_SKIP, - $_SESSION['locale'] > 0 ? 1 : DBSIMPLE_SKIP, - $lootid - ); - if($rows_qm) - { - if (!isset($item['rewardof'])) - $item['rewardof'] = []; - foreach($rows_qm as $row) - $item['rewardof'][] = GetQuestInfo($row, 0xFFFFFF); - } - unset($rows_qm); - } - } - unset($drops_qm); + // pickpocketed from - // Поиск вещей, в которых находятся эти вещи - $drops_cii = drop('item_loot_template', $item['entry']); - if($drops_cii) - { - $item['containedinitem'] = []; - foreach($drops_cii as $lootid => $drop) - { - $rows = $DB->select(' - SELECT c.?#, c.entry, maxcount - { , l.name_loc?d AS name_loc } - FROM ?_icons, item_template c - { LEFT JOIN (locales_item l) ON l.entry=c.entry AND ? } - WHERE - c.entry=?d - AND id=displayid - ', - $item_cols[2], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $lootid - ); - foreach($rows as $row) - $item['containedinitem'][] = array_merge(iteminfo2($row, 0), $drop); - } - unset($drops_cii); - unset($rows); - unset($lootid); - unset($drop); - } + // skinning_loot skinned from & salvaged from - // Какие вещи содержатся в этой вещи - if(!($item['contains'] = loot('item_loot_template', $item['entry']))) - unset($item['contains']); + // prospecting & prospected from + // milling & milled from - // Поиск созданий, у которых воруется вещь - $drops_pp = drop('pickpocketing_loot_template', $item['entry']); - if($drops_pp) - { - $item['pickpocketingloot'] = []; - foreach($drops_pp as $lootid => $drop) - { - $rows = $DB->select(' - SELECT c.?#, c.entry - { - , l.name_loc?d AS name_loc - , l.subname_loc?d AS subname_loc - } - FROM ?_factiontemplate, creature_template c - { LEFT JOIN (locales_creature l) ON l.entry=c.entry AND ? } - WHERE - pickpocketloot=?d - AND factiontemplateID=faction_A - ', - $npc_cols[0], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $lootid - ); - foreach($rows as $row) - $item['pickpocketingloot'][] = array_merge(creatureinfo2($row), $drop); - } - unset($rows); - unset($lootid); - unset($drop); - } - unset($drops_pp); + // disentchanting from & to - // Поиск созданий, с которых сдираеццо эта шкура - $drops_sk = drop('skinning_loot_template', $item['entry']); - if($drops_sk) - { - $item['skinnedfrom'] = []; - foreach($drops_sk as $lootid => $drop) - { - $rows = $DB->select(' - SELECT c.?#, c.entry - { - , l.name_loc?d AS name_loc - , l.subname_loc?d AS subname_loc - } - FROM ?_factiontemplate, creature_template c - { LEFT JOIN (locales_creature l) ON l.entry=c.entry AND ? } - WHERE - skinloot=?d - AND factiontemplateID=faction_A - ', - $npc_cols[0], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $lootid - ); - foreach($rows as $row) - $item['skinnedfrom'][] = array_merge(creatureinfo2($row), $drop); - } - unset($rows); - unset($lootid); - unset($drop); - } - unset($drops_sk); + // can be placed in + // if($item['BagFamily'] == 256) + // { + // // Если это ключ + // $item['key'] = true; + // } - // Перерабатывается в: - if(!($item['prospecting'] = loot('prospecting_loot_template', $item['entry']))) - unset($item['prospecting']); + // reagent for - // Поиск вещей, из которых перерабатывается эта вещь - $drops_pr = drop('prospecting_loot_template', $item['entry']); - if($drops_pr) - { - $item['prospectingloot'] = []; - foreach($drops_pr as $lootid => $drop) - { - $rows = $DB->select(' - SELECT c.?#, c.entry, maxcount - { - , l.name_loc?d AS name_loc - } - FROM ?_icons, item_template c - { LEFT JOIN (locales_item l) ON l.entry=c.entry AND ? } - WHERE - c.entry = ?d - AND id = displayid - ', - $item_cols[2], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $lootid - ); - foreach($rows as $row) - $item['prospectingloot'][] = array_merge(iteminfo2($row, 0), $drop); - } - unset($rows); - unset($lootid); - unset($drop); - } - unset($drops_pr); + // created by [spell] - // Дизенчантитcя в: - if(!($item['disenchanting'] = loot('disenchant_loot_template', $item['DisenchantID']))) - unset($item['disenchanting']); + // fished in - // Получается дизэнчантом из.. - $drops_de = drop('disenchant_loot_template', $item['entry']); - if($drops_de) - { - $item['disenchantedfrom'] = []; - foreach($drops_de as $lootid => $drop) - { - $rows = $DB->select(' - SELECT c.?#, c.entry, maxcount - { - , l.name_loc?d AS name_loc - } - FROM ?_icons, item_template c - { LEFT JOIN (locales_item l) ON l.entry=c.entry AND ? } - WHERE - DisenchantID=?d - AND id=displayid - ', - $item_cols[2], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $lootid - ); - foreach($rows as $row) - $item['disenchantedfrom'][] = array_merge(iteminfo2($row, 0), $drop); - } - unset($rows); - unset($lootid); - unset($drop); - } - unset($drops_de); + // currency for - // Поиск сумок в которые эту вещь можно положить - if($item['BagFamily'] == 256) - { - // Если это ключ - $item['key'] = true; - } - elseif($item['BagFamily'] > 0 and $item['ContainerSlots'] == 0) - { - $rows_cpi = $DB->select(' - SELECT c.?#, c.entry, maxcount - { - , l.name_loc?d AS name_loc - } - FROM ?_icons, item_template c - { LEFT JOIN (locales_item l) ON l.entry=c.entry AND ? } - WHERE - BagFamily=?d - AND ContainerSlots>0 - AND id=displayid - ', - $item_cols[2], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $item['BagFamily'] - ); - if($rows_cpi) - { - $item['canbeplacedin'] = []; - foreach($rows_cpi as $row) - $item['canbeplacedin'][] = iteminfo2($row, 0); - } - unset($rows_cpi); - } + // [spell_loot_template] ehh... - // Реагент для... - $rows_r = $DB->select(' - SELECT ?#, spellID - FROM ?_spell s, ?_spellicons i - WHERE - (( reagent1=?d - OR reagent2=?d - OR reagent3=?d - OR reagent4=?d - OR reagent5=?d - OR reagent6=?d - OR reagent7=?d - OR reagent8=?d - ) AND ( i.id=s.spellicon)) - ', - $spell_cols[2], - $item['entry'], $item['entry'], $item['entry'], $item['entry'], - $item['entry'], $item['entry'], $item['entry'], $item['entry'] - ); - if($rows_r) - { - $item['reagentfor'] = []; - $quality = 1; - foreach($rows_r as $i=>$row) - { - $item['reagentfor'][$i] = []; - $item['reagentfor'][$i]['entry'] = $row['spellID']; - $item['reagentfor'][$i]['name'] = $row['spellname_loc'.$_SESSION['locale']]; - $item['reagentfor'][$i]['school'] = $row['resistancesID']; - $item['reagentfor'][$i]['level'] = $row['levelspell']; - $item['reagentfor'][$i]['quality'] = '@'; - for ($j=1;$j<=8;$j++) - if($row['reagent'.$j]) - { - $item['reagentfor'][$i]['reagents'][]['entry'] = $row['reagent'.$j]; - $item['reagentfor'][$i]['reagents'][count($item['reagentfor'][$i]['reagents'])-1]['count'] = $row['reagentcount'.$j]; - allitemsinfo($row['reagent'.$j], 0); - } - for ($j=1;$j<=3;$j++) - if($row['effect'.$j.'itemtype']) - { - $item['reagentfor'][$i]['creates'][]['entry'] = $row['effect'.$j.'itemtype']; - $item['reagentfor'][$i]['creates'][count($item['reagentfor'][$i]['creates'])-1]['count'] = 1 + $row['effect'.$j.'BasePoints']; - allitemsinfo($row['effect'.$j.'itemtype'], 0); - @$item['reagentfor'][$i]['quality'] = 7 - $allitems[$row['effect'.$j.'itemtype']]['quality']; - } - // Добавляем в таблицу спеллов - allspellsinfo2($row); - } - unset($quality); - } - unset($rows_r); + // criteria of + // array(ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM, ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM, ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM), - // Создается из... - $rows_cf = $DB->select(' - SELECT ?#, s.spellID - FROM ?_spell s, ?_spellicons i - WHERE - ((s.effect1itemtype=?d - OR s.effect2itemtype=?d - OR s.effect3itemtype=?) - AND (i.id = s.spellicon)) - ', - $spell_cols[2], - $item['entry'], $item['entry'], $item['entry'] - ); - if($rows_cf) - { - $item['createdfrom'] = []; - foreach($rows_cf as $row) - { - $skillrow = $DB->selectRow(' - SELECT skillID, min_value, max_value - FROM ?_skill_line_ability - WHERE spellID=?d - LIMIT 1 - ', - $row['spellID'] - ); - $item['createdfrom'][] = spellinfo2(array_merge($row, $skillrow)); - } - unset($skillrow); - } - unset($rows_cf); + // teaches - // Ловится в ... - $drops_fi = drop('fishing_loot_template', $item['entry']); - if($drops_fi) - { - $item['fishedin'] = []; - foreach($drops_fi as $lootid => $drop) - { - // Обычные локации - $row = $DB->selectRow(' - SELECT name_loc'.$_SESSION['locale'].' AS name, areatableID as id - FROM ?_zones - WHERE - areatableID=?d - /*AND (x_min!=0 AND x_max!=0 AND y_min!=0 AND y_max!=0)*/ - LIMIT 1 - ', - $lootid - ); - if($row) - { - $item['fishedin'][] = array_merge($row, $drop); - } - else - { - // Инсты - $row = $DB->selectRow(' - SELECT name_loc'.$_SESSION['locale'].' AS name, mapID as id - FROM ?_zones - WHERE - areatableID=?d - LIMIT 1 - ', - $lootid - ); - if($row) - $item['fishedin'][] = array_merge($row, $drop); - } - } - unset($row); - unset($num); - } - unset($drops_fi); - - // Размалывается в - if(!$item['milling'] = loot('milling_loot_template', $item['entry'])) - unset($item['milling']); - - // Получается размалыванием из - $drops_mi = drop('milling_loot_template', $item['entry']); - if($drops_mi) - { - $item['milledfrom'] = []; - foreach($drops_mi as $lootid => $drop) - { - $rows = $DB->select(' - SELECT c.?#, c.entry, maxcount - { - , l.name_loc?d AS name_loc - } - FROM ?_icons, item_template c - { LEFT JOIN (locales_item l) ON l.entry=c.entry AND ? } - WHERE - c.entry=?d - AND id=displayid - ', - $item_cols[2], - ($_SESSION['locale']>0)? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale']>0)? 1: DBSIMPLE_SKIP, - $lootid - ); - foreach($rows as $row) - $item['milledfrom'][] = array_merge(iteminfo2($row, 0), $drop); - } - unset($rows); - unset($lootid); - unset($drop); - } - unset($drops_mi); - - // Валюта для... - $rows_cf = $DB->select(' - SELECT ?#, i.entry, i.maxcount, n.`maxcount` as `drop-maxcount`, n.ExtendedCost, - {l.name_loc?d AS `name_loc`,} - reqitem1, reqitem2, reqitem3, reqitem4, reqitem5, - reqitemcount1, reqitemcount2, reqitemcount3, reqitemcount4, reqitemcount5, - reqhonorpoints, reqarenapoints - FROM npc_vendor n, ?_icons, ?_item_extended_cost iec, item_template i - {LEFT JOIN (locales_item l) ON l.entry=i.entry AND ?d} - WHERE (iec.reqitem1=? - OR iec.reqitem2=? - OR iec.reqitem3=? - OR iec.reqitem4=? - OR iec.reqitem5=?) - AND iec.extendedcostID=ABS(n.ExtendedCost) - AND i.entry=n.item - AND id=i.displayid - ', - $item_cols[2], - ($_SESSION['locale'])? $_SESSION['locale']: DBSIMPLE_SKIP, - ($_SESSION['locale'])? 1: DBSIMPLE_SKIP, - $item['entry'], $item['entry'], $item['entry'], $item['entry'], $item['entry'] - ); - if($rows_cf) - { - $item['currencyfor'] = []; - foreach($rows_cf as $row) - { - $_id=$row['entry']; - $item['currencyfor'][$_id] = []; - $item['currencyfor'][$_id] = iteminfo2($row); - $item['currencyfor'][$_id]['maxcount'] = $row['drop-maxcount']; - $item['currencyfor'][$_id]['cost'] = []; - if($row['BuyPrice']>0) - $npc['sells'][$_id]['cost']['money'] = $row['BuyPrice']; - - if($row['reqhonorpoints']>0) - $item['currencyfor'][$_id]['cost']['honor'] =/* ($row['A']==1?1:-1)* */$row['reqhonorpoints']; //FIXME_BUG - if($row['reqarenapoints']>0) - $item['currencyfor'][$_id]['cost']['arena'] = $row['reqarenapoints']; - $item['currencyfor'][$_id]['cost']['items'] = []; - for($j=1; $j<=5; $j++) - if(($row['reqitem'.$j]>0) and ($row['reqitemcount'.$j]>0)) - { - allitemsinfo($row['reqitem'.$j], 0); - $item['currencyfor'][$_id]['cost']['items'][] = array( - 'item' => $row['reqitem'.$j], - 'count' => $row['reqitemcount'.$j] - ); - } - } - } - unset($rows_cf); - - // Добывается из спелла - $drops_sp = drop('spell_loot_template', $item['entry']); - if($drops_sp) - { - $item['containedinspell'] = []; - foreach($drops_sp as $lootid => $drop) - { - $rows = $DB->select(' - SELECT s.?#, s.spellID - FROM ?_spell s, ?_spellicons i - WHERE - s.spellID = ?d - AND i.id = s.spellicon - ', - $spell_cols[2], - $lootid - ); - foreach($rows as $row) - $item['containedinspell'][] = array_merge(spellinfo2($row), $drop); - unset($rows); - } - } - unset($drops_sp); - - // Цель критерии - $rows = $DB->select(' - SELECT a.id, a.faction, a.name_loc?d AS name, a.description_loc?d AS description, a.category, a.points, s.iconname, z.areatableID - FROM ?_spellicons s, ?_achievementcriteria c, ?_achievement a - LEFT JOIN (?_zones z) ON a.map != -1 AND a.map = z.mapID - WHERE - a.icon = s.id - AND a.id = c.refAchievement - AND c.type IN (?a) - AND c.value1 = ?d - GROUP BY a.id - ORDER BY a.name_loc?d - ', - $_SESSION['locale'], - $_SESSION['locale'], - array(ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM, ACHIEVEMENT_CRITERIA_TYPE_USE_ITEM, ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, ACHIEVEMENT_CRITERIA_TYPE_EQUIP_ITEM), - $item['entry'], - $_SESSION['locale'] - ); - if($rows) - { - $item['criteria_of'] = []; - foreach($rows as $row) - { - allachievementsinfo2($row['id']); - $item['criteria_of'][] = achievementinfo2($row); - } - } - - $item['color'] = colorByQuality($item['quality']); + // unlocks + // $locks_row = $DB->selectCol(' + // SELECT lockID + // FROM ?_lock + // WHERE + // (type1=1 AND lockproperties1=?d) OR + // (type2=1 AND lockproperties2=?d) OR + // (type3=1 AND lockproperties3=?d) OR + // (type4=1 AND lockproperties4=?d) OR + // (type5=1 AND lockproperties5=?d) + // ', + // $item['entry'], $item['entry'], $item['entry'], $item['entry'], $item['entry'] + // ); $smarty->saveCache($cacheKeyPage, $pageData); } @@ -843,12 +204,18 @@ if (!$smarty->loadCache($cacheKeyPage, $item)) // menuId 0: Item g_initPath() // tabId 0: Database g_initHeader() $smarty->updatePageVars(array( - 'book' => $pageData['page']['pagetext'] ? true : false, + 'book' => $pageData['pagetext'] ? true : false, 'title' => implode(" - ", $pageData['title']), 'path' => json_encode($pageData['path'], JSON_NUMERIC_CHECK), 'tab' => 0, 'type' => TYPE_ITEM, - 'typeid' => $_id + 'typeId' => $_id, + 'reqJS' => array( + 'template/js/swfobject.js', + 'template/js/profile.js', + 'template/js/filters.js', + '?data=weight-presets' + ) )); $smarty->assign('community', CommunityContent::getAll(TYPE_ITEM, $_id)); // comments, screenshots, videos $smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$item, ['colon' => Lang::$colon])); diff --git a/pages/items.php b/pages/items.php new file mode 100644 index 00000000..f27ff100 --- /dev/null +++ b/pages/items.php @@ -0,0 +1,302 @@ + + Find upgrades...Find upgrades... + +*/ + +$cats = Util::extractURLParams($pageParam); +$path = [0, 0]; +$title = [Lang::$game['items']]; +$filter = ['panel1' => false, 'panel2' => false]; +$filterHash = !empty($_GET['filter']) ? '#'.sha1(serialize($_GET['filter'])) : null; +$cacheKey = implode('_', [CACHETYPE_PAGE, TYPE_ITEM, -1, implode('.', $cats).$filterHash, User::$localeId]); +$validCats = array( // if > 0 class => subclass + 2 => [15, 13, 0, 4, 7, 6, 10, 1, 5, 8, 2, 18, 3, 16, 19, 20, 14], + 4 => array( + 0 => true, + 1 => [1, 3, 5, 6, 7, 8, 9, 10], + 2 => [1, 3, 5, 6, 7, 8, 9, 10], + 3 => [1, 3, 5, 6, 7, 8, 9, 10], + 4 => [1, 3, 5, 6, 7, 8, 9, 10], + 6 => true, + 7 => true, + 8 => true, + 9 => true, + 10 => true, + -2 => true, // Rings + -3 => true, // Amulets + -4 => true, // Trinkets + -5 => true, // Off-hand Frills + -6 => true, // Cloaks + -7 => true, // Tabards + -8 => true // Shirts + ), + 1 => [0, 1, 2, 3, 4, 5, 6, 7, 8], + 0 => array( + 7 => true, + 0 => true, + 2 => [1, 2], // elixirs: [Battle, Guardian] + 3 => true, + 5 => true, + 6 => true, // Item Enhancements (Permanent) + -3 => true, // Item Enhancements (Temporary) + 1 => true, + 4 => true, + 8 => true + ), + 16 => array( // Glyphs by class: [major, minor] + 1 => [1, 2], + 2 => [1, 2], + 3 => [1, 2], + 4 => [1, 2], + 5 => [1, 2], + 6 => [1, 2], + 7 => [1, 2], + 8 => [1, 2], + 9 => [1, 2], + 11 => [1, 2] + ), + 7 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + 6 => [2, 3], + 11 => [2, 3], + 9 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // some of the books to propper professions + 3 => [0, 1, 2, 3, 4, 5, 6, 7, 8], + 15 => [-2, -7, 0, 1, 2, 3, 4, 5], // -2: armor tokes, -7: flying mounts fuck it .. need major overhaul + 10 => true, + 12 => true, // todo: contains enchantments + 13 => true +); + +if (!Util::isValidPage($validCats, $cats)) + $smarty->error(); + +if (!$smarty->loadCache($cacheKey, $pageData, $filter)) +{ + $conditions = []; + $visibleCols = []; + $hiddenCols = []; + + if ($cats[0] !== null) + $path = array_merge($path, $cats); + + /* + display available submenu and slot, if applicable + todo: 'type' gets ignored if cats[1] is set + [$strArr, $keyMask] + */ + $type = $slot = [[], null]; + if ($cats[0] === null) + { + $slot = [Lang::$item['inventoryType'], null]; + asort($slot[0]); + } + else + { + if (isset($cats[1])) + $catList = Lang::$item['cat'][$cats[0]][1][$cats[1]]; + else + $catList = Lang::$item['cat'][$cats[0]]; + + array_unshift($title, is_array($catList) ? $catList[0] : $catList); + + switch ($cats[0]) + { + case 0: + if (!isset($cats[1])) + $type = [Lang::$item['cat'][0][1], null]; + + if (!isset($cats[1]) || in_array($cats[1], [6, -3])) + { + $slot = [Lang::$item['inventoryType'], 0x63EFEA]; + asort($slot[0]); + } + break; + case 2: + if (!isset($cats[1])) + $type = [Lang::$item['cat'][2][1], null]; + + $slot = [Lang::$item['inventoryType'], 0x262A000]; + asort($slot[0]); + break; + case 4: + if (!isset($cats[1])) + { + $slot = [Lang::$item['inventoryType'], 0x10895FFE]; + $type = [Lang::$item['cat'][4][1], null]; + } + else if (in_array($cats[1], [1, 2, 3, 4])) + $slot = [Lang::$item['inventoryType'], 0x7EA]; + + asort($slot[0]); + break; + case 3: + case 16: + if (!isset($cats[1])) + asort($catList[1]); + case 1: + case 7: + case 9: + case 15: + if (!isset($cats[1])) + $type = [$catList[1], null]; + + break; + } + } + + foreach ($type[0] as $k => $str) + if ($str && (!$type[1] || ($type[1] & (1 << $k)))) + $filter['type'][$k] = $str; + + foreach ($slot[0] as $k => $str) + if ($str && (!$slot[1] || ($slot[1] & (1 << $k)))) + $filter['slot'][$k] = $str; + + if (isset($filter['slot'][INVTYPE_SHIELD])) // "Off Hand" => "Shield" + $filter['slot'][INVTYPE_SHIELD] = Lang::$item['armorSubClass'][6]; + + + /* + set conditions + */ + $conditions[] = ['i.class', $cats[0]]; + if (isset($cats[1])) + $conditions[] = ['i.subClass', $cats[1]]; + if (isset($cats[2])) + $conditions[] = ['i.subSubClass', $cats[2]]; + + $items = new ItemList($conditions, true); + + $items->addGlobalsToJscript($smarty); + + // recreate form selection + $filter = array_merge($items->filterGetForm('form'), $filter); + $filter['query'] = isset($_GET['filter']) ? $_GET['filter'] : NULL; + $filter['fi'] = $items->filterGetForm(); + + // if slot-dropdown is available && Armor && $path points to Armor-Class + if (count($path) == 4 && $cats[0] == 4 && isset($filter['sl']) && !is_array($filter['sl'])) + $path[] = $filter['sl']; + + $pageData = array( + 'page' => [], + 'data' => $items->getListviewData(ITEMINFO_JSON), + 'title' => $title, + 'path' => $path, + 'params' => [] + ); + + if ($items->filterGetError()) + $pageData['params']['_errors'] = '$1'; + + if (!empty($filter['upg'])) + { + // uogarde-item got deleted by filter + if (empty($pageData['data'][$filter['upg']])) + { + $w = $items->filterGetForm('setWeights', true); + $upgItem = new ItemList(array(['id', $filter['upg']]), false, ['wt' => $w[0], 'wtv' => $w[1]]); + if (!$upgItem->error) + { + $upgItem->addGlobalsToJScript($smarty); + $pageData['data'][$filter['upg']] = $upgItem->getListviewData(ITEMINFO_JSON)[$filter['upg']]; + } + } + + if (!empty($filter['gb'])) + $pageData['params']['customFilter'] = '$fi_filterUpgradeListview'; + + $pageData['params']['_upgradeIds'] = "$[".$filter['upg']."]"; + } + + if (!empty($filter['fi']['extraCols'])) + $pageData['params']['extraCols'] = '$fi_getExtraCols(fi_extraCols, '.(empty($filter['gm']) ? 0 : $filter['gm']).', 0)'; + + if (!empty($filter['fi']['setWeights'])) + { + if (!empty($filter['gm'])) + { + $pageData['params']['computeDataFunc'] = '$fi_scoreSockets'; + + $w = $items->filterGetForm('setWeights', true); + $q = intVal($filter['gm']); + $mask = 14; + $cnd = [10, ['class', ITEM_CLASS_GEM], ['gemColorMask', &$mask, '&'], ['quality', &$q]]; + if (!isset($filter['jc'])) + $cnd[] = ['itemLimitCategory', 0]; // Jeweler's Gems + + $anyColor = new ItemList($cnd, false, ['wt' => $w[0], 'wtv' => $w[1]]); + if (!$anyColor->error) + { + $anyColor->addGlobalsToJScript($smarty); + $pageData['page']['gemScores'][0] = array_values($anyColor->getListviewData(ITEMINFO_GEM)); + } + + for ($i = 0; $i < 4; $i++) + { + $mask = 1 << $i; + $q = !$i ? 3 : intVal($filter['gm']); // meta gems are always included.. + $byColor = new ItemList($cnd, false, ['wt' => $w[0], 'wtv' => $w[1]]); + if (!$byColor->error) + { + $byColor->addGlobalsToJScript($smarty); + $pageData['page']['gemScores'][$mask] = array_values($byColor->getListviewData(ITEMINFO_GEM)); + } + } + + $pageData['page']['gemScores'] = json_encode($pageData['page']['gemScores'], JSON_NUMERIC_CHECK); + } + + $pageData['params']['onBeforeCreate'] = '$fi_initWeightedListview'; + $pageData['params']['onAfterCreate'] = '$fi_addUpgradeIndicator'; + $pageData['params']['sort'] = "$['-score', 'name']"; + + if ($items->hasSetFields(['armor'])) + $pageData['params']['visibleCols'] = "$['armor']"; + + $pageData['params']['hiddenCols'] = "$['type', 'source']"; + } + + // create note if search limit was exceeded; overwriting 'note' is intentional + if ($items->getMatches() > $AoWoWconf['sqlLimit']) + { + $pageData['params']['note'] = sprintf(Util::$tryFilteringString, 'LANG.lvnote_itemsfound', $items->getMatches(), $AoWoWconf['sqlLimit']); + $pageData['params']['_truncated'] = 1; + } + + $smarty->saveCache($cacheKey, $pageData, $filter); +} + + +// sort for dropdown-menus +asort(Lang::$game['ra']); +asort(Lang::$game['cl']); + +// menuId 0: Item g_initPath() +// tabId 0: Database g_initHeader() +$smarty->updatePageVars(array( + 'title' => implode(' - ', $pageData['title']), + 'path' => json_encode($pageData['path'], JSON_NUMERIC_CHECK), + 'tab' => 0, + 'subCat' => $pageParam !== null ? '='.$pageParam : '', + 'reqJS' => array( + 'template/js/filters.js', + 'template/js/swfobject.js', + '?data=weight-presets' + ) +)); +$smarty->assign('filter', $filter); +$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$item, ['colon' => Lang::$colon])); +$smarty->assign('lvData', $pageData); + +// load the page +$smarty->display('items.tpl'); + +?> diff --git a/pages/itemset.php b/pages/itemset.php index 39a93efa..45bef0aa 100644 --- a/pages/itemset.php +++ b/pages/itemset.php @@ -76,15 +76,15 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) $pieces = []; $eqList = []; $compare = []; - $iList = new ItemList(array(['i.entry', array_keys($iSet->pieceToSet)])); + $iList = new ItemList(array(['i.id', array_keys($iSet->pieceToSet)])); $data = $iList->getListviewData(ITEMINFO_SUBITEMS | ITEMINFO_JSON); foreach ($iList->iterate() as $itemId => $__) { if (empty($data[$itemId])) continue; - $slot = $iList->getField('InventoryType'); - $disp = $iList->getField('displayid'); + $slot = $iList->getField('slot'); + $disp = $iList->getField('displayId'); if ($slot && $disp) $eqList[] = [$slot, $disp]; @@ -93,8 +93,8 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) $pieces[] = array( 'id' => $itemId, 'name' => $iList->getField('name', true), - 'quality' => $iList->getField('Quality'), - 'icon' => $iList->getField('icon'), + 'quality' => $iList->getField('quality'), + 'icon' => $iList->getField('iconString'), 'json' => json_encode($data[$itemId], JSON_NUMERIC_CHECK) ); } @@ -255,7 +255,7 @@ $smarty->updatePageVars(array( ) )); $smarty->assign('community', CommunityContent::getAll(TYPE_ITEMSET, $_id)); // comments, screenshots, videos -$smarty->assign('lang', array_merge(Lang::$main, Lang::$itemset)); +$smarty->assign('lang', array_merge(Lang::$main, Lang::$itemset, ['colon' => Lang::$colon])); $smarty->assign('lvData', $pageData); // load the page diff --git a/pages/itemsets.php b/pages/itemsets.php index 1ed18666..2f2f47b8 100644 --- a/pages/itemsets.php +++ b/pages/itemsets.php @@ -4,14 +4,12 @@ if (!defined('AOWOW_REVISION')) die('illegal access'); -require 'includes/class.filter.php'; - $filter = []; $path = [0, 2]; $filterHash = !empty($_GET['filter']) ? sha1(serialize($_GET['filter'])) : -1; $cacheKey = implode('_', [CACHETYPE_PAGE, TYPE_ITEMSET, -1, $filterHash, User::$localeId]); -if (!$smarty->loadCache($cacheKey, $pageData)) +if (!$smarty->loadCache($cacheKey, $pageData, $filter)) { $itemsets = new ItemsetList([], true); // class selection is via filter, nothing applies here @@ -23,9 +21,12 @@ if (!$smarty->loadCache($cacheKey, $pageData)) $itemsets->addGlobalsToJscript($smarty); // recreate form selection + $filter = array_merge($itemsets->filterGetForm('form'), $filter); $filter['query'] = isset($_GET['filter']) ? $_GET['filter'] : NULL; - $filter['setCr'] = $itemsets->filterGetSetCriteria(); - $filter = array_merge($itemsets->filterGetForm(), $filter); + $filter['fi'] = $itemsets->filterGetForm(); + + if (!empty($filter['fi']['extraCols'])) + $pageData['params']['extraCols'] = '$fi_getExtraCols(fi_extraCols, 0, 0)'; if (isset($filter['cl'])) $path[] = $filter['cl']; @@ -40,7 +41,7 @@ if (!$smarty->loadCache($cacheKey, $pageData)) if ($itemsets->filterGetError()) $pageData['params']['_errors'] = '$1'; - $smarty->saveCache($cacheKey, $pageData); + $smarty->saveCache($cacheKey, $pageData, $filter); } @@ -61,7 +62,7 @@ $smarty->updatePageVars(array( ) )); $smarty->assign('filter', $filter); -$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$itemset, Lang::$item)); +$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$itemset, Lang::$item, ['colon' => lang::$colon])); $smarty->assign('lvData', $pageData); // load the page diff --git a/pages/pet.php b/pages/pet.php index 18a63f15..c9f57128 100644 --- a/pages/pet.php +++ b/pages/pet.php @@ -19,11 +19,11 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) $infobox = []; // level range - $infobox[] = '[li]'.Lang::$game['level'].Lang::$colon.$pet->getField('minLevel').' - '.$pet->getField('maxLevel').'[/li]'; + $infobox[] = Lang::$game['level'].Lang::$colon.$pet->getField('minLevel').' - '.$pet->getField('maxLevel'); // exotic if ($pet->getField('exotic')) - $infobox[] = '[li][url=?spell=53270]'.Lang::$pet['exotic'].'[/url][/li]'; + $infobox[] = '[url=?spell=53270]'.Lang::$pet['exotic'].'[/url]'; $pageData = array( 'title' => $pet->getField('name', true), diff --git a/pages/race.php b/pages/race.php index 36a3b09a..4fe96b4d 100644 --- a/pages/race.php +++ b/pages/race.php @@ -157,9 +157,9 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) $items = isset($mountVendors[$_id]) ? DB::Aowow()->selectCol('SELECT item FROM npc_vendor WHERE entry IN (?a)', $mountVendors[$_id]) : 0; $conditions = array( - ['i.entry', $items], - ['i.class', 15], // misc - ['i.subclass', 5], // mounts + ['i.id', $items], + ['i.class', ITEM_CLASS_MISC], + ['i.subClass', 5], // mounts ); $mounts = new ItemList($conditions); diff --git a/pages/skill.php b/pages/skill.php index eeb8d873..2f46424b 100644 --- a/pages/skill.php +++ b/pages/skill.php @@ -60,7 +60,7 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) // 2 recipe Items [items] (Books) $conditions = array( - ['RequiredSkill', $_id], + ['requiredSkill', $_id], ['class', ITEM_CLASS_RECIPE], 0 ); @@ -92,7 +92,7 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) if ($created) { - $created = new ItemList(array(['i.entry', $created], 0)); + $created = new ItemList(array(['i.id', $created], 0)); if (!$created->error) { $created->addGlobalsToJscript($smarty, GLOBALINFO_SELF); @@ -112,7 +112,7 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) // 4a required by [item] $conditions = array( - ['RequiredSkill', $_id], + ['requiredSkill', $_id], ['class', ITEM_CLASS_RECIPE, '!'], 0 ); diff --git a/pages/spell.php b/pages/spell.php index 4a8bbbc1..62059bb1 100644 --- a/pages/spell.php +++ b/pages/spell.php @@ -230,7 +230,7 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) $pageData['page']['reagents'][] = array( 'name' => $spell->relItems->getField('name', true), - 'quality' => $spell->relItems->getField('Quality'), + 'quality' => $spell->relItems->getField('quality'), 'entry' => $itemId, 'count' => $_[$itemId], ); @@ -270,9 +270,9 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) $foo['icon'] = array( 'id' => $spell->relItems->id, 'name' => $spell->relItems->getField('name', true), - 'quality' => $spell->relItems->getField('Quality'), + 'quality' => $spell->relItems->getField('quality'), 'count' => $effDS + $effBP, - 'icon' => $spell->relItems->getField('icon') + 'icon' => $spell->relItems->getField('iconString') ); } @@ -744,12 +744,12 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) // tab: used by - item $conditions = array( - 'OR', // 6: learn spell - ['AND', ['spelltrigger_1', 6, '!'], ['spellid_1', $spell->id]], - ['AND', ['spelltrigger_2', 6, '!'], ['spellid_2', $spell->id]], - ['AND', ['spelltrigger_3', 6, '!'], ['spellid_3', $spell->id]], - ['AND', ['spelltrigger_4', 6, '!'], ['spellid_4', $spell->id]], - ['AND', ['spelltrigger_5', 6, '!'], ['spellid_5', $spell->id]] + 'OR', // 6: learn spell + ['AND', ['spellTrigger1', 6, '!'], ['spellId1', $spell->id]], + ['AND', ['spellTrigger2', 6, '!'], ['spellId2', $spell->id]], + ['AND', ['spellTrigger3', 6, '!'], ['spellId3', $spell->id]], + ['AND', ['spellTrigger4', 6, '!'], ['spellId4', $spell->id]], + ['AND', ['spellTrigger5', 6, '!'], ['spellId5', $spell->id]] ); $ubItems = new ItemList($conditions); @@ -803,7 +803,7 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) if ($ids) { // todo (high): generic loot-processing function - $slItems = new ItemList(array(['i.entry', $ids])); + $slItems = new ItemList(array(['i.id', $ids])); $slItems->addGlobalsToJscript($smarty); $lv += $slItems->getListviewData(); @@ -837,7 +837,7 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) { $lv[$bar] = $foo[$bar]; $lv[$bar]['percent'] = $extraItem['additionalCreateChance']; - $lv[$bar]['condition'] = json_encode(['type' => TYPE_SPELL, 'typeId' => $extraItem['requiredSpecialization'], 'status' => 2], JSON_NUMERIC_CHECK); + $lv[$bar]['condition'] = ['type' => TYPE_SPELL, 'typeId' => $extraItem['requiredSpecialization'], 'status' => 2]; $smarty->extendGlobalIds(TYPE_SPELL, $extraItem['requiredSpecialization']); $extraCols[] = 'Listview.extraCols.condition'; @@ -1012,7 +1012,7 @@ if (!$smarty->loadCache($cacheKeyPage, $pageData)) /* source item first check source if not item : break - spellid_1 = id OR spellid_1 = "LEAR_SPELL_GENERIC" AND spellid_2 = id + spellId1 = id OR spellId1 = "LEAR_SPELL_GENERIC" AND spellId2 = id spelltrigger_1/2 = 6 */ diff --git a/pages/spells.php b/pages/spells.php index 9bb2b626..30583bfe 100644 --- a/pages/spells.php +++ b/pages/spells.php @@ -4,8 +4,6 @@ if (!defined('AOWOW_REVISION')) die('illegal access'); -require 'includes/class.filter.php'; - $cats = Util::extractURLParams($pageParam); $path = [0, 1]; $title = [Lang::$game['spells']]; // display max 2 cats, remove this base if nesecary @@ -202,7 +200,7 @@ if (!$smarty->loadCache($cacheKey, $pageData, $filter)) break; } - $pageData['params']['note'] = '$sprintf(LANG.lvnote_pettalents, "'.$url.'")'; + $pageData['params']['note'] = '$$WH.sprintf(LANG.lvnote_pettalents, "'.$url.'")'; } $pageData['params']['_petTalents'] = 1; // not conviced, this is correct, but .. it works @@ -409,9 +407,12 @@ if (!$smarty->loadCache($cacheKey, $pageData, $filter)) $pageData['params']['hiddenCols'] = '$'.json_encode($hiddenCols); // recreate form selection + $filter = array_merge($spells->filterGetForm('form'), $filter); $filter['query'] = isset($_GET['filter']) ? $_GET['filter'] : NULL; - $filter['setCr'] = $spells->filterGetSetCriteria(); - $filter = array_merge($spells->filterGetForm(), $filter); + $filter['fi'] = $spells->filterGetForm(); + + if (!empty($filter['fi']['extraCols'])) + $pageData['params']['extraCols'] = '$fi_getExtraCols(fi_extraCols, 0, 0)'; $smarty->saveCache($cacheKey, $pageData, $filter); } @@ -430,7 +431,6 @@ asort(Lang::$game['ra']); asort(Lang::$game['cl']); asort(Lang::$game['sc']); asort(Lang::$game['me']); -Lang::$game['race'] = Util::ucFirst(Lang::$game['race']); // menuId 1: Spell g_initPath() // tabId 0: Database g_initHeader() @@ -444,7 +444,7 @@ $smarty->updatePageVars(array( ) )); $smarty->assign('filter', $filter); -$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$achievement)); +$smarty->assign('lang', array_merge(Lang::$main, Lang::$game, Lang::$achievement, ['colon' => Lang::$colon])); $smarty->assign('lvData', $pageData); // load the page diff --git a/pages/talent.php b/pages/talent.php index e59792ee..fa95a944 100644 --- a/pages/talent.php +++ b/pages/talent.php @@ -26,7 +26,7 @@ $smarty->updatePageVars(array( ) )); $smarty->assign('tcType', $petCalc ? 'pc' : 'tc'); -$smarty->assign('lang', Lang::$main); +$smarty->assign('lang', array_merge(Lang::$main, ['colon' => Lang::$colon])); // load the page $smarty->display('talent.tpl'); diff --git a/search.php b/search.php index a1f531dd..d5e823cf 100644 --- a/search.php +++ b/search.php @@ -37,7 +37,7 @@ if (!defined('AOWOW_REVISION')) 16: Listview - template: 'quest', id: 'quests', name: LANG.tab_quests, 17: Listview - template: 'achievement', id: 'achievements', name: LANG.tab_achievements, 18: Listview - template: 'achievement', id: 'statistics', name: LANG.tab_statistics, -todo 19: Listview - template: 'zone', id: 'zones', name: LANG.tab_zones, + 19: Listview - template: 'zone', id: 'zones', name: LANG.tab_zones, todo 20: Listview - template: 'object', id: 'objects', name: LANG.tab_objects, todo 21: Listview - template: 'faction', id: 'factions', name: LANG.tab_factions, 22: Listview - template: 'skill', id: 'skills', name: LANG.tab_skills, @@ -331,18 +331,18 @@ if ($searchMask & 0x40) if (($searchMask & SEARCH_TYPE_JSON) && $type == TYPE_ITEMSET && isset($found['itemset'])) { - $conditions = [['i.entry', array_keys($found['itemset']['pcsToSet'])], 0]; + $conditions = [['i.id', array_keys($found['itemset']['pcsToSet'])], 0]; $miscData = ['pcsToSet' => @$found['itemset']['pcsToSet']]; } else if (($searchMask & SEARCH_TYPE_JSON) && $type == TYPE_ITEM) { - $conditions = [['i.class', [2, 4]], [User::$localeId ? 'name_loc'.User::$localeId : 'name', $query], $AoWoWconf['sqlLimit']]; + $conditions = [['i.class', [ITEM_CLASS_WEAPON, ITEM_CLASS_GEM, ITEM_CLASS_ARMOR]], ['name_loc'.User::$localeId, $query], $AoWoWconf['sqlLimit']]; $miscData = ['wt' => $_wt, 'wtv' => $_wtv]; } else - $conditions = [[User::$localeId ? 'name_loc'.User::$localeId : 'name', $query], $maxResults]; + $conditions = [['name_loc'.User::$localeId, $query], $maxResults]; - $items = new ItemList($conditions, $miscData); + $items = new ItemList($conditions, false, $miscData); if ($data = $items->getListviewData($searchMask & SEARCH_TYPE_JSON ? (ITEMINFO_SUBITEMS | ITEMINFO_JSON) : 0)) { @@ -350,8 +350,8 @@ if ($searchMask & 0x40) foreach ($items->iterate() as $__) { - $data[$items->id]['param1'] = '"'.$items->getField('icon').'"'; - $data[$items->id]['param2'] = $items->getField('Quality'); + $data[$items->id]['param1'] = '"'.$items->getField('iconString').'"'; + $data[$items->id]['param2'] = $items->getField('quality'); if ($searchMask & SEARCH_TYPE_OPEN) $data[$items->id]['name'] = substr($data[$items->id]['name'], 1); @@ -815,7 +815,37 @@ if ($searchMask & 0x20000) } // 19 Zones -// if ($searchMask & 0x40000) +if ($searchMask & 0x40000) +{ + $conditions = array( + ['name_loc'.User::$localeId, $query], + $maxResults + ); + + $zones = new ZoneList($conditions); + + if ($data = $zones->getListviewData()) + { + $zones->addGlobalsToJScript($smarty); + + $found['zone'] = array( + 'type' => TYPE_ZONE, + 'appendix' => ' (Zone)', + 'matches' => $zones->getMatches(), + 'file' => 'zone', + 'data' => $data, + 'params' => [ + 'tabs' => '$myTabs', + ] + ); + + if ($zones->getMatches() > $maxResults) + { + $found['zone']['params']['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_zonesfound', $zones->getMatches(), $maxResults); + $found['zone']['params']['_truncated'] = 1; + } + } +} // 20 Objects // if ($searchMask & 0x80000) diff --git a/setup/tools/dataset-assembler/enchants.php b/setup/tools/dataset-assembler/enchants.php index 95fb37d1..4dfd0f58 100644 --- a/setup/tools/dataset-assembler/enchants.php +++ b/setup/tools/dataset-assembler/enchants.php @@ -142,28 +142,28 @@ if (!defined('AOWOW_REVISION')) // check if this item can be cast via item -> Source:Item if (!isset($castItems[$enchantSpells->id])) - $castItems[$enchantSpells->id] = new ItemList([['spellid_1', $enchantSpells->id], ['name', 'Scroll of Enchant%', '!']]); // do not reuse enchantment scrolls + $castItems[$enchantSpells->id] = new ItemList([['spellId1', $enchantSpells->id], ['name', 'Scroll of Enchant%', '!']]); // do not reuse enchantment scrolls $cI = &$castItems[$enchantSpells->id]; // this construct is a bit .. unwieldy foreach ($cI->iterate() as $__) { $ench['name'][] = $cI->getField('name', true); $ench['source'][] = -$cI->id; - $ench['icon'] = strTolower($cI->getField('icon')); + $ench['icon'] = strTolower($cI->getField('iconString')); $ench['slots'][] = $slot; - if ($cI->getField('Quality') > $ench['quality']) - $ench['quality'] = $cI->getField('Quality'); + if ($cI->getField('quality') > $ench['quality']) + $ench['quality'] = $cI->getField('quality'); - if ($cI->getField('AllowableClass') > 0) + if ($cI->getField('requiredClass') > 0) { - $ench['classes'] = $cI->getField('AllowableClass'); - $ench['jsonequip']['classes'] = $cI->getField('AllowableClass'); + $ench['classes'] = $cI->getField('requiredClass'); + $ench['jsonequip']['classes'] = $cI->getField('requiredClass'); } if (!isset($ench['jsonequip']['reqlevel'])) - if ($cI->getField('RequiredLevel') > 0) - $ench['jsonequip']['reqlevel'] = $cI->getField('RequiredLevel'); + if ($cI->getField('requiredLevel') > 0) + $ench['jsonequip']['reqlevel'] = $cI->getField('requiredLevel'); } // enchant spell not in use diff --git a/setup/tools/dataset-assembler/gems.php b/setup/tools/dataset-assembler/gems.php index 101d71f0..8e88b6d1 100644 --- a/setup/tools/dataset-assembler/gems.php +++ b/setup/tools/dataset-assembler/gems.php @@ -37,7 +37,7 @@ if (!defined('AOWOW_REVISION')) FROM item_template it, locales_item li, - ?_gemProperties gp, + dbc.gemProperties gp, ?_icons i, ?_itemEnchantment ie WHERE @@ -45,7 +45,7 @@ if (!defined('AOWOW_REVISION')) li.entry = it.entry AND gp.Id = it.GemProperties AND i.Id = it.displayid AND - gp.itemEnchantmentId = ie.Id + gp.spellItemEnchantmentId = ie.Id ORDER BY it.entry DESC ; diff --git a/setup/tools/dataset-assembler/realms.php b/setup/tools/dataset-assembler/realms.php index fc8b306a..aea44a08 100644 --- a/setup/tools/dataset-assembler/realms.php +++ b/setup/tools/dataset-assembler/realms.php @@ -82,7 +82,7 @@ if (!defined('AOWOW_REVISION')) ]] ]; - $rows = DB::Auth()->select('SELECT id AS ARRAY_KEY, name, ?s AS battlegroup, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0', $AoWoWconf['battlegroup']); + $rows = DB::Auth()->select('SELECT id AS ARRAY_KEY, name, ? AS battlegroup, IF(timezone IN (8, 9, 10, 11, 12), "eu", "us") AS region FROM realmlist WHERE allowedSecurityLevel = 0', $AoWoWconf['battlegroup']); $str = 'var g_realms = '.json_encode($rows, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE).';'; $handle = fOpen('datasets\\realms', "w"); diff --git a/setup/tools/sql/_item.php b/setup/tools/sql/_item.php index a962ae8d..4f2de1bc 100644 --- a/setup/tools/sql/_item.php +++ b/setup/tools/sql/_item.php @@ -3,6 +3,306 @@ if (!defined('AOWOW_REVISION')) die('illegal access'); +/* +-- custom itemSubClass + +itemClass: itemSubClass - diff to Client + 0: { + 6: "Perm. Enhancement", + "-3": "Temp. Enhancement", + }, + 15: { + "-7": "Flying Mount", + "-6": "Combat Pet", + "-2": "Armor Token", + }, +} + +DROP TABLE IF EXISTS `aowow_item_stats`; +CREATE TABLE `aowow_item_stats` ( + `id` mediumint(8) UNSIGNED NOT NULL , + `nsockets` mediumint(8) NOT NULL , + `dmgmin1` mediumint(8) NOT NULL , + `dmgmax1` mediumint(8) NOT NULL , + `speed` float(8,2) NOT NULL , + `dps` float(8,2) NOT NULL , + `mledmgmin` mediumint(8) NOT NULL , + `mledmgmax` mediumint(8) NOT NULL , + `mlespeed` float(8,2) NOT NULL , + `mledps` float(8,2) NOT NULL , + `rgddmgmin` mediumint(8) NOT NULL , + `rgddmgmax` mediumint(8) NOT NULL , + `rgdspeed` float(8,2) NOT NULL , + `rgddps` float(8,2) NOT NULL , + `dmg` float(8,2) NOT NULL , + `damagetype` mediumint(8) NOT NULL , + `mana` mediumint(8) NOT NULL , + `health` mediumint(8) NOT NULL , + `agi` mediumint(8) NOT NULL , + `str` mediumint(8) NOT NULL , + `int` mediumint(8) NOT NULL , + `spi` mediumint(8) NOT NULL , + `sta` mediumint(8) NOT NULL , + `energy` mediumint(8) NOT NULL , + `rage` mediumint(8) NOT NULL , + `focus` mediumint(8) NOT NULL , + `runicpwr` mediumint(8) NOT NULL , + `defrtng` mediumint(8) NOT NULL , + `dodgertng` mediumint(8) NOT NULL , + `parryrtng` mediumint(8) NOT NULL , + `blockrtng` mediumint(8) NOT NULL , + `mlehitrtng` mediumint(8) NOT NULL , + `rgdhitrtng` mediumint(8) NOT NULL , + `splhitrtng` mediumint(8) NOT NULL , + `mlecritstrkrtng` mediumint(8) NOT NULL , + `rgdcritstrkrtng` mediumint(8) NOT NULL , + `splcritstrkrtng` mediumint(8) NOT NULL , + `_mlehitrtng` mediumint(8) NOT NULL , + `_rgdhitrtng` mediumint(8) NOT NULL , + `_splhitrtng` mediumint(8) NOT NULL , + `_mlecritstrkrtng` mediumint(8) NOT NULL , + `_rgdcritstrkrtng` mediumint(8) NOT NULL , + `_splcritstrkrtng` mediumint(8) NOT NULL , + `mlehastertng` mediumint(8) NOT NULL , + `rgdhastertng` mediumint(8) NOT NULL , + `splhastertng` mediumint(8) NOT NULL , + `hitrtng` mediumint(8) NOT NULL , + `critstrkrtng` mediumint(8) NOT NULL , + `_hitrtng` mediumint(8) NOT NULL , + `_critstrkrtng` mediumint(8) NOT NULL , + `resirtng` mediumint(8) NOT NULL , + `hastertng` mediumint(8) NOT NULL , + `exprtng` mediumint(8) NOT NULL , + `atkpwr` mediumint(8) NOT NULL , + `mleatkpwr` mediumint(8) NOT NULL , + `rgdatkpwr` mediumint(8) NOT NULL , + `feratkpwr` mediumint(8) NOT NULL , + `splheal` mediumint(8) NOT NULL , + `spldmg` mediumint(8) NOT NULL , + `manargn` mediumint(8) NOT NULL , + `armorpenrtng` mediumint(8) NOT NULL , + `splpwr` mediumint(8) NOT NULL , + `healthrgn` mediumint(8) NOT NULL , + `splpen` mediumint(8) NOT NULL , + `block` mediumint(8) NOT NULL , + `mastrtng` mediumint(8) NOT NULL , + `armor` mediumint(8) NOT NULL , + `armorbonus` mediumint(8) NOT NULL , + `firres` mediumint(8) NOT NULL , + `frores` mediumint(8) NOT NULL , + `holres` mediumint(8) NOT NULL , + `shares` mediumint(8) NOT NULL , + `natres` mediumint(8) NOT NULL , + `arcres` mediumint(8) NOT NULL , + `firsplpwr` mediumint(8) NOT NULL , + `frosplpwr` mediumint(8) NOT NULL , + `holsplpwr` mediumint(8) NOT NULL , + `shasplpwr` mediumint(8) NOT NULL , + `natsplpwr` mediumint(8) NOT NULL , + `arcsplpwr` mediumint(8) NOT NULL , + PRIMARY KEY (`id`), + INDEX `item` (`id`) +) ENGINE=MyISAM DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci; + + CREATE TABLE aowow_items LIKE item_template; + INSERT INTO aowow_items SELECT * FROM item_template; + + ALTER TABLE `aowow_items` + DROP COLUMN `SoundOverrideSubclass`, + DROP COLUMN `StatsCount`, + DROP COLUMN `Material`, + DROP COLUMN `sheath`, + DROP COLUMN `WDBVerified`, + CHANGE COLUMN `entry` `id` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 FIRST , + CHANGE COLUMN `subclass` `subClass` tinyint(3) NOT NULL DEFAULT 0 AFTER `class`, + ADD COLUMN `subClassBak` tinyint(3) NOT NULL AFTER `subClass`, + ADD COLUMN `subSubClass` tinyint(3) NOT NULL AFTER `subClassBak`, + CHANGE COLUMN `name` `name_loc0` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' AFTER `subSubClass`, + ADD COLUMN `name_loc2` varchar(255) NOT NULL AFTER `name_loc0`, + ADD COLUMN `name_loc3` varchar(255) NOT NULL AFTER `name_loc2`, + ADD COLUMN `name_loc6` varchar(255) NOT NULL AFTER `name_loc3`, + ADD COLUMN `name_loc8` varchar(255) NOT NULL AFTER `name_loc6`, + CHANGE COLUMN `displayid` `displayId` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `name_loc8`, + ADD COLUMN `iconString` varchar(127) NOT NULL AFTER `displayid`, + CHANGE COLUMN `Quality` `quality` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `displayId`, + CHANGE COLUMN `Flags` `flags` bigint(20) NOT NULL DEFAULT 0 AFTER `quality`, + CHANGE COLUMN `FlagsExtra` `flagsExtra` int(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `flags`, + CHANGE COLUMN `BuyCount` `buyCount` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 AFTER `flagsExtra`, + CHANGE COLUMN `BuyPrice` `buyPrice` bigint(20) NOT NULL DEFAULT 0 AFTER `buyCount`, + CHANGE COLUMN `SellPrice` `sellPrice` int(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `buyPrice`, + ADD COLUMN `slot` tinyint(3) NOT NULL AFTER `sellPrice`, + CHANGE COLUMN `InventoryType` `slotBak` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `slot`, + CHANGE COLUMN `AllowableClass` `requiredClass` int(11) NOT NULL DEFAULT '-1' AFTER `slotBak`, + CHANGE COLUMN `AllowableRace` `requiredRace` int(11) NOT NULL DEFAULT '-1' AFTER `requiredClass`, + CHANGE COLUMN `ItemLevel` `itemLevel` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredRace`, + CHANGE COLUMN `RequiredLevel` `requiredLevel` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `itemLevel`, + CHANGE COLUMN `RequiredSkill` `requiredSkill` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredLevel`, + CHANGE COLUMN `RequiredSkillRank` `requiredSkillRank` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredSkill`, + CHANGE COLUMN `requiredspell` `requiredSpell` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredSkillRank`, + CHANGE COLUMN `requiredhonorrank` `requiredHonorRank` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredSpell`, + CHANGE COLUMN `RequiredCityRank` `requiredCityRank` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredHonorRank`, + CHANGE COLUMN `RequiredReputationFaction` `requiredFaction` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredCityRank`, + CHANGE COLUMN `RequiredReputationRank` `requiredFactionRank` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredFaction`, + CHANGE COLUMN `maxcount` `maxCount` int(11) NOT NULL DEFAULT 0 AFTER `requiredFactionRank`, + CHANGE COLUMN `ContainerSlots` `slots` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `stackable`, + CHANGE COLUMN `stat_type1` `statType1` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `slots`, + CHANGE COLUMN `stat_value1` `statValue1` smallint(6) NOT NULL DEFAULT 0 AFTER `statType1`, + CHANGE COLUMN `stat_type2` `statType2` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue1`, + CHANGE COLUMN `stat_value2` `statValue2` smallint(6) NOT NULL DEFAULT 0 AFTER `statType2`, + CHANGE COLUMN `stat_type3` `statType3` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue2`, + CHANGE COLUMN `stat_value3` `statValue3` smallint(6) NOT NULL DEFAULT 0 AFTER `statType3`, + CHANGE COLUMN `stat_type4` `statType4` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue3`, + CHANGE COLUMN `stat_value4` `statValue4` smallint(6) NOT NULL DEFAULT 0 AFTER `statType4`, + CHANGE COLUMN `stat_type5` `statType5` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue4`, + CHANGE COLUMN `stat_value5` `statValue5` smallint(6) NOT NULL DEFAULT 0 AFTER `statType5`, + CHANGE COLUMN `stat_type6` `statType6` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue5`, + CHANGE COLUMN `stat_value6` `statValue6` smallint(6) NOT NULL DEFAULT 0 AFTER `statType6`, + CHANGE COLUMN `stat_type7` `statType7` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue6`, + CHANGE COLUMN `stat_value7` `statValue7` smallint(6) NOT NULL DEFAULT 0 AFTER `statType7`, + CHANGE COLUMN `stat_type8` `statType8` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue7`, + CHANGE COLUMN `stat_value8` `statValue8` smallint(6) NOT NULL DEFAULT 0 AFTER `statType8`, + CHANGE COLUMN `stat_type9` `statType9` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue8`, + CHANGE COLUMN `stat_value9` `statValue9` smallint(6) NOT NULL DEFAULT 0 AFTER `statType9`, + CHANGE COLUMN `stat_type10` `statType10` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `statValue9`, + CHANGE COLUMN `stat_value10` `statValue10` smallint(6) NOT NULL DEFAULT 0 AFTER `statType10`, + CHANGE COLUMN `ScalingStatDistribution` `scalingStatDistribution` smallint(6) NOT NULL DEFAULT 0 AFTER `statValue10`, + CHANGE COLUMN `ScalingStatValue` `scalingStatValue` int(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `scalingStatDistribution`, + CHANGE COLUMN `dmg_min1` `dmgMin1` float NOT NULL DEFAULT 0 AFTER `scalingStatValue`, + CHANGE COLUMN `dmg_max1` `dmgMax1` float NOT NULL DEFAULT 0 AFTER `dmgMin1`, + CHANGE COLUMN `dmg_type1` `dmgType1` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `dmgMax1`, + CHANGE COLUMN `dmg_min2` `dmgMin2` float NOT NULL DEFAULT 0 AFTER `dmgType1`, + CHANGE COLUMN `dmg_max2` `dmgMax2` float NOT NULL DEFAULT 0 AFTER `dmgMin2`, + CHANGE COLUMN `dmg_type2` `dmgType2` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `dmgMax2`, + MODIFY COLUMN `delay` smallint(5) UNSIGNED NOT NULL DEFAULT 1000 AFTER `dmgType2`, + MODIFY COLUMN `armor` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `delay`, + CHANGE COLUMN `ArmorDamageModifier` `armorDamageModifier` float NOT NULL DEFAULT 0 AFTER `armor`, + MODIFY COLUMN `block` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `armorDamageModifier`, + CHANGE COLUMN `holy_res` `resHoly` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `block`, + CHANGE COLUMN `fire_res` `resFire` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `resHoly`, + CHANGE COLUMN `nature_res` `resNature` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `resFire`, + CHANGE COLUMN `frost_res` `resFrost` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `resNature`, + CHANGE COLUMN `shadow_res` `resShadow` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `resFrost`, + CHANGE COLUMN `arcane_res` `resArcane` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `resShadow`, + CHANGE COLUMN `ammo_type` `ammoType` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `resArcane`, + CHANGE COLUMN `RangedModRange` `rangedModRange` float NOT NULL DEFAULT 0 AFTER `ammoType`, + CHANGE COLUMN `spellid_1` `spellId1` mediumint(8) NOT NULL DEFAULT 0 AFTER `rangedModRange`, + CHANGE COLUMN `spelltrigger_1` `spellTrigger1` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellId1`, + CHANGE COLUMN `spellcharges_1` `spellCharges1` smallint(6) NULL DEFAULT NULL AFTER `spellTrigger1`, + CHANGE COLUMN `spellppmRate_1` `spellppmRate1` float NOT NULL DEFAULT 0 AFTER `spellCharges1`, + CHANGE COLUMN `spellcooldown_1` `spellCooldown1` int(11) NOT NULL DEFAULT '-1' AFTER `spellppmRate1`, + CHANGE COLUMN `spellcategory_1` `spellCategory1` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellCooldown1`, + CHANGE COLUMN `spellcategorycooldown_1` `spellCategoryCooldown1` int(11) NOT NULL DEFAULT '-1' AFTER `spellCategory1`, + CHANGE COLUMN `spellid_2` `spellId2` mediumint(8) NOT NULL DEFAULT 0 AFTER `spellCategoryCooldown1`, + CHANGE COLUMN `spelltrigger_2` `spellTrigger2` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellId2`, + CHANGE COLUMN `spellcharges_2` `spellCharges2` smallint(6) NULL DEFAULT NULL AFTER `spellTrigger2`, + CHANGE COLUMN `spellppmRate_2` `spellppmRate2` float NOT NULL DEFAULT 0 AFTER `spellCharges2`, + CHANGE COLUMN `spellcooldown_2` `spellCooldown2` int(11) NOT NULL DEFAULT '-1' AFTER `spellppmRate2`, + CHANGE COLUMN `spellcategory_2` `spellCategory2` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellCooldown2`, + CHANGE COLUMN `spellcategorycooldown_2` `spellCategoryCooldown2` int(11) NOT NULL DEFAULT '-1' AFTER `spellCategory2`, + CHANGE COLUMN `spellid_3` `spellId3` mediumint(8) NOT NULL DEFAULT 0 AFTER `spellCategoryCooldown2`, + CHANGE COLUMN `spelltrigger_3` `spellTrigger3` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellId3`, + CHANGE COLUMN `spellcharges_3` `spellCharges3` smallint(6) NULL DEFAULT NULL AFTER `spellTrigger3`, + CHANGE COLUMN `spellppmRate_3` `spellppmRate3` float NOT NULL DEFAULT 0 AFTER `spellCharges3`, + CHANGE COLUMN `spellcooldown_3` `spellCooldown3` int(11) NOT NULL DEFAULT '-1' AFTER `spellppmRate3`, + CHANGE COLUMN `spellcategory_3` `spellCategory3` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellCooldown3`, + CHANGE COLUMN `spellcategorycooldown_3` `spellCategoryCooldown3` int(11) NOT NULL DEFAULT '-1' AFTER `spellCategory3`, + CHANGE COLUMN `spellid_4` `spellId4` mediumint(8) NOT NULL DEFAULT 0 AFTER `spellCategoryCooldown3`, + CHANGE COLUMN `spelltrigger_4` `spellTrigger4` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellId4`, + CHANGE COLUMN `spellcharges_4` `spellCharges4` smallint(6) NULL DEFAULT NULL AFTER `spellTrigger4`, + CHANGE COLUMN `spellppmRate_4` `spellppmRate4` float NOT NULL DEFAULT 0 AFTER `spellCharges4`, + CHANGE COLUMN `spellcooldown_4` `spellCooldown4` int(11) NOT NULL DEFAULT '-1' AFTER `spellppmRate4`, + CHANGE COLUMN `spellcategory_4` `spellCategory4` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellCooldown4`, + CHANGE COLUMN `spellcategorycooldown_4` `spellCategoryCooldown4` int(11) NOT NULL DEFAULT '-1' AFTER `spellCategory4`, + CHANGE COLUMN `spellid_5` `spellId5` mediumint(8) NOT NULL DEFAULT 0 AFTER `spellCategoryCooldown4`, + CHANGE COLUMN `spelltrigger_5` `spellTrigger5` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellId5`, + CHANGE COLUMN `spellcharges_5` `spellCharges5` smallint(6) NULL DEFAULT NULL AFTER `spellTrigger5`, + CHANGE COLUMN `spellppmRate_5` `spellppmRate5` float NOT NULL DEFAULT 0 AFTER `spellCharges5`, + CHANGE COLUMN `spellcooldown_5` `spellCooldown5` int(11) NOT NULL DEFAULT '-1' AFTER `spellppmRate5`, + CHANGE COLUMN `spellcategory_5` `spellCategory5` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `spellCooldown5`, + CHANGE COLUMN `spellcategorycooldown_5` `spellCategoryCooldown5` int(11) NOT NULL DEFAULT '-1' AFTER `spellCategory5`, + CHANGE COLUMN `description` `description_loc0` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' AFTER `bonding`, + ADD COLUMN `description_loc2` varchar(255) NOT NULL AFTER `description_loc0`, + ADD COLUMN `description_loc3` varchar(255) NOT NULL AFTER `description_loc2`, + ADD COLUMN `description_loc6` varchar(255) NOT NULL AFTER `description_loc3`, + ADD COLUMN `description_loc8` varchar(255) NOT NULL AFTER `description_loc6`, + CHANGE COLUMN `PageText` `pageTextId` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `description_loc8`, + CHANGE COLUMN `LanguageID` `languageId` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `pageTextId`, + CHANGE COLUMN `PageMaterial` `pageMaterial` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `languageId`, + CHANGE COLUMN `startquest` `startQuest` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `pageMaterial`, + CHANGE COLUMN `lockid` `lockId` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `startQuest`, + CHANGE COLUMN `RandomProperty` `randomProperty` mediumint(8) NOT NULL DEFAULT 0 AFTER `lockId`, + CHANGE COLUMN `RandomSuffix` `randomSuffix` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `randomProperty`, + MODIFY COLUMN `itemset` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `randomSuffix`, + CHANGE COLUMN `MaxDurability` `durability` smallint(5) UNSIGNED NOT NULL DEFAULT 0 AFTER `itemset`, + CHANGE COLUMN `Map` `map` smallint(6) NOT NULL DEFAULT 0 AFTER `area`, + CHANGE COLUMN `BagFamily` `bagFamily` mediumint(8) NOT NULL DEFAULT 0 AFTER `map`, + CHANGE COLUMN `TotemCategory` `totemCategory` mediumint(8) NOT NULL DEFAULT 0 AFTER `bagFamily`, + CHANGE COLUMN `socketColor_1` `socketColor1` tinyint(4) NOT NULL DEFAULT 0 AFTER `totemCategory`, + CHANGE COLUMN `socketContent_1` `socketContent1` mediumint(8) NOT NULL DEFAULT 0 AFTER `socketColor1`, + CHANGE COLUMN `socketColor_2` `socketColor2` tinyint(4) NOT NULL DEFAULT 0 AFTER `socketContent1`, + CHANGE COLUMN `socketContent_2` `socketContent2` mediumint(8) NOT NULL DEFAULT 0 AFTER `socketColor2`, + CHANGE COLUMN `socketColor_3` `socketColor3` tinyint(4) NOT NULL DEFAULT 0 AFTER `socketContent2`, + CHANGE COLUMN `socketContent_3` `socketContent3` mediumint(8) NOT NULL DEFAULT 0 AFTER `socketColor3`, + CHANGE COLUMN `GemProperties` `gemColorMask` mediumint(8) NOT NULL DEFAULT 0 AFTER `socketBonus`, + ADD COLUMN `gemEnchantmentId` mediumint(8) NOT NULL AFTER `gemColorMask`, + CHANGE COLUMN `RequiredDisenchantSkill` `requiredDisenchantSkill` smallint(6) NOT NULL DEFAULT '-1' AFTER `gemProperties`, + CHANGE COLUMN `DisenchantID` `disenchantId` mediumint(8) UNSIGNED NOT NULL DEFAULT 0 AFTER `requiredDisenchantSkill`, + MODIFY COLUMN `duration` int(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `disenchantId`, + CHANGE COLUMN `ItemLimitCategory` `itemLimitCategory` smallint(6) NOT NULL DEFAULT 0 AFTER `duration`, + CHANGE COLUMN `HolidayId` `holidayId` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `itemLimitCategory`, + CHANGE COLUMN `ScriptName` `scriptName` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' AFTER `holidayId`, + CHANGE COLUMN `FoodType` `foodType` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `scriptName`, + DROP PRIMARY KEY, + ADD PRIMARY KEY (`id`); + + -- localization + UPDATE aowow_items a, locales_item b SET + a.name_loc2 = b.name_loc2, + a.name_loc3 = b.name_loc3, + a.name_loc6 = b.name_loc6, + a.name_loc8 = b.name_loc8, + a.description_loc2 = b.description_loc2, + a.description_loc3 = b.description_loc3, + a.description_loc6 = b.description_loc6, + a.description_loc8 = b.description_loc8 + WHERE a.id = b.entry; + + -- merge with gemProperties + UPDATE aowow_items a, dbc.gemProperties b SET + a.gemEnchantmentId = b.spellItemEnchantmentId, + a.gemColorMask = b.colorMask + WHERE a.gemColorMask = b.id; + + -- icon + UPDATE aowow_items a, dbc.itemDisplayInfo b SET + a.iconString = b.inventoryIcon1 + WHERE a.displayId = b.id; + + -- Robes => Chest and Ranged (right) => Ranged + UPDATE aowow_items SET slot = 15 WHERE slotbak = 26; + UPDATE aowow_items SET slot = 5 WHERE slotbak = 20; + + -- custom sub-classes + UPDATE aowow_items SET subClassBak = subClass, slot = slotBak; + UPDATE aowow_items SET subclass = IF( + slot = 4, -8, IF( -- shirt + slot = 19, -7, IF( -- tabard + slot = 16, -6, IF( -- cloak + slot = 23, -5, IF( -- held in offhand + slot = 12, -4, IF( -- trinket + slot = 2, -3, IF( -- amulet + slot = 11, -2, subClassBak -- ring + ) + ) + ) + ) + ) + ) + ) + WHERE class = 4; + + +*/ class ItemSetup extends ItemList { @@ -14,13 +314,9 @@ class ItemSetup extends ItemList set_time_limit(300); $conditions = array( - ['i.entry', $start, '>'], - ['i.entry', $end, '<='], - [ - 'OR', - ['class', 4], - ['class', 2] - ], + ['i.id', $start, '>'], + ['i.id', $end, '<='], + ['class', [ITEM_CLASS_WEAPON, ITEM_CLASS_GEM, ITEM_CLASS_ARMOR]], 0 ); @@ -36,10 +332,10 @@ class ItemSetup extends ItemList foreach (@$this->json[$this->id] as $k => $v) { - if (!in_array($k, $this->cols) || !$v) + if (!in_array($k, $this->cols) || !$v || $k == 'id') continue; - $updateFields[$k] = number_format($v, 2, ',', ''); + $updateFields[$k] = number_format($v, 2, '.', ''); } if (isset($this->itemMods[$this->id])) @@ -48,15 +344,14 @@ class ItemSetup extends ItemList { if (!$v) continue; - if ($str = Util::$itemMods[$k]) - $updateFields[$str] = number_format($v, 2, ',', ''); + $updateFields[$str] = number_format($v, 2, '.', ''); } } if ($updateFields) - DB::Aowow()->query('REPLACE INTO ?_item_stats (`itemid`, `'.implode('`, `', array_keys($updateFields)).'`) VALUES (?d, "'.implode('", "', $updateFields).'")', $this->id); + DB::Aowow()->query('REPLACE INTO ?_item_stats (`id`, `'.implode('`, `', array_keys($updateFields)).'`) VALUES (?d, "'.implode('", "', $updateFields).'")', $this->id); } } } diff --git a/setup/tools/sql/_itemset.php b/setup/tools/sql/_itemset.php index f014394c..96133d44 100644 --- a/setup/tools/sql/_itemset.php +++ b/setup/tools/sql/_itemset.php @@ -12,6 +12,13 @@ if (!defined('AOWOW_REVISION')) and i have no idea how to merge the prefixes/suffixes for wotlk-raidsets and arena-sets in gereral onto the name.. at least not for all locales and i'll be damned if i have to skip one */ +/* + ALTER TABLE `aowow_itemset` ADD COLUMN `npieces` tinyint(3) NOT NULL AFTER `bonusParsed`; + UPDATE `aowow_itemset`SET npieces = ( + IF(item1 > 0, 1, 0) + IF(item2 > 0, 1, 0) + IF(item3 > 0, 1, 0) + IF(item4 > 0, 1, 0) + IF(item5 > 0, 1, 0) + + IF(item6 > 0, 1, 0) + IF(item7 > 0, 1, 0) + IF(item8 > 0, 1, 0) + IF(item9 > 0, 1, 0) + IF(item10 > 0, 1, 0) + ); +*/ // script terminates self unexpectedly.. i don't know why..; split calls via ajax if (!isset($_GET['setId'])) @@ -159,7 +166,7 @@ $tagsById = array( 12 => [621,624,625,626,631,632,633,638,639,640,645,648,651,654,655,663,664], // "Tier 4 Raid Set" 13 => [622,627,628,629,634,635,636,641,642,643,646,649,652,656,657,665,666], // "Tier 5 Raid Set", 14 => [620,623,630,637,644,647,650,653,658,659,660,661,662], // "Dungeon Set 3", - 15 => [467,468,469,470,471,472,473483,484,485,486,487,488, 908], // "Arathi Basin Set", + 15 => [467,468,469,470,471,472,473,483,484,485,486,487,488, 908], // "Arathi Basin Set", 16 => [587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,688,689,691,692,693,694,695,696], // "Level 70 PvP Rare Set", 18 => [668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684], // "Tier 6 Raid Set", 21 => [738,739,740,741,742,743,744,745,746,747,748,749,750,751,752], // "Level 70 PvP Rare Set 2", diff --git a/template/achievement.tpl b/template/achievement.tpl index 458a53e7..b374f482 100644 --- a/template/achievement.tpl +++ b/template/achievement.tpl @@ -22,7 +22,7 @@ {$lang.quickFacts}