* detatch from User and Util and move to its own enum class
 * added definitions for all locales the 12340 client could in theory have
 * this is incompatble with the Intl extension
 * version bump and php requirement bump
This commit is contained in:
Sarjuuk
2025-01-27 07:47:19 +01:00
parent 40c2c63d1b
commit 398b93e9a7
72 changed files with 1007 additions and 924 deletions

View File

@@ -20,17 +20,11 @@ class AjaxAccount extends AjaxHandler
'remove' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
// 'sessionKey' => ['filter' => FILTER_SANITIZE_NUMBER_INT]
);
protected $_get = array(
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale']
);
public function __construct(array $params)
{
parent::__construct($params);
if (is_numeric($this->_get['locale']))
User::useLocale($this->_get['locale']);
if (!$this->params || !User::$id)
return;
@@ -60,12 +54,12 @@ class AjaxAccount extends AjaxHandler
// we don't get signaled whether an id should be added to or removed from either includes or excludes
// so we throw everything into one table and toggle the mode if its already in here
$includes = DB::Aowow()->selectCol('SELECT typeId FROM ?_profiler_excludes WHERE type = ?d AND typeId IN (?a)', $type, $ids);
$includes = DB::Aowow()->selectCol('SELECT `typeId` FROM ?_profiler_excludes WHERE `type` = ?d AND `typeId` IN (?a)', $type, $ids);
foreach ($ids as $typeId)
DB::Aowow()->query('INSERT INTO ?_account_excludes (`userId`, `type`, `typeId`, `mode`) VALUES (?a) ON DUPLICATE KEY UPDATE mode = (mode ^ 0x3)', array(
User::$id, $type, $typeId, in_array($typeId, $includes) ? 2 : 1
));
DB::Aowow()->query('INSERT INTO ?_account_excludes (`userId`, `type`, `typeId`, `mode`) VALUES (?a) ON DUPLICATE KEY UPDATE `mode` = (`mode` ^ 0x3)',
[User::$id, $type, $typeId, in_array($typeId, $includes) ? 2 : 1]
);
return;
}

View File

@@ -248,7 +248,7 @@ class AjaxComment extends AjaxHandler
if ($this->_get['rating'] < 0)
$val *= -1;
if (User::getCurDailyVotes() <= 0)
if (User::getCurrentDailyVotes() <= 0)
return Util::toJSON(['error' => 1, 'message' => Lang::main('tooManyVotes')]);
else if (!$target || $val != $this->_get['rating'])
return Util::toJSON(['error' => 1, 'message' => Lang::main('genericError')]);

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
class AjaxData extends AjaxHandler
{
protected $_get = array(
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale' ],
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'Locale::tryFrom' ],
't' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkTextLine'],
'catg' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
'skill' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxData::checkSkill' ],
@@ -18,8 +18,8 @@ class AjaxData extends AjaxHandler
{
parent::__construct($params);
if (is_numeric($this->_get['locale']))
User::useLocale($this->_get['locale']);
if ($this->_get['locale']?->validate())
Lang::load($this->_get['locale']);
// always this one
$this->handler = 'handleData';
@@ -103,7 +103,7 @@ class AjaxData extends AjaxHandler
case 'pets':
case 'zones':
if (!Util::loadStaticFile($set, $result, true) && Cfg::get('DEBUG'))
$result .= "alert('could not fetch static data: ".$set." for locale: ".User::$localeString."');";
$result .= "alert('could not fetch static data: ".$set." for locale: ".Lang::getLocale()->json()."');";
$result .= "\n\n";
break;
@@ -118,7 +118,7 @@ class AjaxData extends AjaxHandler
protected static function checkSkill(string $val) : array
{
return array_intersect([171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, 185, 129, 356], explode(',', $val));
return array_intersect(array_merge(SKILLS_TRADE_PRIMARY, [SKILL_FIRST_AID, SKILL_COOKING, SKILL_FISHING]), explode(',', $val));
}
protected static function checkCallback(string $val) : bool

View File

@@ -6,7 +6,7 @@ if (!defined('AOWOW_REVISION'))
class AjaxLocale extends AjaxHandler
{
protected $_get = array(
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale']
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'Locale::tryFrom']
);
public function __construct(array $params)
@@ -23,8 +23,11 @@ class AjaxLocale extends AjaxHandler
*/
protected function handleLocale() : string
{
User::setLocale($this->_get['locale']);
User::save();
if ($this->_get['locale']?->validate())
{
User::$preferedLoc = $this->_get['locale'];
User::save(true);
}
return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '.';
}

View File

@@ -654,10 +654,10 @@ class AjaxProfile extends AjaxHandler
{
// get and apply inventory
$gItems[$iId] = array(
'name_'.User::$localeString => $phItems->getField('name', true),
'quality' => $phItems->getField('quality'),
'icon' => $phItems->getField('iconString'),
'jsonequip' => $data[$iId]
'name_'.Lang::getLocale()->json() => $phItems->getField('name', true),
'quality' => $phItems->getField('quality'),
'icon' => $phItems->getField('iconString'),
'jsonequip' => $data[$iId]
);
$profile['inventory'][$slot] = [$iId, 0, 0, 0, 0, 0, 0, 0];
@@ -682,10 +682,10 @@ class AjaxProfile extends AjaxHandler
{
// get and apply inventory
$gItems[$i['item']] = array(
'name_'.User::$localeString => $itemz->getField('name', true),
'quality' => $itemz->getField('quality'),
'icon' => $itemz->getField('iconString'),
'jsonequip' => $data[$i['item']]
'name_'.Lang::getLocale()->json() => $itemz->getField('name', true),
'quality' => $itemz->getField('quality'),
'icon' => $itemz->getField('iconString'),
'jsonequip' => $data[$i['item']]
);
$profile['inventory'][$i['slot']] = [$i['item'], $i['subItem'], $i['permEnchant'], $i['tempEnchant'], $i['gem1'], $i['gem2'], $i['gem3'], $i['gem4']];
}
@@ -719,7 +719,7 @@ class AjaxProfile extends AjaxHandler
// }
// }
// $buff .= 'g_spells.add('.$id.", {id:".$id.", name:'".Util::jsEscape(mb_substr($data['name'], 1))."', icon:'".$data['icon']."', modifier:".Util::toJSON($mods)."});\n";
// $buff .= 'g_spells.add('.$id.", {id:".$id.", name:'".Util::jsEscape(mb_substr($data['name'], 1))."', icon:'".$data['icon']."', callback:".Util::toJSON($mods)."});\n";
// }
// $buff .= "\n";
// }

View File

@@ -1479,7 +1479,7 @@ abstract class Filter
private function genericString($field, $value, $strFlags)
{
if ($strFlags & STR_LOCALIZED)
$field .= '_loc'.User::$localeId;
$field .= '_loc'.Lang::getLocale()->value;
return $this->modularizeString([$field], (string)$value, $strFlags & STR_MATCH_EXACT, $strFlags & STR_ALLOW_SHORT);
}

View File

@@ -139,21 +139,6 @@ define('U_GROUP_MODERATOR', (U_GROUP_ADMIN|U_GROUP_MOD|U_GROUP_B
define('U_GROUP_COMMENTS_MODERATOR', (U_GROUP_MODERATOR|U_GROUP_LOCALIZER));
define('U_GROUP_PREMIUM_PERMISSIONS', (U_GROUP_PREMIUM|U_GROUP_STAFF|U_GROUP_VIP));
// Locales
define('LOCALE_EN', 0); // enGB, enUS
define('LOCALE_KR', 1); // koKR [aowwo unused]
define('LOCALE_FR', 2); // frFR
define('LOCALE_DE', 3); // deDE
define('LOCALE_CN', 4); // zhCN, enCN
define('LOCALE_TW', 5); // zhTW, enTW [aowow unused]
define('LOCALE_ES', 6); // esES
define('LOCALE_MX', 7); // esMX [aowow unused]
define('LOCALE_RU', 8); // ruRU
define('LOCALE_JP', 9); // jaJP [aowow & 335 unused]
define('LOCALE_PT', 10); // ptPT, ptBR [aowow unused]
define('LOCALE_IT', 11); // itIT [aowow & 335 unused]
define('LOCALE_MASK_ALL', 0b000101011101);
// red buttons on the top of the page
define('BUTTON_WOWHEAD', 0);
define('BUTTON_UPGRADE', 1);
@@ -330,7 +315,6 @@ define('GUIDE_CU_NO_RATING', 0x200);
define('MAX_LEVEL', 80);
define('MAX_SKILL', 450);
define('MAX_LOCALES', 16); // technical limitation, 6 in use here
define('WOW_BUILD', 12340);
// Loot handles

View File

@@ -220,7 +220,7 @@ class Game
$pages = [];
while ($ptId)
{
if ($row = DB::World()->selectRow('SELECT ptl.Text AS Text_loc?d, pt.* FROM page_text pt LEFT JOIN page_text_locale ptl ON pt.ID = ptl.ID AND locale = ? WHERE pt.ID = ?d', User::$localeId, User::$localeString, $ptId))
if ($row = DB::World()->selectRow('SELECT ptl.Text AS Text_loc?d, pt.* FROM page_text pt LEFT JOIN page_text_locale ptl ON pt.ID = ptl.ID AND locale = ? WHERE pt.ID = ?d', Lang::getLocale()->value, Lang::getLocale()->json(), $ptId))
{
$ptId = $row['NextPageID'];
$pages[] = Util::parseHtmlText(Util::localizedString($row, 'Text'));
@@ -392,28 +392,20 @@ class Game
$quotes = [];
$soundIds = [];
$quoteSrc = DB::World()->select('
SELECT
ct.GroupID AS ARRAY_KEY, ct.ID as ARRAY_KEY2,
ct.`Type` AS `talkType`,
ct.TextRange AS `range`,
IFNULL(bct.`LanguageID`, ct.`Language`) AS lang,
IFNULL(NULLIF(bct.Text, ""), IFNULL(NULLIF(bct.Text1, ""), IFNULL(ct.`Text`, ""))) AS text_loc0,
{IFNULL(NULLIF(bctl.Text, ""), IFNULL(NULLIF(bctl.Text1, ""), IFNULL(ctl.Text, ""))) AS text_loc?d,}
IF(bct.SoundEntriesID > 0, bct.SoundEntriesID, ct.Sound) AS soundId
FROM
creature_text ct
{LEFT JOIN
creature_text_locale ctl ON ct.CreatureID = ctl.CreatureID AND ct.GroupID = ctl.GroupID AND ct.ID = ctl.ID AND ctl.Locale = ?}
LEFT JOIN
broadcast_text bct ON ct.BroadcastTextId = bct.ID
{LEFT JOIN
broadcast_text_locale bctl ON ct.BroadcastTextId = bctl.ID AND bctl.locale = ?}
WHERE
ct.CreatureID = ?d',
User::$localeId ?: DBSIMPLE_SKIP,
User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP,
User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP,
$quoteSrc = DB::World()->select(
'SELECT ct.`GroupID` AS ARRAY_KEY, ct.`ID` AS ARRAY_KEY2, ct.`Type` AS "talkType", ct.TextRange AS "range",
IFNULL(bct.`LanguageID`, ct.`Language`) AS "lang",
IFNULL(NULLIF(bct.`Text`, ""), IFNULL(NULLIF(bct.`Text1`, ""), IFNULL(ct.`Text`, ""))) AS "text_loc0",
{ IFNULL(NULLIF(bctl.`Text`, ""), IFNULL(NULLIF(bctl.`Text1`, ""), IFNULL(ctl.`Text`, ""))) AS text_loc?d, }
IF(bct.`SoundEntriesID` > 0, bct.`SoundEntriesID`, ct.`Sound`) AS "soundId"
FROM creature_text ct
{ LEFT JOIN creature_text_locale ctl ON ct.`CreatureID` = ctl.`CreatureID` AND ct.`GroupID` = ctl.`GroupID` AND ct.`ID` = ctl.`ID` AND ctl.`Locale` = ? }
LEFT JOIN broadcast_text bct ON ct.`BroadcastTextId` = bct.`ID`
{ LEFT JOIN broadcast_text_locale bctl ON ct.`BroadcastTextId` = bctl.`ID` AND bctl.`locale` = ? }
WHERE ct.`CreatureID` = ?d',
Lang::getLocale()->value ?: DBSIMPLE_SKIP,
Lang::getLocale()->value ? Lang::getLocale()->json() : DBSIMPLE_SKIP,
Lang::getLocale()->value ? Lang::getLocale()->json() : DBSIMPLE_SKIP,
$creatureId
);

View File

@@ -3,7 +3,7 @@
mb_internal_encoding('UTF-8');
mysqli_report(MYSQLI_REPORT_ERROR);
define('AOWOW_REVISION', 39);
define('AOWOW_REVISION', 40);
define('OS_WIN', substr(PHP_OS, 0, 3) == 'WIN'); // OS_WIN as per compile info of php
define('CLI', PHP_SAPI === 'cli');
define('CLI_HAS_E', CLI && // WIN10 and later usually support ANSI escape sequences
@@ -11,25 +11,30 @@ define('CLI_HAS_E', CLI && // WIN10 and later u
$reqExt = ['SimpleXML', 'gd', 'mysqli', 'mbstring', 'fileinfo'/*, 'gmp'*/];
$badExt = ['Intl']; // Intl contains its own class Locale. What? Namespaces? Never heard of those!
$error = '';
if ($ext = array_filter($reqExt, function($x) { return !extension_loaded($x); }))
if ($ext = array_filter($reqExt, fn($x) => !extension_loaded($x)))
$error .= 'Required Extension <b>'.implode(', ', $ext)."</b> was not found. Please check if it should exist, using \"<i>php -m</i>\"\n\n";
if (version_compare(PHP_VERSION, '8.0.0') < 0)
$error .= 'PHP Version <b>8.0</b> or higher required! Your version is <b>'.PHP_VERSION."</b>.\nCore functions are unavailable!\n";
if ($ext = array_filter($badExt, fn($x) => extension_loaded($x)))
$error .= 'Loaded Extension <b>'.implode(', ', $ext)."</b> is incompatible and must be disabled.\n\n";
if (version_compare(PHP_VERSION, '8.2.0') < 0)
$error .= 'PHP Version <b>8.2</b> or higher required! Your version is <b>'.PHP_VERSION."</b>.\nCore functions are unavailable!\n";
if ($error)
die(CLI ? strip_tags($error) : $error);
require_once 'includes/defines.php';
require_once 'includes/locale.class.php';
require_once 'includes/stats.class.php'; // Game entity statistics conversion
require_once 'includes/libs/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (using variant: https://github.com/ivan1986/DbSimple/tree/master)
require_once 'includes/utilities.php'; // helper functions
require_once 'includes/config.class.php'; // Config holder
require_once 'includes/game.php'; // game related data & functions
require_once 'includes/profiler.class.php';
require_once 'includes/user.class.php';
require_once 'includes/profiler.class.php'; // Profiler feature
require_once 'includes/user.class.php'; // Session handling
require_once 'includes/markup.class.php'; // manipulate markup text
require_once 'includes/database.class.php'; // wrap DBSimple
require_once 'includes/community.class.php'; // handle comments, screenshots and videos
@@ -213,6 +218,12 @@ if (!CLI)
if (User::init())
User::save(); // save user-variables in session
// hard override locale for this call (should this be here..?)
if (isset($_GET['locale']) && ($loc = Locale::tryFrom($_GET['locale'])))
Lang::load($loc);
else
Lang::load(User::$preferedLoc);
// set up some logging (~10 queries will execute before we init the user and load the config)
if (Cfg::get('DEBUG') >= CLI::LOG_INFO && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
{
@@ -227,17 +238,6 @@ if (!CLI)
DB::Characters($idx)->setLogger(['DB', 'profiler']);
}
// hard-override locale for this call (should this be here..?)
// all strings attached..
if (isset($_GET['locale']))
{
$loc = intVal($_GET['locale']);
if ($loc <= MAX_LOCALES && $loc >= 0 && (Cfg::get('LOCALES') & (1 << $loc)))
User::useLocale($loc);
}
Lang::load(User::$localeId);
// parse page-parameters .. sanitize before use!
$str = explode('&', $_SERVER['QUERY_STRING'] ?? '', 2)[0];
$_ = explode('=', $str, 2);
@@ -245,6 +245,6 @@ if (!CLI)
$pageParam = $_[1] ?? '';
}
else if (DB::isConnected(DB_AOWOW))
Lang::load(LOCALE_EN);
Lang::load(Locale::EN);
?>

209
includes/locale.class.php Normal file
View File

@@ -0,0 +1,209 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
enum Locale : int
{ // unused by TC WoW self
case EN = 0; // enGB, enUS
case KR = 1; // koKR ?
case FR = 2; // frFR
case DE = 3; // deDE
case CN = 4; // zhCN, enCN
case TW = 5; // zhTW, enTW x
case ES = 6; // esES
case MX = 7; // esMX x
case RU = 8; // ruRU
case JP = 9; // jaJP x x x
case PT = 10; // ptPT, ptBR x ?
case IT = 11; // itIT x x x
private const MASK_ALL = 0b000101011101; // technically supported locales
public function domain() : string // our subdomain / locale in web context
{
return match ($this)
{
self::EN => 'en',
self::KR => 'kt',
self::FR => 'fr',
self::DE => 'de',
self::CN => 'cn',
self::TW => 'tw',
self::ES => 'es',
self::MX => 'mx',
self::RU => 'ru',
self::JP => 'jp',
self::PT => 'pt',
self::IT => 'it'
};
}
public function json() : string // internal usage / json string
{
return match ($this)
{
self::EN => 'enus',
self::KR => 'kokr',
self::FR => 'frfr',
self::DE => 'dede',
self::CN => 'zhcn',
self::TW => 'zhtw',
self::ES => 'eses',
self::MX => 'esmx',
self::RU => 'ruru',
self::JP => 'jajp',
self::PT => 'ptpt',
self::IT => 'itit'
};
}
public function title() : string // localized language name
{
return match ($this)
{
self::EN => 'English',
self::KR => '한국어',
self::FR => 'Français',
self::DE => 'Deutsch',
self::CN => '简体中文',
self::TW => '繁體中文',
self::ES => 'Español',
self::MX => 'Mexicano',
self::RU => 'Русский',
self::JP => '日本語',
self::PT => 'Português',
self::IT => 'Italiano'
};
}
public function gameDirs() : array // setup data source / wow client locale code
{
return match ($this)
{
self::EN => ['enGB', 'enUS', ''],
self::KR => ['koKR'],
self::FR => ['frFR'],
self::DE => ['deDE'],
self::CN => ['zhCN', 'enCN'],
self::TW => ['zhTW', 'enTW'],
self::ES => ['esES'],
self::MX => ['esMX'],
self::RU => ['ruRU'],
self::JP => ['jaJP'],
self::PT => ['ptPT', 'ptBR'],
self::IT => ['itIT']
};
}
public function httpCode() : array // HTTP_ACCEPT_LANGUAGE
{
return match ($this)
{
self::EN => ['en', 'en-au', 'en-bz', 'en-ca', 'en-ie', 'en-jm', 'en-nz', 'en-ph', 'en-za', 'en-tt', 'en-gb', 'en-us', 'en-zw'],
self::KR => ['ko', 'ko-kp', 'ko-kr'],
self::FR => ['fr', 'fr-be', 'fr-ca', 'fr-fr', 'fr-lu', 'fr-mc', 'fr-ch'],
self::DE => ['de', 'de-at', 'de-de', 'de-li', 'de-lu', 'de-ch'],
self::CN => ['zh', 'zh-hk', 'zh-cn', 'zh-sg'],
self::TW => ['tw', 'zh-tw'],
self::ES => ['es', 'es-ar', 'es-bo', 'es-cl', 'es-co', 'es-cr', 'es-do', 'es-ec', 'es-sv', 'es-gt', 'es-hn', 'es-ni', 'es-pa', 'es-py', 'es-pe', 'es-pr', 'es-es', 'es-uy', 'es-ve'],
self::MX => ['mx', 'es-mx'],
self::RU => ['ru', 'ru-mo'],
self::JP => ['ja'],
self::PT => ['pt', 'pt-br'],
self::IT => ['it', 'it-ch']
};
}
public function isLogographic() : bool
{
return $this == Locale::CN || $this == Locale::TW || $this == Locale::KR;
}
public function validate() : ?self
{
return ($this->maskBit() & self::MASK_ALL & (Cfg::get('LOCALES') ?: 0xFFFF)) ? $this : null;
}
public function maskBit() : int
{
return (1 << $this->value);
}
public static function tryFromDomain(string $str) : ?self
{
foreach (self::cases() as $l)
if ($l->validate() && $str == $l->domain())
return $l;
return null;
}
public static function tryFromHttpAcceptLanguage(string $httpAccept) : ?self
{
if (!$httpAccept)
return null;
$available = [];
// e.g.: de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
foreach (explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $loc)
if (preg_match('/([a-z\-]+)(?:\s*;\s*q\s*=\s*([\.\d]+))?/ui', $loc, $m, PREG_UNMATCHED_AS_NULL))
$available[Util::lower($m[1])] = floatVal($m[2] ?? 1); // no quality set: assume 100%
arsort($available, SORT_NUMERIC); // highest quality on top
foreach ($available as $code => $_)
foreach (self::cases() as $l)
if ($l->validate() && in_array($code, $l->httpCode()))
return $l;
return null;
}
public static function getFallback() : self
{
foreach (Locale::cases() as $l)
if ($l->validate())
return $l;
// wow, you really fucked up your config mate!
trigger_error('Locale::getFallback - there are no valid locales', E_USER_ERROR);
return self::EN;
}
}
/* The shape of things to come */
class LocString
{
private WeakMap $store;
public function __construct(array $data, string $key = 'name', ?callable $callback = null)
{
$this->store = new WeakMap();
$callback ??= fn($x) => $x;
if (!array_filter($data, fn($v, $k) => $v && strstr($k, $key.'_loc'), ARRAY_FILTER_USE_BOTH))
trigger_error('LocString - is entrirely empty', E_USER_WARNING);
foreach (Locale::cases() as $l)
$this->store[$l] = (string)$callback($data[$key.'_loc'.$l->value] ?? '');
}
public function __toString() : string
{
if ($str = $this->store[Lang::getLocale()])
return $str;
foreach (Locale::cases() as $l) // desired loc not set, use any other
if ($str = $this->store[$l])
return Cfg::get('DEBUG') ? '['.$str.']' : $str;
return Cfg::get('DEBUG') ? '[LOCSTRING]' : '';
}
}
?>

View File

@@ -918,16 +918,18 @@ class SmartAI
$this->jsGlobals[Type::NPC][] = $e['param'][1];
break;
case SAI_EVENT_LINK: // 61 - Used to link together multiple events as a chain of events.
$e['param'][10] = LANG::concat(DB::World()->selectCol('SELECT CONCAT("#[b]", id, "[/b]") FROM smart_scripts WHERE link = ?d AND entryorguid = ?d AND source_type = ?d', $this->itr['id'], $this->entry, $this->srcType), false);
if ($links = DB::World()->selectCol('SELECT `id` FROM smart_scripts WHERE `link` = ?d AND `entryorguid` = ?d AND `source_type` = ?d', $this->itr['id'], $this->entry, $this->srcType))
$e['param'][10] = LANG::concat($links, false, fn($x) => "#[b]".$x."[/b]");
break;
case SAI_EVENT_GOSSIP_SELECT: // 62 - On gossip clicked (gossip_menu_option335).
$gmo = DB::World()->selectRow('SELECT gmo.OptionText AS text_loc0 {, gmol.OptionText AS text_loc?d}
FROM gossip_menu_option gmo LEFT JOIN gossip_menu_option_locale gmol ON gmo.MenuID = gmol.MenuID AND gmo.OptionID = gmol.OptionID AND gmol.Locale = ?d
WHERE gmo.MenuId = ?d AND gmo.OptionID = ?d',
User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP,
User::$localeId,
$e['param'][0],
$e['param'][1]
$gmo = DB::World()->selectRow(
'SELECT gmo.`OptionText` AS "text_loc0" {, gmol.`OptionText` AS text_loc?d }
FROM gossip_menu_option gmo
LEFT JOIN gossip_menu_option_locale gmol ON gmo.`MenuID` = gmol.`MenuID` AND gmo.`OptionID` = gmol.`OptionID` AND gmol.`Locale` = ?d
WHERE gmo.`MenuId` = ?d AND gmo.`OptionID` = ?d',
Lang::getLocale() != Locale::EN ? Lang::getLocale()->json() : DBSIMPLE_SKIP,
Lang::getLocale()->value,
$e['param'][0], $e['param'][1]
);
if ($gmo)
@@ -941,11 +943,11 @@ class SmartAI
break;
case SAI_EVENT_DISTANCE_CREATURE: // 75 - On creature guid OR any instance of creature entry is within distance.
if ($e['param'][0])
$e['param'][10] = DB::World()->selectCell('SELECT id FROM creature WHERE guid = ?d', $e['param'][0]);
$e['param'][10] = DB::World()->selectCell('SELECT `id` FROM creature WHERE `guid` = ?d', $e['param'][0]);
// do not break;
case SAI_EVENT_DISTANCE_GAMEOBJECT: // 76 - On gameobject guid OR any instance of gameobject entry is within distance.
if ($e['param'][0] && !$e['param'][10])
$e['param'][10] = DB::World()->selectCell('SELECT id FROM gameobject WHERE guid = ?d', $e['param'][0]);
$e['param'][10] = DB::World()->selectCell('SELECT `id` FROM gameobject WHERE `guid` = ?d', $e['param'][0]);
else if ($e['param'][1])
$e['param'][10] = $e['param'][1];
else if (!$e['param'][10])
@@ -1244,7 +1246,7 @@ class SmartAI
JOIN ?_taxinodes tn1 ON tp.startNodeId = tn1.id
JOIN ?_taxinodes tn2 ON tp.endNodeId = tn2.id
WHERE tp.id = ?d',
User::$localeId, User::$localeId, User::$localeId, User::$localeId, $a['param'][0]
Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, $a['param'][0]
);
$a['param'][6] = Util::localizedString($nodes, 'start');
$a['param'][7] = Util::localizedString($nodes, 'end');

View File

@@ -343,9 +343,9 @@ class AchievementListFilter extends Filter
{
$_ = [];
if (isset($_v['ex']) && $_v['ex'] == 'on')
$_ = $this->modularizeString(['name_loc'.User::$localeId, 'reward_loc'.User::$localeId, 'description_loc'.User::$localeId]);
$_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value, 'reward_loc'.Lang::getLocale()->value, 'description_loc'.Lang::getLocale()->value]);
else
$_ = $this->modularizeString(['name_loc'.User::$localeId]);
$_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value]);
if ($_)
$parts[] = $_;

View File

@@ -353,9 +353,9 @@ class CreatureListFilter extends Filter
{
$_ = [];
if (isset($_v['ex']) && $_v['ex'] == 'on')
$_ = $this->modularizeString(['name_loc'.User::$localeId, 'subname_loc'.User::$localeId]);
$_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value, 'subname_loc'.Lang::getLocale()->value]);
else
$_ = $this->modularizeString(['name_loc'.User::$localeId]);
$_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value]);
if ($_)
$parts[] = $_;

View File

@@ -13,7 +13,7 @@ class EnchantmentList extends BaseType
public static $dataTable = '?_itemenchantment';
private $jsonStats = [];
private $relSpells = [];
private SpellList $relSpells;
private $triggerIds = [];
protected $queryBase = 'SELECT ie.*, ie.id AS ARRAY_KEY FROM ?_itemenchantment ie';
@@ -26,6 +26,8 @@ class EnchantmentList extends BaseType
{
parent::__construct($conditions, $miscData);
$relSpells = [];
// post processing
foreach ($this->iterate() as &$curTpl)
{
@@ -40,15 +42,15 @@ class EnchantmentList extends BaseType
case ENCHANTMENT_TYPE_COMBAT_SPELL:
$proc = -$this->getField('ppmRate') ?: ($this->getField('procChance') ?: $this->getField('amount'.$i));
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_HIT, $curTpl['charges'], $proc];
$this->relSpells[] = $curTpl['object'.$i];
$relSpells[] = $curTpl['object'.$i];
break;
case ENCHANTMENT_TYPE_EQUIP_SPELL:
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_EQUIP, $curTpl['charges'], 0];
$this->relSpells[] = $curTpl['object'.$i];
$relSpells[] = $curTpl['object'.$i];
break;
case ENCHANTMENT_TYPE_USE_SPELL:
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_USE, $curTpl['charges'], 0];
$this->relSpells[] = $curTpl['object'.$i];
$relSpells[] = $curTpl['object'.$i];
break;
}
}
@@ -56,8 +58,8 @@ class EnchantmentList extends BaseType
$this->jsonStats[$this->id] = (new StatsContainer)->fromJson($curTpl, true);
}
if ($this->relSpells)
$this->relSpells = new SpellList(array(['id', $this->relSpells]));
if ($relSpells)
$this->relSpells = new SpellList(array(['id', $relSpells]));
}
// use if you JUST need the name
@@ -241,7 +243,7 @@ class EnchantmentListFilter extends Filter
//string
if (isset($_v['na']))
if ($_ = $this->modularizeString(['name_loc'.User::$localeId]))
if ($_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value]))
$parts[] = $_;
// type

View File

@@ -180,7 +180,7 @@ class GameObjectListFilter extends Filter
// name
if (isset($_v['na']))
if ($_ = $this->modularizeString(['name_loc'.User::$localeId]))
if ($_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value]))
$parts[] = $_;
return $parts;

View File

@@ -1935,7 +1935,7 @@ class ItemListFilter extends Filter
104 => [FILTER_CR_STRING, 'description', STR_LOCALIZED ], // flavortext
105 => [FILTER_CR_CALLBACK, 'cbDropsInInstance', SRC_FLAG_DUNGEON_DROP, 1 ], // dropsinnormal [heroicdungeon-any]
106 => [FILTER_CR_CALLBACK, 'cbDropsInInstance', SRC_FLAG_DUNGEON_DROP, 2 ], // dropsinheroic [heroicdungeon-any]
107 => [FILTER_CR_NYI_PH, null, 1, ], // effecttext [str] not yet parsed ['effectsParsed_loc'.User::$localeId, $cr[2]]
107 => [FILTER_CR_NYI_PH, null, 1, ], // effecttext [str] not yet parsed ['effectsParsed_loc'.Lang::getLocale()->value, $cr[2]]
109 => [FILTER_CR_CALLBACK, 'cbArmorBonus', null, null ], // armorbonus [op] [int]
111 => [FILTER_CR_NUMERIC, 'requiredSkillRank', NUM_CAST_INT, true ], // reqskillrank
113 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
@@ -2122,7 +2122,7 @@ class ItemListFilter extends Filter
// name
if (isset($_v['na']))
if ($_ = $this->modularizeString(['name_loc'.User::$localeId]))
if ($_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value]))
$parts[] = $_;
// usable-by (not excluded by requiredClass && armor or weapons match mask from ?_classes)
@@ -2277,7 +2277,7 @@ class ItemListFilter extends Filter
$n = preg_replace(Filter::PATTERN_NAME, '', $cr[2]);
$n = $this->transformString($n, false);
$randIds = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, ABS(`id`) AS `id`, name_loc?d, `name_loc0` FROM ?_itemrandomenchant WHERE name_loc?d LIKE ?', User::$localeId, User::$localeId, $n);
$randIds = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, ABS(`id`) AS `id`, name_loc?d, `name_loc0` FROM ?_itemrandomenchant WHERE name_loc?d LIKE ?', Lang::getLocale()->value, Lang::getLocale()->value, $n);
$tplIds = $randIds ? DB::World()->select('SELECT `entry`, `ench` FROM item_enchantment_template WHERE `ench` IN (?a)', array_column($randIds, 'id')) : [];
foreach ($tplIds as &$set)
{

View File

@@ -211,7 +211,7 @@ class ItemsetListFilter extends Filter
// name [str]
if (isset($_v['na']))
if ($_ = $this->modularizeString(['name_loc'.User::$localeId]))
if ($_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value]))
$parts[] = $_;
// quality [enum]

View File

@@ -498,9 +498,9 @@ class QuestListFilter extends Filter
{
$_ = [];
if (isset($_v['ex']) && $_v['ex'] == 'on')
$_ = $this->modularizeString(['name_loc'.User::$localeId, 'objectives_loc'.User::$localeId, 'details_loc'.User::$localeId]);
$_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value, 'objectives_loc'.Lang::getLocale()->value, 'details_loc'.Lang::getLocale()->value]);
else
$_ = $this->modularizeString(['name_loc'.User::$localeId]);
$_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value]);
if ($_)
$parts[] = $_;

View File

@@ -1436,8 +1436,8 @@ class SpellList extends BaseType
$this->charLevel = $level;
// step -1: already handled?
if (isset($this->parsedText[$this->id][$type][User::$localeId][$this->charLevel][(int)$this->interactive]))
return $this->parsedText[$this->id][$type][User::$localeId][$this->charLevel][(int)$this->interactive];
if (isset($this->parsedText[$this->id][$type][Lang::getLocale()->value][$this->charLevel][(int)$this->interactive]))
return $this->parsedText[$this->id][$type][Lang::getLocale()->value][$this->charLevel][(int)$this->interactive];
// step 0: get text
$data = $this->getField($type, true);
@@ -1543,7 +1543,7 @@ class SpellList extends BaseType
$data = strtr($data, ["\r" => '', "\n" => '<br />']);
// cache result
$this->parsedText[$this->id][$type][User::$localeId][$this->charLevel][(int)$this->interactive] = [$data, $relSpells, $this->scaling[$this->id]];
$this->parsedText[$this->id][$type][Lang::getLocale()->value][$this->charLevel][(int)$this->interactive] = [$data, $relSpells, $this->scaling[$this->id]];
return [$data, $relSpells, $this->scaling[$this->id]];
}
@@ -2451,9 +2451,9 @@ class SpellListFilter extends Filter
{
$_ = [];
if (isset($_v['ex']) && $_v['ex'] == 'on')
$_ = $this->modularizeString(['name_loc'.User::$localeId, 'buff_loc'.User::$localeId, 'description_loc'.User::$localeId]);
$_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value, 'buff_loc'.Lang::getLocale()->value, 'description_loc'.Lang::getLocale()->value]);
else
$_ = $this->modularizeString(['name_loc'.User::$localeId]);
$_ = $this->modularizeString(['name_loc'.Lang::getLocale()->value]);
if ($_)
$parts[] = $_;

View File

@@ -45,9 +45,13 @@ class TitleList extends BaseType
unset($_curTpl['src3']);
// shorthand for more generic access
foreach (Util::$localeStrings as $i => $str)
if ($str)
$_curTpl['name_loc'.$i] = trim(str_replace('%s', '', $_curTpl['male_loc'.$i]));
// i don't see it being used anywhere..?
/*
foreach (Locale::cases() as $loc)
if ($loc->validate())
$_curTpl['name'] = new LocString($_curTpl, 'male', fn($x) => trim(str_replace('%s', '', $x)));
// $_curTpl['name_loc'.$loc->value] = trim(str_replace('%s', '', $_curTpl['male_loc'.$loc->value]));
*/
}
}

View File

@@ -6,28 +6,34 @@ if (!defined('AOWOW_REVISION'))
class User
{
public static $id = 0;
public static $displayName = '';
public static $banStatus = 0x0; // see ACC_BAN_* defines
public static $groups = 0x0;
public static $perms = 0;
public static $localeId = 0;
public static $localeString = 'enus';
public static $avatar = 'inv_misc_questionmark';
public static $dailyVotes = 0;
public static int $id = 0;
public static string $displayName = '';
public static int $banStatus = 0x0; // see ACC_BAN_* defines
public static int $groups = 0x0;
public static int $perms = 0;
public static string $avatar = 'inv_misc_questionmark';
public static int $dailyVotes = 0;
public static $ip = null;
private static $reputation = 0;
private static $dataKey = '';
private static $expires = false;
private static $passHash = '';
private static $excludeGroups = 1;
private static $profiles = null;
private static int $reputation = 0;
private static string $dataKey = '';
private static bool $expires = false;
private static string $passHash = '';
private static int $excludeGroups = 1;
public static Locale $preferedLoc;
private static LocalProfileList $profiles;
public static function init()
{
self::setIP();
self::setLocale();
if (isset($_SESSION['locale']) && $_SESSION['locale'] instanceof Locale)
self::$preferedLoc = $_SESSION['locale']->validate() ?? Locale::getFallback();
else if (!empty($_SERVER["HTTP_ACCEPT_LANGUAGE"]) && ($loc = Locale::tryFromHttpAcceptLanguage($_SERVER["HTTP_ACCEPT_LANGUAGE"])))
self::$preferedLoc = $loc;
else
self::$preferedLoc = Locale::getFallback();
// session have a dataKey to access the JScripts (yes, also the anons)
if (empty($_SESSION['dataKey']))
@@ -39,12 +45,12 @@ class User
return false;
// check IP bans
if ($ipBan = DB::Aowow()->selectRow('SELECT count, unbanDate FROM ?_account_bannedips WHERE ip = ? AND type = 0', self::$ip))
if ($ipBan = DB::Aowow()->selectRow('SELECT `count`, `unbanDate` FROM ?_account_bannedips WHERE `ip` = ? AND `type` = 0', self::$ip))
{
if ($ipBan['count'] > Cfg::get('ACC_FAILED_AUTH_COUNT') && $ipBan['unbanDate'] > time())
return false;
else if ($ipBan['unbanDate'] <= time())
DB::Aowow()->query('DELETE FROM ?_account_bannedips WHERE ip = ?', self::$ip);
DB::Aowow()->query('DELETE FROM ?_account_bannedips WHERE `ip` = ?', self::$ip);
}
// try to restore session
@@ -55,36 +61,39 @@ class User
if (!empty($_SESSION['timeout']) && $_SESSION['timeout'] <= time())
return false;
$query = DB::Aowow()->SelectRow('
SELECT a.id, a.passHash, a.displayName, a.locale, a.userGroups, a.userPerms, a.allowExpire, BIT_OR(ab.typeMask) AS bans, IFNULL(SUM(r.amount), 0) as reputation, a.avatar, a.dailyVotes, a.excludeGroups
$uData = DB::Aowow()->SelectRow(
'SELECT a.`id`, a.`passHash`, a.`displayName`, a.`locale`, a.`userGroups`, a.`userPerms`, a.`allowExpire`, BIT_OR(ab.`typeMask`) AS "bans", IFNULL(SUM(r.`amount`), 0) AS "reputation", a.`avatar`, a.`dailyVotes`, a.`excludeGroups`
FROM ?_account a
LEFT JOIN ?_account_banned ab ON a.id = ab.userId AND ab.end > UNIX_TIMESTAMP()
LEFT JOIN ?_account_reputation r ON a.id = r.userId
WHERE a.id = ?d
GROUP BY a.id',
LEFT JOIN ?_account_banned ab ON a.`id` = ab.`userId` AND ab.`end` > UNIX_TIMESTAMP()
LEFT JOIN ?_account_reputation r ON a.`id` = r.`userId`
WHERE a.`id` = ?d
GROUP BY a.`id`',
$_SESSION['user']
);
if (!$query)
if (!$uData)
return false;
if ($loc = Locale::tryFrom($uData['locale']))
self::$preferedLoc = $loc;
// password changed, terminate session
if (AUTH_MODE_SELF && $query['passHash'] != $_SESSION['hash'])
if (AUTH_MODE_SELF && $uData['passHash'] != $_SESSION['hash'])
{
self::destroy();
return false;
}
self::$id = intval($query['id']);
self::$displayName = $query['displayName'];
self::$passHash = $query['passHash'];
self::$expires = (bool)$query['allowExpire'];
self::$reputation = $query['reputation'];
self::$banStatus = $query['bans'];
self::$groups = $query['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($query['userGroups']);
self::$perms = $query['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($query['userPerms']);
self::$dailyVotes = $query['dailyVotes'];
self::$excludeGroups = $query['excludeGroups'];
self::$id = intVal($uData['id']);
self::$displayName = $uData['displayName'];
self::$passHash = $uData['passHash'];
self::$expires = (bool)$uData['allowExpire'];
self::$reputation = $uData['reputation'];
self::$banStatus = $uData['bans'];
self::$groups = $uData['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($uData['userGroups']);
self::$perms = $uData['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($uData['userPerms']);
self::$dailyVotes = $uData['dailyVotes'];
self::$excludeGroups = $uData['excludeGroups'];
$conditions = array(
[['cuFlags', PROFILER_CU_DELETED, '&'], 0],
@@ -96,11 +105,8 @@ class User
self::$profiles = (new LocalProfileList($conditions));
if ($query['avatar'])
self::$avatar = $query['avatar'];
if (self::$localeId != $query['locale']) // reset, if changed
self::setLocale(intVal($query['locale']));
if ($uData['avatar'])
self::$avatar = $uData['avatar'];
// stuff, that updates on a daily basis goes here (if you keep you session alive indefinitly, the signin-handler doesn't do very much)
// - conscutive visits
@@ -115,10 +121,10 @@ class User
// daily votes (we need to reset this one)
self::$dailyVotes = self::getMaxDailyVotes();
DB::Aowow()->query('
UPDATE ?_account
SET dailyVotes = ?d, prevLogin = curLogin, curLogin = UNIX_TIMESTAMP(), prevIP = curIP, curIP = ?
WHERE id = ?d',
DB::Aowow()->query(
'UPDATE ?_account
SET `dailyVotes` = ?d, `prevLogin` = `curLogin`, `curLogin` = UNIX_TIMESTAMP(), `prevIP` = `curIP`, `curIP` = ?
WHERE `id` = ?d',
self::$dailyVotes,
self::$ip,
self::$id
@@ -131,16 +137,16 @@ class User
// increment consecutive visits (next day or first of new month and not more than 48h)
// i bet my ass i forgott a corner case
if ((date('j', $lastLogin) + 1 == date('j') || (date('j') == 1 && date('n', $lastLogin) != date('n'))) && (time() - $lastLogin) < 2 * DAY)
DB::Aowow()->query('UPDATE ?_account SET consecutiveVisits = consecutiveVisits + 1 WHERE id = ?d', self::$id);
DB::Aowow()->query('UPDATE ?_account SET `consecutiveVisits` = `consecutiveVisits` + 1 WHERE `id` = ?d', self::$id);
else
DB::Aowow()->query('UPDATE ?_account SET consecutiveVisits = 0 WHERE id = ?d', self::$id);
DB::Aowow()->query('UPDATE ?_account SET `consecutiveVisits` = 0 WHERE `id` = ?d', self::$id);
}
}
return true;
}
private static function setIP()
private static function setIP() : void
{
$ipAddr = '';
$method = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
@@ -165,68 +171,32 @@ class User
self::$ip = $ipAddr ?: null;
}
/****************/
/* set language */
/****************/
// set and save
public static function setLocale($set = -1)
public static function save(bool $toDB = false)
{
$loc = LOCALE_EN;
$_SESSION['user'] = self::$id;
$_SESSION['hash'] = self::$passHash;
$_SESSION['locale'] = self::$preferedLoc;
$_SESSION['timeout'] = self::$expires ? time() + Cfg::get('SESSION_TIMEOUT_DELAY') : 0;
// $_SESSION['dataKey'] does not depend on user login status and is set in User::init()
// get
if ($set != -1 && isset(Util::$localeStrings[$set]))
$loc = $set;
else if (isset($_SESSION['locale']) && isset(Util::$localeStrings[$_SESSION['locale']]))
$loc = $_SESSION['locale'];
else if (!empty($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
{
$loc = strtolower(substr($_SERVER["HTTP_ACCEPT_LANGUAGE"], 0, 2));
switch ($loc) {
case 'fr': $loc = LOCALE_FR; break;
case 'de': $loc = LOCALE_DE; break;
case 'zh': $loc = LOCALE_CN; break; // may cause issues in future with zh-tw
case 'es': $loc = LOCALE_ES; break;
case 'ru': $loc = LOCALE_RU; break;
default: $loc = LOCALE_EN;
}
}
// check; pick first viable if failed
$allowedLoc = Cfg::get('LOCALES');
if ($allowedLoc && !($allowedLoc & (1 << $loc)))
{
foreach (Util::$localeStrings as $idx => $__)
{
if ($allowedLoc & (1 << $idx))
{
$loc = $idx;
break;
}
}
}
// set
if (self::$id)
DB::Aowow()->query('UPDATE ?_account SET locale = ? WHERE id = ?', $loc, self::$id);
self::useLocale($loc);
if (self::$id && $toDB)
DB::Aowow()->query('UPDATE ?_account SET `locale` = ? WHERE `id` = ?', self::$preferedLoc->value, self::$id);
}
// only use once
public static function useLocale($use)
public static function destroy()
{
self::$localeId = isset(Util::$localeStrings[$use]) ? $use : LOCALE_EN;
self::$localeString = self::localeString(self::$localeId);
session_regenerate_id(true); // session itself is not destroyed; status changed => regenerate id
session_unset();
$_SESSION['locale'] = self::$preferedLoc; // keep locale
$_SESSION['dataKey'] = self::$dataKey; // keep dataKey
self::$id = 0;
self::$displayName = '';
self::$perms = 0;
self::$groups = U_GROUP_NONE;
}
private static function localeString($loc = -1)
{
if (!isset(Util::$localeStrings[$loc]))
$loc = 0;
return Util::$localeStrings[$loc];
}
/*******************/
/* auth mechanisms */
@@ -245,21 +215,21 @@ class User
return AUTH_INTERNAL_ERR;
// handle login try limitation
$ip = DB::Aowow()->selectRow('SELECT ip, count, unbanDate FROM ?_account_bannedips WHERE type = 0 AND ip = ?', self::$ip);
$ip = DB::Aowow()->selectRow('SELECT `ip`, `count`, `unbanDate` FROM ?_account_bannedips WHERE `type` = 0 AND `ip` = ?', self::$ip);
if (!$ip || $ip['unbanDate'] < time()) // no entry exists or time expired; set count to 1
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 0, 1, UNIX_TIMESTAMP() + ?d)', self::$ip, Cfg::get('ACC_FAILED_AUTH_BLOCK'));
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (`ip`, `type`, `count`, `unbanDate`) VALUES (?, 0, 1, UNIX_TIMESTAMP() + ?d)', self::$ip, Cfg::get('ACC_FAILED_AUTH_BLOCK'));
else // entry already exists; increment count
DB::Aowow()->query('UPDATE ?_account_bannedips SET count = count + 1, unbanDate = UNIX_TIMESTAMP() + ?d WHERE ip = ?', Cfg::get('ACC_FAILED_AUTH_BLOCK'), self::$ip);
DB::Aowow()->query('UPDATE ?_account_bannedips SET `count` = `count` + 1, `unbanDate` = UNIX_TIMESTAMP() + ?d WHERE `ip` = ?', Cfg::get('ACC_FAILED_AUTH_BLOCK'), self::$ip);
if ($ip && $ip['count'] >= Cfg::get('ACC_FAILED_AUTH_COUNT') && $ip['unbanDate'] >= time())
return AUTH_IPBANNED;
$query = DB::Aowow()->SelectRow('
SELECT a.id, a.passHash, BIT_OR(ab.typeMask) AS bans, a.status
$query = DB::Aowow()->SelectRow(
'SELECT a.`id`, a.`passHash`, BIT_OR(ab.`typeMask`) AS "bans", a.`status`
FROM ?_account a
LEFT JOIN ?_account_banned ab ON a.id = ab.userId AND ab.end > UNIX_TIMESTAMP()
WHERE a.user = ?
GROUP BY a.id',
LEFT JOIN ?_account_banned ab ON a.`id` = ab.`userId` AND ab.`end` > UNIX_TIMESTAMP()
WHERE a.`user` = ?
GROUP BY a.`id`',
$name
);
if (!$query)
@@ -270,7 +240,7 @@ class User
return AUTH_WRONGPASS;
// successfull auth; clear bans for this IP
DB::Aowow()->query('DELETE FROM ?_account_bannedips WHERE type = 0 AND ip = ?', self::$ip);
DB::Aowow()->query('DELETE FROM ?_account_bannedips WHERE `type` = 0 AND `ip` = ?', self::$ip);
if ($query['bans'] & (ACC_BAN_PERM | ACC_BAN_TEMP))
return AUTH_BANNED;
@@ -304,10 +274,19 @@ class User
case AUTH_MODE_EXTERNAL:
{
if (!file_exists('config/extAuth.php'))
{
trigger_error('config/extAuth.php not found');
return AUTH_INTERNAL_ERR;
}
require 'config/extAuth.php';
if (!function_exists('extAuth'))
{
trigger_error('external auth function extAuth() not defined in config/extAuth.php');
return AUTH_INTERNAL_ERR;
}
$extGroup = -1;
$result = extAuth($name, $pass, $extId, $extGroup);
@@ -335,27 +314,22 @@ class User
return AUTH_OK;
}
// create a linked account for our settings if nessecary
private static function checkOrCreateInDB($extId, $name, $userGroup = -1)
// create a linked account for our settings if necessary
private static function checkOrCreateInDB(int $extId, string $name, int $userGroup = -1) : int
{
if (!intVal($extId))
return 0;
$userGroup = intVal($userGroup);
if ($_ = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE extId = ?d', $extId))
if ($_ = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE `extId` = ?d', $extId))
{
if ($userGroup >= U_GROUP_NONE)
DB::Aowow()->query('UPDATE ?_account SET userGroups = ?d WHERE extId = ?d', $userGroup, $extId);
DB::Aowow()->query('UPDATE ?_account SET `userGroups` = ?d WHERE `extId` = ?d', $userGroup, $extId);
return $_;
}
$newId = DB::Aowow()->query('INSERT IGNORE INTO ?_account (extId, user, displayName, joinDate, prevIP, prevLogin, locale, status, userGroups) VALUES (?d, ?, ?, UNIX_TIMESTAMP(), ?, UNIX_TIMESTAMP(), ?d, ?d, ?d)',
$newId = DB::Aowow()->query('INSERT IGNORE INTO ?_account (`extId`, `user`, `displayName`, `joinDate`, `prevIP`, `prevLogin`, `locale`, `status`, `userGroups`) VALUES (?d, ?, ?, UNIX_TIMESTAMP(), ?, UNIX_TIMESTAMP(), ?d, ?d, ?d)',
$extId,
$name,
Util::ucFirst($name),
isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : '',
User::$localeId,
$_SERVER["REMOTE_ADDR"] ?? '',
self::$preferedLoc->value,
ACC_STATUS_OK,
$userGroup >= U_GROUP_NONE ? $userGroup : U_GROUP_NONE
);
@@ -439,39 +413,17 @@ class User
return $errCode == 0;
}
public static function save()
{
$_SESSION['user'] = self::$id;
$_SESSION['hash'] = self::$passHash;
$_SESSION['locale'] = self::$localeId;
$_SESSION['timeout'] = self::$expires ? time() + Cfg::get('SESSION_TIMEOUT_DELAY') : 0;
// $_SESSION['dataKey'] does not depend on user login status and is set in User::init()
}
public static function destroy()
{
session_regenerate_id(true); // session itself is not destroyed; status changed => regenerate id
session_unset();
$_SESSION['locale'] = self::$localeId; // keep locale
$_SESSION['dataKey'] = self::$dataKey; // keep dataKey
self::$id = 0;
self::$displayName = '';
self::$perms = 0;
self::$groups = U_GROUP_NONE;
}
/*********************/
/* access management */
/*********************/
public static function isInGroup($group)
public static function isInGroup($group) : bool
{
return (self::$groups & $group) != 0;
}
public static function canComment()
public static function canComment() : bool
{
if (!self::$id || self::$banStatus & (ACC_BAN_COMMENT | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
@@ -479,7 +431,7 @@ class User
return self::$perms || self::$reputation >= Cfg::get('REP_REQ_COMMENT');
}
public static function canReply()
public static function canReply() : bool
{
if (!self::$id || self::$banStatus & (ACC_BAN_COMMENT | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
@@ -487,7 +439,7 @@ class User
return self::$perms || self::$reputation >= Cfg::get('REP_REQ_REPLY');
}
public static function canUpvote()
public static function canUpvote() : bool
{
if (!self::$id || self::$banStatus & (ACC_BAN_COMMENT | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
@@ -495,7 +447,7 @@ class User
return self::$perms || (self::$reputation >= Cfg::get('REP_REQ_UPVOTE') && self::$dailyVotes > 0);
}
public static function canDownvote()
public static function canDownvote() : bool
{
if (!self::$id || self::$banStatus & (ACC_BAN_RATE | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
@@ -503,7 +455,7 @@ class User
return self::$perms || (self::$reputation >= Cfg::get('REP_REQ_DOWNVOTE') && self::$dailyVotes > 0);
}
public static function canSupervote()
public static function canSupervote() : bool
{
if (!self::$id || self::$banStatus & (ACC_BAN_RATE | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
@@ -511,7 +463,7 @@ class User
return self::$reputation >= Cfg::get('REP_REQ_SUPERVOTE');
}
public static function canUploadScreenshot()
public static function canUploadScreenshot() : bool
{
if (!self::$id || self::$banStatus & (ACC_BAN_SCREENSHOT | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
@@ -519,7 +471,7 @@ class User
return true;
}
public static function canWriteGuide()
public static function canWriteGuide() : bool
{
if (!self::$id || self::$banStatus & (ACC_BAN_GUIDE | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
@@ -527,7 +479,7 @@ class User
return true;
}
public static function canSuggestVideo()
public static function canSuggestVideo() : bool
{
if (!self::$id || self::$banStatus & (ACC_BAN_VIDEO | ACC_BAN_PERM | ACC_BAN_TEMP))
return false;
@@ -535,27 +487,28 @@ class User
return true;
}
public static function isPremium()
public static function isPremium() : bool
{
return self::isInGroup(U_GROUP_PREMIUM) || self::$reputation >= Cfg::get('REP_REQ_PREMIUM');
}
/**************/
/* js-related */
/**************/
public static function decrementDailyVotes()
public static function decrementDailyVotes() : void
{
self::$dailyVotes--;
DB::Aowow()->query('UPDATE ?_account SET dailyVotes = ?d WHERE id = ?d', self::$dailyVotes, self::$id);
DB::Aowow()->query('UPDATE ?_account SET `dailyVotes` = ?d WHERE `id` = ?d', self::$dailyVotes, self::$id);
}
public static function getCurDailyVotes()
public static function getCurrentDailyVotes() : int
{
return self::$dailyVotes;
}
public static function getMaxDailyVotes()
public static function getMaxDailyVotes() : int
{
if (!self::$id || self::$banStatus & (ACC_BAN_PERM | ACC_BAN_TEMP))
return 0;
@@ -563,12 +516,12 @@ class User
return Cfg::get('USER_MAX_VOTES') + (self::$reputation >= Cfg::get('REP_REQ_VOTEMORE_BASE') ? 1 + intVal((self::$reputation - Cfg::get('REP_REQ_VOTEMORE_BASE')) / Cfg::get('REP_REQ_VOTEMORE_ADD')) : 0);
}
public static function getReputation()
public static function getReputation() : int
{
return self::$reputation;
}
public static function getUserGlobals()
public static function getUserGlobals() : array
{
$gUser = array(
'id' => self::$id,
@@ -613,34 +566,34 @@ class User
return $gUser;
}
public static function getWeightScales()
public static function getWeightScales() : array
{
$result = [];
$res = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, name FROM ?_account_weightscales WHERE userId = ?d', self::$id);
$res = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `name` FROM ?_account_weightscales WHERE `userId` = ?d', self::$id);
if (!$res)
return $result;
$weights = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, `field` AS ARRAY_KEY2, val FROM ?_account_weightscale_data WHERE id IN (?a)', array_keys($res));
$weights = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `field` AS ARRAY_KEY2, `val` FROM ?_account_weightscale_data WHERE `id` IN (?a)', array_keys($res));
foreach ($weights as $id => $data)
$result[] = array_merge(['name' => $res[$id], 'id' => $id], $data);
return $result;
}
public static function getProfilerExclusions()
public static function getProfilerExclusions() : array
{
$result = [];
$modes = [1 => 'excludes', 2 => 'includes'];
foreach ($modes as $mode => $field)
if ($ex = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, typeId AS ARRAY_KEY2, typeId FROM ?_account_excludes WHERE mode = ?d AND userId = ?d', $mode, self::$id))
if ($ex = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, `typeId` AS ARRAY_KEY2, `typeId` FROM ?_account_excludes WHERE `mode` = ?d AND `userId` = ?d', $mode, self::$id))
foreach ($ex as $type => $ids)
$result[$field][$type] = array_values($ids);
return $result;
}
public static function getCharacters()
public static function getCharacters() : array
{
if (!self::$profiles)
return [];
@@ -648,7 +601,7 @@ class User
return self::$profiles->getJSGlobals(PROFILEINFO_CHARACTER);
}
public static function getProfiles()
public static function getProfiles() : array
{
if (!self::$profiles)
return [];
@@ -675,7 +628,7 @@ class User
return [];
}
public static function getGuides()
public static function getGuides() : array
{
$result = [];
@@ -689,17 +642,17 @@ class User
return $result;
}
public static function getCookies()
public static function getCookies() : array
{
$data = [];
if (self::$id)
$data = DB::Aowow()->selectCol('SELECT name AS ARRAY_KEY, data FROM ?_account_cookies WHERE userId = ?d', self::$id);
$data = DB::Aowow()->selectCol('SELECT `name` AS ARRAY_KEY, `data` FROM ?_account_cookies WHERE `userId` = ?d', self::$id);
return $data;
}
public static function getFavorites()
public static function getFavorites() : array
{
if (!self::$id)
return [];

View File

@@ -77,22 +77,6 @@ trait TrRequestData
return 0;
}
private static function checkLocale(string $val) : int
{
if (preg_match('/^'.implode('|', array_keys(array_filter(Util::$localeStrings))).'$/', $val))
return intVal($val);
return -1;
}
private static function checkDomain(string $val) : string
{
if (preg_match('/^'.implode('|', array_filter(Util::$subDomains)).'$/i', $val))
return strtolower($val);
return '';
}
private static function checkIdList(string $val) : array
{
if (preg_match('/^-?\d+(,-?\d+)*$/', $val))
@@ -538,14 +522,6 @@ abstract class Util
private static $perfectGems = null;
public static $localeStrings = array( // zero-indexed
'enus', null, 'frfr', 'dede', 'zhcn', null, 'eses', null, 'ruru'
);
public static $subDomains = array(
'en', null, 'fr', 'de', 'cn', null, 'es', null, 'ru'
);
public static $regions = array(
'us', 'eu', 'kr', 'tw', 'cn', 'dev'
);
@@ -919,19 +895,19 @@ abstract class Util
$silent = true;
// default case: selected locale available
if (!empty($data[$field.'_loc'.User::$localeId]))
return $data[$field.'_loc'.User::$localeId];
if (!empty($data[$field.'_loc'.Lang::getLocale()->value]))
return $data[$field.'_loc'.Lang::getLocale()->value];
// locale not enUS; aowow-type localization available; add brackets if not silent
else if (User::$localeId != LOCALE_EN && !empty($data[$field.'_loc0']))
else if (Lang::getLocale() != Locale::EN && !empty($data[$field.'_loc0']))
return $silent ? $data[$field.'_loc0'] : '['.$data[$field.'_loc0'].']';
// locale not enUS; TC localization; add brackets if not silent
else if (User::$localeId != LOCALE_EN && !empty($data[$field]))
else if (Lang::getLocale() != Locale::EN && !empty($data[$field]))
return $silent ? $data[$field] : '['.$data[$field].']';
// locale enUS; TC localization; return normal
else if (User::$localeId == LOCALE_EN && !empty($data[$field]))
else if (Lang::getLocale() == Locale::EN && !empty($data[$field]))
return $data[$field];
// nothing to find; be empty
@@ -969,22 +945,6 @@ abstract class Util
return Lang::item('ratingString', [$statId, $result, $level]);
}
public static function powerUseLocale(string $domain = 'en') : void
{
foreach (Util::$subDomains as $k => $v)
{
if ($domain != $v)
continue;
User::useLocale($k);
Lang::load($k);
return;
}
User::useLocale(LOCALE_EN);
Lang::load(LOCALE_EN);
}
// default ucFirst doesn't convert UTF-8 chars
public static function ucFirst($str)
{
@@ -1097,8 +1057,8 @@ abstract class Util
$success = true;
if ($localized)
{
if (file_exists('datasets/'.User::$localeString.'/'.$file))
$result .= file_get_contents('datasets/'.User::$localeString.'/'.$file);
if (file_exists('datasets/'.Lang::getLocale()->json().'/'.$file))
$result .= file_get_contents('datasets/'.Lang::getLocale()->json().'/'.$file);
else if (file_exists('datasets/enus/'.$file))
$result .= file_get_contents('datasets/enus/'.$file);
else