mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
Template/Endpoints (Base)
* redo page render following the logic of:
Response ─┬─> TextResponse ─> TextResponseImpl
└─> TemplateResponse ─> TemplateResponseImpl
* split up giant files, one per response path
* caching becomes a trait, implemented where necessary
* TextResponses (Ajax) can now be cached
* make use of previously defined php classes for js objects
* Tabs, Listview, Tooltip, Announcement, Markup, Book, ...
* \Aowow\Template\PageTemplate is the new class to be cached
* do not discard error messages generated after vars have been sent to template
and store in session for display at a later time
* implement tracking consent management
* move logic out of template into their respective endpoints
This commit is contained in:
657
includes/components/response/baseresponse.class.php
Normal file
657
includes/components/response/baseresponse.class.php
Normal file
@@ -0,0 +1,657 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
trait TrRecoveryHelper
|
||||
{
|
||||
const MODE_INFO = 0;
|
||||
const MODE_FORM_PASS = 1;
|
||||
const MODE_FORM_EMAIL = 2;
|
||||
|
||||
private function startRecovery(int $newStatus, string $mailTemplate, string $email) : string
|
||||
{
|
||||
if (!$newStatus <= ACC_STATUS_NEW && $newStatus > ACC_STATUS_CHANGE_PASS)
|
||||
return Lang::main('intError');
|
||||
|
||||
// check if already processing
|
||||
if ($_ = DB::Aowow()->selectCell('SELECT `statusTimer` - UNIX_TIMESTAMP() FROM ?_account WHERE `email` = ? AND `status` > ?d AND `statusTimer` > UNIX_TIMESTAMP()', $email, ACC_STATUS_NEW))
|
||||
return sprintf(Lang::account('isRecovering'), Util::formatTime($_ * 1000));
|
||||
|
||||
// create new token and write to db
|
||||
$token = Util::createHash();
|
||||
if (!DB::Aowow()->query('UPDATE ?_account SET `token` = ?, `status` = ?d, `statusTimer` = UNIX_TIMESTAMP() + ?d WHERE `email` = ?', $token, $newStatus, Cfg::get('ACC_RECOVERY_DECAY'), $email))
|
||||
return Lang::main('intError');
|
||||
|
||||
// send recovery mail
|
||||
if (!Util::sendMail($email, $mailTemplate, [$token], Cfg::get('ACC_RECOVERY_DECAY')))
|
||||
return sprintf(Lang::main('intError2'), 'send mail');
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
trait TrGetNext
|
||||
{
|
||||
private function getNext(bool $forHeader = false) : string
|
||||
{
|
||||
$next = '';
|
||||
if (!empty($this->_get['next']))
|
||||
$next = $this->_get['next'];
|
||||
else if (isset($_SERVER['HTTP_REFERER']) && strstr($_SERVER['HTTP_REFERER'], '?'))
|
||||
$next = explode('?', $_SERVER['HTTP_REFERER'])[1];
|
||||
else if ($forHeader)
|
||||
return '.';
|
||||
|
||||
return ($forHeader ? '?' : '').$next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Interface ICache
|
||||
{
|
||||
public function saveCache(string|Template\PageTemplate $toCache) : void;
|
||||
public function loadCache(bool|string|Template\PageTemplate &$fromCache) : bool;
|
||||
public function setOnCacheLoaded(callable $callback, mixed $params = null) : void;
|
||||
public function getCacheKeyComponents() : array;
|
||||
public function applyOnCacheLoaded(mixed &$data) : mixed;
|
||||
}
|
||||
|
||||
trait TrCache
|
||||
{
|
||||
private const STORE_METHOD_OBJECT = 0;
|
||||
private const STORE_METHOD_STRING = 1;
|
||||
|
||||
private int $_cacheType = CACHE_TYPE_NONE;
|
||||
private int $skipCache = 0x0;
|
||||
private ?int $decay = null;
|
||||
private string $cacheDir = 'cache/template/';
|
||||
private bool $cacheInited = false;
|
||||
private ?\Memcached $memcached = null;
|
||||
private array $onCacheLoaded = [null, null]; // post-load updater
|
||||
|
||||
public static array $cacheStats = []; // load info for page footer
|
||||
|
||||
// visible properties or given strings are cached
|
||||
public function saveCache(string|object $toCache) : void
|
||||
{
|
||||
$this->initCache();
|
||||
|
||||
if ($this->_cacheType == CACHE_TYPE_NONE)
|
||||
return;
|
||||
|
||||
if (!Cfg::get('CACHE_MODE') /* || Cfg::get('DEBUG') */)
|
||||
return;
|
||||
|
||||
if (!$this->decay)
|
||||
return;
|
||||
|
||||
$cKey = $this->formatCacheKey();
|
||||
$method = is_object($toCache) ? self::STORE_METHOD_OBJECT : self::STORE_METHOD_STRING;
|
||||
|
||||
if ($method == self::STORE_METHOD_OBJECT)
|
||||
$toCache = serialize($toCache);
|
||||
else
|
||||
$toCache = (string)$toCache;
|
||||
|
||||
if (is_callable($this->onCacheLoaded[0]))
|
||||
$postCache = serialize($this->onCacheLoaded);
|
||||
|
||||
if (Cfg::get('CACHE_MODE') & CACHE_MODE_MEMCACHED)
|
||||
{
|
||||
// on &refresh also clear related
|
||||
if ($this->skipCache & CACHE_MODE_MEMCACHED)
|
||||
$this->deleteCache(CACHE_MODE_MEMCACHED);
|
||||
|
||||
$data = array(
|
||||
'timestamp' => time(),
|
||||
'lifetime' => $this->decay,
|
||||
'revision' => AOWOW_REVISION,
|
||||
'method' => $method,
|
||||
'postCache' => $postCache ?? null,
|
||||
'data' => $toCache
|
||||
);
|
||||
|
||||
$this->memcached()?->set($cKey[2], $data);
|
||||
}
|
||||
|
||||
if (Cfg::get('CACHE_MODE') & CACHE_MODE_FILECACHE)
|
||||
{
|
||||
$data = time()." ".$this->decay." ".AOWOW_REVISION." ".$method."\n";
|
||||
$data .= ($postCache ?? '')."\n";
|
||||
$data .= gzcompress($toCache, 9);
|
||||
|
||||
// on &refresh also clear related
|
||||
if ($this->skipCache & CACHE_MODE_FILECACHE)
|
||||
$this->deleteCache(CACHE_MODE_FILECACHE);
|
||||
|
||||
if (Util::writeDir($this->cacheDir . implode(DIRECTORY_SEPARATOR, array_slice($cKey, 0, 2))))
|
||||
file_put_contents($this->cacheDir . implode(DIRECTORY_SEPARATOR, $cKey), $data);
|
||||
}
|
||||
}
|
||||
|
||||
public function loadCache(mixed &$fromCache) : bool
|
||||
{
|
||||
$this->initCache();
|
||||
|
||||
if ($this->_cacheType == CACHE_TYPE_NONE)
|
||||
return false;
|
||||
|
||||
if (!Cfg::get('CACHE_MODE') /* || Cfg::get('DEBUG') */)
|
||||
return false;
|
||||
|
||||
$cKey = $this->formatCacheKey();
|
||||
$rev = $method = $data = $postCache = null;
|
||||
|
||||
if ((Cfg::get('CACHE_MODE') & CACHE_MODE_MEMCACHED) && !($this->skipCache & CACHE_MODE_MEMCACHED))
|
||||
{
|
||||
if ($cache = $this->memcached()?->get($cKey[2]))
|
||||
{
|
||||
$method = $cache['method'];
|
||||
$data = $cache['data'];
|
||||
$postCache = $cache['postCache'];
|
||||
|
||||
if (($cache['timestamp'] + $cache['lifetime']) > time() && $cache['revision'] == AOWOW_REVISION)
|
||||
self::$cacheStats = [CACHE_MODE_MEMCACHED, $cache['timestamp'], $cache['lifetime']];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$data && (Cfg::get('CACHE_MODE') & CACHE_MODE_FILECACHE) && !($this->skipCache & CACHE_MODE_FILECACHE))
|
||||
{
|
||||
$file = $this->cacheDir . implode(DIRECTORY_SEPARATOR, $cKey);
|
||||
if (!file_exists($file))
|
||||
return false;
|
||||
|
||||
$content = file_get_contents($file);
|
||||
if (!$content)
|
||||
return false;
|
||||
|
||||
[$head, $postCache, $data] = explode("\n", $content, 3);
|
||||
if (substr_count($head, ' ') != 3)
|
||||
return false;
|
||||
|
||||
[$time, $lifetime, $rev, $method] = explode(' ', $head);
|
||||
|
||||
if (($time + $lifetime) < time() || $rev != AOWOW_REVISION)
|
||||
return false;
|
||||
|
||||
self::$cacheStats = [CACHE_MODE_FILECACHE, $time, $lifetime];
|
||||
$data = gzuncompress($data);
|
||||
}
|
||||
|
||||
if (!$data)
|
||||
return false;
|
||||
|
||||
if ($postCache)
|
||||
$this->onCacheLoaded = unserialize($postCache);
|
||||
|
||||
$fromCache = false;
|
||||
if ($method == self::STORE_METHOD_OBJECT)
|
||||
$fromCache = unserialize($data);
|
||||
else if ($method == self::STORE_METHOD_STRING)
|
||||
$fromCache = $data;
|
||||
|
||||
return $fromCache !== false;
|
||||
}
|
||||
|
||||
public function deleteCache(int $modeMask = 0x3) : void
|
||||
{
|
||||
$this->initCache();
|
||||
|
||||
// type+typeId+catg; 3+6+10
|
||||
$cKey = $this->formatCacheKey();
|
||||
$cKey[2] = substr($cKey[2], 0, 19);
|
||||
|
||||
if ($modeMask & CACHE_MODE_MEMCACHED)
|
||||
foreach ($this->memcached()?->getAllKeys() ?? [] as $k)
|
||||
if (strpos($k, $cKey[2]) === 0)
|
||||
$this->memcached()?->delete($k);
|
||||
|
||||
if ($modeMask & CACHE_MODE_FILECACHE)
|
||||
foreach (glob(implode(DIRECTORY_SEPARATOR, $cKey).'*') as $file)
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
private function memcached() : ?\Memcached
|
||||
{
|
||||
if (!$this->memcached && (Cfg::get('CACHE_MODE') & CACHE_MODE_MEMCACHED))
|
||||
{
|
||||
$this->memcached = new \Memcached();
|
||||
$this->memcached->addServer('localhost', 11211);
|
||||
}
|
||||
|
||||
return $this->memcached;
|
||||
}
|
||||
|
||||
private function initCache() : void
|
||||
{
|
||||
// php's missing trait property conflict resolution is going to be the end of me
|
||||
// also allow reevaluation even if inited. It may have changed in generate(), because of an error.
|
||||
if (isset($this->cacheType))
|
||||
$this->_cacheType = $this->cacheType;
|
||||
|
||||
if ($this->cacheInited)
|
||||
return;
|
||||
|
||||
// force refresh
|
||||
if (isset($_GET['refresh']) && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_DEV))
|
||||
{
|
||||
if ($_GET['refresh'] == 'filecache')
|
||||
$this->skipCache = CACHE_MODE_FILECACHE;
|
||||
else if ($_GET['refresh'] == 'memcached')
|
||||
$this->skipCache = CACHE_MODE_MEMCACHED;
|
||||
else if ($_GET['refresh'] == '')
|
||||
$this->skipCache = CACHE_MODE_FILECACHE | CACHE_MODE_MEMCACHED;
|
||||
}
|
||||
|
||||
$this->decay ??= Cfg::get('CACHE_DECAY');
|
||||
|
||||
$cacheDir = Cfg::get('CACHE_DIR');
|
||||
if ($cacheDir && Util::writeDir($cacheDir))
|
||||
$this->cacheDir = mb_substr($cacheDir, -1) != '/' ? $cacheDir.'/' : $cacheDir;
|
||||
|
||||
$this->cacheInited = true;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/466521
|
||||
private function formatCacheKey() : array
|
||||
{
|
||||
[$dbType, $dbTypeId, $category, $staffMask, $miscInfo] = $this->getCacheKeyComponents();
|
||||
|
||||
$fileKey = '';
|
||||
// DBType: 3
|
||||
$fileKey .= str_pad(dechex($dbType & 0xFFF), 3, 0, STR_PAD_LEFT);
|
||||
// DBTypeId: 6
|
||||
$fileKey .= str_pad(dechex($dbTypeId & 0xFFFFFF), 6, 0, STR_PAD_LEFT);
|
||||
// category: (2+4+4)
|
||||
$fileKey .= str_pad(dechex($category & 0xFFFFFFFFFF), 2+4+4, 0, STR_PAD_LEFT);
|
||||
// cacheType: 1
|
||||
$fileKey .= str_pad(dechex($this->_cacheType & 0xF), 1, 0, STR_PAD_LEFT);
|
||||
// localeId: 2,
|
||||
$fileKey .= str_pad(dechex(Lang::getLocale()->value & 0xFF), 2, 0, STR_PAD_LEFT);
|
||||
// staff mask: 4
|
||||
$fileKey .= str_pad(dechex($staffMask & 0xFFFFFFFF), 4, 0, STR_PAD_LEFT);
|
||||
// optioal: miscInfo
|
||||
if ($miscInfo)
|
||||
$fileKey .= '-'.$miscInfo;
|
||||
|
||||
// topDir, 2ndDir, file
|
||||
return array(
|
||||
str_pad(dechex($dbType & 0xFF), 2, 0, STR_PAD_LEFT),
|
||||
str_pad(dechex(($dbTypeId > 0 ? $dbTypeId : $category) & 0xFF), 2, 0, STR_PAD_LEFT),
|
||||
$fileKey
|
||||
);
|
||||
}
|
||||
|
||||
public function setOnCacheLoaded(callable $callback, mixed $params = null) : void
|
||||
{
|
||||
$this->onCacheLoaded = [$callback, $params];
|
||||
}
|
||||
|
||||
public function applyOnCacheLoaded(mixed &$data) : mixed
|
||||
{
|
||||
if (is_callable($this->onCacheLoaded[0]))
|
||||
return $this->onCacheLoaded[0]($data, $this->onCacheLoaded[1]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setCacheDecay(int $seconds) : void
|
||||
{
|
||||
if ($seconds < 0)
|
||||
return;
|
||||
|
||||
$this->decay = $seconds;
|
||||
}
|
||||
|
||||
abstract public function getCacheKeyComponents() : array;
|
||||
}
|
||||
|
||||
trait TrSearch
|
||||
{
|
||||
private int $maxResults = 500;
|
||||
private string $query = ''; // sanitized search string
|
||||
private int $searchMask = 0; // what to search for
|
||||
private Search $searchObj;
|
||||
|
||||
public function getCacheKeyComponents() : array
|
||||
{
|
||||
return array(
|
||||
-1, // DBType
|
||||
-1, // DBTypeId
|
||||
$this->searchMask, // category
|
||||
User::$groups, // staff mask
|
||||
md5($this->query) // misc (here search query)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Interface IProfilerList
|
||||
{
|
||||
public function getRegions() : void;
|
||||
}
|
||||
|
||||
trait TrProfiler
|
||||
{
|
||||
protected int $realmId = 0;
|
||||
protected string $battlegroup = ''; // not implemented, since no pserver supports it
|
||||
|
||||
public string $region = '';
|
||||
public string $realm = '';
|
||||
|
||||
private function getSubjectFromUrl(string $pageParam) : void
|
||||
{
|
||||
if (!$pageParam)
|
||||
return;
|
||||
|
||||
// cat[0] is always region
|
||||
// cat[1] is realm or bGroup (must be realm if cat[2] is set)
|
||||
// cat[2] is arena-team, guild or character
|
||||
$cat = explode('.', mb_strtolower($pageParam), 3);
|
||||
|
||||
$cat = array_map('urldecode', $cat);
|
||||
|
||||
if (array_search($cat[0], Util::$regions) === false)
|
||||
return;
|
||||
|
||||
$this->region = $cat[0];
|
||||
|
||||
// if ($cat[1] == Profiler::urlize(Cfg::get('BATTLEGROUP')))
|
||||
// $this->battlegroup = Cfg::get('BATTLEGROUP');
|
||||
if (isset($cat[1]))
|
||||
{
|
||||
foreach (Profiler::getRealms() as $rId => $r)
|
||||
{
|
||||
if (Profiler::urlize($r['name'], true) == $cat[1])
|
||||
{
|
||||
$this->realm = $r['name'];
|
||||
$this->realmId = $rId;
|
||||
if (isset($cat[2]) && mb_strlen($cat[2]) >= 2)
|
||||
$this->subjectName = mb_strtolower($cat[2]); // cannot reconstruct original name from urlized form; match against special name field
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function followBreadcrumbPath() : void
|
||||
{
|
||||
if ($this->region)
|
||||
{
|
||||
$this->breadcrumb[] = $this->region;
|
||||
|
||||
if ($this->realm)
|
||||
$this->breadcrumb[] = Profiler::urlize($this->realm, true);
|
||||
// else
|
||||
// $this->breadcrumb[] = Profiler::urlize(Cfg::get('BATTLEGROUP'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait TrProfilerDetail
|
||||
{
|
||||
use TrProfiler { TrProfiler::getSubjectFromUrl as _getSubjectFromUrl; }
|
||||
|
||||
protected string $subjectName = '';
|
||||
|
||||
public int $typeId = 0;
|
||||
public ?array $doResync = null;
|
||||
|
||||
private function getSubjectFromUrl(string $pageParam) : void
|
||||
{
|
||||
if (!$pageParam)
|
||||
return;
|
||||
|
||||
if (Util::checkNumeric($pageParam, NUM_CAST_INT))
|
||||
$this->typeId = $pageParam;
|
||||
else
|
||||
$this->_getSubjectFromUrl($pageParam);
|
||||
}
|
||||
|
||||
private function handleIncompleteData(int $type, int $guid) : void
|
||||
{
|
||||
// queue full fetch
|
||||
if ($newId = Profiler::scheduleResync($type, $this->realmId, $guid))
|
||||
{
|
||||
$this->template = 'text-page-generic';
|
||||
$this->doResync = [Type::getFileString($type), $newId];
|
||||
$this->inputbox = ['inputbox-status', ['head' => Lang::profiler('firstUseTitle', [Util::ucFirst($this->subjectName), $this->realm])]];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// todo: base info should have been created in __construct .. why are we here..?
|
||||
$this->forward('?'.Type::getFileString($type).'s='.$this->region.'.'.Profiler::urlize($this->realm, true).'&filter=na='.Util::ucFirst($this->subjectName).';ex=on');
|
||||
}
|
||||
}
|
||||
|
||||
trait TrProfilerList
|
||||
{
|
||||
use TrProfiler;
|
||||
|
||||
public array $regions = [];
|
||||
|
||||
public function getRegions() : void
|
||||
{
|
||||
$usedRegions = array_column(Profiler::getRealms(), 'region');
|
||||
foreach (Util::$regions as $idx => $id)
|
||||
if (in_array($id, $usedRegions))
|
||||
$this->regions[$id] = Lang::profiler('regions', $id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class BaseResponse
|
||||
{
|
||||
protected const PATTERN_TEXT_LINE = '/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Cn}]/ui';
|
||||
protected const PATTERN_TEXT_BLOB = '/[\x00-\x09\x0B-\x1F\p{Cf}\p{Co}\p{Cs}\p{Cn}]/ui';
|
||||
|
||||
protected static array $sql = []; // debug: sql stats container
|
||||
|
||||
protected array $expectedPOST = []; // fill with variables you that are going to be used; eg:
|
||||
protected array $expectedGET = []; // 'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList']
|
||||
protected array $expectedCOOKIE = [];
|
||||
|
||||
protected array $_post = []; // the filtered variable result
|
||||
protected array $_get = [];
|
||||
protected array $_cookie = [];
|
||||
|
||||
protected int $requiredUserGroup = U_GROUP_NONE; // by default accessible to everone
|
||||
protected bool $requiresLogin = false; // normal users and guests are both U_GROUP_NONE, soooo.....
|
||||
protected mixed $result = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->initRequestData();
|
||||
|
||||
if (!User::isInGroup($this->requiredUserGroup))
|
||||
$this->onUserGroupMismatch();
|
||||
|
||||
if ($this->requiresLogin && !User::isLoggedIn())
|
||||
$this->onUserGroupMismatch();
|
||||
}
|
||||
|
||||
public function process() : void
|
||||
{
|
||||
$fromCache = false;
|
||||
|
||||
if ($this instanceof ICache)
|
||||
$fromCache = $this->loadCache($this->result);
|
||||
|
||||
if (!$this->result)
|
||||
$this->generate();
|
||||
|
||||
$this->display();
|
||||
|
||||
if ($this instanceof ICache && !$fromCache)
|
||||
$this->saveCache($this->result);
|
||||
}
|
||||
|
||||
private function initRequestData() : void
|
||||
{
|
||||
// php bug? If INPUT_X is empty, filter_input_array returns null/fails
|
||||
// only really relevant for INPUT_POST
|
||||
// manuall set everything null in this case
|
||||
|
||||
if ($this->expectedPOST)
|
||||
{
|
||||
if ($_POST)
|
||||
$this->_post = filter_input_array(INPUT_POST, $this->expectedPOST);
|
||||
else
|
||||
$this->_post = array_fill_keys(array_keys($this->expectedPOST), null);
|
||||
}
|
||||
|
||||
if ($this->expectedGET)
|
||||
{
|
||||
if ($_GET)
|
||||
$this->_get = filter_input_array(INPUT_GET, $this->expectedGET);
|
||||
else
|
||||
$this->_get = array_fill_keys(array_keys($this->expectedGET), null);
|
||||
}
|
||||
|
||||
if ($this->expectedCOOKIE)
|
||||
{
|
||||
if ($_COOKIE)
|
||||
$this->_cookie = filter_input_array(INPUT_COOKIE, $this->expectedCOOKIE);
|
||||
else
|
||||
$this->_cookie = array_fill_keys(array_keys($this->expectedCOOKIE), null);
|
||||
}
|
||||
}
|
||||
|
||||
protected function forward(string $url = '') : never
|
||||
{
|
||||
$this->sendNoCacheHeader();
|
||||
header('Location: '.($url ?: '.'), true, 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
protected function forwardToSignIn(string $next = '') : never
|
||||
{
|
||||
$this->forward('?account=signin'.($next ? '&next='.$next : ''));
|
||||
}
|
||||
|
||||
protected function sumSQLStats() : void
|
||||
{
|
||||
Util::arraySumByKey(self::$sql, DB::Aowow()->getStatistics(), DB::World()->getStatistics());
|
||||
foreach (Profiler::getRealms() as $rId => $_)
|
||||
Util::arraySumByKey(self::$sql, DB::Characters($rId)->getStatistics());
|
||||
}
|
||||
|
||||
protected function sendNoCacheHeader()
|
||||
{
|
||||
header('Expires: Sat, 01 Jan 2000 01:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
}
|
||||
|
||||
|
||||
/****************************/
|
||||
/* required Parameter tests */
|
||||
/****************************/
|
||||
|
||||
protected function assertPOST(string ...$keys) : bool
|
||||
{
|
||||
foreach ($keys as $k) // not sent by browser || empty text field sent || validation failed
|
||||
if (!isset($this->_post[$k]) || $this->_post[$k] === null || $this->_post[$k] === '' || $this->_post[$k] === false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function assertGET(string ...$keys) : bool
|
||||
{
|
||||
foreach ($keys as $k)
|
||||
if (!isset($this->_get[$k]) || $this->_get[$k] === null || $this->_get[$k] === '' || $this->_get[$k] === false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function assertCOOKIE(string ...$keys) : bool
|
||||
{
|
||||
foreach ($keys as $k)
|
||||
if (!isset($this->_cookie[$k]) || $this->_cookie[$k] === null || $this->_cookie[$k] === '' || $this->_cookie[$k] === false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*******************************/
|
||||
/* Parameter validation checks */
|
||||
/*******************************/
|
||||
|
||||
protected static function checkRememberMe(string $val) : bool
|
||||
{
|
||||
return $val === 'yes';
|
||||
}
|
||||
|
||||
protected static function checkCheckbox(string $val) : bool
|
||||
{
|
||||
return $val === 'on';
|
||||
}
|
||||
|
||||
protected static function checkEmptySet(string $val) : bool
|
||||
{
|
||||
return $val === ''; // parameter is set and expected to be empty
|
||||
}
|
||||
|
||||
protected static function checkIdList(string $val) : array
|
||||
{
|
||||
if (preg_match('/^-?\d+(,-?\d+)*$/', $val))
|
||||
return array_map('intVal', explode(',', $val));
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected static function checkIntArray(string $val) : array
|
||||
{
|
||||
if (preg_match('/^-?\d+(:-?\d+)*$/', $val))
|
||||
return array_map('intVal', explode(':', $val));
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected static function checkIdListUnsigned(string $val) : array
|
||||
{
|
||||
if (preg_match('/^\d+(,\d+)*$/', $val))
|
||||
return array_map('intVal', explode(',', $val));
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected static function checkTextLine(string $val) : string
|
||||
{
|
||||
// trim non-printable chars
|
||||
return preg_replace(self::PATTERN_TEXT_LINE, '', trim(urldecode($val)));
|
||||
}
|
||||
|
||||
protected static function checkTextBlob(string $val) : string
|
||||
{
|
||||
// trim non-printable chars + excessive whitespaces (pattern includes \r)
|
||||
$str = preg_replace(self::PATTERN_TEXT_BLOB, '', trim($val));
|
||||
return preg_replace('/ +/', ' ', trim($str));
|
||||
}
|
||||
|
||||
|
||||
/********************/
|
||||
/* child implements */
|
||||
/********************/
|
||||
|
||||
// calc response
|
||||
abstract protected function generate() : void;
|
||||
|
||||
// send response
|
||||
abstract protected function display() : void;
|
||||
|
||||
// handling differs by medium
|
||||
abstract protected function onUserGroupMismatch() : never;
|
||||
}
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user