AjaxHandler

* separated into appropriate subclasses
* unified sanitizing of $_GET and $_POST data using build in filter_input()
* index now always tries to resolve page calls with ajaxHandler first and as a page last

minor bug-fixes to bugs that wre not reported yet, because they didn't occur yet
(e.g.: nobody tried to compose a comment with >7500 characters yet)
This commit is contained in:
Sarjuuk
2015-11-08 18:53:44 +01:00
parent 141bc10901
commit 99fdad29dd
14 changed files with 1570 additions and 1297 deletions

View File

@@ -0,0 +1,323 @@
<?php
if (!defined('AOWOW_REVISION'))
die('invalid access');
class AjaxProfile extends AjaxHandler
{
protected $validParams = ['link', 'unlink', 'pin', 'unpin', 'public', 'private', 'avatar', 'resync', 'status', 'delete', 'purge', 'summary', 'load'];
protected $_get = array(
'id' => [FILTER_CALLBACK, ['options' => 'AjaxProfile::checkId']],
// 'items' => [FILTER_CALLBACK, ['options' => 'AjaxProfile::checkItems']],
'size' => [FILTER_SANITIZE_STRING, 0xC], // FILTER_FLAG_STRIP_LOW | *_HIGH
);
public function __construct(array $params)
{
parent::__construct($params);
if (!$this->params)
return;
switch ($this->params[0])
{
case 'link':
case 'unlink':
$this->handler = 'handleLink'; // always returns null
break;
case 'pin':
case 'unpin':
$this->handler = 'handlePin'; // always returns null
break;
case 'public':
case 'private':
$this->handler = 'handlePrivacy'; // always returns null
break;
case 'avatar':
$this->handler = 'handleAvatar'; // sets an image header
break; // so it has to die here or another header will be set
case 'resync':
case 'status':
$this->handler = 'handleResync';
break;
case 'save':
$this->handler = 'handleSave';
break;
case 'delete':
$this->handler = 'handleDelete';
break;
case 'purge':
$this->handler = 'handlePurge';
break;
case 'summary': // page is generated by jScript
die(); // just be empty
case 'load':
$this->handler = 'handleLoad';
break;
}
}
protected function handleLink($id, $mode) // links char with account
{
/* params
id: <prId1,prId2,..,prIdN>
user: <string> [optional]
return: null
*/
}
protected function handlePin($id, $mode) // (un)favorite
{
/* params
id: <prId1,prId2,..,prIdN>
user: <string> [optional]
return: null
*/
}
protected function handlePrivacy($id, $mode) // public visibility
{
/* params
id: <prId1,prId2,..,prIdN>
user: <string> [optional]
return: null
*/
}
protected function handleAvatar() // image
{
// something happened in the last years: those textures do not include tiny icons
$sizes = [/* 'tiny' => 15, */'small' => 18, 'medium' => 36, 'large' => 56];
$aPath = 'uploads/avatars/%d.jpg';
$s = $this->_get['size'] ?: 'medium';
if (!$this->_get['id'] || !preg_match('/^([0-9]+)\.(jpg|gif)$/', $this->_get['id'][0], $matches) || !in_array($s, array_keys($sizes)))
return;
$this->contentType = 'image/'.$matches[2];
$id = $matches[1];
$dest = imageCreateTruecolor($sizes[$s], $sizes[$s]);
if (file_exists(sprintf($aPath, $id)))
{
$offsetX = $offsetY = 0;
switch ($s)
{
case 'tiny':
$offsetX += $sizes['small'];
case 'small':
$offsetY += $sizes['medium'];
case 'medium':
$offsetX += $sizes['large'];
}
$src = imageCreateFromJpeg(printf($aPath, $id));
imagecopymerge($dest, $src, 0, 0, $offsetX, $offsetY, $sizes[$s], $sizes[$s], 100);
}
if ($matches[2] == 'gif')
imageGif($dest);
else
imageJpeg($dest);
return;
}
protected function handleResync() // resync init and status requests
{
/* params
id: <prId1,prId2,..,prIdN>
user: <string> [optional]
return
null [onOK]
int or str [onError]
*/
if ($this->params[0] == 'resync')
return '1';
else // $this->params[0] == 'status'
{
/*
not all fields are required, if zero they are omitted
statusCode:
0: end the request
1: waiting
2: working...
3: ready; click to view
4: error / retry
errorCode:
0: unk error
1: char does not exist
2: armory gone
[
processId,
[StatusCode, timeToRefresh, iCount, errorCode, iNResyncs],
[<anotherStatus>]...
]
*/
return '[0, [4, 10000, 1, 2]]';
}
}
protected function handleSave() // unKill a profile
{
/* params GET
id: <prId1,prId2,..,prIdN>
params POST
name, level, class, race, gender, nomodel, talenttree1, talenttree2, talenttree3, activespec, talentbuild1, glyphs1, talentbuild2, glyphs2, gearscore, icon, public [always]
description, source, copy, inv { inventory: array containing itemLinks } [optional]
}
return
int > 0 [profileId, if we came from an armoryProfile create a new one]
int < 0 [onError]
str [onError]
*/
return 'NYI';
}
protected function handleDelete() // kill a profile
{
/* params
id: <prId1,prId2,..,prIdN>
return
null
*/
return 'NYI';
}
protected function handlePurge() // removes certain saved information but not the entire character
{
/* params
id: <prId1,prId2,..,prIdN>
data: <mode> [string, tabName?]
return
null
*/
return 'NYI';
}
protected function handleLoad()
{
/* params
id: profileId
items: string [itemIds.join(':')]
unnamed: unixtime [only to force the browser to reload instead of cache]
return
lots...
*/
// titles, achievements, characterData, talents (, pets)
// and some onLoad-hook to .. load it registerProfile($data)
// everything else goes through data.php .. strangely enough
if (!$this->_get['id'])
return;
$char = new ProfileList(array(['id', $this->_get['id'][0]])); // or string or whatever
$buff = '';
if ($it = array_column($char->getField('inventory'), 0))
{
$itemz = new ItemList(array(['id', $it, CFG_SQL_LIMIT_NONE]));
$data = $itemz->getListviewData(ITEMINFO_JSON | ITEMINFO_SUBITEMS);
// get and apply inventory
foreach ($itemz->iterate() as $iId => $__)
$buff .= 'g_items.add('.$iId.', {name_'.User::$localeString.":'".Util::jsEscape($itemz->getField('name', true))."', quality:".$itemz->getField('quality').", icon:'".$itemz->getField('iconString')."', jsonequip:".Util::toJSON($data[$iId])."});\n";
$buff .= "\n";
}
if ($au = $char->getField('auras'))
{
$auraz = new SpellList(array(['id', $char->getField('auras')], CFG_SQL_LIMIT_NONE));
$dataz = $auraz->getListviewData();
$modz = $auraz->getProfilerMods();
// get and apply aura-mods
foreach ($dataz as $id => $data)
{
$mods = [];
if (!empty($modz[$id]))
{
foreach ($modz[$id] as $k => $v)
{
if (is_array($v))
$mods[] = $v;
else if ($str = @Util::$itemMods[$k])
$mods[$str] = $v;
}
}
$buff .= 'g_spells.add('.$id.", {id:".$id.", name:'".Util::jsEscape(mb_substr($data['name'], 1))."', icon:'".$data['icon']."', modifier:".Util::toJSON($mods)."});\n";
}
$buff .= "\n";
}
/* depending on progress-achievements
// required by progress in JScript move to handleLoad()?
Util::$pageTemplate->extendGlobalIds(TYPE_NPC, [29120, 31134, 29306, 29311, 23980, 27656, 26861, 26723, 28923, 15991]);
*/
// load available titles
Util::loadStaticFile('p-titles-'.$char->getField('gender'), $buff, true);
// load available achievements
if (!Util::loadStaticFile('p-achievements', $buff, true))
{
$buff .= "\n\ng_achievement_catorder = [];";
$buff .= "\n\ng_achievement_points = [0];";
}
// excludes; structure UNK type => [maskBit => [typeIds]] ?
/*
g_user.excludes = [type:[typeIds]]
g_user.includes = [type:[typeIds]]
g_user.excludegroups = groupMask // requires g_user.settings != null
maskBit are matched against fieldId from excludeGroups
id: 1, label: LANG.dialog_notavail
id: 2, label: LANG.dialog_tcg
id: 4, label: LANG.dialog_collector
id: 8, label: LANG.dialog_promo
id: 16, label: LANG.dialog_nonus
id: 96, label: LANG.dialog_faction
id: 896, label: LANG.dialog_profession
id: 1024, label: LANG.dialog_noexalted
*/
// $buff .= "\n\ng_excludes = {};";
// add profile to buffer
$buff .= "\n\n\$WowheadProfiler.registerProfile(".Util::toJSON($char->getEntry(2)).");"; // can't use JSON_NUMERIC_CHECK or the talent-string becomes a float
return $buff."\n";
}
protected function checkId($val)
{
// expecting id-list
if (preg_match('/\d+(,\d+)*/', $val))
return array_map('intVal', explode(', ', $val));
return null;
}
protected function checkItems($val)
{
// expecting item-list
if (preg_match('/\d+(:\d+)*/', $val))
return array_map('intVal', explode(': ', $val));
return null;
}
}
?>