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

推荐订阅源

W
WeLiveSecurity
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
The Register - Security
The Register - Security
Stack Overflow Blog
Stack Overflow Blog
博客园 - 三生石上(FineUI控件)
T
Threat Research - Cisco Blogs
S
SegmentFault 最新的问题
V2EX - 技术
V2EX - 技术
Hacker News: Ask HN
Hacker News: Ask HN
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
P
Proofpoint News Feed
J
Java Code Geeks
Microsoft Security Blog
Microsoft Security Blog
M
MIT News - Artificial intelligence
AI
AI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
Hacker News - Newest:
Hacker News - Newest: "LLM"
B
Blog
N
News and Events Feed by Topic
N
News | PayPal Newsroom
Google DeepMind News
Google DeepMind News
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
C
Cybersecurity and Infrastructure Security Agency CISA
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
U
Unit 42
腾讯CDC
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Cloudflare Blog
H
Help Net Security
Recent Announcements
Recent Announcements
P
Privacy & Cybersecurity Law Blog
IT之家
IT之家
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 热门话题
Martin Fowler
Martin Fowler
MongoDB | Blog
MongoDB | Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
H
Heimdal Security Blog
博客园 - 聂微东
S
Securelist
大猫的无限游戏
大猫的无限游戏
Cloudbric
Cloudbric
Cisco Talos Blog
Cisco Talos Blog

兴起百年 - XQBN.com - 2025年12月

2025年终回顾:在曲折中前行的一年 自律,是改写人生的那支笔 逃离都市喧嚣:我在Canyon Woods Resort Club的治愈周末 我的定投日记:纳斯达克指数基金10个月收益16.33%的思考与展望 Typecho关键词链接插件提示错误 Warning: Undefined array key 2 in 的解决方法 真正让你成长的,往往是那些“感觉好累”的时刻 自部署去中心聊天项目 Matrix 服务器Debian12+宝塔面板+Docker安装Matrix免费开源聊天项目
Typecho缩短链接插件提示错误Deprecated: unserialize(): Passing null to parameter #1 ($data) of type string is deprecated的解决方法
作者: 兴起百年 · 2025-12-06 · via 兴起百年 - XQBN.com - 2025年12月

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();
        }
    }
}