Typecho网站了的服务器环境 Debian 12 + Nginx 1.28 + PHP8.2 + Mysql 8.4 + Typecho 1.3.0
网站主题使用Typecho-Butterfly
网站开启了以下插件 AutoSaveImage、Keywords、Paste Image、ShortLinks
其中缩短链接插件关的作用:可以将外链转换成内链,减少链接损失;另外也可以直到缩短链接的作用;

问题说明:
打开网站首页时,页面上有以下错误提示信息:
Deprecated: unserialize(): Passing null to parameter #1 ($data) of type string is deprecated in /www/wwwroot/xqbn.com/usr/plugins/ShortLinks/Plugin.php on line 181其中缩短链接插件的代码如下:
<?php
/**
* 把外部链接转换为指定内部链接
*
* @package ShortLinks
* @author Ryan
* @version 1.2.0 b2
* @link https://github.com/benzBrake/ShortLinks
*/
class ShortLinks_Plugin implements Typecho_Plugin_Interface
{
/**
* 激活插件方法,如果激活失败,直接抛出异常
*
* @access public
* @return String
* @throws Typecho_Plugin_Exception
* @throws \Typecho\Db\Exception
*/
public static function activate()
{
$db = self::db();
$tableName = $db->getPrefix() . 'shortlinks';
$adapter = $db->getAdapterName();
if ("Pdo_SQLite" === $adapter || "SQLite" === $adapter) {
$db->query(" CREATE TABLE IF NOT EXISTS " . $tableName . " (
id INTEGER PRIMARY KEY,
key TEXT,
target TEXT,
count NUMERIC)");
}
if ("Pdo_Mysql" === $adapter || "Mysql" === $adapter) {
$dbConfig = null;
if (class_exists('\Typecho\Db')) {
$dbConfig = $db->getConfig($db::READ);
} else {
$dbConfig = $db->getConfig()[0];
}
$charset = $dbConfig->charset;
$db->query("CREATE TABLE IF NOT EXISTS " . $tableName . " (
`id` int(8) NOT NULL AUTO_INCREMENT,
`key` varchar(64) NOT NULL,
`target` varchar(10000) NOT NULL,
`count` int(8) DEFAULT '0',
PRIMARY KEY (`id`)
) DEFAULT CHARSET=$charset AUTO_INCREMENT=1");
}
Helper::addAction('shortlinks', 'ShortLinks_Action');
Helper::addRoute('go', '/go/[key]/', 'ShortLinks_Action', 'shortlink');
Helper::addPanel(2, 'ShortLinks/panel.php', '短链管理', '短链接管理', 'administrator');
if (class_exists('\Widget\Base\Contents')) {
Typecho\Plugin::factory('\Widget\Base\Contents')->contentEx = array('ShortLinks_Plugin', 'replace');
Typecho\Plugin::factory('\Widget\Base\Contents')->excerptEx = array('ShortLinks_Plugin', 'replace');
Typecho\Plugin::factory('\Widget\Base\Contents')->filter = array('ShortLinks_Plugin', 'forceConvert');
Typecho\Plugin::factory('\Widget\Base\Comments')->contentEx = array('ShortLinks_Plugin', 'replace');
Typecho\Plugin::factory('\Widget\Base\Comments')->filter = array('ShortLinks_Plugin', 'authorUrlConvert');
Typecho\Plugin::factory('\Widget\Archive')->singleHandle = array('ShortLinks_Plugin', 'fieldsConvert');
} else {
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('ShortLinks_Plugin', 'replace');
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('ShortLinks_Plugin', 'replace');
Typecho_Plugin::factory('Widget_Abstract_Contents')->filter = array('ShortLinks_Plugin', 'forceConvert');
Typecho_Plugin::factory('Widget_Abstract_Comments')->contentEx = array('ShortLinks_Plugin', 'replace');
Typecho_Plugin::factory('Widget_Abstract_Comments')->filter = array('ShortLinks_Plugin', 'authorUrlConvert');
Typecho_Plugin::factory('Widget_Archive')->singleHandle = array('ShortLinks_Plugin', 'fieldsConvert');
}
return ('数据表 ' . $tableName . ' 创建成功,插件已经成功激活!');
}
/**
* 禁用插件方法,如果禁用失败,直接抛出异常
*
* @static
* @access public
* @return String
* @throws Typecho_Plugin_Exception
* @throws \Typecho\Plugin\Exception
*/
public static function deactivate()
{
$config = self::options('ShortLinks');
$db = self::db();
Helper::removeRoute('go');
Helper::removeAction('shortlinks');
Helper::removePanel(2, 'ShortLinks/panel.php');
if ($config->isDrop == 0) {
$db->query("DROP TABLE `{$db->getPrefix()}shortlinks`", Typecho_Db::WRITE);
return (_t('短链接插件已被禁用,其表(%s)已被删除!', $db->getPrefix() . 'shortlinks'));
} else {
return (_t('短链接插件已被禁用,但是其表(%s)并没有被删除!', $db->getPrefix() . 'shortlinks'));
}
}
/**
* 获取插件配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form 配置面板
* @return void
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
$radio = new Typecho_Widget_Helper_Form_Element_Radio('convert', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('外链转内链'), _t('开启后会帮你把外链转换成内链'));
$form->addInput($radio);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('convertCommentLink', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('转换评论者链接'), _t('开启后会帮你把评论者链接转换成内链'));
$form->addInput($radio);
$template_files = scandir(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'templates');
$goTemplates = array('NULL' => '禁用');
foreach ($template_files as $item) {
if (PATH_SEPARATOR !== ':') {
$item = mb_convert_encoding($item, "UTF-8", "GBK");
}
$name = mb_split("\.", $item)[0];
if (empty($name)) {
continue;
}
$goTemplates[$name] = $name;
}
$edit = new Typecho_Widget_Helper_Form_Element_Select('goTemplate', $goTemplates, 'NULL', _t('跳转页面模板'));
$form->addInput($edit);
$edit = new Typecho_Widget_Helper_Form_Element_Text('goDelay', null, _t('3'), _t('跳转延时'), _t('跳转页面停留时间(秒)'));
$form->addInput($edit);
$edit = new Typecho_Widget_Helper_Form_Element_Text('siteCreatedYear', null, _t('2020'), _t('建站年份'), _t('建站年份,用于模板内容替换模板中使用 <code>{{siteCreatedYear}}</code> 来代表建站年份'));
$form->addInput($edit);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('target', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('新窗口打开文章中的链接'), _t('开启后给文章中的链接新增 target 属性'));
$form->addInput($radio);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('authorPermalinkTarget', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('新窗口打开评论者链接'), _t('开启后给评论者链接新增 target 属性。(URL 中 target 属性,<b style="color:red">开启可能会引起主题异常</b>)'));
$form->addInput($radio);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('forceSwitch', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('强力模式'), _t('主要为了支持 editor.md / vditor 等前台解析<b style="color:red">(实验性功能)</b>'));
$form->addInput($radio);
$textarea = new Typecho_Widget_Helper_Form_Element_Textarea('convertCustomField', null, null, _t('需要处理的自定义字段'), _t('在这里设置需要处理的自定义字段,一行一个<b style="color:red">(实验性功能)</b>'));
$form->addInput($textarea);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('nullReferer', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('允许空 referer'), _t('开启后会允许空 referer'));
$form->addInput($radio);
$refererList = new Typecho_Widget_Helper_Form_Element_Textarea('refererList', null, null, _t('referer 白名单'), _t('在这里设置 referer 白名单,一行一个'));
$form->addInput($refererList);
$nonConvertList = new Typecho_Widget_Helper_Form_Element_Textarea('nonConvertList', null, _t("b0.upaiyun.com" . PHP_EOL . "glb.clouddn.com" . PHP_EOL . "qbox.me" . PHP_EOL . "qnssl.com"), _t('外链转换白名单'), _t('在这里设置外链转换白名单(评论者链接不生效)'));
$form->addInput($nonConvertList);
$isDrop = new Typecho_Widget_Helper_Form_Element_Radio('isDrop', array('0' => '删除', '1' => '不删除'), '1', '彻底卸载(<b style="color:red">请慎重选择</b>)', '请选择是否在禁用插件时,删除数据表');
$form->addInput($isDrop);
}
/**
* 个人用户的配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form
* @return void
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form)
{
}
/**
* 外链转内链
*
* @access public
* @param string $text
* @param mixed $widget
* @param mixed $lastResult
* @return array|string|string[] $content
* @throws \Typecho\Plugin\Exception
*/
public static function replace($text, $widget, $lastResult)
{
$text = empty($lastResult) ? $text : $lastResult;
$pluginOption = self::options('ShortLinks'); // 插件选项
$target = ($pluginOption->target) ? ' target="_blank" ' : ''; // 新窗口打开
if ($pluginOption->convert == 1) {
$fields = unserialize($widget->fields);
if (is_array($fields) && array_key_exists("noshort", $fields)) {
// 部分文章不转换
return $text;
}
// 文章内容和评论内容处理
@preg_match_all('/<a(.*?)href="(?!#)(.*?)"(.*?)>/', $text, $matches);
if ($matches) {
foreach ($matches[2] as $link) {
$text = str_replace("href=\"$link\"", "href=\"" . self::convertLink($link) . "\"" . $target, $text);
}
}
}
return $text;
}
/**
* 自定义字段处理
*
* @param Widget_Archive $widget
* @param Typecho_Db_Query $select
* @param mixed $lastResult
* @return void
* @throws \Typecho\Plugin\Exception
*/
public static function fieldsConvert($widget, $select, $lastResult)
{
$widget = empty($lastResult) ? $widget : $lastResult;
$pluginOption = self::options('ShortLinks'); // 插件选项
$fieldsList = self::textareaToArr($pluginOption->convertCustomField);
if ($pluginOption->convert == 1) { // 总开关
if ($fieldsList) {
foreach ($fieldsList as $field) {
if (isset($text->fields[$field])) {
// 非强力模式转换 a 标签
@preg_match_all('/<a(.*?)href="(?!#)(.*?)"(.*?)>/', $widget->fields[$field], $matches);
if ($matches) {
foreach ($matches[2] as $link) {
$widget->fields[$field] = str_replace("href=\"$link\"", "href=\"" . self::convertLink($link) . "\"", $widget->fields[$field]);
}
}
// 强力模式匹配所有链接
if ($pluginOption->forceSwitch == 1) {
$widget->fields[$field] = self::autoLink($widget->fields[$field]);
}
}
}
}
}
}
/**
* 用户链接转换
*
* @param Array $value
* @param Widget_Abstract_Comments $widget
* @param mixed $lastResult
* @return void
* @throws \Typecho\Plugin\Exception
*/
public static function authorUrlConvert($value, $widget, $lastResult)
{
$value = empty($lastResult) ? $value : $lastResult;
$pluginOption = self::options('ShortLinks'); // 插件选项
if ($pluginOption->convert == 1) { // 总开关
if ($pluginOption->convertCommentLink == 1) {
// 评论者链接处理
$url = $value['url'];
if (strpos($url, '://') !== false && strpos($url, rtrim(self::options()->siteUrl, '/')) === false) {
$value['url'] = self::convertLink($url, false);
if ($pluginOption->authorPermalinkTarget) {
$value['url'] = $value['url'] . '" target="_blank';
}
}
}
}
return $value;
}
/**
* 转换链接形式
*
* @access public
* @param $link
* @param bool $check
* @return mixed $string
* @throws \Typecho\Plugin\Exception
*/
public static function convertLink($link, $check = true)
{
$pluginOption = self::options('ShortLinks');
$linkBase = ltrim(rtrim(Typecho_Router::get('go')['url'], '/'), '/'); // 防止链接形式修改后不能用
$siteUrl = self::options()->siteUrl;
$nonConvertList = self::textareaToArr($pluginOption->nonConvertList); // 不转换列表
if ($check) {
if (strpos($link, '://') !== false && strpos($link, rtrim($siteUrl, '/')) !== false) {
return $link;
}
// 本站链接不处理 不转换列表中的不处理
if (self::checkDomain($link, $nonConvertList)) {
return $link;
}
// 图片不处理
if (preg_match('/\.(jpg|jepg|png|ico|bmp|gif|tiff)/i', $link)) {
return $link;
}
}
return Typecho_Common::url(str_replace('[key]', self::urlSafeB64Encode(htmlspecialchars_decode($link)), $linkBase), self::options()->index);
}
/**
* 强力转换
*
* @param Array $value
* @param mixed $widget
* @param mixed $lastResult
* @return Array
* @throws \Typecho\Plugin\Exception
*/
public static function forceConvert($value, $widget, $lastResult)
{
$value = empty($lastResult) ? $value : $lastResult;
$pluginOption = self::options('ShortLinks');
if ($pluginOption->convert == 1 && $pluginOption->forceSwitch == 1) {
$value['text'] = self::autoLink($value['text']);
}
return $value;
}
/**
* 文本链接转A标签
*
* @param string $content
* @return string
* @throws \Typecho\Plugin\Exception
*/
public static function autoLink($content)
{
$url = '~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?<![\.,:])~i';
$target = (self::options()->target) ? ' target="_blank" ' : ''; // 新窗口打开
return preg_replace_callback($url, function ($matches) use ($target) {
if (preg_match('/\.(jpg|jepg|png|ico|bmp|gif|tiff)/i', $matches[0])) {
return $matches[0];
}
if (strpos($matches[0], '://') !== false && strpos($matches[0], rtrim(self::options()->siteUrl, '/')) !== false) {
return '<a href="' . self::convertLink($matches[0]) . '" title="' . $matches[0] . '"' . $target . '>' . $matches[0] . '</a>';
}
return $matches[0];
}, $content);
}
/**
* 检查域名是否在数组中存在
*
* @access public
* @param $url $arr
* @param $class
* @return boolean
*/
public static function checkDomain($url, $arr)
{
if ($arr === null) {
return false;
}
if (count($arr) === 0) {
return false;
}
foreach ($arr as $a) {
if (strpos($url, $a) !== false) {
return true;
}
}
return false;
}
/**
* 一行一个文本框转数组
*
* @access public
* @param $textarea
* @param $class
* @return $arr
*/
public static function textareaToArr($textarea)
{
$str = str_replace(array("\r\n", "\r", "\n"), "|", $textarea);
if ($str == "") {
return null;
}
return explode("|", $str);
}
/**
* Base64 解码
*
* @param string $str
* @return string
* @date 2020-05-01
*/
public static function urlSafeB64Decode($str)
{
$data = str_replace(array('-', '_'), array('+', '/'), $str);
$mod = strlen($data) % 4;
if ($mod) {
$data .= substr('====', $mod);
}
return base64_decode($data);
}
/**
* Base64 编码
*
* @param string $str
* @return string
* @date 2020-05-01
*/
public static function urlSafeB64Encode($str)
{
$data = base64_encode($str);
return str_replace(array('+', '/', '='), array('-', '_', ''), $data);
}
/**
* 获得配置信息
*
* @return Typecho_Options
* @throws \Typecho\Plugin\Exception
*/
public static function options($plugin = null)
{
$options = null;
if (function_exists('\Widget\Options::alloc')) {
$options = \Widget\Options::alloc();
} else {
$options = Typecho_Widget::widget('Widget_Options');
}
if ($plugin) {
$options = $options->plugin($plugin);
}
return $options;
}
/**
* 获取数据库对象
* @return mixed
* @throws \Typecho\Db\Exception
*/
public static function db()
{
if (class_exists('Typecho\Db')) {
return Typecho\Db::get();
} else {
return Typecho_Db::get();
}
}
}报错问题分析
这个警告的出现,是因为在 PHP 8.1+ 版本中,unserialize() 函数要求传入的参数必须是字符串,而 $widget->fields 在某些情况下可能为 null,从而触发了弃用通知。
🔧 修复 ShortLinks 插件警告
要修复第181行的警告,需要修改 /www/wwwroot/xqbn.com/usr/plugins/ShortLinks/Plugin.php 文件。
定位代码:找到
replace函数中的第181行,原代码如下:$fields = unserialize($widget->fields);修改代码:将其修改为以下内容,增加对
$widget->fields的判断:// 修复:在反序列化前检查字段是否存在且为字符串 $fields = (!empty($widget->fields) && is_string($widget->fields)) ? unserialize($widget->fields) : false;修改后的完整上下文(第180-184行) 应类似于:
if ($pluginOption->convert == 1) { $fields = (!empty($widget->fields) && is_string($widget->fields)) ? unserialize($widget->fields) : false; if (is_array($fields) && array_key_exists("noshort", $fields)) { // 部分文章不转换 return $text; }
修改完成后,Typecho缩短链接插件 ShortLinks 完整的代码如下:
<?php
/**
* 把外部链接转换为指定内部链接
*
* @package ShortLinks
* @author Ryan
* @version 1.2.0 b2
* @link https://github.com/benzBrake/ShortLinks
*/
class ShortLinks_Plugin implements Typecho_Plugin_Interface
{
/**
* 激活插件方法,如果激活失败,直接抛出异常
*
* @access public
* @return String
* @throws Typecho_Plugin_Exception
* @throws \Typecho\Db\Exception
*/
public static function activate()
{
$db = self::db();
$tableName = $db->getPrefix() . 'shortlinks';
$adapter = $db->getAdapterName();
if ("Pdo_SQLite" === $adapter || "SQLite" === $adapter) {
$db->query(" CREATE TABLE IF NOT EXISTS " . $tableName . " (
id INTEGER PRIMARY KEY,
key TEXT,
target TEXT,
count NUMERIC)");
}
if ("Pdo_Mysql" === $adapter || "Mysql" === $adapter) {
$dbConfig = null;
if (class_exists('\Typecho\Db')) {
$dbConfig = $db->getConfig($db::READ);
} else {
$dbConfig = $db->getConfig()[0];
}
$charset = $dbConfig->charset;
$db->query("CREATE TABLE IF NOT EXISTS " . $tableName . " (
`id` int(8) NOT NULL AUTO_INCREMENT,
`key` varchar(64) NOT NULL,
`target` varchar(10000) NOT NULL,
`count` int(8) DEFAULT '0',
PRIMARY KEY (`id`)
) DEFAULT CHARSET=$charset AUTO_INCREMENT=1");
}
Helper::addAction('shortlinks', 'ShortLinks_Action');
Helper::addRoute('go', '/go/[key]/', 'ShortLinks_Action', 'shortlink');
Helper::addPanel(2, 'ShortLinks/panel.php', '短链管理', '短链接管理', 'administrator');
if (class_exists('\Widget\Base\Contents')) {
Typecho\Plugin::factory('\Widget\Base\Contents')->contentEx = array('ShortLinks_Plugin', 'replace');
Typecho\Plugin::factory('\Widget\Base\Contents')->excerptEx = array('ShortLinks_Plugin', 'replace');
Typecho\Plugin::factory('\Widget\Base\Contents')->filter = array('ShortLinks_Plugin', 'forceConvert');
Typecho\Plugin::factory('\Widget\Base\Comments')->contentEx = array('ShortLinks_Plugin', 'replace');
Typecho\Plugin::factory('\Widget\Base\Comments')->filter = array('ShortLinks_Plugin', 'authorUrlConvert');
Typecho\Plugin::factory('\Widget\Archive')->singleHandle = array('ShortLinks_Plugin', 'fieldsConvert');
} else {
Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('ShortLinks_Plugin', 'replace');
Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('ShortLinks_Plugin', 'replace');
Typecho_Plugin::factory('Widget_Abstract_Contents')->filter = array('ShortLinks_Plugin', 'forceConvert');
Typecho_Plugin::factory('Widget_Abstract_Comments')->contentEx = array('ShortLinks_Plugin', 'replace');
Typecho_Plugin::factory('Widget_Abstract_Comments')->filter = array('ShortLinks_Plugin', 'authorUrlConvert');
Typecho_Plugin::factory('Widget_Archive')->singleHandle = array('ShortLinks_Plugin', 'fieldsConvert');
}
return ('数据表 ' . $tableName . ' 创建成功,插件已经成功激活!');
}
/**
* 禁用插件方法,如果禁用失败,直接抛出异常
*
* @static
* @access public
* @return String
* @throws Typecho_Plugin_Exception
* @throws \Typecho\Plugin\Exception
*/
public static function deactivate()
{
$config = self::options('ShortLinks');
$db = self::db();
Helper::removeRoute('go');
Helper::removeAction('shortlinks');
Helper::removePanel(2, 'ShortLinks/panel.php');
if ($config->isDrop == 0) {
$db->query("DROP TABLE `{$db->getPrefix()}shortlinks`", Typecho_Db::WRITE);
return (_t('短链接插件已被禁用,其表(%s)已被删除!', $db->getPrefix() . 'shortlinks'));
} else {
return (_t('短链接插件已被禁用,但是其表(%s)并没有被删除!', $db->getPrefix() . 'shortlinks'));
}
}
/**
* 获取插件配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form 配置面板
* @return void
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
$radio = new Typecho_Widget_Helper_Form_Element_Radio('convert', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('外链转内链'), _t('开启后会帮你把外链转换成内链'));
$form->addInput($radio);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('convertCommentLink', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('转换评论者链接'), _t('开启后会帮你把评论者链接转换成内链'));
$form->addInput($radio);
$template_files = scandir(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'templates');
$goTemplates = array('NULL' => '禁用');
foreach ($template_files as $item) {
if (PATH_SEPARATOR !== ':') {
$item = mb_convert_encoding($item, "UTF-8", "GBK");
}
$name = mb_split("\.", $item)[0];
if (empty($name)) {
continue;
}
$goTemplates[$name] = $name;
}
$edit = new Typecho_Widget_Helper_Form_Element_Select('goTemplate', $goTemplates, 'NULL', _t('跳转页面模板'));
$form->addInput($edit);
$edit = new Typecho_Widget_Helper_Form_Element_Text('goDelay', null, _t('3'), _t('跳转延时'), _t('跳转页面停留时间(秒)'));
$form->addInput($edit);
$edit = new Typecho_Widget_Helper_Form_Element_Text('siteCreatedYear', null, _t('2020'), _t('建站年份'), _t('建站年份,用于模板内容替换模板中使用 <code>{{siteCreatedYear}}</code> 来代表建站年份'));
$form->addInput($edit);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('target', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('新窗口打开文章中的链接'), _t('开启后给文章中的链接新增 target 属性'));
$form->addInput($radio);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('authorPermalinkTarget', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('新窗口打开评论者链接'), _t('开启后给评论者链接新增 target 属性。(URL 中 target 属性,<b style="color:red">开启可能会引起主题异常</b>)'));
$form->addInput($radio);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('forceSwitch', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('强力模式'), _t('主要为了支持 editor.md / vditor 等前台解析<b style="color:red">(实验性功能)</b>'));
$form->addInput($radio);
$textarea = new Typecho_Widget_Helper_Form_Element_Textarea('convertCustomField', null, null, _t('需要处理的自定义字段'), _t('在这里设置需要处理的自定义字段,一行一个<b style="color:red">(实验性功能)</b>'));
$form->addInput($textarea);
$radio = new Typecho_Widget_Helper_Form_Element_Radio('nullReferer', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('允许空 referer'), _t('开启后会允许空 referer'));
$form->addInput($radio);
$refererList = new Typecho_Widget_Helper_Form_Element_Textarea('refererList', null, null, _t('referer 白名单'), _t('在这里设置 referer 白名单,一行一个'));
$form->addInput($refererList);
$nonConvertList = new Typecho_Widget_Helper_Form_Element_Textarea('nonConvertList', null, _t("b0.upaiyun.com" . PHP_EOL . "glb.clouddn.com" . PHP_EOL . "qbox.me" . PHP_EOL . "qnssl.com"), _t('外链转换白名单'), _t('在这里设置外链转换白名单(评论者链接不生效)'));
$form->addInput($nonConvertList);
$isDrop = new Typecho_Widget_Helper_Form_Element_Radio('isDrop', array('0' => '删除', '1' => '不删除'), '1', '彻底卸载(<b style="color:red">请慎重选择</b>)', '请选择是否在禁用插件时,删除数据表');
$form->addInput($isDrop);
}
/**
* 个人用户的配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form
* @return void
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form)
{
}
/**
* 外链转内链
*
* @access public
* @param string $text
* @param mixed $widget
* @param mixed $lastResult
* @return array|string|string[] $content
* @throws \Typecho\Plugin\Exception
*/
public static function replace($text, $widget, $lastResult)
{
$text = empty($lastResult) ? $text : $lastResult;
$pluginOption = self::options('ShortLinks'); // 插件选项
$target = ($pluginOption->target) ? ' target="_blank" ' : ''; // 新窗口打开
if ($pluginOption->convert == 1) {
// 修复:在反序列化前检查字段是否存在且为字符串
$fields = (!empty($widget->fields) && is_string($widget->fields)) ? unserialize($widget->fields) : false;
if (is_array($fields) && array_key_exists("noshort", $fields)) {
// 部分文章不转换
return $text;
}
// 文章内容和评论内容处理
@preg_match_all('/<a(.*?)href="(?!#)(.*?)"(.*?)>/', $text, $matches);
if ($matches) {
foreach ($matches[2] as $link) {
$text = str_replace("href=\"$link\"", "href=\"" . self::convertLink($link) . "\"" . $target, $text);
}
}
}
return $text;
}
/**
* 自定义字段处理
*
* @param Widget_Archive $widget
* @param Typecho_Db_Query $select
* @param mixed $lastResult
* @return void
* @throws \Typecho\Plugin\Exception
*/
public static function fieldsConvert($widget, $select, $lastResult)
{
$widget = empty($lastResult) ? $widget : $lastResult;
$pluginOption = self::options('ShortLinks'); // 插件选项
$fieldsList = self::textareaToArr($pluginOption->convertCustomField);
if ($pluginOption->convert == 1) { // 总开关
if ($fieldsList) {
foreach ($fieldsList as $field) {
if (isset($text->fields[$field])) {
// 非强力模式转换 a 标签
@preg_match_all('/<a(.*?)href="(?!#)(.*?)"(.*?)>/', $widget->fields[$field], $matches);
if ($matches) {
foreach ($matches[2] as $link) {
$widget->fields[$field] = str_replace("href=\"$link\"", "href=\"" . self::convertLink($link) . "\"", $widget->fields[$field]);
}
}
// 强力模式匹配所有链接
if ($pluginOption->forceSwitch == 1) {
$widget->fields[$field] = self::autoLink($widget->fields[$field]);
}
}
}
}
}
}
/**
* 用户链接转换
*
* @param Array $value
* @param Widget_Abstract_Comments $widget
* @param mixed $lastResult
* @return void
* @throws \Typecho\Plugin\Exception
*/
public static function authorUrlConvert($value, $widget, $lastResult)
{
$value = empty($lastResult) ? $value : $lastResult;
$pluginOption = self::options('ShortLinks'); // 插件选项
if ($pluginOption->convert == 1) { // 总开关
if ($pluginOption->convertCommentLink == 1) {
// 评论者链接处理
$url = $value['url'];
if (strpos($url, '://') !== false && strpos($url, rtrim(self::options()->siteUrl, '/')) === false) {
$value['url'] = self::convertLink($url, false);
if ($pluginOption->authorPermalinkTarget) {
$value['url'] = $value['url'] . '" target="_blank';
}
}
}
}
return $value;
}
/**
* 转换链接形式
*
* @access public
* @param $link
* @param bool $check
* @return mixed $string
* @throws \Typecho\Plugin\Exception
*/
public static function convertLink($link, $check = true)
{
$pluginOption = self::options('ShortLinks');
$linkBase = ltrim(rtrim(Typecho_Router::get('go')['url'], '/'), '/'); // 防止链接形式修改后不能用
$siteUrl = self::options()->siteUrl;
$nonConvertList = self::textareaToArr($pluginOption->nonConvertList); // 不转换列表
if ($check) {
if (strpos($link, '://') !== false && strpos($link, rtrim($siteUrl, '/')) !== false) {
return $link;
}
// 本站链接不处理 不转换列表中的不处理
if (self::checkDomain($link, $nonConvertList)) {
return $link;
}
// 图片不处理
if (preg_match('/\.(jpg|jepg|png|ico|bmp|gif|tiff)/i', $link)) {
return $link;
}
}
return Typecho_Common::url(str_replace('[key]', self::urlSafeB64Encode(htmlspecialchars_decode($link)), $linkBase), self::options()->index);
}
/**
* 强力转换
*
* @param Array $value
* @param mixed $widget
* @param mixed $lastResult
* @return Array
* @throws \Typecho\Plugin\Exception
*/
public static function forceConvert($value, $widget, $lastResult)
{
$value = empty($lastResult) ? $value : $lastResult;
$pluginOption = self::options('ShortLinks');
if ($pluginOption->convert == 1 && $pluginOption->forceSwitch == 1) {
$value['text'] = self::autoLink($value['text']);
}
return $value;
}
/**
* 文本链接转A标签
*
* @param string $content
* @return string
* @throws \Typecho\Plugin\Exception
*/
public static function autoLink($content)
{
$url = '~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?<![\.,:])~i';
$target = (self::options()->target) ? ' target="_blank" ' : ''; // 新窗口打开
return preg_replace_callback($url, function ($matches) use ($target) {
if (preg_match('/\.(jpg|jepg|png|ico|bmp|gif|tiff)/i', $matches[0])) {
return $matches[0];
}
if (strpos($matches[0], '://') !== false && strpos($matches[0], rtrim(self::options()->siteUrl, '/')) !== false) {
return '<a href="' . self::convertLink($matches[0]) . '" title="' . $matches[0] . '"' . $target . '>' . $matches[0] . '</a>';
}
return $matches[0];
}, $content);
}
/**
* 检查域名是否在数组中存在
*
* @access public
* @param $url $arr
* @param $class
* @return boolean
*/
public static function checkDomain($url, $arr)
{
if ($arr === null) {
return false;
}
if (count($arr) === 0) {
return false;
}
foreach ($arr as $a) {
if (strpos($url, $a) !== false) {
return true;
}
}
return false;
}
/**
* 一行一个文本框转数组
*
* @access public
* @param $textarea
* @param $class
* @return $arr
*/
public static function textareaToArr($textarea)
{
$str = str_replace(array("\r\n", "\r", "\n"), "|", $textarea);
if ($str == "") {
return null;
}
return explode("|", $str);
}
/**
* Base64 解码
*
* @param string $str
* @return string
* @date 2020-05-01
*/
public static function urlSafeB64Decode($str)
{
$data = str_replace(array('-', '_'), array('+', '/'), $str);
$mod = strlen($data) % 4;
if ($mod) {
$data .= substr('====', $mod);
}
return base64_decode($data);
}
/**
* Base64 编码
*
* @param string $str
* @return string
* @date 2020-05-01
*/
public static function urlSafeB64Encode($str)
{
$data = base64_encode($str);
return str_replace(array('+', '/', '='), array('-', '_', ''), $data);
}
/**
* 获得配置信息
*
* @return Typecho_Options
* @throws \Typecho\Plugin\Exception
*/
public static function options($plugin = null)
{
$options = null;
if (function_exists('\Widget\Options::alloc')) {
$options = \Widget\Options::alloc();
} else {
$options = Typecho_Widget::widget('Widget_Options');
}
if ($plugin) {
$options = $options->plugin($plugin);
}
return $options;
}
/**
* 获取数据库对象
* @return mixed
* @throws \Typecho\Db\Exception
*/
public static function db()
{
if (class_exists('Typecho\Db')) {
return Typecho\Db::get();
} else {
return Typecho_Db::get();
}
}
} 




















