xxb/framework/xuanxuan.class.php
2023-10-23 15:51:36 +08:00

567 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

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

<?php
include 'router.class.php';
class xuanxuan extends router
{
/**
* The input params.
*
* @var array
* @access public
*/
public $input = array();
/**
* The request params.
*
* @var array
* @access public
*/
public $params = array();
/**
* 构造方法, 设置路径,类,超级变量等。注意:
* 1.应该使用createApp()方法实例化router类
* 2.如果$appRoot为空框架会根据$appName计算应用路径。
*
* The construct function.
* Prepare all the paths, classes, super objects and so on.
* Notice:
* 1. You should use the createApp() method to get an instance of the router.
* 2. If the $appRoot is empty, the framework will compute the appRoot according the $appName
*
* @param string $appName the name of the app
* @param string $appRoot the root path of the app
* @access public
* @return void
*/
public function __construct($appName = 'sys', $appRoot = '')
{
parent::__construct($appName, $appRoot);
$this->setViewType();
$this->setXuanClientLang();
$this->setXuanDebug();
}
/**
* Set view type.
*
* @access public
* @return void
*/
public function setViewType()
{
$this->viewType = RUN_MODE == 'xuanxuan' ? 'json' : 'html';
}
/**
* 根据用户浏览器的语言设置和服务器配置,选择显示的语言。
* 优先级:$lang参数 > session > cookie > 浏览器 > 配置文件。
*
* Set the language.
* Using the order of method $lang param, session, cookie, browser and the default lang.
*
* @param string $lang zh-cn|zh-tw|zh-hk|en
* @access public
* @return void
*/
public function setXuanClientLang()
{
$row = $this->dbh->query('SELECT `value` FROM ' . TABLE_CONFIG . " WHERE `owner`='system' AND `module`='common' AND `section`='xuanxuan' AND `key`='backendLang'")->fetch();
$lang = empty($row) ? 'zh-cn' : $row->value;
parent::setClientLang($lang);
}
/**
* Set debug.
*
* @access public
* @return void
*/
public function setXuanDebug()
{
$row = $this->dbh->query('SELECT `value` FROM ' . TABLE_CONFIG . " WHERE `owner`='system' AND `module`='common' AND `section`='xuanxuan' AND `key`='debug'")->fetch();
$this->debug = empty($row) ? false : ($row->value == 1);
}
/**
* Set input params.
*
* @access public
* @return void
*/
public function setInput()
{
if(RUN_MODE == 'xuanxuan')
{
$input = empty($_POST) ? file_get_contents("php://input") : $_POST;
/* Decrypt only if AES is enabled. */
$enableAES = $this->dbh->query('SELECT `value` FROM ' . TABLE_CONFIG . " WHERE `owner`='system' AND `module`='common' AND `section`='xuanxuan' AND `key`='aes'")->fetch();
if(empty($enableAES) || $enableAES->value == 'on') $this->enableAES = true;
if(!empty($enableAES) && $enableAES->value == 'off') $this->enableAES = false;
if($this->enableAES) $this->initAES();
$input = $this->decrypt($input);
$this->input['rid'] = zget($input, 'rid' , '');
$this->input['userID'] = zget($input, 'userID' , '');
$this->input['client'] = zget($input, 'client' , '');
$this->input['module'] = zget($input, 'module' , 'im');
$this->input['method'] = zget($input, 'method' , '');
$this->input['lang'] = zget($input, 'lang' , 'zh-cn');
$this->input['params'] = zget($input, 'params' , array());
$this->input['version'] = zget($input, 'version', '');
$this->input['device'] = zget($input, 'device' , 'desktop');
}
else
{
$this->input['module'] = 'im';
$this->input['method'] = 'debug';
$this->input['params'] = array();
}
}
/**
* Init aes object.
*
* @access public
* @return void
*/
public function initAES()
{
$row = $this->dbh->query('SELECT `value` FROM ' . TABLE_CONFIG . " WHERE `owner`='system' AND `module`='common' AND `section`='xuanxuan' AND `key`='key'")->fetch();
$key = $row->value;
$iv = substr($key, 0, 16);
$this->aes = $this->loadClass('phpaes');
$this->aes->init($key, $iv);
if($this->debug)
{
$this->log("engine: " . $this->aes->getEngine());
}
}
/**
* Set params.
*
* @param array $params
* @access public
* @return void
*/
public function setParams($params = array())
{
$this->params = $params;
}
/**
* 解析本次请求的入口方法,根据请求的类型(PATH_INFO GET),调用相应的方法。
* The entrance of parseing request. According to the requestType, call related methods.
*
* @access public
* @return void
*/
public function parseRequest()
{
$this->setInput();
extract($this->input);
$module = strtolower($module);
$method = strtolower($method);
if(RUN_MODE == 'xuanxuan')
{
if(!isset($this->config->xuanxuan->enabledMethods[$module][$method]))
{
$data = new stdclass();
$data->module = 'im';
$data->method = 'error';
$data->data = 'Illegal Request.';
$this->output($this->encrypt($data));
return false;
}
/* Check for default parameters of the method if params is an object instead of an array. */
$filledWithDefaults = false;
if(is_object($params)) // cannot check ($params !== array_values($params)) for now because the decoder could not tell if data type name is needed and the name is always set.
{
$inputParams = (object)$params;
$defaultParams = $this->getDefaultParams($method, $module);
$filledParams = array();
foreach($defaultParams as $name => $defaultValue)
{
if(in_array($name, array('userID', 'version', 'device'))) continue;
$filledParams[] = isset($inputParams->$name) ? $inputParams->$name : $defaultValue;
}
$filledWithDefaults = true;
$params = $filledParams;
}
/* Unset servername param of handshake methods. */
$handshakeMethods = array('userlogin', 'sysgetserverinfo');
if(!$filledWithDefaults && $module == 'im' && in_array(strtolower($method), $handshakeMethods) && is_array($params)) unset($params[0]);
if(is_array($params))
{
$params[] = $userID;
$params[] = $version;
$params[] = $device;
}
$this->session->set('userID', $userID);
$this->session->set('clientIP', $client);
$this->session->set('clientLang', $lang);
}
elseif($module != 'im' or $method != 'debug')
{
$this->output('Access Denied');
return false;
}
$this->setModuleName($module);
$this->setMethodName($method);
$this->setParams($params);
$this->setControlFile();
return true;
}
/**
* 使用反射机制获取函数参数的默认值。
* Get the default settings of the method to be called using the reflecting.
*
* @param string $methodName
* @param string $moduleName
* @access public
* @return array
*/
public function getDefaultParams($methodName, $moduleName = 'im')
{
$control = $this->moduleRoot . $moduleName . DS . 'control.php';
$moduleExtPaths = $this->getModuleExtPath('', $moduleName, 'control');
if(!empty($moduleExtPaths))
{
$extActionFile = $moduleExtPaths['common'] . $methodName . '.php';
if(file_exists($extActionFile))
{
$control = $extActionFile;
}
elseif(isset($moduleExtPaths['xuan']))
{
$extActionFile = $moduleExtPaths['xuan'] . $methodName . '.php';
if(file_exists($extActionFile)) $control = $extActionFile;
}
}
helper::cd(dirname($control));
helper::import($control);
helper::cd();
$moduleName = class_exists("my$moduleName") ? "my$moduleName" : $moduleName;
$defaultParams = array();
$methodReflect = new reflectionMethod($moduleName, $methodName);
foreach($methodReflect->getParameters() as $param)
{
$name = $param->getName();
$default = '_NOT_SET';
if($param->isDefaultValueAvailable()) $default = $param->getDefaultValue();
$defaultParams[$name] = $default;
}
return $defaultParams;
}
/**
* 加载一个模块:
* 1. 引入控制器文件或扩展的方法文件;
* 2. 创建control对象
* 3. 解析url得到请求的参数
* 4. 使用call_user_function_array调用相应的方法。
*
* Load a module.
* 1. include the control file or the extension action file.
* 2. create the control object.
* 3. set the params passed in through url.
* 4. call the method by call_user_function_array
*
* @access public
* @return bool|object if the module object of die.
*/
public function loadModule()
{
$appName = $this->appName;
$moduleName = $this->moduleName;
$methodName = $this->methodName;
/*
* 引入该模块的control文件。
* Include the control file of the module.
**/
$file2Included = $this->setActionExtFile() ? $this->extActionFile : $this->controlFile;
chdir(dirname($file2Included));
helper::import($file2Included);
/*
* 设置control的类名。
* Set the class name of the control.
**/
$className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName;
if(!class_exists($className))
{
$this->triggerError("the control $className not found", __FILE__, __LINE__);
return false;
}
/*
* 创建control类的实例。
* Create a instance of the control.
**/
$module = new $className();
if(!method_exists($module, $methodName))
{
$this->triggerError("the module $moduleName has no $methodName method", __FILE__, __LINE__);
return false;
}
/* If the db server restarted, must reset dbh. */
$this->control = $module;
/* include default value for module*/
$defaultValueFiles = glob($this->getTmpRoot() . "defaultvalue/*.php");
if($defaultValueFiles) foreach($defaultValueFiles as $file) include $file;
/*
* 使用反射机制获取函数参数的默认值。
* Get the default settings of the method to be called using the reflecting.
*
* */
$defaultParams = array();
$methodReflect = new reflectionMethod($className, $methodName);
foreach($methodReflect->getParameters() as $param)
{
$name = $param->getName();
$default = '_NOT_SET';
if(isset($paramDefaultValue[$appName][$className][$methodName][$name]))
{
$default = $paramDefaultValue[$appName][$className][$methodName][$name];
}
elseif(isset($paramDefaultValue[$className][$methodName][$name]))
{
$default = $paramDefaultValue[$className][$methodName][$name];
}
elseif($param->isDefaultValueAvailable())
{
$default = $param->getDefaultValue();
}
$defaultParams[$name] = $default;
}
/* Merge params. */
$mergedParams = array();
if(isset($this->params))
{
$mergedParams = $this->mergeParams($defaultParams, (array)$this->params);
}
else
{
$this->triggerError("param error: {$this->request->raw}", __FILE__, __LINE__);
return false;
}
/* Call the method. */
$this->response = call_user_func_array(array($module, $methodName), $mergedParams);
return true;
}
/**
* 合并请求的参数和默认参数,这样就可以省略已经有默认值的参数了。
* Merge the params passed in and the default params. Thus the params which have default values needn't pass value, just like a function.
*
* @param array $defaultParams the default params defined by the method.
* @param array $passedParams the params passed in through url.
* @access public
* @return array the merged params.
*/
public function mergeParams($defaultParams, $passedParams)
{
/* Remove these two params. */
unset($passedParams['HTTP_X_REQUESTED_WITH']);
/* Check params from URL. */
foreach($passedParams as $param => $value)
{
if(preg_match('/[^a-zA-Z0-9_\.]/', $param))
{
$this->output('Bad Request!');
return array();
}
}
$passedParams = array_values($passedParams);
$i = 0;
foreach($defaultParams as $key => $defaultValue)
{
if(isset($passedParams[$i]))
{
$defaultParams[$key] = $passedParams[$i];
}
else
{
if($defaultValue === '_NOT_SET') $this->triggerError("The param '$key' should pass value. ", __FILE__, __LINE__, $exit = true);
}
$i++;
}
return $defaultParams;
}
/**
* Decrypt an input string.
*
* @param string $input
* @access public
* @return object
*/
public function decrypt($input = '')
{
if($this->enableAES) $input = $this->aes->decrypt($input);
if($this->debug) $this->log("decrypt: " . $input);
$input = json_decode($input);
if(!$input) $this->triggerError('Input data is not json.', __FILE__, __LINE__);
if($input && is_object($input)) return $input;
return $this->decodeInput($input);
}
/**
* Encrypt an output object.
*
* @param mixed $output array | object
* @access public
* @return string
*/
public function encrypt($output = null)
{
if(is_array($output) or is_object($output)) $output = helper::jsonEncode($output);
if($this->debug)
{
$this->log("encrypt: " . $output);
}
if($this->enableAES) $output = $this->aes->encrypt($output);
return helper::removeUTF8Bom($output);
}
/**
* Decode input params.
*
* @param array $input
* @access public
* @return array
*/
public function decodeInput($inputArray)
{
list($api, $input) = $inputArray;
$maps = zget($this->config->maps, $api, array());
/* Using requestPack as fallback maps */
if(empty($maps)) $maps = zget($this->config->maps, 'requestPack', array());
if($this->debug && empty($maps))
{
$this->log("warning: decode maps is empty for api \"$api\"");
}
$decodedParams = self::decodeArray($maps, $input);
if($this->debug)
{
$this->log("decode input($api): " . json_encode($decodedParams));
}
return $decodedParams;
}
/**
* Decode array to assorted array.
*
* @param array $maps
* @param array $data
* @static
* @access public
* @return array
*/
public static function decodeArray($maps, $data)
{
$params = array();
if(empty($maps)) return array();
/* $maps should be a type with dataType set. */
foreach($maps['dataType'] as $key => $prop)
{
/* Skip extra dataTypes that do not exist in the $data array. */
if(!isset($data[$key])) continue;
/* Basic type may contain options that allow using an index to represent a value defined in the scheme. */
if($prop['type'] == 'basic')
{
if(isset($prop['options']))
{
$params[$prop['name']] = zget($prop['options'], $data[$key]);
}
else
{
$params[$prop['name']] = zget($data, $key, '');
}
continue;
}
/* Object type may have a dataType of nested or mixed types. */
if($prop['type'] == 'object')
{
$params[$prop['name']] = self::decodeArray($prop, $data[$key]);
continue;
}
/* List type describes an array of its dataType instances. */
if($prop['type'] == 'list')
{
$params[$prop['name']] = self::decodeArray(array('dataType' => array($prop['dataType'])), $data[$key]);
}
}
return $params;
}
/**
* Output message with echo.
*
* @param string $message
* @access public
* @return void
*/
public function output($message)
{
echo $message;
}
/**
* Save a log.
*
* @param string $log
* @param string $file
* @param string $line
* @access public
* @return void
*/
public function log($message, $file = '', $line = '')
{
$log = "\n" . date('H:i:s') . " $message";
if($file) $log .= " in <strong>$file</strong>";
if($line) $log .= " on line <strong>$line</strong> ";
$file = $this->getLogRoot() . 'xuanxuan.' . date('Ymd') . '.log.php';
if(!is_file($file)) file_put_contents($file, "<?php\n die();\n?>\n");
$fh = @fopen($file, 'a');
if($fh) fwrite($fh, $log) && fclose($fh);
}
}