first commit
This commit is contained in:
1517
var/Typecho/Common.php
Executable file
1517
var/Typecho/Common.php
Executable file
File diff suppressed because it is too large
Load Diff
249
var/Typecho/Config.php
Executable file
249
var/Typecho/Config.php
Executable file
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
/**
|
||||
* 配置管理类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Config
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Config extends \stdClass implements \Iterator, \ArrayAccess
|
||||
{
|
||||
/**
|
||||
* 当前配置
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $currentConfig = [];
|
||||
|
||||
/**
|
||||
* 实例化一个当前配置
|
||||
*
|
||||
* @access public
|
||||
* @param array|string|null $config 配置列表
|
||||
*/
|
||||
public function __construct($config = [])
|
||||
{
|
||||
/** 初始化参数 */
|
||||
$this->setDefault($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工厂模式实例化一个当前配置
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array|string|null $config 配置列表
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
public static function factory($config = []): Config
|
||||
{
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认的配置
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $config 配置信息
|
||||
* @param boolean $replace 是否替换已经存在的信息
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDefault($config, bool $replace = false)
|
||||
{
|
||||
if (empty($config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** 初始化参数 */
|
||||
if (is_string($config)) {
|
||||
parse_str($config, $params);
|
||||
} else {
|
||||
$params = $config;
|
||||
}
|
||||
|
||||
/** 设置默认参数 */
|
||||
foreach ($params as $name => $value) {
|
||||
if ($replace || !array_key_exists($name, $this->currentConfig)) {
|
||||
$this->currentConfig[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->currentConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重设指针
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
reset($this->currentConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前值
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return current($this->currentConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指针后移一位
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
next($this->currentConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前指针
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return key($this->currentConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证当前值是否到达最后
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return false !== $this->current();
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术函数获取一个配置值
|
||||
*
|
||||
* @access public
|
||||
* @param string $name 配置名称
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
return $this->offsetGet($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术函数设置一个配置值
|
||||
*
|
||||
* @access public
|
||||
* @param string $name 配置名称
|
||||
* @param mixed $value 配置值
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
$this->offsetSet($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接输出默认配置值
|
||||
*
|
||||
* @access public
|
||||
* @param string $name 配置名称
|
||||
* @param array|null $args 参数
|
||||
* @return void
|
||||
*/
|
||||
public function __call(string $name, ?array $args)
|
||||
{
|
||||
echo $this->currentConfig[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前配置值是否存在
|
||||
*
|
||||
* @access public
|
||||
* @param string $name 配置名称
|
||||
* @return boolean
|
||||
*/
|
||||
public function __isSet(string $name): bool
|
||||
{
|
||||
return $this->offsetExists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术方法,打印当前配置数组
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return json_encode($this->currentConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->currentConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->currentConfig[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->currentConfig[$offset] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
$this->currentConfig[$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
unset($this->currentConfig[$offset]);
|
||||
}
|
||||
}
|
||||
170
var/Typecho/Cookie.php
Executable file
170
var/Typecho/Cookie.php
Executable file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
/**
|
||||
* cookie支持
|
||||
*
|
||||
* @author qining
|
||||
* @category typecho
|
||||
* @package Cookie
|
||||
*/
|
||||
class Cookie
|
||||
{
|
||||
/**
|
||||
* 前缀
|
||||
*
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
private static string $prefix = '';
|
||||
|
||||
/**
|
||||
* 路径
|
||||
*
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
private static string $path = '/';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
private static string $domain = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @access private
|
||||
*/
|
||||
private static bool $secure = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @access private
|
||||
*/
|
||||
private static bool $httponly = false;
|
||||
|
||||
/**
|
||||
* 获取前缀
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public static function getPrefix(): string
|
||||
{
|
||||
return self::$prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置前缀
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function setPrefix(string $url)
|
||||
{
|
||||
self::$prefix = md5($url);
|
||||
$parsed = parse_url($url);
|
||||
|
||||
self::$domain = $parsed['host'];
|
||||
/** 在路径后面强制加上斜杠 */
|
||||
self::$path = empty($parsed['path']) ? '/' : Common::url(null, $parsed['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目录
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public static function getPath(): string
|
||||
{
|
||||
return self::$path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public static function getDomain(): string
|
||||
{
|
||||
return self::$domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public static function getSecure(): bool
|
||||
{
|
||||
return self::$secure ?: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置额外的选项
|
||||
*
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public static function setOptions(array $options)
|
||||
{
|
||||
self::$domain = $options['domain'] ?: self::$domain;
|
||||
self::$secure = !!$options['secure'];
|
||||
self::$httponly = !!$options['httponly'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定的COOKIE值
|
||||
*
|
||||
* @param string $key 指定的参数
|
||||
* @param string|null $default 默认的参数
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get(string $key, ?string $default = null)
|
||||
{
|
||||
$key = self::$prefix . $key;
|
||||
$value = $_COOKIE[$key] ?? $default;
|
||||
return is_array($value) ? $default : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定的COOKIE值
|
||||
*
|
||||
* @param string $key 指定的参数
|
||||
* @param mixed $value 设置的值
|
||||
* @param integer $expire 过期时间,默认为0,表示随会话时间结束
|
||||
*/
|
||||
public static function set(string $key, $value, int $expire = 0)
|
||||
{
|
||||
$key = self::$prefix . $key;
|
||||
$_COOKIE[$key] = $value;
|
||||
Response::getInstance()->setCookie(
|
||||
$key,
|
||||
$value,
|
||||
$expire,
|
||||
self::$path,
|
||||
self::$domain,
|
||||
self::$secure,
|
||||
self::$httponly
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定的COOKIE值
|
||||
*
|
||||
* @param string $key 指定的参数
|
||||
*/
|
||||
public static function delete(string $key)
|
||||
{
|
||||
$key = self::$prefix . $key;
|
||||
if (!isset($_COOKIE[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
Response::getInstance()->setCookie($key, '', -1, self::$path, self::$domain, self::$secure, self::$httponly);
|
||||
unset($_COOKIE[$key]);
|
||||
}
|
||||
}
|
||||
128
var/Typecho/Date.php
Executable file
128
var/Typecho/Date.php
Executable file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
/**
|
||||
* 日期处理
|
||||
*
|
||||
* @author qining
|
||||
* @category typecho
|
||||
* @package Date
|
||||
*/
|
||||
class Date
|
||||
{
|
||||
/**
|
||||
* 期望时区偏移
|
||||
*
|
||||
* @access public
|
||||
* @var integer
|
||||
*/
|
||||
public static int $timezoneOffset = 0;
|
||||
|
||||
/**
|
||||
* 服务器时区偏移
|
||||
*
|
||||
* @access public
|
||||
* @var integer
|
||||
*/
|
||||
public static int $serverTimezoneOffset = 0;
|
||||
|
||||
/**
|
||||
* 当前的服务器时间戳
|
||||
*
|
||||
* @access public
|
||||
* @var integer
|
||||
*/
|
||||
public static int $serverTimeStamp = 0;
|
||||
|
||||
/**
|
||||
* 可以被直接转换的时间戳
|
||||
*
|
||||
* @access public
|
||||
* @var integer
|
||||
*/
|
||||
public int $timeStamp = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public string $year;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public string $month;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public string $day;
|
||||
|
||||
/**
|
||||
* 初始化参数
|
||||
*
|
||||
* @param integer|null $time 时间戳
|
||||
*/
|
||||
public function __construct(?int $time = null)
|
||||
{
|
||||
$this->timeStamp = (null === $time ? self::time() : $time)
|
||||
+ (self::$timezoneOffset - self::$serverTimezoneOffset);
|
||||
|
||||
$this->year = date('Y', $this->timeStamp);
|
||||
$this->month = date('m', $this->timeStamp);
|
||||
$this->day = date('d', $this->timeStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前期望的时区偏移
|
||||
*
|
||||
* @param integer $offset
|
||||
*/
|
||||
public static function setTimezoneOffset(int $offset)
|
||||
{
|
||||
self::$timezoneOffset = $offset;
|
||||
self::$serverTimezoneOffset = idate('Z');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取格式化时间
|
||||
*
|
||||
* @param string $format 时间格式
|
||||
* @return string
|
||||
*/
|
||||
public function format(string $format): string
|
||||
{
|
||||
return date($format, $this->timeStamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取国际化偏移时间
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function word(): string
|
||||
{
|
||||
return I18n::dateWord($this->timeStamp, self::time() + (self::$timezoneOffset - self::$serverTimezoneOffset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取GMT时间
|
||||
*
|
||||
* @deprecated
|
||||
* @return int
|
||||
*/
|
||||
public static function gmtTime(): int
|
||||
{
|
||||
return self::time();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器时间
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function time(): int
|
||||
{
|
||||
return self::$serverTimeStamp ?: (self::$serverTimeStamp = time());
|
||||
}
|
||||
}
|
||||
465
var/Typecho/Db.php
Executable file
465
var/Typecho/Db.php
Executable file
@@ -0,0 +1,465 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
use Typecho\Db\Adapter;
|
||||
use Typecho\Db\Query;
|
||||
use Typecho\Db\Exception as DbException;
|
||||
|
||||
/**
|
||||
* 包含获取数据支持方法的类.
|
||||
* 必须定义__TYPECHO_DB_HOST__, __TYPECHO_DB_PORT__, __TYPECHO_DB_NAME__,
|
||||
* __TYPECHO_DB_USER__, __TYPECHO_DB_PASS__, __TYPECHO_DB_CHAR__
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class Db
|
||||
{
|
||||
/** 读取数据库 */
|
||||
public const READ = 1;
|
||||
|
||||
/** 写入数据库 */
|
||||
public const WRITE = 2;
|
||||
|
||||
/** 升序方式 */
|
||||
public const SORT_ASC = 'ASC';
|
||||
|
||||
/** 降序方式 */
|
||||
public const SORT_DESC = 'DESC';
|
||||
|
||||
/** 表内连接方式 */
|
||||
public const INNER_JOIN = 'INNER';
|
||||
|
||||
/** 表外连接方式 */
|
||||
public const OUTER_JOIN = 'OUTER';
|
||||
|
||||
/** 表左连接方式 */
|
||||
public const LEFT_JOIN = 'LEFT';
|
||||
|
||||
/** 表右连接方式 */
|
||||
public const RIGHT_JOIN = 'RIGHT';
|
||||
|
||||
/** 数据库查询操作 */
|
||||
public const SELECT = 'SELECT';
|
||||
|
||||
/** 数据库更新操作 */
|
||||
public const UPDATE = 'UPDATE';
|
||||
|
||||
/** 数据库插入操作 */
|
||||
public const INSERT = 'INSERT';
|
||||
|
||||
/** 数据库删除操作 */
|
||||
public const DELETE = 'DELETE';
|
||||
|
||||
/**
|
||||
* 数据库适配器
|
||||
* @var Adapter
|
||||
*/
|
||||
private Adapter $adapter;
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* 已经连接
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $connectedPool;
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $prefix;
|
||||
|
||||
/**
|
||||
* 适配器名称
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $adapterName;
|
||||
|
||||
/**
|
||||
* 实例化的数据库对象
|
||||
* @var Db
|
||||
*/
|
||||
private static Db $instance;
|
||||
|
||||
/**
|
||||
* 数据库类构造函数
|
||||
*
|
||||
* @param mixed $adapterName 适配器名称
|
||||
* @param string $prefix 前缀
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
public function __construct($adapterName, string $prefix = 'typecho_')
|
||||
{
|
||||
/** 获取适配器名称 */
|
||||
$adapterName = $adapterName == 'Mysql' ? 'Mysqli' : $adapterName;
|
||||
$this->adapterName = $adapterName;
|
||||
|
||||
/** 数据库适配器 */
|
||||
$adapterName = '\Typecho\Db\Adapter\\' . str_replace('_', '\\', $adapterName);
|
||||
|
||||
if (!call_user_func([$adapterName, 'isAvailable'])) {
|
||||
throw new DbException("Adapter {$adapterName} is not available");
|
||||
}
|
||||
|
||||
$this->prefix = $prefix;
|
||||
|
||||
/** 初始化内部变量 */
|
||||
$this->connectedPool = [];
|
||||
|
||||
$this->config = [
|
||||
self::READ => [],
|
||||
self::WRITE => []
|
||||
];
|
||||
|
||||
//实例化适配器对象
|
||||
$this->adapter = new $adapterName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Adapter
|
||||
*/
|
||||
public function getAdapter(): Adapter
|
||||
{
|
||||
return $this->adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取适配器名称
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getAdapterName(): string
|
||||
{
|
||||
return $this->adapterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表前缀
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getPrefix(): string
|
||||
{
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config $config
|
||||
* @param int $op
|
||||
*/
|
||||
public function addConfig(Config $config, int $op)
|
||||
{
|
||||
if ($op & self::READ) {
|
||||
$this->config[self::READ][] = $config;
|
||||
}
|
||||
|
||||
if ($op & self::WRITE) {
|
||||
$this->config[self::WRITE][] = $config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getConfig
|
||||
*
|
||||
* @param int $op
|
||||
*
|
||||
* @return Config
|
||||
* @throws DbException
|
||||
*/
|
||||
public function getConfig(int $op): Config
|
||||
{
|
||||
if (empty($this->config[$op])) {
|
||||
/** DbException */
|
||||
throw new DbException('Missing Database Connection');
|
||||
}
|
||||
|
||||
$key = array_rand($this->config[$op]);
|
||||
return $this->config[$op][$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置连接池
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flushPool()
|
||||
{
|
||||
$this->connectedPool = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择数据库
|
||||
*
|
||||
* @param int $op
|
||||
*
|
||||
* @return mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public function selectDb(int $op)
|
||||
{
|
||||
if (!isset($this->connectedPool[$op])) {
|
||||
$selectConnectionConfig = $this->getConfig($op);
|
||||
$selectConnectionHandle = $this->adapter->connect($selectConnectionConfig);
|
||||
$this->connectedPool[$op] = $selectConnectionHandle;
|
||||
}
|
||||
|
||||
return $this->connectedPool[$op];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SQL词法构建器实例化对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function sql(): Query
|
||||
{
|
||||
return new Query($this->adapter, $this->prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为多数据库提供支持
|
||||
*
|
||||
* @access public
|
||||
* @param array $config 数据库实例
|
||||
* @param integer $op 数据库操作
|
||||
* @return void
|
||||
*/
|
||||
public function addServer(array $config, int $op)
|
||||
{
|
||||
$this->addConfig(Config::factory($config), $op);
|
||||
$this->flushPool();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本
|
||||
*
|
||||
* @param int $op
|
||||
*
|
||||
* @return string
|
||||
* @throws DbException
|
||||
*/
|
||||
public function getVersion(int $op = self::READ): string
|
||||
{
|
||||
return $this->adapter->getVersion($this->selectDb($op));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认数据库对象
|
||||
*
|
||||
* @access public
|
||||
* @param Db $db 数据库对象
|
||||
* @return void
|
||||
*/
|
||||
public static function set(Db $db)
|
||||
{
|
||||
self::$instance = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库实例化对象
|
||||
* 用静态变量存储实例化的数据库对象,可以保证数据连接仅进行一次
|
||||
*
|
||||
* @return Db
|
||||
* @throws DbException
|
||||
*/
|
||||
public static function get(): Db
|
||||
{
|
||||
if (empty(self::$instance)) {
|
||||
/** DbException */
|
||||
throw new DbException('Missing Database Object');
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择查询字段
|
||||
*
|
||||
* @param ...$ags
|
||||
*
|
||||
* @return Query
|
||||
* @throws DbException
|
||||
*/
|
||||
public function select(...$ags): Query
|
||||
{
|
||||
$this->selectDb(self::READ);
|
||||
|
||||
$args = func_get_args();
|
||||
return call_user_func_array([$this->sql(), 'select'], $args ?: ['*']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新记录操作(UPDATE)
|
||||
*
|
||||
* @param string $table 需要更新记录的表
|
||||
*
|
||||
* @return Query
|
||||
* @throws DbException
|
||||
*/
|
||||
public function update(string $table): Query
|
||||
{
|
||||
$this->selectDb(self::WRITE);
|
||||
|
||||
return $this->sql()->update($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记录操作(DELETE)
|
||||
*
|
||||
* @param string $table 需要删除记录的表
|
||||
*
|
||||
* @return Query
|
||||
* @throws DbException
|
||||
*/
|
||||
public function delete(string $table): Query
|
||||
{
|
||||
$this->selectDb(self::WRITE);
|
||||
|
||||
return $this->sql()->delete($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入记录操作(INSERT)
|
||||
*
|
||||
* @param string $table 需要插入记录的表
|
||||
*
|
||||
* @return Query
|
||||
* @throws DbException
|
||||
*/
|
||||
public function insert(string $table): Query
|
||||
{
|
||||
$this->selectDb(self::WRITE);
|
||||
|
||||
return $this->sql()->insert($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @throws DbException
|
||||
*/
|
||||
public function truncate($table)
|
||||
{
|
||||
$table = preg_replace("/^table\./", $this->prefix, $table);
|
||||
$this->adapter->truncate($table, $this->selectDb(self::WRITE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行查询语句
|
||||
*
|
||||
* @param mixed $query 查询语句或者查询对象
|
||||
* @param int $op 数据库读写状态
|
||||
* @param string $action 操作动作
|
||||
*
|
||||
* @return mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public function query($query, int $op = self::READ, string $action = self::SELECT)
|
||||
{
|
||||
$table = null;
|
||||
|
||||
/** 在适配器中执行查询 */
|
||||
if ($query instanceof Query) {
|
||||
$action = $query->getAttribute('action');
|
||||
$table = $query->getAttribute('table');
|
||||
$op = (self::UPDATE == $action || self::DELETE == $action
|
||||
|| self::INSERT == $action) ? self::WRITE : self::READ;
|
||||
} elseif (!is_string($query)) {
|
||||
/** 如果query不是对象也不是字符串,那么将其判断为查询资源句柄,直接返回 */
|
||||
return $query;
|
||||
}
|
||||
|
||||
/** 选择连接池 */
|
||||
$handle = $this->selectDb($op);
|
||||
|
||||
/** 如果是查询对象,则将其转换为查询语句 */
|
||||
$sql = $query instanceof Query ? $query->prepare($query) : $query;
|
||||
|
||||
/** 提交查询 */
|
||||
$resource = $this->adapter->query($sql, $handle, $op, $action, $table);
|
||||
|
||||
if ($action) {
|
||||
//根据查询动作返回相应资源
|
||||
switch ($action) {
|
||||
case self::UPDATE:
|
||||
case self::DELETE:
|
||||
return $this->adapter->affectedRows($resource, $handle);
|
||||
case self::INSERT:
|
||||
return $this->adapter->lastInsertId($resource, $handle);
|
||||
case self::SELECT:
|
||||
default:
|
||||
return $resource;
|
||||
}
|
||||
} else {
|
||||
//如果直接执行查询语句则返回资源
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次取出所有行
|
||||
*
|
||||
* @param mixed $query 查询对象
|
||||
* @param callable|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
|
||||
*
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAll($query, ?callable $filter = null): array
|
||||
{
|
||||
//执行查询
|
||||
$resource = $this->query($query);
|
||||
$result = $this->adapter->fetchAll($resource);
|
||||
|
||||
return $filter ? array_map($filter, $result) : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次取出一行
|
||||
*
|
||||
* @param mixed $query 查询对象
|
||||
* @param callable|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
|
||||
* @return array|null
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchRow($query, ?callable $filter = null): ?array
|
||||
{
|
||||
$resource = $this->query($query);
|
||||
|
||||
return ($rows = $this->adapter->fetch($resource)) ?
|
||||
($filter ? call_user_func($filter, $rows) : $rows) :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次取出一个对象
|
||||
*
|
||||
* @param mixed $query 查询对象
|
||||
* @param array|null $filter 行过滤器函数,将查询的每一行作为第一个参数传入指定的过滤器中
|
||||
* @return \stdClass|null
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchObject($query, ?array $filter = null): ?\stdClass
|
||||
{
|
||||
$resource = $this->query($query);
|
||||
|
||||
return ($rows = $this->adapter->fetchObject($resource)) ?
|
||||
($filter ? call_user_func($filter, $rows) : $rows) :
|
||||
null;
|
||||
}
|
||||
}
|
||||
134
var/Typecho/Db/Adapter.php
Executable file
134
var/Typecho/Db/Adapter.php
Executable file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Db;
|
||||
|
||||
/**
|
||||
* Typecho数据库适配器
|
||||
* 定义通用的数据库适配接口
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
interface Adapter
|
||||
{
|
||||
/**
|
||||
* 判断适配器是否可用
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAvailable(): bool;
|
||||
|
||||
/**
|
||||
* 数据库连接函数
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @return mixed
|
||||
*/
|
||||
public function connect(Config $config);
|
||||
|
||||
/**
|
||||
* 获取数据库版本
|
||||
*
|
||||
* @param mixed $handle
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion($handle): string;
|
||||
|
||||
/**
|
||||
* 获取数据库类型
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDriver(): string;
|
||||
|
||||
/**
|
||||
* 清空数据表
|
||||
*
|
||||
* @param string $table 数据表名
|
||||
* @param mixed $handle 连接对象
|
||||
*/
|
||||
public function truncate(string $table, $handle);
|
||||
|
||||
/**
|
||||
* 执行数据库查询
|
||||
*
|
||||
* @param string $query 数据库查询SQL字符串
|
||||
* @param mixed $handle 连接对象
|
||||
* @param integer $op 数据库读写状态
|
||||
* @param string|null $action 数据库动作
|
||||
* @param string|null $table 数据表
|
||||
* @return resource
|
||||
*/
|
||||
public function query(string $query, $handle, int $op = Db::READ, ?string $action = null, ?string $table = null);
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为数组取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param resource $resource 查询的资源数据
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetch($resource): ?array;
|
||||
|
||||
/**
|
||||
* 将数据查询的结果作为数组全部取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param resource $resource 查询的资源数据
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll($resource): array;
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为对象取出,其中字段名对应对象属性
|
||||
*
|
||||
* @param resource $resource 查询的资源数据
|
||||
* @return \stdClass|null
|
||||
*/
|
||||
public function fetchObject($resource): ?\stdClass;
|
||||
|
||||
/**
|
||||
* 引号转义函数
|
||||
*
|
||||
* @param mixed $string 需要转义的字符串
|
||||
* @return string
|
||||
*/
|
||||
public function quoteValue($string): string;
|
||||
|
||||
/**
|
||||
* 对象引号过滤
|
||||
*
|
||||
* @access public
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
public function quoteColumn(string $string): string;
|
||||
|
||||
/**
|
||||
* 合成查询语句
|
||||
*
|
||||
* @access public
|
||||
* @param array $sql 查询对象词法数组
|
||||
* @return string
|
||||
*/
|
||||
public function parseSelect(array $sql): string;
|
||||
|
||||
/**
|
||||
* 取出最后一次查询影响的行数
|
||||
*
|
||||
* @param resource $resource 查询的资源数据
|
||||
* @param mixed $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function affectedRows($resource, $handle): int;
|
||||
|
||||
/**
|
||||
* 取出最后一次插入返回的主键值
|
||||
*
|
||||
* @param resource $resource 查询的资源数据
|
||||
* @param mixed $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function lastInsertId($resource, $handle): int;
|
||||
}
|
||||
18
var/Typecho/Db/Adapter/ConnectionException.php
Executable file
18
var/Typecho/Db/Adapter/ConnectionException.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Typecho\Db\Exception as DbException;
|
||||
|
||||
/**
|
||||
* 数据库连接异常类
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class ConnectionException extends DbException
|
||||
{
|
||||
}
|
||||
40
var/Typecho/Db/Adapter/MysqlTrait.php
Executable file
40
var/Typecho/Db/Adapter/MysqlTrait.php
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
trait MysqlTrait
|
||||
{
|
||||
use QueryTrait;
|
||||
|
||||
/**
|
||||
* 清空数据表
|
||||
*
|
||||
* @param string $table
|
||||
* @param mixed $handle 连接对象
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function truncate(string $table, $handle)
|
||||
{
|
||||
$this->query('TRUNCATE TABLE ' . $this->quoteColumn($table), $handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合成查询语句
|
||||
*
|
||||
* @access public
|
||||
* @param array $sql 查询对象词法数组
|
||||
* @return string
|
||||
*/
|
||||
public function parseSelect(array $sql): string
|
||||
{
|
||||
return $this->buildQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDriver(): string
|
||||
{
|
||||
return 'mysql';
|
||||
}
|
||||
}
|
||||
216
var/Typecho/Db/Adapter/Mysqli.php
Executable file
216
var/Typecho/Db/Adapter/Mysqli.php
Executable file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Db;
|
||||
use Typecho\Db\Adapter;
|
||||
use mysqli_sql_exception;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库Mysqli适配器
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class Mysqli implements Adapter
|
||||
{
|
||||
use MysqlTrait;
|
||||
|
||||
/**
|
||||
* 数据库连接字符串标示
|
||||
*
|
||||
* @access private
|
||||
* @var \mysqli
|
||||
*/
|
||||
private \mysqli $dbLink;
|
||||
|
||||
/**
|
||||
* 判断适配器是否可用
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return extension_loaded('mysqli');
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库连接函数
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @return \mysqli
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function connect(Config $config): \mysqli
|
||||
{
|
||||
$mysqli = mysqli_init();
|
||||
if ($mysqli) {
|
||||
try {
|
||||
if (!empty($config->sslCa)) {
|
||||
$mysqli->ssl_set(null, null, $config->sslCa, null, null);
|
||||
|
||||
if (isset($config->sslVerify)) {
|
||||
$mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, $config->sslVerify);
|
||||
}
|
||||
}
|
||||
|
||||
$host = $config->host;
|
||||
$port = empty($config->port) ? null : $config->port;
|
||||
$socket = null;
|
||||
if (strpos($host, '/') !== false) {
|
||||
$socket = $host;
|
||||
$host = 'localhost';
|
||||
$port = null;
|
||||
}
|
||||
|
||||
$mysqli->real_connect(
|
||||
$host,
|
||||
$config->user,
|
||||
$config->password,
|
||||
$config->database,
|
||||
$port,
|
||||
$socket
|
||||
);
|
||||
|
||||
$this->dbLink = $mysqli;
|
||||
|
||||
if ($config->charset) {
|
||||
$this->dbLink->query("SET NAMES '{$config->charset}'");
|
||||
}
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
throw new ConnectionException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
return $this->dbLink;
|
||||
}
|
||||
|
||||
/** 数据库异常 */
|
||||
throw new ConnectionException("Couldn't connect to database.", mysqli_connect_errno());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库版本
|
||||
*
|
||||
* @param mixed $handle
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion($handle): string
|
||||
{
|
||||
return $this->dbLink->server_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库查询
|
||||
*
|
||||
* @param string $query 数据库查询SQL字符串
|
||||
* @param mixed $handle 连接对象
|
||||
* @param integer $op 数据库读写状态
|
||||
* @param string|null $action 数据库动作
|
||||
* @param string|null $table 数据表
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function query(
|
||||
string $query,
|
||||
$handle,
|
||||
int $op = Db::READ,
|
||||
?string $action = null,
|
||||
?string $table = null
|
||||
) {
|
||||
try {
|
||||
if ($resource = $this->dbLink->query($query)) {
|
||||
return $resource;
|
||||
}
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
/** 数据库异常 */
|
||||
throw new SQLException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
/** 数据库异常 */
|
||||
throw new SQLException($this->dbLink->error, $this->dbLink->errno);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象引号过滤
|
||||
*
|
||||
* @access public
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
public function quoteColumn(string $string): string
|
||||
{
|
||||
return '`' . $string . '`';
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为数组取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param \mysqli_result $resource 查询返回资源标识
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetch($resource): ?array
|
||||
{
|
||||
return $resource->fetch_assoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的结果作为数组全部取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param \mysqli_result $resource 查询返回资源标识
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll($resource): array
|
||||
{
|
||||
return $resource->fetch_all(MYSQLI_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为对象取出,其中字段名对应对象属性
|
||||
*
|
||||
* @param \mysqli_result $resource 查询的资源数据
|
||||
* @return \stdClass|null
|
||||
*/
|
||||
public function fetchObject($resource): ?\stdClass
|
||||
{
|
||||
return $resource->fetch_object();
|
||||
}
|
||||
|
||||
/**
|
||||
* 引号转义函数
|
||||
*
|
||||
* @param mixed $string 需要转义的字符串
|
||||
* @return string
|
||||
*/
|
||||
public function quoteValue($string): string
|
||||
{
|
||||
return "'" . $this->dbLink->real_escape_string($string) . "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出最后一次查询影响的行数
|
||||
*
|
||||
* @param mixed $resource 查询的资源数据
|
||||
* @param \mysqli $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function affectedRows($resource, $handle): int
|
||||
{
|
||||
return $handle->affected_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出最后一次插入返回的主键值
|
||||
*
|
||||
* @param mixed $resource 查询的资源数据
|
||||
* @param \mysqli $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function lastInsertId($resource, $handle): int
|
||||
{
|
||||
return $handle->insert_id;
|
||||
}
|
||||
}
|
||||
184
var/Typecho/Db/Adapter/Pdo.php
Executable file
184
var/Typecho/Db/Adapter/Pdo.php
Executable file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Db;
|
||||
use Typecho\Db\Adapter;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库PDOMysql适配器
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
abstract class Pdo implements Adapter
|
||||
{
|
||||
/**
|
||||
* 数据库对象
|
||||
*
|
||||
* @access protected
|
||||
* @var \PDO
|
||||
*/
|
||||
protected \PDO $object;
|
||||
|
||||
/**
|
||||
* 最后一次操作的数据表
|
||||
*
|
||||
* @access protected
|
||||
* @var string|null
|
||||
*/
|
||||
protected ?string $lastTable;
|
||||
|
||||
/**
|
||||
* 判断适配器是否可用
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return class_exists('PDO');
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库连接函数
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @return \PDO
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function connect(Config $config): \PDO
|
||||
{
|
||||
try {
|
||||
$this->object = $this->init($config);
|
||||
$this->object->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
return $this->object;
|
||||
} catch (\PDOException $e) {
|
||||
/** 数据库异常 */
|
||||
throw new ConnectionException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @abstract
|
||||
* @access public
|
||||
* @return \PDO
|
||||
*/
|
||||
abstract public function init(Config $config): \PDO;
|
||||
|
||||
/**
|
||||
* 获取数据库版本
|
||||
*
|
||||
* @param mixed $handle
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion($handle): string
|
||||
{
|
||||
return $handle->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库查询
|
||||
*
|
||||
* @param string $query 数据库查询SQL字符串
|
||||
* @param \PDO $handle 连接对象
|
||||
* @param integer $op 数据库读写状态
|
||||
* @param string|null $action 数据库动作
|
||||
* @param string|null $table 数据表
|
||||
* @return \PDOStatement
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function query(
|
||||
string $query,
|
||||
$handle,
|
||||
int $op = Db::READ,
|
||||
?string $action = null,
|
||||
?string $table = null
|
||||
): \PDOStatement {
|
||||
try {
|
||||
$this->lastTable = $table;
|
||||
$resource = $handle->prepare($query);
|
||||
$resource->execute();
|
||||
} catch (\PDOException $e) {
|
||||
/** 数据库异常 */
|
||||
throw new SQLException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的结果作为数组全部取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param \PDOStatement $resource 查询的资源数据
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll($resource): array
|
||||
{
|
||||
return $resource->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为数组取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param \PDOStatement $resource 查询返回资源标识
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetch($resource): ?array
|
||||
{
|
||||
return $resource->fetch(\PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为对象取出,其中字段名对应对象属性
|
||||
*
|
||||
* @param \PDOStatement $resource 查询的资源数据
|
||||
* @return \stdClass|null
|
||||
*/
|
||||
public function fetchObject($resource): ?\stdClass
|
||||
{
|
||||
return $resource->fetchObject() ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 引号转义函数
|
||||
*
|
||||
* @param mixed $string 需要转义的字符串
|
||||
* @return string
|
||||
*/
|
||||
public function quoteValue($string): string
|
||||
{
|
||||
return $this->object->quote($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出最后一次查询影响的行数
|
||||
*
|
||||
* @param \PDOStatement $resource 查询的资源数据
|
||||
* @param \PDO $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function affectedRows($resource, $handle): int
|
||||
{
|
||||
return $resource->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出最后一次插入返回的主键值
|
||||
*
|
||||
* @param \PDOStatement $resource 查询的资源数据
|
||||
* @param \PDO $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function lastInsertId($resource, $handle): int
|
||||
{
|
||||
return $handle->lastInsertId();
|
||||
}
|
||||
}
|
||||
102
var/Typecho/Db/Adapter/Pdo/Mysql.php
Executable file
102
var/Typecho/Db/Adapter/Pdo/Mysql.php
Executable file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter\Pdo;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Db\Adapter\MysqlTrait;
|
||||
use Typecho\Db\Adapter\Pdo;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库Pdo_Mysql适配器
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class Mysql extends Pdo
|
||||
{
|
||||
use MysqlTrait;
|
||||
|
||||
/**
|
||||
* 判断适配器是否可用
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return parent::isAvailable() && in_array('mysql', \PDO::getAvailableDrivers());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象引号过滤
|
||||
*
|
||||
* @access public
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
public function quoteColumn(string $string): string
|
||||
{
|
||||
return '`' . $string . '`';
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @access public
|
||||
* @return \PDO
|
||||
*/
|
||||
public function init(Config $config): \PDO
|
||||
{
|
||||
$options = [];
|
||||
if (!empty($config->sslCa)) {
|
||||
$options[\PDO::MYSQL_ATTR_SSL_CA] = $config->sslCa;
|
||||
|
||||
if (isset($config->sslVerify)) {
|
||||
// FIXME: https://github.com/php/php-src/issues/8577
|
||||
$options[\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $config->sslVerify;
|
||||
}
|
||||
}
|
||||
|
||||
$dsn = !empty($config->dsn)
|
||||
? $config->dsn
|
||||
: (strpos($config->host, '/') !== false
|
||||
? "mysql:dbname={$config->database};unix_socket={$config->host}"
|
||||
: "mysql:dbname={$config->database};host={$config->host};port={$config->port}");
|
||||
|
||||
$pdo = new \PDO(
|
||||
$dsn,
|
||||
$config->user,
|
||||
$config->password,
|
||||
$options
|
||||
);
|
||||
|
||||
if (class_exists('\Pdo\Mysql')) {
|
||||
// 新版本写法
|
||||
$pdo->setAttribute(\Pdo\Mysql::ATTR_USE_BUFFERED_QUERY, true);
|
||||
} else {
|
||||
// 兼容旧版本
|
||||
$pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
|
||||
}
|
||||
|
||||
if ($config->charset) {
|
||||
$pdo->exec("SET NAMES '{$config->charset}'");
|
||||
}
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 引号转义函数
|
||||
*
|
||||
* @param mixed $string 需要转义的字符串
|
||||
* @return string
|
||||
*/
|
||||
public function quoteValue($string): string
|
||||
{
|
||||
return '\'' . str_replace(['\'', '\\'], ['\'\'', '\\\\'], $string) . '\'';
|
||||
}
|
||||
}
|
||||
83
var/Typecho/Db/Adapter/Pdo/Pgsql.php
Executable file
83
var/Typecho/Db/Adapter/Pdo/Pgsql.php
Executable file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter\Pdo;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Db;
|
||||
use Typecho\Db\Adapter\SQLException;
|
||||
use Typecho\Db\Adapter\Pdo;
|
||||
use Typecho\Db\Adapter\PgsqlTrait;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库Pdo_Pgsql适配器
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class Pgsql extends Pdo
|
||||
{
|
||||
use PgsqlTrait;
|
||||
|
||||
/**
|
||||
* 判断适配器是否可用
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return parent::isAvailable() && in_array('pgsql', \PDO::getAvailableDrivers());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库查询
|
||||
*
|
||||
* @param string $query 数据库查询SQL字符串
|
||||
* @param \PDO $handle 连接对象
|
||||
* @param integer $op 数据库读写状态
|
||||
* @param string|null $action 数据库动作
|
||||
* @param string|null $table 数据表
|
||||
* @return \PDOStatement
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function query(
|
||||
string $query,
|
||||
$handle,
|
||||
int $op = Db::READ,
|
||||
?string $action = null,
|
||||
?string $table = null
|
||||
): \PDOStatement {
|
||||
$this->prepareQuery($query, $handle, $action, $table);
|
||||
return parent::query($query, $handle, $op, $action, $table);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @return \PDO
|
||||
*/
|
||||
public function init(Config $config): \PDO
|
||||
{
|
||||
$dsn = "pgsql:dbname={$config->database};host={$config->host};port={$config->port}";
|
||||
|
||||
if ($config->sslVerify) {
|
||||
$dsn .= ';sslmode=require';
|
||||
}
|
||||
|
||||
$pdo = new \PDO(
|
||||
$dsn,
|
||||
$config->user,
|
||||
$config->password
|
||||
);
|
||||
|
||||
if ($config->charset) {
|
||||
$pdo->exec("SET NAMES '{$config->charset}'");
|
||||
}
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
}
|
||||
81
var/Typecho/Db/Adapter/Pdo/SQLite.php
Executable file
81
var/Typecho/Db/Adapter/Pdo/SQLite.php
Executable file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter\Pdo;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Db\Adapter\Pdo;
|
||||
use Typecho\Db\Adapter\SQLiteTrait;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库Pdo_SQLite适配器
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class SQLite extends Pdo
|
||||
{
|
||||
use SQLiteTrait;
|
||||
|
||||
/**
|
||||
* 判断适配器是否可用
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return parent::isAvailable() && in_array('sqlite', \PDO::getAvailableDrivers());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @access public
|
||||
* @return \PDO
|
||||
*/
|
||||
public function init(Config $config): \PDO
|
||||
{
|
||||
$pdo = new \PDO("sqlite:{$config->file}");
|
||||
$this->isSQLite2 = version_compare($pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '3.0.0', '<');
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为对象取出,其中字段名对应对象属性
|
||||
*
|
||||
* @param \PDOStatement $resource 查询的资源数据
|
||||
* @return \stdClass|null
|
||||
*/
|
||||
public function fetchObject($resource): ?\stdClass
|
||||
{
|
||||
$result = $this->fetch($resource);
|
||||
return $result ? (object) $result : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为数组取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param \PDOStatement $resource 查询返回资源标识
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetch($resource): ?array
|
||||
{
|
||||
$result = parent::fetch($resource);
|
||||
return $result ? $this->filterColumnName($result) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的结果作为数组全部取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param \PDOStatement $resource 查询的资源数据
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll($resource): array
|
||||
{
|
||||
return array_map([$this, 'filterColumnName'], parent::fetchAll($resource));
|
||||
}
|
||||
}
|
||||
151
var/Typecho/Db/Adapter/Pgsql.php
Executable file
151
var/Typecho/Db/Adapter/Pgsql.php
Executable file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Db;
|
||||
use Typecho\Db\Adapter;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库Pgsql适配器
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class Pgsql implements Adapter
|
||||
{
|
||||
use PgsqlTrait;
|
||||
|
||||
/**
|
||||
* 判断适配器是否可用
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return extension_loaded('pgsql');
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库连接函数
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @return resource
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function connect(Config $config)
|
||||
{
|
||||
$dsn = "host={$config->host} port={$config->port}"
|
||||
. " dbname={$config->database} user={$config->user} password={$config->password}";
|
||||
|
||||
if ($config->sslVerify) {
|
||||
$dsn .= ' sslmode=require';
|
||||
}
|
||||
|
||||
if ($config->charset) {
|
||||
$dsn .= " options='--client_encoding={$config->charset}'";
|
||||
}
|
||||
|
||||
if ($dbLink = @pg_connect($dsn)) {
|
||||
return $dbLink;
|
||||
}
|
||||
|
||||
/** 数据库异常 */
|
||||
throw new ConnectionException("Couldn't connect to database.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库版本
|
||||
*
|
||||
* @param mixed $handle
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion($handle): string
|
||||
{
|
||||
$version = pg_version($handle);
|
||||
return $version['server'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库查询
|
||||
*
|
||||
* @param string $query 数据库查询SQL字符串
|
||||
* @param resource $handle 连接对象
|
||||
* @param integer $op 数据库读写状态
|
||||
* @param string|null $action 数据库动作
|
||||
* @param string|null $table 数据表
|
||||
* @return resource
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function query(string $query, $handle, int $op = Db::READ, ?string $action = null, ?string $table = null)
|
||||
{
|
||||
$this->prepareQuery($query, $handle, $action, $table);
|
||||
if ($resource = pg_query($handle, $query)) {
|
||||
return $resource;
|
||||
}
|
||||
|
||||
/** 数据库异常 */
|
||||
throw new SQLException(
|
||||
@pg_last_error($handle),
|
||||
pg_result_error_field(pg_get_result($handle), PGSQL_DIAG_SQLSTATE)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为数组取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param resource $resource 查询返回资源标识
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetch($resource): ?array
|
||||
{
|
||||
return pg_fetch_assoc($resource) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为对象取出,其中字段名对应对象属性
|
||||
*
|
||||
* @param resource $resource 查询的资源数据
|
||||
* @return \stdClass|null
|
||||
*/
|
||||
public function fetchObject($resource): ?\stdClass
|
||||
{
|
||||
return pg_fetch_object($resource) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $resource
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll($resource): array
|
||||
{
|
||||
return pg_fetch_all($resource, PGSQL_ASSOC) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出最后一次查询影响的行数
|
||||
*
|
||||
* @param resource $resource 查询的资源数据
|
||||
* @param resource $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function affectedRows($resource, $handle): int
|
||||
{
|
||||
return pg_affected_rows($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 引号转义函数
|
||||
*
|
||||
* @param mixed $string 需要转义的字符串
|
||||
* @return string
|
||||
*/
|
||||
public function quoteValue($string): string
|
||||
{
|
||||
return '\'' . str_replace('\'', '\'\'', $string) . '\'';
|
||||
}
|
||||
}
|
||||
175
var/Typecho/Db/Adapter/PgsqlTrait.php
Executable file
175
var/Typecho/Db/Adapter/PgsqlTrait.php
Executable file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
use Typecho\Db;
|
||||
|
||||
trait PgsqlTrait
|
||||
{
|
||||
use QueryTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $pk = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $compatibleInsert = false;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $lastInsertTable = null;
|
||||
|
||||
/**
|
||||
* 清空数据表
|
||||
*
|
||||
* @param string $table
|
||||
* @param resource $handle 连接对象
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function truncate(string $table, $handle)
|
||||
{
|
||||
$this->query('TRUNCATE TABLE ' . $this->quoteColumn($table) . ' RESTART IDENTITY', $handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合成查询语句
|
||||
*
|
||||
* @access public
|
||||
* @param array $sql 查询对象词法数组
|
||||
* @return string
|
||||
*/
|
||||
public function parseSelect(array $sql): string
|
||||
{
|
||||
return $this->buildQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象引号过滤
|
||||
*
|
||||
* @access public
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
public function quoteColumn(string $string): string
|
||||
{
|
||||
return '"' . $string . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @param $handle
|
||||
* @param string|null $action
|
||||
* @param string|null $table
|
||||
* @throws SQLException
|
||||
*/
|
||||
protected function prepareQuery(string &$query, $handle, ?string $action = null, ?string $table = null)
|
||||
{
|
||||
if (Db::INSERT == $action && !empty($table)) {
|
||||
$this->compatibleInsert = false;
|
||||
|
||||
if (!isset($this->pk[$table])) {
|
||||
$resource = $this->query("SELECT
|
||||
pg_attribute.attname,
|
||||
format_type(pg_attribute.atttypid, pg_attribute.atttypmod)
|
||||
FROM pg_index, pg_class, pg_attribute, pg_namespace
|
||||
WHERE
|
||||
pg_class.oid = " . $this->quoteValue($table) . "::regclass AND
|
||||
indrelid = pg_class.oid AND
|
||||
nspname = 'public' AND
|
||||
pg_class.relnamespace = pg_namespace.oid AND
|
||||
pg_attribute.attrelid = pg_class.oid AND
|
||||
pg_attribute.attnum = any(pg_index.indkey)
|
||||
AND indisprimary", $handle, Db::READ, Db::SELECT, $table);
|
||||
|
||||
$result = $this->fetch($resource);
|
||||
|
||||
if (!empty($result)) {
|
||||
$this->pk[$table] = $result['attname'];
|
||||
}
|
||||
}
|
||||
|
||||
// 使用兼容模式监听插入结果
|
||||
if (isset($this->pk[$table])) {
|
||||
$this->compatibleInsert = true;
|
||||
$this->lastInsertTable = $table;
|
||||
$query .= ' RETURNING ' . $this->quoteColumn($this->pk[$table]);
|
||||
}
|
||||
} else {
|
||||
$this->lastInsertTable = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出最后一次插入返回的主键值
|
||||
*
|
||||
* @param resource $resource 查询的资源数据
|
||||
* @param resource $handle 连接对象
|
||||
* @return integer
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function lastInsertId($resource, $handle): int
|
||||
{
|
||||
$lastTable = $this->lastInsertTable;
|
||||
|
||||
if ($this->compatibleInsert) {
|
||||
$result = $this->fetch($resource);
|
||||
$pk = $this->pk[$lastTable];
|
||||
|
||||
if (!empty($result) && isset($result[$pk])) {
|
||||
return (int) $result[$pk];
|
||||
}
|
||||
} else {
|
||||
$resource = $this->query(
|
||||
'SELECT oid FROM pg_class WHERE relname = '
|
||||
. $this->quoteValue($lastTable . '_seq'),
|
||||
$handle,
|
||||
Db::READ,
|
||||
Db::SELECT,
|
||||
$lastTable
|
||||
);
|
||||
|
||||
$result = $this->fetch($resource);
|
||||
|
||||
if (!empty($result)) {
|
||||
$resource = $this->query(
|
||||
'SELECT CURRVAL(' . $this->quoteValue($lastTable . '_seq') . ') AS last_insert_id',
|
||||
$handle,
|
||||
Db::READ,
|
||||
Db::SELECT,
|
||||
$lastTable
|
||||
);
|
||||
|
||||
$result = $this->fetch($resource);
|
||||
if (!empty($result)) {
|
||||
return (int) $result['last_insert_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDriver(): string
|
||||
{
|
||||
return 'pgsql';
|
||||
}
|
||||
|
||||
abstract public function query(
|
||||
string $query,
|
||||
$handle,
|
||||
int $op = Db::READ,
|
||||
?string $action = null,
|
||||
?string $table = null
|
||||
);
|
||||
|
||||
abstract public function quoteValue(string $string): string;
|
||||
|
||||
abstract public function fetch($resource): ?array;
|
||||
}
|
||||
29
var/Typecho/Db/Adapter/QueryTrait.php
Executable file
29
var/Typecho/Db/Adapter/QueryTrait.php
Executable file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
/**
|
||||
* Build Sql
|
||||
*/
|
||||
trait QueryTrait
|
||||
{
|
||||
/**
|
||||
* @param array $sql
|
||||
* @return string
|
||||
*/
|
||||
private function buildQuery(array $sql): string
|
||||
{
|
||||
if (!empty($sql['join'])) {
|
||||
foreach ($sql['join'] as $val) {
|
||||
[$table, $condition, $op] = $val;
|
||||
$sql['table'] = "{$sql['table']} {$op} JOIN {$table} ON {$condition}";
|
||||
}
|
||||
}
|
||||
|
||||
$sql['limit'] = isset($sql['limit']) ? ' LIMIT ' . $sql['limit'] : '';
|
||||
$sql['offset'] = isset($sql['offset']) ? ' OFFSET ' . $sql['offset'] : '';
|
||||
|
||||
return 'SELECT ' . $sql['fields'] . ' FROM ' . $sql['table'] .
|
||||
$sql['where'] . $sql['group'] . $sql['having'] . $sql['order'] . $sql['limit'] . $sql['offset'];
|
||||
}
|
||||
}
|
||||
18
var/Typecho/Db/Adapter/SQLException.php
Executable file
18
var/Typecho/Db/Adapter/SQLException.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Typecho\Db\Exception as DbException;
|
||||
|
||||
/**
|
||||
* 数据库连接异常类
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class SQLException extends DbException
|
||||
{
|
||||
}
|
||||
166
var/Typecho/Db/Adapter/SQLite.php
Executable file
166
var/Typecho/Db/Adapter/SQLite.php
Executable file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Db;
|
||||
use Typecho\Db\Adapter;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库SQLite适配器
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class SQLite implements Adapter
|
||||
{
|
||||
use SQLiteTrait;
|
||||
|
||||
/**
|
||||
* 判断适配器是否可用
|
||||
*
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isAvailable(): bool
|
||||
{
|
||||
return extension_loaded('sqlite3');
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库连接函数
|
||||
*
|
||||
* @param Config $config 数据库配置
|
||||
* @return \SQLite3
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function connect(Config $config): \SQLite3
|
||||
{
|
||||
try {
|
||||
$dbHandle = new \SQLite3($config->file);
|
||||
$this->isSQLite2 = version_compare(\SQLite3::version()['versionString'], '3.0.0', '<');
|
||||
} catch (\Exception $e) {
|
||||
throw new ConnectionException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
return $dbHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库版本
|
||||
*
|
||||
* @param mixed $handle
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion($handle): string
|
||||
{
|
||||
return \SQLite3::version()['versionString'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库查询
|
||||
*
|
||||
* @param string $query 数据库查询SQL字符串
|
||||
* @param \SQLite3 $handle 连接对象
|
||||
* @param integer $op 数据库读写状态
|
||||
* @param string|null $action 数据库动作
|
||||
* @param string|null $table 数据表
|
||||
* @return \SQLite3Result
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function query(
|
||||
string $query,
|
||||
$handle,
|
||||
int $op = Db::READ,
|
||||
?string $action = null,
|
||||
?string $table = null
|
||||
): \SQLite3Result {
|
||||
if ($stm = $handle->prepare($query)) {
|
||||
if ($resource = $stm->execute()) {
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
|
||||
/** 数据库异常 */
|
||||
throw new SQLException($handle->lastErrorMsg(), $handle->lastErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为对象取出,其中字段名对应对象属性
|
||||
*
|
||||
* @param \SQLite3Result $resource 查询的资源数据
|
||||
* @return \stdClass|null
|
||||
*/
|
||||
public function fetchObject($resource): ?\stdClass
|
||||
{
|
||||
$result = $this->fetch($resource);
|
||||
return $result ? (object) $result : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的其中一行作为数组取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param \SQLite3Result $resource 查询返回资源标识
|
||||
* @return array|null
|
||||
*/
|
||||
public function fetch($resource): ?array
|
||||
{
|
||||
$result = $resource->fetchArray(SQLITE3_ASSOC);
|
||||
return $result ? $this->filterColumnName($result) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据查询的结果作为数组全部取出,其中字段名对应数组键值
|
||||
*
|
||||
* @param \SQLite3Result $resource 查询的资源数据
|
||||
* @return array
|
||||
*/
|
||||
public function fetchAll($resource): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
while ($row = $this->fetch($resource)) {
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 引号转义函数
|
||||
*
|
||||
* @param mixed $string 需要转义的字符串
|
||||
* @return string
|
||||
*/
|
||||
public function quoteValue($string): string
|
||||
{
|
||||
return '\'' . str_replace('\'', '\'\'', $string) . '\'';
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出最后一次查询影响的行数
|
||||
*
|
||||
* @param \SQLite3Result $resource 查询的资源数据
|
||||
* @param \SQLite3 $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function affectedRows($resource, $handle): int
|
||||
{
|
||||
return $handle->changes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出最后一次插入返回的主键值
|
||||
*
|
||||
* @param \SQLite3Result $resource 查询的资源数据
|
||||
* @param \SQLite3 $handle 连接对象
|
||||
* @return integer
|
||||
*/
|
||||
public function lastInsertId($resource, $handle): int
|
||||
{
|
||||
return $handle->lastInsertRowID();
|
||||
}
|
||||
}
|
||||
110
var/Typecho/Db/Adapter/SQLiteTrait.php
Executable file
110
var/Typecho/Db/Adapter/SQLiteTrait.php
Executable file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Adapter;
|
||||
|
||||
/**
|
||||
* SQLite Special Util
|
||||
*/
|
||||
trait SQLiteTrait
|
||||
{
|
||||
use QueryTrait;
|
||||
|
||||
private bool $isSQLite2 = false;
|
||||
|
||||
/**
|
||||
* 清空数据表
|
||||
*
|
||||
* @param string $table
|
||||
* @param mixed $handle 连接对象
|
||||
* @throws SQLException
|
||||
*/
|
||||
public function truncate(string $table, $handle)
|
||||
{
|
||||
$this->query('DELETE FROM ' . $this->quoteColumn($table), $handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象引号过滤
|
||||
*
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
public function quoteColumn(string $string): string
|
||||
{
|
||||
return '"' . $string . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤字段名
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param array $result
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function filterColumnName(array $result): array
|
||||
{
|
||||
/** 如果结果为空,直接返回 */
|
||||
if (empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$tResult = [];
|
||||
|
||||
/** 遍历数组 */
|
||||
foreach ($result as $key => $val) {
|
||||
/** 按点分隔 */
|
||||
if (false !== ($pos = strpos($key, '.'))) {
|
||||
$key = substr($key, $pos + 1);
|
||||
}
|
||||
|
||||
$tResult[trim($key, '"')] = $val;
|
||||
}
|
||||
|
||||
return $tResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理sqlite2的distinct count
|
||||
*
|
||||
* @param string $sql
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function filterCountQuery(string $sql): string
|
||||
{
|
||||
if (preg_match("/SELECT\s+COUNT\(DISTINCT\s+([^\)]+)\)\s+(AS\s+[^\s]+)?\s*FROM\s+(.+)/is", $sql, $matches)) {
|
||||
return 'SELECT COUNT(' . $matches[1] . ') ' . $matches[2] . ' FROM SELECT DISTINCT '
|
||||
. $matches[1] . ' FROM ' . $matches[3];
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合成查询语句
|
||||
*
|
||||
* @access public
|
||||
* @param array $sql 查询对象词法数组
|
||||
* @return string
|
||||
*/
|
||||
public function parseSelect(array $sql): string
|
||||
{
|
||||
$query = $this->buildQuery($sql);
|
||||
|
||||
if ($this->isSQLite2) {
|
||||
$query = $this->filterCountQuery($query);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDriver(): string
|
||||
{
|
||||
return 'sqlite';
|
||||
}
|
||||
}
|
||||
18
var/Typecho/Db/Exception.php
Executable file
18
var/Typecho/Db/Exception.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Typecho\Exception as TypechoException;
|
||||
|
||||
/**
|
||||
* 数据库异常类
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class Exception extends TypechoException
|
||||
{
|
||||
}
|
||||
558
var/Typecho/Db/Query.php
Executable file
558
var/Typecho/Db/Query.php
Executable file
@@ -0,0 +1,558 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db;
|
||||
|
||||
use Typecho\Db;
|
||||
|
||||
/**
|
||||
* Typecho数据库查询语句构建类
|
||||
* 使用方法:
|
||||
* $query = new Query(); //或者使用DB积累的sql方法返回实例化对象
|
||||
* $query->select('posts', 'post_id, post_title')
|
||||
* ->where('post_id = %d', 1)
|
||||
* ->limit(1);
|
||||
* echo $query;
|
||||
* 打印的结果将是
|
||||
* SELECT post_id, post_title FROM posts WHERE 1=1 AND post_id = 1 LIMIT 1
|
||||
*
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class Query
|
||||
{
|
||||
/** 数据库关键字 */
|
||||
private const KEYWORDS = '*PRIMARY|AND|OR|LIKE|ILIKE|BINARY|BY|DISTINCT|AS|IN|NOT|IS|NULL';
|
||||
|
||||
/**
|
||||
* 默认字段
|
||||
*
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private static array $default = [
|
||||
'action' => null,
|
||||
'table' => null,
|
||||
'fields' => '*',
|
||||
'join' => [],
|
||||
'where' => null,
|
||||
'limit' => null,
|
||||
'offset' => null,
|
||||
'order' => null,
|
||||
'group' => null,
|
||||
'having' => null,
|
||||
'rows' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* 数据库适配器
|
||||
*
|
||||
* @var Adapter
|
||||
*/
|
||||
private Adapter $adapter;
|
||||
|
||||
/**
|
||||
* 查询语句预结构,由数组构成,方便组合为SQL查询字符串
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $sqlPreBuild;
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $prefix;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $params = [];
|
||||
|
||||
/**
|
||||
* 构造函数,引用数据库适配器作为内部数据
|
||||
*
|
||||
* @param Adapter $adapter 数据库适配器
|
||||
* @param string $prefix 前缀
|
||||
*/
|
||||
public function __construct(Adapter $adapter, string $prefix)
|
||||
{
|
||||
$this->adapter = &$adapter;
|
||||
$this->prefix = $prefix;
|
||||
|
||||
$this->sqlPreBuild = self::$default;
|
||||
}
|
||||
|
||||
/**
|
||||
* set default params
|
||||
*
|
||||
* @param array $default
|
||||
*/
|
||||
public static function setDefault(array $default)
|
||||
{
|
||||
self::$default = array_merge(self::$default, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询字串属性值
|
||||
*
|
||||
* @access public
|
||||
* @param string $attributeName 属性名称
|
||||
* @return string
|
||||
*/
|
||||
public function getAttribute(string $attributeName): ?string
|
||||
{
|
||||
return $this->sqlPreBuild[$attributeName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除查询字串属性值
|
||||
*
|
||||
* @access public
|
||||
* @param string $attributeName 属性名称
|
||||
* @return Query
|
||||
*/
|
||||
public function cleanAttribute(string $attributeName): Query
|
||||
{
|
||||
if (isset($this->sqlPreBuild[$attributeName])) {
|
||||
$this->sqlPreBuild[$attributeName] = self::$default[$attributeName];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接表
|
||||
*
|
||||
* @param string $table 需要连接的表
|
||||
* @param string $condition 连接条件
|
||||
* @param string $op 连接方法(LEFT, RIGHT, INNER)
|
||||
* @return Query
|
||||
*/
|
||||
public function join(string $table, string $condition, string $op = Db::INNER_JOIN): Query
|
||||
{
|
||||
$this->sqlPreBuild['join'][] = [$this->filterPrefix($table), $this->filterColumn($condition), $op];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤表前缀,表前缀由table.构成
|
||||
*
|
||||
* @param string $string 需要解析的字符串
|
||||
* @return string
|
||||
*/
|
||||
private function filterPrefix(string $string): string
|
||||
{
|
||||
return (0 === strpos($string, 'table.')) ? substr_replace($string, $this->prefix, 0, 6) : $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤数组键值
|
||||
*
|
||||
* @access private
|
||||
* @param string $str 待处理字段值
|
||||
* @return string
|
||||
*/
|
||||
private function filterColumn(string $str): string
|
||||
{
|
||||
$str = $str . ' 0';
|
||||
$length = strlen($str);
|
||||
$lastIsAlnum = false;
|
||||
$result = '';
|
||||
$word = '';
|
||||
$split = '';
|
||||
$quotes = 0;
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$cha = $str[$i];
|
||||
|
||||
if (ctype_alnum($cha) || false !== strpos('_*', $cha)) {
|
||||
if (!$lastIsAlnum) {
|
||||
if (
|
||||
$quotes > 0 && !ctype_digit($word) && '.' != $split
|
||||
&& false === strpos(self::KEYWORDS, strtoupper($word))
|
||||
) {
|
||||
$word = $this->adapter->quoteColumn($word);
|
||||
} elseif ('.' == $split && 'table' == $word) {
|
||||
$word = $this->prefix;
|
||||
$split = '';
|
||||
}
|
||||
|
||||
$result .= $word . $split;
|
||||
$word = '';
|
||||
$quotes = 0;
|
||||
}
|
||||
|
||||
$word .= $cha;
|
||||
$lastIsAlnum = true;
|
||||
} else {
|
||||
if ($lastIsAlnum) {
|
||||
if (0 == $quotes) {
|
||||
if (false !== strpos(' ,)=<>.+-*/', $cha)) {
|
||||
$quotes = 1;
|
||||
} elseif ('(' == $cha) {
|
||||
$quotes = - 1;
|
||||
}
|
||||
}
|
||||
|
||||
$split = '';
|
||||
}
|
||||
|
||||
$split .= $cha;
|
||||
$lastIsAlnum = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* AND条件查询语句
|
||||
*
|
||||
* @param ...$args
|
||||
* @return $this
|
||||
*/
|
||||
public function where(...$args): Query
|
||||
{
|
||||
[$condition] = $args;
|
||||
$condition = str_replace('?', "%s", $this->filterColumn($condition));
|
||||
$operator = empty($this->sqlPreBuild['where']) ? ' WHERE ' : ' AND';
|
||||
|
||||
if (count($args) <= 1) {
|
||||
$this->sqlPreBuild['where'] .= $operator . ' (' . $condition . ')';
|
||||
} else {
|
||||
array_shift($args);
|
||||
$this->sqlPreBuild['where'] .= $operator . ' (' . vsprintf($condition, $this->quoteValues($args)) . ')';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义参数
|
||||
*
|
||||
* @param array $values
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function quoteValues(array $values): array
|
||||
{
|
||||
foreach ($values as &$value) {
|
||||
if (is_array($value)) {
|
||||
$value = '(' . implode(',', array_map([$this, 'quoteValue'], $value)) . ')';
|
||||
} else {
|
||||
$value = $this->quoteValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟转义
|
||||
*
|
||||
* @param $value
|
||||
* @return string
|
||||
*/
|
||||
public function quoteValue($value): string
|
||||
{
|
||||
$this->params[] = $value;
|
||||
return '#param:' . (count($this->params) - 1) . '#';
|
||||
}
|
||||
|
||||
/**
|
||||
* OR条件查询语句
|
||||
*
|
||||
* @param ...$args
|
||||
* @return Query
|
||||
*/
|
||||
public function orWhere(...$args): Query
|
||||
{
|
||||
[$condition] = $args;
|
||||
$condition = str_replace('?', "%s", $this->filterColumn($condition));
|
||||
$operator = empty($this->sqlPreBuild['where']) ? ' WHERE ' : ' OR';
|
||||
|
||||
if (func_num_args() <= 1) {
|
||||
$this->sqlPreBuild['where'] .= $operator . ' (' . $condition . ')';
|
||||
} else {
|
||||
array_shift($args);
|
||||
$this->sqlPreBuild['where'] .= $operator . ' (' . vsprintf($condition, $this->quoteValues($args)) . ')';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询行数限制
|
||||
*
|
||||
* @param mixed $limit 需要查询的行数
|
||||
* @return Query
|
||||
*/
|
||||
public function limit($limit): Query
|
||||
{
|
||||
$this->sqlPreBuild['limit'] = intval($limit);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询行数偏移量
|
||||
*
|
||||
* @param mixed $offset 需要偏移的行数
|
||||
* @return Query
|
||||
*/
|
||||
public function offset($offset): Query
|
||||
{
|
||||
$this->sqlPreBuild['offset'] = intval($offset);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param mixed $page 页数
|
||||
* @param mixed $pageSize 每页行数
|
||||
* @return Query
|
||||
*/
|
||||
public function page($page, $pageSize): Query
|
||||
{
|
||||
$pageSize = intval($pageSize);
|
||||
$this->sqlPreBuild['limit'] = $pageSize;
|
||||
$this->sqlPreBuild['offset'] = (max(intval($page), 1) - 1) * $pageSize;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定需要写入的栏目及其值
|
||||
*
|
||||
* @param array $rows
|
||||
* @return Query
|
||||
*/
|
||||
public function rows(array $rows): Query
|
||||
{
|
||||
foreach ($rows as $key => $row) {
|
||||
$this->sqlPreBuild['rows'][$this->filterColumn($key)]
|
||||
= is_null($row) ? 'NULL' : $this->adapter->quoteValue($row);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定需要写入栏目及其值
|
||||
* 单行且不会转义引号
|
||||
*
|
||||
* @param string $key 栏目名称
|
||||
* @param mixed $value 指定的值
|
||||
* @param bool $escape 是否转义
|
||||
* @return Query
|
||||
*/
|
||||
public function expression(string $key, $value, bool $escape = true): Query
|
||||
{
|
||||
$this->sqlPreBuild['rows'][$this->filterColumn($key)] = $escape ? $this->filterColumn($value) : $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序顺序(ORDER BY)
|
||||
*
|
||||
* @param string $orderBy 排序的索引
|
||||
* @param string $sort 排序的方式(ASC, DESC)
|
||||
* @return Query
|
||||
*/
|
||||
public function order(string $orderBy, string $sort = Db::SORT_ASC): Query
|
||||
{
|
||||
if (empty($this->sqlPreBuild['order'])) {
|
||||
$this->sqlPreBuild['order'] = ' ORDER BY ';
|
||||
} else {
|
||||
$this->sqlPreBuild['order'] .= ', ';
|
||||
}
|
||||
|
||||
$this->sqlPreBuild['order'] .= $this->filterColumn($orderBy) . (empty($sort) ? null : ' ' . $sort);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合聚集(GROUP BY)
|
||||
*
|
||||
* @param string $key 聚集的键值
|
||||
* @return Query
|
||||
*/
|
||||
public function group(string $key): Query
|
||||
{
|
||||
$this->sqlPreBuild['group'] = ' GROUP BY ' . $this->filterColumn($key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $condition
|
||||
* @param ...$args
|
||||
* @return $this
|
||||
*/
|
||||
public function having(string $condition, ...$args): Query
|
||||
{
|
||||
$condition = str_replace('?', "%s", $this->filterColumn($condition));
|
||||
$operator = empty($this->sqlPreBuild['having']) ? ' HAVING ' : ' AND';
|
||||
|
||||
if (count($args) == 0) {
|
||||
$this->sqlPreBuild['having'] .= $operator . ' (' . $condition . ')';
|
||||
} else {
|
||||
$this->sqlPreBuild['having'] .= $operator . ' (' . vsprintf($condition, $this->quoteValues($args)) . ')';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择查询字段
|
||||
*
|
||||
* @param mixed ...$args 查询字段
|
||||
* @return $this
|
||||
*/
|
||||
public function select(...$args): Query
|
||||
{
|
||||
$this->sqlPreBuild['action'] = Db::SELECT;
|
||||
|
||||
$this->sqlPreBuild['fields'] = $this->getColumnFromParameters($args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从参数中合成查询字段
|
||||
*
|
||||
* @access private
|
||||
* @param array $parameters
|
||||
* @return string
|
||||
*/
|
||||
private function getColumnFromParameters(array $parameters): string
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
foreach ($parameters as $value) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key => $val) {
|
||||
$fields[] = $key . ' AS ' . $val;
|
||||
}
|
||||
} else {
|
||||
$fields[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->filterColumn(implode(' , ', $fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询记录操作(SELECT)
|
||||
*
|
||||
* @param string $table 查询的表
|
||||
* @return Query
|
||||
*/
|
||||
public function from(string $table): Query
|
||||
{
|
||||
$this->sqlPreBuild['table'] = $this->filterPrefix($table);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新记录操作(UPDATE)
|
||||
*
|
||||
* @param string $table 需要更新记录的表
|
||||
* @return Query
|
||||
*/
|
||||
public function update(string $table): Query
|
||||
{
|
||||
$this->sqlPreBuild['action'] = Db::UPDATE;
|
||||
$this->sqlPreBuild['table'] = $this->filterPrefix($table);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记录操作(DELETE)
|
||||
*
|
||||
* @param string $table 需要删除记录的表
|
||||
* @return Query
|
||||
*/
|
||||
public function delete(string $table): Query
|
||||
{
|
||||
$this->sqlPreBuild['action'] = Db::DELETE;
|
||||
$this->sqlPreBuild['table'] = $this->filterPrefix($table);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入记录操作(INSERT)
|
||||
*
|
||||
* @param string $table 需要插入记录的表
|
||||
* @return Query
|
||||
*/
|
||||
public function insert(string $table): Query
|
||||
{
|
||||
$this->sqlPreBuild['action'] = Db::INSERT;
|
||||
$this->sqlPreBuild['table'] = $this->filterPrefix($table);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @return string
|
||||
*/
|
||||
public function prepare(string $query): string
|
||||
{
|
||||
$params = $this->params;
|
||||
$adapter = $this->adapter;
|
||||
|
||||
return preg_replace_callback("/#param:([0-9]+)#/", function ($matches) use ($params, $adapter) {
|
||||
if (array_key_exists($matches[1], $params)) {
|
||||
return is_null($params[$matches[1]]) ? 'NULL' : $adapter->quoteValue($params[$matches[1]]);
|
||||
} else {
|
||||
return $matches[0];
|
||||
}
|
||||
}, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造最终查询语句
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
switch ($this->sqlPreBuild['action']) {
|
||||
case Db::SELECT:
|
||||
return $this->adapter->parseSelect($this->sqlPreBuild);
|
||||
case Db::INSERT:
|
||||
return 'INSERT INTO '
|
||||
. $this->sqlPreBuild['table']
|
||||
. '(' . implode(' , ', array_keys($this->sqlPreBuild['rows'])) . ')'
|
||||
. ' VALUES '
|
||||
. '(' . implode(' , ', array_values($this->sqlPreBuild['rows'])) . ')'
|
||||
. $this->sqlPreBuild['limit'];
|
||||
case Db::DELETE:
|
||||
return 'DELETE FROM '
|
||||
. $this->sqlPreBuild['table']
|
||||
. $this->sqlPreBuild['where'];
|
||||
case Db::UPDATE:
|
||||
$columns = [];
|
||||
if (isset($this->sqlPreBuild['rows'])) {
|
||||
foreach ($this->sqlPreBuild['rows'] as $key => $val) {
|
||||
$columns[] = "$key = $val";
|
||||
}
|
||||
}
|
||||
|
||||
return 'UPDATE '
|
||||
. $this->sqlPreBuild['table']
|
||||
. ' SET ' . implode(' , ', $columns)
|
||||
. $this->sqlPreBuild['where'];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
var/Typecho/Db/Query/Exception.php
Executable file
18
var/Typecho/Db/Query/Exception.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Db\Query;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Typecho\Db\Exception as DbException;
|
||||
|
||||
/**
|
||||
* 数据库查询异常类
|
||||
*
|
||||
* @package Db
|
||||
*/
|
||||
class Exception extends DbException
|
||||
{
|
||||
}
|
||||
19
var/Typecho/Exception.php
Executable file
19
var/Typecho/Exception.php
Executable file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
/**
|
||||
* Typecho异常基类
|
||||
* 主要重载异常打印函数
|
||||
*
|
||||
* @package Exception
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
|
||||
public function __construct($message, $code = 0)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->code = $code;
|
||||
}
|
||||
}
|
||||
412
var/Typecho/Feed.php
Executable file
412
var/Typecho/Feed.php
Executable file
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
/**
|
||||
* Feed
|
||||
*
|
||||
* @package Feed
|
||||
*/
|
||||
class Feed
|
||||
{
|
||||
/** 定义RSS 1.0类型 */
|
||||
public const RSS1 = 'RSS 1.0';
|
||||
|
||||
/** 定义RSS 2.0类型 */
|
||||
public const RSS2 = 'RSS 2.0';
|
||||
|
||||
/** 定义ATOM 1.0类型 */
|
||||
public const ATOM1 = 'ATOM 1.0';
|
||||
|
||||
/** 定义RSS时间格式 */
|
||||
public const DATE_RFC822 = 'r';
|
||||
|
||||
/** 定义ATOM时间格式 */
|
||||
public const DATE_W3CDTF = 'c';
|
||||
|
||||
/** 定义行结束符 */
|
||||
public const EOL = "\n";
|
||||
|
||||
/**
|
||||
* feed状态
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $type;
|
||||
|
||||
/**
|
||||
* 字符集编码
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $charset;
|
||||
|
||||
/**
|
||||
* 语言状态
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $lang;
|
||||
|
||||
/**
|
||||
* 聚合地址
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $feedUrl;
|
||||
|
||||
/**
|
||||
* 基本地址
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $baseUrl;
|
||||
|
||||
/**
|
||||
* 聚合标题
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $title;
|
||||
|
||||
/**
|
||||
* 聚合副标题
|
||||
*
|
||||
* @access private
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $subTitle;
|
||||
|
||||
/**
|
||||
* 版本信息
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $version;
|
||||
|
||||
/**
|
||||
* 所有的items
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $items = [];
|
||||
|
||||
/**
|
||||
* 创建Feed对象
|
||||
*
|
||||
* @param $version
|
||||
* @param string $type
|
||||
* @param string $charset
|
||||
* @param string $lang
|
||||
*/
|
||||
public function __construct($version, string $type = self::RSS2, string $charset = 'UTF-8', string $lang = 'en')
|
||||
{
|
||||
$this->version = $version;
|
||||
$this->type = $type;
|
||||
$this->charset = $charset;
|
||||
$this->lang = $lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标题
|
||||
*
|
||||
* @param string $title 标题
|
||||
*/
|
||||
public function setTitle(string $title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置副标题
|
||||
*
|
||||
* @param string|null $subTitle 副标题
|
||||
*/
|
||||
public function setSubTitle(?string $subTitle)
|
||||
{
|
||||
$this->subTitle = $subTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置聚合地址
|
||||
*
|
||||
* @param string $feedUrl 聚合地址
|
||||
*/
|
||||
public function setFeedUrl(string $feedUrl)
|
||||
{
|
||||
$this->feedUrl = $feedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFeedUrl(): string
|
||||
{
|
||||
return $this->feedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主页
|
||||
*
|
||||
* @param string $baseUrl 主页地址
|
||||
*/
|
||||
public function setBaseUrl(string $baseUrl)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* $item的格式为
|
||||
* <code>
|
||||
* array (
|
||||
* 'title' => 'xxx',
|
||||
* 'content' => 'xxx',
|
||||
* 'excerpt' => 'xxx',
|
||||
* 'date' => 'xxx',
|
||||
* 'link' => 'xxx',
|
||||
* 'author' => 'xxx',
|
||||
* 'comments' => 'xxx',
|
||||
* 'commentsUrl'=> 'xxx',
|
||||
* 'commentsFeedUrl' => 'xxx',
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @param array $item
|
||||
*/
|
||||
public function addItem(array $item)
|
||||
{
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出字符串
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$result = '<?xml version="1.0" encoding="' . $this->charset . '"?>' . self::EOL;
|
||||
|
||||
if (self::RSS1 == $this->type) {
|
||||
$result .= '<rdf:RDF
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://purl.org/rss/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">' . self::EOL;
|
||||
|
||||
$content = '';
|
||||
$links = [];
|
||||
$lastUpdate = 0;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$content .= '<item rdf:about="' . $item['link'] . '">' . self::EOL;
|
||||
$content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
|
||||
$content .= '<link>' . $item['link'] . '</link>' . self::EOL;
|
||||
$content .= '<dc:date>' . $this->dateFormat($item['date']) . '</dc:date>' . self::EOL;
|
||||
$content .= '<description>' . strip_tags($item['content']) . '</description>' . self::EOL;
|
||||
if (!empty($item['suffix'])) {
|
||||
$content .= $item['suffix'];
|
||||
}
|
||||
$content .= '</item>' . self::EOL;
|
||||
|
||||
$links[] = $item['link'];
|
||||
|
||||
if ($item['date'] > $lastUpdate) {
|
||||
$lastUpdate = $item['date'];
|
||||
}
|
||||
}
|
||||
|
||||
$result .= '<channel rdf:about="' . $this->feedUrl . '">
|
||||
<title>' . htmlspecialchars($this->title) . '</title>
|
||||
<link>' . $this->baseUrl . '</link>
|
||||
<description>' . htmlspecialchars($this->subTitle ?? '') . '</description>
|
||||
<items>
|
||||
<rdf:Seq>' . self::EOL;
|
||||
|
||||
foreach ($links as $link) {
|
||||
$result .= '<rdf:li resource="' . $link . '"/>' . self::EOL;
|
||||
}
|
||||
|
||||
$result .= '</rdf:Seq>
|
||||
</items>
|
||||
</channel>' . self::EOL;
|
||||
|
||||
$result .= $content . '</rdf:RDF>';
|
||||
} elseif (self::RSS2 == $this->type) {
|
||||
$result .= '<rss version="2.0"
|
||||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
|
||||
<channel>' . self::EOL;
|
||||
|
||||
$content = '';
|
||||
$lastUpdate = 0;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$content .= '<item>' . self::EOL;
|
||||
$content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
|
||||
$content .= '<link>' . $item['link'] . '</link>' . self::EOL;
|
||||
$content .= '<guid>' . $item['link'] . '</guid>' . self::EOL;
|
||||
$content .= '<pubDate>' . $this->dateFormat($item['date']) . '</pubDate>' . self::EOL;
|
||||
$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName)
|
||||
. '</dc:creator>' . self::EOL;
|
||||
|
||||
if (!empty($item['category']) && is_array($item['category'])) {
|
||||
foreach ($item['category'] as $category) {
|
||||
$content .= '<category><![CDATA[' . $category['name'] . ']]></category>' . self::EOL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($item['excerpt'])) {
|
||||
$content .= '<description><![CDATA[' . strip_tags($item['excerpt'])
|
||||
. ']]></description>' . self::EOL;
|
||||
}
|
||||
|
||||
if (!empty($item['content'])) {
|
||||
$content .= '<content:encoded xml:lang="' . $this->lang . '"><![CDATA['
|
||||
. self::EOL .
|
||||
$item['content'] . self::EOL .
|
||||
']]></content:encoded>' . self::EOL;
|
||||
}
|
||||
|
||||
if (isset($item['comments']) && strlen($item['comments']) > 0) {
|
||||
$content .= '<slash:comments>' . $item['comments'] . '</slash:comments>' . self::EOL;
|
||||
}
|
||||
|
||||
$content .= '<comments>' . $item['link'] . '#comments</comments>' . self::EOL;
|
||||
if (!empty($item['commentsFeedUrl'])) {
|
||||
$content .= '<wfw:commentRss>' . $item['commentsFeedUrl'] . '</wfw:commentRss>' . self::EOL;
|
||||
}
|
||||
|
||||
if (!empty($item['suffix'])) {
|
||||
$content .= $item['suffix'];
|
||||
}
|
||||
|
||||
$content .= '</item>' . self::EOL;
|
||||
|
||||
if ($item['date'] > $lastUpdate) {
|
||||
$lastUpdate = $item['date'];
|
||||
}
|
||||
}
|
||||
|
||||
$result .= '<title>' . htmlspecialchars($this->title) . '</title>
|
||||
<link>' . $this->baseUrl . '</link>
|
||||
<atom:link href="' . $this->feedUrl . '" rel="self" type="application/rss+xml" />
|
||||
<language>' . $this->lang . '</language>
|
||||
<description>' . htmlspecialchars($this->subTitle ?? '') . '</description>
|
||||
<lastBuildDate>' . $this->dateFormat($lastUpdate) . '</lastBuildDate>
|
||||
<pubDate>' . $this->dateFormat($lastUpdate) . '</pubDate>' . self::EOL;
|
||||
|
||||
$result .= $content . '</channel>
|
||||
</rss>';
|
||||
} elseif (self::ATOM1 == $this->type) {
|
||||
$result .= '<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
xmlns:thr="http://purl.org/syndication/thread/1.0"
|
||||
xml:lang="' . $this->lang . '"
|
||||
xml:base="' . $this->baseUrl . '"
|
||||
>' . self::EOL;
|
||||
|
||||
$content = '';
|
||||
$lastUpdate = 0;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$content .= '<entry>' . self::EOL;
|
||||
$content .= '<title type="html"><![CDATA[' . $item['title'] . ']]></title>' . self::EOL;
|
||||
$content .= '<link rel="alternate" type="text/html" href="' . $item['link'] . '" />' . self::EOL;
|
||||
$content .= '<id>' . $item['link'] . '</id>' . self::EOL;
|
||||
$content .= '<updated>' . $this->dateFormat($item['date']) . '</updated>' . self::EOL;
|
||||
$content .= '<published>' . $this->dateFormat($item['date']) . '</published>' . self::EOL;
|
||||
$content .= '<author>
|
||||
<name>' . $item['author']->screenName . '</name>
|
||||
<uri>' . $item['author']->url . '</uri>
|
||||
</author>' . self::EOL;
|
||||
|
||||
if (!empty($item['category']) && is_array($item['category'])) {
|
||||
foreach ($item['category'] as $category) {
|
||||
$content .= '<category scheme="' . $category['permalink'] . '" term="'
|
||||
. $category['name'] . '" />' . self::EOL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($item['excerpt'])) {
|
||||
$content .= '<summary type="html"><![CDATA[' . htmlspecialchars($item['excerpt'])
|
||||
. ']]></summary>' . self::EOL;
|
||||
}
|
||||
|
||||
if (!empty($item['content'])) {
|
||||
$content .= '<content type="html" xml:base="' . $item['link']
|
||||
. '" xml:lang="' . $this->lang . '"><![CDATA['
|
||||
. self::EOL .
|
||||
$item['content'] . self::EOL .
|
||||
']]></content>' . self::EOL;
|
||||
}
|
||||
|
||||
if (isset($item['comments']) && strlen($item['comments']) > 0) {
|
||||
$content .= '<link rel="replies" type="text/html" href="' . $item['link']
|
||||
. '#comments" thr:count="' . $item['comments'] . '" />' . self::EOL;
|
||||
|
||||
if (!empty($item['commentsFeedUrl'])) {
|
||||
$content .= '<link rel="replies" type="application/atom+xml" href="'
|
||||
. $item['commentsFeedUrl'] . '" thr:count="' . $item['comments'] . '"/>' . self::EOL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($item['suffix'])) {
|
||||
$content .= $item['suffix'];
|
||||
}
|
||||
|
||||
$content .= '</entry>' . self::EOL;
|
||||
|
||||
if ($item['date'] > $lastUpdate) {
|
||||
$lastUpdate = $item['date'];
|
||||
}
|
||||
}
|
||||
|
||||
$result .= '<title type="text">' . htmlspecialchars($this->title) . '</title>
|
||||
<subtitle type="text">' . htmlspecialchars($this->subTitle ?? '') . '</subtitle>
|
||||
<updated>' . $this->dateFormat($lastUpdate) . '</updated>
|
||||
<generator uri="https://typecho.org/" version="' . $this->version . '">Typecho</generator>
|
||||
<link rel="alternate" type="text/html" href="' . $this->baseUrl . '" />
|
||||
<id>' . $this->feedUrl . '</id>
|
||||
<link rel="self" type="application/atom+xml" href="' . $this->feedUrl . '" />
|
||||
';
|
||||
$result .= $content . '</feed>';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Feed时间格式
|
||||
*
|
||||
* @param integer $stamp 时间戳
|
||||
* @return string
|
||||
*/
|
||||
public function dateFormat(int $stamp): string
|
||||
{
|
||||
if (self::RSS2 == $this->type) {
|
||||
return date(self::DATE_RFC822, $stamp);
|
||||
} elseif (self::RSS1 == $this->type || self::ATOM1 == $this->type) {
|
||||
return date(self::DATE_W3CDTF, $stamp);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
398
var/Typecho/Http/Client.php
Executable file
398
var/Typecho/Http/Client.php
Executable file
@@ -0,0 +1,398 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Http;
|
||||
|
||||
use Typecho\Common;
|
||||
use Typecho\Http\Client\Exception;
|
||||
|
||||
/**
|
||||
* Http客户端
|
||||
*
|
||||
* @category typecho
|
||||
* @package Http
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
/** POST方法 */
|
||||
public const METHOD_POST = 'POST';
|
||||
|
||||
/** GET方法 */
|
||||
public const METHOD_GET = 'GET';
|
||||
|
||||
/** PUT方法 */
|
||||
public const METHOD_PUT = 'PUT';
|
||||
|
||||
/** DELETE方法 */
|
||||
public const METHOD_DELETE = 'DELETE';
|
||||
|
||||
/**
|
||||
* 方法名
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $method = self::METHOD_GET;
|
||||
|
||||
/**
|
||||
* User-Agent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $agent;
|
||||
|
||||
/**
|
||||
* 传递参数
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $query;
|
||||
|
||||
/**
|
||||
* 设置超时
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private int $timeout = 3;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $multipart = true;
|
||||
|
||||
/**
|
||||
* 需要在body中传递的值
|
||||
*
|
||||
* @var array|string
|
||||
*/
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* 头信息参数
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $headers = [];
|
||||
|
||||
/**
|
||||
* cookies
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $cookies = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
/**
|
||||
* 回执头部信息
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $responseHeader = [];
|
||||
|
||||
/**
|
||||
* 回执代码
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private int $responseStatus;
|
||||
|
||||
/**
|
||||
* 回执身体
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $responseBody;
|
||||
|
||||
/**
|
||||
* 设置指定的COOKIE值
|
||||
*
|
||||
* @param string $key 指定的参数
|
||||
* @param mixed $value 设置的值
|
||||
* @return $this
|
||||
*/
|
||||
public function setCookie(string $key, $value): Client
|
||||
{
|
||||
$this->cookies[$key] = $value;
|
||||
$this->setHeader('Cookie', str_replace('&', '; ', http_build_query($this->cookies)));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置传递参数
|
||||
*
|
||||
* @param mixed $query 传递参数
|
||||
* @return $this
|
||||
*/
|
||||
public function setQuery($query): Client
|
||||
{
|
||||
$query = is_array($query) ? http_build_query($query) : $query;
|
||||
$this->query = empty($this->query) ? $query : $this->query . '&' . $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要POST的数据
|
||||
*
|
||||
* @param array|string $data 需要POST的数据
|
||||
* @param string $method
|
||||
* @return $this
|
||||
*/
|
||||
public function setData($data, string $method = self::METHOD_POST): Client
|
||||
{
|
||||
if (is_array($data) && is_array($this->data)) {
|
||||
$this->data = array_merge($this->data, $data);
|
||||
} else {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
$this->setMethod($method);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要请求的Json数据
|
||||
*
|
||||
* @param $data
|
||||
* @param string $method
|
||||
* @return $this
|
||||
*/
|
||||
public function setJson($data, string $method = self::METHOD_POST): Client
|
||||
{
|
||||
$this->setData(json_encode($data), $method)
|
||||
->setMultipart(true)
|
||||
->setHeader('Content-Type', 'application/json');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置方法名
|
||||
*
|
||||
* @param string $method
|
||||
* @return $this
|
||||
*/
|
||||
public function setMethod(string $method): Client
|
||||
{
|
||||
$this->method = $method;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要POST的文件
|
||||
*
|
||||
* @param array $files 需要POST的文件
|
||||
* @param string $method
|
||||
* @return $this
|
||||
*/
|
||||
public function setFiles(array $files, string $method = self::METHOD_POST): Client
|
||||
{
|
||||
if (is_array($this->data)) {
|
||||
foreach ($files as $name => $file) {
|
||||
$this->data[$name] = new \CURLFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setMethod($method);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置超时时间
|
||||
*
|
||||
* @param integer $timeout 超时时间
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimeout(int $timeout): Client
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* setAgent
|
||||
*
|
||||
* @param string $agent
|
||||
* @return $this
|
||||
*/
|
||||
public function setAgent(string $agent): Client
|
||||
{
|
||||
$this->agent = $agent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $multipart
|
||||
* @return $this
|
||||
*/
|
||||
public function setMultipart(bool $multipart): Client
|
||||
{
|
||||
$this->multipart = $multipart;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $key
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setOption(int $key, $value): Client
|
||||
{
|
||||
$this->options[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置头信息参数
|
||||
*
|
||||
* @param string $key 参数名称
|
||||
* @param string $value 参数值
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeader(string $key, string $value): Client
|
||||
{
|
||||
$key = str_replace(' ', '-', ucwords(str_replace('-', ' ', $key)));
|
||||
|
||||
if ($key == 'User-Agent') {
|
||||
$this->setAgent($value);
|
||||
} else {
|
||||
$this->headers[$key] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
*
|
||||
* @param string $url 请求地址
|
||||
* @throws Exception
|
||||
*/
|
||||
public function send(string $url)
|
||||
{
|
||||
$params = parse_url($url);
|
||||
$query = empty($params['query']) ? '' : $params['query'];
|
||||
|
||||
if (!empty($this->query)) {
|
||||
$query = empty($query) ? $this->query : '&' . $this->query;
|
||||
}
|
||||
|
||||
if (!empty($query)) {
|
||||
$params['query'] = $query;
|
||||
}
|
||||
|
||||
$url = Common::buildUrl($params);
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
|
||||
|
||||
if (isset($this->agent)) {
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, $this->agent);
|
||||
}
|
||||
|
||||
/** 设置header信息 */
|
||||
if (!empty($this->headers)) {
|
||||
$headers = [];
|
||||
|
||||
foreach ($this->headers as $key => $val) {
|
||||
$headers[] = $key . ': ' . $val;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
}
|
||||
|
||||
if (!empty($this->data)) {
|
||||
$data = $this->data;
|
||||
|
||||
if (!$this->multipart) {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
$data = is_array($data) ? http_build_query($data) : $data;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) {
|
||||
$parts = explode(':', $header, 2);
|
||||
|
||||
if (count($parts) == 2) {
|
||||
[$key, $value] = $parts;
|
||||
$this->responseHeader[strtolower(trim($key))] = trim($value);
|
||||
}
|
||||
|
||||
return strlen($header);
|
||||
});
|
||||
|
||||
foreach ($this->options as $key => $val) {
|
||||
curl_setopt($ch, $key, $val);
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
if (false === $response) {
|
||||
$error = curl_error($ch);
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
unset($ch);
|
||||
} else {
|
||||
curl_close($ch);
|
||||
}
|
||||
throw new Exception($error, 500);
|
||||
}
|
||||
|
||||
$this->responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$this->responseBody = $response;
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
unset($ch);
|
||||
} else {
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取回执的头部信息
|
||||
*
|
||||
* @param string $key 头信息名称
|
||||
* @return ?string
|
||||
*/
|
||||
public function getResponseHeader(string $key): ?string
|
||||
{
|
||||
$key = strtolower($key);
|
||||
return $this->responseHeader[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取回执代码
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getResponseStatus(): int
|
||||
{
|
||||
return $this->responseStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取回执身体
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getResponseBody(): string
|
||||
{
|
||||
return $this->responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的连接
|
||||
*
|
||||
* @return ?Client
|
||||
*/
|
||||
public static function get(): ?Client
|
||||
{
|
||||
return extension_loaded('curl') ? new static() : null;
|
||||
}
|
||||
}
|
||||
18
var/Typecho/Http/Client/Exception.php
Executable file
18
var/Typecho/Http/Client/Exception.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Http\Client;
|
||||
|
||||
use Typecho\Exception as TypechoException;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Http客户端异常类
|
||||
*
|
||||
* @package Http
|
||||
*/
|
||||
class Exception extends TypechoException
|
||||
{
|
||||
}
|
||||
169
var/Typecho/I18n.php
Executable file
169
var/Typecho/I18n.php
Executable file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
use Typecho\I18n\GetTextMulti;
|
||||
|
||||
/**
|
||||
* 国际化字符翻译
|
||||
*
|
||||
* @package I18n
|
||||
*/
|
||||
class I18n
|
||||
{
|
||||
/**
|
||||
* 是否已经载入的标志位
|
||||
*
|
||||
* @access private
|
||||
* @var GetTextMulti|null
|
||||
*/
|
||||
private static ?GetTextMulti $loaded = null;
|
||||
|
||||
/**
|
||||
* 语言文件
|
||||
*
|
||||
* @access private
|
||||
* @var string|null
|
||||
*/
|
||||
private static ?string $lang = null;
|
||||
|
||||
/**
|
||||
* 翻译文字
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $string 待翻译的文字
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function translate(string $string): string
|
||||
{
|
||||
self::init();
|
||||
return self::$loaded ? self::$loaded->translate($string) : $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化语言文件
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private static function init()
|
||||
{
|
||||
/** GetText支持 */
|
||||
if (!isset(self::$loaded) && self::$lang && file_exists(self::$lang)) {
|
||||
self::$loaded = new GetTextMulti(self::$lang);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 针对复数形式的翻译函数
|
||||
*
|
||||
* @param string $single 单数形式的翻译
|
||||
* @param string $plural 复数形式的翻译
|
||||
* @param integer $number 数字
|
||||
* @return string
|
||||
*/
|
||||
public static function ngettext(string $single, string $plural, int $number): string
|
||||
{
|
||||
self::init();
|
||||
return self::$loaded ? self::$loaded->ngettext($single, $plural, $number) : ($number > 1 ? $plural : $single);
|
||||
}
|
||||
|
||||
/**
|
||||
* 词义化时间
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int $from 起始时间
|
||||
* @param int $now 终止时间
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function dateWord(int $from, int $now): string
|
||||
{
|
||||
$between = $now - $from;
|
||||
|
||||
/** 如果是一天 */
|
||||
if ($between >= 0 && $between < 86400 && date('d', $from) == date('d', $now)) {
|
||||
/** 如果是一小时 */
|
||||
if ($between < 3600) {
|
||||
/** 如果是一分钟 */
|
||||
if ($between < 60) {
|
||||
if (0 == $between) {
|
||||
return _t('刚刚');
|
||||
} else {
|
||||
return str_replace('%d', $between, _n('一秒前', '%d秒前', $between));
|
||||
}
|
||||
}
|
||||
|
||||
$min = floor($between / 60);
|
||||
return str_replace('%d', $min, _n('一分钟前', '%d分钟前', $min));
|
||||
}
|
||||
|
||||
$hour = floor($between / 3600);
|
||||
return str_replace('%d', $hour, _n('一小时前', '%d小时前', $hour));
|
||||
}
|
||||
|
||||
/** 如果是昨天 */
|
||||
if (
|
||||
$between > 0
|
||||
&& $between < 172800
|
||||
&& (date('z', $from) + 1 == date('z', $now) // 在同一年的情况
|
||||
|| date('z', $from) + 1 == date('L') + 365 + date('z', $now))
|
||||
) { // 跨年的情况
|
||||
return _t('昨天 %s', date('H:i', $from));
|
||||
}
|
||||
|
||||
/** 如果是一个星期 */
|
||||
if ($between > 0 && $between < 604800) {
|
||||
$day = floor($between / 86400);
|
||||
return str_replace('%d', $day, _n('一天前', '%d天前', $day));
|
||||
}
|
||||
|
||||
/** 如果是 */
|
||||
if (date('Y', $from) == date('Y', $now)) {
|
||||
return date(_t('n月j日'), $from);
|
||||
}
|
||||
|
||||
return date(_t('Y年m月d日'), $from);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加语言项
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $lang 语言名称
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addLang(string $lang)
|
||||
{
|
||||
self::$loaded->addFile($lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语言项
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public static function getLang(): ?string
|
||||
{
|
||||
return self::$lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言项
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $lang 配置信息
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setLang(string $lang)
|
||||
{
|
||||
self::$lang = $lang;
|
||||
}
|
||||
}
|
||||
407
var/Typecho/I18n/GetText.php
Executable file
407
var/Typecho/I18n/GetText.php
Executable file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\I18n;
|
||||
|
||||
/*
|
||||
Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
|
||||
Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is part of PHP-gettext
|
||||
*
|
||||
* @author Danilo Segan <danilo@kvota.net>, Nico Kaiser <nico@siriux.net>
|
||||
* @category typecho
|
||||
* @package I18n
|
||||
*/
|
||||
class GetText
|
||||
{
|
||||
//public:
|
||||
public int $error = 0; // public variable that holds error code (0 if no error)
|
||||
|
||||
//private:
|
||||
private int $BYTE_ORDER = 0; // 0: low endian, 1: big endian
|
||||
|
||||
private $STREAM = null;
|
||||
|
||||
private bool $short_circuit = false;
|
||||
|
||||
private bool $enable_cache = false;
|
||||
|
||||
private ?int $originals = null; // offset of original table
|
||||
|
||||
private ?int $translations = null; // offset of translation table
|
||||
|
||||
private ?string $pluralHeader = null; // cache header field for plural forms
|
||||
|
||||
private int $total = 0; // total string count
|
||||
|
||||
private ?array $table_originals = null; // table for original strings (offsets)
|
||||
|
||||
private ?array $table_translations = null; // table for translated strings (offsets)
|
||||
|
||||
private ?array $cache_translations = null; // original -> translation mapping
|
||||
|
||||
|
||||
/* Methods */
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $file file name
|
||||
* @param boolean $enable_cache Enable or disable caching of strings (default on)
|
||||
*/
|
||||
public function __construct(string $file, bool $enable_cache = true)
|
||||
{
|
||||
// If there isn't a StreamReader, turn on short circuit mode.
|
||||
if (!file_exists($file)) {
|
||||
$this->short_circuit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Caching can be turned off
|
||||
$this->enable_cache = $enable_cache;
|
||||
$this->STREAM = @fopen($file, 'rb');
|
||||
|
||||
$unpacked = unpack('c', $this->read(4));
|
||||
$magic = array_shift($unpacked);
|
||||
|
||||
if (-34 == $magic) {
|
||||
$this->BYTE_ORDER = 0;
|
||||
} elseif (-107 == $magic) {
|
||||
$this->BYTE_ORDER = 1;
|
||||
} else {
|
||||
$this->error = 1; // not MO file
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Do we care about revision? We should.
|
||||
$revision = $this->readInt();
|
||||
|
||||
$this->total = $this->readInt();
|
||||
$this->originals = $this->readInt();
|
||||
$this->translations = $this->readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string
|
||||
*
|
||||
* @access public
|
||||
* @param string $string to be translated
|
||||
* @param integer|null $num found string number
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
public function translate($string, ?int &$num): string
|
||||
{
|
||||
if ($this->short_circuit) {
|
||||
return $string;
|
||||
}
|
||||
$this->loadTables();
|
||||
|
||||
if ($this->enable_cache) {
|
||||
// Caching enabled, get translated string from cache
|
||||
if (array_key_exists($string, $this->cache_translations)) {
|
||||
return $this->cache_translations[$string];
|
||||
} else {
|
||||
return $string;
|
||||
}
|
||||
} else {
|
||||
// Caching not enabled, try to find string
|
||||
$num = $this->findString($string);
|
||||
if ($num == -1) {
|
||||
return $string;
|
||||
} else {
|
||||
return $this->getTranslationString($num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext
|
||||
*
|
||||
* @access public
|
||||
* @param string single
|
||||
* @param string plural
|
||||
* @param string number
|
||||
* @param integer|null $num found string number
|
||||
* @return string plural form
|
||||
*/
|
||||
public function ngettext($single, $plural, $number, ?int &$num): string
|
||||
{
|
||||
$number = intval($number);
|
||||
|
||||
if ($this->short_circuit) {
|
||||
if ($number != 1) {
|
||||
return $plural;
|
||||
} else {
|
||||
return $single;
|
||||
}
|
||||
}
|
||||
|
||||
// find out the appropriate form
|
||||
$select = $this->selectString($number);
|
||||
|
||||
// this should contains all strings separated by NULLs
|
||||
$key = $single . chr(0) . $plural;
|
||||
|
||||
|
||||
if ($this->enable_cache) {
|
||||
if (!array_key_exists($key, $this->cache_translations)) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->cache_translations[$key];
|
||||
$list = explode(chr(0), $result);
|
||||
return $list[$select] ?? '';
|
||||
}
|
||||
} else {
|
||||
$num = $this->findString($key);
|
||||
if ($num == -1) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->getTranslationString($num);
|
||||
$list = explode(chr(0), $result);
|
||||
return $list[$select] ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭文件句柄
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
fclose($this->STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* read
|
||||
*
|
||||
* @param mixed $count
|
||||
* @access private
|
||||
* @return false|string
|
||||
*/
|
||||
private function read($count)
|
||||
{
|
||||
$count = abs($count);
|
||||
|
||||
if ($count > 0) {
|
||||
return fread($this->STREAM, $count);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 32bit Integer from the Stream
|
||||
*
|
||||
* @access private
|
||||
* @return Integer from the Stream
|
||||
*/
|
||||
private function readInt(): int
|
||||
{
|
||||
$end = unpack($this->BYTE_ORDER == 0 ? 'V' : 'N', $this->read(4));
|
||||
return array_shift($end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the translation tables from the MO file into the cache
|
||||
* If caching is enabled, also loads all strings into a cache
|
||||
* to speed up translation lookups
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function loadTables()
|
||||
{
|
||||
if (
|
||||
is_array($this->cache_translations) &&
|
||||
is_array($this->table_originals) &&
|
||||
is_array($this->table_translations)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* get original and translations tables */
|
||||
fseek($this->STREAM, $this->originals);
|
||||
$this->table_originals = $this->readIntArray($this->total * 2);
|
||||
fseek($this->STREAM, $this->translations);
|
||||
$this->table_translations = $this->readIntArray($this->total * 2);
|
||||
|
||||
if ($this->enable_cache) {
|
||||
$this->cache_translations = ['' => null];
|
||||
/* read all strings in the cache */
|
||||
for ($i = 0; $i < $this->total; $i++) {
|
||||
if ($this->table_originals[$i * 2 + 1] > 0) {
|
||||
fseek($this->STREAM, $this->table_originals[$i * 2 + 2]);
|
||||
$original = fread($this->STREAM, $this->table_originals[$i * 2 + 1]);
|
||||
fseek($this->STREAM, $this->table_translations[$i * 2 + 2]);
|
||||
$translation = fread($this->STREAM, $this->table_translations[$i * 2 + 1]);
|
||||
$this->cache_translations[$original] = $translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an array of Integers from the Stream
|
||||
*
|
||||
* @param int $count How many elements should be read
|
||||
* @return array of Integers
|
||||
*/
|
||||
private function readIntArray(int $count): array
|
||||
{
|
||||
return unpack(($this->BYTE_ORDER == 0 ? 'V' : 'N') . $count, $this->read(4 * $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search for string
|
||||
*
|
||||
* @access private
|
||||
* @param string $string
|
||||
* @param int $start (internally used in recursive function)
|
||||
* @param int $end (internally used in recursive function)
|
||||
* @return int string number (offset in originals table)
|
||||
*/
|
||||
private function findString(string $string, int $start = -1, int $end = -1): int
|
||||
{
|
||||
if (($start == -1) or ($end == -1)) {
|
||||
// findString is called with only one parameter, set start end end
|
||||
$start = 0;
|
||||
$end = $this->total;
|
||||
}
|
||||
if (abs($start - $end) <= 1) {
|
||||
// We're done, now we either found the string, or it doesn't exist
|
||||
$txt = $this->getOriginalString($start);
|
||||
if ($string == $txt) {
|
||||
return $start;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} elseif ($start > $end) {
|
||||
// start > end -> turn around and start over
|
||||
return $this->findString($string, $end, $start);
|
||||
} else {
|
||||
// Divide table in two parts
|
||||
$half = (int)(($start + $end) / 2);
|
||||
$cmp = strcmp($string, $this->getOriginalString($half));
|
||||
if ($cmp == 0) {
|
||||
// string is exactly in the middle => return it
|
||||
return $half;
|
||||
} elseif ($cmp < 0) {
|
||||
// The string is in the upper half
|
||||
return $this->findString($string, $start, $half);
|
||||
} else { // The string is in the lower half
|
||||
return $this->findString($string, $half, $end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string from the "originals" table
|
||||
*
|
||||
* @access private
|
||||
* @param int $num Offset number of original string
|
||||
* @return string Requested string if found, otherwise ''
|
||||
*/
|
||||
private function getOriginalString(int $num): string
|
||||
{
|
||||
$length = $this->table_originals[$num * 2 + 1];
|
||||
$offset = $this->table_originals[$num * 2 + 2];
|
||||
if (!$length) {
|
||||
return '';
|
||||
}
|
||||
fseek($this->STREAM, $offset);
|
||||
$data = fread($this->STREAM, $length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string from the "translations" table
|
||||
*
|
||||
* @access private
|
||||
* @param int $num Offset number of original string
|
||||
* @return string Requested string if found, otherwise ''
|
||||
*/
|
||||
private function getTranslationString(int $num): string
|
||||
{
|
||||
$length = $this->table_translations[$num * 2 + 1];
|
||||
$offset = $this->table_translations[$num * 2 + 2];
|
||||
if (!$length) {
|
||||
return '';
|
||||
}
|
||||
fseek($this->STREAM, $offset);
|
||||
$data = fread($this->STREAM, $length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects which plural form to take
|
||||
*
|
||||
* @param int $n count
|
||||
* @return int array index of the right plural form
|
||||
*/
|
||||
private function selectString(int $n): int
|
||||
{
|
||||
$string = $this->getPluralForms();
|
||||
$string = str_replace('nplurals', "\$total", $string);
|
||||
$string = str_replace("n", $n, $string);
|
||||
$string = str_replace('plural', "\$plural", $string);
|
||||
|
||||
$total = 0;
|
||||
$plural = 0;
|
||||
|
||||
eval("$string");
|
||||
if ($plural >= $total) {
|
||||
$plural = $total - 1;
|
||||
}
|
||||
return $plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get possible plural forms from MO header
|
||||
*
|
||||
* @access private
|
||||
* @return string plural form header
|
||||
*/
|
||||
private function getPluralForms(): string
|
||||
{
|
||||
// lets assume message number 0 is header
|
||||
// this is true, right?
|
||||
$this->loadTables();
|
||||
|
||||
// cache header field for plural forms
|
||||
if (!is_string($this->pluralHeader)) {
|
||||
if ($this->enable_cache) {
|
||||
$header = $this->cache_translations[""];
|
||||
} else {
|
||||
$header = $this->getTranslationString(0);
|
||||
}
|
||||
|
||||
if (!is_null($header) && preg_match("/plural\-forms: ([^\n]*)\n/i", $header, $regs)) {
|
||||
$expr = $regs[1];
|
||||
} else {
|
||||
$expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
|
||||
}
|
||||
$this->pluralHeader = $expr;
|
||||
}
|
||||
return $this->pluralHeader;
|
||||
}
|
||||
}
|
||||
102
var/Typecho/I18n/GetTextMulti.php
Executable file
102
var/Typecho/I18n/GetTextMulti.php
Executable file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\I18n;
|
||||
|
||||
/**
|
||||
* 用于解决一个多个mo文件带来的读写问题
|
||||
* 我们重写了一个文件读取类
|
||||
*
|
||||
* @author qining
|
||||
* @category typecho
|
||||
* @package I18n
|
||||
*/
|
||||
class GetTextMulti
|
||||
{
|
||||
/**
|
||||
* 所有的文件读写句柄
|
||||
*
|
||||
* @access private
|
||||
* @var GetText[]
|
||||
*/
|
||||
private array $handlers = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @access public
|
||||
* @param string $fileName 语言文件名
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $fileName)
|
||||
{
|
||||
$this->addFile($fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一个语言文件
|
||||
*
|
||||
* @access public
|
||||
* @param string $fileName 语言文件名
|
||||
* @return void
|
||||
*/
|
||||
public function addFile(string $fileName)
|
||||
{
|
||||
$this->handlers[] = new GetText($fileName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string
|
||||
*
|
||||
* @access public
|
||||
* @param string $string string to be translated
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
public function translate(string $string): string
|
||||
{
|
||||
foreach ($this->handlers as $handle) {
|
||||
$string = $handle->translate($string, $count);
|
||||
if (- 1 != $count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext
|
||||
*
|
||||
* @access public
|
||||
* @param string $single single
|
||||
* @param string $plural plural
|
||||
* @param int $number number
|
||||
* @return string translated plural form
|
||||
*/
|
||||
public function ngettext(string $single, string $plural, int $number): string
|
||||
{
|
||||
$count = - 1;
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
$string = $handler->ngettext($single, $plural, $number, $count);
|
||||
if (- 1 != $count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭所有句柄
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
/** 显示的释放内存 */
|
||||
unset($handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
491
var/Typecho/Plugin.php
Executable file
491
var/Typecho/Plugin.php
Executable file
@@ -0,0 +1,491 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
use Typecho\Plugin\Exception as PluginException;
|
||||
|
||||
/**
|
||||
* 插件处理类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Plugin
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Plugin
|
||||
{
|
||||
/**
|
||||
* 所有启用的插件
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static array $plugin = [];
|
||||
|
||||
/**
|
||||
* 实例化的插件对象
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static array $instances;
|
||||
|
||||
/**
|
||||
* 临时存储变量
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static array $tmp = [];
|
||||
|
||||
/**
|
||||
* 唯一句柄
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $handle;
|
||||
|
||||
/**
|
||||
* 组件
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $component;
|
||||
|
||||
/**
|
||||
* 是否触发插件的信号
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private bool $signal = false;
|
||||
|
||||
/**
|
||||
* 插件初始化
|
||||
*
|
||||
* @param string $handle 插件
|
||||
*/
|
||||
public function __construct(string $handle)
|
||||
{
|
||||
if (defined('__TYPECHO_CLASS_ALIASES__')) {
|
||||
$alias = array_search('\\' . ltrim($handle, '\\'), __TYPECHO_CLASS_ALIASES__);
|
||||
$handle = $alias ?: $handle;
|
||||
}
|
||||
|
||||
$this->handle = Common::nativeClassName($handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件初始化
|
||||
*
|
||||
* @param array $plugins 插件列表
|
||||
*/
|
||||
public static function init(array $plugins)
|
||||
{
|
||||
$plugins['activated'] = array_key_exists('activated', $plugins) ? $plugins['activated'] : [];
|
||||
$plugins['handles'] = array_key_exists('handles', $plugins) ? $plugins['handles'] : [];
|
||||
|
||||
/** 初始化变量 */
|
||||
self::$plugin = $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实例化插件对象
|
||||
*
|
||||
* @param string $handle 插件
|
||||
* @return Plugin
|
||||
*/
|
||||
public static function factory(string $handle): Plugin
|
||||
{
|
||||
return self::$instances[$handle] ?? (self::$instances[$handle] = new self($handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用插件
|
||||
*
|
||||
* @param string $pluginName 插件名称
|
||||
*/
|
||||
public static function activate(string $pluginName)
|
||||
{
|
||||
self::$plugin['activated'][$pluginName] = self::$tmp;
|
||||
self::$tmp = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用插件
|
||||
*
|
||||
* @param string $pluginName 插件名称
|
||||
*/
|
||||
public static function deactivate(string $pluginName)
|
||||
{
|
||||
/** 去掉所有相关回调函数 */
|
||||
if (
|
||||
isset(self::$plugin['activated'][$pluginName]['handles'])
|
||||
&& is_array(self::$plugin['activated'][$pluginName]['handles'])
|
||||
) {
|
||||
foreach (self::$plugin['activated'][$pluginName]['handles'] as $handle => $handles) {
|
||||
self::$plugin['handles'][$handle] = self::pluginHandlesDiff(
|
||||
empty(self::$plugin['handles'][$handle]) ? [] : self::$plugin['handles'][$handle],
|
||||
empty($handles) ? [] : $handles
|
||||
);
|
||||
if (empty(self::$plugin['handles'][$handle])) {
|
||||
unset(self::$plugin['handles'][$handle]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 禁用当前插件 */
|
||||
unset(self::$plugin['activated'][$pluginName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件handle比对
|
||||
*
|
||||
* @param array $pluginHandles
|
||||
* @param array $otherPluginHandles
|
||||
* @return array
|
||||
*/
|
||||
private static function pluginHandlesDiff(array $pluginHandles, array $otherPluginHandles): array
|
||||
{
|
||||
foreach ($otherPluginHandles as $handle) {
|
||||
while (false !== ($index = array_search($handle, $pluginHandles))) {
|
||||
unset($pluginHandles[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
return $pluginHandles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出当前插件设置
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function export(): array
|
||||
{
|
||||
return self::$plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件文件的头信息
|
||||
*
|
||||
* @param string $pluginFile 插件文件路径
|
||||
* @return array
|
||||
*/
|
||||
public static function parseInfo(string $pluginFile): array
|
||||
{
|
||||
$tokens = token_get_all(file_get_contents($pluginFile));
|
||||
$isDoc = false;
|
||||
$isFunction = false;
|
||||
$isClass = false;
|
||||
$isInClass = false;
|
||||
$isInFunction = false;
|
||||
$isDefined = false;
|
||||
$current = null;
|
||||
|
||||
/** 初始信息 */
|
||||
$info = [
|
||||
'description' => '',
|
||||
'title' => '',
|
||||
'author' => '',
|
||||
'homepage' => '',
|
||||
'version' => '',
|
||||
'since' => '',
|
||||
'activate' => false,
|
||||
'deactivate' => false,
|
||||
'config' => false,
|
||||
'personalConfig' => false
|
||||
];
|
||||
|
||||
$map = [
|
||||
'package' => 'title',
|
||||
'author' => 'author',
|
||||
'link' => 'homepage',
|
||||
'since' => 'since',
|
||||
'version' => 'version'
|
||||
];
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
/** 获取doc comment */
|
||||
if (!$isDoc && is_array($token) && T_DOC_COMMENT == $token[0]) {
|
||||
|
||||
/** 分行读取 */
|
||||
$described = false;
|
||||
$lines = preg_split("([\r\n])", $token[1]);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (!empty($line) && '*' == $line[0]) {
|
||||
$line = trim(substr($line, 1));
|
||||
if (!$described && !empty($line) && '@' == $line[0]) {
|
||||
$described = true;
|
||||
}
|
||||
|
||||
if (!$described && !empty($line)) {
|
||||
$info['description'] .= $line . "\n";
|
||||
} elseif ($described && !empty($line) && '@' == $line[0]) {
|
||||
$info['description'] = trim($info['description']);
|
||||
$line = trim(substr($line, 1));
|
||||
$args = explode(' ', $line);
|
||||
$key = array_shift($args);
|
||||
|
||||
if (isset($map[$key])) {
|
||||
$info[$map[$key]] = trim(implode(' ', $args));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$isDoc = true;
|
||||
}
|
||||
|
||||
if (is_array($token)) {
|
||||
switch ($token[0]) {
|
||||
case T_FUNCTION:
|
||||
$isFunction = true;
|
||||
break;
|
||||
case T_IMPLEMENTS:
|
||||
$isClass = true;
|
||||
break;
|
||||
case T_WHITESPACE:
|
||||
case T_COMMENT:
|
||||
case T_DOC_COMMENT:
|
||||
break;
|
||||
case T_STRING:
|
||||
$string = strtolower($token[1]);
|
||||
switch ($string) {
|
||||
case 'typecho_plugin_interface':
|
||||
case 'plugininterface':
|
||||
$isInClass = $isClass;
|
||||
break;
|
||||
case 'activate':
|
||||
case 'deactivate':
|
||||
case 'config':
|
||||
case 'personalconfig':
|
||||
if ($isFunction) {
|
||||
$current = ('personalconfig' == $string ? 'personalConfig' : $string);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!empty($current) && $isInFunction && $isInClass) {
|
||||
$info[$current] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!empty($current) && $isInFunction && $isInClass) {
|
||||
$info[$current] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$token = strtolower($token);
|
||||
switch ($token) {
|
||||
case '{':
|
||||
if ($isDefined) {
|
||||
$isInFunction = true;
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
if ($isFunction && !$isDefined) {
|
||||
$isDefined = true;
|
||||
}
|
||||
break;
|
||||
case '}':
|
||||
case ';':
|
||||
$isDefined = false;
|
||||
$isFunction = false;
|
||||
$isInFunction = false;
|
||||
$current = null;
|
||||
break;
|
||||
default:
|
||||
if (!empty($current) && $isInFunction && $isInClass) {
|
||||
$info[$current] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件路径和类名
|
||||
* 返回值为一个数组
|
||||
* 第一项为插件路径,第二项为类名
|
||||
*
|
||||
* @param string $pluginName 插件名
|
||||
* @param string $path 插件目录
|
||||
* @return array
|
||||
* @throws PluginException
|
||||
*/
|
||||
public static function portal(string $pluginName, string $path): array
|
||||
{
|
||||
switch (true) {
|
||||
case file_exists($pluginFileName = $path . '/' . $pluginName . '/Plugin.php'):
|
||||
$className = "\\" . PLUGIN_NAMESPACE . "\\{$pluginName}\\Plugin";
|
||||
break;
|
||||
case file_exists($pluginFileName = $path . '/' . $pluginName . '.php'):
|
||||
$className = "\\" . PLUGIN_NAMESPACE . "\\" . $pluginName;
|
||||
break;
|
||||
default:
|
||||
throw new PluginException('Missing Plugin ' . $pluginName, 404);
|
||||
}
|
||||
|
||||
return [$pluginFileName, $className];
|
||||
}
|
||||
|
||||
/**
|
||||
* 版本依赖性检测
|
||||
*
|
||||
* @param string|null $version 插件版本
|
||||
* @return boolean
|
||||
*/
|
||||
public static function checkDependence(?string $version): bool
|
||||
{
|
||||
//如果没有检测规则,直接掠过
|
||||
if (empty($version)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return version_compare(Common::VERSION, $version, '>=');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断插件是否存在
|
||||
*
|
||||
* @param string $pluginName 插件名称
|
||||
* @return bool
|
||||
*/
|
||||
public static function exists(string $pluginName): bool
|
||||
{
|
||||
return array_key_exists($pluginName, self::$plugin['activated']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件调用后的触发器
|
||||
*
|
||||
* @param boolean|null $signal 触发器
|
||||
* @return Plugin
|
||||
*/
|
||||
public function trigger(?bool &$signal): Plugin
|
||||
{
|
||||
$signal = false;
|
||||
$this->signal = &$signal;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过魔术函数设置当前组件位置
|
||||
*
|
||||
* @param string $component 当前组件
|
||||
* @return Plugin
|
||||
*/
|
||||
public function __get(string $component)
|
||||
{
|
||||
$this->component = $component;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置回调函数
|
||||
*
|
||||
* @param string $component 当前组件
|
||||
* @param callable $value 回调函数
|
||||
*/
|
||||
public function __set(string $component, callable $value)
|
||||
{
|
||||
$weight = 0;
|
||||
|
||||
if (strpos($component, '_') > 0) {
|
||||
$parts = explode('_', $component, 2);
|
||||
[$component, $weight] = $parts;
|
||||
$weight = intval($weight) - 10;
|
||||
}
|
||||
|
||||
$component = $this->handle . ':' . $component;
|
||||
|
||||
if (!isset(self::$plugin['handles'][$component])) {
|
||||
self::$plugin['handles'][$component] = [];
|
||||
}
|
||||
|
||||
if (!isset(self::$tmp['handles'][$component])) {
|
||||
self::$tmp['handles'][$component] = [];
|
||||
}
|
||||
|
||||
foreach (self::$plugin['handles'][$component] as $key => $val) {
|
||||
$key = floatval($key);
|
||||
|
||||
if ($weight > $key) {
|
||||
break;
|
||||
} elseif ($weight == $key) {
|
||||
$weight += 0.001;
|
||||
}
|
||||
}
|
||||
|
||||
self::$plugin['handles'][$component][strval($weight)] = $value;
|
||||
self::$tmp['handles'][$component][] = $value;
|
||||
|
||||
ksort(self::$plugin['handles'][$component], SORT_NUMERIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调处理函数
|
||||
*
|
||||
* @param string $component 当前组件
|
||||
* @param array $args 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function call(string $component, ...$args)
|
||||
{
|
||||
$componentKey = $this->handle . ':' . $component;
|
||||
|
||||
if (!isset(self::$plugin['handles'][$componentKey])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$return = null;
|
||||
$this->signal = true;
|
||||
|
||||
foreach (self::$plugin['handles'][$componentKey] as $callback) {
|
||||
$return = call_user_func_array($callback, $args);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤处理函数
|
||||
*
|
||||
* @param string $component 当前组件
|
||||
* @param mixed $value 值
|
||||
* @param array $args 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function filter(string $component, $value, ...$args)
|
||||
{
|
||||
$componentKey = $this->handle . ':' . $component;
|
||||
|
||||
if (!isset(self::$plugin['handles'][$componentKey])) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$result = $value;
|
||||
$this->signal = true;
|
||||
|
||||
foreach (self::$plugin['handles'][$componentKey] as $callback) {
|
||||
$currentArgs = array_merge([$result], $args, [$result]);
|
||||
$result = call_user_func_array($callback, $currentArgs);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated ^1.3.0
|
||||
* @param string $component
|
||||
* @param array $args
|
||||
* @return false|mixed|null
|
||||
*/
|
||||
public function __call(string $component, array $args)
|
||||
{
|
||||
return $this->call($component, ... $args);
|
||||
}
|
||||
}
|
||||
26
var/Typecho/Plugin/Exception.php
Executable file
26
var/Typecho/Plugin/Exception.php
Executable file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Plugin;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Typecho\Exception as TypechoException;
|
||||
|
||||
/**
|
||||
* Typecho Blog Platform
|
||||
*
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
* @version $Id: WidgetException.php 46 2008-03-10 13:59:36Z magike.net $
|
||||
*/
|
||||
|
||||
/**
|
||||
* 插件异常
|
||||
*
|
||||
* @package Plugin
|
||||
*/
|
||||
class Exception extends TypechoException
|
||||
{
|
||||
}
|
||||
46
var/Typecho/Plugin/PluginInterface.php
Executable file
46
var/Typecho/Plugin/PluginInterface.php
Executable file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Plugin;
|
||||
|
||||
use Typecho\Widget\Helper\Form;
|
||||
|
||||
/**
|
||||
* 插件接口
|
||||
*
|
||||
* @package Plugin
|
||||
* @abstract
|
||||
*/
|
||||
interface PluginInterface
|
||||
{
|
||||
/**
|
||||
* 启用插件方法,如果启用失败,直接抛出异常
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function activate();
|
||||
|
||||
/**
|
||||
* 禁用插件方法,如果禁用失败,直接抛出异常
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function deactivate();
|
||||
|
||||
/**
|
||||
* 获取插件配置面板
|
||||
*
|
||||
* @param Form $form 配置面板
|
||||
*/
|
||||
public static function config(Form $form);
|
||||
|
||||
/**
|
||||
* 个人用户的配置面板
|
||||
*
|
||||
* @param Form $form
|
||||
*/
|
||||
public static function personalConfig(Form $form);
|
||||
}
|
||||
687
var/Typecho/Request.php
Executable file
687
var/Typecho/Request.php
Executable file
@@ -0,0 +1,687 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
/**
|
||||
* 服务器请求处理类
|
||||
*
|
||||
* @package Request
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* 单例句柄
|
||||
*
|
||||
* @access private
|
||||
* @var Request
|
||||
*/
|
||||
private static Request $instance;
|
||||
|
||||
/**
|
||||
* 沙箱参数
|
||||
*
|
||||
* @access private
|
||||
* @var Config|null
|
||||
*/
|
||||
private ?Config $sandbox;
|
||||
|
||||
/**
|
||||
* 用户参数
|
||||
*
|
||||
* @access private
|
||||
* @var Config|null
|
||||
*/
|
||||
private ?Config $params;
|
||||
|
||||
/**
|
||||
* 路径信息
|
||||
*
|
||||
* @access private
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $pathInfo = null;
|
||||
|
||||
/**
|
||||
* requestUri
|
||||
*
|
||||
* @var string|null
|
||||
* @access private
|
||||
*/
|
||||
private ?string $requestUri = null;
|
||||
|
||||
/**
|
||||
* requestRoot
|
||||
*
|
||||
* @var string|null
|
||||
* @access private
|
||||
*/
|
||||
private ?string $requestRoot = null;
|
||||
|
||||
/**
|
||||
* 获取baseurl
|
||||
*
|
||||
* @var string|null
|
||||
* @access private
|
||||
*/
|
||||
private ?string $baseUrl = null;
|
||||
|
||||
/**
|
||||
* 客户端ip地址
|
||||
*
|
||||
* @access private
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $ip = null;
|
||||
|
||||
/**
|
||||
* 域名前缀
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $urlPrefix = null;
|
||||
|
||||
/**
|
||||
* 获取单例句柄
|
||||
*
|
||||
* @access public
|
||||
* @return Request
|
||||
*/
|
||||
public static function getInstance(): Request
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化变量
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function beginSandbox(Config $sandbox): Request
|
||||
{
|
||||
$this->sandbox = $sandbox;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function endSandbox(): Request
|
||||
{
|
||||
$this->sandbox = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config $params
|
||||
* @return $this
|
||||
*/
|
||||
public function proxy(Config $params): Request
|
||||
{
|
||||
$this->params = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function endProxy(): Request
|
||||
{
|
||||
if (isset($this->params)) {
|
||||
$this->params = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实际传递参数
|
||||
*
|
||||
* @param string $key 指定参数
|
||||
* @param mixed $default 默认参数 (default: NULL)
|
||||
* @param bool|null $exists detect exists
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key, $default = null, ?bool &$exists = true)
|
||||
{
|
||||
$value = null;
|
||||
|
||||
switch (true) {
|
||||
case isset($this->params) && isset($this->params[$key]):
|
||||
$value = $this->params[$key];
|
||||
break;
|
||||
case isset($this->sandbox):
|
||||
if (isset($this->sandbox[$key])) {
|
||||
$value = $this->sandbox[$key];
|
||||
}
|
||||
break;
|
||||
case $key === '@json':
|
||||
if ($this->isJson()) {
|
||||
$body = file_get_contents('php://input');
|
||||
|
||||
if (false !== $body) {
|
||||
$value = json_decode($body, true, 16);
|
||||
$default = $default ?? $value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case isset($_GET[$key]):
|
||||
$value = $_GET[$key];
|
||||
break;
|
||||
case isset($_POST[$key]):
|
||||
$value = $_POST[$key];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($value) && $value !== '') {
|
||||
$exists = true;
|
||||
if (is_array($default) == is_array($value)) {
|
||||
return $value;
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
} else {
|
||||
$exists = false;
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实际传递参数(magic)
|
||||
*
|
||||
* @deprecated ^1.3.0
|
||||
* @param string $key 指定参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数是否存在
|
||||
*
|
||||
* @deprecated ^1.3.0
|
||||
* @param string $key 指定参数
|
||||
* @return boolean
|
||||
*/
|
||||
public function __isset(string $key)
|
||||
{
|
||||
$this->get($key, null, $exists);
|
||||
return $exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个数组
|
||||
*
|
||||
* @param $key
|
||||
* @return array
|
||||
*/
|
||||
public function getArray($key): array
|
||||
{
|
||||
$result = $this->get($key, [], $exists);
|
||||
|
||||
if (!empty($result) || !$exists) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return [$this->get($key)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从参数列表指定的值中获取http传递参数
|
||||
*
|
||||
* @param mixed $params 指定的参数
|
||||
* @return array
|
||||
*/
|
||||
public function from($params): array
|
||||
{
|
||||
$result = [];
|
||||
$args = is_array($params) ? $params : func_get_args();
|
||||
|
||||
foreach ($args as $arg) {
|
||||
$result[$arg] = $this->get($arg);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* getRequestRoot
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestRoot(): string
|
||||
{
|
||||
if (null === $this->requestRoot) {
|
||||
$root = rtrim($this->getUrlPrefix() . $this->getBaseUrl(), '/') . '/';
|
||||
|
||||
$pos = strrpos($root, '.php/');
|
||||
if ($pos) {
|
||||
$root = dirname(substr($root, 0, $pos));
|
||||
}
|
||||
|
||||
$this->requestRoot = rtrim($root, '/');
|
||||
}
|
||||
|
||||
return $this->requestRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestUrl(): string
|
||||
{
|
||||
return $this->getUrlPrefix() . $this->getRequestUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前uri构造指定参数的uri
|
||||
*
|
||||
* @param mixed $parameter 指定的参数
|
||||
* @return string
|
||||
*/
|
||||
public function makeUriByRequest($parameter = null): string
|
||||
{
|
||||
/** 初始化地址 */
|
||||
$requestUri = $this->getRequestUrl();
|
||||
$parts = parse_url($requestUri);
|
||||
|
||||
/** 初始化参数 */
|
||||
if (is_string($parameter)) {
|
||||
parse_str($parameter, $args);
|
||||
} elseif (is_array($parameter)) {
|
||||
$args = $parameter;
|
||||
} else {
|
||||
return $requestUri;
|
||||
}
|
||||
|
||||
/** 构造query */
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $currentArgs);
|
||||
$args = array_merge($currentArgs, $args);
|
||||
}
|
||||
$parts['query'] = http_build_query($args);
|
||||
|
||||
/** 返回地址 */
|
||||
return Common::buildUrl($parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前pathinfo
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPathInfo(): ?string
|
||||
{
|
||||
/** 缓存信息 */
|
||||
if (null !== $this->pathInfo) {
|
||||
return $this->pathInfo;
|
||||
}
|
||||
|
||||
//参考Zend Framework对pathinfo的处理, 更好的兼容性
|
||||
$pathInfo = null;
|
||||
|
||||
//处理requestUri
|
||||
$requestUri = $this->getRequestUri();
|
||||
$finalBaseUrl = $this->getBaseUrl();
|
||||
|
||||
// Remove the query string from REQUEST_URI
|
||||
if ($pos = strpos($requestUri, '?')) {
|
||||
$requestUri = substr($requestUri, 0, $pos);
|
||||
}
|
||||
|
||||
if (
|
||||
(null !== $finalBaseUrl)
|
||||
&& (false === ($pathInfo = substr($requestUri, strlen($finalBaseUrl))))
|
||||
) {
|
||||
// If substr() returns false then PATH_INFO is set to an empty string
|
||||
$pathInfo = '/';
|
||||
} elseif (null === $finalBaseUrl) {
|
||||
$pathInfo = $requestUri;
|
||||
}
|
||||
|
||||
if (!empty($pathInfo)) {
|
||||
//针对iis的utf8编码做强制转换
|
||||
$pathInfo = defined('__TYPECHO_PATHINFO_ENCODING__') ?
|
||||
mb_convert_encoding($pathInfo, 'UTF-8', __TYPECHO_PATHINFO_ENCODING__) : $pathInfo;
|
||||
} else {
|
||||
$pathInfo = '/';
|
||||
}
|
||||
|
||||
// fix issue 456
|
||||
return ($this->pathInfo = '/' . ltrim(urldecode($pathInfo), '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的内容类型
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getContentType(): ?string
|
||||
{
|
||||
return $this->getHeader('Content-Type');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取环境变量
|
||||
*
|
||||
* @param string $name 获取环境变量名
|
||||
* @param string|null $default
|
||||
* @return string|null
|
||||
*/
|
||||
public function getServer(string $name, ?string $default = null): ?string
|
||||
{
|
||||
return $_SERVER[$name] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ip地址
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIp(): string
|
||||
{
|
||||
if (null === $this->ip) {
|
||||
$header = defined('__TYPECHO_IP_SOURCE__') ? __TYPECHO_IP_SOURCE__ : 'X-Forwarded-For';
|
||||
$ip = $this->getHeader($header, $this->getHeader('Client-Ip', $this->getServer('REMOTE_ADDR')));
|
||||
|
||||
if (!empty($ip)) {
|
||||
[$ip] = array_map('trim', explode(',', $ip));
|
||||
$ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6);
|
||||
}
|
||||
|
||||
if (!empty($ip)) {
|
||||
$this->ip = $ip;
|
||||
} else {
|
||||
$this->ip = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* get header value
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|null $default
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHeader(string $key, ?string $default = null): ?string
|
||||
{
|
||||
$key = strtoupper(str_replace('-', '_', $key));
|
||||
|
||||
// Content-Type 和 Content-Length 这两个 header 还需要从不带 HTTP_ 的 key 尝试获取
|
||||
if (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) {
|
||||
$default = $this->getServer($key, $default);
|
||||
}
|
||||
|
||||
return $this->getServer('HTTP_' . $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAgent(): ?string
|
||||
{
|
||||
return $this->getHeader('User-Agent');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getReferer(): ?string
|
||||
{
|
||||
return $this->getHeader('Referer');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为https
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSecure(): bool
|
||||
{
|
||||
return (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && !strcasecmp('https', $_SERVER['HTTP_X_FORWARDED_PROTO']))
|
||||
|| (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && !strcasecmp('quic', $_SERVER['HTTP_X_FORWARDED_PROTO']))
|
||||
|| (!empty($_SERVER['HTTP_X_FORWARDED_PORT']) && 443 == $_SERVER['HTTP_X_FORWARDED_PORT'])
|
||||
|| (!empty($_SERVER['HTTPS']) && 'off' != strtolower($_SERVER['HTTPS']))
|
||||
|| (!empty($_SERVER['SERVER_PORT']) && 443 == $_SERVER['SERVER_PORT'])
|
||||
|| (defined('__TYPECHO_SECURE__') && __TYPECHO_SECURE__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isCli(): bool
|
||||
{
|
||||
return php_sapi_name() == 'cli';
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为get方法
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isGet(): bool
|
||||
{
|
||||
return 'GET' == $this->getServer('REQUEST_METHOD');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为post方法
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isPost(): bool
|
||||
{
|
||||
return 'POST' == $this->getServer('REQUEST_METHOD');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为put方法
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isPut(): bool
|
||||
{
|
||||
return 'PUT' == $this->getServer('REQUEST_METHOD');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为ajax
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAjax(): bool
|
||||
{
|
||||
return 'XMLHttpRequest' == $this->getHeader('X-Requested-With');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为Json请求
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isJson(): bool
|
||||
{
|
||||
return !!preg_match(
|
||||
"/^\s*application\/json(;|$)/i",
|
||||
$this->getContentType() ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断输入是否满足要求
|
||||
*
|
||||
* @param mixed $query 条件
|
||||
* @return boolean
|
||||
*/
|
||||
public function is($query): bool
|
||||
{
|
||||
$validated = false;
|
||||
|
||||
/** 解析串 */
|
||||
if (is_string($query)) {
|
||||
parse_str($query, $params);
|
||||
} elseif (is_array($query)) {
|
||||
$params = $query;
|
||||
}
|
||||
|
||||
/** 验证串 */
|
||||
if (!empty($params)) {
|
||||
$validated = true;
|
||||
foreach ($params as $key => $val) {
|
||||
$param = $this->get($key, null, $exists);
|
||||
$validated = empty($val) ? $exists : ($val == $param);
|
||||
|
||||
if (!$validated) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $validated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求资源地址
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRequestUri(): ?string
|
||||
{
|
||||
if (!empty($this->requestUri)) {
|
||||
return $this->requestUri;
|
||||
}
|
||||
|
||||
//处理requestUri
|
||||
$requestUri = '/';
|
||||
|
||||
if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // check this first so IIS will catch
|
||||
$requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
|
||||
} elseif (
|
||||
// IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem)
|
||||
isset($_SERVER['IIS_WasUrlRewritten'])
|
||||
&& $_SERVER['IIS_WasUrlRewritten'] == '1'
|
||||
&& isset($_SERVER['UNENCODED_URL'])
|
||||
&& $_SERVER['UNENCODED_URL'] != ''
|
||||
) {
|
||||
$requestUri = $_SERVER['UNENCODED_URL'];
|
||||
} elseif (isset($_SERVER['REQUEST_URI'])) {
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
$parts = @parse_url($requestUri);
|
||||
|
||||
if (isset($_SERVER['HTTP_HOST']) && strstr($requestUri, $_SERVER['HTTP_HOST'])) {
|
||||
if (false !== $parts) {
|
||||
$requestUri = (empty($parts['path']) ? '' : $parts['path'])
|
||||
. ((empty($parts['query'])) ? '' : '?' . $parts['query']);
|
||||
}
|
||||
} elseif (!empty($_SERVER['QUERY_STRING']) && empty($parts['query'])) {
|
||||
// fix query missing
|
||||
$requestUri .= '?' . $_SERVER['QUERY_STRING'];
|
||||
}
|
||||
} elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0, PHP as CGI
|
||||
$requestUri = $_SERVER['ORIG_PATH_INFO'];
|
||||
if (!empty($_SERVER['QUERY_STRING'])) {
|
||||
$requestUri .= '?' . $_SERVER['QUERY_STRING'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->requestUri = $requestUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取url前缀
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUrlPrefix(): ?string
|
||||
{
|
||||
if (empty($this->urlPrefix)) {
|
||||
if (defined('__TYPECHO_URL_PREFIX__')) {
|
||||
$this->urlPrefix = __TYPECHO_URL_PREFIX__;
|
||||
} elseif (php_sapi_name() != 'cli') {
|
||||
$this->urlPrefix = ($this->isSecure() ? 'https' : 'http') . '://'
|
||||
. ($_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME']);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->urlPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* getBaseUrl
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getBaseUrl(): ?string
|
||||
{
|
||||
if (null !== $this->baseUrl) {
|
||||
return $this->baseUrl;
|
||||
}
|
||||
|
||||
//处理baseUrl
|
||||
$filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : '';
|
||||
|
||||
if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) {
|
||||
$baseUrl = $_SERVER['SCRIPT_NAME'];
|
||||
} elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename) {
|
||||
$baseUrl = $_SERVER['PHP_SELF'];
|
||||
} elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) {
|
||||
$baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility
|
||||
} else {
|
||||
// Backtrack up the script_filename to find the portion matching
|
||||
// php_self
|
||||
$path = $_SERVER['PHP_SELF'] ?? '';
|
||||
$file = $_SERVER['SCRIPT_FILENAME'] ?? '';
|
||||
$segs = explode('/', trim($file, '/'));
|
||||
$segs = array_reverse($segs);
|
||||
$index = 0;
|
||||
$last = count($segs);
|
||||
$baseUrl = '';
|
||||
do {
|
||||
$seg = $segs[$index];
|
||||
$baseUrl = '/' . $seg . $baseUrl;
|
||||
++$index;
|
||||
} while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos));
|
||||
}
|
||||
|
||||
// Does the baseUrl have anything in common with the request_uri?
|
||||
$finalBaseUrl = null;
|
||||
$requestUri = $this->getRequestUri();
|
||||
|
||||
if (0 === strpos($requestUri, $baseUrl)) {
|
||||
// full $baseUrl matches
|
||||
$finalBaseUrl = $baseUrl;
|
||||
} elseif (0 === strpos($requestUri, dirname($baseUrl))) {
|
||||
// directory portion of $baseUrl matches
|
||||
$finalBaseUrl = rtrim(dirname($baseUrl), '/');
|
||||
} elseif (!strpos($requestUri, basename($baseUrl))) {
|
||||
// no match whatsoever; set it blank
|
||||
$finalBaseUrl = '';
|
||||
} elseif (
|
||||
(strlen($requestUri) >= strlen($baseUrl))
|
||||
&& ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))
|
||||
) {
|
||||
// If using mod_rewrite or ISAPI_Rewrite strip the script filename
|
||||
// out of baseUrl. $pos !== 0 makes sure it is not matching a value
|
||||
// from PATH_INFO or QUERY_STRING
|
||||
$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
|
||||
}
|
||||
|
||||
return ($this->baseUrl = (null === $finalBaseUrl) ? rtrim($baseUrl, '/') : $finalBaseUrl);
|
||||
}
|
||||
}
|
||||
354
var/Typecho/Response.php
Executable file
354
var/Typecho/Response.php
Executable file
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
use Typecho\Widget\Terminal;
|
||||
|
||||
/**
|
||||
* Typecho公用方法
|
||||
*
|
||||
* @category typecho
|
||||
* @package Response
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* http code
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private const HTTP_CODE = [
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
307 => 'Temporary Redirect',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Request Entity Too Large',
|
||||
414 => 'Request-URI Too Long',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Requested Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported'
|
||||
];
|
||||
|
||||
//默认的字符编码
|
||||
/**
|
||||
* 单例句柄
|
||||
*
|
||||
* @access private
|
||||
* @var Response
|
||||
*/
|
||||
private static Response $instance;
|
||||
|
||||
/**
|
||||
* 字符编码
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $contentType = 'text/html';
|
||||
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
private array $responders = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $cookies = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $headers = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private int $status = 200;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $enableAutoSendHeaders = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $sandbox = false;
|
||||
|
||||
/**
|
||||
* init responder
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例句柄
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public static function getInstance(): Response
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function beginSandbox(): Response
|
||||
{
|
||||
$this->sandbox = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function endSandbox(): Response
|
||||
{
|
||||
$this->sandbox = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $enable
|
||||
*/
|
||||
public function enableAutoSendHeaders(bool $enable = true)
|
||||
{
|
||||
$this->enableAutoSendHeaders = $enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* clean all
|
||||
*/
|
||||
public function clean()
|
||||
{
|
||||
$this->headers = [];
|
||||
$this->cookies = [];
|
||||
$this->status = 200;
|
||||
$this->responders = [];
|
||||
$this->setContentType('text/html');
|
||||
}
|
||||
|
||||
/**
|
||||
* send all headers
|
||||
*/
|
||||
public function sendHeaders()
|
||||
{
|
||||
if ($this->sandbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sentHeaders = [];
|
||||
foreach (headers_list() as $header) {
|
||||
[$key] = explode(':', $header, 2);
|
||||
$sentHeaders[] = strtolower(trim($key));
|
||||
}
|
||||
|
||||
header('HTTP/1.1 ' . $this->status . ' ' . self::HTTP_CODE[$this->status], true, $this->status);
|
||||
|
||||
// set header
|
||||
foreach ($this->headers as $name => $value) {
|
||||
if (!in_array(strtolower($name), $sentHeaders)) {
|
||||
header($name . ': ' . $value);
|
||||
}
|
||||
}
|
||||
|
||||
// set cookie
|
||||
foreach ($this->cookies as $cookie) {
|
||||
[$key, $value, $timeout, $path, $domain, $secure, $httponly] = $cookie;
|
||||
|
||||
if ($timeout > 0) {
|
||||
$now = time();
|
||||
$timeout += $timeout > $now - 86400 ? 0 : $now;
|
||||
} elseif ($timeout < 0) {
|
||||
$timeout = 1;
|
||||
}
|
||||
|
||||
setrawcookie($key, rawurlencode($value), $timeout, $path, $domain, $secure, $httponly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* respond data
|
||||
* @throws Terminal
|
||||
*/
|
||||
public function respond()
|
||||
{
|
||||
if ($this->sandbox) {
|
||||
throw new Terminal('sandbox mode');
|
||||
}
|
||||
|
||||
if ($this->enableAutoSendHeaders) {
|
||||
$this->sendHeaders();
|
||||
}
|
||||
|
||||
foreach ($this->responders as $responder) {
|
||||
call_user_func($responder, $this);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置HTTP状态
|
||||
*
|
||||
* @access public
|
||||
* @param integer $code http代码
|
||||
* @return $this
|
||||
*/
|
||||
public function setStatus(int $code): Response
|
||||
{
|
||||
if (!$this->sandbox) {
|
||||
$this->status = $code;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置http头
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param string $value 对应值
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeader(string $name, string $value): Response
|
||||
{
|
||||
if (!$this->sandbox) {
|
||||
$name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
|
||||
$this->headers[$name] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定的COOKIE值
|
||||
*
|
||||
* @param string $key 指定的参数
|
||||
* @param mixed $value 设置的值
|
||||
* @param integer $timeout 过期时间,默认为0,表示随会话时间结束
|
||||
* @param string $path 路径信息
|
||||
* @param string|null $domain 域名信息
|
||||
* @param bool $secure 是否仅可通过安全的 HTTPS 连接传给客户端
|
||||
* @param bool $httponly 是否仅可通过 HTTP 协议访问
|
||||
* @return $this
|
||||
*/
|
||||
public function setCookie(
|
||||
string $key,
|
||||
$value,
|
||||
int $timeout = 0,
|
||||
string $path = '/',
|
||||
string $domain = '',
|
||||
bool $secure = false,
|
||||
bool $httponly = false
|
||||
): Response {
|
||||
if (!$this->sandbox) {
|
||||
$this->cookies[] = [$key, $value, $timeout, $path, $domain, $secure, $httponly];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在http头部请求中声明类型和字符集
|
||||
*
|
||||
* @param string $contentType 文档类型
|
||||
* @return $this
|
||||
*/
|
||||
public function setContentType(string $contentType): Response
|
||||
{
|
||||
if (!$this->sandbox) {
|
||||
$this->contentType = $contentType;
|
||||
$this->setHeader('Content-Type', $this->contentType . '; charset=' . $this->charset);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符集
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCharset(): string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认回执编码
|
||||
*
|
||||
* @param string $charset 字符集
|
||||
* @return $this
|
||||
*/
|
||||
public function setCharset(string $charset): Response
|
||||
{
|
||||
if (!$this->sandbox) {
|
||||
$this->charset = $charset;
|
||||
$this->setHeader('Content-Type', $this->contentType . '; charset=' . $this->charset);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* add responder
|
||||
*
|
||||
* @param callable $responder
|
||||
* @return $this
|
||||
*/
|
||||
public function addResponder(callable $responder): Response
|
||||
{
|
||||
if (!$this->sandbox) {
|
||||
$this->responders[] = $responder;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
191
var/Typecho/Router.php
Executable file
191
var/Typecho/Router.php
Executable file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
use Typecho\Router\ParamsDelegateInterface;
|
||||
use Typecho\Router\Parser;
|
||||
use Typecho\Router\Exception as RouterException;
|
||||
|
||||
/**
|
||||
* Typecho组件基类
|
||||
*
|
||||
* @package Router
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
/**
|
||||
* 当前路由名称
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static string $current;
|
||||
|
||||
/**
|
||||
* 已经解析完毕的路由表配置
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static array $routingTable = [];
|
||||
|
||||
/**
|
||||
* 是否已经匹配过,防止递归匹配
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static bool $matched = false;
|
||||
|
||||
/**
|
||||
* 解析路径
|
||||
*
|
||||
* @param string $pathInfo 全路径
|
||||
* @param mixed $parameter 输入参数
|
||||
* @param bool $once 是否只匹配一次
|
||||
* @return false|Widget
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function match(string $pathInfo, $parameter = null, bool $once = true)
|
||||
{
|
||||
if ($once && self::$matched) {
|
||||
throw new RouterException("Path '{$pathInfo}' not found", 404);
|
||||
}
|
||||
|
||||
self::$matched = true;
|
||||
|
||||
foreach (self::route($pathInfo) as $result) {
|
||||
[$route, $params] = $result;
|
||||
try {
|
||||
return Widget::widget($route['widget'], $parameter, $params);
|
||||
} catch (\Exception $e) {
|
||||
if (404 == $e->getCode()) {
|
||||
Widget::destroy($route['widget']);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由分发函数
|
||||
*
|
||||
* @throws RouterException|\Exception
|
||||
*/
|
||||
public static function dispatch()
|
||||
{
|
||||
/** 获取PATHINFO */
|
||||
$pathInfo = Request::getInstance()->getPathInfo();
|
||||
|
||||
foreach (self::route($pathInfo) as $result) {
|
||||
[$route, $params] = $result;
|
||||
|
||||
try {
|
||||
$widget = Widget::widget($route['widget'], null, $params);
|
||||
|
||||
if (isset($route['action'])) {
|
||||
$widget->{$route['action']}();
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (\Exception $e) {
|
||||
if (404 == $e->getCode()) {
|
||||
Widget::destroy($route['widget']);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/** 载入路由异常支持 */
|
||||
throw new RouterException("Path '{$pathInfo}' not found", 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由反解析函数
|
||||
*
|
||||
* @param string $name 路由配置表名称
|
||||
* @param mixed $value 路由填充值
|
||||
* @param string|null $prefix 最终合成路径的前缀
|
||||
* @return string
|
||||
*/
|
||||
public static function url(
|
||||
string $name,
|
||||
$value = null,
|
||||
?string $prefix = null
|
||||
): string {
|
||||
if (!isset(self::$routingTable[$name])) {
|
||||
return '#';
|
||||
}
|
||||
|
||||
$route = self::$routingTable[$name];
|
||||
|
||||
//交换数组键值
|
||||
$pattern = [];
|
||||
foreach ($route['params'] as $param) {
|
||||
if (is_array($value) && isset($value[$param])) {
|
||||
$pattern[$param] = $value[$param];
|
||||
} elseif ($value instanceof ParamsDelegateInterface) {
|
||||
$pattern[$param] = $value->getRouterParam($param);
|
||||
} else {
|
||||
$pattern[$param] = '{' . $param . '}';
|
||||
}
|
||||
}
|
||||
|
||||
return Common::url(vsprintf($route['format'], $pattern), $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置路由器默认配置
|
||||
*
|
||||
* @param mixed $routes 配置信息
|
||||
* @return void
|
||||
*/
|
||||
public static function setRoutes($routes)
|
||||
{
|
||||
if (isset($routes[0])) {
|
||||
self::$routingTable = $routes[0];
|
||||
} else {
|
||||
/** 解析路由配置 */
|
||||
$parser = new Parser($routes);
|
||||
self::$routingTable = $parser->parse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取路由信息
|
||||
*
|
||||
* @param string $routeName 路由名称
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get(string $routeName)
|
||||
{
|
||||
return self::$routingTable[$routeName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pathInfo
|
||||
* @return \Generator
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function route(string $pathInfo): \Generator
|
||||
{
|
||||
foreach (self::$routingTable as $key => $route) {
|
||||
if (preg_match($route['regx'], $pathInfo, $matches)) {
|
||||
self::$current = $key;
|
||||
|
||||
/** 载入参数 */
|
||||
$params = null;
|
||||
|
||||
if (!empty($route['params'])) {
|
||||
unset($matches[0]);
|
||||
$params = array_combine($route['params'], $matches);
|
||||
}
|
||||
|
||||
yield [$route, $params];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
var/Typecho/Router/Exception.php
Executable file
18
var/Typecho/Router/Exception.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Router;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Typecho\Exception as TypechoException;
|
||||
|
||||
/**
|
||||
* 路由异常类
|
||||
*
|
||||
* @package Router
|
||||
*/
|
||||
class Exception extends TypechoException
|
||||
{
|
||||
}
|
||||
8
var/Typecho/Router/ParamsDelegateInterface.php
Executable file
8
var/Typecho/Router/ParamsDelegateInterface.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Router;
|
||||
|
||||
interface ParamsDelegateInterface
|
||||
{
|
||||
public function getRouterParam(string $key): string;
|
||||
}
|
||||
115
var/Typecho/Router/Parser.php
Executable file
115
var/Typecho/Router/Parser.php
Executable file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Router;
|
||||
|
||||
/**
|
||||
* 路由器解析器
|
||||
*
|
||||
* @category typecho
|
||||
* @package Router
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
/**
|
||||
* 默认匹配表
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $defaultRegex;
|
||||
|
||||
/**
|
||||
* 路由器映射表
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $routingTable;
|
||||
|
||||
/**
|
||||
* 参数表
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $params;
|
||||
|
||||
/**
|
||||
* 设置路由表
|
||||
*
|
||||
* @access public
|
||||
* @param array $routingTable 路由器映射表
|
||||
*/
|
||||
public function __construct(array $routingTable)
|
||||
{
|
||||
$this->routingTable = $routingTable;
|
||||
|
||||
$this->defaultRegex = [
|
||||
'string' => '(.%s)',
|
||||
'char' => '([^/]%s)',
|
||||
'digital' => '([0-9]%s)',
|
||||
'alpha' => '([_0-9a-zA-Z-]%s)',
|
||||
'alphaslash' => '([_0-9a-zA-Z-/]%s)',
|
||||
'split' => '((?:[^/]+/)%s[^/]+)',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 局部匹配并替换正则字符串
|
||||
*
|
||||
* @access public
|
||||
* @param array $matches 匹配部分
|
||||
* @return string
|
||||
*/
|
||||
public function match(array $matches): string
|
||||
{
|
||||
$params = explode(' ', $matches[1]);
|
||||
$paramsNum = count($params);
|
||||
$this->params[] = $params[0];
|
||||
|
||||
if (1 == $paramsNum) {
|
||||
return sprintf($this->defaultRegex['char'], '+');
|
||||
} elseif (2 == $paramsNum) {
|
||||
return sprintf($this->defaultRegex[$params[1]], '+');
|
||||
} elseif (3 == $paramsNum) {
|
||||
return sprintf($this->defaultRegex[$params[1]], $params[2] > 0 ? '{' . $params[2] . '}' : '*');
|
||||
} elseif (4 == $paramsNum) {
|
||||
return sprintf($this->defaultRegex[$params[1]], '{' . $params[2] . ',' . $params[3] . '}');
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析路由表
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function parse(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->routingTable as $key => $route) {
|
||||
$this->params = [];
|
||||
$route['regx'] = preg_replace_callback(
|
||||
"/%([^%]+)%/",
|
||||
[$this, 'match'],
|
||||
preg_quote(str_replace(['[', ']', ':'], ['%', '%', ' '], $route['url']))
|
||||
);
|
||||
|
||||
/** 处理斜线 */
|
||||
$route['regx'] = rtrim($route['regx'], '/');
|
||||
$route['regx'] = '|^' . $route['regx'] . '[/]?$|';
|
||||
|
||||
$route['format'] = preg_replace("/\[([^\]]+)\]/", "%s", $route['url']);
|
||||
$route['params'] = $this->params;
|
||||
|
||||
$result[$key] = $route;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
341
var/Typecho/Validate.php
Executable file
341
var/Typecho/Validate.php
Executable file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
/**
|
||||
* 验证类
|
||||
*
|
||||
* @package Validate
|
||||
*/
|
||||
class Validate
|
||||
{
|
||||
/**
|
||||
* 内部数据
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $data;
|
||||
|
||||
/**
|
||||
* 当前验证指针
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $key;
|
||||
|
||||
/**
|
||||
* 验证规则数组
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $rules = [];
|
||||
|
||||
/**
|
||||
* 中断模式,一旦出现验证错误即抛出而不再继续执行
|
||||
*
|
||||
* @access private
|
||||
* @var boolean
|
||||
*/
|
||||
private bool $break = false;
|
||||
|
||||
/**
|
||||
* 最小长度
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $str 待处理的字符串
|
||||
* @param integer $length 最小长度
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function minLength(string $str, int $length): bool
|
||||
{
|
||||
return (Common::strLen($str) >= $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举类型判断
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $str 待处理的字符串
|
||||
* @param array $params 枚举值
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function enum(string $str, array $params): bool
|
||||
{
|
||||
$keys = array_flip($params);
|
||||
return isset($keys[$str]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Max Length
|
||||
*
|
||||
* @param string $str
|
||||
* @param int $length
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function maxLength(string $str, int $length): bool
|
||||
{
|
||||
return (Common::strLen($str) < $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid Email
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function email(string $str): bool
|
||||
{
|
||||
$email = filter_var($str, FILTER_SANITIZE_EMAIL);
|
||||
return !!filter_var($str, FILTER_VALIDATE_EMAIL) && ($email === $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证是否为网址
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function url(string $str): bool
|
||||
{
|
||||
$url = Common::safeUrl($str);
|
||||
return !!filter_var($str, FILTER_VALIDATE_URL) && ($url === $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alpha
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function alpha(string $str): bool
|
||||
{
|
||||
return ctype_alpha($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alpha-numeric
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function alphaNumeric(string $str): bool
|
||||
{
|
||||
return ctype_alnum($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alpha-numeric with underscores and dashes
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function alphaDash(string $str): bool
|
||||
{
|
||||
return !!preg_match("/^([_a-z0-9-])+$/i", $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对xss字符串的检测
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function xssCheck(string $str): bool
|
||||
{
|
||||
$search = 'abcdefghijklmnopqrstuvwxyz';
|
||||
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$search .= '1234567890!@#$%^&*()';
|
||||
$search .= '~`";:?+/={}[]-_|\'\\';
|
||||
|
||||
for ($i = 0; $i < strlen($search); $i++) {
|
||||
// ;? matches the ;, which is optional
|
||||
// 0{0,7} matches any padded zeros, which are optional and go up to 8 chars
|
||||
|
||||
// @ @ search for the hex values
|
||||
$str = preg_replace('/(&#[xX]0{0,8}' . dechex(ord($search[$i])) . ';?)/i', $search[$i], $str); // with a ;
|
||||
// @ @ 0{0,7} matches '0' zero to seven times
|
||||
$str = preg_replace('/(�{0,8}' . ord($search[$i]) . ';?)/', $search[$i], $str); // with a ;
|
||||
}
|
||||
|
||||
return !preg_match('/(\(|\)|\\\|"|<|>|[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|' . "\r|\n|\t" . ')/', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Numeric
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $str
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isFloat($str): bool
|
||||
{
|
||||
return filter_var($str, FILTER_VALIDATE_FLOAT) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Numeric
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $str
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isInteger($str): bool
|
||||
{
|
||||
return filter_var($str, FILTER_VALIDATE_INT) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 正则表达式验证
|
||||
*
|
||||
* @param string $str
|
||||
* @param string $pattern
|
||||
* @return bool
|
||||
*/
|
||||
public static function regexp(string $str, string $pattern): bool
|
||||
{
|
||||
return preg_match($pattern, $str) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加验证规则
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $key 数值键值
|
||||
* @param string|callable $rule 规则名称
|
||||
* @param string $message 错误字符串
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addRule(string $key, $rule, string $message): Validate
|
||||
{
|
||||
if (func_num_args() <= 3) {
|
||||
$this->rules[$key][] = [$rule, $message];
|
||||
} else {
|
||||
$params = func_get_args();
|
||||
$params = array_splice($params, 3);
|
||||
$this->rules[$key][] = array_merge([$rule, $message], $params);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置为中断模式
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function setBreak()
|
||||
{
|
||||
$this->break = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the Validator
|
||||
* This function does all the work.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $data 需要验证的数据
|
||||
* @param array|null $rules 验证数据遵循的规则
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function run(array $data, ?array $rules = null): array
|
||||
{
|
||||
$result = [];
|
||||
$this->data = $data;
|
||||
$rules = empty($rules) ? $this->rules : $rules;
|
||||
|
||||
// Cycle through the rules and test for errors
|
||||
foreach ($rules as $key => $rule) {
|
||||
$this->key = $key;
|
||||
$data[$key] = (is_array($data[$key]) ? 0 == count($data[$key])
|
||||
: 0 == strlen($data[$key] ?? '')) ? null : $data[$key];
|
||||
|
||||
foreach ($rule as $params) {
|
||||
$method = $params[0];
|
||||
|
||||
if ('required' != $method && 'confirm' != $method && 0 == strlen($data[$key] ?? '')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$message = $params[1];
|
||||
$params[1] = $data[$key];
|
||||
$params = array_slice($params, 1);
|
||||
|
||||
if (!call_user_func_array(is_callable($method) ? $method : [$this, $method], $params)) {
|
||||
$result[$key] = $message;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** 开启中断 */
|
||||
if ($this->break && $result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证输入是否一致
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string|null $str 待处理的字符串
|
||||
* @param string $key 需要一致性检查的键值
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function confirm(?string $str, string $key): bool
|
||||
{
|
||||
return !empty($this->data[$key]) ? ($str == $this->data[$key]) : empty($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为空
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function required(): bool
|
||||
{
|
||||
return array_key_exists($this->key, $this->data) &&
|
||||
(is_array($this->data[$this->key]) ? 0 < count($this->data[$this->key])
|
||||
: 0 < strlen($this->data[$this->key] ?? ''));
|
||||
}
|
||||
}
|
||||
551
var/Typecho/Widget.php
Executable file
551
var/Typecho/Widget.php
Executable file
@@ -0,0 +1,551 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho;
|
||||
|
||||
use Typecho\Widget\Helper\EmptyClass;
|
||||
use Typecho\Widget\Request as WidgetRequest;
|
||||
use Typecho\Widget\Response as WidgetResponse;
|
||||
use Typecho\Widget\Terminal;
|
||||
|
||||
/**
|
||||
* Typecho组件基类
|
||||
*
|
||||
* @property $sequence
|
||||
* @property $length
|
||||
* @property-read $request
|
||||
* @property-read $response
|
||||
* @property-read $parameter
|
||||
*/
|
||||
abstract class Widget
|
||||
{
|
||||
/**
|
||||
* widget对象池
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static array $widgetPool = [];
|
||||
|
||||
/**
|
||||
* widget别名
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static array $widgetAlias = [];
|
||||
|
||||
/**
|
||||
* request对象
|
||||
*
|
||||
* @var WidgetRequest
|
||||
*/
|
||||
protected WidgetRequest $request;
|
||||
|
||||
/**
|
||||
* response对象
|
||||
*
|
||||
* @var WidgetResponse
|
||||
*/
|
||||
protected WidgetResponse $response;
|
||||
|
||||
/**
|
||||
* 数据堆栈
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $stack = [];
|
||||
|
||||
/**
|
||||
* 当前队列指针顺序值,从1开始
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected int $sequence = 0;
|
||||
|
||||
/**
|
||||
* 队列长度
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected int $length = 0;
|
||||
|
||||
/**
|
||||
* config对象
|
||||
*
|
||||
* @var Config
|
||||
*/
|
||||
protected Config $parameter;
|
||||
|
||||
/**
|
||||
* 数据堆栈每一行
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $row = [];
|
||||
|
||||
/**
|
||||
* 构造函数,初始化组件
|
||||
*
|
||||
* @param WidgetRequest $request request对象
|
||||
* @param WidgetResponse $response response对象
|
||||
* @param mixed $params 参数列表
|
||||
*/
|
||||
public function __construct(WidgetRequest $request, WidgetResponse $response, $params = null)
|
||||
{
|
||||
//设置函数内部对象
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->parameter = Config::factory($params);
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* init method
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* widget别名
|
||||
*
|
||||
* @param string $widgetClass
|
||||
* @param string $aliasClass
|
||||
*/
|
||||
public static function alias(string $widgetClass, string $aliasClass)
|
||||
{
|
||||
self::$widgetAlias[$widgetClass] = $aliasClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工厂方法,将类静态化放置到列表中
|
||||
*
|
||||
* @param class-string $alias 组件别名
|
||||
* @param mixed $params 传递的参数
|
||||
* @param mixed $request 前端参数
|
||||
* @param bool|callable $disableSandboxOrCallback 回调
|
||||
* @return Widget
|
||||
*/
|
||||
public static function widget(
|
||||
string $alias,
|
||||
$params = null,
|
||||
$request = null,
|
||||
$disableSandboxOrCallback = true
|
||||
): Widget {
|
||||
[$className] = explode('@', $alias);
|
||||
$key = Common::nativeClassName($alias);
|
||||
|
||||
if (isset(self::$widgetAlias[$className])) {
|
||||
$className = self::$widgetAlias[$className];
|
||||
}
|
||||
|
||||
$sandbox = false;
|
||||
|
||||
if ($disableSandboxOrCallback === false || is_callable($disableSandboxOrCallback)) {
|
||||
$sandbox = true;
|
||||
Request::getInstance()->beginSandbox(new Config($request));
|
||||
Response::getInstance()->beginSandbox();
|
||||
}
|
||||
|
||||
if ($sandbox || !isset(self::$widgetPool[$key])) {
|
||||
$requestObject = new WidgetRequest(Request::getInstance(), isset($request) ? new Config($request) : null);
|
||||
$responseObject = new WidgetResponse(Request::getInstance(), Response::getInstance());
|
||||
|
||||
try {
|
||||
$widget = new $className($requestObject, $responseObject, $params);
|
||||
$widget->execute();
|
||||
|
||||
if ($sandbox && is_callable($disableSandboxOrCallback)) {
|
||||
call_user_func($disableSandboxOrCallback, $widget);
|
||||
}
|
||||
} catch (Terminal $e) {
|
||||
$widget = $widget ?? null;
|
||||
} finally {
|
||||
if ($sandbox) {
|
||||
Response::getInstance()->endSandbox();
|
||||
Request::getInstance()->endSandbox();
|
||||
|
||||
return $widget;
|
||||
}
|
||||
}
|
||||
|
||||
self::$widgetPool[$key] = $widget;
|
||||
}
|
||||
|
||||
return self::$widgetPool[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* alloc widget instance
|
||||
*
|
||||
* @param mixed $params
|
||||
* @param mixed $request
|
||||
* @param bool|callable $disableSandboxOrCallback
|
||||
* @return $this
|
||||
*/
|
||||
public static function alloc($params = null, $request = null, $disableSandboxOrCallback = true): Widget
|
||||
{
|
||||
return self::widget(static::class, $params, $request, $disableSandboxOrCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* alloc widget instance with alias
|
||||
*
|
||||
* @param string|null $alias
|
||||
* @param mixed $params
|
||||
* @param mixed $request
|
||||
* @param bool|callable $disableSandboxOrCallback
|
||||
* @return $this
|
||||
*/
|
||||
public static function allocWithAlias(
|
||||
?string $alias,
|
||||
$params = null,
|
||||
$request = null,
|
||||
$disableSandboxOrCallback = true
|
||||
): Widget {
|
||||
return self::widget(
|
||||
static::class . (isset($alias) ? '@' . $alias : ''),
|
||||
$params,
|
||||
$request,
|
||||
$disableSandboxOrCallback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放组件
|
||||
*
|
||||
* @param string $alias 组件名称
|
||||
* @deprecated alias for destroy
|
||||
*/
|
||||
public static function destory(string $alias)
|
||||
{
|
||||
self::destroy($alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放组件
|
||||
*
|
||||
* @param string|null $alias 组件名称
|
||||
*/
|
||||
public static function destroy(?string $alias = null)
|
||||
{
|
||||
if (Common::nativeClassName(static::class) == 'Typecho_Widget') {
|
||||
if (isset($alias)) {
|
||||
unset(self::$widgetPool[$alias]);
|
||||
} else {
|
||||
self::$widgetPool = [];
|
||||
}
|
||||
} else {
|
||||
$alias = static::class . (isset($alias) ? '@' . $alias : '');
|
||||
unset(self::$widgetPool[$alias]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* execute function.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* post事件触发
|
||||
*
|
||||
* @param boolean $condition 触发条件
|
||||
*
|
||||
* @return $this|EmptyClass
|
||||
*/
|
||||
public function on(bool $condition)
|
||||
{
|
||||
if ($condition) {
|
||||
return $this;
|
||||
} else {
|
||||
return new EmptyClass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将类本身赋值
|
||||
*
|
||||
* @param mixed $variable 变量名
|
||||
* @return $this
|
||||
*/
|
||||
public function to(&$variable): Widget
|
||||
{
|
||||
return $variable = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模版渲染
|
||||
*
|
||||
* @param string $template 模版
|
||||
* @return string
|
||||
*/
|
||||
public function template(string $template): string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
"/\{([_a-z0-9]+)\}/i",
|
||||
function (array $matches) {
|
||||
return $this->{$matches[1]};
|
||||
},
|
||||
$template
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化解析堆栈内的所有数据
|
||||
*
|
||||
* @param string $template 模版
|
||||
*/
|
||||
public function parse(string $template)
|
||||
{
|
||||
while ($this->next()) {
|
||||
echo $this->template($template);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $column
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
public function toColumn($column)
|
||||
{
|
||||
if (is_array($column)) {
|
||||
$item = [];
|
||||
foreach ($column as $key) {
|
||||
$item[$key] = $this->{$key};
|
||||
}
|
||||
|
||||
return $item;
|
||||
} else {
|
||||
return $this->{$column};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $column
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($column): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
while ($this->next()) {
|
||||
$result[] = $this->toColumn($column);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回堆栈每一行的值
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
$key = key($this->stack);
|
||||
|
||||
if ($key !== null && isset($this->stack[$key])) {
|
||||
$this->row = current($this->stack);
|
||||
next($this->stack);
|
||||
$this->sequence++;
|
||||
} else {
|
||||
reset($this->stack);
|
||||
$this->sequence = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->row;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将每一行的值压入堆栈
|
||||
*
|
||||
* @param array $value 每一行的值
|
||||
* @return mixed
|
||||
*/
|
||||
public function push(array $value)
|
||||
{
|
||||
//将行数据按顺序置位
|
||||
$this->row = $value;
|
||||
$this->length++;
|
||||
|
||||
$this->stack[] = $value;
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有行的值压入堆栈
|
||||
*
|
||||
* @param array $values 所有行的值
|
||||
*/
|
||||
public function pushAll(array $values)
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
$this->push($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据余数输出
|
||||
*
|
||||
* @param mixed ...$args
|
||||
*/
|
||||
public function alt(...$args)
|
||||
{
|
||||
$this->altBy($this->sequence, ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据余数输出
|
||||
*
|
||||
* @param int $current
|
||||
* @param mixed ...$args
|
||||
*/
|
||||
public function altBy(int $current, ...$args)
|
||||
{
|
||||
$num = count($args);
|
||||
$split = $current % $num;
|
||||
echo $args[(0 == $split ? $num : $split) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回堆栈是否为空
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function have(): bool
|
||||
{
|
||||
return !empty($this->stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术函数,用于挂接其它函数
|
||||
*
|
||||
* @param string $name 函数名
|
||||
* @param array $args 函数参数
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$method = 'call' . ucfirst($name);
|
||||
self::pluginHandle()->trigger($plugged)->call($method, $this, $args);
|
||||
|
||||
if (!$plugged) {
|
||||
echo $this->{$name};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象插件句柄
|
||||
*
|
||||
* @return Plugin
|
||||
*/
|
||||
public static function pluginHandle(): Plugin
|
||||
{
|
||||
return Plugin::factory(static::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术函数,用于获取内部变量
|
||||
*
|
||||
* @param string $name 变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
$method = '___' . $name;
|
||||
$key = '#' . $name;
|
||||
|
||||
if (array_key_exists($key, $this->row)) {
|
||||
return $this->row[$key];
|
||||
} elseif (method_exists($this, $method)) {
|
||||
$this->row[$key] = $this->$method();
|
||||
return $this->row[$key];
|
||||
} elseif (array_key_exists($name, $this->row)) {
|
||||
return $this->row[$name];
|
||||
} else {
|
||||
$return = self::pluginHandle()->trigger($plugged)->call($method, $this);
|
||||
if ($plugged) {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定堆栈每一行的值
|
||||
*
|
||||
* @param string $name 值对应的键值
|
||||
* @param mixed $value 相应的值
|
||||
*/
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
$method = '___' . $name;
|
||||
$key = '#' . $name;
|
||||
|
||||
if (isset($this->row[$key]) || method_exists($this, $method)) {
|
||||
$this->row[$key] = $value;
|
||||
} else {
|
||||
$this->row[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证堆栈值是否存在
|
||||
*
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public function __isSet(string $name)
|
||||
{
|
||||
$method = '___' . $name;
|
||||
$key = '#' . $name;
|
||||
|
||||
return isset($this->row[$key]) || method_exists($this, $method) || isset($this->row[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出顺序值
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function ___sequence(): int
|
||||
{
|
||||
return $this->sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出数据长度
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function ___length(): int
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WidgetRequest
|
||||
*/
|
||||
public function ___request(): WidgetRequest
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WidgetResponse
|
||||
*/
|
||||
public function ___response(): WidgetResponse
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
public function ___parameter(): Config
|
||||
{
|
||||
return $this->parameter;
|
||||
}
|
||||
}
|
||||
18
var/Typecho/Widget/Exception.php
Executable file
18
var/Typecho/Widget/Exception.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Typecho\Exception as TypechoException;
|
||||
|
||||
/**
|
||||
* 组件异常类
|
||||
*
|
||||
* @package Widget
|
||||
*/
|
||||
class Exception extends TypechoException
|
||||
{
|
||||
}
|
||||
50
var/Typecho/Widget/Helper/EmptyClass.php
Executable file
50
var/Typecho/Widget/Helper/EmptyClass.php
Executable file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper;
|
||||
|
||||
/**
|
||||
* widget对象帮手,用于处理空对象方法
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class EmptyClass
|
||||
{
|
||||
/**
|
||||
* 单例句柄
|
||||
*
|
||||
* @access private
|
||||
* @var EmptyClass
|
||||
*/
|
||||
private static ?EmptyClass $instance = null;
|
||||
|
||||
/**
|
||||
* 获取单例句柄
|
||||
*
|
||||
* @access public
|
||||
* @return EmptyClass
|
||||
*/
|
||||
public static function getInstance(): EmptyClass
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有方法请求直接返回
|
||||
*
|
||||
* @access public
|
||||
* @param string $name 方法名
|
||||
* @param array $args 参数列表
|
||||
* @return $this
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
241
var/Typecho/Widget/Helper/Form.php
Executable file
241
var/Typecho/Widget/Helper/Form.php
Executable file
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper;
|
||||
|
||||
use Typecho\Cookie;
|
||||
use Typecho\Request;
|
||||
use Typecho\Validate;
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单处理帮手
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Form extends Layout
|
||||
{
|
||||
/** 表单post方法 */
|
||||
public const POST_METHOD = 'post';
|
||||
|
||||
/** 表单get方法 */
|
||||
public const GET_METHOD = 'get';
|
||||
|
||||
/** 标准编码方法 */
|
||||
public const STANDARD_ENCODE = 'application/x-www-form-urlencoded';
|
||||
|
||||
/** 混合编码 */
|
||||
public const MULTIPART_ENCODE = 'multipart/form-data';
|
||||
|
||||
/** 文本编码 */
|
||||
public const TEXT_ENCODE = 'text/plain';
|
||||
|
||||
/**
|
||||
* 输入元素列表
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $inputs = [];
|
||||
|
||||
/**
|
||||
* 构造函数,设置基本属性
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($action = null, $method = self::GET_METHOD, $enctype = self::STANDARD_ENCODE)
|
||||
{
|
||||
/** 设置表单标签 */
|
||||
parent::__construct('form');
|
||||
|
||||
/** 关闭自闭合 */
|
||||
$this->setClose(false);
|
||||
|
||||
/** 设置表单属性 */
|
||||
$this->setAction($action);
|
||||
$this->setMethod($method);
|
||||
$this->setEncodeType($enctype);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单提交目的
|
||||
*
|
||||
* @param string|null $action 表单提交目的
|
||||
* @return $this
|
||||
*/
|
||||
public function setAction(?string $action): Form
|
||||
{
|
||||
$this->setAttribute('action', $action);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单提交方法
|
||||
*
|
||||
* @param string $method 表单提交方法
|
||||
* @return $this
|
||||
*/
|
||||
public function setMethod(string $method): Form
|
||||
{
|
||||
$this->setAttribute('method', $method);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单编码方案
|
||||
*
|
||||
* @param string $enctype 编码方法
|
||||
* @return $this
|
||||
*/
|
||||
public function setEncodeType(string $enctype): Form
|
||||
{
|
||||
$this->setAttribute('enctype', $enctype);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加输入元素
|
||||
*
|
||||
* @access public
|
||||
* @param Element $input 输入元素
|
||||
* @return $this
|
||||
*/
|
||||
public function addInput(Element $input): Form
|
||||
{
|
||||
$this->inputs[$input->name] = $input;
|
||||
$this->addItem($input);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入项
|
||||
*
|
||||
* @param string $name 输入项名称
|
||||
* @return mixed
|
||||
*/
|
||||
public function getInput(string $name)
|
||||
{
|
||||
return $this->inputs[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有输入项的提交值
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllRequest(): array
|
||||
{
|
||||
return $this->getParams(array_keys($this->inputs));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取此表单的所有输入项固有值
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues(): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
foreach ($this->inputs as $name => $input) {
|
||||
$values[$name] = $input->value;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取此表单的所有输入项
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getInputs(): array
|
||||
{
|
||||
return $this->inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证表单
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function validate(): array
|
||||
{
|
||||
$validator = new Validate();
|
||||
$rules = [];
|
||||
|
||||
foreach ($this->inputs as $name => $input) {
|
||||
$rules[$name] = $input->rules;
|
||||
}
|
||||
|
||||
$id = md5(implode('"', array_keys($this->inputs)));
|
||||
|
||||
/** 表单值 */
|
||||
$formData = $this->getParams(array_keys($rules));
|
||||
$error = $validator->run($formData, $rules);
|
||||
|
||||
if ($error) {
|
||||
/** 利用session记录错误 */
|
||||
Cookie::set('__typecho_form_message_' . $id, json_encode($error));
|
||||
|
||||
/** 利用session记录表单值 */
|
||||
Cookie::set('__typecho_form_record_' . $id, json_encode($formData));
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提交数据源
|
||||
*
|
||||
* @param array $params 数据参数集
|
||||
* @return array
|
||||
*/
|
||||
public function getParams(array $params): array
|
||||
{
|
||||
$result = [];
|
||||
$request = Request::getInstance();
|
||||
|
||||
foreach ($params as $param) {
|
||||
$result[$param] = $request->get($param, is_array($this->getInput($param)->value) ? [] : null);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示表单
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$id = md5(implode('"', array_keys($this->inputs)));
|
||||
$record = Cookie::get('__typecho_form_record_' . $id);
|
||||
$message = Cookie::get('__typecho_form_message_' . $id);
|
||||
|
||||
/** 恢复表单值 */
|
||||
if (!empty($record)) {
|
||||
$record = json_decode($record, true);
|
||||
$message = json_decode($message, true);
|
||||
foreach ($this->inputs as $name => $input) {
|
||||
$input->value($record[$name] ?? $input->value);
|
||||
|
||||
/** 显示错误消息 */
|
||||
if (isset($message[$name])) {
|
||||
$input->message($message[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
Cookie::delete('__typecho_form_record_' . $id);
|
||||
}
|
||||
|
||||
parent::render();
|
||||
Cookie::delete('__typecho_form_message_' . $id);
|
||||
}
|
||||
}
|
||||
330
var/Typecho/Widget/Helper/Form/Element.php
Executable file
330
var/Typecho/Widget/Helper/Form/Element.php
Executable file
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form;
|
||||
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单元素抽象类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
abstract class Element extends Layout
|
||||
{
|
||||
/**
|
||||
* 单例唯一id
|
||||
*
|
||||
* @access protected
|
||||
* @var integer
|
||||
*/
|
||||
protected static int $uniqueId = 0;
|
||||
|
||||
/**
|
||||
* 表单元素容器
|
||||
*
|
||||
* @access public
|
||||
* @var Layout
|
||||
*/
|
||||
public Layout $container;
|
||||
|
||||
/**
|
||||
* 输入栏
|
||||
*
|
||||
* @access public
|
||||
* @var Layout|null
|
||||
*/
|
||||
public ?Layout $input;
|
||||
|
||||
/**
|
||||
* inputs
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public array $inputs = [];
|
||||
|
||||
/**
|
||||
* 表单标题
|
||||
*
|
||||
* @access public
|
||||
* @var Layout
|
||||
*/
|
||||
public Layout $label;
|
||||
|
||||
/**
|
||||
* 表单验证器
|
||||
*
|
||||
* @access public
|
||||
* @var array
|
||||
*/
|
||||
public array $rules = [];
|
||||
|
||||
/**
|
||||
* 表单名称
|
||||
*
|
||||
* @access public
|
||||
* @var string
|
||||
*/
|
||||
public ?string $name;
|
||||
|
||||
/**
|
||||
* 表单值
|
||||
*
|
||||
* @access public
|
||||
* @var mixed
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* 表单描述
|
||||
*
|
||||
* @access private
|
||||
* @var Layout
|
||||
*/
|
||||
protected Layout $description;
|
||||
|
||||
/**
|
||||
* 表单消息
|
||||
*
|
||||
* @access protected
|
||||
* @var Layout
|
||||
*/
|
||||
protected Layout $message;
|
||||
|
||||
/**
|
||||
* 多行输入
|
||||
*
|
||||
* @access public
|
||||
* @var array()
|
||||
*/
|
||||
protected array $multiline = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param string|null $name 表单输入项名称
|
||||
* @param array|null $options 选择项
|
||||
* @param mixed $value 表单默认值
|
||||
* @param string|null $label 表单标题
|
||||
* @param string|null $description 表单描述
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
?string $name = null,
|
||||
?array $options = null,
|
||||
$value = null,
|
||||
?string $label = null,
|
||||
?string $description = null
|
||||
) {
|
||||
/** 创建html元素,并设置class */
|
||||
parent::__construct(
|
||||
'ul',
|
||||
['class' => 'typecho-option', 'id' => 'typecho-option-item-' . $name . '-' . self::$uniqueId]
|
||||
);
|
||||
|
||||
$this->name = $name;
|
||||
self::$uniqueId++;
|
||||
|
||||
/** 运行自定义初始函数 */
|
||||
$this->init();
|
||||
|
||||
/** 初始化表单标题 */
|
||||
if (null !== $label) {
|
||||
$this->label($label);
|
||||
}
|
||||
|
||||
/** 初始化表单项 */
|
||||
$this->input = $this->input($name, $options);
|
||||
|
||||
/** 初始化表单值 */
|
||||
if (null !== $value) {
|
||||
$this->value($value);
|
||||
}
|
||||
|
||||
/** 初始化表单描述 */
|
||||
if (null !== $description) {
|
||||
$this->description($description);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义初始函数
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表单标题
|
||||
*
|
||||
* @param string $value 标题字符串
|
||||
* @return $this
|
||||
*/
|
||||
public function label(string $value): Element
|
||||
{
|
||||
/** 创建标题元素 */
|
||||
if (empty($this->label)) {
|
||||
$this->label = new Layout('label', ['class' => 'typecho-label']);
|
||||
$this->container($this->label);
|
||||
}
|
||||
|
||||
$this->label->html($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在容器里增加元素
|
||||
*
|
||||
* @param Layout $item 表单元素
|
||||
* @return $this
|
||||
*/
|
||||
public function container(Layout $item): Element
|
||||
{
|
||||
/** 创建表单容器 */
|
||||
if (empty($this->container)) {
|
||||
$this->container = new Layout('li');
|
||||
$this->addItem($this->container);
|
||||
}
|
||||
|
||||
$this->container->addItem($item);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化当前输入项
|
||||
*
|
||||
* @param string|null $name 表单元素名称
|
||||
* @param array|null $options 选择项
|
||||
* @return Layout|null
|
||||
*/
|
||||
abstract public function input(?string $name = null, ?array $options = null): ?Layout;
|
||||
|
||||
/**
|
||||
* 设置表单元素值
|
||||
*
|
||||
* @param mixed $value 表单元素值
|
||||
* @return Element
|
||||
*/
|
||||
public function value($value): Element
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->inputValue($value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置描述信息
|
||||
*
|
||||
* @param string $description 描述信息
|
||||
* @return Element
|
||||
*/
|
||||
public function description(string $description): Element
|
||||
{
|
||||
/** 创建描述元素 */
|
||||
if (empty($this->description)) {
|
||||
$this->description = new Layout('p', ['class' => 'description']);
|
||||
$this->container($this->description);
|
||||
}
|
||||
|
||||
$this->description->html($description);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置提示信息
|
||||
*
|
||||
* @param string $message 提示信息
|
||||
* @return Element
|
||||
*/
|
||||
public function message(string $message): Element
|
||||
{
|
||||
if (empty($this->message)) {
|
||||
$this->message = new Layout('p', ['class' => 'message error']);
|
||||
$this->container($this->message);
|
||||
}
|
||||
|
||||
$this->message->html($message);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多行输出模式
|
||||
*
|
||||
* @return Layout
|
||||
*/
|
||||
public function multiline(): Layout
|
||||
{
|
||||
$item = new Layout('span');
|
||||
$this->multiline[] = $item;
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多行输出模式
|
||||
*
|
||||
* @return Element
|
||||
*/
|
||||
public function multiMode(): Element
|
||||
{
|
||||
foreach ($this->multiline as $item) {
|
||||
$item->setAttribute('class', 'multiline');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加验证器
|
||||
*
|
||||
* @param mixed ...$rules
|
||||
* @return $this
|
||||
*/
|
||||
public function addRule(...$rules): Element
|
||||
{
|
||||
$this->rules[] = $rules;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一设置所有输入项的属性值
|
||||
*
|
||||
* @param string $attributeName
|
||||
* @param mixed $attributeValue
|
||||
*/
|
||||
public function setInputsAttribute(string $attributeName, $attributeValue)
|
||||
{
|
||||
foreach ($this->inputs as $input) {
|
||||
$input->setAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单元素值
|
||||
*
|
||||
* @param mixed $value 表单元素值
|
||||
*/
|
||||
abstract protected function inputValue($value);
|
||||
|
||||
/**
|
||||
* filterValue
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function filterValue(string $value): string
|
||||
{
|
||||
if (preg_match_all('/[_0-9a-z-]+/i', $value, $matches)) {
|
||||
return implode('-', $matches[0]);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
76
var/Typecho/Widget/Helper/Form/Element/Checkbox.php
Executable file
76
var/Typecho/Widget/Helper/Form/Element/Checkbox.php
Executable file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多选框帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Checkbox extends Element
|
||||
{
|
||||
/**
|
||||
* 选择值
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
/**
|
||||
* 初始化当前输入项
|
||||
*
|
||||
* @param string|null $name 表单元素名称
|
||||
* @param array|null $options 选择项
|
||||
* @return Layout|null
|
||||
*/
|
||||
public function input(?string $name = null, ?array $options = null): ?Layout
|
||||
{
|
||||
foreach ($options as $value => $label) {
|
||||
$this->options[$value] = new Layout('input');
|
||||
$item = $this->multiline();
|
||||
$id = $this->name . '-' . $this->filterValue($value);
|
||||
$this->inputs[] = $this->options[$value];
|
||||
|
||||
$item->addItem($this->options[$value]->setAttribute('name', $this->name . '[]')
|
||||
->setAttribute('type', 'checkbox')
|
||||
->setAttribute('value', $value)
|
||||
->setAttribute('id', $id));
|
||||
|
||||
$labelItem = new Layout('label');
|
||||
$item->addItem($labelItem->setAttribute('for', $id)->html($label));
|
||||
$this->container($item);
|
||||
}
|
||||
|
||||
return current($this->options) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单元素值
|
||||
*
|
||||
* @param mixed $value 表单元素值
|
||||
*/
|
||||
protected function inputValue($value)
|
||||
{
|
||||
$values = isset($value) ? (is_array($value) ? $value : [$value]) : [];
|
||||
|
||||
foreach ($this->options as $option) {
|
||||
$option->removeAttribute('checked');
|
||||
}
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (isset($this->options[$value])) {
|
||||
$this->options[$value]->setAttribute('checked', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
var/Typecho/Widget/Helper/Form/Element/Fake.php
Executable file
68
var/Typecho/Widget/Helper/Form/Element/Fake.php
Executable file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟域帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Fake extends Element
|
||||
{
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param string $name 表单输入项名称
|
||||
* @param mixed $value 表单默认值
|
||||
*/
|
||||
public function __construct(string $name, $value)
|
||||
{
|
||||
$this->name = $name;
|
||||
self::$uniqueId++;
|
||||
|
||||
/** 运行自定义初始函数 */
|
||||
$this->init();
|
||||
|
||||
/** 初始化表单项 */
|
||||
$this->input = $this->input($name);
|
||||
|
||||
/** 初始化表单值 */
|
||||
if (null !== $value) {
|
||||
$this->value($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化当前输入项
|
||||
*
|
||||
* @param string|null $name 表单元素名称
|
||||
* @param array|null $options 选择项
|
||||
* @return Layout|null
|
||||
*/
|
||||
public function input(?string $name = null, ?array $options = null): ?Layout
|
||||
{
|
||||
$input = new Layout('input');
|
||||
$this->inputs[] = $input;
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单项默认值
|
||||
*
|
||||
* @param mixed $value 表单项默认值
|
||||
*/
|
||||
protected function inputValue($value)
|
||||
{
|
||||
$this->input->setAttribute('value', $value);
|
||||
}
|
||||
}
|
||||
50
var/Typecho/Widget/Helper/Form/Element/Hidden.php
Executable file
50
var/Typecho/Widget/Helper/Form/Element/Hidden.php
Executable file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏域帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Hidden extends Element
|
||||
{
|
||||
use TextInputTrait;
|
||||
|
||||
/**
|
||||
* 自定义初始函数
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
/** 隐藏此行 */
|
||||
$this->setAttribute('style', 'display:none');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function filterValue(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'hidden';
|
||||
}
|
||||
}
|
||||
18
var/Typecho/Widget/Helper/Form/Element/Number.php
Executable file
18
var/Typecho/Widget/Helper/Form/Element/Number.php
Executable file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Number extends Text
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'number';
|
||||
}
|
||||
}
|
||||
39
var/Typecho/Widget/Helper/Form/Element/Password.php
Executable file
39
var/Typecho/Widget/Helper/Form/Element/Password.php
Executable file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码输入表单项帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Password extends Element
|
||||
{
|
||||
use TextInputTrait;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function filterValue(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'password';
|
||||
}
|
||||
}
|
||||
74
var/Typecho/Widget/Helper/Form/Element/Radio.php
Executable file
74
var/Typecho/Widget/Helper/Form/Element/Radio.php
Executable file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单选框帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Radio extends Element
|
||||
{
|
||||
/**
|
||||
* 选择值
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
/**
|
||||
* 初始化当前输入项
|
||||
*
|
||||
* @param string|null $name 表单元素名称
|
||||
* @param array|null $options 选择项
|
||||
* @return Layout|null
|
||||
*/
|
||||
public function input(?string $name = null, ?array $options = null): ?Layout
|
||||
{
|
||||
foreach ($options as $value => $label) {
|
||||
$this->options[$value] = new Layout('input');
|
||||
$item = $this->multiline();
|
||||
$id = $this->name . '-' . $this->filterValue($value);
|
||||
$this->inputs[] = $this->options[$value];
|
||||
|
||||
$item->addItem($this->options[$value]->setAttribute('name', $this->name)
|
||||
->setAttribute('type', 'radio')
|
||||
->setAttribute('value', $value)
|
||||
->setAttribute('id', $id));
|
||||
|
||||
$labelItem = new Layout('label');
|
||||
$item->addItem($labelItem->setAttribute('for', $id)->html($label));
|
||||
$this->container($item);
|
||||
}
|
||||
|
||||
return current($this->options) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单元素值
|
||||
*
|
||||
* @param mixed $value 表单元素值
|
||||
*/
|
||||
protected function inputValue($value)
|
||||
{
|
||||
foreach ($this->options as $option) {
|
||||
$option->removeAttribute('checked');
|
||||
}
|
||||
|
||||
if (isset($value) && isset($this->options[$value])) {
|
||||
$this->value = $value;
|
||||
$this->options[$value]->setAttribute('checked', 'true');
|
||||
$this->input = $this->options[$value];
|
||||
}
|
||||
}
|
||||
}
|
||||
67
var/Typecho/Widget/Helper/Form/Element/Select.php
Executable file
67
var/Typecho/Widget/Helper/Form/Element/Select.php
Executable file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉选择框帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Select extends Element
|
||||
{
|
||||
/**
|
||||
* 选择值
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
/**
|
||||
* 初始化当前输入项
|
||||
*
|
||||
* @param string|null $name 表单元素名称
|
||||
* @param array|null $options 选择项
|
||||
* @return Layout|null
|
||||
*/
|
||||
public function input(?string $name = null, ?array $options = null): ?Layout
|
||||
{
|
||||
$input = new Layout('select');
|
||||
$this->container($input->setAttribute('name', $name)
|
||||
->setAttribute('id', $name . '-0-' . self::$uniqueId));
|
||||
$this->label->setAttribute('for', $name . '-0-' . self::$uniqueId);
|
||||
$this->inputs[] = $input;
|
||||
|
||||
foreach ($options as $value => $label) {
|
||||
$this->options[$value] = new Layout('option');
|
||||
$input->addItem($this->options[$value]->setAttribute('value', $value)->html($label));
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单元素值
|
||||
*
|
||||
* @param mixed $value 表单元素值
|
||||
*/
|
||||
protected function inputValue($value)
|
||||
{
|
||||
foreach ($this->options as $option) {
|
||||
$option->removeAttribute('selected');
|
||||
}
|
||||
|
||||
if (isset($value) && isset($this->options[$value])) {
|
||||
$this->options[$value]->setAttribute('selected', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
48
var/Typecho/Widget/Helper/Form/Element/Submit.php
Executable file
48
var/Typecho/Widget/Helper/Form/Element/Submit.php
Executable file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交按钮表单项帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Submit extends Element
|
||||
{
|
||||
/**
|
||||
* 初始化当前输入项
|
||||
*
|
||||
* @param string|null $name 表单元素名称
|
||||
* @param array|null $options 选择项
|
||||
* @return Layout|null
|
||||
*/
|
||||
public function input(?string $name = null, ?array $options = null): ?Layout
|
||||
{
|
||||
$this->setAttribute('class', 'typecho-option typecho-option-submit');
|
||||
$input = new Layout('button', ['type' => 'submit']);
|
||||
$this->container($input);
|
||||
$this->inputs[] = $input;
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单元素值
|
||||
*
|
||||
* @param mixed $value 表单元素值
|
||||
*/
|
||||
protected function inputValue($value)
|
||||
{
|
||||
$this->input->html($value ?? 'Submit');
|
||||
}
|
||||
}
|
||||
39
var/Typecho/Widget/Helper/Form/Element/Text.php
Executable file
39
var/Typecho/Widget/Helper/Form/Element/Text.php
Executable file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文字输入表单项帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Text extends Element
|
||||
{
|
||||
use TextInputTrait;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function filterValue(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'text';
|
||||
}
|
||||
}
|
||||
59
var/Typecho/Widget/Helper/Form/Element/TextInputTrait.php
Executable file
59
var/Typecho/Widget/Helper/Form/Element/TextInputTrait.php
Executable file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
|
||||
trait TextInputTrait
|
||||
{
|
||||
/**
|
||||
* 初始化当前输入项
|
||||
*
|
||||
* @param string|null $name 表单元素名称
|
||||
* @param array|null $options 选择项
|
||||
* @return Layout|null
|
||||
*/
|
||||
public function input(?string $name = null, ?array $options = null): ?Layout
|
||||
{
|
||||
$input = new Layout('input', [
|
||||
'id' => $name . '-0-' . self::$uniqueId,
|
||||
'name' => $name,
|
||||
'type' => $this->getType(),
|
||||
'class' => 'text'
|
||||
]);
|
||||
|
||||
$this->container($input);
|
||||
$this->inputs[] = $input;
|
||||
|
||||
if (isset($this->label)) {
|
||||
$this->label->setAttribute('for', $name . '-0-' . self::$uniqueId);
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单项默认值
|
||||
*
|
||||
* @param mixed $value 表单项默认值
|
||||
*/
|
||||
protected function inputValue($value)
|
||||
{
|
||||
if (isset($value)) {
|
||||
$this->input->setAttribute('value', $this->filterValue($value));
|
||||
} else {
|
||||
$this->input->removeAttribute('value');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function filterValue(string $value): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getType(): string;
|
||||
}
|
||||
48
var/Typecho/Widget/Helper/Form/Element/Textarea.php
Executable file
48
var/Typecho/Widget/Helper/Form/Element/Textarea.php
Executable file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多行文字域帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Textarea extends Element
|
||||
{
|
||||
/**
|
||||
* 初始化当前输入项
|
||||
*
|
||||
* @param string|null $name 表单元素名称
|
||||
* @param array|null $options 选择项
|
||||
* @return Layout|null
|
||||
*/
|
||||
public function input(?string $name = null, ?array $options = null): ?Layout
|
||||
{
|
||||
$input = new Layout('textarea', ['id' => $name . '-0-' . self::$uniqueId, 'name' => $name]);
|
||||
$this->label->setAttribute('for', $name . '-0-' . self::$uniqueId);
|
||||
$this->container($input->setClose(false));
|
||||
$this->inputs[] = $input;
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单项默认值
|
||||
*
|
||||
* @param mixed $value 表单项默认值
|
||||
*/
|
||||
protected function inputValue($value)
|
||||
{
|
||||
$this->input->html(htmlspecialchars($value ?? ''));
|
||||
}
|
||||
}
|
||||
40
var/Typecho/Widget/Helper/Form/Element/Url.php
Executable file
40
var/Typecho/Widget/Helper/Form/Element/Url.php
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
use Typecho\Common;
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Url 表单项帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Url extends Element
|
||||
{
|
||||
use TextInputTrait;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function filterValue(string $value): string
|
||||
{
|
||||
return htmlspecialchars(Common::idnToUtf8($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'url';
|
||||
}
|
||||
}
|
||||
323
var/Typecho/Widget/Helper/Layout.php
Executable file
323
var/Typecho/Widget/Helper/Layout.php
Executable file
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper;
|
||||
|
||||
/**
|
||||
* HTML布局帮手类
|
||||
*
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Layout
|
||||
{
|
||||
/**
|
||||
* 元素列表
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $items = [];
|
||||
|
||||
/**
|
||||
* 表单属性列表
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $attributes = [];
|
||||
|
||||
/**
|
||||
* 标签名称
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $tagName = 'div';
|
||||
|
||||
/**
|
||||
* 是否自闭合
|
||||
*
|
||||
* @access private
|
||||
* @var boolean
|
||||
*/
|
||||
private bool $close = false;
|
||||
|
||||
/**
|
||||
* 是否强制自闭合
|
||||
*
|
||||
* @access private
|
||||
* @var boolean|null
|
||||
*/
|
||||
private ?bool $forceClose = null;
|
||||
|
||||
/**
|
||||
* 内部数据
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private string $html;
|
||||
|
||||
/**
|
||||
* 父节点
|
||||
*
|
||||
* @access private
|
||||
* @var Layout
|
||||
*/
|
||||
private $parent;
|
||||
|
||||
/**
|
||||
* 构造函数,设置标签名称
|
||||
*
|
||||
* @param string $tagName 标签名称
|
||||
* @param array|null $attributes 属性列表
|
||||
*
|
||||
*/
|
||||
public function __construct(string $tagName = 'div', ?array $attributes = null)
|
||||
{
|
||||
$this->setTagName($tagName);
|
||||
|
||||
if (!empty($attributes)) {
|
||||
foreach ($attributes as $attributeName => $attributeValue) {
|
||||
$this->setAttribute($attributeName, (string)$attributeValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单属性
|
||||
*
|
||||
* @param string $attributeName 属性名称
|
||||
* @param mixed $attributeValue 属性值
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttribute(string $attributeName, $attributeValue): Layout
|
||||
{
|
||||
$this->attributes[$attributeName] = (string) $attributeValue;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除元素
|
||||
*
|
||||
* @param Layout $item 元素
|
||||
* @return $this
|
||||
*/
|
||||
public function removeItem(Layout $item): Layout
|
||||
{
|
||||
unset($this->items[array_search($item, $this->items)]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* getItems
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getItems(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* getTagName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTagName(): string
|
||||
{
|
||||
return $this->tagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标签名
|
||||
*
|
||||
* @param string $tagName 标签名
|
||||
*/
|
||||
public function setTagName(string $tagName)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除某个属性
|
||||
*
|
||||
* @param string $attributeName 属性名称
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAttribute(string $attributeName): Layout
|
||||
{
|
||||
if (isset($this->attributes[$attributeName])) {
|
||||
unset($this->attributes[$attributeName]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $attributeName 属性名
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAttribute(string $attributeName): ?string
|
||||
{
|
||||
return $this->attributes[$attributeName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否自闭合
|
||||
*
|
||||
* @param boolean $close 是否自闭合
|
||||
* @return $this
|
||||
*/
|
||||
public function setClose(bool $close): Layout
|
||||
{
|
||||
$this->forceClose = $close;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父节点
|
||||
*
|
||||
* @return Layout
|
||||
*/
|
||||
public function getParent(): Layout
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置父节点
|
||||
*
|
||||
* @param Layout $parent 父节点
|
||||
* @return $this
|
||||
*/
|
||||
public function setParent(Layout $parent): Layout
|
||||
{
|
||||
$this->parent = $parent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加到某布局元素集合中
|
||||
*
|
||||
* @param Layout $parent 布局对象
|
||||
* @return $this
|
||||
*/
|
||||
public function appendTo(Layout $parent): Layout
|
||||
{
|
||||
$parent->addItem($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加元素
|
||||
*
|
||||
* @param Layout $item 元素
|
||||
* @return $this
|
||||
*/
|
||||
public function addItem(Layout $item): Layout
|
||||
{
|
||||
$item->setParent($this);
|
||||
$this->items[] = $item;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性
|
||||
*
|
||||
* @param string $name 属性名称
|
||||
* @return string|null
|
||||
*/
|
||||
public function __get(string $name): ?string
|
||||
{
|
||||
return $this->attributes[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性
|
||||
*
|
||||
* @param string $name 属性名称
|
||||
* @param string $value 属性值
|
||||
*/
|
||||
public function __set(string $name, string $value)
|
||||
{
|
||||
$this->attributes[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出所有元素
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
if (empty($this->items) && empty($this->html)) {
|
||||
$this->close = true;
|
||||
}
|
||||
|
||||
if (null !== $this->forceClose) {
|
||||
$this->close = $this->forceClose;
|
||||
}
|
||||
|
||||
$this->start();
|
||||
$this->html();
|
||||
$this->end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始标签
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
/** 输出标签 */
|
||||
echo $this->tagName ? "<{$this->tagName}" : null;
|
||||
|
||||
/** 输出属性 */
|
||||
foreach ($this->attributes as $attributeName => $attributeValue) {
|
||||
echo " {$attributeName}=\"{$attributeValue}\"";
|
||||
}
|
||||
|
||||
/** 支持自闭合 */
|
||||
if (!$this->close && $this->tagName) {
|
||||
echo ">\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内部数据
|
||||
*
|
||||
* @param string|null $html 内部数据
|
||||
* @return void|$this
|
||||
*/
|
||||
public function html(?string $html = null)
|
||||
{
|
||||
if (null === $html) {
|
||||
if (empty($this->html)) {
|
||||
foreach ($this->items as $item) {
|
||||
$item->render();
|
||||
}
|
||||
} else {
|
||||
echo $this->html;
|
||||
}
|
||||
} else {
|
||||
$this->html = $html;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束标签
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function end()
|
||||
{
|
||||
if ($this->tagName) {
|
||||
echo $this->close ? " />\n" : "</{$this->tagName}>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
115
var/Typecho/Widget/Helper/PageNavigator.php
Executable file
115
var/Typecho/Widget/Helper/PageNavigator.php
Executable file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper;
|
||||
|
||||
use Typecho\Widget\Exception;
|
||||
|
||||
/**
|
||||
* 内容分页抽象类
|
||||
*
|
||||
* @package Widget
|
||||
*/
|
||||
abstract class PageNavigator
|
||||
{
|
||||
/**
|
||||
* 记录总数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected int $total;
|
||||
|
||||
/**
|
||||
* 页面总数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected int $totalPage;
|
||||
|
||||
/**
|
||||
* 当前页面
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected int $currentPage;
|
||||
|
||||
/**
|
||||
* 每页内容数
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected int $pageSize;
|
||||
|
||||
/**
|
||||
* 页面链接模板
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $pageTemplate;
|
||||
|
||||
/**
|
||||
* 链接锚点
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $anchor = '';
|
||||
|
||||
/**
|
||||
* 页面占位符
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $pageHolder = ['{page}', '%7Bpage%7D'];
|
||||
|
||||
/**
|
||||
* 构造函数,初始化页面基本信息
|
||||
*
|
||||
* @param integer $total 记录总数
|
||||
* @param integer $currentPage 当前页面
|
||||
* @param integer $pageSize 每页记录数
|
||||
* @param string $pageTemplate 页面链接模板
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct(int $total, int $currentPage, int $pageSize, string $pageTemplate)
|
||||
{
|
||||
$this->total = $total;
|
||||
$this->totalPage = ceil($total / $pageSize);
|
||||
$this->currentPage = $currentPage;
|
||||
$this->pageSize = $pageSize;
|
||||
$this->pageTemplate = $pageTemplate;
|
||||
|
||||
if (($currentPage > $this->totalPage || $currentPage < 1) && $total > 0) {
|
||||
throw new Exception('Page Not Exists', 404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置页面占位符
|
||||
*
|
||||
* @param string $holder 页面占位符
|
||||
*/
|
||||
public function setPageHolder(string $holder)
|
||||
{
|
||||
$this->pageHolder = ['{' . $holder . '}',
|
||||
str_replace(['{', '}'], ['%7B', '%7D'], $holder)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置锚点
|
||||
*
|
||||
* @param string $anchor 锚点
|
||||
*/
|
||||
public function setAnchor(string $anchor)
|
||||
{
|
||||
$this->anchor = '#' . $anchor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出方法
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
throw new Exception('Method Not Implemented', 500);
|
||||
}
|
||||
}
|
||||
136
var/Typecho/Widget/Helper/PageNavigator/Box.php
Executable file
136
var/Typecho/Widget/Helper/PageNavigator/Box.php
Executable file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\PageNavigator;
|
||||
|
||||
use Typecho\Widget\Helper\PageNavigator;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 盒状分页样式
|
||||
*
|
||||
* @author qining
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Box extends PageNavigator
|
||||
{
|
||||
/**
|
||||
* 输出盒装样式分页栏
|
||||
*
|
||||
* @access public
|
||||
* @param string $prevWord 上一页文字
|
||||
* @param string $nextWord 下一页文字
|
||||
* @param int $splitPage 分割范围
|
||||
* @param string $splitWord 分割字符
|
||||
* @param array $template
|
||||
* @return void
|
||||
*/
|
||||
public function render(
|
||||
string $prevWord = 'PREV',
|
||||
string $nextWord = 'NEXT',
|
||||
int $splitPage = 3,
|
||||
string $splitWord = '...',
|
||||
array $template = []
|
||||
) {
|
||||
if ($this->total < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$default = [
|
||||
'itemTag' => 'li',
|
||||
'textTag' => 'span',
|
||||
'currentClass' => 'current',
|
||||
'prevClass' => 'prev',
|
||||
'nextClass' => 'next'
|
||||
];
|
||||
|
||||
$template = array_merge($default, $template);
|
||||
extract($template);
|
||||
|
||||
// 定义item
|
||||
$itemBegin = empty($itemTag) ? '' : ('<' . $itemTag . '>');
|
||||
$itemCurrentBegin = empty($itemTag) ? '' : ('<' . $itemTag
|
||||
. (empty($currentClass) ? '' : ' class="' . $currentClass . '"') . '>');
|
||||
$itemPrevBegin = empty($itemTag) ? '' : ('<' . $itemTag
|
||||
. (empty($prevClass) ? '' : ' class="' . $prevClass . '"') . '>');
|
||||
$itemNextBegin = empty($itemTag) ? '' : ('<' . $itemTag
|
||||
. (empty($nextClass) ? '' : ' class="' . $nextClass . '"') . '>');
|
||||
$itemEnd = empty($itemTag) ? '' : ('</' . $itemTag . '>');
|
||||
$textBegin = empty($textTag) ? '' : ('<' . $textTag . '>');
|
||||
$textEnd = empty($textTag) ? '' : ('</' . $textTag . '>');
|
||||
$linkBegin = '<a href="%s">';
|
||||
$linkCurrentBegin = empty($itemTag) ? ('<a href="%s"'
|
||||
. (empty($currentClass) ? '' : ' class="' . $currentClass . '"') . '>')
|
||||
: $linkBegin;
|
||||
$linkPrevBegin = empty($itemTag) ? ('<a href="%s"'
|
||||
. (empty($prevClass) ? '' : ' class="' . $prevClass . '"') . '>')
|
||||
: $linkBegin;
|
||||
$linkNextBegin = empty($itemTag) ? ('<a href="%s"'
|
||||
. (empty($nextClass) ? '' : ' class="' . $nextClass . '"') . '>')
|
||||
: $linkBegin;
|
||||
$linkEnd = '</a>';
|
||||
|
||||
$from = max(1, $this->currentPage - $splitPage);
|
||||
$to = min($this->totalPage, $this->currentPage + $splitPage);
|
||||
|
||||
//输出上一页
|
||||
if ($this->currentPage > 1) {
|
||||
echo $itemPrevBegin . sprintf(
|
||||
$linkPrevBegin,
|
||||
str_replace($this->pageHolder, $this->currentPage - 1, $this->pageTemplate) . $this->anchor
|
||||
)
|
||||
. $prevWord . $linkEnd . $itemEnd;
|
||||
}
|
||||
|
||||
//输出第一页
|
||||
if ($from > 1) {
|
||||
echo $itemBegin
|
||||
. sprintf($linkBegin, str_replace($this->pageHolder, 1, $this->pageTemplate) . $this->anchor)
|
||||
. '1' . $linkEnd . $itemEnd;
|
||||
|
||||
if ($from > 2) {
|
||||
//输出省略号
|
||||
echo $itemBegin . $textBegin . $splitWord . $textEnd . $itemEnd;
|
||||
}
|
||||
}
|
||||
|
||||
//输出中间页
|
||||
for ($i = $from; $i <= $to; $i++) {
|
||||
$current = ($i == $this->currentPage);
|
||||
|
||||
echo ($current ? $itemCurrentBegin : $itemBegin) . sprintf(
|
||||
($current ? $linkCurrentBegin : $linkBegin),
|
||||
str_replace($this->pageHolder, $i, $this->pageTemplate) . $this->anchor
|
||||
)
|
||||
. $i . $linkEnd . $itemEnd;
|
||||
}
|
||||
|
||||
//输出最后页
|
||||
if ($to < $this->totalPage) {
|
||||
if ($to < $this->totalPage - 1) {
|
||||
echo $itemBegin . $textBegin . $splitWord . $textEnd . $itemEnd;
|
||||
}
|
||||
|
||||
echo $itemBegin
|
||||
. sprintf(
|
||||
$linkBegin,
|
||||
str_replace($this->pageHolder, $this->totalPage, $this->pageTemplate) . $this->anchor
|
||||
)
|
||||
. $this->totalPage . $linkEnd . $itemEnd;
|
||||
}
|
||||
|
||||
//输出下一页
|
||||
if ($this->currentPage < $this->totalPage) {
|
||||
echo $itemNextBegin . sprintf(
|
||||
$linkNextBegin,
|
||||
str_replace($this->pageHolder, $this->currentPage + 1, $this->pageTemplate) . $this->anchor
|
||||
)
|
||||
. $nextWord . $linkEnd . $itemEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
var/Typecho/Widget/Helper/PageNavigator/Classic.php
Executable file
71
var/Typecho/Widget/Helper/PageNavigator/Classic.php
Executable file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget\Helper\PageNavigator;
|
||||
|
||||
use Typecho\Widget\Helper\PageNavigator;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 经典分页样式
|
||||
*
|
||||
* @author qining
|
||||
* @category typecho
|
||||
* @package Widget
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Classic extends PageNavigator
|
||||
{
|
||||
/**
|
||||
* 输出经典样式的分页
|
||||
*
|
||||
* @access public
|
||||
* @param string $prevWord 上一页文字
|
||||
* @param string $nextWord 下一页文字
|
||||
* @return void
|
||||
*/
|
||||
public function render(string $prevWord = 'PREV', string $nextWord = 'NEXT')
|
||||
{
|
||||
$this->prev($prevWord);
|
||||
$this->next($nextWord);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出上一页
|
||||
*
|
||||
* @access public
|
||||
* @param string $prevWord 上一页文字
|
||||
* @return void
|
||||
*/
|
||||
public function prev(string $prevWord = 'PREV')
|
||||
{
|
||||
//输出上一页
|
||||
if ($this->total > 0 && $this->currentPage > 1) {
|
||||
echo '<a class="prev" href="'
|
||||
. str_replace($this->pageHolder, $this->currentPage - 1, $this->pageTemplate)
|
||||
. $this->anchor . '">'
|
||||
. $prevWord . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出下一页
|
||||
*
|
||||
* @access public
|
||||
* @param string $nextWord 下一页文字
|
||||
* @return void
|
||||
*/
|
||||
public function next(string $nextWord = 'NEXT')
|
||||
{
|
||||
//输出下一页
|
||||
if ($this->total > 0 && $this->currentPage < $this->totalPage) {
|
||||
echo '<a class="next" title="" href="'
|
||||
. str_replace($this->pageHolder, $this->currentPage + 1, $this->pageTemplate)
|
||||
. $this->anchor . '">'
|
||||
. $nextWord . '</a>';
|
||||
}
|
||||
}
|
||||
}
|
||||
390
var/Typecho/Widget/Request.php
Executable file
390
var/Typecho/Widget/Request.php
Executable file
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget;
|
||||
|
||||
use Typecho\Config;
|
||||
use Typecho\Request as HttpRequest;
|
||||
|
||||
/**
|
||||
* Widget Request Wrapper
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* 支持的过滤器列表
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private const FILTERS = [
|
||||
'int' => 'intval',
|
||||
'integer' => 'intval',
|
||||
'encode' => 'urlencode',
|
||||
'html' => 'htmlspecialchars',
|
||||
'search' => ['\Typecho\Common', 'filterSearchQuery'],
|
||||
'xss' => ['\Typecho\Common', 'removeXSS'],
|
||||
'url' => ['\Typecho\Common', 'safeUrl'],
|
||||
'slug' => ['\Typecho\Common', 'slugName']
|
||||
];
|
||||
|
||||
/**
|
||||
* 当前过滤器
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private array $filter = [];
|
||||
|
||||
/**
|
||||
* @var HttpRequest
|
||||
*/
|
||||
private HttpRequest $request;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
private Config $params;
|
||||
|
||||
/**
|
||||
* @param HttpRequest $request
|
||||
* @param Config|null $params
|
||||
*/
|
||||
public function __construct(HttpRequest $request, ?Config $params = null)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->params = $params ?? new Config();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置http传递参数
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $name 指定的参数
|
||||
* @param mixed $value 参数值
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParam(string $name, $value)
|
||||
{
|
||||
$this->params[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置多个参数
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param mixed $params 参数列表
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParams($params)
|
||||
{
|
||||
$this->params->setDefault($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filter to request
|
||||
*
|
||||
* @param string|callable ...$filters
|
||||
* @return $this
|
||||
*/
|
||||
public function filter(...$filters): Request
|
||||
{
|
||||
foreach ($filters as $filter) {
|
||||
$this->filter[] = $this->wrapFilter(
|
||||
is_string($filter) && isset(self::FILTERS[$filter])
|
||||
? self::FILTERS[$filter] : $filter
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实际传递参数(magic)
|
||||
*
|
||||
* @deprecated ^1.3.0
|
||||
* @param string $key 指定参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数是否存在
|
||||
*
|
||||
* @deprecated ^1.3.0
|
||||
* @param string $key 指定参数
|
||||
* @return boolean
|
||||
*/
|
||||
public function __isset(string $key)
|
||||
{
|
||||
$this->get($key, null, $exists);
|
||||
return $exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param null $default
|
||||
* @param bool|null $exists detect exists
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key, $default = null, ?bool &$exists = true)
|
||||
{
|
||||
return $this->applyFilter($this->request->proxy($this->params)->get($key, $default, $exists));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return array
|
||||
*/
|
||||
public function getArray($key): array
|
||||
{
|
||||
return $this->applyFilter($this->request->proxy($this->params)->getArray($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ...$params
|
||||
* @return array
|
||||
*/
|
||||
public function from(...$params): array
|
||||
{
|
||||
return $this->applyFilter(call_user_func_array([$this->request->proxy($this->params), 'from'], $params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断输入是否满足要求
|
||||
*
|
||||
* @param mixed $query 条件
|
||||
* @return boolean
|
||||
*/
|
||||
public function is($query): bool
|
||||
{
|
||||
$result = $this->request->proxy($this->params)->is($query);
|
||||
$this->request->endProxy();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestRoot(): string
|
||||
{
|
||||
return $this->request->getRequestRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前完整的请求url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestUrl(): string
|
||||
{
|
||||
return $this->request->getRequestUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求资源地址
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRequestUri(): ?string
|
||||
{
|
||||
return $this->request->getRequestUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前pathinfo
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPathInfo(): ?string
|
||||
{
|
||||
return $this->request->getPathInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取url前缀
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUrlPrefix(): ?string
|
||||
{
|
||||
return $this->request->getUrlPrefix();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前uri构造指定参数的uri
|
||||
*
|
||||
* @param mixed $parameter 指定的参数
|
||||
* @return string
|
||||
*/
|
||||
public function makeUriByRequest($parameter = null): string
|
||||
{
|
||||
return $this->request->makeUriByRequest($parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的内容类型
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getContentType(): ?string
|
||||
{
|
||||
return $this->request->getContentType();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取环境变量
|
||||
*
|
||||
* @param string $name 获取环境变量名
|
||||
* @param string|null $default
|
||||
* @return string|null
|
||||
*/
|
||||
public function getServer(string $name, ?string $default = null): ?string
|
||||
{
|
||||
return $this->request->getServer($name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ip地址
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIp(): string
|
||||
{
|
||||
return $this->request->getIp();
|
||||
}
|
||||
|
||||
/**
|
||||
* get header value
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|null $default
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHeader(string $key, ?string $default = null): ?string
|
||||
{
|
||||
return $this->request->getHeader($key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAgent(): ?string
|
||||
{
|
||||
return $this->request->getAgent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getReferer(): ?string
|
||||
{
|
||||
return $this->request->getReferer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为https
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSecure(): bool
|
||||
{
|
||||
return $this->request->isSecure();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为get方法
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isGet(): bool
|
||||
{
|
||||
return $this->request->isGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为post方法
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isPost(): bool
|
||||
{
|
||||
return $this->request->isPost();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为put方法
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isPut(): bool
|
||||
{
|
||||
return $this->request->isPut();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为ajax
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAjax(): bool
|
||||
{
|
||||
return $this->request->isAjax();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为json
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isJson(): bool
|
||||
{
|
||||
return $this->request->isJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用过滤器
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function applyFilter($value)
|
||||
{
|
||||
if ($this->filter) {
|
||||
foreach ($this->filter as $filter) {
|
||||
$value = is_array($value) ? array_map($filter, $value) :
|
||||
call_user_func($filter, $value);
|
||||
}
|
||||
|
||||
$this->filter = [];
|
||||
}
|
||||
|
||||
$this->request->endProxy();
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a filter to make sure it always receives a string.
|
||||
*
|
||||
* @param callable $filter
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
private function wrapFilter(callable $filter): callable
|
||||
{
|
||||
return function ($value) use ($filter) {
|
||||
return call_user_func($filter, $value ?? '');
|
||||
};
|
||||
}
|
||||
}
|
||||
245
var/Typecho/Widget/Response.php
Executable file
245
var/Typecho/Widget/Response.php
Executable file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget;
|
||||
|
||||
use Typecho\Common;
|
||||
use Typecho\Request as HttpRequest;
|
||||
use Typecho\Response as HttpResponse;
|
||||
|
||||
/**
|
||||
* Widget Response Wrapper
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* @var HttpRequest
|
||||
*/
|
||||
private HttpRequest $request;
|
||||
|
||||
/**
|
||||
* @var HttpResponse
|
||||
*/
|
||||
private HttpResponse $response;
|
||||
|
||||
/**
|
||||
* @param HttpRequest $request
|
||||
* @param HttpResponse $response
|
||||
*/
|
||||
public function __construct(HttpRequest $request, HttpResponse $response)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $code
|
||||
* @return $this
|
||||
*/
|
||||
public function setStatus(int $code): Response
|
||||
{
|
||||
$this->response->setStatus($code);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeader(string $name, $value): Response
|
||||
{
|
||||
$this->response->setHeader($name, (string)$value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认回执编码
|
||||
*
|
||||
* @param string $charset 字符集
|
||||
* @return $this
|
||||
*/
|
||||
public function setCharset(string $charset): Response
|
||||
{
|
||||
$this->response->setCharset($charset);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contentType
|
||||
* @return $this
|
||||
*/
|
||||
public function setContentType(string $contentType = 'text/html'): Response
|
||||
{
|
||||
$this->response->setContentType($contentType);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function throwCallback(callable $callback, string $contentType = 'text/html')
|
||||
{
|
||||
$this->response->setContentType($contentType)
|
||||
->addResponder($callback)
|
||||
->respond();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function throwFinish()
|
||||
{
|
||||
$isFastCGI = function_exists('fastcgi_finish_request');
|
||||
|
||||
if (!$isFastCGI) {
|
||||
ob_end_clean();
|
||||
ob_start();
|
||||
$this->setHeader('Connection', 'close');
|
||||
$this->setHeader('Content-Encoding', 'none');
|
||||
$this->setHeader('Content-Length', ob_get_length());
|
||||
}
|
||||
|
||||
$this->response->sendHeaders();
|
||||
|
||||
if (!$isFastCGI) {
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else {
|
||||
fastcgi_finish_request();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function throwContent(string $content, string $contentType = 'text/html')
|
||||
{
|
||||
$this->throwCallback(function () use ($content) {
|
||||
echo $content;
|
||||
}, $contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
*/
|
||||
public function throwXml($message)
|
||||
{
|
||||
$this->throwCallback(function () use ($message) {
|
||||
echo '<?xml version="1.0" encoding="' . $this->response->getCharset() . '"?>',
|
||||
'<response>',
|
||||
$this->parseXml($message),
|
||||
'</response>';
|
||||
}, 'text/xml');
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出json回执信息
|
||||
*
|
||||
* @param mixed $message 消息体
|
||||
*/
|
||||
public function throwJson($message)
|
||||
{
|
||||
$this->throwCallback(function () use ($message) {
|
||||
echo json_encode($message);
|
||||
}, 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $file
|
||||
* @param string|null $contentType
|
||||
*/
|
||||
public function throwFile($file, ?string $contentType = null)
|
||||
{
|
||||
if (!empty($contentType)) {
|
||||
$this->response->setContentType($contentType);
|
||||
}
|
||||
|
||||
$this->response->setHeader('Content-Length', filesize($file))
|
||||
->addResponder(function () use ($file) {
|
||||
readfile($file);
|
||||
})
|
||||
->respond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定向函数
|
||||
*
|
||||
* @param string $location 重定向路径
|
||||
* @param boolean $isPermanently 是否为永久重定向
|
||||
*/
|
||||
public function redirect(string $location, bool $isPermanently = false)
|
||||
{
|
||||
$location = Common::safeUrl($location);
|
||||
|
||||
$this->response->setStatus($isPermanently ? 301 : 302)
|
||||
->setHeader('Location', $location)
|
||||
->respond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回来路
|
||||
*
|
||||
* @param string|null $suffix 附加地址
|
||||
* @param string|null $default 默认来路
|
||||
*/
|
||||
public function goBack(?string $suffix = null, ?string $default = null)
|
||||
{
|
||||
//获取来源
|
||||
$referer = $this->request->getReferer();
|
||||
|
||||
//判断来源
|
||||
if (!empty($referer)) {
|
||||
// ~ fix Issue 38
|
||||
if (!empty($suffix)) {
|
||||
$parts = parse_url($referer);
|
||||
$myParts = parse_url($suffix);
|
||||
|
||||
if (isset($myParts['fragment'])) {
|
||||
$parts['fragment'] = $myParts['fragment'];
|
||||
}
|
||||
|
||||
if (isset($myParts['query'])) {
|
||||
$args = [];
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
parse_str($myParts['query'], $currentArgs);
|
||||
$args = array_merge($args, $currentArgs);
|
||||
$parts['query'] = http_build_query($args);
|
||||
}
|
||||
|
||||
$referer = Common::buildUrl($parts);
|
||||
}
|
||||
|
||||
$this->redirect($referer);
|
||||
} else {
|
||||
$this->redirect($default ?: '/');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析ajax回执的内部函数
|
||||
*
|
||||
* @param mixed $message 格式化数据
|
||||
* @return string
|
||||
*/
|
||||
private function parseXml($message): string
|
||||
{
|
||||
/** 对于数组型则继续递归 */
|
||||
if (is_array($message)) {
|
||||
$result = '';
|
||||
|
||||
foreach ($message as $key => $val) {
|
||||
$tagName = is_int($key) ? 'item' : $key;
|
||||
$result .= '<' . $tagName . '>' . $this->parseXml($val) . '</' . $tagName . '>';
|
||||
}
|
||||
|
||||
return $result;
|
||||
} else {
|
||||
return preg_match("/^[^<>]+$/is", $message) ? $message : '<![CDATA[' . $message . ']]>';
|
||||
}
|
||||
}
|
||||
}
|
||||
14
var/Typecho/Widget/Terminal.php
Executable file
14
var/Typecho/Widget/Terminal.php
Executable file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Widget;
|
||||
|
||||
if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special exception to break executor
|
||||
*/
|
||||
class Terminal extends Exception
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user