mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
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
This commit is contained in:
@@ -116,397 +116,6 @@ trait TrRequestData
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CLI
|
||||
{
|
||||
private const CHR_BELL = 7;
|
||||
private const CHR_BACK = 8;
|
||||
private const CHR_TAB = 9;
|
||||
private const CHR_LF = 10;
|
||||
private const CHR_CR = 13;
|
||||
private const CHR_ESC = 27;
|
||||
private const CHR_BACKSPACE = 127;
|
||||
|
||||
public const LOG_NONE = -1;
|
||||
public const LOG_BLANK = 0;
|
||||
public const LOG_ERROR = 1;
|
||||
public const LOG_WARN = 2;
|
||||
public const LOG_INFO = 3;
|
||||
public const LOG_OK = 4;
|
||||
|
||||
private static $logHandle = null;
|
||||
private static $hasReadline = null;
|
||||
|
||||
private static $overwriteLast = false;
|
||||
|
||||
/********************/
|
||||
/* formatted output */
|
||||
/********************/
|
||||
|
||||
public static function writeTable(array $out, bool $timestamp = false, bool $headless = false) : void
|
||||
{
|
||||
if (!$out)
|
||||
return;
|
||||
|
||||
$pads = [];
|
||||
$nCols = 0;
|
||||
|
||||
foreach ($out as $i => $row)
|
||||
{
|
||||
if (!is_array($out[0]))
|
||||
{
|
||||
unset($out[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$nCols = max($nCols, count($row));
|
||||
|
||||
for ($j = 0; $j < $nCols; $j++)
|
||||
$pads[$j] = max($pads[$j] ?? 0, mb_strlen(self::purgeEscapes($row[$j] ?? '')));
|
||||
}
|
||||
|
||||
foreach ($out as $i => $row)
|
||||
{
|
||||
for ($j = 0; $j < $nCols; $j++)
|
||||
{
|
||||
if (!isset($row[$j]))
|
||||
break;
|
||||
|
||||
$len = ($pads[$j] - mb_strlen(self::purgeEscapes($row[$j])));
|
||||
for ($k = 0; $k < $len; $k++) // can't use str_pad(). it counts invisible chars.
|
||||
$row[$j] .= ' ';
|
||||
}
|
||||
|
||||
if ($i || $headless)
|
||||
self::write(' '.implode(' ' . self::tblDelim(' ') . ' ', $row), CLI::LOG_NONE, $timestamp);
|
||||
else
|
||||
self::write(self::tblHead(' '.implode(' ', $row)), CLI::LOG_NONE, $timestamp);
|
||||
}
|
||||
|
||||
if (!$headless)
|
||||
self::write(self::tblHead(str_pad('', array_sum($pads) + count($pads) * 3 - 2)), CLI::LOG_NONE, $timestamp);
|
||||
|
||||
self::write();
|
||||
}
|
||||
|
||||
|
||||
/***********/
|
||||
/* logging */
|
||||
/***********/
|
||||
|
||||
public static function initLogFile(string $file = '') : void
|
||||
{
|
||||
if (!$file)
|
||||
return;
|
||||
|
||||
$file = self::nicePath($file);
|
||||
if (!file_exists($file))
|
||||
self::$logHandle = fopen($file, 'w');
|
||||
else
|
||||
{
|
||||
$logFileParts = pathinfo($file);
|
||||
|
||||
$i = 1;
|
||||
while (file_exists($logFileParts['dirname'].'/'.$logFileParts['filename'].$i.(isset($logFileParts['extension']) ? '.'.$logFileParts['extension'] : '')))
|
||||
$i++;
|
||||
|
||||
$file = $logFileParts['dirname'].'/'.$logFileParts['filename'].$i.(isset($logFileParts['extension']) ? '.'.$logFileParts['extension'] : '');
|
||||
self::$logHandle = fopen($file, 'w');
|
||||
}
|
||||
}
|
||||
|
||||
private static function tblHead(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[1;48;5;236m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
private static function tblDelim(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[48;5;236m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function grey(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[90m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function red(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[31m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function green(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[32m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function yellow(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[33m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function blue(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[36m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function bold(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[1m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function write(string $txt = '', int $lvl = self::LOG_BLANK, bool $timestamp = true, bool $tmpRow = false) : void
|
||||
{
|
||||
$msg = '';
|
||||
if ($txt)
|
||||
{
|
||||
if ($timestamp)
|
||||
$msg = str_pad(date('H:i:s'), 10);
|
||||
|
||||
switch ($lvl)
|
||||
{
|
||||
case self::LOG_ERROR: // red critical error
|
||||
$msg .= '['.self::red('ERR').'] ';
|
||||
break;
|
||||
case self::LOG_WARN: // yellow notice
|
||||
$msg .= '['.self::yellow('WARN').'] ';
|
||||
break;
|
||||
case self::LOG_OK: // green success
|
||||
$msg .= '['.self::green('OK').'] ';
|
||||
break;
|
||||
case self::LOG_INFO: // blue info
|
||||
$msg .= '['.self::blue('INFO').'] ';
|
||||
break;
|
||||
case self::LOG_BLANK:
|
||||
$msg .= ' ';
|
||||
break;
|
||||
}
|
||||
|
||||
$msg .= $txt;
|
||||
}
|
||||
|
||||
// https://shiroyasha.svbtle.com/escape-sequences-a-quick-guide-1#movement_1
|
||||
$msg = (self::$overwriteLast && CLI_HAS_E ? "\e[1G\e[0K" : "\n") . $msg;
|
||||
self::$overwriteLast = $tmpRow;
|
||||
|
||||
fwrite($lvl == self::LOG_ERROR ? STDERR : STDOUT, $msg);
|
||||
|
||||
if (self::$logHandle) // remove control sequences from log
|
||||
fwrite(self::$logHandle, self::purgeEscapes($msg));
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
public static function logLevelFromE(int $phpError) : int
|
||||
{
|
||||
if ($phpError & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR))
|
||||
return self::LOG_ERROR;
|
||||
|
||||
if ($phpError & (E_WARNING | E_USER_WARNING | E_NOTICE | E_USER_NOTICE | E_CORE_WARNING | E_COMPILE_WARNING))
|
||||
return self::LOG_WARN;
|
||||
|
||||
if ($phpError & (E_STRICT | E_NOTICE | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED))
|
||||
return self::LOG_INFO;
|
||||
|
||||
return self::LOG_BLANK;
|
||||
}
|
||||
|
||||
private static function purgeEscapes(string $msg) : string
|
||||
{
|
||||
return preg_replace(["/\e\[[\d;]+[mK]/", "/\e\[\d+G/"], ['', "\n"], $msg);
|
||||
}
|
||||
|
||||
public static function nicePath(string $fileOrPath, string ...$pathParts) : string
|
||||
{
|
||||
$path = '';
|
||||
|
||||
if ($pathParts)
|
||||
{
|
||||
foreach ($pathParts as &$pp)
|
||||
$pp = trim($pp);
|
||||
|
||||
$path .= implode(DIRECTORY_SEPARATOR, $pathParts);
|
||||
}
|
||||
|
||||
$path .= ($path ? DIRECTORY_SEPARATOR : '').trim($fileOrPath);
|
||||
|
||||
// remove double quotes (from erronous user input), single quotes are
|
||||
// valid chars for filenames and removing those mutilates several wow icons
|
||||
$path = str_replace('"', '', $path);
|
||||
|
||||
if (!$path) // empty strings given. (faulty dbc data?)
|
||||
return '';
|
||||
|
||||
if (DIRECTORY_SEPARATOR == '/') // *nix
|
||||
{
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = preg_replace('/\/+/i', '/', $path);
|
||||
}
|
||||
else if (DIRECTORY_SEPARATOR == '\\') // win
|
||||
{
|
||||
$path = str_replace('/', '\\', $path);
|
||||
$path = preg_replace('/\\\\+/i', '\\', $path);
|
||||
}
|
||||
else
|
||||
CLI::write('Dafuq! Your directory separator is "'.DIRECTORY_SEPARATOR.'". Please report this!', CLI::LOG_ERROR);
|
||||
|
||||
// resolve *nix home shorthand
|
||||
if (!OS_WIN)
|
||||
{
|
||||
if (preg_match('/^~(\w+)\/.*/i', $path, $m))
|
||||
$path = '/home/'.substr($path, 1);
|
||||
else if (substr($path, 0, 2) == '~/')
|
||||
$path = getenv('HOME').substr($path, 1);
|
||||
else if ($path[0] == DIRECTORY_SEPARATOR && substr($path, 0, 6) != '/home/')
|
||||
$path = substr($path, 1);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
|
||||
/**************/
|
||||
/* read input */
|
||||
/**************/
|
||||
|
||||
/*
|
||||
since the CLI on WIN ist not interactive, the following things have to be considered
|
||||
you do not receive keystrokes but whole strings upon pressing <Enter> (wich also appends a \r)
|
||||
as such <ESC> and probably other control chars can not be registered
|
||||
this also means, you can't hide input at all, least process it
|
||||
*/
|
||||
|
||||
public static function read(array $fields, ?array &$userInput = []) : bool
|
||||
{
|
||||
// first time set
|
||||
if (self::$hasReadline === null)
|
||||
self::$hasReadline = function_exists('readline_callback_handler_install');
|
||||
|
||||
// prevent default output if able
|
||||
if (self::$hasReadline)
|
||||
readline_callback_handler_install('', function() { });
|
||||
|
||||
if (!STDIN)
|
||||
return false;
|
||||
|
||||
stream_set_blocking(STDIN, false);
|
||||
|
||||
// pad default values onto $fields
|
||||
array_walk($fields, function(&$val, $_, $pad) { $val += $pad; }, ['', false, false, '']);
|
||||
|
||||
foreach ($fields as $name => [$desc, $isHidden, $singleChar, $validPattern])
|
||||
{
|
||||
$charBuff = '';
|
||||
|
||||
if ($desc)
|
||||
fwrite(STDOUT, "\n".$desc.": ");
|
||||
|
||||
while (true) {
|
||||
if (feof(STDIN))
|
||||
return false;
|
||||
|
||||
$r = [STDIN];
|
||||
$w = $e = null;
|
||||
$n = stream_select($r, $w, $e, 200000);
|
||||
|
||||
if (!$n || !in_array(STDIN, $r))
|
||||
continue;
|
||||
|
||||
// stream_get_contents is always blocking under WIN - fgets should work similary as php always receives a terminated line of text
|
||||
$chars = str_split(OS_WIN ? fgets(STDIN) : stream_get_contents(STDIN));
|
||||
$ordinals = array_map('ord', $chars);
|
||||
|
||||
if ($ordinals[0] == self::CHR_ESC)
|
||||
{
|
||||
if (count($ordinals) == 1)
|
||||
{
|
||||
fwrite(STDOUT, chr(self::CHR_BELL));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($chars as $idx => $char)
|
||||
{
|
||||
$keyId = $ordinals[$idx];
|
||||
|
||||
// skip char if horizontal tab or \r if followed by \n
|
||||
if ($keyId == self::CHR_TAB || ($keyId == self::CHR_CR && ($ordinals[$idx + 1] ?? '') == self::CHR_LF))
|
||||
continue;
|
||||
|
||||
if ($keyId == self::CHR_BACKSPACE)
|
||||
{
|
||||
if (!$charBuff)
|
||||
continue 2;
|
||||
|
||||
$charBuff = mb_substr($charBuff, 0, -1);
|
||||
if (!$isHidden && self::$hasReadline)
|
||||
fwrite(STDOUT, chr(self::CHR_BACK)." ".chr(self::CHR_BACK));
|
||||
}
|
||||
// standalone \n or \r
|
||||
else if ($keyId == self::CHR_LF || $keyId == self::CHR_CR)
|
||||
{
|
||||
$userInput[$name] = $charBuff;
|
||||
break 2;
|
||||
}
|
||||
else if (!$validPattern || preg_match($validPattern, $char))
|
||||
{
|
||||
$charBuff .= $char;
|
||||
if (!$isHidden && self::$hasReadline)
|
||||
fwrite(STDOUT, $char);
|
||||
|
||||
if ($singleChar && self::$hasReadline)
|
||||
{
|
||||
$userInput[$name] = $charBuff;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fwrite(STDOUT, chr(self::CHR_BELL));
|
||||
|
||||
foreach ($userInput as $ui)
|
||||
if (strlen($ui))
|
||||
return true;
|
||||
|
||||
$userInput = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Timer
|
||||
{
|
||||
private $t_cur = 0;
|
||||
private $t_new = 0;
|
||||
private $intv = 0;
|
||||
|
||||
public function __construct(int $intervall)
|
||||
{
|
||||
$this->intv = $intervall / 1000; // in msec
|
||||
$this->t_cur = microtime(true);
|
||||
}
|
||||
|
||||
public function update() : bool
|
||||
{
|
||||
$this->t_new = microtime(true);
|
||||
if ($this->t_new > $this->t_cur + $this->intv)
|
||||
{
|
||||
$this->t_cur = $this->t_cur + $this->intv;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function reset() : void
|
||||
{
|
||||
$this->t_cur = microtime(true) - $this->intv;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class Util
|
||||
{
|
||||
@@ -573,7 +182,7 @@ abstract class Util
|
||||
public static $tcEncoding = '0zMcmVokRsaqbdrfwihuGINALpTjnyxtgevElBCDFHJKOPQSUWXYZ123456789';
|
||||
private static $notes = [];
|
||||
|
||||
public static function addNote(string $note, int $uGroupMask = U_GROUP_EMPLOYEE, int $level = CLI::LOG_ERROR) : void
|
||||
public static function addNote(string $note, int $uGroupMask = U_GROUP_EMPLOYEE, int $level = LOG_LEVEL_ERROR) : void
|
||||
{
|
||||
self::$notes[] = [$note, $uGroupMask, $level];
|
||||
}
|
||||
@@ -581,7 +190,7 @@ abstract class Util
|
||||
public static function getNotes() : array
|
||||
{
|
||||
$notes = [];
|
||||
$severity = CLI::LOG_INFO;
|
||||
$severity = LOG_LEVEL_INFO;
|
||||
foreach (self::$notes as [$note, $uGroup, $level])
|
||||
{
|
||||
if ($uGroup && !User::isInGroup($uGroup))
|
||||
@@ -1581,7 +1190,7 @@ abstract class Util
|
||||
|
||||
public static function buildPosFixMenu(int $mapId, float $posX, float $posY, int $type, int $guid, int $parentArea = 0, int $parentFloor = 0) : array
|
||||
{
|
||||
$points = Game::worldPosToZonePos($mapId, $posX, $posY);
|
||||
$points = WorldPosition::toZonePos($mapId, $posX, $posY);
|
||||
if (!$points || count($points) < 2)
|
||||
return [];
|
||||
|
||||
@@ -1620,490 +1229,4 @@ abstract class Util
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class Type
|
||||
{
|
||||
public const NPC = 1;
|
||||
public const OBJECT = 2;
|
||||
public const ITEM = 3;
|
||||
public const ITEMSET = 4;
|
||||
public const QUEST = 5;
|
||||
public const SPELL = 6;
|
||||
public const ZONE = 7;
|
||||
public const FACTION = 8;
|
||||
public const PET = 9;
|
||||
public const ACHIEVEMENT = 10;
|
||||
public const TITLE = 11;
|
||||
public const WORLDEVENT = 12;
|
||||
public const CHR_CLASS = 13;
|
||||
public const CHR_RACE = 14;
|
||||
public const SKILL = 15;
|
||||
public const STATISTIC = 16;
|
||||
public const CURRENCY = 17;
|
||||
// PROJECT = 18;
|
||||
public const SOUND = 19;
|
||||
// BUILDING = 20;
|
||||
// FOLLOWER = 21;
|
||||
// MISSION_ABILITY = 22;
|
||||
// MISSION = 23;
|
||||
// SHIP = 25;
|
||||
// THREAT = 26;
|
||||
// RESOURCE = 27;
|
||||
// CHAMPION = 28;
|
||||
public const ICON = 29;
|
||||
// ORDER_ADVANCEMENT = 30;
|
||||
// FOLLOWER_ALLIANCE = 31;
|
||||
// FOLLOWER_HORDE = 32;
|
||||
// SHIP_ALLIANCE = 33;
|
||||
// SHIP_HORDE = 34;
|
||||
// CHAMPION_ALLIANCE = 35;
|
||||
// CHAMPION_HORDE = 36;
|
||||
// TRANSMOG_ITEM = 37;
|
||||
// BFA_CHAMPION = 38;
|
||||
// BFA_CHAMPION_ALLIANCE = 39;
|
||||
// AFFIX = 40;
|
||||
// BFA_CHAMPION_HORDE = 41;
|
||||
// AZERITE_ESSENCE_POWER = 42;
|
||||
// AZERITE_ESSENCE = 43;
|
||||
// STORYLINE = 44;
|
||||
// ADVENTURE_COMBATANT_ABILITY = 46;
|
||||
// ENCOUNTER = 47;
|
||||
// COVENANT = 48;
|
||||
// SOULBIND = 49;
|
||||
// DI_ITEM = 50;
|
||||
// GATHERER_SCREENSHOT = 91;
|
||||
// GATHERER_GUIDE_IMAGE = 98;
|
||||
public const PROFILE = 100;
|
||||
// our own things
|
||||
public const GUILD = 101;
|
||||
// TRANSMOG_SET = 101; // future conflict inc.
|
||||
public const ARENA_TEAM = 102;
|
||||
// OUTFIT = 110;
|
||||
// GEAR_SET = 111;
|
||||
// GATHERER_LISTVIEW = 158;
|
||||
// GATHERER_SURVEY_COVENANTS = 161;
|
||||
// NEWS_POST = 162;
|
||||
// BATTLE_PET_ABILITY = 200;
|
||||
public const GUIDE = 300; // should have been 100, but conflicts with old version of Profile/List
|
||||
public const USER = 500;
|
||||
public const EMOTE = 501;
|
||||
public const ENCHANTMENT = 502;
|
||||
public const AREATRIGGER = 503;
|
||||
public const MAIL = 504;
|
||||
// Blizzard API things
|
||||
// MOUNT = -1000;
|
||||
// RECIPE = -1001;
|
||||
// BATTLE_PET = -1002;
|
||||
|
||||
public const FLAG_NONE = 0x0;
|
||||
public const FLAG_RANDOM_SEARCHABLE = 0x1;
|
||||
/* public const FLAG_SEARCHABLE = 0x2 general search? */
|
||||
|
||||
public const IDX_LIST_OBJ = 0;
|
||||
public const IDX_FILE_STR = 1;
|
||||
public const IDX_JSG_TPL = 2;
|
||||
public const IDX_FLAGS = 3;
|
||||
|
||||
private static array $data = array(
|
||||
self::NPC => [__NAMESPACE__ . '\CreatureList', 'npc', 'g_npcs', 0x1],
|
||||
self::OBJECT => [__NAMESPACE__ . '\GameObjectList', 'object', 'g_objects', 0x1],
|
||||
self::ITEM => [__NAMESPACE__ . '\ItemList', 'item', 'g_items', 0x1],
|
||||
self::ITEMSET => [__NAMESPACE__ . '\ItemsetList', 'itemset', 'g_itemsets', 0x1],
|
||||
self::QUEST => [__NAMESPACE__ . '\QuestList', 'quest', 'g_quests', 0x1],
|
||||
self::SPELL => [__NAMESPACE__ . '\SpellList', 'spell', 'g_spells', 0x1],
|
||||
self::ZONE => [__NAMESPACE__ . '\ZoneList', 'zone', 'g_gatheredzones', 0x1],
|
||||
self::FACTION => [__NAMESPACE__ . '\FactionList', 'faction', 'g_factions', 0x1],
|
||||
self::PET => [__NAMESPACE__ . '\PetList', 'pet', 'g_pets', 0x1],
|
||||
self::ACHIEVEMENT => [__NAMESPACE__ . '\AchievementList', 'achievement', 'g_achievements', 0x1],
|
||||
self::TITLE => [__NAMESPACE__ . '\TitleList', 'title', 'g_titles', 0x1],
|
||||
self::WORLDEVENT => [__NAMESPACE__ . '\WorldEventList', 'event', 'g_holidays', 0x1],
|
||||
self::CHR_CLASS => [__NAMESPACE__ . '\CharClassList', 'class', 'g_classes', 0x1],
|
||||
self::CHR_RACE => [__NAMESPACE__ . '\CharRaceList', 'race', 'g_races', 0x1],
|
||||
self::SKILL => [__NAMESPACE__ . '\SkillList', 'skill', 'g_skills', 0x1],
|
||||
self::STATISTIC => [__NAMESPACE__ . '\AchievementList', 'achievement', 'g_achievements', 0x0], // alias for achievements; exists only for Markup
|
||||
self::CURRENCY => [__NAMESPACE__ . '\CurrencyList', 'currency', 'g_gatheredcurrencies',0x1],
|
||||
self::SOUND => [__NAMESPACE__ . '\SoundList', 'sound', 'g_sounds', 0x1],
|
||||
self::ICON => [__NAMESPACE__ . '\IconList', 'icon', 'g_icons', 0x1],
|
||||
self::GUIDE => [__NAMESPACE__ . '\GuideList', 'guide', '', 0x0],
|
||||
self::PROFILE => [__NAMESPACE__ . '\ProfileList', '', '', 0x0], // x - not known in javascript
|
||||
self::GUILD => [__NAMESPACE__ . '\GuildList', '', '', 0x0], // x
|
||||
self::ARENA_TEAM => [__NAMESPACE__ . '\ArenaTeamList', '', '', 0x0], // x
|
||||
self::USER => [__NAMESPACE__ . '\UserList', 'user', 'g_users', 0x0], // x
|
||||
self::EMOTE => [__NAMESPACE__ . '\EmoteList', 'emote', 'g_emotes', 0x1],
|
||||
self::ENCHANTMENT => [__NAMESPACE__ . '\EnchantmentList', 'enchantment', 'g_enchantments', 0x1],
|
||||
self::AREATRIGGER => [__NAMESPACE__ . '\AreatriggerList', 'areatrigger', '', 0x0],
|
||||
self::MAIL => [__NAMESPACE__ . '\MailList', 'mail', '', 0x1]
|
||||
);
|
||||
|
||||
|
||||
/********************/
|
||||
/* Field Operations */
|
||||
/********************/
|
||||
|
||||
public static function newList(int $type, array $conditions = []) : ?BaseType
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return null;
|
||||
|
||||
return new (self::$data[$type][self::IDX_LIST_OBJ])($conditions);
|
||||
}
|
||||
|
||||
public static function getFileString(int $type) : string
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return '';
|
||||
|
||||
return self::$data[$type][self::IDX_FILE_STR];
|
||||
}
|
||||
|
||||
public static function getJSGlobalString(int $type) : string
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return '';
|
||||
|
||||
return self::$data[$type][self::IDX_JSG_TPL];
|
||||
}
|
||||
|
||||
public static function getJSGlobalTemplate(int $type) : array
|
||||
{
|
||||
if (!self::exists($type) || !self::$data[$type][self::IDX_JSG_TPL])
|
||||
return [];
|
||||
|
||||
// [key, [data], [extraData]]
|
||||
return [self::$data[$type][self::IDX_JSG_TPL], [], []];
|
||||
}
|
||||
|
||||
public static function checkClassAttrib(int $type, string $attr, ?int $attrVal = null) : bool
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return false;
|
||||
|
||||
return isset((self::$data[$type][self::IDX_LIST_OBJ])::$$attr) && ($attrVal === null || ((self::$data[$type][self::IDX_LIST_OBJ])::$$attr & $attrVal));
|
||||
}
|
||||
|
||||
public static function getClassAttrib(int $type, string $attr) : mixed
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return null;
|
||||
|
||||
return (self::$data[$type][self::IDX_LIST_OBJ])::$$attr ?? null;
|
||||
}
|
||||
|
||||
public static function exists(int $type) : bool
|
||||
{
|
||||
return !empty(self::$data[$type]);
|
||||
}
|
||||
|
||||
public static function getIndexFrom(int $idx, string $match) : int
|
||||
{
|
||||
$i = array_search($match, array_column(self::$data, $idx));
|
||||
if ($i === false)
|
||||
return 0;
|
||||
|
||||
return array_keys(self::$data)[$i];
|
||||
}
|
||||
|
||||
|
||||
/*********************/
|
||||
/* Column Operations */
|
||||
/*********************/
|
||||
|
||||
public static function getClassesFor(int $flags = 0x0, string $attr = '', ?int $attrVal = null) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [$o, , , $f])
|
||||
if ($o && (!$flags || $flags & $f))
|
||||
if (!$attr || self::checkClassAttrib($k, $attr, $attrVal))
|
||||
$x[$k] = $o;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public static function getFileStringsFor(int $flags = 0x0) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [, $s, , $f])
|
||||
if ($s && (!$flags || $flags & $f))
|
||||
$x[$k] = $s;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public static function getJSGTemplatesFor(int $flags = 0x0) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [, , $a, $f])
|
||||
if ($a && (!$flags || $flags & $f))
|
||||
$x[$k] = $a;
|
||||
|
||||
return $x;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Report
|
||||
{
|
||||
public const MODE_GENERAL = 0;
|
||||
public const MODE_COMMENT = 1;
|
||||
public const MODE_FORUM_POST = 2;
|
||||
public const MODE_SCREENSHOT = 3;
|
||||
public const MODE_CHARACTER = 4;
|
||||
public const MODE_VIDEO = 5;
|
||||
public const MODE_GUIDE = 6;
|
||||
|
||||
public const GEN_FEEDBACK = 1;
|
||||
public const GEN_BUG_REPORT = 2;
|
||||
public const GEN_TYPO_TRANSLATION = 3;
|
||||
public const GEN_OP_ADVERTISING = 4;
|
||||
public const GEN_OP_PARTNERSHIP = 5;
|
||||
public const GEN_PRESS_INQUIRY = 6;
|
||||
public const GEN_MISCELLANEOUS = 7;
|
||||
public const GEN_MISINFORMATION = 8;
|
||||
public const CO_ADVERTISING = 15;
|
||||
public const CO_INACCURATE = 16;
|
||||
public const CO_OUT_OF_DATE = 17;
|
||||
public const CO_SPAM = 18;
|
||||
public const CO_INAPPROPRIATE = 19;
|
||||
public const CO_MISCELLANEOUS = 20;
|
||||
public const FO_ADVERTISING = 30;
|
||||
public const FO_AVATAR = 31;
|
||||
public const FO_INACCURATE = 32;
|
||||
public const FO_OUT_OF_DATE = 33;
|
||||
public const FO_SPAM = 34;
|
||||
public const FO_STICKY_REQUEST = 35;
|
||||
public const FO_INAPPROPRIATE = 36;
|
||||
public const FO_MISCELLANEOUS = 37;
|
||||
public const SS_INACCURATE = 45;
|
||||
public const SS_OUT_OF_DATE = 46;
|
||||
public const SS_INAPPROPRIATE = 47;
|
||||
public const SS_MISCELLANEOUS = 48;
|
||||
public const PR_INACCURATE_DATA = 60;
|
||||
public const PR_MISCELLANEOUS = 61;
|
||||
public const VI_INACCURATE = 45;
|
||||
public const VI_OUT_OF_DATE = 46;
|
||||
public const VI_INAPPROPRIATE = 47;
|
||||
public const VI_MISCELLANEOUS = 48;
|
||||
public const AR_INACCURATE = 45;
|
||||
public const AR_OUT_OF_DATE = 46;
|
||||
public const AR_MISCELLANEOUS = 48;
|
||||
|
||||
private array $context = array(
|
||||
self::MODE_GENERAL => array(
|
||||
self::GEN_FEEDBACK => true,
|
||||
self::GEN_BUG_REPORT => true,
|
||||
self::GEN_TYPO_TRANSLATION => true,
|
||||
self::GEN_OP_ADVERTISING => true,
|
||||
self::GEN_OP_PARTNERSHIP => true,
|
||||
self::GEN_PRESS_INQUIRY => true,
|
||||
self::GEN_MISCELLANEOUS => true,
|
||||
self::GEN_MISINFORMATION => true
|
||||
),
|
||||
self::MODE_COMMENT => array(
|
||||
self::CO_ADVERTISING => U_GROUP_MODERATOR,
|
||||
self::CO_INACCURATE => true,
|
||||
self::CO_OUT_OF_DATE => true,
|
||||
self::CO_SPAM => U_GROUP_MODERATOR,
|
||||
self::CO_INAPPROPRIATE => U_GROUP_MODERATOR,
|
||||
self::CO_MISCELLANEOUS => U_GROUP_MODERATOR
|
||||
),
|
||||
self::MODE_FORUM_POST => array(
|
||||
self::FO_ADVERTISING => U_GROUP_MODERATOR,
|
||||
self::FO_AVATAR => true,
|
||||
self::FO_INACCURATE => true,
|
||||
self::FO_OUT_OF_DATE => U_GROUP_MODERATOR,
|
||||
self::FO_SPAM => U_GROUP_MODERATOR,
|
||||
self::FO_STICKY_REQUEST => U_GROUP_MODERATOR,
|
||||
self::FO_INAPPROPRIATE => U_GROUP_MODERATOR
|
||||
),
|
||||
self::MODE_SCREENSHOT => array(
|
||||
self::SS_INACCURATE => true,
|
||||
self::SS_OUT_OF_DATE => true,
|
||||
self::SS_INAPPROPRIATE => U_GROUP_MODERATOR,
|
||||
self::SS_MISCELLANEOUS => U_GROUP_MODERATOR
|
||||
),
|
||||
self::MODE_CHARACTER => array(
|
||||
self::PR_INACCURATE_DATA => true,
|
||||
self::PR_MISCELLANEOUS => true
|
||||
),
|
||||
self::MODE_VIDEO => array(
|
||||
self::VI_INACCURATE => true,
|
||||
self::VI_OUT_OF_DATE => true,
|
||||
self::VI_INAPPROPRIATE => U_GROUP_MODERATOR,
|
||||
self::VI_MISCELLANEOUS => U_GROUP_MODERATOR
|
||||
),
|
||||
self::MODE_GUIDE => array(
|
||||
self::AR_INACCURATE => true,
|
||||
self::AR_OUT_OF_DATE => true,
|
||||
self::AR_MISCELLANEOUS => true
|
||||
)
|
||||
);
|
||||
|
||||
private const ERR_NONE = 0; // aka: success
|
||||
private const ERR_INVALID_CAPTCHA = 1; // captcha not in use
|
||||
private const ERR_DESC_TOO_LONG = 2;
|
||||
private const ERR_NO_DESC = 3;
|
||||
private const ERR_ALREADY_REPORTED = 7;
|
||||
private const ERR_MISCELLANEOUS = -1;
|
||||
|
||||
public const STATUS_OPEN = 0;
|
||||
public const STATUS_ASSIGNED = 1;
|
||||
public const STATUS_CLOSED_WONTFIX = 2;
|
||||
public const STATUS_CLOSED_SOLVED = 3;
|
||||
|
||||
private int $errorCode = self::ERR_NONE;
|
||||
|
||||
|
||||
public function __construct(private int $mode, private int $reason, private ?int $subject = 0)
|
||||
{
|
||||
if ($mode < 0 || $reason <= 0)
|
||||
{
|
||||
trigger_error('Report - malformed contact request received', E_USER_ERROR);
|
||||
$this->errorCode = self::ERR_MISCELLANEOUS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($this->context[$mode][$reason]))
|
||||
{
|
||||
trigger_error('Report - report has invalid context (mode:'.$mode.' / reason:'.$reason.')', E_USER_ERROR);
|
||||
$this->errorCode = self::ERR_MISCELLANEOUS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!User::$id && !User::$ip)
|
||||
{
|
||||
trigger_error('Report - could not determine IP for anonymous user', E_USER_ERROR);
|
||||
$this->errorCode = self::ERR_MISCELLANEOUS;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->subject ??= 0; // 0 for utility, tools and misc pages?
|
||||
}
|
||||
|
||||
private function checkTargetContext() : int
|
||||
{
|
||||
// check already reported
|
||||
$field = User::$id ? 'userId' : 'ip';
|
||||
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $this->mode, $this->reason, $this->subject, $field, User::$id ?: User::$ip))
|
||||
return self::ERR_ALREADY_REPORTED;
|
||||
|
||||
// check targeted post/postOwner staff status
|
||||
$ctxCheck = $this->context[$this->mode][$this->reason];
|
||||
if (is_int($ctxCheck))
|
||||
{
|
||||
$roles = User::$groups;
|
||||
if ($this->mode == self::MODE_COMMENT)
|
||||
$roles = DB::Aowow()->selectCell('SELECT `roles` FROM ?_comments WHERE `id` = ?d', $this->subject);
|
||||
// else if if ($this->mode == self::MODE_FORUM_POST)
|
||||
// $roles = DB::Aowow()->selectCell('SELECT `roles` FROM ?_forum_posts WHERE `id` = ?d', $this->subject);
|
||||
|
||||
return $roles & $ctxCheck ? self::ERR_NONE : self::ERR_MISCELLANEOUS;
|
||||
}
|
||||
else
|
||||
return $ctxCheck ? self::ERR_NONE : self::ERR_MISCELLANEOUS;
|
||||
|
||||
// Forum not in use, else:
|
||||
// check post owner
|
||||
// User::$id == post.op && !post.sticky;
|
||||
// check user custom avatar
|
||||
// g_users[post.user].avatar == 2 && (post.roles & U_GROUP_MODERATOR) == 0
|
||||
}
|
||||
|
||||
public function create(string $desc, ?string $userAgent = null, ?string $appName = null, ?string $pageUrl = null, ?string $relUrl = null, ?string $email = null) : bool
|
||||
{
|
||||
if ($this->errorCode)
|
||||
return false;
|
||||
|
||||
if (!$desc)
|
||||
{
|
||||
$this->errorCode = self::ERR_NO_DESC;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mb_strlen($desc) > 500)
|
||||
{
|
||||
$this->errorCode = self::ERR_DESC_TOO_LONG;
|
||||
return false;
|
||||
}
|
||||
|
||||
if($err = $this->checkTargetContext())
|
||||
{
|
||||
$this->errorCode = $err;
|
||||
return false;
|
||||
}
|
||||
|
||||
$update = array(
|
||||
'userId' => User::$id,
|
||||
'createDate' => time(),
|
||||
'mode' => $this->mode,
|
||||
'reason' => $this->reason,
|
||||
'subject' => $this->subject,
|
||||
'ip' => User::$ip,
|
||||
'description' => $desc,
|
||||
'userAgent' => $userAgent ?: $_SERVER['HTTP_USER_AGENT'],
|
||||
'appName' => $appName ?: (get_browser(null, true)['browser'] ?: '')
|
||||
);
|
||||
|
||||
if ($pageUrl)
|
||||
$update['url'] = $pageUrl;
|
||||
|
||||
if ($relUrl)
|
||||
$update['relatedurl'] = $relUrl;
|
||||
|
||||
if ($email)
|
||||
$update['email'] = $email;
|
||||
|
||||
return DB::Aowow()->query('INSERT INTO ?_reports (?#) VALUES (?a)', array_keys($update), array_values($update));
|
||||
}
|
||||
|
||||
public function getSimilar(int ...$status) : array
|
||||
{
|
||||
if ($this->errorCode)
|
||||
return [];
|
||||
|
||||
foreach ($status as &$s)
|
||||
if ($s < self::STATUS_OPEN || $s > self::STATUS_CLOSED_SOLVED)
|
||||
unset($s);
|
||||
|
||||
return DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, r.* FROM ?_reports r WHERE {`status` IN (?a) AND }`mode` = ?d AND `reason` = ?d AND `subject` = ?d',
|
||||
$status ?: DBSIMPLE_SKIP, $this->mode, $this->reason, $this->subject);
|
||||
}
|
||||
|
||||
public function close(int $closeStatus, bool $inclAssigned = false) : bool
|
||||
{
|
||||
if ($closeStatus != self::STATUS_CLOSED_SOLVED && $closeStatus != self::STATUS_CLOSED_WONTFIX)
|
||||
return false;
|
||||
|
||||
if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_MOD))
|
||||
return false;
|
||||
|
||||
$fromStatus = [self::STATUS_OPEN];
|
||||
if ($inclAssigned)
|
||||
$fromStatus[] = self::STATUS_ASSIGNED;
|
||||
|
||||
if ($reports = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `userId` FROM ?_reports WHERE `status` IN (?a) AND `mode` = ?d AND `reason` = ?d AND `subject` = ?d',
|
||||
$fromStatus, $this->mode, $this->reason, $this->subject))
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_reports SET `status` = ?d, `assigned` = 0 WHERE `id` IN (?a)', $closeStatus, array_keys($reports));
|
||||
|
||||
foreach ($reports as $rId => $uId)
|
||||
Util::gainSiteReputation($uId, $closeStatus == self::STATUS_CLOSED_SOLVED ? SITEREP_ACTION_GOOD_REPORT : SITEREP_ACTION_BAD_REPORT, ['id' => $rId]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function reopen(int $assignedTo = 0) : bool
|
||||
{
|
||||
// assignedTo = 0 ? status = STATUS_OPEN : status = STATUS_ASSIGNED, userId = assignedTo
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getError() : int
|
||||
{
|
||||
return $this->errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user