Files
aowow/includes/types/basetype.class.php
Sarjuuk db1d3ccace Core/Cleanup
* try to give included files a logical structure
 * move objects from Util and Game to their own files
 * make non-essential files auto-loaded
2025-04-01 22:33:36 +02:00

1619 lines
59 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die("illegal access");
abstract class BaseType
{
public $id = 0;
public $error = true;
protected $templates = [];
protected $curTpl = [];
protected $matches = 0; // total matches unaffected by sqlLimit in config
protected $dbNames = ['Aowow']; // multiple DBs in profiler
protected $queryBase = '';
protected $queryOpts = [];
private $itrStack = [];
public static $dataTable = '';
public static $contribute = CONTRIBUTE_ANY;
/*
* condition as array [expression, value, operator]
* expression: str - must match fieldname;
* int - 1: select everything; 0: select nothing
* array - another condition array
* value: str - operator defaults to: LIKE <val>
* int - operator defaults to: = <val>
* array - operator defaults to: IN (<val>)
* null - operator defaults to: IS [NULL]
* 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],
* ['joinedTbl.field', NULL] // NULL must be explicitly specified "['joinedTbl.field']" would be skipped as erronous definition (only really usefull when left-joining)
* 'OR',
* 5
* )
* results in
* WHERE ((`id` = 45) OR (`name` NOT LIKE "test%") OR ((`flags` & 255) AND (`flags2` & 15)) OR ((`mask` & 3) = 0)) OR (`joinedTbl`.`field` IS NULL) LIMIT 5
*/
public function __construct(array $conditions = [], array $miscData = [])
{
$where = [];
$linking = ' AND ';
$limit = Cfg::get('SQL_LIMIT_DEFAULT');
$calcTotal = false;
$totalQuery = '';
if (!$this->queryBase || $conditions === null)
return;
$prefixes = [];
if (preg_match('/FROM \??[\w\_]+( AS)?\s?`?(\w+)`?$/i', $this->queryBase, $match))
$prefixes['base'] = $match[2];
else
$prefixes['base'] = '';
if (!empty($miscData['extraOpts']))
$this->extendQueryOpts($miscData['extraOpts']);
if (!empty($miscData['calcTotal']))
$calcTotal = true;
$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 $sql ? '('.implode($subLink, $sql).')' : null;
}
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];
// numeric allows for formulas e.g. (1 < 3)
if (Util::checkNumeric($f))
return $f;
// skip condition if fieldName contains illegal chars
if (preg_match('/[^\d\w\.\_]/i', $f))
return null;
$f = explode('.', $f);
switch (count($f))
{
case 2:
if (!in_array($f[0], $prefixes))
{
// choose table to join or return null if prefix does not exist
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]) && !empty($c[1]))
{
array_walk($c[1], function(&$item, $key) {
$item = Util::checkNumeric($item) ? $item : DB::Aowow()->escape($item);
});
$op = (isset($c[2]) && $c[2] == '!') ? 'NOT IN' : 'IN';
$val = '('.implode(', ', $c[1]).')';
}
else if (Util::checkNumeric($c[1])) // Note: should this be a NUM_REQ_* check?
{
$op = (isset($c[2]) && $c[2] == '!') ? '<>' : '=';
$val = $c[1];
}
else if (is_string($c[1]))
{
$op = (isset($c[2]) && $c[2] == '!') ? 'NOT LIKE' : 'LIKE';
$val = DB::Aowow()->escape($c[1]);
}
else if (count($c) > 1 && $c[1] === null) // specifficly check for NULL
{
$op = (isset($c[2]) && $c[2] == '!') ? 'IS NOT' : 'IS';
$val = 'NULL';
}
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]);
// prepare usage of guids if using multiple realms (which have non-zoro indizes)
if (key($this->dbNames) != 0)
$this->queryBase = preg_replace('/\s([^\s]+)\sAS\sARRAY_KEY/i', ' CONCAT("DB_IDX", ":", \1) AS ARRAY_KEY', $this->queryBase);
// insert additional selected fields
if ($s = array_column($this->queryOpts, 's'))
$this->queryBase = str_replace('ARRAY_KEY', 'ARRAY_KEY '.implode('', $s), $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_filter(array_column($this->queryOpts, 'g')))
$this->queryBase .= ' GROUP BY '.implode(', ', $g);
// append post filtering
if ($h = array_filter(array_column($this->queryOpts, 'h')))
$this->queryBase .= ' HAVING '.implode(' AND ', $h);
// without applied LIMIT and ORDER
if ($calcTotal)
$totalQuery = $this->queryBase;
// append ordering
if ($o = array_filter(array_column($this->queryOpts, 'o')))
$this->queryBase .= ' ORDER BY '.implode(', ', $o);
// apply limit
if ($limit)
$this->queryBase .= ' LIMIT '.$limit;
// execute query (finally)
$rows = [];
// this is purely because of multiple realms per server
foreach ($this->dbNames as $dbIdx => $n)
{
$query = str_replace('DB_IDX', $dbIdx, $this->queryBase);
if ($rows = DB::{$n}($dbIdx)->select($query))
{
if ($calcTotal)
{
// hackfix the inner items query to not contain duplicate column names
// yes i know the real solution would be to not have items and item_stats share column names
// soon™....
if (get_class($this) == __NAMESPACE__.'\ItemList')
$totalQuery = str_replace([', `is`.*', ', i.id AS id'], '', $totalQuery);
$this->matches += DB::{$n}($dbIdx)->selectCell('SELECT COUNT(*) FROM ('.$totalQuery.') x');
}
foreach ($rows as $id => $row)
{
if (isset($this->templates[$id]))
trigger_error('GUID for List already in use #'.$id.'. Additional occurrence omitted!', E_USER_ERROR);
else
$this->templates[$id] = $row;
}
}
}
if (!$this->templates)
return;
// push first element for instant use
$this->reset();
// all clear
$this->error = false;
}
public function &iterate()
{
$this->itrStack[] = $this->id;
// reset on __construct
$this->reset();
foreach ($this->templates as $id => $__)
{
$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
}
// fforward to old index
$this->reset();
$oldIdx = array_pop($this->itrStack);
do
{
if (key($this->templates) != $oldIdx)
continue;
$this->curTpl = current($this->templates);
$this->id = key($this->templates);
next($this->templates);
break;
}
while (next($this->templates));
}
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 getEntry($id)
{
if (isset($this->templates[$id]))
{
unset($this->curTpl); // kill reference or strange stuff will happen
$this->curTpl = $this->templates[$id];
$this->id = $id;
return $this->templates[$id];
}
return null;
}
public function getField($field, $localized = false, $silent = false)
{
if (!$this->curTpl || (!$localized && !isset($this->curTpl[$field])))
return '';
if ($localized)
return Util::localizedString($this->curTpl, $field, $silent);
$value = $this->curTpl[$field];
Util::checkNumeric($value);
return $value;
}
public function getAllFields($field, $localized = false, $silent = false)
{
$data = [];
foreach ($this->iterate() as $__)
$data[$this->id] = $this->getField($field, $localized, $silent);
return $data;
}
public function getRandomId() : int
{
// ORDER BY RAND() is not optimal, so if anyone has an alternative idea..
$where = User::isInGroup(U_GROUP_EMPLOYEE) ? ' WHERE (`cuFlags` & '.CUSTOM_EXCLUDE_FOR_LISTVIEW.') = 0' : '';
if (preg_match('/SELECT .*? FROM (\?\_[\w_-]+) /i', $this->queryBase, $m))
return DB::Aowow()->selectCell(sprintf('SELECT `id` FROM %s%s ORDER BY RAND() ASC LIMIT 1', $m[1], $where));
return 0;
}
public function getFoundIDs()
{
return array_keys($this->templates);
}
public function getMatches()
{
return $this->matches;
}
protected function extendQueryOpts($extra) // needs to be called from __construct
{
foreach ($extra as $tbl => $sets)
{
foreach ($sets as $module => $value)
{
if (!$value || !is_array($value))
continue;
switch ($module)
{
// additional (str)
case 'g': // group by
case 's': // select
if (!empty($this->queryOpts[$tbl][$module]))
$this->queryOpts[$tbl][$module] .= implode(' ', $value);
else
$this->queryOpts[$tbl][$module] = implode(' ', $value);
break;
case 'h': // having
if (!empty($this->queryOpts[$tbl][$module]))
$this->queryOpts[$tbl][$module] .= implode(' AND ', $value);
else
$this->queryOpts[$tbl][$module] = implode(' AND ', $value);
break;
// additional (arr)
case 'j': // join
if (!empty($this->queryOpts[$tbl][$module]) && is_array($this->queryOpts[$tbl][$module]))
$this->queryOpts[$tbl][$module][0][] = $value;
else
$this->queryOpts[$tbl][$module] = $value;
break;
// replacement (str)
case 'l': // limit
case 'o': // order by
$this->queryOpts[$tbl][$module] = $value[0];
break;
}
}
}
}
/* source More .. keys seen used
'n': name [always set]
't': type [always set]
'ti': typeId [always set]
'bd': BossDrop [0; 1] [Creature / GO]
'dd': DungeonDifficulty [-2: DungeonHC; -1: DungeonNM; 1: Raid10NM; 2:Raid25NM; 3:Raid10HM; 4: Raid25HM] [Creature / GO]
'q': cssQuality [Items]
'z': zone [set when all happens in here]
'p': PvP [pvpSourceId]
's': Type::TITLE: side; Type::SPELL: skillId (yeah, double use. Ain't life just grand)
'c': category [Spells / Quests]
'c2': subCat [Quests]
'icon': iconString
*/
public function getSourceData(int $id = 0) : array { return []; }
// 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 getJSGlobals($addMask = GLOBALINFO_ANY);
// NPC, GO, Item, Quest, Spell, Achievement, Profile would require this
abstract public function renderTooltip();
}
trait listviewHelper
{
public function hasSetFields(?string ...$fields) : int
{
$result = 0x0;
foreach ($this->iterate() as $__)
{
foreach ($fields as $k => $str)
{
if (!$str)
{
unset($fields[$k]);
continue;
}
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(?string ...$fields) : int
{
$base = [];
$result = 0x0;
foreach ($fields as $k => $str)
$base[$str] = $this->getField($str);
foreach ($this->iterate() as $__)
{
foreach ($fields as $k => $str)
{
if (!$str)
{
unset($fields[$k]);
continue;
}
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;
}
}
/*
!IMPORTANT!
It is flat out impossible to distinguish between floors for multi-level areas, if the floors overlap each other!
The coordinates generated by the script WILL be on every level and will have to be removed MANUALLY!
impossible := you are not keen on reading wmo-data;
*/
trait spawnHelper
{
private $spawnResult = array(
SPAWNINFO_FULL => null,
SPAWNINFO_SHORT => null,
SPAWNINFO_ZONES => null,
SPAWNINFO_QUEST => null
);
private function createShortSpawns() // [zoneId, floor, [[x1, y1], [x2, y2], ..]] as tooltip2 if enabled by <a rel="map" ...> or anchor #map (one area, one floor, one creature, no survivors)
{
$this->spawnResult[SPAWNINFO_SHORT] = new \StdClass;
// first get zone/floor with the most spawns
if ($res = DB::Aowow()->selectRow('SELECT `areaId`, `floor` FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d AND `posX` > 0 AND `posY` > 0 GROUP BY `areaId`, `floor` ORDER BY COUNT(1) DESC LIMIT 1', self::$type, $this->id))
{
// get relevant spawn points
$points = DB::Aowow()->select('SELECT `posX`, `posY` FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d AND `areaId` = ?d AND `floor` = ?d AND `posX` > 0 AND `posY` > 0', self::$type, $this->id, $res['areaId'], $res['floor']);
$spawns = [];
foreach ($points as $p)
$spawns[] = [$p['posX'], $p['posY']];
$this->spawnResult[SPAWNINFO_SHORT]->zone = $res['areaId'];
$this->spawnResult[SPAWNINFO_SHORT]->coords = [$res['floor'] => $spawns];
}
}
private function createFullSpawns() // for display on map (object/npc detail page)
{
$data = [];
$wpSum = [];
$wpIdx = 0;
$worldPos = [];
$spawns = DB::Aowow()->select("SELECT * FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d AND `posX` > 0 AND `posY` > 0", self::$type, $this->id);
if (!$spawns)
return;
if (User::isInGroup(U_GROUP_MODERATOR))
if ($guids = array_column(array_filter($spawns, fn($x) => $x['guid'] > 0 || $x['type'] != Type::NPC), 'guid'))
$worldPos = WorldPosition::getForGUID(self::$type, ...$guids);
foreach ($spawns as $s)
{
$isAccessory = $s['guid'] < 0 && $s['type'] == Type::NPC;
// check, if we can attach waypoints to creature
// we will get a nice clusterfuck of dots if we do this for more GUIDs, than we have colors though
if (count($spawns) < 6 && $s['type'] == Type::NPC)
{
if ($wPoints = DB::Aowow()->select('SELECT * FROM ?_creature_waypoints WHERE creatureOrPath = ?d AND floor = ?d', $s['pathId'] ? -$s['pathId'] : $this->id, $s['floor']))
{
foreach ($wPoints as $i => $p)
{
$label = [Lang::npc('waypoint').Lang::main('colon').$p['point']];
if ($p['wait'])
$label[] = Lang::npc('wait').Lang::main('colon').Util::formatTime($p['wait'], false);
$opts = array( // \0 doesn't get printed and tricks Util::toJSON() into handling this as a string .. i feel slightly dirty now
'label' => "\0$<br><span class=\"q0\">".implode('<br>', $label).'</span>',
'type' => $wpIdx
);
// connective line
if ($i > 0 && $wPoints[$i - 1]['areaId'] == $p['areaId'])
$opts['lines'] = [[$wPoints[$i - 1]['posX'], $wPoints[$i - 1]['posY']]];
$data[$p['areaId']][$p['floor']]['coords'][] = [$p['posX'], $p['posY'], $opts];
if (empty($wpSum[$p['areaId']][$p['floor']]))
$wpSum[$p['areaId']][$p['floor']] = 1;
else
$wpSum[$p['areaId']][$p['floor']]++;
}
$wpIdx++;
}
}
$opts = $menu = $tt = $info = [];
$footer = '';
if ($s['respawn'] > 0)
$info[1] = '<span class="q0">'.Lang::npc('respawnIn', [Lang::formatTime($s['respawn'] * 1000, 'game', 'timeAbbrev', true)]).'</span>';
else if ($s['respawn'] < 0)
{
$info[1] = '<span class="q0">'.Lang::npc('despawnAfter', [Lang::formatTime(-$s['respawn'] * 1000, 'game', 'timeAbbrev', true)]).'</span>';
$opts['type'] = 4; // make pip purple
}
if (User::isInGroup(U_GROUP_STAFF))
{
if ($isAccessory)
$info[0] = 'Vehicle Accessory';
else if ($s['guid'] > 0 && ($s['type'] == Type::NPC || $s['type'] == Type::OBJECT))
$info[0] = 'GUID'.Lang::main('colon').$s['guid'];
if ($s['phaseMask'] > 1 && ($s['phaseMask'] & 0xFFFF) != 0xFFFF)
$info[2] = Lang::game('phases').Lang::main('colon').Util::asHex($s['phaseMask']);
if ($s['spawnMask'] == 15)
$info[3] = Lang::game('mode').Lang::main('colon').Lang::game('modes', -1);
else if ($s['spawnMask'])
{
$_ = [];
for ($i = 0; $i < 4; $i++)
if ($s['spawnMask'] & 1 << $i)
$_[] = Lang::game('modes', $i);
$info[4] = Lang::game('mode').Lang::main('colon').implode(', ', $_);
}
if ($s['type'] == Type::AREATRIGGER)
{
// teleporter endpoint
if ($s['guid'] < 0)
{
$opts['type'] = 4;
$info[5] = 'Teleport Destination';
}
else
{
$o = Util::O2Deg($this->getField('orientation'));
$info[5] = 'Orientation'.Lang::main('colon').$o[0].'° ('.$o[1].')';
}
}
// guid < 0 are vehicle accessories. those are moved by moving the vehicle
if (User::isInGroup(U_GROUP_MODERATOR) && $worldPos && !$isAccessory && isset($worldPos[$s['guid']]))
$menu = Util::buildPosFixMenu($worldPos[$s['guid']]['mapId'], $worldPos[$s['guid']]['posX'], $worldPos[$s['guid']]['posY'], $s['type'], $s['guid'], $s['areaId'], $s['floor']);
if ($menu)
$footer = '<br /><span class="q2">Click to move pin</span>';
}
if ($info)
$tt['info'] = $info;
if ($footer)
$tt['footer'] = $footer;
if ($tt)
$opts['tooltip'] = [$this->getField('name', true) => $tt];
if ($menu)
$opts['menu'] = $menu;
$data[$s['areaId']] [$s['floor']] ['coords'] [] = [$s['posX'], $s['posY'], $opts];
}
foreach ($data as $a => &$areas)
foreach ($areas as $f => &$floor)
$floor['count'] = count($floor['coords']) - (!empty($wpSum[$a][$f]) ? $wpSum[$a][$f] : 0);
uasort($data, array($this, 'sortBySpawnCount'));
$this->spawnResult[SPAWNINFO_FULL] = $data;
}
private function sortBySpawnCount($a, $b)
{
$aCount = current($a)['count'];
$bCount = current($b)['count'];
if ($aCount == $bCount) {
return 0;
}
return ($aCount < $bCount) ? 1 : -1;
}
private function createZoneSpawns() // [zoneId1, zoneId2, ..] for locations-column in listview
{
$res = DB::Aowow()->selectCol("SELECT `typeId` AS ARRAY_KEY, GROUP_CONCAT(DISTINCT `areaId`) FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) AND `posX` > 0 AND `posY` > 0 GROUP BY `typeId`", self::$type, $this->getfoundIDs());
foreach ($res as &$r)
{
$r = explode(',', $r);
if (count($r) > 3)
array_splice($r, 3, count($r), -1);
}
$this->spawnResult[SPAWNINFO_ZONES] = $res;
}
private function createQuestSpawns() // [zoneId => [floor => [[x1, y1], [x2, y2], ..]]] mapper on quest detail page
{
if (self::$type == Type::SOUND)
return;
$res = DB::Aowow()->select('SELECT `areaId`, `floor`, `typeId`, `posX`, `posY` FROM ?_spawns WHERE `type` = ?d AND `typeId` IN (?a) AND `posX` > 0 AND `posY` > 0', self::$type, $this->getFoundIDs());
$spawns = [];
foreach ($res as $data)
{
// zone => floor => spawnData
// todo (low): why is there a single set of coordinates; which one should be picked, instead of the first? gets used in ShowOnMap.buildTooltip i think
if (!isset($spawns[$data['areaId']][$data['floor']][$data['typeId']]))
{
$spawns[$data['areaId']][$data['floor']][$data['typeId']] = array(
'type' => self::$type,
'id' => $data['typeId'],
'point' => '', // tbd later (start, end, requirement, sourcestart, sourceend, sourcerequirement)
'name' => Util::localizedString($this->templates[$data['typeId']], 'name'),
'coord' => [$data['posX'], $data['posY']],
'coords' => [[$data['posX'], $data['posY']]],
'objective' => 0, // tbd later (1-4 set a color; id of creature this entry gives credit for)
'reactalliance' => $this->templates[$data['typeId']]['A'] ?: 0,
'reacthorde' => $this->templates[$data['typeId']]['H'] ?: 0
);
}
else
$spawns[$data['areaId']][$data['floor']][$data['typeId']]['coords'][] = [$data['posX'], $data['posY']];
}
$this->spawnResult[SPAWNINFO_QUEST] = $spawns;
}
public function getSpawns($mode)
{
// only Creatures, GOs and SoundEmitters can be spawned
if (!self::$type || !$this->getfoundIDs() || (self::$type != Type::NPC && self::$type != Type::OBJECT && self::$type != Type::SOUND && self::$type != Type::AREATRIGGER))
return [];
switch ($mode)
{
case SPAWNINFO_SHORT:
if ($this->spawnResult[SPAWNINFO_SHORT] === null)
$this->createShortSpawns();
return $this->spawnResult[SPAWNINFO_SHORT];
case SPAWNINFO_FULL:
if (empty($this->spawnResult[SPAWNINFO_FULL]))
$this->createFullSpawns();
return $this->spawnResult[SPAWNINFO_FULL];
case SPAWNINFO_ZONES:
if (empty($this->spawnResult[SPAWNINFO_ZONES]))
$this->createZoneSpawns();
return !empty($this->spawnResult[SPAWNINFO_ZONES][$this->id]) ? $this->spawnResult[SPAWNINFO_ZONES][$this->id] : [];
case SPAWNINFO_QUEST:
if (empty($this->spawnResult[SPAWNINFO_QUEST]))
$this->createQuestSpawns();
return $this->spawnResult[SPAWNINFO_QUEST];
}
return [];
}
}
trait profilerHelper
{
public static $type = 0; // arena teams dont actually have one
public static $brickFile = 'profile'; // profile is multipurpose
private static $subjectGUID = 0;
public function selectRealms($fi)
{
$this->dbNames = [];
foreach(Profiler::getRealms() as $idx => $r)
{
if (!empty($fi['sv']) && Profiler::urlize($r['name']) != Profiler::urlize($fi['sv']) && intVal($fi['sv']) != $idx)
continue;
if (!empty($fi['rg']) && Profiler::urlize($r['region']) != Profiler::urlize($fi['rg']))
continue;
$this->dbNames[$idx] = 'Characters';
}
return !!$this->dbNames;
}
}
trait sourceHelper
{
protected $sources = [];
protected $sourceMore = null;
public function getSources(?array &$s = [], ?array &$sm = []) : bool
{
$s = $sm = [];
if (empty($this->sources[$this->id]))
return false;
if ($this->sourceMore === null)
{
$buff = [];
$this->sourceMore = [];
foreach ($this->iterate() as $_curTpl)
if ($_curTpl['moreType'] && $_curTpl['moreTypeId'])
$buff[$_curTpl['moreType']][] = $_curTpl['moreTypeId'];
foreach ($buff as $type => $ids)
$this->sourceMore[$type] = Type::newList($type, [Cfg::get('SQL_LIMIT_NONE'), ['id', $ids]]);
}
$s = array_keys($this->sources[$this->id]);
if ($this->curTpl['moreType'] && $this->curTpl['moreTypeId'] && ($srcData = $this->sourceMore[$this->curTpl['moreType']]->getSourceData($this->curTpl['moreTypeId'])))
$sm = $srcData[$this->curTpl['moreTypeId']];
else if (!empty($this->sources[$this->id][SRC_PVP]))
$sm['p'] = $this->sources[$this->id][SRC_PVP][0];
if ($z = $this->curTpl['moreZoneId'])
$sm['z'] = $z;
if ($this->curTpl['moreMask'] & SRC_FLAG_BOSSDROP)
$sm['bd'] = 1;
if (isset($this->sources[$this->id][SRC_DROP][0]))
{
/*
mode srcFlag log2 dd Flag
10N/D-NH 0b0001 0 0b001
25N/D-HC 0b0010 1 0b010
10H 0b0100 2 0b011
25H 0b1000 3 0b100
*/
if ($this->curTpl['moreMask'] & SRC_FLAG_DUNGEON_DROP)
$sm['dd'] = $this->sources[$this->id][SRC_DROP][0] * -1;
else if ($this->curTpl['moreMask'] & SRC_FLAG_RAID_DROP)
{
$dd = log($this->sources[$this->id][SRC_DROP][0], 2);
if ($dd == intVal($dd)) // only one bit set
$sm['dd'] = $dd + 1;
}
}
if ($sm)
$sm = [$sm];
return true;
}
}
abstract class Filter
{
private static $wCards = ['*' => '%', '?' => '_'];
public const CR_BOOLEAN = 1;
public const CR_FLAG = 2;
public const CR_NUMERIC = 3;
public const CR_STRING = 4;
public const CR_ENUM = 5;
public const CR_STAFFFLAG = 6;
public const CR_CALLBACK = 7;
public const CR_NYI_PH = 999;
public const V_EQUAL = 8;
public const V_RANGE = 9;
public const V_LIST = 10;
public const V_CALLBACK = 11;
public const V_REGEX = 12;
protected const ENUM_ANY = -2323;
protected const ENUM_NONE = -2324;
protected const PATTERN_NAME = '/[\p{C};%\\\\]/ui';
protected const PATTERN_CRV = '/[\p{C};:%\\\\]/ui';
protected const PATTERN_INT = '/\D/';
protected const ENUM_FACTION = array( 469, 1037, 1106, 529, 1012, 87, 21, 910, 609, 942, 909, 530, 69, 577, 930, 1068, 1104, 729, 369, 92,
54, 946, 67, 1052, 749, 47, 989, 1090, 1098, 978, 1011, 93, 1015, 1038, 76, 470, 349, 1031, 1077, 809,
911, 890, 970, 169, 730, 72, 70, 932, 1156, 933, 510, 1126, 1067, 1073, 509, 941, 1105, 990, 934, 935,
1094, 1119, 1124, 1064, 967, 1091, 59, 947, 81, 576, 922, 68, 1050, 1085, 889, 589, 270);
protected const ENUM_CURRENCY = array(32572, 32569, 29736, 44128, 20560, 20559, 29434, 37829, 23247, 44990, 24368, 52027, 52030, 43016, 41596, 34052, 45624, 49426, 40752, 47241,
40753, 29024, 24245, 26045, 26044, 38425, 29735, 24579, 24581, 32897, 22484, 52026, 52029, 4291, 28558, 43228, 34664, 47242, 52025, 52028,
37836, 20558, 34597, 43589);
protected const ENUM_EVENT = array( 372, 283, 285, 353, 420, 400, 284, 201, 374, 409, 141, 324, 321, 424, 423, 327, 341, 181, 404, 398,
301);
protected const ENUM_ZONE = array( 4494, 36, 2597, 3358, 45, 331, 3790, 4277, 16, 3524, 3, 3959, 719, 1584, 25, 1583, 2677, 3702, 3522, 4,
3525, 3537, 46, 1941, 2918, 3905, 4024, 2817, 4395, 4378, 148, 393, 1657, 41, 2257, 405, 2557, 65, 4196, 1,
14, 10, 15, 139, 12, 3430, 3820, 361, 357, 3433, 721, 394, 3923, 4416, 2917, 4272, 4820, 4264, 3483, 3562,
267, 495, 4742, 3606, 210, 4812, 1537, 4710, 4080, 3457, 38, 4131, 3836, 3792, 2100, 2717, 493, 215, 3518, 3698,
3456, 3523, 2367, 2159, 1637, 4813, 4298, 2437, 722, 491, 44, 3429, 3968, 796, 2057, 51, 3607, 3791, 3789, 209,
3520, 3703, 3711, 1377, 3487, 130, 3679, 406, 1519, 4384, 33, 2017, 1477, 4075, 8, 440, 141, 3428, 3519, 3848,
17, 2366, 3840, 3713, 3847, 3775, 4100, 1581, 3557, 3845, 4500, 4809, 47, 3849, 4265, 4493, 4228, 3698, 4406, 3714,
3717, 3715, 717, 67, 3716, 457, 4415, 400, 1638, 1216, 85, 4723, 4722, 1337, 4273, 490, 1497, 206, 1196, 4603,
718, 3277, 28, 40, 11, 4197, 618, 3521, 3805, 66, 1176, 1977);
protected const ENUM_HEROICDUNGEON = array( 4494, 3790, 4277, 4196, 4416, 4272, 4820, 4264, 3562, 4131, 3792, 2367, 4813, 3791, 3789, 3848, 2366, 3713, 3847, 4100,
4809, 3849, 4265, 4228, 3714, 3717, 3715, 3716, 4415, 4723, 206, 1196);
protected const ENUM_MULTIMODERAID = array( 4812, 3456, 2159, 4500, 4493, 4722, 4273, 4603, 4987);
protected const ENUM_HEROICRAID = array( 4987, 4812, 4722);
protected const ENUM_CLASSS = array( null, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, 11, true, false);
protected const ENUM_RACE = array( null, 1, 2, 3, 4, 5, 6, 7, 8, null, 10, 11, true, false);
protected const ENUM_PROFESSION = array( null, 171, 164, 185, 333, 202, 129, 755, 165, 186, 197, true, false, 356, 182, 773);
public $error = false; // erronous search fields
private $cndSet = [];
/* genericFilter: [FILTER_TYPE, colOrFnName, param1, param2]
[self::CR_BOOLEAN, <string:colName>, <bool:isString>, null]
[self::CR_FLAG, <string:colName>, <int:testBit>, <bool:matchAny>] # default param2: matchExact
[self::CR_NUMERIC, <string:colName>, <int:NUM_FLAGS>, <bool:addExtraCol>]
[self::CR_STRING, <string:colName>, <int:STR_FLAGS>, null]
[self::CR_ENUM, <string:colName>, <bool:ANYNONE>, <bool:isEnumVal>] # param3 ? crv is val in enum : key in enum
[self::CR_STAFFFLAG, <string:colName>, null, null]
[self::CR_CALLBACK, <string:fnName>, <mixed:param1>, <mixed:param2>]
[self::CR_NYI_PH, null, <int:returnVal>, param2] # mostly 1: to ignore this criterium; 0: to fail the whole query
*/
protected $genericFilter = [];
protected $enums = []; // criteriumID => [validOptionList]
/*
fieldId => [checkType, checkValue[, fieldIsArray]]
*/
protected $inputFields = []; // list of input fields defined per page
protected $parentCats = []; // used to validate ty-filter
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
'reputationCols' => [] // simlar and exclusive to extraCols - added as required
);
// parse the provided request into a usable format
public function __construct(bool $fromPOST = false, array $opts = [])
{
if (!empty($opts['parentCats']))
$this->parentCats = $opts['parentCats'];
if ($fromPOST)
$this->evaluatePOST();
else
{
// an error occured, while processing POST
if (isset($_SESSION['fiError']))
{
$this->error = $_SESSION['fiError'] == get_class($this);
unset($_SESSION['fiError']);
}
$this->evaluateGET();
}
}
// use to generate cacheKey for filterable pages
public function __sleep()
{
return ['formData'];
}
public function mergeCat(&$cats) : void
{
foreach ($this->parentCats as $idx => $cat)
$cats[$idx] = $cat;
}
private function &criteriaIterator() : \Generator
{
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;
}
}
/***********************/
/* get prepared values */
/***********************/
public function getFilterString(array $override = [], array $addCr = []) : string
{
$filterURL = [];
foreach (array_merge($this->fiData['c'], $this->fiData['v'], $override) as $k => $v)
{
if (isset($addCr[$k]))
{
$v = $v ? array_merge((array)$v, (array)$addCr[$k]) : $addCr[$k];
unset($addCr[$k]);
}
if (is_array($v) && !empty($v))
$filterURL[$k] = $k.'='.implode(':', $v);
else if ($v !== '')
$filterURL[$k] = $k.'='.$v;
}
// no criteria were set, so no merge occured .. append
if ($addCr)
{
$filterURL['cr'] = 'cr='.$addCr['cr'];
$filterURL['crs'] = 'crs='.$addCr['crs'];
$filterURL['crv'] = 'crv='.$addCr['crv'];
}
return implode(';', $filterURL);
}
// [<string>ExtraCol, ...]
public function getExtraCols() : array
{
return array_unique($this->formData['extraCols']);
}
// ['cr' => <array>Criterium, 'crs' => <array>CriteriumSign, 'crv' => <array>CriteriumValue]
public function getSetCriteria() : array
{
return $this->formData['setCriteria'];
}
// [<array>WeightID, <array>WeightValue]
public function getSetWeights() : array
{
return $this->formData['setWeights'];
}
// [<string>ExtraCol, ...]
public function getReputationCols() : array
{
return $this->formData['reputationCols'];
}
// [inputField => FieldValue, ...]
public function getForm() : array
{
return $this->formData['form'];
}
public function getConditions() : array
{
if (!$this->cndSet)
{
// values
$this->cndSet = $this->createSQLForValues();
// criteria
foreach ($this->criteriaIterator() as &$_cr)
if ($cnd = $this->createSQLForCriterium(...$_cr))
$this->cndSet[] = $cnd;
if ($this->cndSet)
array_unshift($this->cndSet, empty($this->fiData['v']['ma']) ? 'AND' : 'OR');
}
return $this->cndSet;
}
/**********************/
/* input sanitization */
/**********************/
private function evaluatePOST() : void
{
// doesn't need to set formData['form']; this happens in GET-step
foreach ($this->inputFields as $inp => [$type, $valid, $asArray])
{
if (!isset($_POST[$inp]) || $_POST[$inp] === '')
continue;
$val = $_POST[$inp];
$k = in_array($inp, ['cr', 'crs', 'crv']) ? 'c' : 'v';
if ($asArray)
{
$buff = [];
foreach ((array)$val as $v)
if ($v !== '' && $this->checkInput($type, $valid, $v) && $v !== '')
$buff[] = $v;
if ($buff)
$this->fiData[$k][$inp] = $buff;
}
else if ($val !== '' && $this->checkInput($type, $valid, $val) && $val !== '')
$this->fiData[$k][$inp] = $val;
}
$this->setWeights();
$this->setCriteria();
}
private function evaluateGET() : void
{
if (empty($_GET['filter']))
return;
// squash into usable format
$post = [];
foreach (explode(';', $_GET['filter']) as $f)
{
if (!strstr($f, '='))
{
$this->error = true;
continue;
}
$_ = explode('=', $f);
$post[$_[0]] = $_[1];
}
foreach ($this->inputFields as $inp => [$type, $valid, $asArray])
{
if (!isset($post[$inp]) || $post[$inp] === '')
continue;
$val = $post[$inp];
$k = in_array($inp, ['cr', 'crs', 'crv']) ? 'c' : 'v';
if ($asArray)
{
$buff = [];
foreach (explode(':', $val) as $v)
if ($v !== '' && $this->checkInput($type, $valid, $v) && $v !== '')
$buff[] = $v;
if ($buff)
{
if ($k == 'v')
$this->formData['form'][$inp] = $buff;
$this->fiData[$k][$inp] = $buff;
}
}
else if ($val !== '' && $this->checkInput($type, $valid, $val) && $val !== '')
{
if ($k == 'v')
$this->formData['form'][$inp] = $val;
$this->fiData[$k][$inp] = $val;
}
}
$this->setWeights();
$this->setCriteria();
}
private function setCriteria() : void // [cr]iterium, [cr].[s]ign, [cr].[v]alue
{
if (empty($this->fiData['c']['cr']) && empty($this->fiData['c']['crs']) && empty($this->fiData['c']['crv']))
return;
else if (empty($this->fiData['c']['cr']) || empty($this->fiData['c']['crs']) || empty($this->fiData['c']['crv']))
{
unset($this->fiData['c']['cr']);
unset($this->fiData['c']['crs']);
unset($this->fiData['c']['crv']);
$this->error = true;
return;
}
$_cr = &$this->fiData['c']['cr'];
$_crs = &$this->fiData['c']['crs'];
$_crv = &$this->fiData['c']['crv'];
if (count($_cr) != count($_crv) || count($_cr) != count($_crs) || count($_cr) > 5 || count($_crs) > 5 /*|| count($_crv) > 5*/)
{
// use min provided criterion as basis; 5 criteria at most
$min = max(5, min(count($_cr), count($_crv), count($_crs)));
if (count($_cr) > $min)
array_splice($_cr, $min);
if (count($_crv) > $min)
array_splice($_crv, $min);
if (count($_crs) > $min)
array_splice($_crs, $min);
$this->error = true;
}
for ($i = 0; $i < count($_cr); $i++)
{
// conduct filter specific checks & casts here
$unsetme = false;
if (isset($this->genericFilter[$_cr[$i]]))
{
$gf = $this->genericFilter[$_cr[$i]];
switch ($gf[0])
{
case self::CR_NUMERIC:
$_ = $_crs[$i];
if (!Util::checkNumeric($_crv[$i], $gf[2]) || !$this->int2Op($_))
$unsetme = true;
break;
case self::CR_BOOLEAN:
case self::CR_FLAG:
$_ = $_crs[$i];
if (!$this->int2Bool($_))
$unsetme = true;
break;
case self::CR_ENUM:
case self::CR_STAFFFLAG:
if (!Util::checkNumeric($_crs[$i], NUM_CAST_INT))
$unsetme = true;
break;
}
}
if (!$unsetme && intval($_cr[$i]) && $_crs[$i] !== '' && $_crv[$i] !== '')
continue;
unset($_cr[$i]);
unset($_crs[$i]);
unset($_crv[$i]);
$this->error = true;
}
$this->formData['setCriteria'] = array(
'cr' => $_cr,
'crs' => $_crs,
'crv' => $_crv
);
}
private function setWeights() : void
{
// both empty: not in use
if (empty($this->fiData['v']['wt']) && empty($this->fiData['v']['wtv']))
return;
// one empty: erroneous manual input?
if (empty($this->fiData['v']['wt']) || empty($this->fiData['v']['wtv']))
{
unset($this->fiData['v']['wt']);
unset($this->fiData['v']['wtv']);
$this->error = true;
return;
}
$_wt = &$this->fiData['v']['wt'];
$_wtv = &$this->fiData['v']['wtv'];
$nwt = count($_wt);
$nwtv = count($_wtv);
if ($nwt > $nwtv)
{
array_splice($_wt, $nwtv);
$this->error = true;
}
else if ($nwtv > $nwt)
{
array_splice($_wtv, $nwt);
$this->error = true;
}
$this->formData['setWeights'] = [$_wt, $_wtv];
}
protected function checkInput(int $type, mixed $valid, mixed &$val, bool $recursive = false) : bool
{
switch ($type)
{
case self::V_EQUAL:
if (gettype($valid) == 'integer')
$val = intval($val);
else if (gettype($valid) == 'double')
$val = floatval($val);
else /* if (gettype($valid) == 'string') */
$val = strval($val);
if ($valid == $val)
return true;
break;
case self::V_LIST:
if (!Util::checkNumeric($val, NUM_CAST_INT))
return false;
foreach ($valid as $k => $v)
{
if (gettype($v) != 'array')
continue;
if ($this->checkInput(self::V_RANGE, $v, $val, true))
return true;
unset($valid[$k]);
}
if (in_array($val, $valid))
return true;
break;
case self::V_RANGE:
if (Util::checkNumeric($val, NUM_CAST_INT) && $val >= $valid[0] && $val <= $valid[1])
return true;
break;
case self::V_CALLBACK:
if ($this->$valid($val))
return true;
break;
case self::V_REGEX:
if (!preg_match($valid, $val))
return true;
break;
}
if (!$recursive)
$this->error = true;
return false;
}
protected function transformString(string $string, bool $exact) : string
{
// escape manually entered _; entering % should be prohibited
$string = str_replace('_', '\\_', $string);
// now replace search wildcards with sql wildcards
$string = strtr($string, self::$wCards);
return sprintf($exact ? '%s' : '%%%s%%', $string);
}
protected function modularizeString(array $fields, string $string = '', bool $exact = false, bool $shortStr = false) : array
{
if (!$string && !empty($this->fiData['v']['na']))
$string = $this->fiData['v']['na'];
$qry = [];
foreach ($fields as $f)
{
$sub = [];
$parts = $exact ? [$string] : array_filter(explode(' ', $string));
foreach ($parts as $p)
{
if ($p[0] == '-' && (mb_strlen($p) > 3 || $shortStr))
$sub[] = [$f, $this->transformString(mb_substr($p, 1), $exact), '!'];
else if ($p[0] != '-' && (mb_strlen($p) > 2 || $shortStr))
$sub[] = [$f, $this->transformString($p, $exact)];
}
// single cnd?
if (!$sub)
continue;
else if (count($sub) > 1)
array_unshift($sub, 'AND');
else
$sub = $sub[0];
$qry[] = $sub;
}
// single cnd?
if (!$qry)
$this->error = true;
else if (count($qry) > 1)
array_unshift($qry, 'OR');
else
$qry = $qry[0];
return $qry;
}
protected function int2Op(mixed &$op) : bool
{
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;
case 6: $op = '!='; return true;
default: return false;
}
}
protected function int2Bool(mixed &$op) : bool
{
switch ($op)
{
case 1: $op = true; return true;
case 2: $op = false; return true;
default: return false;
}
}
protected function list2Mask(array $list, bool $noOffset = false) : int
{
$mask = 0x0;
$o = $noOffset ? 0 : 1; // schoolMask requires this..?
foreach ($list as $itm)
$mask += (1 << (intval($itm) - $o));
return $mask;
}
/**************************/
/* create conditions from */
/* generic criteria */
/**************************/
private function genericBoolean($field, $op, bool $isString) : ?array
{
if ($this->int2Bool($op))
{
$value = $isString ? '' : 0;
$operator = $op ? '!' : null;
return [$field, $value, $operator];
}
return null;
}
private function genericBooleanFlags($field, $value, $op, ?bool $matchAny = false) : ?array
{
if (!$this->int2Bool($op))
return null;
if (!$op)
return [[$field, $value, '&'], 0];
else if ($matchAny)
return [[$field, $value, '&'], 0, '!'];
else
return [[$field, $value, '&'], $value];
}
private function genericString($field, $value, $strFlags) : ?array
{
if ($strFlags & STR_LOCALIZED)
$field .= '_loc'.Lang::getLocale()->value;
return $this->modularizeString([$field], (string)$value, $strFlags & STR_MATCH_EXACT, $strFlags & STR_ALLOW_SHORT);
}
private function genericNumeric($field, $value, $op, $typeCast) : ?array
{
if (!Util::checkNumeric($value, $typeCast))
return null;
if ($this->int2Op($op))
return [$field, $value, $op];
return null;
}
private function genericEnum($field, $value) : ?array
{
if (is_bool($value))
return [$field, 0, ($value ? '>' : '<=')];
else if ($value == self::ENUM_ANY)
return [$field, 0, '!'];
else if ($value == self::ENUM_NONE)
return [$field, 0];
else if ($value !== null)
return [$field, $value];
return null;
}
protected function genericCriterion(int $cr, int $crs, string $crv) : ?array
{
[$crType, $colOrFn, $param1, $param2] = array_pad($this->genericFilter[$cr], 4, null);
$result = null;
switch ($crType)
{
case self::CR_NUMERIC:
$result = $this->genericNumeric($colOrFn, $crv, $crs, $param1);
break;
case self::CR_FLAG:
$result = $this->genericBooleanFlags($colOrFn, $param1, $crs, $param2);
break;
case self::CR_STAFFFLAG:
if (User::isInGroup(U_GROUP_EMPLOYEE) && $crs > 0)
$result = $this->genericBooleanFlags($colOrFn, (1 << ($crs - 1)), true);
break;
case self::CR_BOOLEAN:
$result = $this->genericBoolean($colOrFn, $crs, !empty($param1));
break;
case self::CR_STRING:
$result = $this->genericString($colOrFn, $crv, $param1);
break;
case self::CR_ENUM:
if (!$param2 && isset($this->enums[$cr][$crs]))
$result = $this->genericEnum($colOrFn, $this->enums[$cr][$crs]);
if ($param2 && in_array($crs, $this->enums[$cr]))
$result = $this->genericEnum($colOrFn, $crs);
else if ($param1 && ($crs == self::ENUM_ANY || $crs == self::ENUM_NONE))
$result = $this->genericEnum($colOrFn, $crs);
break;
case self::CR_CALLBACK:
$result = $this->{$colOrFn}($cr, $crs, $crv, $param1, $param2);
break;
case self::CR_NYI_PH: // do not limit with not implemented filters
if (is_int($param1))
return [$param1];
// for nonsensical values; compare against 0
if ($this->int2Op($crs) && Util::checkNumeric($crv))
{
if ($crs == '=')
$crs = '==';
return eval('return ('.$crv.' '.$crs.' 0);') ? [1] : [0];
}
else
return [0];
}
if ($result && $crType == self::CR_NUMERIC && !empty($param2))
$this->formData['extraCols'][] = $cr;
return $result;
}
/***********************************/
/* create conditions from */
/* non-generic values and criteria */
/***********************************/
protected function createSQLForCriterium(int &$cr, int &$crs, string &$crv) : array
{
if (!$this->genericFilter) // criteria not in use - no error
return [];
if (in_array($cr, array_keys($this->genericFilter)))
if ($genCr = $this->genericCriterion($cr, $crs, $crv))
return $genCr;
$this->error = true;
trigger_error('Filter::createSQLForCriterium - received unhandled criterium: ["'.$cr.'", "'.$crs.'", "'.$crv.'"]', E_USER_WARNING);
unset($cr, $crs, $crv);
return [];
}
abstract protected function createSQLForValues();
}
?>