monkeye 1 год назад
Сommit
117d121b25
39 измененных файлов с 5107 добавлено и 0 удалено
  1. 15 0
      .gitignore
  2. 575 0
      api/restful/class_restful.php
  3. 174 0
      api/restful/index.php
  4. 272 0
      config/config_global_default.php
  5. 35 0
      config/config_ucenter_default.php
  6. 1 0
      config/index.htm
  7. 17 0
      index.php
  8. 0 0
      source/app/index.htm
  9. 12 0
      source/app/index/index.php
  10. BIN
      source/app/index/static/image/icon.png
  11. 0 0
      source/app/index/table/index.htm
  12. 37 0
      source/app/index/table/table_test1.php
  13. 7 0
      source/app/index/template/sample.php
  14. 5 0
      source/app/index/yyy/zzz.php
  15. 262 0
      source/class/class_core.php
  16. 398 0
      source/class/class_template.php
  17. 35 0
      source/class/class_tplfile.php
  18. 107 0
      source/class/class_xml.php
  19. 308 0
      source/class/class_zip.php
  20. 247 0
      source/class/db/db_driver_mysqli.php
  21. 95 0
      source/class/db/db_driver_mysqli_slave.php
  22. 248 0
      source/class/db/db_driver_pdo.php
  23. 98 0
      source/class/db/db_driver_pdo_slave.php
  24. 0 0
      source/class/db/index.htm
  25. 348 0
      source/class/discuz/discuz_application.php
  26. 62 0
      source/class/discuz/discuz_base.php
  27. 152 0
      source/class/discuz/discuz_container.php
  28. 16 0
      source/class/discuz/discuz_core.php
  29. 623 0
      source/class/discuz/discuz_database.php
  30. 352 0
      source/class/discuz/discuz_error.php
  31. 244 0
      source/class/discuz/discuz_table.php
  32. 148 0
      source/class/discuz/discuz_table_archive.php
  33. 0 0
      source/class/discuz/index.htm
  34. 1 0
      source/class/index.htm
  35. 19 0
      source/discuz_version.php
  36. 193 0
      source/function/function_core.php
  37. 0 0
      source/function/index.htm
  38. 1 0
      source/index.htm
  39. 0 0
      vendor/index.htm

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+.idea/*
+.htaccess
+.user.ini
+config/config_global.php
+config/config_ucenter.php
+data/*
+!data/plugindata/
+!data/ipdata/
+uc_client/data/cache/*
+uc_server/data/*
+install/index.php
+uc_server/install/index.php
+*.DS_Store
+source/plugin/yunnuo_*
+.editorconfig

+ 575 - 0
api/restful/class_restful.php

@@ -0,0 +1,575 @@
+<?php
+
+define('ROOT_PATH', dirname(__FILE__).'/../../');
+
+class _dzRestful {
+
+	private $apiParam;
+
+	private $postParam;
+
+	public $token;
+
+	public $tokenData;
+
+	private $_appId;
+
+	private $_m;
+
+	const SignTTL = 300;
+
+	const TokenTTL = 3600;
+
+	const TokenSaveTTL = 86400 * 30;
+
+	const AuthTokenTTL = 300;
+
+	const TokenPre = 'rToken_';
+
+	const AuthTokenPre = 'rAuthToken_';
+
+	const AppIdConfPre = 'rApp_';
+
+	const ApiConfPre = 'rApi_';
+	const ApiFreqPre = 'rFreq_';
+	const ApiFreqTTL = 60;
+
+	const AuthPre = 'rAuth_';
+	const AuthTTL = 60;
+
+	const Error = [
+		-100 => 'run: script is exception',
+		-101 => 'checkSign: param is missing',
+		-102 => 'checkSign: appid is invalid',
+		-103 => 'checkSign: sign is expire',
+		-104 => 'checkSign: sign is invalid',
+		-105 => 'checkToken: token is missing',
+		-106 => 'checkToken: token is expire',
+		-107 => 'checkToken: appid is error',
+		-108 => 'getTokenData: token is error',
+		-109 => 'getApiParam: api is invalid',
+		-110 => 'getAppidPerm: appid is invalid',
+		-111 => 'getSecret: appid is invalid',
+		-112 => 'parseQuery: api url is empty',
+		-113 => 'parseQuery: api url is error',
+		-114 => 'initParam: api is invalid',
+		-115 => 'apiPermCheck: api is invalid',
+		-116 => 'apiFreqCheck: out of frequency',
+		-117 => 'scriptCheck: script is empty',
+		-118 => 'scriptCheck: script format is error',
+		-119 => 'callback: authtoken is invalid',
+	];
+
+	const Developer = false;
+
+	public function __construct($postParam = []) {
+		require_once ROOT_PATH.'./source/function/function_core.php';
+		$this->postParam = $postParam;
+	}
+
+	public function parseQuery() {
+		$s = rawurldecode($_SERVER['QUERY_STRING']);
+		if(!$s) {
+			$this->error(-112);
+		}
+		if(str_ends_with($s, '=')) {
+			$s = substr($s, 0, -1);
+		}
+		if(!str_starts_with($s, '/')) {
+			$s = '/'.$s;
+		}
+		$e = explode('/', $s);
+		$c = count($e);
+		if(preg_match('/^v\d+$/', $e[$c - 1])) {
+			$api = array_slice($e, 1, -1);
+			$ver = $e[$c - 1];
+		} else {
+			$api = array_slice($e, 1);
+			$ver = 'v1';
+		}
+		if(!$api || !$ver) {
+			$this->error(-113);
+		}
+		return [$api, $ver];
+	}
+
+	public function initParam($api, $ver) {
+		$apiParams = $this->_getApiParam($api, $ver);
+		$key = '/'.implode('/', $api);
+		if(empty($apiParams[$key])) {
+			$this->error(-114);
+		}
+		$params = $apiParams[$key];
+		$params['perms'] = [$key.'/'.$ver];
+		$this->apiParam = $params;
+		return $params['script'];
+	}
+
+	public function paramDecode($key) {
+		if(empty($this->apiParam[$key])) {
+			return [];
+		}
+
+		if(is_array($this->apiParam[$key])) {
+			$v = $this->apiParam[$key];
+		} else {
+			$_tmp = unserialize($this->apiParam[$key]);
+			$v = $_tmp === false ? $this->apiParam[$key] : $_tmp;
+		}
+		return $this->_replacePostParams($v);
+	}
+
+	public function getRequestParam() {
+		return !empty($this->postParam['_REQUEST']) ? json_decode($this->postParam['_REQUEST'], true) : [];
+	}
+
+	public function getShutdownFunc() {
+		$output = !empty($this->apiParam['output']) ? $this->apiParam['output'] : [];
+		$rawOutput = !empty($this->apiParam['raw']);
+		$shutdownFunc = 'showOutput';
+		if($rawOutput) {
+			$shutdownFunc = 'rawOutput';
+		} elseif($output) {
+			$shutdownFunc = 'convertOutput';
+		}
+		return [$shutdownFunc, $output];
+	}
+
+	private function _replacePostParams($v) {
+		foreach($v as $_k => $_v) {
+			if(is_array($_v)) {
+				$v[$_k] = $this->_replacePostParams($_v);
+			} elseif(is_string($_v)) {
+				if(str_starts_with($_v, '{') && preg_match('/^\{:(\w+):\}$/', $_v, $r)) {
+					$v[$_k] = !empty($this->postParam[$r[1]]) ? $this->postParam[$r[1]] : null;
+				}
+			}
+		}
+		return $v;
+	}
+
+	public function apiPermCheck() {
+		if(!empty($this->tokenData['_conf']['apis']) && !array_intersect($this->apiParam['perms'], $this->tokenData['_conf']['apis'])) {
+			$this->error(-115);
+		}
+	}
+
+	public function apiFreqCheck() {
+		$api = $this->apiParam['perms'][0];
+		if(!empty($this->tokenData['_conf']['freq'][$api])) {
+			$key = self::ApiFreqPre.$this->_appId.'_'.$api;
+			$v = $this->_memory('get', [$key]);
+			if(!$v) {
+				$this->_memory('inc', [$key]);
+				$this->_memory('expire', [$key, 10]);//self::ApiFreqTTL
+			} elseif($v >= $this->tokenData['_conf']['freq'][$api]) {
+				$this->error(-116);
+			} else {
+				$this->_memory('inc', [$key]);
+			}
+		}
+	}
+
+	public function scriptCheck() {
+		if(empty($this->apiParam['script'])) {
+			$this->error(-117);
+		}
+		if(!preg_match('/^\w+$/', $this->apiParam['script'])) {
+			$this->error(-118);
+		}
+		return $this->apiParam['script'];
+	}
+
+	public function error($code) {
+		$this->output([
+			'ret' => $code,
+			'msg' => !empty(self::Error[$code]) ? self::Error[$code] : '',
+		]);
+	}
+
+	public function output($value) {
+		$this->_setSysVar($return['data'], $value);
+		echo json_encode($value);
+		exit;
+	}
+
+	public function showOutput() {
+		$return = ['ret' => 0];
+
+		$s = ob_get_contents();
+		ob_end_clean();
+		$return['data']['content'] = $s;
+
+		$this->plugin('after', $return['data']);
+		$this->output($return);
+	}
+
+	public function rawOutput() {
+		$newTokenData = $this->updateTokenData();
+		if($newTokenData) {
+			$ttl = $this->tokenData['refreshExptime'] - time();
+			$this->setToken($this->token, $newTokenData, $ttl);
+		}
+		exit;
+	}
+
+	public function convertOutput($output) {
+		ob_end_clean();
+		$return = ['ret' => 0];
+
+		$tmp = $GLOBALS;
+		foreach($output as $k => $v) {
+			if(str_contains($k, '/')) {
+				$return['data'][$v] = $this->_arrayVar($tmp, $k);
+			} else {
+				$return['data'][$v] = $this->_singleVar($tmp, $k);
+			}
+		}
+		$this->plugin('after', $return['data']);
+
+		$this->output($return);
+	}
+
+	public function plugin($type, &$data) {
+		if(empty($this->apiParam['plugin'][$type])) {
+			return;
+		}
+		foreach($this->apiParam['plugin'][$type] as $method => $value) {
+			$vars = explode(':', $method);
+			if(count($vars) == 2) {
+				if(!preg_match('/^\w+$/', $vars[0])) {
+					continue;
+				}
+				require_once ROOT_PATH.'./source/function/function_path.php';
+				$f = ROOT_PATH.PLUGIN_ROOT.$vars[0].'/restful.class.php';
+				$c = 'restful_'.$vars[0];
+				$m = $vars[1];
+			} else {
+				$f = ROOT_PATH.'./source/class/class_restfulplugin.php';
+				$c = 'restfulplugin';
+				$m = $method;
+			}
+			if(!file_exists($f)) {
+				continue;
+			}
+			@require_once $f;
+			if(!class_exists($c) || !method_exists($c, $m)) {
+				continue;
+			}
+			call_user_func_array([$c, $m], [&$data, explode(',', $value['param'] ?? '')]);
+		}
+	}
+
+	public function sessionDecode() {
+		$session = unserialize(base64_decode($this->tokenData['_session']));
+		if(!empty($this->postParam['_authSign'])) {
+			$this->decodeAuthSign($session);
+		}
+		return $session;
+	}
+
+	public function checkSign() {
+		if(empty($_SERVER['HTTP_APPID']) ||
+			empty($_SERVER['HTTP_SIGN']) ||
+			empty($_SERVER['HTTP_NONCE']) ||
+			empty($_SERVER['HTTP_T'])) {
+			$this->error(-101);
+		}
+		$this->_getAppidPerm($_SERVER['HTTP_APPID']);
+		$secret = $this->_getSecret();
+		if(!$secret) {
+			$this->error(-102);
+		}
+		if(time() - $_SERVER['HTTP_T'] > self::SignTTL) {
+			$this->error(-103);
+		}
+		if($_SERVER['HTTP_SIGN'] != base64_encode(hash('sha256', $_SERVER['HTTP_NONCE'].$_SERVER['HTTP_T'].$secret))) {
+			$this->error(-104);
+		}
+	}
+
+	public function checkToken() {
+		if(empty($this->_getToken())) {
+			$this->error(-105);
+		}
+		$v = $this->_getTokenData();
+		if(time() >= $v['exptime']) {
+			$this->error(-106);
+		}
+		$this->tokenData = $v;
+		if(!$v['_appid'] || $v['_appid'] != $_SERVER['HTTP_APPID']) {
+			$this->error(-107);
+		}
+		$this->_getAppidPerm($v['_appid']);
+		$this->postParam['formhash'] = !empty($this->tokenData['_formhash']) ? $this->tokenData['_formhash'] : '';
+		$this->token = $this->_getToken();
+	}
+
+	public function setToken($key, $value, $ttl = self::TokenSaveTTL) {
+		$this->_memory('set', [self::TokenPre.$key, serialize($value), $ttl]);
+	}
+
+	public function setAuthToken($token, $value) {
+		if($this->getAuthToken($token)) {
+			return false;
+		}
+		global $_G;
+		$value = authcode(serialize($value), 'ENCODE', md5($_G['config']['security']['authkey']));
+		$this->_memory('set', [self::AuthTokenPre.$token, $value, self::AuthTokenTTL]);
+		return true;
+	}
+
+	public function getAuthToken($token) {
+		global $_G;
+		$value = $this->_memory('get', [self::AuthTokenPre.$token]);
+		return $value ? dunserialize(authcode($value, 'DECODE', md5($_G['config']['security']['authkey']))) : [];
+	}
+
+	public function isRefreshToken() {
+		return !empty($this->_getToken());
+	}
+
+	private function _getToken() {
+		return !empty($_SERVER['HTTP_TOKEN']) ? $_SERVER['HTTP_TOKEN'] : (!empty($this->postParam['token']) ? $this->postParam['token'] : '');
+	}
+
+	private function _getTokenData() {
+		$v = dunserialize($this->_memory('get', [self::TokenPre.$this->_getToken()]));
+		if(empty($v)) {
+			$this->error(-108);
+		}
+		return $v;
+	}
+
+	public function refreshTokenData() {
+		$v = $this->_getTokenData();
+		$data['_session'] = $v['_session'];
+		$data['_formhash'] = $this->_singleVar($_G, 'formhash');
+		$data['_appid'] = $_SERVER['HTTP_APPID'];
+		$data['_conf'] = $this->tokenData['_conf'];
+		$data['exptime'] = time() + self::TokenTTL;
+		$data['refreshExptime'] = time() + self::TokenSaveTTL;
+		return serialize($data);
+	}
+
+	public function delTokenData() {
+		$this->_memory('rm', [self::TokenPre.$this->_getToken()]);
+	}
+
+	public function newTokenData() {
+		global $_G;
+		$data = [];
+		$data['_session'] = $this->_sessionEncode($_COOKIE);
+		$data['_formhash'] = $this->_singleVar($_G, 'formhash');
+		$data['_appid'] = $_SERVER['HTTP_APPID'];
+		$data['_conf'] = $this->tokenData['_conf'];
+		$data['exptime'] = time() + self::TokenTTL;
+		$data['refreshExptime'] = time() + self::TokenSaveTTL;
+		return serialize($data);
+	}
+
+	private function _getApiParam($api, $ver) {
+		if(self::Developer) {
+			return $this->_getApiParam_Developer($api, $ver);
+		}
+		$v = $this->_memory('get', [self::ApiConfPre.'/'.$api[0].'_'.$ver]);
+		if(!$v) {
+			$this->error(-109);
+		}
+		return $this->apiParam = dunserialize($v);
+	}
+
+	private function _getAppidPerm($appid) {
+		$v = $this->_memory('get', [self::AppIdConfPre.$appid]);
+		if(!$v) {
+			if(!empty($GLOBALS['discuz'])) {
+				require_once libfile('function/cache');
+				updatecache('restful');
+				$v = $this->_memory('get', [self::AppIdConfPre.$appid]);
+				if(!$v) {
+					$this->error(-110);
+				}
+			} else {
+				$this->error(-110);
+			}
+		}
+		$this->_appId = $appid;
+		$v = dunserialize($v);
+		$this->tokenData['_conf'] = $v;
+	}
+
+	public function updateTokenData() {
+		global $_G;
+		$data = $this->tokenData;
+		$_session = $this->_sessionEncode($_COOKIE);
+		if(!empty($this->tokenData['_session']) && $_session == $this->tokenData['_session']) {
+			return null;
+		}
+		$data['_session'] = $_session;
+		$data['_formhash'] = $this->_singleVar($_G, 'formhash');
+		return serialize($data);
+	}
+
+	private function _getSecret() {
+		if(empty($this->tokenData['_conf']['secret'])) {
+			$this->error(-111);
+		}
+		return $this->tokenData['_conf']['secret'];
+	}
+
+	private function _sessionEncode($v) {
+		return base64_encode(serialize($v));
+	}
+
+	private function _setSysVar(&$data, &$output = []) {
+		global $_G;
+
+		$newTokenData = $this->updateTokenData();
+		if(!empty($this->tokenData['refreshExptime']) && $newTokenData) {
+			$ttl = $this->tokenData['refreshExptime'] - time();
+			$this->setToken($this->token, $newTokenData, $ttl);
+		}
+
+		if(!empty($_G['_multi'])) {
+			$output['multi'] = $_G['_multi'];
+		}
+
+		unset($_G['config'],
+			$_G['setting']['siteuniqueid'],
+			$_G['setting']['ec_tenpay_opentrans_chnid'],
+			$_G['setting']['ec_tenpay_opentrans_key'],
+			$_G['setting']['ec_tenpay_bargainor'],
+			$_G['setting']['ec_tenpay_key'],
+			$_G['setting']['ec_account'],
+			$_G['setting']['ec_contract']);
+	}
+
+	private function _singleVar(&$var, $k) {
+		return $var[$k] ?? null;
+	}
+
+	private function _arrayVar(&$var, $k) {
+		unset($GLOBALS['_L']['_G']);
+		$value = null;
+		$sVar = &$var;
+		$e = explode('/', $k);
+		$count = count($e);
+		foreach($e as $i => $_k) {
+			if($_k == '*') {
+				foreach($sVar as $_k3 => $_v3) {
+					$nKey = implode('/', array_slice($e, $i + 1));
+					$value[$_k3] = $this->_arrayVar($_v3, $nKey);
+				}
+				break;
+			}
+			$isMulti = str_contains($_k, ',');
+			if(!isset($sVar[$_k]) && !$isMulti) {
+				break;
+			}
+			if($isMulti) {
+				$value = null;
+				foreach(explode(',', $_k) as $_k2) {
+					$value[$_k2] = $this->_singleVar($sVar, $_k2);
+				}
+				break;
+			} else {
+				if($count - 1 == $i) {
+					$value = $this->_singleVar($sVar, $_k);
+				}
+				$sVar = &$sVar[$_k];
+			}
+		}
+		return $value;
+	}
+
+	private function _memory($method, $params = []) {
+		if($this->_m == null) {
+			require_once ROOT_PATH.'./source/class/memory/memory_driver_redis.php';
+			$_config = [];
+			require ROOT_PATH.'./config/config_global.php';
+			$this->_m = new memory_driver_redis();
+			if(empty($_config['memory']['redis'])) {
+				return null;
+			}
+			$this->_m->init($_config['memory']['redis']);
+			if(!$this->_m->enable) {
+				return null;
+			}
+			$this->_config = $_config;
+		}
+		if($this->_m == null) {
+			return null;
+		}
+		if(!method_exists($this->_m, $method)) {
+			return null;
+		}
+		return call_user_func_array([$this->_m, $method], $params);
+	}
+
+	// for Developer
+
+	private function _getApiParam_Developer($api, $ver) {
+		$xml = ROOT_PATH.'./data/discuz_restful.xml';
+		require_once ROOT_PATH.'./source/class/class_xml.php';
+		$data = file_get_contents($xml);
+		$xmldata = xml2array($data);
+		foreach($xmldata['Data']['api'] as $ver => $apis) {
+			if(!preg_match('/^v(\d+)$/', $ver, $r)) {
+				continue;
+			}
+			$this->_parseApis($apis, $ver, '/');
+		}
+		if(empty($_ENV['api'][$ver]['/'.$api[0]])) {
+			$this->error(-109);
+		}
+		return $this->apiParam = $_ENV['api'][$ver]['/'.$api[0]];
+	}
+
+	private function _parseApis($apis, $ver, $uriPre = '/') {
+		if(!is_array($apis)) {
+			return;
+		}
+		foreach($apis as $uri => $api) {
+			$k = $uriPre.$uri;
+			list(, $baseuri) = explode('/', $k);
+			$baseuri = '/'.$baseuri;
+			if(!empty($api['script']) && is_string($api['script'])) {
+				$_ENV['api'][$ver][$baseuri][$k] = [];
+				$_ENV['api'][$ver][$baseuri][$k] = $api;
+			} else {
+				$this->_parseApis($api, $ver, $k.'/');
+			}
+		}
+	}
+
+	public function getAuthSign() {
+		static $authSign = null;
+		if($authSign !== null) {
+			return $authSign;
+		}
+		global $_G;
+
+		$this->_memory('set', [self::AuthPre.$_G['cookie']['sid'], serialize([
+			'auth' => $_G['cookie']['auth'],
+			'saltkey' => $_G['cookie']['saltkey'],
+			'sid' => $_G['cookie']['sid']
+		]), self::AuthTTL]);
+		return $authSign = authcode($_G['cookie']['sid'], 'ENCODE', md5($_G['config']['security']['authkey']), self::AuthTTL);
+	}
+
+	public function decodeAuthSign(&$session) {
+		$sid = authcode($this->postParam['_authSign'], 'DECODE', md5($this->_config['security']['authkey']));
+		if(!$sid) {
+			return;
+		}
+		$data = $this->_memory('get', [self::AuthPre.$sid]);
+		if(!$data) {
+			return;
+		}
+		$cookiepre = $this->_config['cookie']['cookiepre'].substr(md5($this->_config['cookie']['cookiepath'].'|'.$this->_config['cookie']['cookiedomain']), 0, 4).'_';
+		$session[$cookiepre.'auth'] = $data['auth'];
+		$session[$cookiepre.'saltkey'] = $data['saltkey'];
+		$session[$cookiepre.'sid'] = $data['sid'];
+	}
+
+}

+ 174 - 0
api/restful/index.php

@@ -0,0 +1,174 @@
+<?php
+
+const IN_API = true;
+const IN_RESTFUL = true;
+const IN_MOBILE_API = true;
+if(!empty($_POST['debug'])) {
+	define('IN_RESTFUL_DEBUG', true);
+}
+
+require_once 'class_restful.php';
+
+$_ENV['restful'] = new _dzRestful($_POST);
+
+[$api, $ver] = $_ENV['restful']->parseQuery();
+
+if($api[0] == 'token') {
+	$_COOKIE = [];
+
+	require_once '../../source/class/class_core.php';
+
+	$discuz = C::app();
+	$discuz->init_cron = false;
+	$discuz->init_session = false;
+	$discuz->init();
+
+	//检测 appid 的有效性
+	$_ENV['restful']->checkSign();
+
+	//生成 token
+	$token = strtoupper(random(16));
+	$tokenData = $_ENV['restful']->isRefreshToken() ?
+		$_ENV['restful']->refreshTokenData() :
+		$_ENV['restful']->newTokenData();
+	$_ENV['restful']->setToken($token, $tokenData);
+	if($_ENV['restful']->isRefreshToken()) {
+		$_ENV['restful']->delTokenData();
+	}
+	$_ENV['restful']->output([
+		'ret' => 0,
+		'token' => $token,
+		'expires_in' => TIMESTAMP + $_ENV['restful']::TokenTTL,
+	]);
+} elseif($api[0] == 'callback') {
+	$_ENV['authtoken'] = $api[1];
+	$_ENV['returntype'] = !empty($api[2]) ? $api[2] : '';
+	require_once '../../source/class/class_core.php';
+
+	$discuz = C::app();
+	$discuz->init_cron = false;
+	$discuz->init_session = false;
+	$discuz->init();
+
+	if(!$_G['uid']) {
+		$authed = false;
+	} else {
+		$authed = $_ENV['restful']->setAuthToken($_ENV['authtoken'], [$_G['uid'], time()]);
+	}
+	if($authed) {
+		require_once libfile('function/member');
+		clearcookies();
+	}
+	if($_ENV['returntype'] == 'json') {
+		if($authed) {
+			$_ENV['restful']->error(0);
+		} else {
+			$_ENV['restful']->error(-119);
+		}
+	} elseif($_ENV['returntype'] == 'html') {
+		$message = $authed ? lang('core', 'restful_auth_success') : lang('core', 'restful_auth_error');
+		include template('common/header_common');
+		include template('common/restful_auth');
+	} else {
+		if($authed) {
+			echo lang('core', 'restful_auth_success');
+		} else {
+			echo lang('core', 'restful_auth_error');
+		}
+	}
+} elseif($api[0] == 'authtoken') {
+	require_once '../../source/class/class_core.php';
+
+	$discuz = C::app();
+	$discuz->init_cron = false;
+	$discuz->init_session = false;
+	$discuz->init();
+
+	$data = $_ENV['restful']->getAuthToken($_GET['authtoken']);
+	if(!$data) {
+		$_ENV['restful']->error(-119);
+	} else {
+		// token校验
+		$_ENV['restful']->checkToken();
+
+		require_once libfile('function/member');
+		$member = getuserbyuid($data[0]);
+		if(!$member) {
+			$_ENV['restful']->error(-119);
+		}
+		setloginstatus($member, 2592000);
+		$_ENV['restful']->convertOutput(['member/uid' => 'uid']);
+	}
+} elseif($api[0] == 'deltoken') {
+	$_ENV['restful']->delTokenData();
+	$_ENV['restful']->output([
+		'ret' => 0,
+		'token' => '',
+		'data' => [
+			'msg' => 'ok'
+		]
+	]);
+} else {
+	define('IN_RESTFUL_API', true);
+
+	//检测 appid 的有效性
+	$_ENV['restful']->checkSign();
+
+	// token校验
+	$_ENV['restful']->checkToken();
+
+	// 初始化接口参数
+	$_ENV['restful']->initParam($api, $ver);
+	// 接口频率控制
+	$_ENV['restful']->apiFreqCheck();
+	// 接口权限校验
+	$_ENV['restful']->apiPermCheck();
+	// script校验
+	$script = $_ENV['restful']->scriptCheck();
+	// 释放 GPC
+	$_GET = $_ENV['restful']->paramDecode('get');
+	$_POST = $_ENV['restful']->paramDecode('post');
+	$_COOKIE = $_ENV['restful']->sessionDecode();
+	$requestParams = $_ENV['restful']->getRequestParam();
+	if($requestParams) {
+		if(!empty($requestParams['GET']) && is_array($requestParams['GET'])) {
+			foreach($requestParams['GET'] as $k => $v) {
+				$_GET[$k] = $v;
+			}
+		}
+		if(!empty($_GET)) {
+			$_SERVER['QUERY_STRING'] = http_build_query($_GET);
+		}
+		if(!empty($requestParams['POST']) && is_array($requestParams['POST'])) {
+			foreach($requestParams['POST'] as $k => $v) {
+				$_POST[$k] = $v;
+			}
+		}
+		if(!empty($requestParams['COOKIE']) && is_array($requestParams['COOKIE'])) {
+			foreach($requestParams['COOKIE'] as $k => $v) {
+				$_COOKIE[$k] = $v;
+			}
+		}
+	}
+	foreach($_COOKIE as $k => $v) {
+		!empty($v) && setcookie($k, $v);
+	}
+
+	if(!defined('IN_RESTFUL_DEBUG')) {
+		// 准备输出
+		[$shutdownFunc, $output] = $_ENV['restful']->getShutdownFunc();
+		// 运行 script
+		register_shutdown_function([$_ENV['restful'], $shutdownFunc], $output);
+	}
+
+	$empty = [];
+	$_ENV['restful']->plugin('before', $empty);
+
+	try {
+		$_GET['index'] = $script;
+		chdir('../../');
+		require './index.php';
+	} catch (Exception $e) {
+		$_ENV['restful']->error(-100);
+	}
+}

+ 272 - 0
config/config_global_default.php

@@ -0,0 +1,272 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+$_config = array();
+
+// 提示:自当前版本起,本文件不支持调用系统内任何变量或函数,请依赖此行为的站点修正实现 //
+
+// ----------------------------  CONFIG DB  ----------------------------- //
+// ----------------------------  数据库相关设置---------------------------- //
+
+/**
+ * 数据库主服务器设置, 支持多组服务器设置, 当设置多组服务器时, 则会根据分布式策略使用某个服务器
+ * @example
+ *
+ * $_config['db']['driver'] = '';// 空(默认)/mysql/pdo
+ *
+ * $_config['db']['1']['dbhost'] = 'localhost'; // 服务器地址
+ * $_config['db']['1']['dbuser'] = 'root'; // 用户
+ * $_config['db']['1']['dbpw'] = 'root';// 密码
+ * $_config['db']['1']['dbcharset'] = 'gbk';// 字符集
+ * $_config['db']['1']['pconnect'] = '0';// 是否持续连接
+ * $_config['db']['1']['dbname'] = 'x1';// 数据库
+ * $_config['db']['1']['tablepre'] = 'pre_';// 表名前缀
+ * $_config['db']['1']['dsn'] = 'mysql:host=localhost;dbname=x1';// DSN配置(PDO)
+ *
+ * $_config['db']['2']['dbhost'] = 'localhost';
+ * ...
+ *
+ */
+$_config['db'][1]['dbhost']  		= '127.0.0.1';
+$_config['db'][1]['dbuser']  		= 'root';
+$_config['db'][1]['dbpw'] 	 	= '';
+$_config['db'][1]['dbcharset'] 		= 'utf8mb4';
+$_config['db'][1]['pconnect'] 		= 0;
+$_config['db'][1]['dbname']  		= 'ultrax';
+$_config['db'][1]['tablepre'] 		= 'pre_';
+
+/**
+ * 数据库从服务器设置( slave, 只读 ), 支持多组服务器设置, 当设置多组服务器时, 系统根据每次随机使用
+ * @example
+ * $_config['db']['1']['slave']['1']['dbhost'] = 'localhost';
+ * $_config['db']['1']['slave']['1']['dbuser'] = 'root';
+ * $_config['db']['1']['slave']['1']['dbpw'] = 'root';
+ * $_config['db']['1']['slave']['1']['dbcharset'] = 'gbk';
+ * $_config['db']['1']['slave']['1']['pconnect'] = '0';
+ * $_config['db']['1']['slave']['1']['dbname'] = 'x1';
+ * $_config['db']['1']['slave']['1']['tablepre'] = 'pre_';
+ * $_config['db']['1']['slave']['1']['weight'] = '0'; //权重:数据越大权重越高
+ *
+ * $_config['db']['1']['slave']['2']['dbhost'] = 'localhost';
+ * ...
+ *
+ */
+$_config['db']['1']['slave'] = array();
+
+//启用从服务器的开关
+$_config['db']['slave'] = false;
+/**
+ * 数据库 分布部署策略设置
+ *
+ * @example 将 common_member 部署到第二服务器, common_session 部署在第三服务器, 则设置为
+ * $_config['db']['map']['common_member'] = 2;
+ * $_config['db']['map']['common_session'] = 3;
+ *
+ * 对于没有明确声明服务器的表, 则一律默认部署在第一服务器上
+ *
+ */
+$_config['db']['map'] = array();
+
+/**
+ * 数据库 公共设置, 此类设置通常对针对每个部署的服务器
+ */
+$_config['db']['common'] = array();
+
+/**
+ *  禁用从数据库的数据表, 表名字之间使用逗号分割
+ *
+ * @example common_session, common_member 这两个表仅从主服务器读写, 不使用从服务器
+ * $_config['db']['common']['slave_except_table'] = 'common_session, common_member';
+ *
+ */
+$_config['db']['common']['slave_except_table'] = '';
+
+/*
+ * 数据库引擎,根据自己的数据库引擎进行设置,X3.5之后默认为innodb,之前为myisam
+ * 对于从X3.4升级到X3.5,并且没有转换数据库引擎的用户,在此设置为myisam
+ */
+$_config['db']['common']['engine'] = 'innodb';
+
+// 多服务器配置
+$_config['servers']['count']                    = 2;                    // 服务器数量
+$_config['servers']['ip']                       = '127.0.0.1';          // 当前服务器 IP(留空默认从 PHP 获取)
+
+/**
+ * 内存服务器优化设置
+ * 以下设置需要PHP扩展组件支持,其中 memcache 优先于其他设置,
+ * 当 memcache 无法启用时,会自动开启另外的两种优化模式
+ */
+
+//内存变量前缀, 可更改,避免同服务器中的程序引用错乱
+$_config['memory']['prefix'] = 'discuz_';
+
+/* Redis设置, 需要PHP扩展组件支持, timeout参数的作用没有查证 */
+$_config['memory']['redis']['server']           = '';
+$_config['memory']['redis']['port']             = 6379;
+$_config['memory']['redis']['pconnect']         = 1;
+$_config['memory']['redis']['timeout']          = 0;
+$_config['memory']['redis']['requirepass']      = '';
+$_config['memory']['redis']['db']               = 0;			//这里可以填写0到15的数字,每个站点使用不同的db
+
+$_config['memory']['memcache']['server']        = '';			// memcache 服务器地址
+$_config['memory']['memcache']['port']          = 11211;		// memcache 服务器端口
+$_config['memory']['memcache']['pconnect']      = 1;			// memcache 是否长久连接
+$_config['memory']['memcache']['timeout']       = 1;			// memcache 服务器连接超时
+
+$_config['memory']['memcached']['server']       = '';			// memcached 服务器地址
+$_config['memory']['memcached']['port']         = 11211;		// memcached 服务器端口
+
+$_config['memory']['apc']                       = 0;			// 启动对 APC 的支持
+$_config['memory']['apcu']                      = 0;			// 启动对 APCu 的支持
+$_config['memory']['xcache']                    = 0;			// 启动对 xcache 的支持
+$_config['memory']['eaccelerator']              = 0;			// 启动对 eaccelerator 的支持
+$_config['memory']['wincache']                  = 0;			// 启动对 wincache 的支持
+$_config['memory']['yac']                       = 0;     		//启动对 YAC 的支持
+$_config['memory']['file']['server']            = '';			// File 缓存存放目录,如设置为 data/cache/filecache ,设置后启动 File 缓存
+
+// 附件下载相关
+//
+// 本地文件读取模式; 模式2为最节省内存方式,但不支持多线程下载
+// 如需附件URL地址、媒体附件播放,需选择支持Range参数的读取模式1或4,其他模式会导致部分浏览器下视频播放异常
+// 1=fread 2=readfile 3=fpassthru 4=fpassthru+multiple
+$_config['download']['readmod'] = 2;
+
+// 是否启用 X-Sendfile 功能(需要服务器支持)0=close 1=nginx 2=lighttpd 3=apache
+$_config['download']['xsendfile']['type'] = 0;
+
+// 启用 nginx X-sendfile 时,论坛附件目录的虚拟映射路径,请使用 / 结尾
+$_config['download']['xsendfile']['dir'] = '/down/';
+
+// 页面输出设置
+$_config['output']['charset'] 			= 'utf-8';	// 页面字符集
+$_config['output']['forceheader']		= 1;		// 强制输出页面字符集,用于避免某些环境乱码
+$_config['output']['gzip'] 			= 0;		// 是否采用 Gzip 压缩输出
+$_config['output']['tplrefresh'] 		= 1;		// 模板自动刷新开关 0=关闭, 1=打开
+$_config['output']['language'] 			= 'zh_cn';	// 页面语言 zh_cn/zh_tw
+$_config['output']['staticurl'] 		= 'static/';	// 站点静态文件路径,“/”结尾
+$_config['output']['ajaxvalidate']		= 0;		// 是否严格验证 Ajax 页面的真实性 0=关闭,1=打开
+$_config['output']['upgradeinsecure']		= 0;		// 在HTTPS环境下请求浏览器升级HTTP内链到HTTPS,此选项影响外域资源链接且与自定义CSP冲突 0=关闭(默认),1=打开
+$_config['output']['css4legacyie']		= 1;		// 是否加载兼容低版本IE的css文件 0=关闭,1=打开(默认),关闭可避免现代浏览器加载不必要的数据,但IE6-8的显示效果会受较大影响,IE9受较小影响。
+$_config['output']['forcehttps']		= 0;            // 是否强制HTTPS访问 0=关闭,1=打开
+
+// COOKIE 设置
+$_config['cookie']['cookiepre'] 		= 'discuz_'; 	// COOKIE前缀
+$_config['cookie']['cookiedomain'] 		= ''; 		// COOKIE作用域
+$_config['cookie']['cookiepath'] 		= '/'; 		// COOKIE作用路径
+
+// 站点安全设置
+$_config['security']['authkey']			= 'abcdefg';	// 站点加密密钥
+$_config['security']['urlxssdefend']		= true;		// 自身 URL XSS 防御
+$_config['security']['attackevasive']		= 0;		// CC 攻击防御 1|2|4|8
+$_config['security']['onlyremoteaddr']		= 1;		// 用户IP地址获取方式 0=信任HTTP_CLIENT_IP、HTTP_X_FORWARDED_FOR(默认) 1=只信任 REMOTE_ADDR(推荐)
+								// 考虑到防止IP撞库攻击、IP限制策略失效的风险,建议您设置为1。使用CDN的用户可以配置ipgetter选项
+								// 安全提示:由于UCenter、UC_Client独立性原因,您需要单独在两个应用内定义常量,从而开启功能
+
+$_config['security']['useipban']		= 1;		// 是否开启允许/禁止IP功能,高负载站点可以将此功能疏解至HTTP Server/CDN/SLB/WAF上,降低服务器压力
+$_config['security']['querysafe']['status']	= 1;		// 是否开启SQL安全检测,可自动预防SQL注入攻击
+$_config['security']['querysafe']['dfunction']	= array('load_file','hex','substring','if','ord','char');
+$_config['security']['querysafe']['daction']	= array('@','intooutfile','intodumpfile','unionselect','(select', 'unionall', 'uniondistinct');
+$_config['security']['querysafe']['dnote']	= array('/*','*/','#','--','"');
+$_config['security']['querysafe']['dlikehex']	= 1;
+$_config['security']['querysafe']['afullnote']	= 0;
+
+$_config['security']['creditsafe']['second'] 	= 0;		// 开启用户积分信息安全,可防止并发刷分,满足 times(次数)/second(秒) 的操作无法提交
+$_config['security']['creditsafe']['times'] 	= 10;
+
+$_config['security']['fsockopensafe']['status']	        = 1;                            // 是否开启fsockopen安全检测
+$_config['security']['fsockopensafe']['port']	        = array(80, 443);	        //fsockopen 有效的端口
+$_config['security']['fsockopensafe']['ipversion']	= array('ipv6', 'ipv4');	//fsockopen 有效的IP协议
+$_config['security']['fsockopensafe']['verifypeer']	= false;	                // fsockopen是否验证证书有效性,开启可提升安全性,但需自行解决证书配置问题
+$_config['security']['fsockopensafe']['allow_host'][0]  = '****.com';                   // 域名白名单
+
+$_config['security']['error']['showerror']      = '1';	//是否在数据库或系统严重异常时显示错误详细信息,0=不显示(更安全),1=显示详细信息(默认),2=只显示错误本身
+$_config['security']['error']['guessplugin']    = '1';	//是否在数据库或系统严重异常时猜测可能报错的插件,0=不猜测,1=猜测(默认)
+
+// 管理中心设置
+$_config['admincp']['founder']			= '1';		// 站点创始人:拥有站点管理后台的最高权限,每个站点可以设置 1名或多名创始人
+								// 可以使用uid,也可以使用用户名;多个创始人之间请使用逗号“,”分开;
+$_config['admincp']['forcesecques']		= 0;		// 管理人员必须设置安全提问才能进入系统设置 0=否, 1=是[安全]
+$_config['admincp']['checkip']			= 1;		// 后台管理操作是否验证管理员的 IP, 1=是[安全], 0=否。仅在管理员无法登陆后台时设置 0。
+$_config['admincp']['runquery']			= 0;		// 是否允许后台运行 SQL 语句 1=是 0=否[安全]
+$_config['admincp']['dbimport']			= 1;		// 是否允许后台恢复论坛数据  1=是 0=否[安全]
+$_config['admincp']['mustlogin']		= 1;		// 是否必须前台登录后才允许后台登录  1=是[安全] 0=否
+
+$_config['admincp']['validate']['method'] = 'default';          // 后台二次校验模式,“/admin.php” 文件删除后有效,
+								// default=系统默认方式,需要补充下放的 user 和 pass
+								// 其他值:可通过 childfile 的 global/adminvalidate/[name] 脚本接管二次校验
+$_config['admincp']['validate']['user'] = '';                   // method=system 时设置 Authenticate 的用户名
+$_config['admincp']['validate']['pass'] = '';                   // method=system 时设置 Authenticate 的密码
+
+/**
+ * 系统远程调用功能模块
+ */
+
+// 远程调用: 总开关 0=关  1=开
+$_config['remote']['on'] = 0;
+
+// 远程调用: 程序目录名. 出于安全考虑,您可以更改这个目录名, 修改完毕, 请手工修改程序的实际目录
+$_config['remote']['dir'] = 'remote';
+
+// 远程调用: 通信密钥. 用于客户端和本服务端的通信加密. 长度不少于 32 位
+//          默认值是 $_config['security']['authkey']	的 md5, 您也可以手工指定
+$_config['remote']['appkey'] = md5($_config['security']['authkey']);
+
+// 远程调用: 开启外部 cron 任务. 系统内部不再执行cron, cron任务由外部程序激活
+$_config['remote']['cron'] = 0;
+
+/**
+ * IP数据库扩展
+ * $_config['ipdb']下除setting外均可用作自定义扩展IP库设置选项,也欢迎大家PR自己的扩展IP库。
+ * 扩展IP库的设置,请使用格式:
+ * 		$_config['ipdb']['扩展ip库名称']['设置项名称'] = '值';
+ * 比如:
+ * 		$_config['ipdb']['redis_ip']['server'] = '172.16.1.8';
+ */
+$_config['ipdb']['setting']['fullstack']        = '';	        // 系统使用的全栈IP库,优先级最高
+$_config['ipdb']['setting']['default']          = '';	        // 系统使用的默认IP库,优先级最低
+$_config['ipdb']['setting']['ipv4']             = 'tiny';	// 系统使用的默认IPv4库,留空为使用默认库
+$_config['ipdb']['setting']['ipv6']             = 'v6wry';      // 系统使用的默认IPv6库,留空为使用默认库
+
+/**
+ * IP获取扩展
+ * 考虑到不同的CDN服务供应商提供的判断CDN源IP的策略不同,您可以定义自己服务供应商的IP获取扩展。
+ * 为空为使用默认体系,非空情况下会自动调用source/class/ip/getter_值.php内的get方法获取IP地址。
+ * 系统提供dnslist(IP反解析域名白名单)、serverlist(IP地址白名单,支持CIDR)、header扩展,具体请参考扩展文件。
+ * 性能提示:自带的两款工具由于依赖RDNS、CIDR判定等操作,对系统效率有较大影响,建议大流量站点使用HTTP Server
+ * 或CDN/SLB/WAF上的IP黑白名单等逻辑实现CDN IP地址白名单,随后使用header扩展指定服务商提供的IP头的方式实现。
+ * 安全提示:由于UCenter、UC_Client独立性及扩展性原因,您需要单独修改相关文件的相关业务逻辑,从而实现此类功能。
+ * $_config['ipgetter']下除setting外均可用作自定义IP获取模型设置选项,也欢迎大家PR自己的扩展IP获取模型。
+ * 扩展IP获取模型的设置,请使用格式:
+ * 		$_config['ipgetter']['IP获取扩展名称']['设置项名称'] = '值';
+ * 比如:
+ * 		$_config['ipgetter']['onlinechk']['server'] = '100.64.10.24';
+ */
+$_config['ipgetter']['setting'] = 'header';
+$_config['ipgetter']['header']['header'] = 'HTTP_X_FORWARDED_FOR';
+$_config['ipgetter']['iplist']['header'] = 'HTTP_X_FORWARDED_FOR';
+$_config['ipgetter']['iplist']['list']['0'] = '127.0.0.1';
+$_config['ipgetter']['dnslist']['header'] = 'HTTP_X_FORWARDED_FOR';
+$_config['ipgetter']['dnslist']['list']['0'] = 'comsenz.com';
+
+/**
+ * WitFrame 云插件开发者调试设置
+ * 请填写 WitFrame SDK 的地址,结尾不加 "/"
+ * 开发者使用,请自行修改 WitSDK 的 /conf/config.ini 文件,补充 RESTful API 的应用参数:
+ * [discuz]
+ * website = 'http://yourwebsite';
+ * appid = '9xxxxxxx';
+ * secret = 'xxxxxxxxxx';
+ */
+//$_config['witframe']['sdkurl'] = 'http://127.0.0.1/WitSdk-Dev'
+
+/**
+ * 体验功能开关
+ */
+$_config['experience']['editor_json'] = false;
+
+?>

+ 35 - 0
config/config_ucenter_default.php

@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+// ============================================================================
+define('UC_CONNECT', 'mysql');				// 连接 UCenter 的方式: mysql/NULL, 默认为空时为 fscoketopen(), mysql 是直接连接的数据库, 为了效率, 建议采用 mysql
+define('UC_STANDALONE', 1);				// 独立模式开关,0=关闭, 1=打开,开启后将不再依赖UCenter Server。注意:开启时必须将 UC_CONNECT 改为 mysql !
+// 数据库相关 (mysql 连接时)
+define('UC_DBHOST', 'localhost');			// UCenter 数据库主机
+define('UC_DBUSER', 'root');				// UCenter 数据库用户名
+define('UC_DBPW', 'root');				// UCenter 数据库密码
+define('UC_DBNAME', 'ucenter');				// UCenter 数据库名称
+define('UC_DBCHARSET', 'utf8mb4');				// UCenter 数据库字符集
+define('UC_DBTABLEPRE', '`ucenter`.uc_');		// UCenter 数据库表前缀
+define('UC_DBCONNECT', '0');				// UCenter 数据库持久连接 0=关闭, 1=打开
+// 头像相关
+define('UC_AVTURL', '');		// 头像服务的基础路径,为空则为默认值,可以设置为独立域名/路径(结尾不能有/),配合CDN使用更佳。如涉及 avatar.php 需在其中再配置一次。
+define('UC_AVTPATH', '');		// 头像存储路径,为空则为默认值,仅限独立模式使用,建议保持默认。
+
+// 通信相关
+define('UC_KEY', 'yeN3g9EbNfiaYfodV63dI1j8Fbk5HaL7W4yaW4y7u2j4Mf45mfg2v899g451k576');	// 与 UCenter 的通信密钥, 要与 UCenter 保持一致
+define('UC_API', 'http://localhost/ucenter/branches/1.5.0/server'); // UCenter 的 URL 地址, 在调用头像时依赖此常量
+define('UC_CHARSET', 'utf-8');				// UCenter 的字符集
+define('UC_IP', '127.0.0.1');				// UCenter 的 IP, 当 UC_CONNECT 为非 mysql 方式时, 并且当前应用服务器解析域名有问题时, 请设置此值
+define('UC_APPID', '1');				// 当前应用的 ID
+
+// ============================================================================
+
+define('UC_PPP', '20');
+
+?>

+ 1 - 0
config/index.htm

@@ -0,0 +1 @@
+ 

+ 17 - 0
index.php

@@ -0,0 +1,17 @@
+<?php
+
+if(!empty($_GET['app'])) {
+	$_app = $_GET['app'];
+} else {
+	$_app = 'index';
+}
+
+define('DISCUZ_APP', $_app);
+
+if(!preg_match('/^\w+$/', $_app)) {
+	exit('Access Denied');
+}
+
+$f = './source/app/'.$_app.'/'.$_app.'.php';
+
+require $f;

+ 0 - 0
source/app/index.htm


+ 12 - 0
source/app/index/index.php

@@ -0,0 +1,12 @@
+<?php
+
+require './source/class/class_core.php';
+
+$discuz = C::app();
+$discuz->cachelist = [];
+$discuz->init();
+
+//新数据库类调用方式
+$userinfo = index\table_test1::t()->fetch_all_by_uid($_G['member']['uid']);
+
+require_once appfile('yyy/zzz');

BIN
source/app/index/static/image/icon.png


+ 0 - 0
source/app/index/table/index.htm


+ 37 - 0
source/app/index/table/table_test1.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace index;
+
+use discuz_table;
+use DB;
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class table_test1 extends discuz_table
+{
+
+	public static function t() {
+		static $_instance;
+		if(!isset($_instance)) {
+			$_instance = new self();
+		}
+		return $_instance;
+	}
+
+	public function __construct() {
+
+		$this->_table = 'sample_test1';
+		$this->_pk    = '';
+
+		parent::__construct();
+	}
+
+	public function fetch_all_by_uid($uid) {
+		return ['uid' => $uid, 'name' => 'test'];
+		//return DB::fetch_all('SELECT * FROM %t WHERE uid=%d', [$this->_table, $uid]);
+	}
+
+}
+

+ 7 - 0
source/app/index/template/sample.php

@@ -0,0 +1,7 @@
+<?php exit; ?>
+
+Sample on $dt
+
+<p>user: $userinfo['name']</p>
+
+<a href="?app=sample&notice=yes">sameple_test</a>

+ 5 - 0
source/app/index/yyy/zzz.php

@@ -0,0 +1,5 @@
+<?php
+
+$dt = date('Y-m-d H:i:s');
+
+require_once template('sample', 0, 'source/app/index/template');

+ 262 - 0
source/class/class_core.php

@@ -0,0 +1,262 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+error_reporting(E_ALL);
+
+const IN_DISCUZ = true;
+define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -12));
+const DISCUZ_DATA = DISCUZ_ROOT.'data/';
+const DISCUZ_ROOT_STATIC = DISCUZ_ROOT;
+const DISCUZ_CORE_DEBUG = false;
+const DISCUZ_TABLE_EXTENDABLE = false;
+
+set_exception_handler(['core', 'handleException']);
+
+if(DISCUZ_CORE_DEBUG) {
+	set_error_handler(['core', 'handleError']);
+	register_shutdown_function(['core', 'handleShutdown']);
+}
+
+spl_autoload_register(['core', 'autoload']);
+
+C::creatapp();
+
+class core {
+	private static $_tables;
+	private static $_imports;
+	private static $_app;
+	private static $_memory;
+
+	public static function app() {
+		return self::$_app;
+	}
+
+	public static function creatapp() {
+		if(!is_object(self::$_app)) {
+			self::$_app = discuz_application::instance();
+		}
+		return self::$_app;
+	}
+
+	public static function t($name) {
+		return self::_make_obj($name, 'table', DISCUZ_TABLE_EXTENDABLE);
+	}
+
+	public static function m($name) {
+		$args = [];
+		if(func_num_args() > 1) {
+			$args = func_get_args();
+			unset($args[0]);
+		}
+		return self::_make_obj($name, 'model', true, $args);
+	}
+
+	protected static function _make_obj($name, $type, $extendable = false, $p = []) {
+		$pluginid = null;
+		if($name[0] === '#') {
+			[, $pluginid, $name] = explode('#', $name);
+		}
+		$cname = $type.'_'.$name;
+		if(!isset(self::$_tables[$cname])) {
+			if(!class_exists($cname, false)) {
+				if($pluginid) {
+					self::importPlugin($pluginid, $type, $name);
+				} else {
+					self::import('class/'.$type.'/'.$name);
+				}
+			}
+			if($extendable) {
+				self::$_tables[$cname] = new discuz_container();
+				switch(count($p)) {
+					case 0:
+						self::$_tables[$cname]->obj = new $cname();
+						break;
+					case 1:
+						self::$_tables[$cname]->obj = new $cname($p[1]);
+						break;
+					case 2:
+						self::$_tables[$cname]->obj = new $cname($p[1], $p[2]);
+						break;
+					case 3:
+						self::$_tables[$cname]->obj = new $cname($p[1], $p[2], $p[3]);
+						break;
+					case 4:
+						self::$_tables[$cname]->obj = new $cname($p[1], $p[2], $p[3], $p[4]);
+						break;
+					case 5:
+						self::$_tables[$cname]->obj = new $cname($p[1], $p[2], $p[3], $p[4], $p[5]);
+						break;
+					default:
+						$ref = new ReflectionClass($cname);
+						self::$_tables[$cname]->obj = $ref->newInstanceArgs($p);
+						unset($ref);
+						break;
+				}
+			} else {
+				self::$_tables[$cname] = new $cname();
+			}
+		}
+		return self::$_tables[$cname];
+	}
+
+	public static function memory() {
+		if(!self::$_memory) {
+			self::$_memory = new discuz_memory();
+			self::$_memory->init(self::app()->config['memory']);
+		}
+		return self::$_memory;
+	}
+
+	public static function importPlugin($pluginid, $type, $name) {
+		$key = $pluginid.':'.$type.$name;
+		if(!isset(self::$_imports[$key])) {
+			$path = DISCUZ_PLUGIN($pluginid).'/';
+			$pre = basename($type);
+			$filename = $type.'/'.$pre.'_'.basename($name).'.php';
+			if(is_file($path.'/'.$filename)) {
+				include $path.'/'.$filename;
+				self::$_imports[$key] = true;
+
+				return true;
+			} else {
+				throw new Exception('Oops! System file lost: '.$filename);
+			}
+		}
+		return true;
+	}
+
+	public static function import($name, $folder = '', $force = true) {
+		$key = $folder.$name;
+		if(!isset(self::$_imports[$key])) {
+			$path = DISCUZ_ROOT.'/source/'.$folder;
+			if(str_contains($name, '/')) {
+				$pre = basename(dirname($name));
+				$filename = dirname($name).'/'.$pre.'_'.basename($name).'.php';
+			} else {
+				$filename = $name.'.php';
+			}
+
+			if(is_file($path.'/'.$filename)) {
+				include $path.'/'.$filename;
+				self::$_imports[$key] = true;
+
+				return true;
+			} elseif(!$force) {
+				return false;
+			} else {
+				throw new Exception('Oops! System file lost: '.$filename);
+			}
+		}
+		return true;
+	}
+
+	public static function handleException($exception) {
+		discuz_error::exception_error($exception);
+	}
+
+
+	public static function handleError($errno, $errstr, $errfile, $errline) {
+		if($errno & DISCUZ_CORE_DEBUG) {
+			discuz_error::system_error($errstr, false, true, false);
+		}
+	}
+
+	public static function handleShutdown() {
+		if(($error = error_get_last()) && $error['type'] & DISCUZ_CORE_DEBUG) {
+			discuz_error::system_error($error['message'], false, true, false);
+		}
+	}
+
+	public static function autoload($class) {
+		$pathfile = null;
+		$class = strtolower($class);
+		if(str_contains($class, '_')) {
+			$_p = strpos($class, '\\');
+			if($_p === false) {
+				[$folder] = explode('_', $class);
+				$file = 'class/'.$folder.'/'.substr($class, strlen($folder) + 1);
+			} else {
+				$class = str_replace('\\', '/', $class);
+				$_rp = strrpos($class, '/');
+				$plugin = substr($class, 0, $_rp);
+				$class = substr($class, $_rp + 1);
+				[$folder] = explode('_', $class);
+				$pathfile = DISCUZ_APP($plugin).'/'.$folder.'/'.$class.'.php';
+				if(!file_exists($pathfile)) {
+					return false;
+				}
+			}
+		} else {
+			$file = 'class/'.$class;
+		}
+
+		try {
+
+			if($pathfile) {
+				include $pathfile;
+			} else {
+				self::import($file);
+			}
+			return true;
+
+		} catch (Exception $exc) {
+
+			$trace = $exc->getTrace();
+			foreach($trace as $log) {
+				if(empty($log['class']) && $log['function'] == 'class_exists') {
+					return false;
+				}
+			}
+			discuz_error::exception_error($exc);
+		}
+	}
+
+	public static function analysisStart($name) {
+		$key = 'other';
+		if($name[0] === '#') {
+			[, $key, $name] = explode('#', $name);
+		}
+		if(!isset($_ENV['analysis'])) {
+			$_ENV['analysis'] = [];
+		}
+		if(!isset($_ENV['analysis'][$key])) {
+			$_ENV['analysis'][$key] = [];
+			$_ENV['analysis'][$key]['sum'] = 0;
+		}
+		$_ENV['analysis'][$key][$name]['start'] = microtime(TRUE);
+		$_ENV['analysis'][$key][$name]['start_memory_get_usage'] = memory_get_usage();
+		$_ENV['analysis'][$key][$name]['start_memory_get_real_usage'] = memory_get_usage(true);
+		$_ENV['analysis'][$key][$name]['start_memory_get_peak_usage'] = memory_get_peak_usage();
+		$_ENV['analysis'][$key][$name]['start_memory_get_peak_real_usage'] = memory_get_peak_usage(true);
+	}
+
+	public static function analysisStop($name) {
+		$key = 'other';
+		if($name[0] === '#') {
+			[, $key, $name] = explode('#', $name);
+		}
+		if(isset($_ENV['analysis'][$key][$name]['start'])) {
+			$diff = round((microtime(TRUE) - $_ENV['analysis'][$key][$name]['start']) * 1000, 5);
+			$_ENV['analysis'][$key][$name]['time'] = $diff;
+			$_ENV['analysis'][$key]['sum'] = $_ENV['analysis'][$key]['sum'] + $diff;
+			unset($_ENV['analysis'][$key][$name]['start']);
+			$_ENV['analysis'][$key][$name]['stop_memory_get_usage'] = memory_get_usage();
+			$_ENV['analysis'][$key][$name]['stop_memory_get_real_usage'] = memory_get_usage(true);
+			$_ENV['analysis'][$key][$name]['stop_memory_get_peak_usage'] = memory_get_peak_usage();
+			$_ENV['analysis'][$key][$name]['stop_memory_get_peak_real_usage'] = memory_get_peak_usage(true);
+		}
+		return $_ENV['analysis'][$key][$name];
+	}
+}
+
+class C extends core {
+}
+
+class DB extends discuz_database {
+}
+

+ 398 - 0
source/class/class_template.php

@@ -0,0 +1,398 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if (!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class template {
+
+	var $subtemplates = [];
+	var $csscurmodules = '';
+	var $replacecode = ['search' => [], 'replace' => []];
+	var $blocks = [];
+	var $language = [];
+	var $file = '';
+	var $filetype = 'htm';
+	var $debug = 0;
+	var $cellFuncs = [];
+
+	function parse_template($tplfile, $templateid = 1, $tpldir = '', $file = '', $cachefile = '', $postparse = null) {
+		$f = $tplfile;
+		$basefile = basename($f, '.' . $this->filetype);
+		$file == 'common/header' && defined('CURMODULE') && CURMODULE && $file = 'common/header_' . CURMODULE;
+		$this->file = $file;
+
+		if (tplfile::file_exists($tplfile)) {
+			$template = tplfile::file_get_contents($tplfile);
+		} elseif (tplfile::file_exists($filename = substr($tplfile, 0, -(strlen($this->filetype) + 1)) . '.php')) {
+			$template = tplfile::file_get_contents($filename);
+			$template = tplfile::getphptemplate($template);
+		} else {
+			$tpl = $tpldir . '/' . $file . '.' . $this->filetype;
+			$tplfile = $tplfile != $tpl ? $tpl . ', ' . $tplfile : $tplfile;
+			$this->error('template_notfound', $tplfile);
+		}
+
+		if ($this->debug) {
+			$template = $this->insertdebugmsg($template, $tplfile);
+		}
+
+		$var_regexp = "((\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\-\>)?[a-zA-Z0-9_\x7f-\xff]*)(\[[a-zA-Z0-9_\-\.\"\'\[\]\$\x7f-\xff]+\])*)";
+		$const_regexp = "([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)";
+
+		$headerexists = preg_match('/{(sub)?template\s+[\w:\/]+?header\}/', $template);
+		$this->subtemplates = [];
+		for ($i = 1; $i <= 3; $i++) {
+			if (strexists($template, '{subtemplate')) {
+				$template = preg_replace_callback("/[\n\r\t]*(\<\!\-\-)?\{subtemplate\s+([a-z0-9_:\/]+)\}(\-\-\>)?[\n\r\t]*/is", [$this, 'parse_template_callback_loadsubtemplate_2'], $template);
+			}
+		}
+
+		$template = preg_replace("/([\n\r]+)\t+/s", "\\1", $template);
+		$template = preg_replace('/\/\*\*\{(.+?)\}\*\//s', "{\\1}", $template);
+		$template = preg_replace('/\<\!\-\-\{(.+?)\}\-\-\>/s', "{\\1}", $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{block\/(\d+?)\}[\n\r\t]*/i", [$this, 'parse_template_callback_blocktags_1'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{blockdata\/(\d+?)\}[\n\r\t]*/i", [$this, 'parse_template_callback_blockdatatags_1'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{ad\/(.+?)\}[\n\r\t]*/i", [$this, 'parse_template_callback_adtags_1'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{ad\s+([a-zA-Z0-9_\[\]]+)\/(.+?)\}[\n\r\t]*/i", [$this, 'parse_template_callback_adtags_21'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{date\((.+?)\)\}[\n\r\t]*/i", [$this, 'parse_template_callback_datetags_1'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{avatar\((.+?)\)\}[\n\r\t]*/i", [$this, 'parse_template_callback_avatartags_1'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{eval\}\s*(\<\!\-\-)*(.+?)(\-\-\>)*\s*\{\/eval\}[\n\r\t]*/is", [$this, 'parse_template_callback_evaltags_2'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{eval\s+(.+?)\s*\}[\n\r\t]*/is", [$this, 'parse_template_callback_evaltags_1'], $template);
+		$template = str_replace('{LF}', "<?=\"\\n\"?>", $template);
+		$template = preg_replace("/\{(\\\$[a-zA-Z0-9_\-\>\[\]\'\"\$\.\x7f-\xff]+)\s(or|\?\?)\s([a-zA-Z0-9\']+)\}/s", "{echo \\1 ?? \\3}", $template);
+		$template = preg_replace("/\{(\\\$[a-zA-Z0-9_\-\>\[\]\'\"\$\.\x7f-\xff]+)\}/s", "<?=\\1?>", $template);
+		$template = preg_replace_callback('/\{hook\/(\w+?)(\s+(.+?))?\}/i', [$this, 'parse_template_callback_hooktags_13'], $template);
+		$template = preg_replace_callback("/$var_regexp/s", [$this, 'parse_template_callback_addquote_1'], $template);
+		$template = preg_replace_callback("/\<\?\=\<\?\=$var_regexp\?\>\?\>/s", [$this, 'parse_template_callback_addquote_1'], $template);
+		$headeradd = $headerexists ? "hookscriptoutput('$basefile');" : '';
+		if (!empty($this->subtemplates)) {
+			$headeradd .= "\n0\n";
+			foreach ($this->subtemplates as $fname) {
+				$headeradd .= "|| checktplrefresh('$tplfile', '$fname', " . time() . ", '$templateid', '$cachefile', '$tpldir', '$file')\n";
+			}
+			$headeradd .= ';';
+		}
+
+		if (!empty($this->blocks)) {
+			$headeradd .= "\n";
+			$headeradd .= "block_get('" . implode(',', $this->blocks) . "');";
+		}
+
+		if (!empty($this->cellFuncs)) {
+			$headeradd .= "\n";
+			$headeradd .= implode('', $this->cellFuncs) . ';';
+		}
+
+		if ($headerexists) {
+			$headeradd .= "if(defined('IN_RESTFUL')) {\$GLOBALS['_L'] = get_defined_vars();exit;}";
+		}
+
+		if ($cachefile) {
+			$template = "<? if(!defined('IN_DISCUZ')) exit('Access Denied'); {$headeradd}?>\n$template";
+		}
+
+		$template = preg_replace_callback("/[\n\r\t]*\{template\s+([a-z0-9_:\/]+)\}[\n\r\t]*/is", [$this, 'parse_template_callback_stripvtags_template1'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{template\s+(.+?)\}[\n\r\t]*/is", [$this, 'parse_template_callback_stripvtags_template1'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{echo\s+(.+?)\}[\n\r\t]*/is", [$this, 'parse_template_callback_stripvtags_echo1'], $template);
+
+		$template = preg_replace_callback("/([\n\r\t]*)\{if\s+(.+?)\}([\n\r\t]*)/is", [$this, 'parse_template_callback_stripvtags_if123'], $template);
+		$template = preg_replace_callback("/([\n\r\t]*)\{elseif\s+(.+?)\}([\n\r\t]*)/is", [$this, 'parse_template_callback_stripvtags_elseif123'], $template);
+		$template = preg_replace('/\{else\}/i', '<? } else { ?>', $template);
+		$template = preg_replace('/\{\/if\}/i', '<? } ?>', $template);
+
+		$template = preg_replace_callback("/[\n\r\t]*\{loop\s+(\S+)\s+(\S+)\}[\n\r\t]*/is", [$this, 'parse_template_callback_stripvtags_loop12'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}[\n\r\t]*/is", [$this, 'parse_template_callback_stripvtags_loop123'], $template);
+		$template = preg_replace('/\{\/loop\}/i', '<? } ?>', $template);
+
+		$template = preg_replace("/\{$const_regexp\}/s", "<?=\\1?>", $template);
+		if (!empty($this->replacecode)) {
+			$template = str_replace($this->replacecode['search'], $this->replacecode['replace'], $template);
+		}
+		$template = preg_replace("/ \?\>[\n\r]*\<\? /s", ' ', $template);
+
+		if ($cachefile && !@$fp = fopen(DISCUZ_DATA . $cachefile, 'c')) {
+			$this->error('directory_notfound', dirname(DISCUZ_DATA . $cachefile));
+		}
+
+		$template = preg_replace_callback("/\"(http)?[\w\.\/:]+\?[^\"]+?&[^\"]+?\"/", [$this, 'parse_template_callback_transamp_0'], $template);
+		$template = preg_replace_callback("/\<script[^\>]*?src=\"(.+?)\"(.*?)\>\s*\<\/script\>/is", [$this, 'parse_template_callback_stripscriptamp_12'], $template);
+		$template = preg_replace_callback("/[\n\r\t]*\{block\s+([a-zA-Z0-9_\[\]']+)\}(.+?)\{\/block\}/is", [$this, 'parse_template_callback_stripblock_12'], $template);
+		$template = preg_replace('/\<\?(\s{1})/is', "<?php\\1", $template);
+		$template = preg_replace('/\<\?\=(.+?)\?\>/is', "<?php echo \\1;?>", $template);
+		if ($this->debug) {
+			$template = preg_replace_callback("/\<script[\s\w=\/\"]*?\>.+?\<\/script\>/is", [$this, 'parse_template_callback_scriptdebugconvert_0'], $template);
+		}
+		if (is_callable($postparse)) {
+			$template = $postparse($template);
+		}
+
+		if (!($cachefile && $fp && flock($fp, LOCK_EX) && ftruncate($fp, 0) && fwrite($fp, $template) && fflush($fp) && flock($fp, LOCK_UN) && fclose($fp))) {
+			return $template;
+		}
+	}
+
+	function parse_template_callback_loadsubtemplate_2($matches) {
+		return $this->loadsubtemplate($matches[2]);
+	}
+
+	function parse_template_callback_blocktags_1($matches) {
+		return $this->blocktags($matches[1]);
+	}
+
+	function parse_template_callback_blockdatatags_1($matches) {
+		return $this->blockdatatags($matches[1]);
+	}
+
+	function parse_template_callback_adtags_1($matches) {
+		return $this->adtags($matches[1]);
+	}
+
+	function parse_template_callback_adtags_21($matches) {
+		return $this->adtags($matches[2], $matches[1]);
+	}
+
+	function parse_template_callback_datetags_1($matches) {
+		return $this->datetags($matches[1]);
+	}
+
+	function parse_template_callback_avatartags_1($matches) {
+		return $this->avatartags($matches[1]);
+	}
+
+	function parse_template_callback_evaltags_2($matches) {
+		return $this->evaltags($matches[2]);
+	}
+
+	function parse_template_callback_evaltags_1($matches) {
+		return $this->evaltags($matches[1]);
+	}
+
+	function parse_template_callback_hooktags_13($matches) {
+		return $this->hooktags($matches[1], $matches[3] ?? '');
+	}
+
+	function parse_template_callback_addquote_1($matches) {
+		return $this->addquote('<?=' . $matches[1] . '?>');
+	}
+
+	function parse_template_callback_stripvtags_template1($matches) {
+		return $this->stripvtags('<? include template(\'' . $matches[1] . '\'); ?>');
+	}
+
+	function parse_template_callback_stripvtags_echo1($matches) {
+		return $this->stripvtags('<? echo ' . $this->echopolyfill($matches[1]) . '; ?>');
+	}
+
+	function parse_template_callback_stripvtags_if123($matches) {
+		return $this->stripvtags($matches[1] . '<? if(' . $matches[2] . ') { ?>' . $matches[3]);
+	}
+
+	function parse_template_callback_stripvtags_elseif123($matches) {
+		return $this->stripvtags($matches[1] . '<? } elseif(' . $matches[2] . ') { ?>' . $matches[3]);
+	}
+
+	function parse_template_callback_stripvtags_loop12($matches) {
+		return $this->stripvtags($this->looptags($matches[1], $matches[2]));
+	}
+
+	function parse_template_callback_stripvtags_loop123($matches) {
+		return $this->stripvtags($this->looptags($matches[1], $matches[2], $matches[3]));
+	}
+
+	function parse_template_callback_transamp_0($matches) {
+		return $this->transamp($matches[0]);
+	}
+
+	function parse_template_callback_stripscriptamp_12($matches) {
+		return $this->stripscriptamp($matches[1], $matches[2]);
+	}
+
+	function parse_template_callback_stripblock_12($matches) {
+		return $this->stripblock($matches[1], $matches[2]);
+	}
+
+	function parse_template_callback_scriptdebugconvert_0($matches) {
+		return $this->scriptdebugconvert($matches[0]);
+	}
+
+	function blocktags($parameter) {
+		$bid = intval(trim($parameter));
+		$this->blocks[] = $bid;
+		$i = count($this->replacecode['search']);
+		$this->replacecode['search'][$i] = $search = "<!--BLOCK_TAG_$i-->";
+		$this->replacecode['replace'][$i] = "<?php block_display('$bid');?>";
+		return $search;
+	}
+
+	function blockdatatags($parameter) {
+		$bid = intval(trim($parameter));
+		$this->blocks[] = $bid;
+		$i = count($this->replacecode['search']);
+		$this->replacecode['search'][$i] = $search = "<!--BLOCKDATA_TAG_$i-->";
+		$this->replacecode['replace'][$i] = '';
+		return $search;
+	}
+
+	function adtags($parameter, $varname = '') {
+		$parameter = stripslashes($parameter);
+		$parameter = preg_replace("/(\\\$[a-zA-Z0-9_\-\>\[\]\'\"\$\.\x7f-\xff]+)/s", "{\\1}", $this->addquote($parameter));
+		$i = count($this->replacecode['search']);
+		$this->replacecode['search'][$i] = $search = "<!--AD_TAG_$i-->";
+		$this->replacecode['replace'][$i] = '<?php ' . (!$varname ? 'echo ' : '$' . $varname . '=') . "adshow(\"$parameter\");?>";
+		return $search;
+	}
+
+	function datetags($parameter) {
+		$parameter = stripslashes($parameter);
+		$i = count($this->replacecode['search']);
+		$this->replacecode['search'][$i] = $search = "<!--DATE_TAG_$i-->";
+		$this->replacecode['replace'][$i] = "<?php echo dgmdate($parameter);?>";
+		return $search;
+	}
+
+	function avatartags($parameter) {
+		$parameter = stripslashes($parameter);
+		$i = count($this->replacecode['search']);
+		$this->replacecode['search'][$i] = $search = "<!--AVATAR_TAG_$i-->";
+		$this->replacecode['replace'][$i] = "<?php echo avatar($parameter);?>";
+		return $search;
+	}
+
+	function evaltags($php) {
+		$i = count($this->replacecode['search']);
+		$this->replacecode['search'][$i] = $search = "<!--EVAL_TAG_$i-->";
+		$this->replacecode['replace'][$i] = $this->debug ? '<? ' . preg_replace(['/^L\d+[\w\.\/]*\-\-\>/', '/\<\!\-\-L\d+[\w\.\/]*\-\-\>/', '/\<\!\-\-L\d+[\w\.\/]*$/', '/^\s*\<\!\-\-/', '/\-\-\>\s*$/'], '', $php) . '?>' : "<? $php?>";
+		return $search;
+	}
+
+	function hooktags($hookid, $key = '') {
+		global $_G;
+		$i = count($this->replacecode['search']);
+		$this->replacecode['search'][$i] = $search = "<!--HOOK_TAG_$i-->";
+		$dev = '';
+		if (isset($_G['config']['plugindeveloper']) && $_G['config']['plugindeveloper'] == 2) {
+			$dev = "echo '<hook>[" . ($key ? 'array' : 'string') . " $hookid" . ($key ? '/\'.' . $key . '.\'' : '') . "]</hook>';";
+		}
+		$key = $key != '' ? "[$key]" : '';
+		$this->replacecode['replace'][$i] = "<?php {$dev}if(!empty(\$_G['setting']['pluginhooks']['$hookid']$key)) echo \$_G['setting']['pluginhooks']['$hookid']$key;?>";
+		return $search;
+	}
+
+	function stripphpcode($type, $code) {
+		$this->phpcode[$type][] = $code;
+		return '{phpcode:' . $type . '/' . (count($this->phpcode[$type]) - 1) . '}';
+	}
+
+	function loadsubtemplate($file) {
+		$tplfile = template($file, 0, '', 1);
+		$filename = $tplfile;
+		if ((tplfile::file_exists($filename) && is_readable($filename) && ($content = tplfile::file_get_contents($filename))) ||
+			(tplfile::file_exists(substr($filename, 0, -4) . '.php') && is_readable(substr($filename, 0, -4) . '.php') && ($content = tplfile::getphptemplate(tplfile::file_get_contents(substr($filename, 0, -4) . '.php'))))) {
+			$this->subtemplates[] = $tplfile;
+			return $this->debug ? $this->insertdebugmsg($content, $tplfile) : $content;
+		} else {
+			return '<!-- ' . $file . ' -->';
+		}
+	}
+
+	function looptags($param1, $param2, $param3 = '') {
+		if (preg_match("/^\<\?\=\\\$.+?\?\>$/s", $param1)) {
+			$exprtemp = $param1;
+			$return = '<? if(isset(' . $param1 . ') && is_array(' . $param1 . ')) ';
+		} else {
+			$exprtemp = '$l_' . random(8);
+			$return = '<? ' . $exprtemp . ' = ' . $param1 . ';if(is_array(' . $exprtemp . ')) ';
+		}
+		if ($param3) {
+			$return .= 'foreach(' . $exprtemp . ' as ' . $param2 . ' => ' . $param3 . ') { ?>';
+		} else {
+			$return .= 'foreach(' . $exprtemp . ' as ' . $param2 . ') { ?>';
+		}
+		return $return;
+	}
+
+	function echopolyfill($str) {
+		$str = str_replace(' or ', ' ?? ', $str);
+		if (str_contains($str, ' ?? ') && version_compare(PHP_VERSION, '7.0', '<')) {
+			$str = preg_replace('/^(.+)\s\?\?\s(.+)$/', "isset(\\1) ? (\\1) : (\\2)", $str);
+		}
+		return $str;
+	}
+
+	function transamp($str) {
+		$str = str_replace('&', '&amp;', $str);
+		$str = str_replace('&amp;amp;', '&amp;', $str);
+		return $str;
+	}
+
+	function addquote($var) {
+		return str_replace("\\\"", "\"", preg_replace_callback("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", [$this, 'addquote_exec'], $var));
+	}
+
+	function addquote_exec($matches) {
+		return is_numeric($matches[1]) ? '[' . $matches[1] . ']' : "['" . $matches[1] . "']";
+	}
+
+
+	function stripvtags($expr, $statement = '') {
+		$expr = str_replace('\\\"', '\"', preg_replace("/\<\?\=(\\\$.+?)\?\>/s", "\\1", $expr));
+		if ($this->debug) {
+			$expr = preg_replace('/\<\!\-\-L\d+[\w\.\/]*\-\-\>/', '', $expr);
+		}
+		$statement = str_replace('\\\"', '\"', $statement);
+		return $expr . $statement;
+	}
+
+	function stripscriptamp($s, $extra) {
+		$s = str_replace('&amp;', '&', $s);
+		return "<script src=\"$s\" type=\"text/javascript\"$extra></script>";
+	}
+
+	function stripblock($var, $s) {
+		$var = $this->addquote($var);
+		$s = preg_replace("/<\?=\\\$(.+?)\?>/", "{\$\\1}", $s);
+		preg_match_all('/<\?=(.+?)\?>/', $s, $constary);
+		$constadd = '';
+		$constary[1] = array_unique($constary[1]);
+		foreach ($constary[1] as $const) {
+			$constadd .= '$__' . $const . ' = ' . $const . ';';
+		}
+		$s = preg_replace('/<\?=(.+?)\?>/', "{\$__\\1}", $s);
+		$s = str_replace('?>', "\n\$$var .= <<<EOF\n", $s);
+		$s = str_replace('<?', "\nEOF;\n", $s);
+		$s = str_replace("\nphp ", "\n", $s);
+		return "<?\n$constadd\$$var = <<<EOF\n" . $s . "\nEOF;\n?>";
+	}
+
+	function scriptdebugconvert($str) {
+		return preg_replace('/\<\!\-\-L(\d+[\w\.\/]*)\-\-\>/', '/**L\1*/', $str);
+	}
+
+	function insertdebugmsg($str, $filename) {
+		$startmsg = '<!-- BEGIN ' . $filename . ' -->';
+		$endmsg = '<!-- END ' . $filename . ' -->';
+		$count = 2;
+		$debuglevel = $this->debug;
+		$str = preg_replace_callback('/\n(\t*)/', function ($matches) use (&$count, $filename, $debuglevel) {
+			if ($debuglevel > 1) {
+				return "\n" . $matches[1] . '<!--L' . $count++ . $filename . '-->';
+			} else {
+				return "\n" . $matches[1] . '<!--L' . $count++ . '-->';
+			}
+		}, $str);
+		return $startmsg . $str . $endmsg;
+	}
+
+	function error($message, $tplname) {
+		discuz_error::template_error($message, $tplname);
+	}
+
+}
+

+ 35 - 0
source/class/class_tplfile.php

@@ -0,0 +1,35 @@
+<?php
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class tplfile {
+
+	public static function getphptemplate($content) {
+		if(strtolower(substr($content, 0, 5)) == '<?php') {
+			$pos = strpos($content, "\n");
+			return $pos !== false ? substr($content, $pos + 1) : $content;
+		} else {
+			return $content;
+		}
+	}
+
+	public static function file_exists($file, $nocache = false) {
+		return file_exists($file) ? 1 : 0;
+	}
+
+	public static function file_get_contents($file) {
+		return self::getphptemplate(@implode('', file($file)));
+	}
+
+	public static function filemtime($file) {
+		if(file_exists($file)) {
+			return filemtime($file);
+		}
+		$ext = fileext($file) == 'htm' ? 'php' : 'htm';
+		$file = substr($file, 0, -4).'.'.$ext;
+		return @filemtime($file);
+	}
+
+}

+ 107 - 0
source/class/class_xml.php

@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ') && !defined('IN_API')) {
+	exit('Access Denied');
+}
+
+function xml2array(&$xml, $isnormal = FALSE) {
+	$xml_parser = new XMLparse($isnormal);
+	$data = $xml_parser->parse($xml);
+	$xml_parser->destruct();
+	return $data;
+}
+
+function array2xml($arr, $htmlon = TRUE, $isnormal = FALSE, $level = 1) {
+	$s = $level == 1 ? "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n<root>\r\n" : '';
+	$space = str_repeat("\t", $level);
+	foreach($arr as $k => $v) {
+		if(!is_array($v)) {
+			$s .= $space."<item id=\"$k\">".($htmlon ? '<![CDATA[' : '').$v.($htmlon ? ']]>' : '')."</item>\r\n";
+		} else {
+			$s .= $space."<item id=\"$k\">\r\n".array2xml($v, $htmlon, $isnormal, $level + 1).$space."</item>\r\n";
+		}
+	}
+	$s = preg_replace("/([\x01-\x08\x0b-\x0c\x0e-\x1f])+/", ' ', $s);
+	return $level == 1 ? $s.'</root>' : $s;
+}
+
+class XMLparse {
+
+	var $parser;
+	var $document;
+	var $stack;
+	var $data;
+	var $last_opened_tag;
+	var $isnormal;
+	var $attrs = [];
+	var $failed = FALSE;
+
+	function __construct($isnormal) {
+		$this->XMLparse($isnormal);
+	}
+
+	function XMLparse($isnormal) {
+		$this->isnormal = $isnormal;
+		$this->parser = xml_parser_create('ISO-8859-1');
+		xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false);
+		xml_set_object($this->parser, $this);
+		xml_set_element_handler($this->parser, 'open', 'close');
+		xml_set_character_data_handler($this->parser, 'data');
+	}
+
+	function destruct() {
+		xml_parser_free($this->parser);
+	}
+
+	function parse(&$data) {
+		$this->document = [];
+		$this->stack = [];
+		return xml_parse($this->parser, $data, true) && !$this->failed ? $this->document : [];
+	}
+
+	function open($parser, $tag, $attributes) {
+		$this->data = '';
+		$this->failed = FALSE;
+		if(!$this->isnormal) {
+			if(isset($attributes['id']) && !(isset($this->document[$attributes['id']]) && is_string($this->document[$attributes['id']]))) {
+				$this->document = &$this->document[$attributes['id']];
+			} else {
+				$this->failed = TRUE;
+			}
+		} else {
+			if(!isset($this->document[$tag]) || !is_string($this->document[$tag])) {
+				$this->document = &$this->document[$tag];
+			} else {
+				$this->failed = TRUE;
+			}
+		}
+		$this->stack[] = &$this->document;
+		$this->last_opened_tag = $tag;
+		$this->attrs = $attributes;
+	}
+
+	function data($parser, $data) {
+		if($this->last_opened_tag != NULL) {
+			$this->data .= $data;
+		}
+	}
+
+	function close($parser, $tag) {
+		if($this->last_opened_tag == $tag) {
+			$this->document = $this->data;
+			$this->last_opened_tag = NULL;
+		}
+		array_pop($this->stack);
+		if($this->stack) {
+			$this->document = &$this->stack[count($this->stack) - 1];
+		}
+	}
+
+}
+

+ 308 - 0
source/class/class_zip.php

@@ -0,0 +1,308 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+
+class zipfile {
+	var $datasec = [];
+
+	var $ctrl_dir = [];
+
+	var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
+
+	var $old_offset = 0;
+
+
+	function unix2DosTime($unixtime = 0) {
+		$timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);
+
+		if($timearray['year'] < 1980) {
+			$timearray['year'] = 1980;
+			$timearray['mon'] = 1;
+			$timearray['mday'] = 1;
+			$timearray['hours'] = 0;
+			$timearray['minutes'] = 0;
+			$timearray['seconds'] = 0;
+		} // end if
+
+		return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
+			($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
+	} // end of the 'unix2DosTime()' method
+
+
+	function addFile($data, $name, $time = 0) {
+		$name = str_replace('\\', '/', $name);
+
+		$dtime = dechex($this->unix2DosTime($time));
+		$hexdtime = '\x'.$dtime[6].$dtime[7]
+			.'\x'.$dtime[4].$dtime[5]
+			.'\x'.$dtime[2].$dtime[3]
+			.'\x'.$dtime[0].$dtime[1];
+		eval('$hexdtime = "'.$hexdtime.'";');
+
+		$fr = "\x50\x4b\x03\x04";
+		$fr .= "\x14\x00";            // ver needed to extract
+		$fr .= "\x00\x00";            // gen purpose bit flag
+		$fr .= "\x08\x00";            // compression method
+		$fr .= $hexdtime;             // last mod time and date
+
+		$unc_len = strlen($data);
+		$crc = crc32($data);
+		$zdata = gzcompress($data);
+		$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug
+		$c_len = strlen($zdata);
+		$fr .= pack('V', $crc);             // crc32
+		$fr .= pack('V', $c_len);           // compressed filesize
+		$fr .= pack('V', $unc_len);         // uncompressed filesize
+		$fr .= pack('v', strlen($name));    // length of filename
+		$fr .= pack('v', 0);                // extra field length
+		$fr .= $name;
+
+		$fr .= $zdata;
+
+
+		$this->datasec[] = $fr;
+
+		$cdrec = "\x50\x4b\x01\x02";
+		$cdrec .= "\x00\x00";                // version made by
+		$cdrec .= "\x14\x00";                // version needed to extract
+		$cdrec .= "\x00\x00";                // gen purpose bit flag
+		$cdrec .= "\x08\x00";                // compression method
+		$cdrec .= $hexdtime;                 // last mod time & date
+		$cdrec .= pack('V', $crc);           // crc32
+		$cdrec .= pack('V', $c_len);         // compressed filesize
+		$cdrec .= pack('V', $unc_len);       // uncompressed filesize
+		$cdrec .= pack('v', strlen($name)); // length of filename
+		$cdrec .= pack('v', 0);             // extra field length
+		$cdrec .= pack('v', 0);             // file comment length
+		$cdrec .= pack('v', 0);             // disk number start
+		$cdrec .= pack('v', 0);             // internal file attributes
+		$cdrec .= pack('V', 32);            // external file attributes - 'archive' bit set
+
+		$cdrec .= pack('V', $this->old_offset); // relative offset of local header
+		$this->old_offset += strlen($fr);
+
+		$cdrec .= $name;
+
+		$this->ctrl_dir[] = $cdrec;
+	} // end of the 'addFile()' method
+
+
+	function file() {
+		$data = implode('', $this->datasec);
+		$ctrldir = implode('', $this->ctrl_dir);
+
+		return
+			$data.
+			$ctrldir.
+			$this->eof_ctrl_dir.
+			pack('v', sizeof($this->ctrl_dir)).  // total # of entries "on this disk"
+			pack('v', sizeof($this->ctrl_dir)).  // total # of entries overall
+			pack('V', strlen($ctrldir)).           // size of central dir
+			pack('V', strlen($data)).              // offset to start of central dir
+			"\x00\x00";                             // .zip file comment length
+	} // end of the 'file()' method
+
+} // end of the 'zipfile' class
+
+
+class SimpleUnzip {
+	var $Comment = '';
+
+	var $Entries = [];
+
+	var $Name = '';
+
+	var $Size = 0;
+
+	var $Time = 0;
+
+	function __construct($in_FileName = '') {
+		if($in_FileName !== '') {
+			SimpleUnzip::ReadFile($in_FileName);
+		}
+	} // end of the 'SimpleUnzip' constructor
+
+	function Count() {
+		return count($this->Entries);
+	} // end of the 'Count()' method
+
+	function GetData($in_Index) {
+		return $this->Entries[$in_Index]->Data;
+	} // end of the 'GetData()' method
+
+	function GetEntry($in_Index) {
+		return $this->Entries[$in_Index];
+	} // end of the 'GetEntry()' method
+
+	function GetError($in_Index) {
+		return $this->Entries[$in_Index]->Error;
+	} // end of the 'GetError()' method
+
+	function GetErrorMsg($in_Index) {
+		return $this->Entries[$in_Index]->ErrorMsg;
+	} // end of the 'GetErrorMsg()' method
+
+	function GetName($in_Index) {
+		return $this->Entries[$in_Index]->Name;
+	} // end of the 'GetName()' method
+
+	function GetPath($in_Index) {
+		return $this->Entries[$in_Index]->Path;
+	} // end of the 'GetPath()' method
+
+	function GetTime($in_Index) {
+		return $this->Entries[$in_Index]->Time;
+	} // end of the 'GetTime()' method
+
+	function ReadFile($in_FileName) {
+		$this->Entries = [];
+
+		$this->Name = $in_FileName;
+		$this->Time = filemtime($in_FileName);
+		$this->Size = filesize($in_FileName);
+
+		$oF = fopen($in_FileName, 'rb');
+		$vZ = fread($oF, $this->Size);
+		fclose($oF);
+
+		$aE = explode("\x50\x4b\x05\x06", $vZ);
+
+
+		$aP = unpack('x16/v1CL', $aE[1]);
+		$this->Comment = substr($aE[1], 18, $aP['CL']);
+
+		$this->Comment = strtr($this->Comment, ["\r\n" => "\n",
+			"\r" => "\n"]);
+
+		$aE = explode("\x50\x4b\x01\x02", $vZ);
+		$aE = explode("\x50\x4b\x03\x04", $aE[0]);
+		array_shift($aE);
+
+		foreach($aE as $vZ) {
+			$aI = [];
+			$aI['E'] = 0;
+			$aI['EM'] = '';
+			$aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ);
+			$bE = $aP['GPF'] && 0x0001;
+			$nF = $aP['FNL'];
+
+			if($aP['GPF'] & 0x0008) {
+				$aP1 = unpack('V1CRC/V1CS/V1UCS', substr($vZ, -12));
+
+				$aP['CRC'] = $aP1['CRC'];
+				$aP['CS'] = $aP1['CS'];
+				$aP['UCS'] = $aP1['UCS'];
+
+				$vZ = substr($vZ, 0, -12);
+			}
+
+			$aI['N'] = substr($vZ, 26, $nF);
+
+			if(str_ends_with($aI['N'], '/')) {
+				continue;
+			}
+
+			$aI['P'] = dirname($aI['N']);
+			$aI['P'] = $aI['P'] == '.' ? '' : $aI['P'];
+			$aI['N'] = basename($aI['N']);
+
+			$vZ = substr($vZ, 26 + $nF);
+
+			if(strlen($vZ) != $aP['CS']) {
+				$aI['E'] = 1;
+				$aI['EM'] = 'Compressed size is not equal with the value in header information.';
+			} else {
+				if($bE) {
+					$aI['E'] = 5;
+					$aI['EM'] = 'File is encrypted, which is not supported from this class.';
+				} else {
+					switch($aP['CM']) {
+						case 0: // Stored
+							break;
+
+						case 8: // Deflated
+							$vZ = gzinflate($vZ);
+							break;
+
+						case 12: // BZIP2
+							if(extension_loaded('bz2')) {
+								$vZ = bzdecompress($vZ);
+							} else {
+								$aI['E'] = 7;
+								$aI['EM'] = 'PHP BZIP2 extension not available.';
+							}
+
+							break;
+
+						default:
+							$aI['E'] = 6;
+							$aI['EM'] = "De-/Compression method {$aP['CM']} is not supported.";
+					}
+
+					if(!$aI['E']) {
+						if($vZ === FALSE) {
+							$aI['E'] = 2;
+							$aI['EM'] = 'Decompression of data failed.';
+						} else {
+							if(strlen($vZ) != $aP['UCS']) {
+								$aI['E'] = 3;
+								$aI['EM'] = 'Uncompressed size is not equal with the value in header information.';
+							} else {
+								if(crc32($vZ) != $aP['CRC']) {
+									$aI['E'] = 4;
+									$aI['EM'] = 'CRC32 checksum is not equal with the value in header information.';
+								}
+							}
+						}
+					}
+				}
+			}
+
+			$aI['D'] = $vZ;
+
+			$aI['T'] = mktime(($aP['FT'] & 0xf800) >> 11,
+				($aP['FT'] & 0x07e0) >> 5,
+				($aP['FT'] & 0x001f) << 1,
+				($aP['FD'] & 0x01e0) >> 5,
+				($aP['FD'] & 0x001f),
+				(($aP['FD'] & 0xfe00) >> 9) + 1980);
+
+			$this->Entries[] = new SimpleUnzipEntry($aI);
+		} // end for each entries
+
+		return $this->Entries;
+	} // end of the 'ReadFile()' method
+} // end of the 'SimpleUnzip' class
+
+class SimpleUnzipEntry {
+	var $Data = '';
+
+	var $Error = 0;
+
+	var $ErrorMsg = '';
+
+	var $Name = '';
+
+	var $Path = '';
+
+	var $Time = 0;
+
+	function __construct($in_Entry) {
+		$this->Data = $in_Entry['D'];
+		$this->Error = $in_Entry['E'];
+		$this->ErrorMsg = $in_Entry['EM'];
+		$this->Name = $in_Entry['N'];
+		$this->Path = $in_Entry['P'];
+		$this->Time = $in_Entry['T'];
+	} // end of the 'SimpleUnzipEntry' constructor
+} // end of the 'SimpleUnzipEntry' class
+

+ 247 - 0
source/class/db/db_driver_mysqli.php

@@ -0,0 +1,247 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class db_driver_mysqli {
+	var $tablepre;
+	var $version = '';
+	var $drivertype = 'mysqli';
+	var $querynum = 0;
+	var $slaveid = 0;
+	var $curlink;
+	var $link = [];
+	var $config = [];
+	var $sqldebug = [];
+	var $map = [];
+
+	function db_mysql($config = []) {
+		if(!empty($config)) {
+			$this->set_config($config);
+		}
+	}
+
+	function set_config($config) {
+		$this->config = &$config;
+		$this->tablepre = $config['1']['tablepre'];
+		if(!empty($this->config['map'])) {
+			$this->map = $this->config['map'];
+			for($i = 1; $i <= 100; $i++) {
+				if(isset($this->map['forum_thread'])) {
+					$this->map['forum_thread_'.$i] = $this->map['forum_thread'];
+				}
+				if(isset($this->map['forum_post'])) {
+					$this->map['forum_post_'.$i] = $this->map['forum_post'];
+				}
+				if(isset($this->map['forum_attachment']) && $i <= 10) {
+					$this->map['forum_attachment_'.($i - 1)] = $this->map['forum_attachment'];
+				}
+			}
+			if(isset($this->map['common_member'])) {
+				$this->map['common_member_archive'] =
+				$this->map['common_member_count'] = $this->map['common_member_count_archive'] =
+				$this->map['common_member_status'] = $this->map['common_member_status_archive'] =
+				$this->map['common_member_profile'] = $this->map['common_member_profile_archive'] =
+				$this->map['common_member_field_forum'] = $this->map['common_member_field_forum_archive'] =
+				$this->map['common_member_field_home'] = $this->map['common_member_field_home_archive'] =
+				$this->map['common_member_validate'] = $this->map['common_member_verify'] =
+				$this->map['common_member_verify_info'] = $this->map['common_member'];
+			}
+		}
+	}
+
+	function connect($serverid = 1) {
+
+		if(empty($this->config) || empty($this->config[$serverid])) {
+			$this->halt('config_db_not_found');
+		}
+
+		$this->link[$serverid] = $this->_dbconnect(
+			$this->config[$serverid]['dbhost'],
+			$this->config[$serverid]['dbuser'],
+			$this->config[$serverid]['dbpw'],
+			$this->config[$serverid]['dbcharset'],
+			$this->config[$serverid]['dbname'],
+			$this->config[$serverid]['pconnect']
+		);
+		$this->curlink = $this->link[$serverid];
+
+	}
+
+	function _dbconnect($dbhost, $dbuser, $dbpw, $dbcharset, $dbname, $pconnect, $halt = true) {
+		mysqli_report(MYSQLI_REPORT_OFF);
+		if(intval($pconnect) === 1) $dbhost = 'p:'.$dbhost; // 前面加p:,表示persistent connection
+		$link = new mysqli();
+		if(!$link->real_connect($dbhost, $dbuser, $dbpw, $dbname, null, null, MYSQLI_CLIENT_COMPRESS)) {
+			$halt && $this->halt('notconnect', $this->errno());
+		} else {
+			$this->curlink = $link;
+			$link->options(MYSQLI_OPT_LOCAL_INFILE, false);
+			$link->set_charset($dbcharset ? $dbcharset : $this->config[1]['dbcharset']);
+			$serverset = 'sql_mode=\'\',';
+			$serverset .= 'character_set_client=binary';
+			$serverset && $link->query("SET $serverset");
+		}
+		return $link;
+	}
+
+	function table_name($tablename) {
+		if(!empty($this->map) && !empty($this->map[$tablename])) {
+			$id = $this->map[$tablename];
+			if(!$this->link[$id]) {
+				$this->connect($id);
+			}
+			$this->curlink = $this->link[$id];
+		} else {
+			$this->curlink = $this->link[1];
+		}
+		return $this->tablepre.$tablename;
+	}
+
+	function select_db($dbname) {
+		return $this->curlink->select_db($dbname);
+	}
+
+	function fetch_array($query, $result_type = MYSQLI_ASSOC) {
+		return $query ? $query->fetch_array($result_type) : null;
+	}
+
+	function fetch_first($sql) {
+		return $this->fetch_array($this->query($sql));
+	}
+
+	function result_first($sql) {
+		return $this->result($this->query($sql), 0);
+	}
+
+	public function query($sql, $silent = false, $unbuffered = false) {
+		if(defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) {
+			$starttime = microtime(true);
+		}
+
+		if('UNBUFFERED' === $silent) {
+			$silent = false;
+			$unbuffered = true;
+		} elseif('SILENT' === $silent) {
+			$silent = true;
+			$unbuffered = false;
+		}
+
+		$resultmode = $unbuffered ? MYSQLI_USE_RESULT : MYSQLI_STORE_RESULT;
+
+		if(!($query = $this->curlink->query($sql, $resultmode))) {
+			if(in_array($this->errno(), [2006, 2013]) && !str_starts_with($silent, 'RETRY')) {
+				$this->connect();
+				return $this->query($sql, 'RETRY'.$silent);
+			}
+			if(!$silent) {
+				$this->halt($this->error(), $this->errno(), $sql);
+			}
+		}
+
+		if(defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) {
+			$this->sqldebug[] = [$sql, number_format((microtime(true) - $starttime), 6), debug_backtrace(), $this->curlink];
+		}
+
+		$this->querynum++;
+		return $query;
+	}
+
+	function affected_rows() {
+		return $this->curlink->affected_rows;
+	}
+
+	function error() {
+		return $this->curlink->error;
+	}
+
+	function errno() {
+		return $this->curlink->errno;
+	}
+
+	function result($query, $row = 0) {
+		if(!$query || $query->num_rows == 0) {
+			return null;
+		}
+		$query->data_seek($row);
+		$assocs = $query->fetch_row();
+		return $assocs[0];
+	}
+
+	function num_rows($query) {
+		$query = $query ? $query->num_rows : 0;
+		return $query;
+	}
+
+	function num_fields($query) {
+		return $query ? $query->field_count : null;
+	}
+
+	function free_result($query) {
+		return $query ? $query->free() : false;
+	}
+
+	function insert_id() {
+		return ($id = $this->curlink->insert_id) >= 0 ? $id : $this->result($this->query('SELECT last_insert_id()'), 0);
+	}
+
+	function fetch_row($query) {
+		$query = $query ? $query->fetch_row() : null;
+		return $query;
+	}
+
+	function fetch_fields($query) {
+		return $query ? $query->fetch_field() : null;
+	}
+
+	function version() {
+		if(empty($this->version)) {
+			$this->version = $this->curlink->server_info;
+		}
+		return $this->version;
+	}
+
+	function escape_string($str) {
+		return $this->curlink->escape_string($str);
+	}
+
+	function close() {
+		return $this->curlink->close();
+	}
+
+	function halt($message = '', $code = 0, $sql = '') {
+		throw new DbException($message, $code, $sql);
+	}
+
+	function begin_transaction() {
+		if(PHP_VERSION < '5.5') {
+			return $this->curlink->autocommit(false);
+		}
+		return $this->curlink->begin_transaction();
+	}
+
+	function commit() {
+		$cr = $this->curlink->commit();
+		if(PHP_VERSION < '5.5') {
+			$this->curlink->autocommit(true);
+		}
+		return $cr;
+	}
+
+	function rollback() {
+		$rr = $this->curlink->rollback();
+		if(PHP_VERSION < '5.5') {
+			$this->curlink->autocommit(true);
+		}
+		return $rr;
+	}
+
+}
+

+ 95 - 0
source/class/db/db_driver_mysqli_slave.php

@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class db_driver_mysqli_slave extends db_driver_mysqli {
+
+	public $slaveid = null;
+
+	public $slavequery = 0;
+
+	public $slaveexcept = false;
+
+	public $excepttables = [];
+
+	public $tablename = '';
+
+	protected $_weighttable = [];
+
+	public $serverid = null;
+
+	function set_config($config) {
+		parent::set_config($config);
+
+		if($this->config['common']['slave_except_table']) {
+			$this->excepttables = explode(',', str_replace(' ', '', $this->config['common']['slave_except_table']));
+		}
+	}
+
+	public function table_name($tablename) {
+		$this->tablename = $tablename;
+		if(!$this->slaveexcept && $this->excepttables) {
+			$this->slaveexcept = in_array($tablename, $this->excepttables, true);
+		}
+		$this->serverid = $this->map[$this->tablename] ?? 1;
+		return $this->tablepre.$tablename;
+	}
+
+	protected function _slave_connect() {
+		if(!empty($this->config[$this->serverid]['slave'])) {
+			$this->_choose_slave();
+			if($this->slaveid) {
+				if(!isset($this->link[$this->slaveid])) {
+					$this->connect($this->slaveid);
+				}
+				$this->slavequery++;
+				$this->curlink = $this->link[$this->slaveid];
+			}
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	protected function _choose_slave() {
+		if(!isset($this->_weighttable[$this->serverid])) {
+			foreach($this->config[$this->serverid]['slave'] as $key => $value) {
+				$this->_weighttable[$this->serverid] .= str_repeat($key, 1 + intval($value['weight']));
+			}
+		}
+		$sid = $this->_weighttable[$this->serverid][mt_rand(0, strlen($this->_weighttable[$this->serverid]) - 1)];
+		$this->slaveid = $this->serverid.'_'.$sid;
+		if(!isset($this->config[$this->slaveid])) {
+			$this->config[$this->slaveid] = $this->config[$this->serverid]['slave'][$sid];
+		}
+	}
+
+	protected function _master_connect() {
+		if($this->serverid === null) {
+			$this->serverid = 1;
+		}
+		if(!$this->link[$this->serverid]) {
+			$this->connect($this->serverid);
+		}
+		$this->curlink = $this->link[$this->serverid];
+	}
+
+	public function query($sql, $silent = false, $unbuffered = false) {
+		if(!(!$this->slaveexcept && strtoupper(substr($sql, 0, 6)) === 'SELECT' && !str_contains(strtoupper($sql), 'FOR UPDATE') && $this->_slave_connect())) {
+			$this->_master_connect();
+		}
+		$this->tablename = '';
+		$this->slaveexcept = false;
+		return parent::query($sql, $silent, $unbuffered);
+	}
+
+}
+

+ 248 - 0
source/class/db/db_driver_pdo.php

@@ -0,0 +1,248 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class db_driver_pdo extends db_driver_mysqli {
+	var $tablepre;
+	var $version = '';
+	var $drivertype = 'pdo';
+	var $querynum = 0;
+	var $slaveid = 0;
+	var $curlink;
+	var $link = [];
+	var $config = [];
+	var $sqldebug = [];
+	var $map = [];
+
+	function db_mysql($config = []) {
+		if(!empty($config)) {
+			$this->set_config($config);
+		}
+	}
+
+	function connect($serverid = 1) {
+
+		if(empty($this->config) || empty($this->config[$serverid])) {
+			$this->halt('config_db_not_found');
+		}
+
+		if(!empty($this->config[$serverid]['dsn'])) {
+			$this->link[$serverid] = $this->_dbconnectWithDSN(
+				$this->config[$serverid]['dsn'],
+				$this->config[$serverid]['dbuser'],
+				$this->config[$serverid]['dbpw'],
+				$this->config[$serverid]['pconnect']
+			);
+		} else {
+			$this->link[$serverid] = $this->_dbconnect(
+				$this->config[$serverid]['dbhost'],
+				$this->config[$serverid]['dbuser'],
+				$this->config[$serverid]['dbpw'],
+				$this->config[$serverid]['dbcharset'],
+				$this->config[$serverid]['dbname'],
+				$this->config[$serverid]['pconnect']
+			);
+		}
+		$this->curlink = $this->link[$serverid];
+
+	}
+
+	function _dbconnect($dbhost, $dbuser, $dbpw, $dbcharset, $dbname, $pconnect, $halt = true) {
+		$option = [];
+		if(intval($pconnect) === 1) {
+			$option = [PDO::ATTR_PERSISTENT => true];
+		}
+		$link = new PDO('mysql:host='.$dbhost.';dbname='.$dbname.';charset='.$dbcharset, $dbuser, $dbpw, $option);
+
+		if(!$link) {
+			$halt && $this->halt('notconnect', $this->errno());
+		} else {
+			$this->curlink = $link;
+			$link->query('SET sql_mode=\'\',character_set_client=binary');
+		}
+		return $link;
+	}
+
+	function _dbconnectWithDSN($dsn, $dbuser, $dbpw, $pconnect, $halt = true) {
+		$option = [];
+		if(intval($pconnect) === 1) {
+			$option = [PDO::ATTR_PERSISTENT => true];
+		}
+		$link = new PDO($dsn, $dbuser, $dbpw, $option);
+
+		if(!$link) {
+			$halt && $this->halt('notconnect', $this->errno());
+		} else {
+			$this->curlink = $link;
+			$link->query('SET sql_mode=\'\',character_set_client=binary');
+		}
+		return $link;
+	}
+
+	function select_db($dbname) {
+		return false;
+	}
+
+	function fetch_array($query, $result_type = MYSQLI_ASSOC) {
+		$result_type = match ($result_type) {
+			'MYSQL_ASSOC', MYSQLI_ASSOC, 1 => PDO::FETCH_ASSOC,
+			'MYSQL_NUM', MYSQLI_NUM, 2 => PDO::FETCH_NUM,
+			default => PDO::FETCH_BOTH,
+		};
+		return $query ? $query->fetch($result_type) : null;
+	}
+
+	function fetch_first($sql) {
+		return $this->fetch_array($this->query($sql));
+	}
+
+	function result_first($sql) {
+		return $this->result($this->query($sql), 0);
+	}
+
+	public function query($sql, $silent = false, $unbuffered = false) {
+		$arg = [];
+		if(is_array($sql)) {
+			$arg = !empty($sql[1]) ? (array)$sql[1] : [];
+			$sql = $sql[0];
+		}
+		if(defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) {
+			$starttime = microtime(true);
+		}
+
+		if('UNBUFFERED' === $silent) {
+			$silent = false;
+			$unbuffered = true;
+		} elseif('SILENT' === $silent) {
+			$silent = true;
+			$unbuffered = false;
+		}
+
+		if(!$unbuffered) {
+			$this->curlink->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, TRUE);
+		}
+
+		$query = $this->curlink->prepare($sql);
+		try {
+			$query->execute($arg);
+		} catch (Exception $e) {
+			if(in_array($this->errno(), ['01002', '08003', '08S01', '08007']) && !str_starts_with($silent, 'RETRY')) {
+				$this->connect();
+				return $this->query([$sql, $arg], 'RETRY'.$silent);
+			}
+
+			if(!$silent) {
+				$this->halt($this->error(), $this->errno(), $sql);
+			}
+		}
+
+		if(defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) {
+			$this->sqldebug[] = [$sql, number_format((microtime(true) - $starttime), 6), debug_backtrace(), $this->curlink, $arg];
+		}
+
+		$this->querynum++;
+
+		$cmd = trim(strtoupper(substr($sql, 0, strpos($sql, ' '))));
+		if($cmd === 'UPDATE' || $cmd === 'DELETE' || $cmd === 'INSERT') {
+			$this->rowCount = $query->rowCount();
+		}
+
+		return $query;
+	}
+
+	function affected_rows() {
+		return $this->rowCount;
+	}
+
+	function error() {
+		return (($this->curlink) ? $this->curlink->errorInfo()[2] : 'pdo_error');
+	}
+
+	function errno() {
+		return intval(($this->curlink) ? $this->curlink->errorCode() : 99999);
+	}
+
+	function result($query, $row = 0) {
+		if(!$query || $query->rowCount() == 0) {
+			return null;
+		}
+		return $query->fetchColumn($row);
+	}
+
+	function num_rows($query) {
+		return $query ? $query->rowCount() : 0;
+	}
+
+	function num_fields($query) {
+		return $query ? $query->columnCount() : null;
+	}
+
+	function free_result($query) {
+		return true;
+	}
+
+	function insert_id() {
+		return ($id = $this->curlink->lastInsertId()) >= 0 ? $id : $this->result($this->query('SELECT last_insert_id()'), 0);
+	}
+
+	function fetch_row($query) {
+		return $query ? $query->fetch_row() : null;
+	}
+
+	function fetch_fields($query) {
+		return $query ? $query->fetch_field() : null;
+	}
+
+	function version() {
+		if(empty($this->version)) {
+			$this->version = $this->curlink->getAttribute(PDO::ATTR_SERVER_VERSION);
+		}
+		return $this->version;
+	}
+
+	function escape_string($str) {
+		return substr($this->curlink->quote($str), 1, -1);
+	}
+
+	function close() {
+		return true;
+	}
+
+	function halt($message = '', $code = 0, $sql = '') {
+		throw new DbException(var_export($message, true), $code, $sql);
+	}
+
+	function begin_transaction() {
+		if($this->curlink->beginTransaction()) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	function commit() {
+		if($this->curlink->commit()) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	function rollback() {
+		if($this->curlink->rollBack()) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+}
+

+ 98 - 0
source/class/db/db_driver_pdo_slave.php

@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class db_driver_pdo_slave extends db_driver_pdo {
+
+	public $slaveid = null;
+
+	public $slavequery = 0;
+
+	public $slaveexcept = false;
+
+	public $excepttables = [];
+
+	public $tablename = '';
+
+	protected $_weighttable = [];
+
+	public $serverid = null;
+
+	function set_config($config) {
+		parent::set_config($config);
+
+		if($this->config['common']['slave_except_table']) {
+			$this->excepttables = explode(',', str_replace(' ', '', $this->config['common']['slave_except_table']));
+		}
+	}
+
+	public function table_name($tablename) {
+		$this->tablename = $tablename;
+		if(!$this->slaveexcept && $this->excepttables) {
+			$this->slaveexcept = in_array($tablename, $this->excepttables, true);
+		}
+		$this->serverid = $this->map[$this->tablename] ?? 1;
+		return $this->tablepre.$tablename;
+	}
+
+	protected function _slave_connect() {
+		if(!empty($this->config[$this->serverid]['slave'])) {
+			$this->_choose_slave();
+			if($this->slaveid) {
+				if(!isset($this->link[$this->slaveid])) {
+					$this->connect($this->slaveid);
+				}
+				$this->slavequery++;
+				$this->curlink = $this->link[$this->slaveid];
+			}
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	protected function _choose_slave() {
+		if(!isset($this->_weighttable[$this->serverid])) {
+			foreach($this->config[$this->serverid]['slave'] as $key => $value) {
+				$this->_weighttable[$this->serverid] .= str_repeat($key, 1 + intval($value['weight']));
+			}
+		}
+		$sid = $this->_weighttable[$this->serverid][mt_rand(0, strlen($this->_weighttable[$this->serverid]) - 1)];
+		$this->slaveid = $this->serverid.'_'.$sid;
+		if(!isset($this->config[$this->slaveid])) {
+			$this->config[$this->slaveid] = $this->config[$this->serverid]['slave'][$sid];
+		}
+	}
+
+	protected function _master_connect() {
+		if($this->serverid === null) {
+			$this->serverid = 1;
+		}
+		if(!$this->link[$this->serverid]) {
+			$this->connect($this->serverid);
+		}
+		$this->curlink = $this->link[$this->serverid];
+	}
+
+	public function query($sql, $silent = false, $unbuffered = false) {
+		if(is_array($sql)) {
+			$_sql = $sql[0];
+		}
+		if(!(!$this->slaveexcept && strtoupper(substr($_sql, 0, 6)) === 'SELECT' && !str_contains(strtoupper($_sql), 'FOR UPDATE') && $this->_slave_connect())) {
+			$this->_master_connect();
+		}
+		$this->tablename = '';
+		$this->slaveexcept = false;
+		return parent::query($sql, $silent, $unbuffered);
+	}
+
+}
+

+ 0 - 0
source/class/db/index.htm


+ 348 - 0
source/class/discuz/discuz_application.php

@@ -0,0 +1,348 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if (!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class discuz_application extends discuz_base {
+
+
+	var $mem = null;
+
+	var $session = null;
+
+	var $config = [];
+
+	var $var = [];
+
+	var $cachelist = [];
+
+	var $init_db = true;
+	var $init_setting = true;
+	var $init_user = true;
+	var $init_session = true;
+	var $init_cron = true;
+	var $init_misc = true;
+	var $init_mobile = true;
+
+	var $initated = false;
+
+	var $superglobal = [
+		'GLOBALS' => 1,
+		'_GET' => 1,
+		'_POST' => 1,
+		'_REQUEST' => 1,
+		'_COOKIE' => 1,
+		'_SERVER' => 1,
+		'_ENV' => 1,
+		'_FILES' => 1,
+	];
+
+	static function &instance() {
+		static $object;
+		if (empty($object)) {
+			$object = new self();
+		}
+		return $object;
+	}
+
+	public function __construct() {
+		$this->_init_env();
+		$this->_init_input();
+		$this->_init_output();
+	}
+
+	public function init() {
+		if (!$this->initated) {
+			$this->_init_db();
+		}
+		$this->initated = true;
+	}
+
+	private function _init_env() {
+		error_reporting(E_ERROR);
+
+		define('ICONV_ENABLE', function_exists('iconv'));
+		define('MB_ENABLE', function_exists('mb_convert_encoding'));
+		define('EXT_OBGZIP', function_exists('ob_gzhandler'));
+
+		define('TIMESTAMP', time());
+
+		if (!defined('DISCUZ_CORE_FUNCTION') && !@include(DISCUZ_ROOT . './source/function/function_core.php')) {
+			exit('function_core.php is missing');
+		}
+
+		if (!defined('DISCUZ_LOG_FUNCTION') && !@include(DISCUZ_ROOT . './source/function/function_log.php')) {
+			exit('function_log.php is missing');
+		}
+
+		if (function_exists('ini_get')) {
+			$memorylimit = @ini_get('memory_limit');
+			if ($memorylimit && return_bytes($memorylimit) < 33554432 && function_exists('ini_set')) {
+				ini_set('memory_limit', '128m');
+			}
+		}
+
+		define('IS_ROBOT', checkrobot());
+
+		foreach ($GLOBALS as $key => $value) {
+			if (!isset($this->superglobal[$key])) {
+				$GLOBALS[$key] = null;
+				unset($GLOBALS[$key]);
+			}
+		}
+
+		if (!defined('APPTYPEID')) {
+			define('APPTYPEID', 0);
+		}
+
+		if (!defined('DISCUZ_APP')) {
+			define('DISCUZ_APP', '');
+		}
+
+		if (!defined('CURSCRIPT')) {
+			define('CURSCRIPT', DISCUZ_APP);
+		}
+
+		global $_G;
+		$_G = [
+			'uid' => 0,
+			'username' => '',
+			'adminid' => 0,
+			'groupid' => 1,
+			'sid' => '',
+			'formhash' => '',
+			'connectguest' => 0,
+			'timestamp' => TIMESTAMP,
+			'starttime' => microtime(true),
+			'clientip' => $this->_get_client_ip(),
+			'remoteport' => $_SERVER['REMOTE_PORT'],
+			'referer' => '',
+			'charset' => '',
+			'gzipcompress' => '',
+			'authkey' => '',
+			'timenow' => [],
+			'widthauto' => 0,
+			'disabledwidthauto' => 0,
+
+			'PHP_SELF' => '',
+			'siteurl' => '',
+			'siteroot' => '',
+			'siteport' => '',
+
+			'pluginrunlist' => !defined('PLUGINRUNLIST') ? [] : explode(',', PLUGINRUNLIST),
+
+			'config' => & $this->config,
+			'setting' => [],
+			'member' => [],
+			'group' => [],
+			'cookie' => [],
+			'style' => [],
+			'cache' => [],
+			'session' => [],
+			'lang' => [],
+
+			'fid' => 0,
+			'tid' => 0,
+			'forum' => [],
+			'thread' => [],
+			'rssauth' => '',
+
+			'home' => [],
+			'space' => [],
+
+			'block' => [],
+			'article' => [],
+
+			'action' => [
+				'action' => APPTYPEID,
+				'fid' => 0,
+				'tid' => 0,
+			],
+
+			'mobile' => '',
+			'notice_structure' => [
+				'mypost' => ['post', 'rate', 'pcomment', 'activity', 'reward', 'goods', 'at'],
+				'interactive' => ['poke', 'friend', 'wall', 'comment', 'click', 'sharenotice'],
+				'system' => ['system', 'credit', 'group', 'verify', 'magic', 'task', 'show', 'group', 'pusearticle', 'mod_member', 'blog', 'article'],
+				'manage' => ['mod_member', 'report', 'pmreport'],
+				'app' => [],
+			],
+			'mobiletpl' => ['1' => 'touch', '2' => 'touch', '3' => 'touch', 'yes' => 'touch'],
+		];
+		$_G['PHP_SELF'] = dhtmlspecialchars($this->_get_script_url());
+		$_G['basescript'] = CURSCRIPT;
+		$_G['basefilename'] = basename($_G['PHP_SELF']);
+		$sitepath = substr($_G['PHP_SELF'], 0, strrpos($_G['PHP_SELF'], '/'));
+		if (defined('IN_API')) {
+			$sitepath = preg_replace('/\/api\/?.*?$/i', '', $sitepath);
+		} elseif (defined('IN_ARCHIVER')) {
+			$sitepath = preg_replace('/\/archiver/i', '', $sitepath);
+		}
+		if (defined('IN_NEWMOBILE')) {
+			$sitepath = preg_replace('/\/m/i', '', $sitepath);
+		}
+		$_G['isHTTPS'] = !empty($_G['config']['output']['forcehttps']) || $this->_is_https();
+		$_G['scheme'] = 'http' . ($_G['isHTTPS'] ? 's' : '');
+		$_G['siteurl'] = dhtmlspecialchars($_G['scheme'] . '://' . $_SERVER['HTTP_HOST'] . $sitepath . '/');
+
+		$url = parse_url($_G['siteurl']);
+		$_G['siteroot'] = $url['path'] ?? '';
+		$_G['siteport'] = empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' || $_SERVER['SERVER_PORT'] == '443' ? '' : ':' . $_SERVER['SERVER_PORT'];
+
+		if (defined('SUB_DIR')) {
+			$_G['siteurl'] = str_replace(SUB_DIR, '/', $_G['siteurl']);
+			$_G['siteroot'] = str_replace(SUB_DIR, '/', $_G['siteroot']);
+		}
+
+		$this->var = &$_G;
+	}
+
+	private function _get_script_url() {
+		if (!isset($this->var['PHP_SELF'])) {
+			$scriptName = basename($_SERVER['SCRIPT_FILENAME']);
+			if (basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
+				$this->var['PHP_SELF'] = $_SERVER['SCRIPT_NAME'];
+			} else if (basename($_SERVER['PHP_SELF']) === $scriptName) {
+				$this->var['PHP_SELF'] = $_SERVER['PHP_SELF'];
+			} else if (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
+				$this->var['PHP_SELF'] = $_SERVER['ORIG_SCRIPT_NAME'];
+			} else if (($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
+				$this->var['PHP_SELF'] = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
+			} else if (isset($_SERVER['DOCUMENT_ROOT']) && str_starts_with($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT'])) {
+				$this->var['PHP_SELF'] = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME']));
+				$this->var['PHP_SELF'][0] != '/' && $this->var['PHP_SELF'] = '/' . $this->var['PHP_SELF'];
+			} else {
+				system_error('request_tainting');
+			}
+		}
+		return $this->var['PHP_SELF'];
+	}
+
+	private function _init_input() {
+		if (isset($_GET['GLOBALS']) || isset($_POST['GLOBALS']) || isset($_COOKIE['GLOBALS']) || isset($_FILES['GLOBALS'])) {
+			system_error('request_tainting');
+		}
+
+		$prelength = strlen($this->config['cookie']['cookiepre']);
+		foreach ($_COOKIE as $key => $val) {
+			if (substr($key, 0, $prelength) == $this->config['cookie']['cookiepre']) {
+				$this->var['cookie'][substr($key, $prelength)] = $val;
+			}
+		}
+
+		if ($_SERVER['REQUEST_METHOD'] == 'POST' && !empty($_POST)) {
+			foreach ($_POST as $k => $v) {
+				$_GET[$k] = $v;
+			}
+		}
+	}
+
+	private function _init_output() {
+
+		if (!empty($_SERVER['HTTP_ACCEPT_ENCODING']) && !str_contains($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
+			$this->config['output']['gzip'] = false;
+		}
+
+		$allowgzip = $this->config['output']['gzip'] && empty($this->var['inajax']) && $this->var['mod'] != 'attachment' && EXT_OBGZIP;
+		setglobal('gzipcompress', $allowgzip);
+
+		if (!ob_start($allowgzip ? 'ob_gzhandler' : null)) {
+			ob_start();
+		}
+
+		setglobal('charset', $this->config['output']['charset']);
+		define('CHARSET', $this->config['output']['charset']);
+		if ($this->config['output']['forceheader']) {
+			@header('Content-Type: text/html; charset=' . CHARSET);
+		}
+
+		if ($this->var['isHTTPS'] && isset($this->config['output']['upgradeinsecure']) && $this->config['output']['upgradeinsecure']) {
+			@header('Content-Security-Policy: upgrade-insecure-requests');
+		}
+
+	}
+
+	public function reject_robot() {
+		if (IS_ROBOT) {
+			exit(header('HTTP/1.1 403 Forbidden'));
+		}
+	}
+
+	private function _is_https() {
+		// PHP 标准服务器变量
+		if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') {
+			return true;
+		}
+		// X-Forwarded-Proto 事实标准头部, 用于反代透传 HTTPS 状态
+		if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') {
+			return true;
+		}
+		// 阿里云全站加速私有 HTTPS 状态头部
+		// Git 意见反馈 https://gitee.com/Discuz/DiscuzX/issues/I3W5GP
+		if (isset($_SERVER['HTTP_X_CLIENT_SCHEME']) && strtolower($_SERVER['HTTP_X_CLIENT_SCHEME']) == 'https') {
+			return true;
+		}
+		// 西部数码建站助手私有 HTTPS 状态头部
+		// 官网意见反馈 https://discuz.dismall.com/thread-3849819-1-1.html
+		if (isset($_SERVER['HTTP_FROM_HTTPS']) && strtolower($_SERVER['HTTP_FROM_HTTPS']) != 'off') {
+			return true;
+		}
+		// 服务器端口号兜底判断
+		if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
+			return true;
+		}
+		return false;
+	}
+
+	private function _get_client_ip() {
+		$ip = $_SERVER['REMOTE_ADDR'];
+		if (!array_key_exists('security', $this->config) || !$this->config['security']['onlyremoteaddr']) {
+			if (array_key_exists('ipgetter', $this->config) && !empty($this->config['ipgetter']['setting'])) {
+				$s = empty($this->config['ipgetter'][$this->config['ipgetter']['setting']]) ? [] : $this->config['ipgetter'][$this->config['ipgetter']['setting']];
+				$c = 'ip_getter_' . $this->config['ipgetter']['setting'];
+				$r = $c::get($s);
+				$ip = ip::validate_ip($r) ? $r : $ip;
+			} elseif (isset($_SERVER['HTTP_CLIENT_IP']) && ip::validate_ip($_SERVER['HTTP_CLIENT_IP'])) {
+				$ip = $_SERVER['HTTP_CLIENT_IP'];
+			} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+				if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') > 0) {
+					$exp = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+					$ip = ip::validate_ip(trim($exp[0])) ? $exp[0] : $ip;
+				} else {
+					$ip = ip::validate_ip($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $ip;
+				}
+			}
+		}
+		return $ip;
+	}
+
+	private function _init_db() {
+		if ($this->init_db && $this->config) {
+			if ($this->config['db']['driver'] == 'pdo' && class_exists('PDO')) {
+				$driver = 'db_driver_pdo';
+				if (getglobal('config/db/slave')) {
+					$driver = 'db_driver_pdo_slave';
+				}
+				$this->var['db_driver'] = 'pdo';
+			} else {
+				$driver = 'db_driver_mysqli';
+				if (getglobal('config/db/slave')) {
+					$driver = 'db_driver_mysqli_slave';
+				}
+				$this->var['db_driver'] = 'mysqli';
+			}
+
+			$this->var['mysql_driver'] = $driver;
+			DB::init($driver, $this->config['db']);
+		}
+	}
+
+}
+

+ 62 - 0
source/class/discuz/discuz_base.php

@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+abstract class discuz_base {
+	private $_e;
+	private $_m;
+
+	public function __construct() {
+
+	}
+
+	public function __set($name, $value) {
+		$setter = 'set'.$name;
+		if(method_exists($this, $setter)) {
+			return $this->$setter($value);
+		} elseif($this->canGetProperty($name)) {
+			throw new Exception('The property "'.get_class($this).'->'.$name.'" is readonly');
+		} else {
+			throw new Exception('The property "'.get_class($this).'->'.$name.'" is not defined');
+		}
+	}
+
+	public function __get($name) {
+		$getter = 'get'.$name;
+		if(method_exists($this, $getter)) {
+			return $this->$getter();
+		} else {
+			throw new Exception('The property "'.get_class($this).'->'.$name.'" is not defined');
+		}
+	}
+
+	public function __call($name, $parameters) {
+		throw new Exception('Class "'.get_class($this).'" does not have a method named "'.$name.'".');
+	}
+
+	public function canGetProperty($name) {
+		return method_exists($this, 'get'.$name);
+	}
+
+	public function canSetProperty($name) {
+		return method_exists($this, 'set'.$name);
+	}
+
+	public function __toString() {
+		return get_class($this);
+	}
+
+	public function __invoke() {
+		return get_class($this);
+	}
+
+}
+

+ 152 - 0
source/class/discuz/discuz_container.php

@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class discuz_container extends discuz_base {
+
+	protected $_obj;
+
+	protected $_objs = [];
+
+	public function __construct($obj = null) {
+		if(isset($obj)) {
+			if(is_object($obj)) {
+				$this->_obj = $obj;
+			} else if(is_string($obj)) {
+				try {
+					if(func_num_args()) {
+						$p = func_get_args();
+						unset($p[0]);
+						$ref = new ReflectionClass($obj);
+						$this->_obj = $ref->newInstanceArgs($p);
+						unset($ref);
+					} else {
+						$this->_obj = new $obj;
+					}
+				} catch (Exception $e) {
+					throw new Exception('Class "'.$obj.'" does not exists.');
+				}
+			}
+		}
+		parent::__construct();
+	}
+
+	public function getobj() {
+		return $this->_obj;
+	}
+
+	public function setobj($value) {
+		$this->_obj = $value;
+	}
+
+	public function __call($name, $p) {
+		if(method_exists($this->_obj, $name)) {
+			if(isset($this->_obj->methods[$name][0])) {
+				$this->_call($name, $p, 0);
+			}
+			$this->_obj->data = match (count($p)) {
+				0 => $this->_obj->{$name}(),
+				1 => $this->_obj->{$name}($p[0]),
+				2 => $this->_obj->{$name}($p[0], $p[1]),
+				3 => $this->_obj->{$name}($p[0], $p[1], $p[2]),
+				4 => $this->_obj->{$name}($p[0], $p[1], $p[2], $p[3]),
+				5 => $this->_obj->{$name}($p[0], $p[1], $p[2], $p[3], $p[4]),
+				default => call_user_func_array([$this->_obj, $name], $p),
+			};
+			if(isset($this->_obj->methods[$name][1])) {
+				$this->_call($name, $p, 1);
+			}
+
+			return $this->_obj->data;
+		} else {
+			throw new Exception('Class "'.get_class($this->_obj).'" does not have a method named "'.$name.'".');
+		}
+	}
+
+	protected function _call($name, $p, $type) {
+		$ret = null;
+		if(isset($this->_obj->methods[$name][$type])) {
+			foreach($this->_obj->methods[$name][$type] as $extend) {
+				if(is_array($extend) && isset($extend['class'])) {
+					$obj = $this->_getobj($extend['class'], $this->_obj);
+					$ret = match (count($p)) {
+						0 => $obj->{$extend['method']}(),
+						1 => $obj->{$extend['method']}($p[0]),
+						2 => $obj->{$extend['method']}($p[0], $p[1]),
+						3 => $obj->{$extend['method']}($p[0], $p[1], $p[2]),
+						4 => $obj->{$extend['method']}($p[0], $p[1], $p[2], $p[3]),
+						5 => $obj->{$extend['method']}($p[0], $p[1], $p[2], $p[3], $p[4]),
+						default => call_user_func_array([$obj, $extend['method']], $p),
+					};
+				} elseif(is_callable($extend, true)) {
+					if(is_array($extend)) {
+						list($obj, $method) = $extend;
+						if(method_exists($obj, $method)) {
+							if(is_object($obj)) {
+								$obj->obj = $this->_obj;
+								$ret = match (count($p)) {
+									0 => $obj->{$method}(),
+									1 => $obj->{$method}($p[0]),
+									2 => $obj->{$method}($p[0], $p[1]),
+									3 => $obj->{$method}($p[0], $p[1], $p[2]),
+									4 => $obj->{$method}($p[0], $p[1], $p[2], $p[3]),
+									5 => $obj->{$method}($p[0], $p[1], $p[2], $p[3], $p[4]),
+									default => call_user_func_array([$obj, $method], $p),
+								};
+							} else {
+								$p[] = $this;
+								$ret = call_user_func_array($extend, $p);
+							}
+						}/* else {
+							throw new Exception('Class "'.get_class($extend[0]).'" does not have a method named "'.$extend[1].'".');
+						}*/
+					} else {
+						$p[] = $this->_obj;
+						$ret = call_user_func_array($extend, $p);
+					}
+				}
+			}
+		}
+		return $ret;
+	}
+
+	protected function _getobj($class, $obj) {
+		if(!isset($this->_objs[$class])) {
+			$this->_objs[$class] = new $class($obj);
+			if(method_exists($this->_objs[$class], 'init_base_var')) {
+				$this->_objs[$class]->init_base_var();
+			}
+		}
+		return $this->_objs[$class];
+	}
+
+	public function __get($name) {
+		if(isset($this->_obj) && property_exists($this->_obj, $name) === true) {
+			return $this->_obj->$name;
+		} else {
+			return parent::__get($name);
+		}
+	}
+
+	public function __set($name, $value) {
+		if(isset($this->_obj) && property_exists($this->_obj, $name) === true) {
+			return $this->_obj->$name = $value;
+		} else {
+			return parent::__set($name, $value);
+		}
+	}
+
+	public function __isset($name) {
+		return isset($this->_obj->$name);
+	}
+
+}
+

+ 16 - 0
source/class/discuz/discuz_core.php

@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class discuz_core extends discuz_application {
+
+}
+

+ 623 - 0
source/class/discuz/discuz_database.php

@@ -0,0 +1,623 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class discuz_database {
+
+	public static $db;
+
+	public static $driver;
+
+	public static function init($driver, $config) {
+		self::$driver = $driver;
+		self::$db = new $driver;
+		self::$db->set_config($config);
+		self::$db->connect();
+	}
+
+	public static function object() {
+		return self::$db;
+	}
+
+	public static function table($table) {
+		return self::$db->table_name($table);
+	}
+
+	public static function delete($table, $condition, $limit = 0, $unbuffered = true) {
+		$arg = null;
+		if(empty($condition)) {
+			return false;
+		} elseif(is_array($condition)) {
+			if(count($condition) == 2 && isset($condition['where']) && isset($condition['arg'])) {
+				if(!self::is_pdo()) {
+					$where = self::format($condition['where'], $condition['arg']);
+				} else {
+					$where = self::format_prepared($condition['where'], $condition['arg']);
+				}
+			} else {
+				if(!self::is_pdo()) {
+					$where = self::implode_field_value($condition, ' AND ');
+				} else {
+					$where = self::implode_field_value_prepared($condition, $arg, ' AND ');
+				}
+			}
+		} else {
+			$where = $condition;
+		}
+		$limit = dintval($limit);
+		$sql = 'DELETE FROM '.self::table($table)." WHERE $where ".($limit > 0 ? "LIMIT $limit" : '');
+		return self::query($sql, $arg, false, $unbuffered);
+	}
+
+	public static function insert($table, $data, $return_insert_id = false, $replace = false, $silent = false) {
+		if(!self::is_pdo()) {
+			$sql = 'SET '.self::implode($data);
+			$arg = null;
+		} else {
+			$arg = [];
+			$sql = self::implode_prepared_insert($data, $arg);
+		}
+
+		$cmd = $replace ? 'REPLACE INTO' : 'INSERT INTO';
+
+		$table = self::table($table);
+		$silent = $silent ? 'SILENT' : '';
+
+		return self::query("$cmd $table $sql", $arg, $silent, !$return_insert_id);
+	}
+
+	public static function update($table, $data, $condition = '', $unbuffered = false, $low_priority = false) {
+		if(!self::is_pdo()) {
+			$sql = self::implode($data);
+			$arg = null;
+		} else {
+			$arg = [];
+			$sql = self::implode_prepared($data, $arg);
+		}
+
+		if(empty($sql)) {
+			return false;
+		}
+		$cmd = 'UPDATE '.($low_priority ? 'LOW_PRIORITY' : '');
+		$table = self::table($table);
+		$where = '';
+		if(empty($condition)) {
+			$where = '1';
+		} elseif(is_array($condition)) {
+			if(!self::is_pdo()) {
+				$where = self::implode($condition, ' AND ');
+			} else {
+				$where = self::implode_prepared($condition, $arg, ' AND ');
+			}
+		} else {
+			$where = $condition;
+		}
+		$res = self::query("$cmd $table SET $sql WHERE $where", $arg, false, $unbuffered);
+		return $res;
+	}
+
+	public static function insert_id() {
+		return self::$db->insert_id();
+	}
+
+	public static function fetch($resourceid, $type = null) {
+		if(!isset($type)) {
+			$type = constant('MYSQLI_ASSOC');
+		}
+		return self::$db->fetch_array($resourceid, $type);
+	}
+
+	public static function fetch_first($sql, $arg = [], $silent = false) {
+		$res = self::query($sql, $arg, $silent, false);
+		if($res === 0) {
+			return [];
+		}
+		$ret = self::$db->fetch_array($res);
+		self::$db->free_result($res);
+		return $ret ? $ret : [];
+	}
+
+	public static function fetch_all($sql, $arg = [], $keyfield = '', $silent = false) {
+
+		$data = [];
+		$query = self::query($sql, $arg, $silent, false);
+		while($row = self::$db->fetch_array($query)) {
+			if($keyfield && isset($row[$keyfield])) {
+				$data[$row[$keyfield]] = $row;
+			} else {
+				$data[] = $row;
+			}
+		}
+		self::$db->free_result($query);
+		return $data;
+	}
+
+	public static function result($resourceid, $row = 0) {
+		return self::$db->result($resourceid, $row);
+	}
+
+	public static function result_first($sql, $arg = [], $silent = false) {
+		$res = self::query($sql, $arg, $silent, false);
+		$ret = self::$db->result($res, 0);
+		self::$db->free_result($res);
+		return $ret;
+	}
+
+	public static function query($sql, $arg = [], $silent = false, $unbuffered = false) {
+		if(!empty($arg)) {
+			if(is_array($arg)) {
+				if(!self::is_pdo()) {
+					$sql = self::format($sql, $arg);
+				} else {
+					$sql = self::format_prepared($sql, $arg);
+				}
+			} elseif($arg === 'SILENT') {
+				$silent = true;
+				$arg = [];
+			} elseif($arg === 'UNBUFFERED') {
+				$unbuffered = true;
+				$arg = [];
+			}
+		}
+		self::checkquery($sql);
+
+		$ret = self::$db->query(!self::is_pdo() ? $sql : [$sql, $arg], $silent, $unbuffered);
+		if(!$unbuffered && $ret) {
+			$cmd = trim(strtoupper(substr($sql, 0, strpos($sql, ' '))));
+			if($cmd === 'SELECT') {
+
+			} elseif($cmd === 'UPDATE' || $cmd === 'DELETE') {
+				$ret = self::$db->affected_rows();
+			} elseif($cmd === 'INSERT') {
+				$ret = self::$db->insert_id();
+			}
+		}
+		return $ret;
+	}
+
+	public static function num_rows($resourceid) {
+		return self::$db->num_rows($resourceid);
+	}
+
+	public static function affected_rows() {
+		return self::$db->affected_rows();
+	}
+
+	public static function free_result($query) {
+		return self::$db->free_result($query);
+	}
+
+	public static function error() {
+		return self::$db->error();
+	}
+
+	public static function errno() {
+		return self::$db->errno();
+	}
+
+	public static function checkquery($sql) {
+		return discuz_database_safecheck::checkquery($sql);
+	}
+
+	public static function quote($str, $noarray = false) {
+
+		if(is_string($str))
+			return '\''.self::$db->escape_string($str).'\'';
+
+		if(is_int($str) or is_float($str))
+			return '\''.$str.'\'';
+
+		if(is_array($str)) {
+			if($noarray === false) {
+				foreach($str as &$v) {
+					$v = self::quote($v, true);
+				}
+				return $str;
+			} else {
+				return '\'\'';
+			}
+		}
+
+		if(is_bool($str))
+			return $str ? '1' : '0';
+
+		return '\'\'';
+	}
+
+	public static function quote_field($field) {
+		if(is_array($field)) {
+			foreach($field as $k => $v) {
+				$field[$k] = self::quote_field($v);
+			}
+		} else {
+			if(str_contains($field, '`'))
+				$field = str_replace('`', '', $field);
+			$field = '`'.$field.'`';
+		}
+		return $field;
+	}
+
+	public static function limit($start, $limit = 0) {
+		$limit = intval($limit > 0 ? $limit : 0);
+		$start = intval($start > 0 ? $start : 0);
+		if($start > 0 && $limit > 0) {
+			return " LIMIT $start, $limit";
+		} elseif($limit) {
+			return " LIMIT $limit";
+		} elseif($start) {
+			return " LIMIT $start";
+		} else {
+			return '';
+		}
+	}
+
+	public static function order($field, $order = 'ASC') {
+		if(empty($field)) {
+			return '';
+		}
+		$order = strtoupper($order) == 'ASC' || empty($order) ? 'ASC' : 'DESC';
+		return self::quote_field($field).' '.$order;
+	}
+
+	public static function field($field, $val, $glue = '=') {
+
+		$field = self::quote_field($field);
+
+		if(is_array($val)) {
+			$glue = $glue == 'notin' ? 'notin' : 'in';
+		} elseif($glue == 'in') {
+			$glue = '=';
+		}
+
+		switch($glue) {
+			case '>=':
+			case '=':
+				return $field.$glue.self::quote($val);
+				break;
+			case '-':
+			case '+':
+				return $field.'='.$field.$glue.self::quote((string)$val);
+				break;
+			case '|':
+			case '&':
+			case '^':
+			case '&~':
+				return $field.'='.$field.$glue.self::quote($val);
+				break;
+			case '>':
+			case '<':
+			case '<>':
+			case '<=':
+				return $field.$glue.self::quote($val);
+				break;
+
+			case 'like':
+				return $field.' LIKE('.self::quote($val).')';
+				break;
+
+			case 'in':
+			case 'notin':
+				$val = $val ? implode(',', self::quote($val)) : '\'\'';
+				return $field.($glue == 'notin' ? ' NOT' : '').' IN('.$val.')';
+				break;
+
+			default:
+				throw new DbException('Not allow this glue between field and value: "'.$glue.'"');
+		}
+	}
+
+	public static function is_pdo() {
+		return getglobal('db_driver') == 'pdo';
+	}
+
+	public static function implode($array, $glue = ',') {
+		$sql = $comma = '';
+		$glue = ' '.trim($glue).' ';
+		foreach($array as $k => $v) {
+			$sql .= $comma.self::quote_field($k).'='.self::quote($v);
+			$comma = $glue;
+		}
+		return $sql;
+	}
+
+	public static function implode_prepared_insert($array, &$arg, $glue = ',') {
+		$sql1 = $sql2 = $comma1 = $comma2 = '';
+		$glue = ' '.trim($glue).' ';
+		foreach($array as $k => $v) {
+			$sql1 .= $comma1.self::quote_field($k);
+			$sql2 .= $comma2.'?';
+			$arg[] = !is_object($v) ? $v : '';
+			$comma1 = $glue;
+			$comma2 = $glue;
+		}
+		return '('.$sql1.') VALUES ('.$sql2.')';
+	}
+
+	public static function implode_prepared($array, &$arg, $glue = ',') {
+		$sql = $comma = '';
+		$glue = ' '.trim($glue).' ';
+		foreach($array as $k => $v) {
+			$sql .= $comma.self::quote_field($k).'=?';
+			$arg[] = !is_object($v) ? $v : '';
+			$comma = $glue;
+		}
+		return $sql;
+	}
+
+	public static function implode_field_value($array, $glue = ',') {
+		return self::implode($array, $glue);
+	}
+
+	public static function implode_field_value_prepared($array, &$arg, $glue = ',') {
+		return self::implode_prepared($array, $arg, $glue);
+	}
+
+	public static function format($sql, $arg) {
+		$count = substr_count($sql, '%');
+		if(!$count) {
+			return $sql;
+		} elseif($count > count($arg)) {
+			throw new DbException('SQL string format error! This SQL need "'.$count.'" vars to replace into.', 0, $sql);
+		}
+
+		$len = strlen($sql);
+		$i = $find = 0;
+		$ret = '';
+		while($i <= $len && $find < $count) {
+			if($sql[$i] == '%') {
+				$next = $sql[$i + 1];
+				if($next == 't') {
+					$ret .= self::table($arg[$find]);
+				} elseif($next == 's') {
+					$ret .= self::quote(is_array($arg[$find]) ? serialize($arg[$find]) : (string)$arg[$find]);
+				} elseif($next == 'f') {
+					$ret .= sprintf('%F', $arg[$find]);
+				} elseif($next == 'd') {
+					$ret .= dintval($arg[$find]);
+				} elseif($next == 'i') {
+					$ret .= $arg[$find];
+				} elseif($next == 'n') {
+					if(!empty($arg[$find])) {
+						$ret .= is_array($arg[$find]) ? implode(',', self::quote($arg[$find])) : self::quote($arg[$find]);
+					} else {
+						$ret .= '0';
+					}
+				} else {
+					$ret .= self::quote($arg[$find]);
+				}
+				$i++;
+				$find++;
+			} else {
+				$ret .= $sql[$i];
+			}
+			$i++;
+		}
+		if($i < $len) {
+			$ret .= substr($sql, $i);
+		}
+		return $ret;
+	}
+
+	public static function format_prepared($sql, &$arg) {
+		$params = [];
+		$count = substr_count($sql, '%');
+		if(!$count) {
+			return $sql;
+		} elseif($count > count($arg)) {
+			throw new DbException('SQL string format error! This SQL need "'.$count.'" vars to replace into.', 0, $sql);
+		}
+
+		$len = strlen($sql);
+		$i = $find = 0;
+		$ret = '';
+		while($i <= $len && $find < $count) {
+			if($sql[$i] == '%') {
+				$next = $sql[$i + 1];
+				$v = $arg[$find];
+				if($next == 't') {
+					$ret .= self::table($v);
+				} elseif($next == 's') {
+					$v = is_array($v) ? serialize($v) : (string)$v;
+					$ret .= '?';
+					$params[] = $v;
+				} elseif($next == 'f') {
+					$ret .= sprintf('%F', $v);
+				} elseif($next == 'd') {
+					$ret .= dintval($v);
+				} elseif($next == 'i') {
+					$ret .= $v;
+				} elseif($next == 'n') {
+					if(!empty($v)) {
+						if(is_array($v)) {
+							$_ret = [];
+							foreach($v as $_v) {
+								$_ret[] = '?';
+								$params[] = $_v;
+							}
+							$ret .= implode(',', $_ret);
+						} else {
+							$ret .= '?';
+							$params[] = $v;
+						}
+					} else {
+						$ret .= '0';
+					}
+				} else {
+					$ret .= '?';
+					$params[] = $v;
+				}
+				$i++;
+				$find++;
+			} else {
+				$ret .= $sql[$i];
+			}
+			$i++;
+		}
+		if($i < $len) {
+			$ret .= substr($sql, $i);
+		}
+		$arg = $params;
+		return $ret;
+	}
+
+	public static function begin_transaction() {
+		return self::$db->begin_transaction();
+	}
+
+	public static function commit() {
+		return self::$db->commit();
+	}
+
+	public static function rollback() {
+		return self::$db->rollback();
+	}
+
+}
+
+class discuz_database_safecheck {
+
+	protected static $checkcmd = ['SEL' => 1, 'UPD' => 1, 'INS' => 1, 'REP' => 1, 'DEL' => 1];
+	protected static $config;
+
+	public static function checkquery($sql) {
+		if(is_array($sql)) {
+			$sql = $sql[0];
+		}
+		if(self::$config === null) {
+			self::$config = getglobal('config/security/querysafe');
+		}
+		if(self::$config['status']) {
+			$check = 1;
+			$cmd = strtoupper(substr(trim($sql), 0, 3));
+			if(isset(self::$checkcmd[$cmd])) {
+				$check = self::_do_query_safe($sql);
+			} elseif(str_starts_with($cmd, '/*')) {
+				$check = -1;
+			}
+
+			if($check < 1) {
+				throw new DbException('It is not safe to do this query', 0, $sql);
+			}
+		}
+		return true;
+	}
+
+	private static function _do_query_safe($sql) {
+		$sql = str_replace(['\\\\', '\\\'', '\\"', '\'\''], '', $sql);
+		$mark = $clean = '';
+		if(!str_contains($sql, '/') && !str_contains($sql, '#') && !str_contains($sql, '-- ') && !str_contains($sql, '@') && !str_contains($sql, '`') && !str_contains($sql, '"')) {
+			$clean = preg_replace("/'(.+?)'/s", '', $sql);
+		} else {
+			$len = strlen($sql);
+			$mark = $clean = '';
+			for($i = 0; $i < $len; $i++) {
+				$str = $sql[$i];
+				switch($str) {
+					case '`':
+						if(!$mark) {
+							$mark = '`';
+							$clean .= $str;
+						} elseif($mark == '`') {
+							$mark = '';
+						}
+						break;
+					case '\'':
+						if(!$mark) {
+							$mark = '\'';
+							$clean .= $str;
+						} elseif($mark == '\'') {
+							$mark = '';
+						}
+						break;
+					case '/':
+						if(empty($mark) && $sql[$i + 1] == '*') {
+							$mark = '/*';
+							$clean .= $mark;
+							$i++;
+						} elseif($mark == '/*' && $sql[$i - 1] == '*') {
+							$mark = '';
+							$clean .= '*';
+						}
+						break;
+					case '#':
+						if(empty($mark)) {
+							$mark = $str;
+							$clean .= $str;
+						}
+						break;
+					case "\n":
+						if($mark == '#' || $mark == '--') {
+							$mark = '';
+						}
+						break;
+					case '-':
+						if(empty($mark) && substr($sql, $i, 3) == '-- ') {
+							$mark = '-- ';
+							$clean .= $mark;
+						}
+						break;
+
+					default:
+
+						break;
+				}
+				$clean .= $mark ? '' : $str;
+			}
+		}
+
+		if(str_contains($clean, '@')) {
+			return '-3';
+		}
+
+		$clean = preg_replace("/[^a-z0-9_\-\(\)#\*\/\"]+/is", '', strtolower($clean));
+
+		if(self::$config['afullnote']) {
+			$clean = str_replace('/**/', '', $clean);
+		}
+
+		if(is_array(self::$config['dfunction'])) {
+			foreach(self::$config['dfunction'] as $fun) {
+				if(str_contains($clean, $fun.'('))
+					return '-1';
+			}
+		}
+
+		if(is_array(self::$config['daction'])) {
+			foreach(self::$config['daction'] as $action) {
+				if(str_contains($clean, $action))
+					return '-3';
+			}
+		}
+
+		if(self::$config['dlikehex'] && strpos($clean, 'like0x')) {
+			return '-2';
+		}
+
+		if(is_array(self::$config['dnote'])) {
+			foreach(self::$config['dnote'] as $note) {
+				if(str_contains($clean, $note))
+					return '-4';
+			}
+		}
+
+		return 1;
+	}
+
+	public static function setconfigstatus($data) {
+		self::$config['status'] = $data ? 1 : 0;
+	}
+
+}
+
+class discuz_database_prepared {
+
+}
+

+ 352 - 0
source/class/discuz/discuz_error.php

@@ -0,0 +1,352 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if (!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+class discuz_error {
+
+	public static function system_error($message, $show = false, $save = true, $halt = true) {
+		list($showtrace, $logtrace) = discuz_error::debug_backtrace();
+
+		if ($save) {
+			$messagesave = '<b>' . $message . '</b><br><b>PHP:</b>' . $logtrace;
+			discuz_error::write_error_log($messagesave);
+		}
+
+		if (!empty($GLOBALS['_G']['config']['security']['error']['showerror']) && $halt) {
+			$show = true;
+		}
+
+		if ($show) {
+			discuz_error::show_error('system', "<li>$message</li>", $showtrace, '', md5(discuz_error::clear($messagesave)));
+		}
+
+		if ($halt && 0) {
+			header('HTTP/1.1 503 Service Unavailable');
+			exit();
+		} else {
+			return $message;
+		}
+	}
+
+	public static function template_error($message, $tplname) {
+		$tplname = str_replace(DISCUZ_ROOT, '', $tplname);
+		$message = $message . ': ' . $tplname;
+		discuz_error::system_error($message);
+	}
+
+	public static function debug_backtrace() {
+		$skipfunc[] = 'discuz_error->debug_backtrace';
+		$skipfunc[] = 'discuz_error->db_error';
+		$skipfunc[] = 'discuz_error->template_error';
+		$skipfunc[] = 'discuz_error->system_error';
+		$skipfunc[] = 'db_mysql->halt';
+		$skipfunc[] = 'db_mysql->query';
+		$skipfunc[] = 'DB::_execute';
+
+		$show = $log = '';
+		$debug_backtrace = debug_backtrace();
+		krsort($debug_backtrace);
+		foreach ($debug_backtrace as $k => $error) {
+			$file = str_replace(DISCUZ_ROOT, '', $error['file']);
+			$func = $error['class'] ?? '';
+			$func .= $error['type'] ?? '';
+			$func .= $error['function'] ?? '';
+			if (in_array($func, $skipfunc)) {
+				break;
+			}
+			$error['line'] = sprintf('%04d', $error['line']);
+
+			$show .= "<li>[Line: {$error['line']}]" . $file . "($func)</li>";
+			$log .= (!empty($log) ? ' -> ' : '') . $file . '#' . $func . ':' . $error['line'];
+		}
+		return [$show, $log];
+	}
+
+	public static function db_error($message, $sql) {
+		global $_G;
+
+		list($showtrace, $logtrace) = discuz_error::debug_backtrace();
+
+		$title = lang('error', 'db_' . $message);
+		$title_msg = lang('error', 'db_error_message');
+		$title_sql = lang('error', 'db_query_sql');
+		$title_backtrace = lang('error', 'backtrace');
+		$title_help = lang('error', 'db_help_link');
+
+		$db = &DB::object();
+		$dberrno = $db->errno();
+		$dberror = str_replace($db->tablepre, '', $db->error());
+		$sql = dhtmlspecialchars(str_replace($db->tablepre, '', $sql));
+
+		$msg = '<li>[Type] ' . $title . '</li>';
+		$msg .= $dberrno ? '<li>[' . $dberrno . '] ' . $dberror . '</li>' : '';
+		$msg .= $sql ? '<li>[Query] ' . $sql . '</li>' : '';
+
+		$errormsg = '<b>' . $title . '</b>';
+		$errormsg .= "[$dberrno]<br /><b>ERR:</b> $dberror<br />";
+		if ($sql) {
+			$errormsg .= '<b>SQL:</b> ' . $sql;
+		}
+		$errormsg .= '<br />';
+		$errormsg .= '<b>PHP:</b> ' . $logtrace;
+
+		discuz_error::write_error_log($errormsg);
+		discuz_error::show_error('db', $msg, $showtrace, '', md5(discuz_error::clear($errormsg)));
+		exit();
+
+	}
+
+	public static function exception_error($exception) {
+
+		if ($exception instanceof DbException) {
+			$type = 'db';
+		} else {
+			$type = 'system';
+		}
+
+		if ($type == 'db') {
+			$errormsg = '(' . $exception->getCode() . ') ';
+			$errormsg .= self::sql_clear($exception->getMessage());
+			if ($exception->getSql()) {
+				$errormsg .= '<div class="sql">';
+				$errormsg .= self::sql_clear($exception->getSql());
+				$errormsg .= '</div>';
+			}
+		} else {
+			$errormsg = $exception->getMessage();
+		}
+
+		$trace = $exception->getTrace();
+		krsort($trace);
+
+		$trace[] = ['file' => $exception->getFile(), 'line' => $exception->getLine(), 'function' => 'break'];
+		$logmsg = '';
+		$phpmsg = [];
+		foreach ($trace as $error) {
+			if (!empty($error['function'])) {
+				$fun = '';
+				if (!empty($error['class'])) {
+					$fun .= $error['class'] . $error['type'];
+				}
+				$fun .= $error['function'] . '(';
+				if (!empty($error['args'])) {
+					$mark = '';
+					foreach ($error['args'] as $arg) {
+						$fun .= $mark;
+						if (is_array($arg)) {
+							$fun .= 'Array';
+						} elseif (is_bool($arg)) {
+							$fun .= $arg ? 'true' : 'false';
+						} elseif (is_int($arg)) {
+							$fun .= (defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) ? $arg : '%d';
+						} elseif (is_float($arg)) {
+							$fun .= (defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) ? $arg : '%f';
+						} elseif (is_resource($arg)) {
+							$fun .= (defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) ? 'Resource' : '%f';
+						} elseif (is_object($arg)) {
+							$fun .= (defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) ? 'Object' : '%f';
+						} else {
+							$arg = (string)$arg;
+							$fun .= (defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) ? '\'' . dhtmlspecialchars(substr(self::clear($arg), 0, 10)) . (strlen($arg) > 10 ? ' ...' : '') . '\'' : '%s';
+						}
+						$mark = ', ';
+					}
+				}
+
+				$fun .= ')';
+				$error['function'] = $fun;
+			}
+			$phpmsg[] = [
+				'file' => str_replace([DISCUZ_ROOT, '\\'], ['', '/'], $error['file']),
+				'line' => $error['line'],
+				'function' => $error['function'],
+			];
+			$file = str_replace([DISCUZ_ROOT, '\\'], ['', '/'], $error['file']);
+			$func = $error['class'] ?? '';
+			$func .= $error['type'] ?? '';
+			$func .= $error['function'] ?? '';
+			$line = sprintf('%04d', $error['line']);
+			$logmsg .= (!empty($logmsg) ? ' -> ' : '') . $file . '#' . $func . ':' . $line;
+		}
+
+		$messagesave = '<b>' . $errormsg . '</b><br><b>PHP:</b>' . $logmsg;
+		self::write_error_log($messagesave);
+
+		self::show_error($type, $errormsg, $phpmsg, '', md5(discuz_error::clear($messagesave)));
+		exit();
+
+	}
+
+	public static function show_error($type, $errormsg, $phpmsg = '', $typemsg = '', $backtraceid = '') {
+		global $_G;
+
+		ob_end_clean();
+		$gzip = getglobal('gzipcompress');
+		ob_start($gzip ? 'ob_gzhandler' : null);
+
+		header('HTTP/1.1 503 Service Temporarily Unavailable');
+		header('Status: 503 Service Temporarily Unavailable');
+		header('Retry-After: 3600');
+
+		$host = $_SERVER['HTTP_HOST'];
+		$title = (!isset($_G['config']['security']['error']['showerror']) || !empty($_G['config']['security']['error']['showerror'])) ? ($type == 'db' ? 'Database' : 'System') : 'General';
+		echo <<<EOT
+<!DOCTYPE html>
+<html>
+<head>
+	<title>$host - $title Error</title>
+	<meta charset="{$_G['config']['output']['charset']}" />
+	<meta name="renderer" content="webkit" />
+	<meta http-equiv="X-UA-Compatible" content="IE=edge" />
+	<meta name="ROBOTS" content="NOINDEX,NOFOLLOW,NOARCHIVE" />
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<style type="text/css">
+	<!--
+	body { background-color: white; color: black; font: 9pt/11pt verdana, arial, sans-serif;}
+	#container { max-width: 1024px; margin: auto; }
+	#message   { max-width: 1024px; color: black; }
+
+	.red  {color: red;}
+	a:link     { font: 9pt/11pt verdana, arial, sans-serif; color: red; }
+	a:visited  { font: 9pt/11pt verdana, arial, sans-serif; color: #4e4e4e; }
+	a.guess { font: 11pt/13pt verdana, arial, sans-serif; color: blue; }
+	h1 { color: #FF0000; font: 18pt "Verdana"; margin-bottom: 0.5em;}
+	.bg1{ background-color: #FFFFCC;}
+	.bg2{ background-color: #EEEEEE;}
+	.bg3{ background-color: #FFA66C; font-weight: bold;}
+	.table {background: #AAAAAA; font: 11pt Menlo,Consolas,"Lucida Console";}
+	.table tbody{word-break: break-all;}
+	.info {
+	    background: none repeat scroll 0 0 #F3F3F3;
+	    border: 0px solid #aaaaaa;
+	    border-radius: 10px 10px 10px 10px;
+	    color: #000000;
+	    font-size: 11pt;
+	    line-height: 160%;
+	    margin-bottom: 1em;
+	    padding: 1em;
+	}
+	.info svg { width: 40%; min-width: 200px; display: block; margin: auto; margin-bottom: 30px; fill: #999; }
+	.info svg .xicon { fill: #d31f0d; }
+
+	.help {
+	    background: #F3F3F3;
+	    border-radius: 10px 10px 10px 10px;
+	    font: 14px verdana, arial, sans-serif;
+	    text-align: center;
+	    line-height: 160%;
+	    padding: 1em;
+	    margin: 1em 0;
+	}
+
+	.sql {
+	    background: none repeat scroll 0 0 #FFFFCC;
+	    border: 1px solid #aaaaaa;
+	    color: #000000;
+	    font: arial, sans-serif;
+	    font-size: 9pt;
+	    line-height: 160%;
+	    margin-top: 1em;
+	    padding: 4px;
+	}
+	-->
+	</style>
+</head>
+<body>
+<div id="container">
+<h1>Discuz! $title Error</h1>
+EOT;
+
+		echo '<p>Time: ' . date('Y-m-d H:i:s O') . ' IP: ' . getglobal('clientip') . ' BackTraceID: ' . $backtraceid . '</p>';
+
+		if (!empty($errormsg) && (!isset($_G['config']['security']['error']['showerror']) || !empty($_G['config']['security']['error']['showerror']))) {
+			echo '<div class="info">' . $errormsg . '</div>';
+		}
+		if (isset($_G['config']['security']['error']['showerror']) && empty($_G['config']['security']['error']['showerror'])) {
+			echo '<div class="info"><svg viewBox="0 0 16 16"><path d="M2.5 5a.5.5 0 100-1 .5.5 0 000 1zM4 5a.5.5 0 100-1 .5.5 0 000 1zm2-.5a.5.5 0 11-1 0 .5.5 0 011 0zM0 4a2 2 0 012-2h11a2 2 0 012 2v4a.5.5 0 01-1 0V7H1v5a1 1 0 001 1h5.5a.5.5 0 010 1H2a2 2 0 01-2-2V4zm1 2h13V4a1 1 0 00-1-1H2a1 1 0 00-1 1v2z"/><path d="M16 12.5a3.5 3.5 0 11-7 0 3.5 3.5 0 017 0zm-4.854-1.354a.5.5 0 000 .708l.647.646-.647.646a.5.5 0 00.708.708l.646-.647.646.647a.5.5 0 00.708-.708l-.647-.646.647-.646a.5.5 0 00-.708-.708l-.646.647-.646-.647a.5.5 0 00-.708 0z" class="xicon"/></svg></div>';
+		}
+
+		if (!empty($phpmsg) && (!isset($_G['config']['security']['error']['showerror']) || $_G['config']['security']['error']['showerror'] == '1')) {
+			echo '<div class="info">';
+			echo '<p><strong>PHP Debug</strong></p>';
+			echo '<table cellpadding="5" cellspacing="1" width="100%" class="table">';
+			if (is_array($phpmsg)) {
+				echo '<tr class="bg2"><td>No.</td><td>File</td><td>Line</td><td>Code</td></tr>';
+				foreach ($phpmsg as $k => $msg) {
+					$k++;
+					$explode = explode('/', $msg['file']);
+					if (isset($explode['1']) && $explode['1'] == 'plugin') {
+						$guess = $explode['2'];
+						$bg = 'bg3';
+					} else {
+						$bg = 'bg1';
+					}
+					echo '<tr class="' . $bg . '">';
+					echo '<td>' . $k . '</td>';
+					echo '<td>' . $msg['file'] . '</td>';
+					echo '<td>' . $msg['line'] . '</td>';
+					echo '<td>' . $msg['function'] . '</td>';
+					echo '</tr>';
+				}
+			} else {
+				echo '<tr><td><ul>' . $phpmsg . '</ul></td></tr>';
+			}
+			echo '</table></div>';
+		}
+
+	}
+
+	public static function clear($message) {
+		return str_replace(["\t", "\r", "\n"], ' ', $message);
+	}
+
+	public static function sql_clear($message) {
+		$message = self::clear($message);
+		$message = str_replace(DB::object()->tablepre, '', $message);
+		$message = dhtmlspecialchars($message);
+		return $message;
+	}
+
+	public static function write_error_log($message) {
+		$message = discuz_error::clear($message);
+		$time = time();
+		$file = DISCUZ_DATA . './log/' . date("Ym") . '_errorlog.php';
+		$hash = md5($message);
+
+		$ip = getglobal('clientip');
+
+		$user = '<b>User:</b> IP=' . $ip . '; RIP:' . $_SERVER['REMOTE_ADDR'];
+		$uri = 'Request: ' . dhtmlspecialchars(discuz_error::clear($_SERVER['REQUEST_URI']));
+
+		$message = "<?PHP exit;?>\t{$time}\t$message\t$hash\t$user $uri\n";
+
+		if ($fp = @fopen($file, 'rb')) {
+			$lastlen = 50000;
+			$maxtime = 60 * 10;
+			$offset = filesize($file) - $lastlen;
+			if ($offset > 0) {
+				fseek($fp, $offset);
+			}
+			if ($data = fread($fp, $lastlen)) {
+				$array = explode("\n", $data);
+				if (is_array($array)) foreach ($array as $key => $val) {
+					$row = explode("\t", $val);
+					if ($row[0] != '<?PHP exit;?>') continue;
+					if ($row[3] == $hash && ($row[1] > $time - $maxtime)) {
+						return;
+					}
+				}
+			}
+		}
+		error_log($message, 3, $file);
+	}
+
+}

+ 244 - 0
source/class/discuz/discuz_table.php

@@ -0,0 +1,244 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+
+class discuz_table extends discuz_base {
+
+	public $data = [];
+
+	public $methods = [];
+
+	protected $_table;
+	protected $_pk;
+	protected $_pre_cache_key;
+	protected $_cache_ttl;
+	protected $_allowmem;
+
+	public function __construct($para = []) {
+		if(!empty($para)) {
+			$this->_table = $para['table'];
+			$this->_pk = $para['pk'];
+		}
+		if(isset($this->_pre_cache_key) && (($ttl = getglobal('setting/memory/'.$this->_table)) !== null || ($ttl = $this->_cache_ttl) !== null) && memory('check')) {
+			$this->_cache_ttl = $ttl;
+			$this->_allowmem = true;
+		}
+		$this->_init_extend();
+		parent::__construct();
+	}
+
+	public function getTable() {
+		return $this->_table;
+	}
+
+	public function setTable($name) {
+		return $this->_table = $name;
+	}
+
+	public function count() {
+		$count = (int)DB::result_first('SELECT count(*) FROM '.DB::table($this->_table));
+		return $count;
+	}
+
+	public function update($val, $data, $unbuffered = false, $low_priority = false) {
+		if(isset($val) && !empty($data) && is_array($data)) {
+			$this->checkpk();
+			$ret = DB::update($this->_table, $data, DB::field($this->_pk, $val), $unbuffered, $low_priority);
+			foreach((array)$val as $id) {
+				$this->update_cache($id, $data);
+			}
+			return $ret;
+		}
+		return !$unbuffered ? 0 : false;
+	}
+
+	public function delete($val, $unbuffered = false) {
+		$ret = false;
+		if(isset($val)) {
+			$this->checkpk();
+			$ret = DB::delete($this->_table, DB::field($this->_pk, $val), null, $unbuffered);
+			$this->clear_cache($val);
+		}
+		return $ret;
+	}
+
+	public function truncate() {
+		DB::query('TRUNCATE '.DB::table($this->_table));
+	}
+
+	public function insert($data, $return_insert_id = false, $replace = false, $silent = false) {
+		return DB::insert($this->_table, $data, $return_insert_id, $replace, $silent);
+	}
+
+	public function checkpk() {
+		if(!$this->_pk) {
+			throw new DbException('Table '.$this->_table.' has not PRIMARY KEY defined');
+		}
+	}
+
+	public function fetch($id, $force_from_db = false) {
+		$data = [];
+		if(!empty($id)) {
+			if($force_from_db || ($data = $this->fetch_cache($id)) === false) {
+				$data = DB::fetch_first('SELECT * FROM '.DB::table($this->_table).' WHERE '.DB::field($this->_pk, $id));
+				if(!empty($data)) $this->store_cache($id, $data);
+			}
+		}
+		return $data;
+	}
+
+	public function fetch_all($ids, $force_from_db = false) {
+		$data = [];
+		if(!empty($ids)) {
+			if($force_from_db || ($data = $this->fetch_cache($ids)) === false || count($ids) != count($data)) {
+				if(is_array($data) && !empty($data)) {
+					$ids = array_diff($ids, array_keys($data));
+				}
+				if($data === false) $data = [];
+				if(!empty($ids)) {
+					$query = DB::query('SELECT * FROM '.DB::table($this->_table).' WHERE '.DB::field($this->_pk, $ids));
+					while($value = DB::fetch($query)) {
+						$data[$value[$this->_pk]] = $value;
+						$this->store_cache($value[$this->_pk], $value);
+					}
+				}
+			}
+		}
+		return $data;
+	}
+
+	public function fetch_all_field() {
+		$data = false;
+		$query = DB::query('SHOW FIELDS FROM '.DB::table($this->_table), '', 'SILENT');
+		if($query) {
+			$data = [];
+			while($value = DB::fetch($query)) {
+				$data[$value['Field']] = $value;
+			}
+		}
+		return $data;
+	}
+
+	public function range($start = 0, $limit = 0, $sort = '') {
+		if($sort) {
+			$this->checkpk();
+		}
+		return DB::fetch_all('SELECT * FROM '.DB::table($this->_table).($sort ? ' ORDER BY '.DB::order($this->_pk, $sort) : '').DB::limit($start, $limit), null, $this->_pk ? $this->_pk : '');
+	}
+
+	public function optimize() {
+		DB::query('OPTIMIZE TABLE '.DB::table($this->_table), 'SILENT');
+	}
+
+	public function fetch_cache($ids, $pre_cache_key = null) {
+		$data = false;
+		if($this->_allowmem) {
+			if($pre_cache_key === null) $pre_cache_key = $this->_pre_cache_key;
+			$data = memory('get', $ids, $pre_cache_key);
+		}
+		return $data;
+	}
+
+	public function store_cache($id, $data, $cache_ttl = null, $pre_cache_key = null) {
+		$ret = false;
+		if($this->_allowmem) {
+			if($pre_cache_key === null) $pre_cache_key = $this->_pre_cache_key;
+			if($cache_ttl === null) $cache_ttl = $this->_cache_ttl;
+			$ret = memory('set', $id, $data, $cache_ttl, $pre_cache_key);
+		}
+		return $ret;
+	}
+
+	public function clear_cache($ids, $pre_cache_key = null) {
+		$ret = false;
+		if($this->_allowmem) {
+			if($pre_cache_key === null) $pre_cache_key = $this->_pre_cache_key;
+			$ret = memory('rm', $ids, $pre_cache_key);
+		}
+		return $ret;
+	}
+
+	public function update_cache($id, $data, $cache_ttl = null, $pre_cache_key = null) {
+		$ret = false;
+		if($this->_allowmem) {
+			if($pre_cache_key === null) $pre_cache_key = $this->_pre_cache_key;
+			if($cache_ttl === null) $cache_ttl = $this->_cache_ttl;
+			if(($_data = memory('get', $id, $pre_cache_key)) !== false) {
+				$ret = $this->store_cache($id, array_merge($_data, $data), $cache_ttl, $pre_cache_key);
+			}
+		}
+		return $ret;
+	}
+
+	public function update_batch_cache($ids, $data, $cache_ttl = null, $pre_cache_key = null) {
+		$ret = false;
+		if($this->_allowmem) {
+			if($pre_cache_key === null) $pre_cache_key = $this->_pre_cache_key;
+			if($cache_ttl === null) $cache_ttl = $this->_cache_ttl;
+			if(($_data = memory('get', $ids, $pre_cache_key)) !== false) {
+				foreach($_data as $id => $value) {
+					$ret = $this->store_cache($id, array_merge($value, $data), $cache_ttl, $pre_cache_key);
+				}
+			}
+		}
+		return $ret;
+	}
+
+	public function reset_cache($ids, $pre_cache_key = null) {
+		$ret = false;
+		if($this->_allowmem) {
+			$keys = [];
+			if(($cache_data = $this->fetch_cache($ids, $pre_cache_key)) !== false) {
+				$keys = array_intersect(array_keys($cache_data), $ids);
+				unset($cache_data);
+			}
+			if(!empty($keys)) {
+				$this->fetch_all($keys, true);
+				$ret = true;
+			}
+		}
+		return $ret;
+	}
+
+	public function increase_cache($ids, $data, $cache_ttl = null, $pre_cache_key = null) {
+		if($this->_allowmem) {
+			if(($cache_data = $this->fetch_cache($ids, $pre_cache_key)) !== false) {
+				foreach($cache_data as $id => $one) {
+					foreach($data as $key => $value) {
+						if(is_array($value)) {
+							$one[$key] = $value[0];
+						} else {
+							$one[$key] = $one[$key] + ($value);
+						}
+					}
+					$this->store_cache($id, $one, $cache_ttl, $pre_cache_key);
+				}
+			}
+		}
+	}
+
+	public function __toString() {
+		return $this->_table;
+	}
+
+	protected function _init_extend() {
+	}
+
+	public function attach_before_method($name, $fn) {
+		$this->methods[$name][0][] = $fn;
+	}
+
+	public function attach_after_method($name, $fn) {
+		$this->methods[$name][1][] = $fn;
+	}
+}
+

+ 148 - 0
source/class/discuz/discuz_table_archive.php

@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+
+class discuz_table_archive extends discuz_table {
+
+	public $membersplit = null;
+
+	public function __construct($para = []) {
+		$this->membersplit = getglobal('setting/membersplit');
+		parent::__construct($para);
+	}
+
+	public $tablestatus = [];
+
+	public function fetch($id, $force_from_db = false, $fetch_archive = 0) {
+		$data = [];
+		if(!empty($id)) {
+			$data = parent::fetch($id, $force_from_db);
+			if(isset($this->membersplit) && $fetch_archive && empty($data)) {
+				$data = C::t($this->_table.'_archive')->fetch($id);
+			}
+		}
+		return $data;
+	}
+
+
+	public function fetch_all($ids, $force_from_db = false, $fetch_archive = 1) {
+		$data = [];
+		if(!empty($ids)) {
+			$data = parent::fetch_all($ids, $force_from_db);
+			if(isset($this->membersplit) && $fetch_archive && count($data) != count($ids)) {
+				$data = $data + C::t($this->_table.'_archive')->fetch_all(array_diff($ids, array_keys($data)));
+			}
+		}
+		return $data;
+	}
+
+
+	public function delete($val, $unbuffered = false, $fetch_archive = 0) {
+		$ret = false;
+		if($val) {
+			$ret = parent::delete($val, $unbuffered);
+			if(isset($this->membersplit) && $fetch_archive) {
+				$_ret = C::t($this->_table.'_archive')->delete($val, $unbuffered);
+				if(!$unbuffered) {
+					$ret = $ret + $_ret;
+				}
+			}
+		}
+		return $ret;
+	}
+
+	public function split_check($wheresql) {
+		$status = helper_dbtool::gettablestatus(DB::table($this->_table), false);
+		if($status && $status['Data_length'] > 100 * 1048576) {//400 * 1048576
+			if($moverows = DB::result_first('SELECT COUNT(*) FROM %t WHERE '.$wheresql, [$this->_table])) {
+				$status['Move_rows'] = $moverows;
+				$this->tablestatus = $status;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public function create_relatedtable($relatedtablename) {
+		if(!helper_dbtool::isexisttable($relatedtablename)) {
+			DB::query('SET SQL_QUOTE_SHOW_CREATE=0', 'SILENT');
+			$tableinfo = DB::fetch_first('SHOW CREATE TABLE '.DB::table($this->_table));
+			$createsql = $tableinfo['Create Table'];
+			$createsql = str_replace($this->_table, $relatedtablename, $createsql);
+			DB::query($createsql);
+		}
+		return true;
+	}
+
+	public function split_table($wheresql) {
+		$limit = 2000;
+		$targettable = helper_dbtool::showtablecloumn($this->_table);
+		$fieldstr = '`'.implode('`, `', array_keys($targettable)).'`';
+
+		if(!$this->_pk && !in_array('split_id', array_keys($targettable))) {
+			DB::query('ALTER TABLE %t ADD split_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, ADD UNIQUE KEY split_id (split_id)', [$this->_table]);
+			return 1;
+		}
+
+		$tmptable = $this->_table.'_tmp___';
+		$archivetable = $this->_table.'_archive';
+		$key = $this->_pk ? $this->_pk : 'split_id';
+		$this->create_relatedtable($tmptable);
+		$this->create_relatedtable($archivetable);
+		DB::query("INSERT INTO %t ($fieldstr) SELECT $fieldstr FROM %t WHERE $wheresql ".DB::limit($limit), [$tmptable, $this->_table]);
+		if(DB::result_first('SELECT COUNT(*) FROM %t', [$tmptable])) {
+			$keylist = DB::fetch_all('SELECT '.$key.' FROM %t', [$tmptable], $key);
+			$keylist = dimplode(array_keys($keylist));
+			if(DB::query("INSERT INTO %t ($fieldstr) SELECT $fieldstr FROM %t WHERE $key in ($keylist)", [$archivetable, $this->_table], false, true)) {
+				DB::query("DELETE FROM %t WHERE $key in ($keylist)", [$this->_table], false, true);
+			}
+			DB::query('DROP TABLE %t', [$tmptable]);
+			return 1;
+		} else {
+			DB::query('DROP TABLE %t', [$tmptable]);
+			$this->optimize();
+			return 2;
+		}
+	}
+
+	public function merge_table() {
+		$limit = 2000;
+
+		$tmptable = $this->_table.'_tmp___';
+		$archivetable = $this->_table.'_archive';
+		$key = $this->_pk ? $this->_pk : 'split_id';
+
+		if(!helper_dbtool::isexisttable($archivetable)) {
+			return 2;
+		}
+
+		$this->create_relatedtable($tmptable);
+		$targettable = helper_dbtool::showtablecloumn($this->_table);
+		$fieldstr = '`'.implode('`, `', array_keys($targettable)).'`';
+		DB::query("INSERT INTO %t ($fieldstr) SELECT $fieldstr FROM %t ".DB::limit($limit), [$tmptable, $archivetable]);
+		if(DB::result_first('SELECT COUNT(*) FROM %t', [$tmptable])) {
+			$keylist = DB::fetch_all('SELECT '.$key.' FROM %t', [$tmptable], $key);
+			$keylist = dimplode(array_keys($keylist));
+			if(DB::query("INSERT INTO %t ($fieldstr) SELECT $fieldstr FROM %t WHERE $key in ($keylist)", [$this->_table, $archivetable], false, true)) {
+				DB::query("DELETE FROM %t WHERE $key in ($keylist)", [$archivetable], false, true);
+			}
+			DB::query('DROP TABLE %t', [$tmptable]);
+			return 1;
+		} else {
+			DB::query('DROP TABLE %t', [$tmptable]);
+			DB::query('DROP TABLE %t', [$archivetable]);
+			$this->optimize();
+			return 2;
+		}
+	}
+}
+

+ 0 - 0
source/class/discuz/index.htm


+ 1 - 0
source/class/index.htm

@@ -0,0 +1 @@
+ 

+ 19 - 0
source/discuz_version.php

@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ')) {
+	exit('Access Denied');
+}
+
+if(!defined('DISCUZ_VERSION')) {
+	define('DISCUZ_VERSION_NAME', 'Discuz! &#x4F01;&#x98DE;&#x7248;');
+	define('DISCUZ_VERSION', 'X5.0');
+	define('DISCUZ_RELEASE', '20240801');
+	define('DISCUZ_FIXBUG', '50000000');
+}
+

+ 193 - 0
source/function/function_core.php

@@ -0,0 +1,193 @@
+<?php
+
+/**
+ * [Discuz!] (C)2001-2099 Discuz! Team
+ * This is NOT a freeware, use is subject to license terms
+ * https://license.discuz.vip
+ */
+
+if(!defined('IN_DISCUZ') && !defined('IN_API')) {
+	exit('Access Denied');
+}
+
+function DISCUZ_APP($app = '') {
+	return DISCUZ_ROOT.'./source/app'.($app !== '' ? '/'.$app : '');
+}
+
+function appfile($filename, $app = '') {
+	$app = $app ?: DISCUZ_APP;
+	$p = strpos($filename, '/');
+	if($p === false) {
+		return '';
+	}
+	$folder = substr($filename, 0, $p);
+
+	$apppath = '/source/app/'.$app;
+	$path = $apppath.'/'.$filename;
+
+	return preg_match('/^[\w\d\/_]+$/i', $path) ? realpath(DISCUZ_ROOT.$path.'.php') : false;
+}
+
+function setglobal($key, $value, $group = null) {
+	global $_G;
+	$key = explode('/', $group === null ? $key : $group.'/'.$key);
+	$p = &$_G;
+	foreach($key as $k) {
+		if(!isset($p[$k]) || !is_array($p[$k])) {
+			$p[$k] = [];
+		}
+		$p = &$p[$k];
+	}
+	$p = $value;
+	return true;
+}
+
+function getglobal($key, $group = null) {
+	global $_G;
+	$key = explode('/', $group === null ? $key : $group.'/'.$key);
+	$v = &$_G;
+	foreach($key as $k) {
+		if(!isset($v[$k])) {
+			return null;
+		}
+		$v = &$v[$k];
+	}
+	return $v;
+}
+
+function getgpc($k, $type = 'GP') {
+	$type = strtoupper($type);
+	switch($type) {
+		case 'G':
+			$var = &$_GET;
+			break;
+		case 'P':
+			$var = &$_POST;
+			break;
+		case 'C':
+			$var = &$_COOKIE;
+			break;
+		default:
+			if(isset($_GET[$k])) {
+				$var = &$_GET;
+			} else {
+				$var = &$_POST;
+			}
+			break;
+	}
+
+	return $var[$k] ?? NULL;
+
+}
+
+function dhtmlspecialchars($string, $flags = null) {
+	if(is_array($string)) {
+		foreach($string as $key => $val) {
+			$string[$key] = dhtmlspecialchars($val, $flags);
+		}
+	} else {
+		if($flags === null) {
+			$string = str_replace(['&', '"', '<', '>'], ['&amp;', '&quot;', '&lt;', '&gt;'], $string);
+		} else {
+			if(PHP_VERSION < '5.4.0') {
+				$string = htmlspecialchars($string, $flags);
+			} else {
+				if(strtolower(CHARSET) == 'utf-8') {
+					$charset = 'UTF-8';
+				} else {
+					$charset = 'ISO-8859-1';
+				}
+				$string = htmlspecialchars($string, $flags, $charset);
+			}
+		}
+	}
+	return $string;
+}
+
+function return_bytes($val) {
+	$last = strtolower($val[strlen($val) - 1]);
+	if(!is_numeric($val)) {
+		$val = substr(trim($val), 0, -1);
+	}
+	switch($last) {
+		case 'g':
+			$val *= 1024;
+		case 'm':
+			$val *= 1024;
+		case 'k':
+			$val *= 1024;
+	}
+	return $val;
+}
+
+function checkrobot($useragent = '') {
+	static $kw_spiders = ['bot', 'crawl', 'spider', 'slurp', 'sohu-search', 'lycos', 'robozilla'];
+	static $kw_browsers = ['msie', 'netscape', 'opera', 'konqueror', 'mozilla'];
+
+	$useragent = strtolower(empty($useragent) ? $_SERVER['HTTP_USER_AGENT'] : $useragent);
+	if(dstrpos($useragent, $kw_spiders)) return true;
+	if(!str_contains($useragent, 'http://') && dstrpos($useragent, $kw_browsers)) return false;
+	return false;
+}
+
+function dstrpos($string, $arr, $returnvalue = false) {
+	if(empty($string)) return false;
+	foreach((array)$arr as $v) {
+		if(str_contains($string, $v)) {
+			$return = $returnvalue ? $v : true;
+			return $return;
+		}
+	}
+	return false;
+}
+
+function checktplrefresh($maintpl, $subtpl, $timecompare, $templateid, $cachefile, $tpldir, $file) {
+	static $tplrefresh, $timestamp, $targettplname;
+	if($tplrefresh === null) {
+		$tplrefresh = getglobal('config/output/tplrefresh');
+		$timestamp = getglobal('timestamp');
+	}
+
+	if(empty($timecompare) || $tplrefresh == 1 || ($tplrefresh > 1 && !($timestamp % $tplrefresh))) {
+		if(empty($timecompare) || tplfile::filemtime($subtpl) > $timecompare) {
+			require_once DISCUZ_ROOT.'/source/class/class_template.php';
+			$template = new template();
+			$template->parse_template($maintpl, $templateid, $tpldir, $file, $cachefile);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+function fileext($filename) {
+	return addslashes(strtolower(substr(strrchr($filename, '.'), 1, 10)));
+}
+
+function strexists($string, $find) {
+	return !(!str_contains($string, $find));
+}
+
+function template($file, $templateid = 0, $tpldir = '', $gettplfile = 0, $primaltpl = '') {
+	global $_G;
+
+	if(str_starts_with($tpldir, 'source/app/')) {
+		$tplfile = DISCUZ_ROOT.$tpldir.'/'.$file.'.php';
+	} else {
+		if(!$tpldir) {
+			$tpldir = './template/default';
+		}
+		$tplfile = DISCUZ_TEMPLATE($tpldir).'/'.$file.'.htm';
+		if(!tplfile::file_exists($tplfile)) {
+			$tplfile = DISCUZ_TEMPLATE($tpldir).'/'.$file.'.php';
+		}
+	}
+
+	$file == 'common/header' && defined('CURMODULE') && CURMODULE && $file = 'common/header_'.$_G['basescript'].'_'.CURMODULE;
+
+	$cachefile = './template/'.$templateid.'_'.str_replace('/', '_', $file).'.tpl.php';
+	if($gettplfile) {
+		return $tplfile;
+	}
+	checktplrefresh($tplfile, $tplfile, tplfile::filemtime(DISCUZ_DATA.$cachefile), $templateid, $cachefile, $tpldir, $file);
+	return DISCUZ_DATA.$cachefile;
+}

+ 0 - 0
source/function/index.htm


+ 1 - 0
source/index.htm

@@ -0,0 +1 @@
+ 

+ 0 - 0
vendor/index.htm