This commit is contained in:
root
2026-03-04 00:23:03 +08:00
commit 6136d791f2
611 changed files with 65539 additions and 0 deletions

204
var/Utils/AutoP.php Executable file
View File

@@ -0,0 +1,204 @@
<?php
namespace Utils;
/**
* AutoP
*
* @copyright Copyright (c) 2012 Typecho Team. (http://typecho.org)
* @author Joyqi <magike.net@gmail.com>
* @license GNU General Public License 2.0
*/
class AutoP
{
// 作为段落的标签
private const BLOCK = 'p|pre|div|blockquote|form|ul|ol|dd|table|ins|h1|h2|h3|h4|h5|h6';
/**
* 唯一id
*
* @access private
* @var integer
*/
private int $uniqueId = 0;
/**
* 存储的段落
*
* @access private
* @var array
*/
private array $blocks = [];
/**
* 替换段落的回调函数
*
* @param array $matches 匹配值
* @return string
*/
public function replaceBlockCallback(array $matches): string
{
$tagMatch = '|' . $matches[1] . '|';
$text = $matches[4];
switch (true) {
/** 用br处理换行 */
case false !== strpos(
'|li|dd|dt|td|p|a|span|cite|strong|sup|sub|small|del|u|i|b|ins|h1|h2|h3|h4|h5|h6|',
$tagMatch
):
$text = nl2br(trim($text));
break;
/** 用段落处理换行 */
case false !== strpos('|div|blockquote|form|', $tagMatch):
$text = $this->cutByBlock($text);
if (false !== strpos($text, '</p><p>')) {
$text = $this->fixParagraph($text);
}
break;
default:
break;
}
/** 没有段落能力的标签 */
if (false !== strpos('|a|span|font|code|cite|strong|sup|sub|small|del|u|i|b|', $tagMatch)) {
$key = '<b' . $matches[2] . '/>';
} else {
$key = '<p' . $matches[2] . '/>';
}
$this->blocks[$key] = "<{$matches[1]}{$matches[3]}>{$text}</{$matches[1]}>";
return $key;
}
/**
* 用段落方法处理换行
*
* @param string $text
* @return string
*/
private function cutByBlock(string $text): string
{
$space = "( | )";
$text = str_replace("\r\n", "\n", trim($text));
$text = preg_replace("/{$space}*\n{$space}*/is", "\n", $text);
$text = preg_replace("/\s*<p:([0-9]{4})\/>\s*/is", "</p><p:\\1/><p>", $text);
$text = preg_replace("/\n{2,}/", "</p><p>", $text);
$text = nl2br($text);
$text = preg_replace("/(<p>)?\s*<p:([0-9]{4})\/>\s*(<\/p>)?/is", "<p:\\2/>", $text);
$text = preg_replace("/<p>{$space}*<\/p>/is", '', $text);
$text = preg_replace("/\s*<p>\s*$/is", '', $text);
$text = preg_replace("/^\s*<\/p>\s*/is", '', $text);
return $text;
}
/**
* 修复段落开头和结尾
*
* @param string $text
* @return string
*/
private function fixParagraph(string $text): string
{
$text = trim($text);
if (!preg_match("/^<(" . self::BLOCK . ")(\s|>)/i", $text)) {
$text = '<p>' . $text;
}
if (!preg_match("/<\/(" . self::BLOCK . ")>$/i", $text)) {
$text = $text . '</p>';
}
return $text;
}
/**
* 自动分段
*
* @param string $text
* @return string
*/
public function parse(string $text): string
{
/** 重置计数器 */
$this->uniqueId = 0;
$this->blocks = [];
/** 将已有的段落后面的换行处理掉 */
$text = preg_replace(["/<\/p>\s+<p(\s*)/is", "/\s*<br\s*\/?>\s*/is"], ["</p><p\\1", "<br />"], trim($text));
/** 将所有非自闭合标签解析为唯一的字符串 */
$foundTagCount = 0;
$textLength = strlen($text);
$uniqueIdList = [];
if (preg_match_all("/<\/\s*([a-z0-9]+)>/is", $text, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[0] as $key => $match) {
$tag = $matches[1][$key][0];
$leftOffset = $match[1] - $textLength;
$posSingle = strrpos($text, '<' . $tag . '>', $leftOffset);
$posFix = strrpos($text, '<' . $tag . ' ', $leftOffset);
$pos = false;
switch (true) {
case (false !== $posSingle && false !== $posFix):
$pos = max($posSingle, $posFix);
break;
case false === $posSingle && false !== $posFix:
$pos = $posFix;
break;
case false !== $posSingle && false === $posFix:
$pos = $posSingle;
break;
default:
break;
}
if (false !== $pos) {
$uniqueId = $this->makeUniqueId();
$uniqueIdList[$uniqueId] = $tag;
$tagLength = strlen($tag);
$text = substr_replace($text, $uniqueId, $pos + 1 + $tagLength, 0);
$text = substr_replace(
$text,
$uniqueId,
$match[1] + 7 + $foundTagCount * 10 + $tagLength,
0
); // 7 = 5 + 2
$foundTagCount++;
}
}
}
foreach ($uniqueIdList as $uniqueId => $tag) {
$text = preg_replace_callback(
"/<({$tag})({$uniqueId})([^>]*)>(.*)<\/\\1\\2>/is",
[$this, 'replaceBlockCallback'],
$text,
1
);
}
$text = $this->cutByBlock($text);
$blocks = array_reverse($this->blocks);
foreach ($blocks as $blockKey => $blockValue) {
$text = str_replace($blockKey, $blockValue, $text);
}
return $this->fixParagraph($text);
}
/**
* 生成唯一的id, 为了速度考虑最多支持1万个tag的处理
*
* @return string
*/
private function makeUniqueId(): string
{
return ':' . str_pad($this->uniqueId ++, 4, '0', STR_PAD_LEFT);
}
}

499
var/Utils/Helper.php Executable file
View File

@@ -0,0 +1,499 @@
<?php
namespace Utils;
use Typecho\Common;
use Typecho\Db;
use Typecho\I18n;
use Typecho\Plugin;
use Typecho\Widget;
use Widget\Base\Options as BaseOptions;
use Widget\Options;
use Widget\Plugins\Edit;
use Widget\Security;
use Widget\Service;
/**
* 插件帮手将默认出现在所有的typecho发行版中.
* 因此你可以放心使用它的功能, 以方便你的插件安装在用户的系统里.
*
* @package Helper
* @author qining
* @version 1.0.0
* @link http://typecho.org
*/
class Helper
{
/**
* 获取Security对象
*
* @return Security
*/
public static function security(): Security
{
return Security::alloc();
}
/**
* 根据ID获取单个Widget对象
*
* @param string $table 表名, 支持 contents, comments, metas, users
* @param int $pkId
* @return Widget|null
*/
public static function widgetById(string $table, int $pkId): ?Widget
{
$table = ucfirst($table);
if (!in_array($table, ['Contents', 'Comments', 'Metas', 'Users'])) {
return null;
}
$keys = [
'Contents' => 'cid',
'Comments' => 'coid',
'Metas' => 'mid',
'Users' => 'uid'
];
$className = '\Widget\Base\\' . $table;
$key = $keys[$table];
$db = Db::get();
$widget = Widget::widget($className . '@' . $pkId);
$db->fetchRow(
$widget->select()->where("{$key} = ?", $pkId)->limit(1),
[$widget, 'push']
);
return $widget;
}
/**
* 请求异步服务
*
* @param $method
* @param $params
*/
public static function requestService($method, ... $params)
{
Service::alloc()->requestService($method, ... $params);
}
/**
* 强行删除某个插件
*
* @param string $pluginName 插件名称
*/
public static function removePlugin(string $pluginName)
{
try {
/** 获取插件入口 */
[$pluginFileName, $className] = Plugin::portal(
$pluginName,
__TYPECHO_ROOT_DIR__ . '/' . __TYPECHO_PLUGIN_DIR__
);
/** 获取已启用插件 */
$plugins = Plugin::export();
$activatedPlugins = $plugins['activated'];
/** 载入插件 */
require_once $pluginFileName;
/** 判断实例化是否成功 */
if (
!isset($activatedPlugins[$pluginName]) || !class_exists($className)
|| !method_exists($className, 'deactivate')
) {
throw new Widget\Exception(_t('无法禁用插件'), 500);
}
call_user_func([$className, 'deactivate']);
} catch (\Exception $e) {
//nothing to do
}
$db = Db::get();
try {
Plugin::deactivate($pluginName);
self::setOption('plugins', Plugin::export());
} catch (Plugin\Exception $e) {
//nothing to do
}
$db->query($db->delete('table.options')->where('name = ?', 'plugin:' . $pluginName));
}
/**
* 导入语言项
*
* @param string $domain
*/
public static function lang(string $domain)
{
$currentLang = I18n::getLang();
if ($currentLang) {
$currentLang = basename($currentLang);
$fileName = dirname(__FILE__) . '/' . $domain . '/lang/' . $currentLang;
if (file_exists($fileName)) {
I18n::addLang($fileName);
}
}
}
/**
* 获取Options对象
*
* @return Options
*/
public static function options(): Options
{
return Options::alloc();
}
/**
* @param string $name
* @param $value
* @return int
*/
public static function setOption(string $name, $value): int
{
$options = self::options();
$options->{$name} = $value;
return BaseOptions::alloc()->update(
['value' => is_array($value) ? json_encode($value) : $value],
Db::get()->sql()->where('name = ?', $name)
);
}
/**
* 增加路由
*
* @param string $name 路由名称
* @param string $url 路由路径
* @param string $widget 组件名称
* @param string|null $action 组件动作
* @param string|null $after 在某个路由后面
* @return integer
*/
public static function addRoute(
string $name,
string $url,
string $widget,
?string $action = null,
?string $after = null
): int {
$routingTable = self::options()->routingTable;
if (isset($routingTable[0])) {
unset($routingTable[0]);
}
$pos = 0;
foreach ($routingTable as $key => $val) {
$pos++;
if ($key == $after) {
break;
}
}
$pre = array_slice($routingTable, 0, $pos);
$next = array_slice($routingTable, $pos);
$routingTable = array_merge($pre, [
$name => [
'url' => $url,
'widget' => $widget,
'action' => $action
]
], $next);
return self::setOption('routingTable', $routingTable);
}
/**
* 移除路由
*
* @param string $name 路由名称
* @return integer
*/
public static function removeRoute(string $name): int
{
$routingTable = self::options()->routingTable;
if (isset($routingTable[0])) {
unset($routingTable[0]);
}
unset($routingTable[$name]);
return self::setOption('routingTable', $routingTable);
}
/**
* 增加action扩展
*
* @param string $actionName 需要扩展的action名称
* @param string $widgetName 需要扩展的widget名称
* @return integer
*/
public static function addAction(string $actionName, string $widgetName): int
{
$actionTable = self::options()->actionTable;
$actionTable = empty($actionTable) ? [] : $actionTable;
$actionTable[$actionName] = $widgetName;
return self::setOption('actionTable', $actionTable);
}
/**
* 删除action扩展
*
* @param string $actionName
* @return int
*/
public static function removeAction(string $actionName): int
{
$actionTable = self::options()->actionTable;
$actionTable = empty($actionTable) ? [] : $actionTable;
if (isset($actionTable[$actionName])) {
unset($actionTable[$actionName]);
reset($actionTable);
}
return self::setOption('actionTable', $actionTable);
}
/**
* 增加一个菜单
*
* @param string $menuName 菜单名
* @return integer
*/
public static function addMenu(string $menuName): int
{
$panelTable = self::options()->panelTable;
$panelTable['parent'] = empty($panelTable['parent']) ? [] : $panelTable['parent'];
$panelTable['parent'][] = $menuName;
self::setOption('panelTable', $panelTable);
end($panelTable['parent']);
return key($panelTable['parent']) + 10;
}
/**
* 移除一个菜单
*
* @param string $menuName 菜单名
* @return integer
*/
public static function removeMenu(string $menuName): int
{
$panelTable = self::options()->panelTable;
$panelTable['parent'] = empty($panelTable['parent']) ? [] : $panelTable['parent'];
if (false !== ($index = array_search($menuName, $panelTable['parent']))) {
unset($panelTable['parent'][$index]);
}
self::setOption('panelTable', $panelTable);
return $index + 10;
}
/**
* 增加一个面板
*
* @param integer $index 菜单索引
* @param string $fileName 文件名称
* @param string $title 面板标题
* @param string $subTitle 面板副标题
* @param string $level 进入权限
* @param boolean $hidden 是否隐藏
* @param string $addLink 新增项目链接, 会显示在页面标题之后
* @return integer
*/
public static function addPanel(
int $index,
string $fileName,
string $title,
string $subTitle,
string $level,
bool $hidden = false,
string $addLink = ''
): int {
$panelTable = self::options()->panelTable;
$panelTable['child'] = empty($panelTable['child']) ? [] : $panelTable['child'];
$panelTable['child'][$index] = empty($panelTable['child'][$index]) ? [] : $panelTable['child'][$index];
$fileName = urlencode(trim($fileName, '/'));
$panelTable['child'][$index][]
= [$title, $subTitle, 'extending.php?panel=' . $fileName, $level, $hidden, $addLink];
$panelTable['file'] = empty($panelTable['file']) ? [] : $panelTable['file'];
$panelTable['file'][] = $fileName;
$panelTable['file'] = array_unique($panelTable['file']);
self::setOption('panelTable', $panelTable);
end($panelTable['child'][$index]);
return key($panelTable['child'][$index]);
}
/**
* 移除一个面板
*
* @param integer $index 菜单索引
* @param string $fileName 文件名称
* @return integer
*/
public static function removePanel(int $index, string $fileName): int
{
$panelTable = self::options()->panelTable;
$panelTable['child'] = empty($panelTable['child']) ? [] : $panelTable['child'];
$panelTable['child'][$index] = empty($panelTable['child'][$index]) ? [] : $panelTable['child'][$index];
$panelTable['file'] = empty($panelTable['file']) ? [] : $panelTable['file'];
$fileName = urlencode(trim($fileName, '/'));
if (false !== ($key = array_search($fileName, $panelTable['file']))) {
unset($panelTable['file'][$key]);
}
$return = 0;
foreach ($panelTable['child'][$index] as $key => $val) {
if ($val[2] == 'extending.php?panel=' . $fileName) {
unset($panelTable['child'][$index][$key]);
$return = $key;
}
}
self::setOption('panelTable', $panelTable);
return $return;
}
/**
* 获取面板url
*
* @param string $fileName
* @return string
*/
public static function url(string $fileName): string
{
return Common::url('extending.php?panel=' . (trim($fileName, '/')), self::options()->adminUrl);
}
/**
* 手动配置插件变量
*
* @param mixed $pluginName 插件名称
* @param array $settings 变量键值对
* @param bool $isPersonal . (default: false) 是否为私人变量
*/
public static function configPlugin($pluginName, array $settings, bool $isPersonal = false)
{
if (empty($settings)) {
return;
}
Edit::configPlugin($pluginName, $settings, $isPersonal);
}
/**
* 评论回复按钮
*
* @access public
* @param string $theId 评论元素id
* @param integer $coid 评论id
* @param string $word 按钮文字
* @param string $formId 表单id
* @param integer $style 样式类型
* @return void
*/
public static function replyLink(
string $theId,
int $coid,
string $word = 'Reply',
string $formId = 'respond',
int $style = 2
) {
if (self::options()->commentsThreaded) {
echo '<a href="#' . $formId . '" rel="nofollow" onclick="return typechoAddCommentReply(\'' .
$theId . '\', ' . $coid . ', \'' . $formId . '\', ' . $style . ');">' . $word . '</a>';
}
}
/**
* 评论取消按钮
*
* @param string $word 按钮文字
* @param string $formId 表单id
*/
public static function cancelCommentReplyLink(string $word = 'Cancel', string $formId = 'respond')
{
if (self::options()->commentsThreaded) {
echo '<a href="#' . $formId . '" rel="nofollow" onclick="return typechoCancelCommentReply(\'' .
$formId . '\');">' . $word . '</a>';
}
}
/**
* 评论回复js脚本
*/
public static function threadedCommentsScript()
{
if (self::options()->commentsThreaded) {
echo
<<<EOF
<script type="text/javascript">
var typechoAddCommentReply = function (cid, coid, cfid, style) {
var _ce = document.getElementById(cid), _cp = _ce.parentNode;
var _cf = document.getElementById(cfid);
var _pi = document.getElementById('comment-parent');
if (null == _pi) {
_pi = document.createElement('input');
_pi.setAttribute('type', 'hidden');
_pi.setAttribute('name', 'parent');
_pi.setAttribute('id', 'comment-parent');
var _form = 'form' == _cf.tagName ? _cf : _cf.getElementsByTagName('form')[0];
_form.appendChild(_pi);
}
_pi.setAttribute('value', coid);
if (null == document.getElementById('comment-form-place-holder')) {
var _cfh = document.createElement('div');
_cfh.setAttribute('id', 'comment-form-place-holder');
_cf.parentNode.insertBefore(_cfh, _cf);
}
1 == style ? (null == _ce.nextSibling ? _cp.appendChild(_cf)
: _cp.insertBefore(_cf, _ce.nextSibling)) : _ce.appendChild(_cf);
return false;
};
var typechoCancelCommentReply = function (cfid) {
var _cf = document.getElementById(cfid),
_cfh = document.getElementById('comment-form-place-holder');
var _pi = document.getElementById('comment-parent');
if (null != _pi) {
_pi.parentNode.removeChild(_pi);
}
if (null == _cfh) {
return true;
}
_cfh.parentNode.insertBefore(_cf, _cfh);
return false;
};
</script>
EOF;
}
}
}

1849
var/Utils/HyperDown.php Executable file

File diff suppressed because it is too large Load Diff

37
var/Utils/Markdown.php Executable file
View File

@@ -0,0 +1,37 @@
<?php
namespace Utils;
/**
* Markdown解析
*
* @package Markdown
* @copyright Copyright (c) 2014 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
*/
class Markdown
{
/**
* convert
*
* @param string $text
* @return string
*/
public static function convert(string $text): string
{
static $parser;
if (empty($parser)) {
$parser = new HyperDown();
$parser->hook('afterParseCode', function ($html) {
return preg_replace("/<code class=\"([_a-z0-9-]+)\">/i", "<code class=\"lang-\\1\">", $html);
});
$parser->enableHtml(true);
}
return str_replace('<p><!--more--></p>', '<!--more-->', $parser->makeHtml($text));
}
}

294
var/Utils/PasswordHash.php Executable file
View File

@@ -0,0 +1,294 @@
<?php
namespace Utils;
/**
* Portable PHP password hashing framework.
*
* @package phpass
* @version 0.2 / genuine.
* @link http://www.openwall.com/phpass/
* @since 2.5
*/
class PasswordHash
{
private string $itoa64;
private int $iteration_count_log2;
private bool $portable_hashes;
private string $random_state;
/**
* @param int $iteration_count_log2
* @param bool $portable_hashes
*/
public function __construct(int $iteration_count_log2, bool $portable_hashes)
{
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
$iteration_count_log2 = 8;
}
$this->iteration_count_log2 = $iteration_count_log2;
$this->portable_hashes = $portable_hashes;
$this->random_state = microtime() . uniqid(rand(), true); // removed getmypid() for compability reasons
}
/**
* @param string $password
* @return string
*/
public function hashPassword(string $password): string
{
$random = '';
if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
$random = $this->getRandomBytes(16);
$hash =
crypt($password, $this->gensaltBlowfish($random));
if (strlen($hash) == 60) {
return $hash;
}
}
if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
if (strlen($random) < 3) {
$random = $this->getRandomBytes(3);
}
$hash =
crypt($password, $this->gensaltExtended($random));
if (strlen($hash) == 20) {
return $hash;
}
}
if (strlen($random) < 6) {
$random = $this->getRandomBytes(6);
}
$hash = $this->cryptPrivate($password, $this->gensaltPrivate($random));
if (strlen($hash) == 34) {
return $hash;
}
# Returning '*' on error is safe here, but would _not_ be safe
# in a crypt(3)-like function used _both_ for generating new
# hashes and for validating passwords against existing hashes.
return '*';
}
/**
* @param int $count
* @return string
*/
private function getRandomBytes(int $count): string
{
$output = '';
if (@is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) {
$output = fread($fh, $count);
fclose($fh);
}
if (strlen($output) < $count) {
$output = '';
for ($i = 0; $i < $count; $i += 16) {
$this->random_state =
md5(microtime() . $this->random_state);
$output .=
pack('H*', md5($this->random_state));
}
$output = substr($output, 0, $count);
}
return $output;
}
/**
* @param string $input
* @return string
*/
private function gensaltBlowfish(string $input): string
{
# This one needs to use a different order of characters and a
# different encoding scheme from the one in encode64() above.
# We care because the last character in our encoded string will
# only represent 2 bits. While two known implementations of
# bcrypt will happily accept and correct a salt string which
# has the 4 unused bits set to non-zero, we do not want to take
# chances and we also do not want to waste an additional byte
# of entropy.
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$output = '$2a$';
$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
$output .= '$';
$i = 0;
do {
$c1 = ord($input[$i++]);
$output .= $itoa64[$c1 >> 2];
$c1 = ($c1 & 0x03) << 4;
if ($i >= 16) {
$output .= $itoa64[$c1];
break;
}
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 4;
$output .= $itoa64[$c1];
$c1 = ($c2 & 0x0f) << 2;
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 6;
$output .= $itoa64[$c1];
$output .= $itoa64[$c2 & 0x3f];
} while (1);
return $output;
}
/**
* @param string $input
* @return string
*/
private function gensaltExtended(string $input): string
{
$count_log2 = min($this->iteration_count_log2 + 8, 24);
# This should be odd to not reveal weak DES keys, and the
# maximum valid value is (2**24 - 1) which is odd anyway.
$count = (1 << $count_log2) - 1;
$output = '_';
$output .= $this->itoa64[$count & 0x3f];
$output .= $this->itoa64[($count >> 6) & 0x3f];
$output .= $this->itoa64[($count >> 12) & 0x3f];
$output .= $this->itoa64[($count >> 18) & 0x3f];
$output .= $this->encode64($input, 3);
return $output;
}
/**
* @param string $input
* @param int $count
* @return string
*/
private function encode64(string $input, int $count): string
{
$output = '';
$i = 0;
do {
$value = ord($input[$i++]);
$output .= $this->itoa64[$value & 0x3f];
if ($i < $count) {
$value |= ord($input[$i]) << 8;
}
$output .= $this->itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= ord($input[$i]) << 16;
}
$output .= $this->itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count) {
break;
}
$output .= $this->itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
/**
* @param string $password
* @param string $setting
* @return string
*/
private function cryptPrivate(string $password, string $setting): string
{
$output = '*0';
if (substr($setting, 0, 2) == $output) {
$output = '*1';
}
if (substr($setting, 0, 3) != '$P$') {
return $output;
}
$count_log2 = strpos($this->itoa64, $setting[3]);
if ($count_log2 < 7 || $count_log2 > 30) {
return $output;
}
$count = 1 << $count_log2;
$salt = substr($setting, 4, 8);
if (strlen($salt) != 8) {
return $output;
}
# We're kind of forced to use MD5 here since it's the only
# cryptographic primitive available in all versions of PHP
# currently in use. To implement our own low-level crypto
# in PHP would result in much worse performance and
# consequently in lower iteration counts and hashes that are
# quicker to crack (by non-PHP code).
if (PHP_VERSION >= '5') {
$hash = md5($salt . $password, true);
do {
$hash = md5($hash . $password, true);
} while (--$count);
} else {
$hash = pack('H*', md5($salt . $password));
do {
$hash = pack('H*', md5($hash . $password));
} while (--$count);
}
$output = substr($setting, 0, 12);
$output .= $this->encode64($hash, 16);
return $output;
}
/**
* @param string $input
* @return string
*/
private function gensaltPrivate(string $input): string
{
$output = '$P$';
$output .= $this->itoa64[min($this->iteration_count_log2 +
((PHP_VERSION >= '5') ? 5 : 3), 30)];
$output .= $this->encode64($input, 6);
return $output;
}
/**
* @param string $password
* @param string $stored_hash
* @return bool
*/
public function checkPassword(string $password, string $stored_hash): bool
{
$hash = $this->cryptPrivate($password, $stored_hash);
if ($hash[0] == '*') {
$hash = crypt($password, $stored_hash);
}
return $hash == $stored_hash;
}
}

98
var/Utils/Upgrade.php Executable file
View File

@@ -0,0 +1,98 @@
<?php
namespace Utils;
use Typecho\Db;
use Widget\Options;
/**
* 升级程序
*
* @category typecho
* @package Upgrade
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
*/
class Upgrade
{
/**
* @param Db $db
* @param Options $options
*/
public static function v1_3_0(Db $db, Options $options)
{
$routingTable = $options->routingTable;
$routingTable['comment_page'] = [
'url' => '[permalink:string]/comment-page-[commentPage:digital]',
'widget' => '\Widget\CommentPage',
'action' => 'action'
];
$routingTable['feed'] = [
'url' => '/feed[feed:string:0]',
'widget' => '\Widget\Feed',
'action' => 'render'
];
unset($routingTable[0]);
$db->query($db->update('table.options')
->rows(['value' => json_encode($routingTable)])
->where('name = ?', 'routingTable'));
// fix options->commentsRequireURL
$db->query($db->update('table.options')
->rows(['name' => 'commentsRequireUrl'])
->where('name = ?', 'commentsRequireURL'));
// fix draft
$db->query($db->update('table.contents')
->rows(['type' => 'revision'])
->where('parent <> 0 AND (type = ? OR type = ?)', 'post_draft', 'page_draft'));
// fix attachment serialize
$lastId = 0;
do {
$rows = $db->fetchAll(
$db->select('cid', 'text')->from('table.contents')
->where('cid > ?', $lastId)
->where('type = ?', 'attachment')
->order('cid', Db::SORT_ASC)
->limit(100)
);
foreach ($rows as $row) {
if (strpos($row['text'], 'a:') !== 0) {
continue;
}
$value = @unserialize($row['text']);
if ($value !== false) {
$db->query($db->update('table.contents')
->rows(['text' => json_encode($value)])
->where('cid = ?', $row['cid']));
}
$lastId = $row['cid'];
}
} while (count($rows) === 100);
$rows = $db->fetchAll($db->select()->from('table.options'));
foreach ($rows as $row) {
if (
in_array($row['name'], ['plugins', 'actionTable', 'panelTable'])
|| strpos($row['name'], 'plugin:') === 0
|| strpos($row['name'], 'theme:') === 0
) {
$value = @unserialize($row['value']);
if ($value !== false) {
$db->query($db->update('table.options')
->rows(['value' => json_encode($value)])
->where('name = ?', $row['name']));
}
}
}
}
}