惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
H
Help Net Security
F
Fortinet All Blogs
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 司徒正美
量子位
N
Netflix TechBlog - Medium
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Recorded Future
Recorded Future
博客园 - 三生石上(FineUI控件)
Vercel News
Vercel News
aimingoo的专栏
aimingoo的专栏
I
InfoQ
Microsoft Security Blog
Microsoft Security Blog
Scott Helme
Scott Helme
The Last Watchdog
The Last Watchdog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
AI
AI
WordPress大学
WordPress大学
Security Archives - TechRepublic
Security Archives - TechRepublic
Google Online Security Blog
Google Online Security Blog
U
Unit 42
V2EX - 技术
V2EX - 技术
MongoDB | Blog
MongoDB | Blog
Schneier on Security
Schneier on Security
博客园 - Franky
H
Heimdal Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
W
WeLiveSecurity
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
B
Blog RSS Feed
N
News | PayPal Newsroom
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
Hacker News - Newest:
Hacker News - Newest: "LLM"
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
雷峰网
雷峰网

兴起百年 - XQBN.com - 学习

小内存服务器大爆发:我是如何优化WordPress扛住上万PV的 我的定投日记:纳斯达克指数基金10个月收益16.33%的思考与展望 Typecho关键词链接插件提示错误 Warning: Undefined array key 2 in 的解决方法 自部署去中心聊天项目 Matrix 服务器Debian12+宝塔面板+Docker安装Matrix免费开源聊天项目 ubuntu 22.04系统 如何将硬盘的inodes参数设置为最大值? ROE投资笔记:大道至简,如何寻找长期稳定高ROE的“宝藏股”? 巴菲特也推崇的选股心法:用好这“三层筛子”,半年收益可达80% 投资进阶必备技能:手把手教你高效查找与阅读公司研报 新能源基金暴涨118%!我的投资学习笔记:碳中和赛道还能追吗?
Typecho缩短链接插件提示错误Deprecated: unserialize(): Passing null to parameter #1 ($data) of type string is deprecated的解决方法
作者: 兴起百年 · 2025-12-06 · via 兴起百年 - XQBN.com - 学习

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 文件。

  1. 定位代码:找到 replace 函数中的第181行,原代码如下:

    $fields = unserialize($widget->fields);
  2. 修改代码:将其修改为以下内容,增加对 $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();
        }
    }
}