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 $file";
if($line) $log .= " on line $line ";
$file = $this->getLogRoot() . 'xuanxuan.' . date('Ymd') . '.log.php';
if(!is_file($file)) file_put_contents($file, "\n");
$fh = @fopen($file, 'a');
if($fh) fwrite($fh, $log) && fclose($fh);
}
}