zentaopms/framework/base/router.class.php
2023-05-16 10:47:08 +08:00

3399 lines
108 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
/**
* 此文件包括ZenTaoPHP框架的三个类baseRouter, config, lang。
* The router, config and lang class file of ZenTaoPHP framework.
*
* @package framework
*
* The author disclaims copyright to this source code. In place of
* a legal notice, here is a blessing:
*
* May you do good and not evil.
* May you find forgiveness for yourself and forgive others.
* May you share freely, never taking more than you give.
*/
class baseRouter
{
/**
* ZenTaoPHP的基础目录一般是程序的根目录。
* The base path of the ZenTaoPHP framework.
*
* @var string
* @access public
*/
public $basePath;
/**
* 框架的根目录。
* The root directory of the framwork($this->basePath/framework)
*
* @var string
* @access public
*/
public $frameRoot;
/**
* 类库的根目录。{$this->basePath/lib}
* The root directory of the library($this->basePath/lib).
*
* @var string
* @access public
*/
public $coreLibRoot;
/**
* 应用名称
* The appName.
*
* @var string
* @access public
*/
public $appName = '';
/**
* 应用程序的根目录。
* The root directory of the app.
*
* @var string
* @access public
*/
public $appRoot;
/**
* 临时文件的根目录。
* The root directory of temp.
*
* @var string
* @access public
*/
public $tmpRoot;
/**
* 缓存的根目录。
* The root directory of cache.
*
* @var string
* @access public
*/
public $cacheRoot;
/**
* WWW目录。
* The root directory of www.
*
* @var string
* @access public
*/
public $wwwRoot;
/**
* 附件存放目录。
* The root directory of data.
*
* @var string
* @access public
*/
public $dataRoot;
/**
* 日志文件的根目录。
* The root directory of log.
*
* @var string
* @access public
*/
public $logRoot;
/**
* 配置文件的根目录。
* The root directory of config.
*
* @var string
* @access public
*/
public $configRoot;
/**
* 模块的根目录。
* The root directory of module.
*
* @var string
* @access public
*/
public $moduleRoot;
/**
* 主题的根目录。
* The root directory of theme.
*
* @var string
* @access public
*/
public $themeRoot;
/**
* 用户使用的语言。
* The lang of the client user.
*
* @var string
* @access public
*/
public $clientLang;
/**
* 请求的原始模块名。
* The requestd module name parsed from a URL.
*
* @var string
* @access public
*/
public $rawModule;
/**
* 请求的原始方法名。
* The requested method name parsed from a URL.
*
* @var string
* @access public
*/
public $rawMethod;
/**
* 当前页面所在的应用,用于左侧菜单栏判断。
* The current app code(url: '#app=?'), highlight left menu.
*
* @var string
* @access public
*/
public $tab;
/**
* 用户使用的主题。
* The theme of the client user.
*
* @var string
* @access public
*/
public $clientTheme;
/**
* 客户端设备类型。
* The device type of client.
*
* @var string
* @access public
*/
public $clientDevice;
/**
* 当前模块的control对象。
* The control object of current module.
*
* @var object
* @access public
*/
public $control;
/**
* 模块名。
* The module name
*
* @var string
* @access public
*/
public $moduleName;
/**
* 当前访问模块的control文件。
* The control file of the module current visiting.
*
* @var string
* @access public
*/
public $controlFile;
/**
* 当前访问的方法名。
* The name of the method current visiting.
*
* @var string
* @access public
*/
public $methodName;
/**
* 当前方法的扩展文件。
* The action extension file of current method.
*
* @var string
* @access public
*/
public $extActionFile;
/**
* 访问的URI。
* The URI.
*
* @var string
* @access public
*/
public $URI;
/**
* url地址传递的参数。
* The params passed in through url.
*
* @var array
* @access public
*/
public $params;
/**
* 视图类型。
* The view type.
*
* @var string
* @access public
*/
public $viewType;
/**
* 全局$config对象。
* The global $config object.
*
* @var object
* @access public
*/
public $config;
/**
* 全局$lang对象。
* The global $lang object.
*
* @var object
* @access public
*/
public $lang;
/**
* 全局$dbh对象数据库连接句柄。
* The global $dbh object, the database connection handler.
*
* @var object
* @access public
*/
public $dbh;
/**
* 从数据库的句柄。
* The slave database handler.
*
* @var object
* @access public
*/
public $slaveDBH;
/**
* $post对象用于访问$_POST变量。
* The $post object, used to access the $_POST var.
*
* @var object
* @access public
*/
public $post;
/**
* $get对象用于访问$_GET变量。
* The $get object, used to access the $_GET var.
*
* @var object
* @access public
*/
public $get;
/**
* $session对象用于访问$_SESSION变量。
* The $session object, used to access the $_SESSION var.
*
* @var object
* @access public
*/
public $session;
/**
* $server对象用于访问$_SERVER变量。
* The $server object, used to access the $_SERVER var.
*
* @var object
* @access public
*/
public $server;
/**
* $cookie对象用于访问$_COOKIE变量。
* The $cookie object, used to access the $_COOKIE var.
*
* @var object
* @access public
*/
public $cookie;
/**
* 原始SESSIONID
* SESSIONID
*
* @var int
* @access public
*/
public $sessionID;
/**
* 网站代号。
* The code of current site.
*
* @var string
* @access public
*/
public $siteCode;
/**
* 构造方法, 设置路径,类,超级变量等。注意:
* 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 = 'demo', $appRoot = '')
{
$this->setPathFix();
$this->setBasePath();
$this->setFrameRoot();
$this->setCoreLibRoot();
$this->setAppRoot($appName, $appRoot);
$this->setTmpRoot();
$this->setCacheRoot();
$this->setLogRoot();
$this->setConfigRoot();
$this->setModuleRoot();
$this->setWwwRoot();
$this->setThemeRoot();
$this->setDataRoot();
$this->loadMainConfig();
$this->loadClass('front', $static = true);
$this->loadClass('filter', $static = true);
$this->loadClass('dao', $static = true);
$this->loadClass('mobile', $static = true);
$this->setCookieSecure();
$this->setOpenApp();
$this->setSuperVars();
$this->setDebug();
$this->setErrorHandler();
$this->setTimezone();
$this->startSession();
if($this->config->framework->multiSite) $this->setSiteCode() && $this->loadExtraConfig();
if($this->config->framework->autoConnectDB) $this->connectDB();
if($this->config->framework->multiLanguage) $this->setClientLang();
$this->setEdition();
$this->setVision();
$needDetectDevice = zget($this->config->framework->detectDevice, $this->clientLang, false);
$this->clientDevice = $needDetectDevice ? $this->setClientDevice() : 'desktop';
if($this->config->framework->multiLanguage) $this->loadLang('common');
if($this->config->framework->multiTheme) $this->setClientTheme();
}
/**
* 创建一个应用。
* Create an application.
*
* @param string $appName 应用名称。 The name of the app.
* @param string $appRoot 应用根路径。The root path of the app.
* @param string $className 应用类名如果对router类做了扩展需要指定类名。When extends router class, you should pass in the child router class name.
* @static
* @access public
* @return static the app object
*/
public static function createApp($appName = 'demo', $appRoot = '', $className = '')
{
if(empty($className)) $className = __CLASS__;
return new $className($appName, $appRoot);
}
//-------------------- 路径相关方法(Path related methods)--------------------//
/**
* 设置应用名称。
* Set app name.
*
* @param string $appName
* @access public
* @return void
*/
public function setAppName($appName)
{
$this->appName = $appName;
}
/**
* 设置目录分隔符。
* Set the path directory separator.
*
* @access public
* @return void
*/
public function setPathFix()
{
define('DS', DIRECTORY_SEPARATOR);
}
/**
* 设置基础目录。
* Set the base path.
*
* @access public
* @return void
*/
public function setBasePath()
{
$this->basePath = realpath(dirname(dirname(dirname(__FILE__)))) . DS;
}
/**
* 设置框架根目录。
* Set the frame root.
*
* @access public
* @return void
*/
public function setFrameRoot()
{
$this->frameRoot = $this->basePath . 'framework' . DS;
}
/**
* 设置类库的根目录。
* Set the app lib root.
*
* @access public
* @return void
*/
public function setCoreLibRoot()
{
$this->coreLibRoot = $this->basePath . 'lib' . DS;
}
/**
* 设置应用的根目录。
* Set the app root.
*
* @param string $appName
* @param string $appRoot
* @access public
* @return void
*/
public function setAppRoot($appName = 'demo', $appRoot = '')
{
if(empty($appRoot)) $this->appRoot = $this->basePath . 'app' . DS . $appName . DS;
if(!empty($appRoot)) $this->appRoot = realpath($appRoot) . DS;
if(!is_dir($this->appRoot)) $this->triggerError("The app you call not found in {$this->appRoot}", __FILE__, __LINE__, $exit = true);
}
/**
* 设置临时文件的根目录。
* Set the tmp root.
*
* @access public
* @return void
*/
public function setTmpRoot()
{
$this->tmpRoot = $this->basePath . 'tmp' . DS;
}
/**
* 设置缓存的根目录。
* Set the cache root.
*
* @access public
* @return void
*/
public function setCacheRoot()
{
$this->cacheRoot = $this->tmpRoot . 'cache' . DS;
}
/**
* 设置log的根目录。
* Set the log root.
*
* @access public
* @return void
*/
public function setLogRoot()
{
$this->logRoot = $this->tmpRoot . 'log' . DS;
}
/**
* 设置config配置文件的根目录。
* Set the config root.
*
* @access public
* @return void
*/
public function setConfigRoot()
{
$this->configRoot = $this->basePath . 'config' . DS;
}
/**
* 设置模块的根目录。
* Set the module root.
*
* @access public
* @return void
*/
public function setModuleRoot()
{
$this->moduleRoot = $this->basePath . 'module' . DS;
}
/**
* 设置www的根目录。
* Set the www root.
*
* @access public
* @return void
*/
public function setWwwRoot()
{
$this->wwwRoot = rtrim(dirname($_SERVER['SCRIPT_FILENAME']), DS) . DS;
}
/**
* 设置主题根目录。
* Set the theme root.
*
* @access public
* @return void
*/
public function setThemeRoot()
{
$this->themeRoot = $this->wwwRoot . 'theme' . DS;
}
/**
* 设置data根目录。
* Set the data root.
*
* @access public
* @return void
*/
public function setDataRoot()
{
$this->dataRoot = $this->wwwRoot . 'data' . DS;
}
/**
* 设置超级变量。
* Set the super vars.
*
* @access public
* @return void
*/
public function setSuperVars()
{
if(isset($_SERVER['REQUEST_URI']))
{
$URI = $_SERVER['REQUEST_URI'];
if(strpos($URI, '?') !== false)
{
$parsedURL = parse_url($URI);
if(isset($parsedURL['query']))
{
parse_str($parsedURL['query'], $parsedQuery);
foreach($parsedQuery as $key => $value)
{
if(!isset($_GET[$key])) $_GET[$key] = $value;
}
}
}
}
$this->post = new super('post');
$this->get = new super('get');
$this->server = new super('server');
$this->cookie = new super('cookie');
$this->session = new super('session', $this->tab);
unset($_REQUEST);
/* Change for CSRF. */
if($this->config->framework->filterCSRF)
{
$httpType = (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on') ? 'https' : 'http';
if(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) and strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') $httpType = 'https';
if(isset($_SERVER['REQUEST_SCHEME']) and strtolower($_SERVER['REQUEST_SCHEME']) == 'https') $httpType = 'https';
$httpHost = zget($_SERVER, 'HTTP_HOST', '');
$apiMode = (defined('RUN_MODE') && RUN_MODE == 'api') || isset($_GET[$this->config->sessionVar]);
if(!$apiMode && (empty($httpHost) or strpos($this->server->http_referer, "$httpType://$httpHost") !== 0)) $_FILES = $_POST = array();
}
$_FILES = validater::filterFiles();
$_POST = validater::filterSuper($_POST);
$_GET = validater::filterSuper($_GET);
$_COOKIE = validater::filterSuper($_COOKIE);
/* Filter common get and cookie vars. */
if($this->config->framework->filterParam == 2)
{
global $filter;
foreach($filter->default->get as $key => $rules)
{
if(isset($_GET[$key]) and !validater::checkByRule($_GET[$key], $rules)) unset($_GET[$key]);
}
foreach($filter->default->cookie as $key => $rules)
{
if(isset($_COOKIE[$key]) and !validater::checkByRule($_COOKIE[$key], $rules)) unset($_COOKIE[$key]);
}
}
}
/**
* Set cookieSecure config.
*
* @access public
* @return void
*/
public function setCookieSecure()
{
$this->config->cookieSecure = false;
if($this->config->framework->setCookieSecure and isHttps()) $this->config->cookieSecure = true;
}
/**
* 设置Debug模式。
* set Debug.
*
* @access public
* @return void
*/
public function setDebug()
{
if(!empty($this->config->debug)) error_reporting(E_ALL & ~ E_STRICT);
}
/**
* 设置版本。
* Set edition.
*
* @access public
* @return void
*/
public function setEdition()
{
if(isset($this->config->edition)) return $this->config->edition;
$edition = substr($this->config->version, 0, 3);
if(in_array($edition, array('pro', 'biz', 'max'))) return $this->config->edition = $edition;
$this->config->edition = 'open';
}
/**
* 设置vision。
* set Debug.
*
* @access public
* @return void
*/
public function setVision()
{
$account = isset($_SESSION['user']) ? $_SESSION['user']->account : '';
if(empty($account) and isset($_POST['account'])) $account = $_POST['account'];
if(empty($account) and isset($_GET['account'])) $account = $_GET['account'];
$vision = '';
if($this->config->installed and validater::checkAccount($account))
{
$sql = new sql();
$account = $sql->quote($account);
$vision = $this->dbh->query("SELECT * FROM " . TABLE_CONFIG . " WHERE owner = $account AND `key` = 'vision' LIMIT 1")->fetch();
if($vision) $vision = $vision->value;
$user = $this->dbh->query("SELECT * FROM " . TABLE_USER . " WHERE account = $account AND deleted = '0' LIMIT 1")->fetch();
if(!empty($user->visions))
{
$userVisions = explode(',', $user->visions);
if(!in_array($vision, $userVisions)) $vision = '';
if(empty($vision)) list($vision) = $userVisions;
}
}
list($defaultVision) = explode(',', trim($this->config->visions, ','));
if($vision and strpos($this->config->visions, ",{$vision},") === false) $vision = $defaultVision;
$this->config->vision = $vision ? $vision : $defaultVision;
}
/**
* Get installed version.
*
* @access public
* @return string
*/
public function getInstalledVersion()
{
$version = $this->dbh->query("SELECT value FROM " . TABLE_CONFIG . " WHERE `owner` = 'system' AND `key` = 'version' AND `module` = 'common' AND `section` = 'global' LIMIT 1")->fetch();
$version = $version ? $version->value : '0.3.beta'; // No version, set as 0.3.beta.
if($version == '3.0.stable') $version = '3.0'; // convert 3.0.stable to 3.0.
return $version;
}
/**
* 设置错误处理句柄。
* Set the error handler.
*
* @access public
* @return void
*/
public function setErrorHandler()
{
set_error_handler(array($this, 'saveError'));
register_shutdown_function(array($this, 'shutdown'));
}
/**
* 获取应用名称
* Get app name
*
* @access public
* @return string
*/
public function getAppName()
{
return $this->appName;
}
/**
* 获取$basePath即基础路径。
* Get the $basePath var.
*
* @access public
* @return string
*/
public function getBasePath()
{
return $this->basePath;
}
/**
* 获取$frameRoot即框架根目录。
* Get the $frameRoot var.
*
* @access public
* @return string
*/
public function getFrameRoot()
{
return $this->frameRoot;
}
/**
* 获取$appRoot变量即应用的根目录。
* Get the $appRoot var.
*
* @access public
* @return string
*/
public function getAppRoot()
{
return $this->appRoot;
}
/**
* 获取$wwwRoot变量。
* Get the $wwwRoot var
*
* @access public
* @return string
*/
public function getWwwRoot()
{
return $this->wwwRoot;
}
/**
* 获取$coreLibRoot变量即应用类库的根目录。
* Get the $coreLibRoot var.
*
* @access public
* @return string
*/
public function getCoreLibRoot()
{
return $this->coreLibRoot;
}
/**
* 获取$tmpRoot变量即临时文件的根目录。
* Get the $tmpRoot var.
*
* @access public
* @return string
*/
public function getTmpRoot()
{
return $this->tmpRoot;
}
/**
* 获取$cacheRoot变量即缓存文件的根目录。
* Get the $cacheRoot var.
*
* @access public
* @return string
*/
public function getCacheRoot()
{
return $this->cacheRoot;
}
/**
* 获取$logRoot变量即日志文件的根目录。
* Get the $logRoot var.
*
* @access public
* @return string
*/
public function getLogRoot()
{
return $this->logRoot;
}
/**
* 获取$configRoot变量即配置文件的根目录。
* Get the $configRoot var.
*
* @access public
* @return string
*/
public function getConfigRoot()
{
return $this->configRoot;
}
/**
* 获取$moduleRoot变量即应用模块的根目录。
* Get the $moduleRoot var.
*
* @param string $appName
* @access public
* @return string
*/
public function getModuleRoot($appName = '')
{
if($appName == '') return $this->moduleRoot;
return dirname($this->moduleRoot) . DS . $appName . DS;
}
/**
* 获取扩展根目录。
* Get the root of extension.
*
* @access public
* @return string
*/
public function getExtensionRoot()
{
return $this->basePath . 'extension' . DS;
}
/**
* 获取$webRoot即应用的路径。
* Get the $webRoot var.
*
* @access public
* @return string
*/
public function getWebRoot()
{
return $this->config->webRoot;
}
/**
* 获取$themeRoot变量即主题的根目录。
* Get the $themeRoot var.
*
* @access public
* @return string
*/
public function getThemeRoot()
{
return $this->themeRoot;
}
/**
* 获取$dataRoot目录
* Get the $dataRoot var
*
* @access public
* @return string
*/
public function getDataRoot()
{
return $this->dataRoot;
}
//------ 客户端环境有关的函数(Client environment related functions) ------//
/**
* 根据配置设置当前时区。
* Set the time zone according to the config.
*
* @access public
* @return void
*/
public function setTimezone()
{
if(isset($this->config->timezone)) date_default_timezone_set($this->config->timezone);
}
/**
* 开启 session
* Start the session.
*
* @access public
* @return void
*/
public function startSession()
{
if(defined('SESSION_STARTED')) return;
if(ini_get('session.save_handler') == 'files' and isset($_GET['tid']))
{
$savePath = ini_get('session.save_path');
$writable = is_writable($savePath);
if(!$writable)
{
$savePath = $this->getTmpRoot() . 'session';
if(!is_dir($savePath)) mkdir($savePath, 0777, true);
$writable = is_writable($savePath);
if($writable) session_save_path($this->getTmpRoot() . 'session');
}
if($writable)
{
$ztSessionHandler = new ztSessionHandler($_GET['tid']);
session_set_save_handler(
array($ztSessionHandler, "open"),
array($ztSessionHandler, "close"),
array($ztSessionHandler, "read"),
array($ztSessionHandler, "write"),
array($ztSessionHandler, "destroy"),
array($ztSessionHandler, "gc")
);
}
}
$sessionName = $this->config->sessionVar;
session_name($sessionName);
session_set_cookie_params(0, $this->config->webRoot, '', $this->config->cookieSecure, true);
if($this->config->customSession) session_save_path($this->getTmpRoot() . 'session');
if(!session_id()) session_start();
$this->sessionID = isset($ztSessionHandler) ? $ztSessionHandler->getSessionID() : session_id();
if(isset($_GET[$this->config->sessionVar]))
{
helper::restartSession($_GET[$this->config->sessionVar]);
}
else if(isset($_SERVER['HTTP_TOKEN'])) // If request header has token, use it as session for authentication.
{
helper::restartSession($_SERVER['HTTP_TOKEN']);
$this->sessionID = isset($ztSessionHandler) ? $ztSessionHandler->getSessionID() : session_id();
}
define('SESSION_STARTED', true);
}
/**
* 从cookie中获取当前的group, 即URL锚链接'#tab=?'。
* Get current group from cookie, original source is url '#tab=?'.
*
* @access public
* @return void
*/
public function setOpenApp()
{
$module = $this->rawModule;
$this->tab = 'my';
if(isset($this->lang->navGroup)) $this->tab = zget($this->lang->navGroup, $module, 'my');
if(isset($_COOKIE['tab']) and $_COOKIE['tab'] and preg_match('/^\w+$/', $_COOKIE['tab'])) $this->tab = $_COOKIE['tab'];
}
/**
* 根据用户浏览器的语言设置和服务器配置,选择显示的语言。
* 优先级:$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 setClientLang($lang = '')
{
if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) $this->clientLang = $this->parseHttpAcceptLang();
if(isset($_COOKIE['lang'])) $this->clientLang = $_COOKIE['lang'];
if(isset($_SESSION['lang'])) $this->clientLang = $_SESSION['lang'];
if(!empty($lang)) $this->clientLang = $lang;
if(!empty($this->clientLang))
{
$this->clientLang = strtolower($this->clientLang);
if(!isset($this->config->langs[$this->clientLang])) $this->clientLang = $this->config->default->lang;
}
else
{
$this->clientLang = $this->config->default->lang;
}
setcookie('lang', $this->clientLang, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
if(!isset($_COOKIE['lang'])) $_COOKIE['lang'] = $this->clientLang;
return true;
}
/**
* 从HTTP_ACCEPT_LANGUAGE中剔除去支持的语言。
* Parse the lang str from HTTP_ACCEPT_LANGUAGE header.
*
* @access public
* @return string
*/
public function parseHttpAcceptLang()
{
if(empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) return '';
$raw = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
$pos = strpos($raw, ',');
$lang = $pos === false ? $raw : substr($raw, 0, $pos);
/* Fix clientLang for ie >= 10. https://www.drupal.org/node/365615. */
if(stripos($lang, 'hans')) $lang = 'zh-cn';
if(stripos($lang, 'hant')) $lang = 'zh-tw';
return $lang;
}
/**
* 设置客户端使用的主题,判断逻辑与客户端的语言相同。
* 主题的css和图片文件应该存放在www/theme/$themeName路径。
*
* Set the theme the client user using. The logic is same as the clientLang.
* The css and images files of an theme should saved at www/theme/$themeName
*
* @param string $theme
* @access public
* @return void
*/
public function setClientTheme($theme = '')
{
if(isset($this->config->client->theme)) $this->clientTheme = $this->config->client->theme;
if(isset($_COOKIE['theme'])) $this->clientTheme = $_COOKIE['theme'];
if(!empty($theme)) $this->clientTheme = $theme;
if(!empty($this->clientTheme))
{
$this->clientTheme = strtolower($this->clientTheme);
if(!isset($this->lang->themes[$this->clientTheme])) $this->clientTheme = $this->config->default->theme;
}
else
{
$this->clientTheme = $this->config->default->theme;
}
setcookie('theme', $this->clientTheme, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
if(!isset($_COOKIE['theme'])) $_COOKIE['theme'] = $this->clientTheme;
return true;
}
/**
* 设置客户端的设备类型。
* Set client device.
*
* @access public
* @return void
*/
public function setClientDevice()
{
$this->clientDevice = 'desktop';
if($this->cookie->device == 'mobile') $this->clientDevice = 'mobile';
if($this->cookie->device == 'desktop') $this->clientDevice = 'desktop';
if(empty($this->cookie->device) || strpos('mobile,desktop', $this->cookie->device) === false)
{
$mobile = new mobile();
$this->clientDevice = ($mobile->isMobile() and !$mobile->isTablet()) ? 'mobile' : 'desktop';
}
setcookie('device', $this->clientDevice, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, true);
if(!isset($_COOKIE['device'])) $_COOKIE['device'] = $this->clientDevice;
return $this->clientDevice;
}
/**
* 设置站点代号,可以针对不同的站点来加载不同的扩展。
* Set the code of current site, thus can load different extension of different site.
*
* @access public
* @return void
*/
public function setSiteCode()
{
return $this->siteCode = helper::parseSiteCode($this->server->http_host);
}
/**
* 获取$clientLang变量即客户端的语言。
* Get the $clientLang var.
*
* @access public
* @return string
*/
public function getClientLang()
{
return $this->clientLang;
}
/**
* 获取$clientTheme变量。
* Get the $clientTheme var.
*
* @access public
* @return string
*/
public function getClientTheme()
{
return $this->config->webRoot . 'theme/' . $this->clientTheme . '/';
}
/**
* 获得客户端的终端设备。
* Get the client device.
*
* @access public
* @return void
*/
public function getClientDevice()
{
return $this->clientDevice;
}
//-------------------- 请求相关的方法(Request related methods) --------------------//
/**
* 解析本次请求的入口方法,根据请求的类型(PATH_INFO GET),调用相应的方法。
* The entrance of parsing request. According to the requestType, call related methods.
*
* @access public
* @return void
*/
public function parseRequest()
{
if($this->config->requestType == 'PATH_INFO' or $this->config->requestType == 'PATH_INFO2')
{
$this->parsePathInfo();
$this->setRouteByPathInfo();
}
elseif($this->config->requestType == 'GET')
{
$this->parseGET();
$this->setRouteByGET();
}
else
{
$this->triggerError("The request type {$this->config->requestType} not supported", __FILE__, __LINE__, $exit = true);
}
}
/**
* PATH_INFO方式解析获取$URI和$viewType。
* Parse PATH_INFO, get the $URI and $viewType.
*
* @access public
* @return void
*/
public function parsePathInfo()
{
$pathInfo = $this->getPathInfo();
if(!empty($pathInfo))
{
$dotPos = strrpos($pathInfo, '.');
if($dotPos)
{
$this->URI = substr($pathInfo, 0, $dotPos);
$this->viewType = substr($pathInfo, $dotPos + 1);
if(strpos($this->config->views, ',' . $this->viewType . ',') === false)
{
$this->viewType = $this->config->default->view;
}
}
else
{
$this->URI = $pathInfo;
$this->viewType = $this->config->default->view;
}
}
else
{
$this->viewType = $this->config->default->view;
}
}
/**
* 从$_SERVER或者$_ENV全局变量根据pathinfo变量名获取$PATH_INFO值。
* PATH_INFO的变量名几乎都是'PATH_INFO'但也有可能是ORIG_PATH_INFO。
*
* Get $PATH_INFO from $_SERVER or $_ENV by the pathinfo var name.
* Mostly, the var name of PATH_INFO is PATH_INFO, but may be ORIG_PATH_INFO.
*
* @access public
* @return string the PATH_INFO
*/
public function getPathInfo()
{
if(isset($_SERVER['PATH_INFO']))
{
$value = $_SERVER['PATH_INFO'];
}
elseif(isset($_SERVER['ORIG_PATH_INFO']))
{
$value = $_SERVER['ORIG_PATH_INFO'];
}
elseif(isset($this->URI))
{
$value = $this->URI;
$subpath = str_replace($_SERVER['DOCUMENT_ROOT'], '', dirname($_SERVER['SCRIPT_FILENAME']));
if($subpath != '/') $subpath = '/' . $subpath;
if($subpath != '' and $subpath != '/' and strpos($value, $subpath) === 0) $value = substr($value, strlen($subpath));
}
else
{
$value = @getenv('PATH_INFO');
if(empty($value)) $value = @getenv('ORIG_PATH_INFO');
}
if(strpos($value, $_SERVER['SCRIPT_NAME']) !== false) $value = str_replace($_SERVER['SCRIPT_NAME'], '', $value);
if(strpos($value, '?') === false) return trim($value, '/');
$value = parse_url($value);
$pathInfo = trim(zget($value, 'path', ''), '/');
if(trim($pathInfo, '/') == trim($this->config->webRoot, '/')) $pathInfo = '';
return $pathInfo;
}
/**
* GET请求方式解析获取$URI和$viewType。
* Parse GET, get $URI and $viewType.
*
* @access public
* @return void
*/
public function parseGET()
{
if(isset($_GET[$this->config->viewVar]))
{
$this->viewType = $_GET[$this->config->viewVar];
if(strpos($this->config->views, ',' . $this->viewType . ',') === false) $this->viewType = $this->config->default->view;
}
else
{
$this->viewType = $this->config->default->view;
}
$this->URI = $_SERVER['REQUEST_URI'];
}
/**
* 获取$URL。
* Get the $URL.
*
* @param bool $full true, the URI contains the webRoot, else only hte URI.
* @access public
* @return string
*/
public function getURI($full = false)
{
if($full and $this->config->requestType == 'PATH_INFO')
{
if($this->URI) return $this->config->webRoot . $this->URI . '.' . $this->viewType;
return $this->config->webRoot;
}
return $this->URI;
}
/**
* 获取$viewType变量。
* Get the $viewType var.
*
* @access public
* @return string
*/
public function getViewType()
{
return $this->viewType;
}
//-------------------- 模块及扩展设置(Module and extension) --------------------//
/**
* 加载common模块。
*
* common模块比较特别它会执行几乎每次请求都需要执行的操作例如
* 打开session检查权限等等。
* 加载完$lang, $config, $dbh后需要在入口文件(www/index.php)中手动调用该方法。
*
* Load the common module
*
* The common module is a special module, which can be used to do some common things. For example:
* start session, check privilege and so on.
* This method should called manually in the router file(www/index.php) after the $lang, $config, $dbh loaded.
*
* @access public
* @return object|bool the common model object or false if not exits.
*/
public function loadCommon()
{
$this->setModuleName('common');
$commonModelFile = $this->setModelFile('common');
if(!file_exists($commonModelFile)) return false;
helper::import($commonModelFile);
if($this->config->framework->extensionLevel == 0 and class_exists('commonModel')) return new commonModel();
if($this->config->framework->extensionLevel > 0 and class_exists('extCommonModel')) return new extCommonModel();
if(class_exists('commonModel')) return new commonModel();
return false;
}
/**
* 设置要被调用的模块名。
* Set the name of the module to be called.
*
* @param string $moduleName the module name
* @access public
* @return void
*/
public function setModuleName($moduleName = '')
{
if($this->checkModuleName($moduleName)) $this->moduleName = strtolower($moduleName);
}
/**
* 设置要被调用的控制器文件。
* Set the control file of the module to be called.
*
* @param bool $exitIfNone 没有找到该控制器文件的情况如果该参数为true则终止程序如果为false则打印错误日志
* The control file was not found: if the parameter is true, the program is terminated;
* if false, the error log is printed.
* @access public
* @return bool
*/
public function setControlFile($exitIfNone = true)
{
$this->controlFile = $this->getModulePath() . 'control.php';
if(file_exists($this->controlFile)) return true;
$this->triggerError("the control file $this->controlFile not found.", __FILE__, __LINE__, $exitIfNone);
}
/**
* 设置要被调用的方法名。
* Set the name of the method calling.
*
* @param string $methodName
* @access public
* @return void
*/
public function setMethodName($methodName = '')
{
if($this->checkMethodName($methodName)) $this->methodName = strtolower($methodName);
}
/**
* 设置要被调用方法的参数。
* Set the params of method calling.
*
* @access public
* @return void
*/
public function setParams()
{
try
{
$appName = $this->appName;
$moduleName = $this->moduleName;
$methodName = $this->methodName;
/*
* 引入该模块的control文件。
* Include the control file of the module.
**/
$isExt = $this->setActionExtFile();
if($isExt)
{
$controlFile = $this->controlFile;
spl_autoload_register(function($class) use ($moduleName, $controlFile)
{
if($class == $moduleName) include $controlFile;
});
}
$file2Included = $isExt ? $this->extActionFile : $this->controlFile;
chdir(dirname($file2Included));
helper::import($file2Included);
/* Check file is encode by ioncube. */
$isEncrypted = false;
if(strpos($file2Included, 'extension' . DS . $this->config->edition . DS) !== false)
{
$fp = fopen($file2Included, 'r');
$line1 = fgets($fp);
$line2 = fgets($fp);
fclose($fp);
if(strpos($line1, '<?php //') === 0 and strpos($line2, "if(!extension_loaded('ionCube Loader'))") === 0) $isEncrypted = true;
}
/*
* 设置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__, $exit = true);
/*
* 创建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__, $exit = true);
$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(!$isEncrypted and $param->isDefaultValueAvailable())
{
$default = $param->getDefaultValue();
}
$defaultParams[$name] = $default;
}
/**
* 根据PATH_INFO或者GET方式设置请求的参数。
* Set params according PATH_INFO or GET.
*/
if($this->config->requestType != 'GET')
{
$this->setParamsByPathInfo($defaultParams);
}
else
{
$this->setParamsByGET($defaultParams);
}
if ('cli' === PHP_SAPI)
{
if ($this->params)
{
$this->params = array_merge($defaultParams, $this->params);
}
else
{
$this->params = $defaultParams;
}
}
else
{
if($this->config->framework->filterParam == 2)
{
$_GET = validater::filterParam($_GET, 'get');
$_COOKIE = validater::filterParam($_COOKIE, 'cookie');
}
}
return true;
}
catch(EndResponseException $endResponseException)
{
echo $endResponseException->getContent();
return false;
}
}
/**
* 获取一个模块的路径。
* Get the path of one module.
*
* @param string $appName the app name
* @param string $moduleName the module name
* @access public
* @return string the module path
*/
public function getModulePath($appName = '', $moduleName = '')
{
if($moduleName == '') $moduleName = $this->moduleName;
$moduleName = strtolower($moduleName);
if($this->checkModuleName($moduleName))
{
$modulePath = $this->getExtensionRoot() . 'saas' . DS . $moduleName . DS;
if(is_dir($modulePath) and (file_exists($modulePath . 'control.php') or file_exists($modulePath . 'model.php'))) return $modulePath;
/* 1. 最后尝试在定制开发中寻找。 Finally, try to find the module in the custom dir. */
$modulePath = $this->getExtensionRoot() . 'custom' . DS . $moduleName . DS;
if(is_dir($modulePath) and (file_exists($modulePath . 'control.php') or file_exists($modulePath . 'model.php'))) return $modulePath;
/* 2. 如果设置过vision尝试在vision中查找。 If vision is set, try to find the module in the vision. */
if($this->config->vision != 'rnd')
{
$modulePath = $this->getExtensionRoot() . $this->config->vision . DS . $moduleName . DS;
if(is_dir($modulePath) and (file_exists($modulePath . 'control.php') or file_exists($modulePath . 'model.php'))) return $modulePath;
}
/* 3. 尝试查找商业版本是否有此模块。 Try to find the module in other editon. */
if($this->config->edition != 'open')
{
$modulePath = $this->getExtensionRoot() . $this->config->edition . DS . $moduleName . DS;
if(is_dir($modulePath) and (file_exists($modulePath . 'control.php') or file_exists($modulePath . 'model.php'))) return $modulePath;
}
/* 4. 尝试查找喧喧是否有此模块。 Try to find the module in xuan. */
$modulePath = $this->getExtensionRoot() . 'xuan' . DS . $moduleName . DS;
if(is_dir($modulePath) and (file_exists($modulePath . 'control.php') or file_exists($modulePath . 'model.php'))) return $modulePath;
/* 5. 如果通用版本里有此模块,优先使用。 If module is in the open edition, use it. */
return $this->getModuleRoot($appName) . $moduleName . DS;
}
}
/**
* 获取一个模块的扩展路径。 Get extension path of one module.
*
* If the extensionLevel == 0, return empty array.
* If the extensionLevel == 1, return the common extension directory.
* If the extensionLevel == 2, return the common and site extension directories.
*
* @param string $appName the app name
* @param string $moduleName the module name
* @param string $ext the extension type, can be control|model|view|lang|config|zen|tao
* @access public
* @return string the extension path.
*/
public function getModuleExtPath($appName, $moduleName, $ext)
{
$saasExtPath = $this->getExtensionRoot() . 'saas' . DS . $moduleName . DS . 'ext' . DS . $ext . DS;
/* 检查失败或者extensionLevel为0直接返回空。If check failed or extensionLevel == 0, return empty array. */
if(!$this->checkModuleName($moduleName) or $this->config->framework->extensionLevel == 0) return array('saas' => $saasExtPath);
$paths = array();
/* When extensionLevel == 1. */
$paths['common'] = $this->config->edition != 'open' ? $this->getExtensionRoot() . $this->config->edition . DS . $moduleName . DS . 'ext' . DS . $ext . DS : '';
$paths['xuan'] = $this->getExtensionRoot() . 'xuan' . DS . $moduleName . DS . 'ext' . DS . $ext . DS;
$paths['vision'] = $this->config->vision == 'rnd' ? '' : $this->basePath . 'extension' . DS . $this->config->vision . DS . $moduleName . DS . 'ext' . DS . $ext . DS;
$paths['custom'] = $this->getExtensionRoot() . 'custom' . DS . $moduleName . DS . 'ext' . DS . $ext . DS;
if($this->config->framework->extensionLevel == 1)
{
$paths['saas'] = $saasExtPath;
return $paths;
}
/* When extensionLevel == 2. */
$paths['site'] = empty($this->siteCode) ? '' : $this->getExtensionRoot() . $this->config->edition . DS . $moduleName . DS . 'ext' . DS . '_' . $this->siteCode . DS . $ext . DS;
$paths['saas'] = $saasExtPath;
return $paths;
}
/**
* 检查模块中某一个变量必须为英文字母和数字组合。Check module a variable must be ascii.
*
* @param string $var
* @param bool $exit
* @access public
* @return bool
*/
public function checkModuleName($var, $exit = true)
{
global $filter;
static $checkedModule = array();
if(!isset($checkedModule[$var]))
{
$rule = $filter->default->moduleName;
$result = validater::checkByRule($var, $rule);
$checkedModule[$var] = $result;
}
if($checkedModule[$var]) return true;
if(!$exit) return false;
$this->triggerError("'$var' illegal. ", __FILE__, __LINE__, $exit = true);
}
/**
* 检查方法中某一个变量必须为英文字母和数字组合。Check method a variable must be ascii.
*
* @param string $var
* @param bool $exit
* @access public
* @return bool
*/
public function checkMethodName($var, $exit = true)
{
global $filter;
$rule = $filter->default->methodName;
if($this->config->framework->filterParam == 2 and isset($filter->{$this->moduleName}->methodName)) $rule = $filter->{$this->moduleName}->methodName;
if(validater::checkByRule($var, $rule)) return true;
if(!$exit) return false;
$this->triggerError("'$var' illegal. ", __FILE__, __LINE__, $exit = true);
}
/**
* 设置Action的扩展文件。 Set the action extension file.
*
* @access public
* @return bool
*/
public function setActionExtFile()
{
$moduleExtPaths = $this->getModuleExtPath('', $this->moduleName, 'control');
/* 如果扩展目录为空不包含任何扩展文件。If there's no ext paths return false.*/
if(empty($moduleExtPaths)) return false;
if(!empty($moduleExtPaths['saas']))
{
$this->extActionFile = $moduleExtPaths['saas'] . $this->methodName . '.php';
if(file_exists($this->extActionFile)) return true;
}
/* 1. 如果extensionLevel == 2且扩展文件存在返回该站点扩展文件。 If extensionLevel == 2 and site extensionFile exists, return it. */
if($this->config->framework->extensionLevel == 2 and !empty($moduleExtPaths['site']))
{
$this->extActionFile = $moduleExtPaths['site'] . $this->methodName . '.php';
if(file_exists($this->extActionFile)) return true;
}
/* 2. 尝试在定制开发目录寻找扩展文件。Then try to find the custom extension file. */
$this->extActionFile = $moduleExtPaths['custom'] . $this->methodName . '.php';
if(file_exists($this->extActionFile)) return true;;
/* 3. 如果设置过vision尝试在vision中查找扩展文件。If vision is set, try to find the vision extension file. */
if($moduleExtPaths['vision']) $this->extActionFile = $moduleExtPaths['vision'] . $this->methodName . '.php';
if(file_exists($this->extActionFile)) return true;;
/* 4. 在喧喧目录中查找扩展文件。Then try to find the xuan extension file. */
if($moduleExtPaths['xuan']) $this->extActionFile = $moduleExtPaths['xuan'] . $this->methodName . '.php';
if(file_exists($this->extActionFile)) return true;;
/* 5. 最后尝试寻找公共扩展文件。Finally, try to find the common extension file. */
$this->extActionFile = $moduleExtPaths['common'] . $this->methodName . '.php';
if(empty($moduleExtPaths['common'])) return false;
return file_exists($this->extActionFile);
}
/**
* Check for API extend.
*
* @access public
* @return bool
*/
public function checkAPIFile()
{
$moduleExtPaths = $this->getModuleExtPath('', $this->moduleName, 'control');
/* 如果扩展目录为空不包含任何扩展文件。If there's no ext paths return false.*/
if(empty($moduleExtPaths)) return false;
/* 如果extensionLevel == 2且扩展文件存在返回该站点扩展文件。If extensionLevel == 2 and site extensionFile exists, return it. */
if($this->config->framework->extensionLevel == 2 and !empty($moduleExtPaths['site']))
{
$locateFile = $moduleExtPaths['site'] . $this->methodName . '.302';
if(file_exists($locateFile)) $this->sendAPI($locateFile);
$requestFile = $moduleExtPaths['site'] . $this->methodName . '.api';
if(file_exists($requestFile)) $this->sendAPI($requestFile);
}
/* 然后再尝试寻找公共扩展文件。Then try to find the common extension file. */
$locateFile = $moduleExtPaths['common'] . $this->methodName . '.302';
if(file_exists($locateFile)) $this->sendAPI($locateFile);
$requestFile = $moduleExtPaths['common'] . $this->methodName . '.api';
if(file_exists($requestFile)) $this->sendAPI($requestFile);
return false;
}
/**
* 设置一个模块的model文件如果存在model扩展一起合并。
* Set the model file of one module. If there's an extension file, merge it with the main model file.
*
* @param string $moduleName 模块名如果为空使用当前模块。The module name, if empty, use current module's name.
* @param string $appName 应用名如果为空使用当前应用。The app name, if empty, use current app's name.
* @access public
* @return string the model file
*/
public function setModelFile($moduleName, $appName = '')
{
return $this->setTargetFile($moduleName, $appName);
}
/**
* 设置一个模块的target文件如果存在target扩展一起合并。
* Set the target file of one module. If there's an extension file, merge it with the main target file.
*
* @param string $moduleName 模块名如果为空使用当前模块。The module name, if empty, use current module's name.
* @param string $appName 应用名如果为空使用当前应用。The app name, if empty, use current app's name.
* @param string $class 对象的类型,可选值 target、zen、tao默认为 target。The type of the object, optional values target, zen, tao, the default is target.
* @access public
* @return string the target file
*/
public function setTargetFile($moduleName, $appName = '', $class = 'model')
{
if($appName == '') $appName = $this->getAppName();
/* 设置主target文件。 Set the main target file. */
$mainTargetFile = $this->getModulePath($appName, $moduleName) . "{$class}.php";
if($this->config->framework->extensionLevel == 0) return $mainTargetFile;
/* 计算扩展的文件和hook文件。Compute the extension files and hook files. */
$hookFiles = array();
$extFiles = array();
$apiFiles = array();
$siteExtended = false;
$targetExtPaths = $this->getModuleExtPath($appName, $moduleName, $class);
foreach($targetExtPaths as $extType => $targetExtPath)
{
if(empty($targetExtPath)) continue;
$tmpHookFiles = helper::ls($targetExtPath . 'hook/', '.php');
$tmpExtFiles = helper::ls($targetExtPath, '.php');
$tmpAPIFiles = helper::ls($targetExtPath, '.api');
$hookFiles = array_merge($hookFiles, $tmpHookFiles);
$extFiles = array_merge($extFiles, $tmpExtFiles);
$apiFiles = array_merge($apiFiles, $tmpAPIFiles);
if($extType == 'site' and (!empty($tmpHookFiles) or !empty($tmpExtFiles) or !empty($tmpAPIFiles))) $siteExtended = true;
}
/* 如果没有扩展文件,返回主文件。 If no extension or hook files, return the main file directly. */
if(empty($extFiles) and empty($hookFiles) and empty($apiFiles)) return $mainTargetFile;
/* 计算合并之后的targetFile路径。Compute the merged target file path. */
$extTargetPrefix = $this->config->edition . DS . $this->config->vision . DS;
if($siteExtended and !empty($this->siteCode)) $extTargetPrefix .= $this->siteCode[0] . DS . $this->siteCode;
$mergedTargetDir = $this->getTmpRoot() . $class . DS . $extTargetPrefix;
$mergedTargetFile = $mergedTargetDir . $moduleName . '.php';
if(!is_dir($mergedTargetDir)) mkdir($mergedTargetDir, 0755, true);
/* 判断生成的缓存文件是否需要更新。 Judge whether the merged target file needed update or not. */
if(!$this->needTargetFileUpdate($mergedTargetFile, $extFiles, $hookFiles, $apiFiles, $targetExtPaths, $mainTargetFile)) return $mergedTargetFile;
/* 合并扩展和hook文件。Merge the extension and hook files. */
$targetLines = $this->mergeTargetExtFiles($moduleName, $extFiles, $mergedTargetDir, $class);
$this->mergeTargetHookFiles($moduleName, $mainTargetFile, $targetLines, $hookFiles, $mergedTargetDir, $mergedTargetFile, $apiFiles, $class);
return $mergedTargetFile;
}
/**
* 检查合并之后的target文件是否需要更新。Check whether the merged target file need update or not.
*
* @param string $mergedTargetFile
* @param array $extFiles
* @param array $hookFiles
* @param array $apiFiles
* @param string $targetExtPaths
* @param string $mainTargetFile
* @access public
* @return bool
*/
public function needTargetFileUpdate($mergedTargetFile, $extFiles, $hookFiles, $apiFiles, $targetExtPaths, $mainTargetFile)
{
$lastTime = file_exists($mergedTargetFile) ? filemtime($mergedTargetFile) : 0;
foreach($extFiles as $extFile) if(filemtime($extFile) > $lastTime) return true;
foreach($hookFiles as $hookFile) if(filemtime($hookFile) > $lastTime) return true;
foreach($apiFiles as $apiFile) if(filemtime($apiFile) > $lastTime) return true;
$targetExtPath = $targetExtPaths['common'];
$targetHookPath = $targetExtPaths['common'] . 'hook/';
if(is_dir($targetExtPath ) and filemtime($targetExtPath) > $lastTime) return true;
if(is_dir($targetHookPath) and filemtime($targetHookPath) > $lastTime) return true;
if(!empty($targetExtPaths['site']))
{
$targetExtPath = $targetExtPaths['site'];
$targetHookPath = $targetExtPaths['site'] . 'hook/';
if(is_dir($targetExtPath ) and filemtime($targetExtPath) > $lastTime) return true;
if(is_dir($targetHookPath) and filemtime($targetHookPath) > $lastTime) return true;
}
if(filemtime($mainTargetFile) > $lastTime) return true;
return false;
}
/**
* 将target的扩展文件合并在一起。Merge target ext files.
*
* @param string $moduleName
* @param array $extFiles
* @param string $mergedTargetDir
* @param string $class model | zen | tao
* @access public
* @return void
*/
public function mergeTargetExtFiles($moduleName, $extFiles, $mergedTargetDir, $class = 'model')
{
/* 设置类名。Set the class names. */
$targetClass = $moduleName . ucfirst($class);
$tmpTargetClass = "tmpExt$targetClass";
/* 开始拼装代码。Prepare the codes. */
$targetLines = "<?php\n";
$targetLines .= "global \$app;\n";
$targetLines .= "helper::import(\$app->getModulePath('', '$moduleName') . " . "'$class.php');\n";
$targetLines .= "class $tmpTargetClass extends $targetClass \n{\n";
/* 将扩展文件的代码合并到代码中。Cycle all the extension files and merge them into target lines. */
$extTargets = array();
foreach($extFiles as $extFile) $extTargets[basename($extFile)] = $extFile;
foreach($extTargets as $extTarget) $targetLines .= self::removePHPTAG($extTarget);
/* 做个标记方便后面替换代码使用。Make a mark for replacing codes. */
$replaceMark = '//**//';
$targetLines .= "\n$replaceMark\n}";
/* 生成一个临时的target扩展文件并加载用于后续的hook文件加载使用。Create a tmp merged target file and import it for merge hook codes using. */
$tmpTargetFile = $mergedTargetDir . "tmp$moduleName.php";
if(@file_put_contents($tmpTargetFile, $targetLines))
{
if(!class_exists($tmpTargetClass)) include $tmpTargetFile;
return $targetLines;
}
$this->triggerError("ERROR: $tmpTargetFile not writable.", __FILE__, __LINE__, true);
}
/**
* 合并target的hook脚本。Merge hook files for a target.
*
* @param string $moduleName
* @param string $mainTargetFile
* @param string $targetLines
* @param array $hookFiles
* @param string $mergedTargetDir
* @param string $mergedTargetFile
* @param array $apiFiles
* @param string $class model | zen | tao
* @access public
* @return void
*/
public function mergeTargetHookFiles($moduleName, $mainTargetFile, $targetLines, $hookFiles, $mergedTargetDir, $mergedTargetFile, $apiFiles, $class = 'model')
{
/* 定义相关变量。Init vars. */
$targetClass = $moduleName . ucfirst($class);
$extTargetClass = 'ext' . $targetClass;
$tmpTargetClass = 'tmpExt' . $targetClass;
$tmpTargetFile = $mergedTargetDir . "tmp$moduleName.php";
$replaceMark = '//**//';
/* 读取hook文件。Get hook codes need to merge. */
$hookCodes = array();
foreach($apiFiles as $apiFile)
{
/* 通过文件名获得其对应的方法名。Get methods according it's filename. */
$fileName = baseName($apiFile);
list($method) = explode('.', $fileName);
$url = self::extractAPIURL($apiFile);
if($url) $hookCodes[$method][] = "return helper::requestAPI('$url');";
}
foreach($hookFiles as $hookFile)
{
/* 通过文件名获得其对应的方法名。Get methods according it's filename. */
$fileName = baseName($hookFile);
list($method) = explode('.', $fileName);
$hookCodes[$method][] = self::removePHPTAG($hookFile);
}
/* 合并Hook文件。Cycle the hook methods and merge hook codes. */
$hookedMethods = array_keys($hookCodes);
$mainTargetCodes = file($mainTargetFile);
$mergedTargetCodes = file($tmpTargetFile);
foreach($hookedMethods as $method)
{
/* 通过反射获得hook脚本对应的方法所在的文件和起止行数。Reflection the hooked method to get it's defined position. */
if(!method_exists($tmpTargetClass, $method)) continue;
$methodRelfection = new reflectionMethod($tmpTargetClass, $method);
$definedFile = $methodRelfection->getFileName();
$startLine = $methodRelfection->getStartLine();
$endLine = $methodRelfection->getEndLine();
/* 将Hook脚本和老的代码合并在一起并替换原来的定义。Merge hook codes with old codes and replace back. */
$oldCodes = $definedFile == $tmpTargetFile ? $mergedTargetCodes : $mainTargetCodes;
$oldCodes = join("", array_slice($oldCodes, $startLine - 1, $endLine - $startLine + 1));
$openBrace = strpos($oldCodes, '{');
$newCodes = substr($oldCodes, 0, $openBrace + 1) . "\n" . join("\n", $hookCodes[$method]) . substr($oldCodes, $openBrace + 1);
if($definedFile == $tmpTargetFile) $targetLines = str_replace($oldCodes, $newCodes, $targetLines);
if($definedFile != $tmpTargetFile) $targetLines = str_replace($replaceMark, $newCodes . "\n$replaceMark", $targetLines);
}
/* 保存最终的Target文件。Save the last merged target file. */
$targetLines = str_replace($tmpTargetClass, $extTargetClass, $targetLines);
file_put_contents($mergedTargetFile, $targetLines);
unlink($tmpTargetFile);
}
/**
* Remove tags of PHP
*
* @param string $fileName
* @static
* @access public
* @return string
*/
static public function removePHPTAG($fileName)
{
$code = trim(file_get_contents($fileName));
if(strpos($code, '<?php') === 0) $code = ltrim($code, '<?php');
if(strrpos($code, '?' . '>') !== false) $code = rtrim($code, '?' . '>');
return trim($code);
}
/**
* Extract API url from api file.
*
* @param string $fileName
* @static
* @access public
* @return string
*/
static public function extractAPIURL($fileName)
{
global $config;
$url = '';
$lines = file($fileName);
foreach($lines as $line)
{
$line = trim($line);
if(empty($line)) continue;
if(preg_match('/^https?\:\/\//', $line))
{
$url = $line;
break;
}
}
if(empty($url)) return false;
return $url;
}
/**
* Send API.
*
* @param string $apiFile
* @access public
* @return void
*/
public function sendAPI($apiFile)
{
$extension = substr($apiFile, strrpos($apiFile, '.') + 1);
if($extension != '302' and $extension != 'api') return false;
$lines = file($apiFile);
$url = '';
foreach($lines as $line)
{
$line = trim($line);
if(empty($line)) continue;
if(preg_match('/^https?\:\/\//', $line))
{
$url = $line;
break;
}
}
if(empty($url)) return false;
$url .= (strpos($url, '?') !== false ? '&' : '?') . $this->config->sessionVar . '=' . session_id() . '&account=' . $_SESSION['user']->account;
if($extension == '302')
{
header("location: $url");
exit;
}
if($extension == 'api')
{
$response = common::http($url);
$headFile = $this->moduleRoot . 'common/view/header.html.php';
$footFile = $this->moduleRoot . 'common/view/footer.html.php';
$obLevel = ob_get_level();
for($i = 0; $i < $obLevel; $i++) ob_end_clean();
$viewFiles = $this->control->setViewFile($this->moduleName, $this->methodName);
if($css) $this->control->view->pageCSS = $css;
if($js) $this->control->view->pageJS = $js;
$output = '';
$output .= $this->control->printViewFile($headFile);
$output .= $response;
if(isset($viewFiles['hookFiles'])) foreach($viewFiles['hookFiles'] as $hookFile) $output .= $this->control->printViewFile($hookFile);
$output .= $this->control->printViewFile($footFile);
die($output);
}
}
//-------------------- 路由相关方法(Routing related methods) --------------------//
/**
* 设置路由(PATH_INFO 方式)
* 1.设置模块名;
* 2.设置方法名;
* 3.设置控制器文件。
*
* Set the route according to PATH_INFO.
* 1. set the module name.
* 2. set the method name.
* 3. set the control file.
*
* @access public
* @return void
*/
public function setRouteByPathInfo()
{
if(!empty($this->URI))
{
/*
* 根据$requestFix分割符分割网址。
* There's the request separator, split the URI by it.
**/
if(strpos($this->URI, $this->config->requestFix) !== false)
{
$items = explode($this->config->requestFix, $this->URI);
$this->setModuleName($items[0]);
$this->setMethodName($items[1]);
}
/*
* 如果网址中没有分隔符,使用默认的方法。
* No request separator, use the default method name.
**/
else
{
$this->setModuleName($this->URI);
$this->setMethodName($this->config->default->method);
}
}
else
{
$this->setModuleName($this->config->default->module); // 使用默认模块 use the default module.
$this->setMethodName($this->config->default->method); // 使用默认方法 use the default method.
}
$this->setControlFile();
}
/**
* 设置路由(GET 方式)
* 1.设置模块名;
* 2.设置方法名;
* 3.设置控制器文件。
*
* Set the route according to GET.
* 1. set the module name.
* 2. set the method name.
* 3. set the control file.
*
* @access public
* @return void
*/
public function setRouteByGET()
{
$moduleName = isset($_GET[$this->config->moduleVar]) ? strtolower($_GET[$this->config->moduleVar]) : $this->config->default->module;
$methodName = isset($_GET[$this->config->methodVar]) ? strtolower($_GET[$this->config->methodVar]) : $this->config->default->method;
$this->setModuleName($moduleName);
$this->setMethodName($methodName);
$this->setControlFile();
}
/**
* 加载一个模块:
* 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()
{
try {
if(is_null($this->params) and !$this->setParams()) return false;
/* 调用该方法 Call the method. */
$module = $this->control;
call_user_func_array(array($module, $this->methodName), $this->params);
$this->checkAPIFile();
return $module;
} catch (EndResponseException $endResponseException) {
echo $endResponseException->getContent();
}
return isset($module) ? $module : false;
}
/**
* 加载指定模块下的某种对象。
* Load the target object of one module.
*
* @param string $moduleName 模块名如果为空使用当前模块。The module name, if empty, use current module's name.
* @param string $appName 应用名如果为空使用当前应用。The app name, if empty, use current app's name.
* @param string $class 对象的类型,可选值 model、zen、tao默认为 model。The type of the object, optional values model, zen, tao, the default is model.
* @access public
* @return object|bool 如果没有model文件返回false否则返回model对象。If no model file, return false, else return the model object.
*/
public function loadTarget($moduleName = '', $appName = '', $class = 'model')
{
if(empty($moduleName)) $moduleName = $this->moduleName;
if(empty($appName)) $appName = $this->appName;
global $loadedTargets;
if(isset($loadedTargets[$class][$appName][$moduleName])) return $loadedTargets[$class][$appName][$moduleName];
$targetFile = $this->setTargetFile($moduleName, $appName, $class);
/**
* 如果没有target文件返回false。
* If no target file, return false.
*/
if(!helper::import($targetFile)) return false;
/**
* 如果没有扩展文件target类名是$moduleName + $class如果有扩展还需要增加ext前缀。
* If no extension file, target class name is $moduleName + $class, else with 'ext' as the prefix.
*/
$targetClass = class_exists('ext' . $appName . $moduleName. $class) ? 'ext' . $appName . $moduleName . $class : $appName . $moduleName . $class;
if(!class_exists($targetClass))
{
$targetClass = class_exists('ext' . $moduleName. $class) ? 'ext' . $moduleName . $class : $moduleName . $class;
if(!class_exists($targetClass)) $this->triggerError(" The $class $targetClass not found", __FILE__, __LINE__, $exit = true);
}
/**
* 因为zen继承自controltao继承自model构造函数里会调用loadTarget方法赋默认值值防止递归调用。
*/
if($class == 'zen' || $class == 'tao') $loadedTargets[$class][$appName][$moduleName] = false;
/**
* 初始化target 对象并返回。
* Init the target object and return it.
*/
$target = new $targetClass($appName);
$loadedTargets[$class][$appName][$moduleName] = $target;
return $target;
}
/**
* 设置请求的参数(PATH_INFO 方式)。
* Set the params by PATH_INFO.
*
* @param array $defaultParams the default settings of the params.
* @param string $type
* @access public
* @return void
*/
public function setParamsByPathInfo($defaultParams = array(), $type = '')
{
$params = array();
if($type != 'fetch')
{
/* 分割URI。 Spit the URI. */
$items = explode($this->config->requestFix, (string)$this->URI);
$itemCount = count($items);
/**
* 前两项为模块名和方法名参数从下标2开始。
* The first two item is moduleName and methodName. So the params should begin at 2.
**/
for($i = 2; $i < $itemCount; $i ++)
{
$key = key($defaultParams); // Get key from the $defaultParams.
if(empty($key)) continue;
$params[$key] = $items[$i];
next($defaultParams);
}
}
$this->params = $this->mergeParams($defaultParams, $params);
}
/**
* 设置请求的参数(GET 方式)。
* Set the params by GET.
*
* @param array $defaultParams the default settings of the params.
* @param string $type
* @access public
* @return void
*/
public function setParamsByGET($defaultParams, $type = '')
{
$params = array();
if($type != 'fetch')
{
/* Unset moduleVar, methodVar, viewVar and session 变量, 剩下的作为参数。 */
/* Unset the moduleVar, methodVar, viewVar and session var, all the left are the params. */
unset($_GET[$this->config->moduleVar]);
unset($_GET[$this->config->methodVar]);
unset($_GET[$this->config->viewVar]);
unset($_GET[$this->config->sessionVar]);
$params = $_GET;
}
/* Fix bug #3267. Param 'words' is not validated when searching. */
if($this->rawModule == 'search' and $this->rawMethod == 'index') unset($params['words']);
$this->params = $this->mergeParams($defaultParams, $params);
}
/**
* 合并请求的参数和默认参数,这样就可以省略已经有默认值的参数了。
* 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)
{
global $filter;
/* Remove these three params. */
unset($passedParams['onlybody']);
unset($passedParams['tid']);
unset($passedParams['HTTP_X_REQUESTED_WITH']);
/* Check params from URL. */
$nameRule = isset($filter->{$this->moduleName}->{$this->methodName}->paramName) ? $filter->{$this->moduleName}->{$this->methodName}->paramName : $filter->default->paramName;
foreach($passedParams as $param => $value)
{
if(!validater::checkByRule($param, $nameRule)) die('Bad Request!');
$valueRule = $filter->default->paramValue;
if(isset($filter->{$this->moduleName}->{$this->methodName}->paramValue[$param]))
{
$valueRule = $filter->{$this->moduleName}->{$this->methodName}->paramValue[$param];
}
if($value and !validater::checkByRule($value, $valueRule)) die('Bad Request!');
}
$passedParams = array_values($passedParams);
$i = 0;
foreach($defaultParams as $key => $defaultValue)
{
if(isset($passedParams[$i]))
{
$defaultParams[$key] = strip_tags($passedParams[$i]);
}
else
{
if($defaultValue === '_NOT_SET') $this->triggerError("The param '$key' should pass value. ", __FILE__, __LINE__, $exit = true);
}
$i ++;
}
return $defaultParams;
}
/**
* 获取$moduleName变量。
* Get the $moduleName var.
*
* @access public
* @return string
*/
public function getModuleName()
{
return $this->moduleName;
}
/**
* 获取$controlFile变量。
* Get the $controlFile var.
*
* @access public
* @return string
*/
public function getControlFile()
{
return $this->controlFile;
}
/**
* 获取$methodName变量。
* Get the $methodName var.
*
* @access public
* @return string
*/
public function getMethodName()
{
return $this->methodName;
}
/**
* 获取$param变量。
* Get the $param var.
*
* @access public
* @return string
*/
public function getParams()
{
return $this->params;
}
//-------------------- 常用的工具方法(Tool methods) ------------------//
/**
* 从类库中加载一个类文件。
*
* Load a class file.
*
* @param string $className the class name
* @param bool $static statis class or not
* @access public
* @return object|bool the instance of the class or just true.
*/
public function loadClass($className, $static = false)
{
$className = strtolower($className);
/* 搜索$coreLibRoot(Search in $coreLibRoot) */
$classFile = $this->coreLibRoot . $className;
if(is_dir($classFile)) $classFile .= DS . $className;
$classFile .= '.class.php';
if(!helper::import($classFile)) $this->triggerError("class file $classFile not found", __FILE__, __LINE__, $exit = true);
/* 如果是静态调用,则返回(If static, return) */
if($static) return true;
/* 实例化该类(Instance it) */
global $$className;
if(!class_exists($className)) $this->triggerError("the class $className not found in $classFile", __FILE__, __LINE__, $exit = true);
if(!is_object($$className)) $$className = new $className();
return $$className;
}
/**
* 加载整个应用公共的配置文件。
* Load the common config files for the app.
*
* @access public
* @return void
*/
public function loadMainConfig()
{
/* 初始化$config对象。Init the $config object. */
global $config, $filter;
if(!is_object($config)) $config = new config();
$this->config = $config;
/* 加载主配置文件。 Load the main config file. */
$mainConfigFile = $this->configRoot . 'config.php';
if(!file_exists($mainConfigFile)) $this->triggerError("The main config file $mainConfigFile not found", __FILE__, __LINE__, $exit = true);
include $mainConfigFile;
}
/**
* 当multiSite功能打开的时候加载额外的配置文件。
* When multiSite feature enabled, load extra config file.
*
* @access public
* @return void
*/
public function loadExtraConfig()
{
global $config;
$multiConfigFile = $this->configRoot . 'multi.php';
if(file_exists($multiConfigFile)) include $multiConfigFile;
$siteConfigFile = $this->configRoot . "sites/{$this->siteCode}.php";
if(file_exists($siteConfigFile)) include $siteConfigFile;
}
/**
* 加载模块的config文件返回全局$config对象。
* 如果该模块是common加载$configRoot的配置文件其他模块则加载其模块的配置文件。
*
* Load config and return it as the global config object.
* If the module is common, search in $configRoot, else in $modulePath.
*
* @param string $moduleName module name
* @param string $appName app name
* @access public
* @return void
*/
public function loadModuleConfig($moduleName, $appName = '')
{
global $config;
if($config and (!isset($config->$moduleName) or !is_object($config->$moduleName))) $config->$moduleName = new stdclass();
/* 将主配置文件和扩展配置文件合并在一起。Put the main config file and extension config files together. */
$configFiles = $this->getMainAndExtFiles($moduleName, $appName, 'config');
if(empty($configFiles)) return false;
/* 加载每一个配置文件。Load every config file. */
static $loadedConfigs = array();
foreach($configFiles as $configFile)
{
if(in_array($configFile, $loadedConfigs)) continue;
if(file_exists($configFile)) include $configFile;
$loadedConfigs[] = $configFile;
}
/* 加载数据库中与本模块相关的配置项。Merge from the db configs. */
if($moduleName != 'common')
{
if(isset($config->system->$moduleName)) $this->mergeConfig($config->system->$moduleName, $moduleName);
if(isset($config->personal->$moduleName)) $this->mergeConfig($config->personal->$moduleName, $moduleName);
}
}
/**
* Merge db config.
*
* @param array $dbConfig
* @param string $moduleName
* @access public
* @return void
*/
public function mergeConfig($dbConfig, $moduleName = 'common')
{
global $config;
/* 如果没有设置本模块配置则首先进行初始化。Init the $config->$moduleName if not set.*/
if($moduleName != 'common' and !isset($config->$moduleName)) $config->$moduleName = new stdclass();
$config2Merge = $config;
if($moduleName != 'common') $config2Merge = $config->$moduleName;
foreach($dbConfig as $item)
{
if($item->section)
{
if(!isset($config2Merge->{$item->section})) $config2Merge->{$item->section} = new stdclass();
if(is_object($config2Merge->{$item->section}))
{
$config2Merge->{$item->section}->{$item->key} = $item->value;
}
}
else
{
$config2Merge->{$item->key} = $item->value;
}
}
}
/**
* 向客户端输出配置参数,客户端可以根据这些参数实现和调整请求的逻辑。
* Export the config params to the client, thus the client can adjust it's logic according the config.
*
* @access public
* @return void
*/
public function exportConfig()
{
$view = new stdclass();
$view->version = $this->config->version;
$view->requestType = $this->config->requestType;
$view->requestFix = $this->config->requestFix;
$view->moduleVar = $this->config->moduleVar;
$view->methodVar = $this->config->methodVar;
$view->viewVar = $this->config->viewVar;
$view->sessionVar = $this->config->sessionVar;
$view->systemMode = $this->config->systemMode;
$view->sprintConcept = zget($this->config->custom, 'sprintConcept', '0');
$view->URAndSR = zget($this->config->custom, 'URAndSR', '0');
$view->maxUploadSize = strtoupper(ini_get('upload_max_filesize'));
$this->session->set('random', mt_rand(0, 10000));
$view->sessionName = session_name();
$view->sessionID = session_id();
$view->random = $this->session->random;
$view->expiredTime = ini_get('session.gc_maxlifetime');
$view->serverTime = time();
echo json_encode($view);
}
/**
* 获取主语言文件。
* Get main lang file.
*
* @param string $moduleName the module name
* @param string $appName the app name
* @access public
* @return string.
*/
private function getMainLangFile($moduleName, $appName = '')
{
$path = $moduleName . DS . 'lang' . DS . $this->clientLang . '.php';
$modulePath = $this->getExtensionRoot() . 'saas' . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
/* 1. 最后尝试在定制开发中寻找。 Finally, try to find the module in the custom dir. */
$modulePath = $this->getExtensionRoot() . 'custom' . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
/* 2. 如果设置过vision尝试在vision中查找。 If vision is set, try to find the module in the vision. */
if($this->config->vision != 'rnd')
{
$modulePath = $this->getExtensionRoot() . $this->config->vision . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
}
/* 3. 尝试查找商业版本是否有此模块。 Try to find the module in other editon. */
if($this->config->edition != 'open')
{
$modulePath = $this->getExtensionRoot() . $this->config->edition . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
}
/* 4. 尝试查找喧喧是否有此模块。 Try to find the module in other editon. */
$modulePath = $this->getExtensionRoot() . 'xuan' . DS;
if(file_exists($modulePath . $path)) return $modulePath . $path;
/* 5. 如果通用版本里有此模块,优先使用。 If module is in the open edition, use it. */
$modulePath = $this->getModuleRoot($appName);
if(file_exists($modulePath . $path)) return $modulePath . $path;
return '';
}
/**
* 加载语言文件,返回全局$lang对象。
* Load lang and return it as the global lang object.
*
* @param string $moduleName the module name
* @param string $appName the app name
* @access public
* @return bool|object the lang object or false.
*/
public function loadLang($moduleName, $appName = '')
{
/* 计算最终要加载的语言文件。 Get the lang files to be loaded. */
$langFilesToLoad = $this->getMainAndExtFiles($moduleName, $appName, 'lang');
if(empty($langFilesToLoad)) return false;
/* 加载语言文件。Load lang files. */
global $lang;
if(!is_object($lang)) $lang = new language();
if(!isset($lang->$moduleName)) $lang->$moduleName = new stdclass();
static $loadedLangs = array();
foreach($langFilesToLoad as $langFile)
{
if(in_array($langFile, $loadedLangs)) continue;
include $langFile;
$loadedLangs[] = $langFile;
}
$this->lang = $lang;
return $lang;
}
/**
* 连接数据库。
* Connect to database.
*
* @access public
* @return void
*/
public function connectDB()
{
global $config, $dbh, $slaveDBH;
if(!isset($config->installed) or !$config->installed) return;
if(isset($config->db->host)) $this->dbh = $dbh = $this->connectByPDO($config->db);
if(isset($config->slaveDB->host)) $this->slaveDBH = $slaveDBH = $this->connectByPDO($config->slaveDB);
}
/**
* 使用PDO连接数据库。
* Connect database by PDO.
*
* @param object $params the database params.
* @access public
* @return object|bool
*/
public function connectByPDO($params)
{
if(!isset($params->driver)) self::triggerError('no pdo driver defined, it should be mysql or sqlite', __FILE__, __LINE__, $exit = true);
if(!isset($params->user)) return false;
if($params->driver == 'mysql')
{
$dsn = "mysql:host={$params->host}; port={$params->port}; dbname={$params->name}";
}
try
{
$dbPassword = helper::decryptPassword($params->password);
$dbh = new PDO($dsn, $params->user, $dbPassword, array(PDO::ATTR_PERSISTENT => $params->persistant));
$dbh->exec("SET NAMES {$params->encoding}");
/*
* 如果系统是Linux开启仿真预处理和缓冲查询。
* If run on linux, set emulatePrepare and bufferQuery to true.
**/
if(!isset($params->emulatePrepare) and PHP_OS == 'Linux') $params->emulatePrepare = true;
if(!isset($params->bufferQuery) and PHP_OS == 'Linux') $params->bufferQuery = true;
if(defined('RUN_MODE') and RUN_MODE == 'api') $params->emulatePrepare = false;
$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if(isset($params->strictMode) and $params->strictMode == false) $dbh->exec("SET @@sql_mode= ''");
if(isset($params->emulatePrepare)) $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, $params->emulatePrepare);
if(isset($params->bufferQuery)) $dbh->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $params->bufferQuery);
return $dbh;
}
catch (PDOException $exception)
{
$message = $exception->getMessage();
if(empty($message))
{
/* Try to repair table. */
header("location: {$this->config->webRoot}checktable.php");
exit;
}
self::triggerError($message, __FILE__, __LINE__, $exit = true);
}
}
//-------------------- 错误处理方法(Error methods) ------------------//
/**
* 程序停止时执行的函数。
* The shutdown handler.
*
* @access public
* @return void
*/
public function shutdown()
{
/* 如果debug模式开启保存sql语句(If debug on, save sql queries) */
if(!empty($this->config->debug)) $this->saveSQL();
/*
* 发现错误,保存到日志中。
* If any error occurs, save it.
* */
if(!function_exists('error_get_last')) return;
$error = error_get_last();
if($error) $this->saveError($error['type'], $error['message'], $error['file'], $error['line']);
}
/**
* 触发一个错误。
* Trigger an error.
*
* @param string $message 错误信息 error message
* @param string $file 所在文件 the file error occurs
* @param int $line 错误行 the line error occurs
* @param bool $exit 是否停止程序 exit the program or not
* @access public
* @return void
*/
public function triggerError($message, $file, $line, $exit = false)
{
/* 设置错误信息(Set the error info) */
$message = htmlSpecialString($message);
if(preg_match('/[^\x00-\x80]/', $message)) $message = helper::convertEncoding($message, 'gbk');
/* Only show error when debug is open. */
if(!$this->config->debug) die();
$log = "ERROR: $message in $file on line $line";
if(isset($_SERVER['SCRIPT_URI'])) $log .= ", request: $_SERVER[SCRIPT_URI]";;
$trace = debug_backtrace();
extract($trace[0]);
extract($trace[1]);
$log .= ", last called by $file on line $line through function $function.\n";
/* Change absolute path to relative path. */
$log = str_replace($this->basePath, '', $log);
/* 触发错误(Trigger the error) */
trigger_error($log, $exit ? E_USER_ERROR : E_USER_WARNING);
}
/**
* 保存错误信息。
* Save error info.
*
* @param int $level
* @param string $message
* @param string $file
* @param int $line
* @access public
* @return void
*/
public function saveError($level, $message, $file, $line)
{
if(empty($this->config->debug)) return true;
if(!is_dir($this->logRoot)) return true;
if(!is_writable($this->logRoot)) return true;
/*
* 删除设定时间之前的日志。
* Delete the log before the set time.
**/
if(mt_rand(0, 10) == 1)
{
$logDays = isset($this->config->framework->logDays) ? $this->config->framework->logDays : 14;
$dayTime = time() - $logDays * 24 * 3600;
foreach(glob($this->getLogRoot() . '*') as $logFile)
{
if(filemtime($logFile) <= $dayTime) unlink($logFile);
}
}
/*
* 忽略该错误Redefining already defined constructor。
* Skip the error: Redefining already defined constructor.
**/
if(strpos($message, 'Redefining') !== false) return true;
/*
* 设置错误信息。
* Set the error info.
**/
if(preg_match('/[^\x00-\x80]/', $message)) $message = helper::convertEncoding($message, 'gbk');
$errorLog = "\n" . date('H:i:s') . " $message in <strong>$file</strong> on line <strong>$line</strong> ";
$URI = $this->getURI();
$errorLog .= "when visiting <strong>" . (empty($URI) ? '' : htmlspecialchars($URI)) . "</strong>\n";
/*
* 为了安全起见,对公网环境隐藏脚本路径。
* If the ip is pulic, hidden the full path of scripts.
*/
$remoteIP = helper::getRemoteIp(true);
if(!defined('IN_SHELL') and !($remoteIP == '127.0.0.1' or filter_var($remoteIP, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false))
{
$errorLog = str_replace($this->getBasePath(), '', $errorLog);
}
/* 保存到日志文件(Save to log file) */
$errorFile = $this->logRoot . 'php.' . date('Ymd') . '.log.php';
if(!is_file($errorFile)) file_put_contents($errorFile, "<?php\n die();\n?" . ">\n");
$fh = fopen($errorFile, 'a');
if($fh) fwrite($fh, strip_tags($errorLog)) and fclose($fh);
/*
* 如果debug > 1显示warning, notice级别的错误。
* If the debug > 1, show warning, notice error.
**/
if($level == E_NOTICE or $level == E_WARNING or $level == E_STRICT or $level == 8192) // 8192: E_DEPRECATED
{
if(!empty($this->config->debug) and $this->config->debug > 1)
{
$cmd = "vim +$line $file";
$size = strlen($cmd);
echo "<pre class='alert alert-danger'>$message: ";
echo "<input type='text' value='$cmd' size='$size' style='border:none; background:none;' onclick='this.select();' /></pre>";
}
}
/*
* 如果是严重错误,停止程序。
* If error level is serious, die.
* */
if($level == E_ERROR or $level == E_PARSE or $level == E_CORE_ERROR or $level == E_COMPILE_ERROR or $level == E_USER_ERROR)
{
if(empty($this->config->debug)) die();
if(PHP_SAPI == 'cli') die($errorLog);
$htmlError = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' /></head>";
$htmlError .= "<body>" . nl2br($errorLog) . "</body></html>";
die($htmlError);
}
}
/**
* 保存sql语句。
* Save the sql.
*
* @access public
* @return void
*/
public function saveSQL()
{
if(!$this->config->debug) return true;
if(!class_exists('dao')) return;
$sqlLog = $this->getLogRoot() . 'sql.' . date('Ymd') . '.log.php';
if(!is_file($sqlLog)) file_put_contents($sqlLog, "<?php\n die();\n?" . ">\n");
$fh = @fopen($sqlLog, 'a');
if(!$fh) return false;
fwrite($fh, date('Ymd H:i:s') . ": " . $this->getURI() . "\n");
foreach(dao::$querys as $query) fwrite($fh, " $query\n");
fwrite($fh, "\n");
fclose($fh);
}
/**
* Check app if it is run in a container.
*
* @access public
* @return bool
*/
public function isContainer()
{
return strtolower(getenv('IS_CONTAINER')) == 'true';
}
/**
* Get main file and ext files.
*
* @param string $moduleName
* @param string $appName
* @param string $type lang|config|control|model
* @access public
* @return array
*/
public function getMainAndExtFiles($moduleName, $appName = '', $type = 'lang')
{
/* 初始化变量。Init vars. */
$modulePath = $this->getModulePath($appName, $moduleName);
$extFiles = array();
$filesToLoad = array();
/* 判断主文件是否存在。Whether the main file exists or not. */
if($type == 'lang')
{
$mainFile = $this->getMainLangFile($moduleName, $appName);
}
else
{
$mainFile = $modulePath . $type . '.php';
}
if($mainFile) $filesToLoad[] = $mainFile;
/* 获取扩展文件。If extensionLevel > 0, get extension files. */
if($this->config->framework->extensionLevel > 0)
{
$commonExtFiles = array();
$siteExtFiles = array();
$extPath = $this->getModuleExtPath($appName, $moduleName, $type);
if($this->config->framework->extensionLevel >= 1)
{
$clientLang = $type == 'lang' ? $this->clientLang : '';
if(!empty($extPath['common'])) $commonExtFiles = helper::ls($extPath['common'] . $clientLang, '.php');
if(!empty($extPath['xuan'])) $commonExtFiles = array_merge($commonExtFiles, helper::ls($extPath['xuan'] . $clientLang, '.php'));
if(!empty($extPath['vision'])) $commonExtFiles = array_merge($commonExtFiles, helper::ls($extPath['vision'] . $clientLang, '.php'));
if(!empty($extPath['custom'])) $commonExtFiles = array_merge($commonExtFiles, helper::ls($extPath['custom'] . $clientLang, '.php'));
if(!empty($extPath['saas'])) $commonExtFiles = array_merge($commonExtFiles, helper::ls($extPath['saas'] . $clientLang, '.php'));
}
if($this->config->framework->extensionLevel == 2 and !empty($extPath['site'])) $siteExtFiles = helper::ls($extPath['site'] . $clientLang, '.php');
$extFiles = array_merge($commonExtFiles, $siteExtFiles);
}
/* 计算最终要加载的文件。 Get the files to be loaded. */
$filesToLoad = array_merge($filesToLoad, $extFiles);
if(empty($filesToLoad)) return false;
return $filesToLoad;
}
}
/**
* config类。
* The config class.
*
* @package framework
*/
class config
{
/**
* 设置成员变量,成员可以是'db.user'类似的格式。
* Set the value of a member. the member can be the format like db.user.
*
* <code>
* <?php
* $config->set('db.user', 'wwccss');
* ?>
* </code>
* @param string $key the key of the member
* @param mixed $value the value
* @access public
* @return void
*/
public function set($key, $value)
{
helper::setMember('config', $key, $value);
}
}
/**
* lang类。
* The lang class.
*
* @package framework
*/
class language
{
/**
* 设置成员变量,成员可以是'db.user'类似的格式。
* Set the value of a member. the member can be the foramt like db.user.
*
* <code>
* <?php
* $lang->set('version', '1.0);
* ?>
* </code>
* @param string $key 成员的键名可以是father.child的形式。
* the key of the member, can be father.child
* @param mixed $value the value
* @access public
* @return void
*/
public function set($key, $value)
{
helper::setMember('lang', $key, $value);
}
/**
* 显示一个成员的值。
* Show a member.
*
* @param object $obj the object
* @param string $key the key
* @access public
* @return void
*/
public function show($obj, $key)
{
$obj = (array)$obj;
echo isset($obj[$key]) ? $obj[$key] : '';
}
}
/**
* 超级对象类,转化超级全局变量。
* The super object class.
*
* @package framework
*/
class super
{
/**
* 构造函数,设置超级变量名。
* Construct, set the var scope.
*
* @param string $scope the scope, can be server, post, get, cookie, session, global
* @access public
* @return void
*/
public function __construct($scope, $tab = '')
{
$this->scope = $scope;
$this->tab = $tab;
}
/**
* 设置超级变量的成员值。
* Set one member value.
*
* @param string $key the key
* @param mixed $value the value
* @param string $tab
* @access public
* @return void
*/
public function set($key, $value, $tab = '')
{
if($this->scope == 'post')
{
$_POST[$key] = $value;
}
elseif($this->scope == 'get')
{
$_GET[$key] = $value;
}
elseif($this->scope == 'server')
{
$_SERVER[$key] = $value;
}
elseif($this->scope == 'cookie')
{
$_COOKIE[$key] = $value;
}
elseif($this->scope == 'session')
{
if($tab) $_SESSION["app-$tab"][$key] = $value;
$_SESSION[$key] = $value;
}
elseif($this->scope == 'env')
{
$_ENV[$key] = $value;
}
elseif($this->scope == 'global')
{
$GLOBALS[$key] = $value;
}
}
/**
* 超级变量的魔术方法,比如用$post->key访问$_POST['key']。
* The magic get method.
*
* @param string $key the key
* @access public
* @return mixed|bool return the value of the key or false.
*/
public function __get($key)
{
if($this->scope == 'post')
{
if(isset($_POST[$key])) return $_POST[$key];
return false;
}
elseif($this->scope == 'get')
{
if(isset($_GET[$key])) return $_GET[$key];
return false;
}
elseif($this->scope == 'server')
{
if($key == 'ajax') return isset($_SERVER['HTTP_X_REQUESTED_WITH']) ? true : false;
if(isset($_SERVER[$key])) return $_SERVER[$key];
$key = strtoupper($key);
if(isset($_SERVER[$key])) return $_SERVER[$key];
return false;
}
elseif($this->scope == 'cookie')
{
if(isset($_COOKIE[$key])) return $_COOKIE[$key];
return false;
}
elseif($this->scope == 'session')
{
$tab = $this->tab;
if($tab and isset($_SESSION["app-$tab"][$key])) return $_SESSION["app-$tab"][$key];
if(isset($_SESSION[$key])) return $_SESSION[$key];
return false;
}
elseif($this->scope == 'env')
{
if(isset($_ENV[$key])) return $_ENV[$key];
return false;
}
elseif($this->scope == 'global')
{
if(isset($GLOBALS[$key])) return $GLOBALS[$key];
return false;
}
else
{
return false;
}
}
/**
* 打印变量的详细结构。
* Print the structure.
*
* @access public
* @return void
*/
public function a()
{
if($this->scope == 'post') a($_POST);
if($this->scope == 'get') a($_GET);
if($this->scope == 'server') a($_SERVER);
if($this->scope == 'cookie') a($_COOKIE);
if($this->scope == 'session') a($_SESSION);
if($this->scope == 'env') a($_ENV);
if($this->scope == 'global') a($GLOBALS);
}
}
class EndResponseException extends \Exception
{
/**
* 响应内容
*
* @var string
*/
private $content;
/**
* @param string $content
*
* @return self
*/
public static function create($content = '')
{
$exception = new self;
$exception->content = $content;
return $exception;
}
/**
* Get 响应内容
*
* @return string
*/
public function getContent()
{
return $this->content;
}
}
/**
* ZenTao session handler.
*
* @package framework
*/
class ztSessionHandler
{
public $sessSavePath;
public $tagID;
public $sessionFile;
public $sessionID;
public $rawID;
public $rawFile;
/**
* Construct.
*
* @param string $tagID
* @access public
* @return void
*/
public function __construct($tagID = '')
{
$this->tagID = $tagID;
ini_set('session.save_handler', 'files');
register_shutdown_function('session_write_close');
}
/**
* Get sessionID
*
* @access public
* @return string
*/
public function getSessionID()
{
return $this->sessionID;
}
/**
* Get session file.
*
* @param string $id
* @access public
* @return string
*/
public function getSessionFile($id)
{
if(!empty($this->sessionFile)) return $this->sessionFile;
$sessionID = $id;
if($this->tagID) $sessionID = md5($id . $this->tagID);
$fileName = "sess_$sessionID";
$this->sessionFile = $this->sessSavePath . '/' . $fileName;
$this->sessionID = $sessionID;
$this->rawID = $id;
$this->rawFile = $this->sessSavePath . '/' . "sess_$id";
return $this->sessionFile;
}
/**
* Open
*
* @param string $savePath
* @param string $sessionName
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function open($savePath, $sessionName)
{
$this->sessSavePath = $savePath;
return true;
}
/**
* Close
*
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function close()
{
return true;
}
/**
* Read
*
* @param string $id
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function read($id)
{
$sessFile = $this->getSessionFile($id);
if(!file_exists($sessFile))
{
($this->tagID and file_exists($this->rawFile)) ? copy($this->rawFile, $sessFile) : touch($sessFile);
}
return (string) file_get_contents($sessFile);
}
/**
* Write
*
* @param string $id
* @param string $sessData
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function write($id, $sessData)
{
$sessFile = $this->getSessionFile($id);
touch($sessFile);
touch($this->rawFile);
if(md5_file($sessFile) == md5($sessData)) return true;
if(file_put_contents($sessFile, $sessData, LOCK_EX))
{
if(strpos($sessData, 'user|') !== false)
{
$rawSessContent = (string) file_get_contents($this->rawFile, false, null, 0, 1024 * 2);
if(strpos($rawSessContent, 'user|') === false) file_put_contents($this->rawFile, $sessData, LOCK_EX);
}
return true;
}
return false;
}
/**
* Destroy
*
* @param string $id
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function destroy($id)
{
$sessFile = $this->getSessionFile($id);
if(file_exists($sessFile)) unlink($sessFile);
if(file_exists($this->rawFile)) unlink($this->rawFile);
touch($sessFile);
touch($this->rawFile);
return true;
}
/**
* GC
*
* @param int $maxlifeTime
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function gc($maxlifeTime)
{
$time = time();
foreach(glob("$this->sessSavePath/sess_*") as $fileName)
{
if(filemtime($fileName) + $maxlifeTime < $time) @unlink($fileName);
}
return true;
}
}