Typecho 文档:如何通过插件达成功能需求?

02月22日

适用程序:Typecho
程序版本:1.2.1
文档作者:Lopwon
作者博客:Lopwon.com
发布页面:Lopwon.com/attachment/3942/
许可方式:CC BY-NC-SA

注意:此文档源于作者在博客改造中的一些经验总结,转载还请署名。

敬告:此文档操作涉及程序核心文件的修改,作者不对你在使用中产生的任何问题造成的不良后果,承担责任。

文档说明

插件的好处是:无需对程序核心代码和主题模板做修改,也可以通过即插即用的方式扩展功能。本文档以(历史上有今天)为案例,实操如何通过插件达成这一功能需求。

历史上有今天,指的是不同年份,但同月同日发布的文章。由此,将博客中具有这一特性的文章关联起来,那么,需要先获取当前(月+日)日期,再与博客中所有文章的发布(月+日)日期做匹配,如有,则输出文章(标题+链接+年份),若无,则输出指定提示,如:历史上无今天。


功能解析
1. 获取当前(月+日)日期,指定为东八区;

    $targetTimezone = new DateTimeZone('Asia/Shanghai'); // 设置目标时区为东八区,避免服务器不处于东八区,但用户为东八区时产生的偏差
    $dateTime = new DateTime('now', $targetTimezone); // 创建一个 DateTime 对象,并设置为东八区的当前时间

    $currMonth = $dateTime->format('m'); // 获取当前月
    $currDay = $dateTime->format('d'); // 获取当前日

2. 获取博客中已发布的所有文章的(月+日)日期,并在遍历中匹配当前日期;

    $db = Typecho_Db::get(); // 获取数据库连接实例
    $result = $db->fetchAll(
        $db->select()
           ->from('table.contents') // 从表 *_contents 获取数据
           ->where('status = ?', 'publish') // 仅限公开的文章(含未发布的)
           ->where('type = ?', 'post') // 仅限文章(不包括独立页和附件)
           ->order('created', Typecho_Db::SORT_DESC) // 最新发布的文章排在前
    );

    $found = false; // 标记是否有匹配的文章,默认为 false

    foreach($result as $value) {
        $filterValue = Typecho_Widget::widget('Widget_Abstract_Contents')->filter($value); // 主要为了获取 permalink year month day 等数据

        $year = $filterValue['year']; // 文章发布年
        $month = $filterValue['month']; // 文章发布月
        $day = $filterValue['day']; // 文章发布日

        $title = $filterValue['title']; // 文章标题
        $permalink = $filterValue['permalink']; // 文章链接

        if ($month === $currMonth && $day === $currDay) { // 如果当前(月+日)与文章发布(月+日)都相同,则为(历史上有今天)文章
            echo '<a class="lopwon_today-item" href="' . $permalink . '">'. $title . ' (' . $year . ')</a>'; // 输出匹配的文章信息
            $found = true; // 找到匹配的文章则标记为 true
        }
    }

    if (!$found) { // 如果没有找到匹配的文章
        echo '<i>历史上无今天!</i>'; // 输出提示
    }
    

3. 使用插件执行及输出结果;

<?php

    // 定义命名空间,Typecho 1.2.0 引入

    namespace TypechoPlugin\LopwonToday; // 其中 LopwonToday 与插件文件夹名称一致

    // 引入 Typecho 插件接口和所需的类库

    use Typecho\Plugin\PluginInterface; // 引入插件接口,插件需要实现这个接口,对应文件 var\Typecho\Plugin\PluginInterface.php
    use Typecho\Widget\Helper\Form; // 引入 Form 类,帮助构建插件的配置表单,对应目录 var\Typecho\Widget\Helper\Form 如定义 config 配置面板时,则对应 Element 内文件
    use Utils\Helper; // 引入 Helper 工具类,提供 Typecho 内置方法,如 options addAction addPanel addRoute 等,对应文件 var\Utils\Helper.php
    use Typecho\Db; // 引入数据库类,用于与 Typecho 数据库进行交互,如 select where join 等,对应目录 var\Typecho\Db 及文件 var\Typecho\Db.php
    use Typecho\Widget; // 引入 Widget 类,允许在插件中创建和管理 Widgets 工具,对应目录 var\Typecho\Widget

    // 防止直接访问该文件,确保只有通过 Typecho 环境加载

    if (!defined('__TYPECHO_ROOT_DIR__')) exit; // 如果常量 __TYPECHO_ROOT_DIR__ 未定义,则退出

    // 定义插件信息,对应文件 var\Typecho\Plugin.php 的 parseInfo()

    /**
     * 历史上有今天
     *
     * @package Lopwon Today
     * @author Lopwon
     * @version 1.0.0
     * @link http://www.lopwon.com
     */

    // 定义一个 Plugin 类,实现 Typecho 插件接口 PluginInterface

    class Plugin implements PluginInterface
    {
        /**
         * 激活插件方法,如果激活失败,直接抛出异常
         */
        public static function activate()
        {

            /**
             * 插件的激活接口,在当前案例中,使用了自定义的接口 \Typecho\Plugin::factory('Lopwon_Today')->Lopwon = [__CLASS__, 'render'];
             * 那么,在主题中则调用 Typecho_Plugin::factory('Lopwon_Today')->Lopwon(); 以输出插件处理结果
             *
             * 其中 Lopwon_Today 和 Lopwon 可自定义,但需要保持定义与调用的名称一致
             * 例如,在插件中自定义接口为 \Typecho\Plugin::factory('forSnapic')->Snapic = [__CLASS__, 'render'];
             * 那么,在主题中则调用 Typecho_Plugin::factory('forSnapic')->Snapic(); 以输出插件处理结果
             *
             * 其中 render 为插件实现方法(函数)的名称,可自定义,但需要与以下 public static function render() 自定义方法(函数)的 render 名称保持一致
             * 例如,在插件中自定义接口为 \Typecho\Plugin::factory('Lopwon_Today')->Lopwon = [__CLASS__, 'output'];
             * 那么,以下自定义方法(函数)则为 public static function output()
             */

            \Typecho\Plugin::factory('Lopwon_Today')->Lopwon = [__CLASS__, 'render']; // 自定义接口,其中 render 为插件实现方法(函数)的名称

            \Typecho\Plugin::factory('Widget_Archive')->header = [__CLASS__, 'head']; // Typecho 内置接口,用于页头输出内容,如 style 样式等,其中 head 与以下函数名称 head 一致
            \Typecho\Plugin::factory('Widget_Archive')->footer = [__CLASS__, 'foot']; // Typecho 内置接口,用于页脚输出内容,如 JavaScript 脚本等,其中 foot 与以下函数名称 foot 一致

        }

        /**
         * 禁用插件方法,如果禁用失败,直接抛出异常
         */
        public static function deactivate()
        {

            /**
             * 插件的禁用接口,在当前案例中,注销插件时没有什么资源需要释放,所以无需编写,但需要保留此函数定义
             *
             * 通常在:增加 addPanel 面板;增加 addAction 动作;增加 addRoute 路由后,禁用插件时做移除面板、动作、路由的操作
             * 也常用于删除缓存文件或数据等操作
             */

        }

        /**
         * 获取插件配置面板
         *
         * @param Form $form 配置面板
         */
        public static function config(Form $form)
        {

            // 插件的配置面板,在当前案例中,没有什么参数需要配置,所以无需编写,但需要保留此函数定义

        }

        /**
         * 个人用户的配置面板
         *
         * @param Form $form
         */
        public static function personalConfig(Form $form)
        {

            // 个人用户的配置面板,在当前案例中,没有此需求,所以无需编写,但需要保留此函数定义

        }

        /**
         * 在页面 head 里输出 css 文件,对应位置在主题中的 <?php $this->header(); ?>
         */
        public static function head()
        {

            $pluginUrl = Helper::options()->pluginUrl . '/LopwonToday/'; // 获取插件路径
            echo '<link rel="stylesheet" href="' . $pluginUrl . 'lopwon.today.css" />'; // 输出引入 lopwon.today.css 样式文件

        }

        /**
         * 在页面 body 里输出 JavaScript 文件,对应位置在主题中的 <?php $this->footer(); ?>
         */
        public static function foot()
        {

            $pluginUrl = Helper::options()->pluginUrl . '/LopwonToday/'; // 获取插件路径
            echo '<link rel="stylesheet" href="' . $pluginUrl . 'lopwon.today.js" />'; // 输出引入 lopwon.today.js 脚本文件

        }

        /**
         * 插件实现方法,对应位置在主题中调用插件的 <?php Typecho_Plugin::factory('Lopwon_Today')->Lopwon(); ?>
         */
        public static function render()
        {

            /**
             * 获取当前(月+日)日期,指定为东八区;
             */

            $targetTimezone = new \DateTimeZone('Asia/Shanghai'); // 设置目标时区为东八区,避免服务器不处于东八区,但用户为东八区时产生的偏差
            $dateTime = new \DateTime('now', $targetTimezone); // 创建一个 DateTime 对象,并设置为东八区的当前时间

            $currMonth = $dateTime->format('m'); // 获取当前月
            $currDay = $dateTime->format('d'); // 获取当前日

            /**
             * 获取博客中已发布的所有文章的(月+日)日期,并在遍历中匹配当前日期;
             */

            $db = Db::get(); // 获取数据库连接实例
            $result = $db->fetchAll(
                $db->select()
                   ->from('table.contents') // 从表 *_contents 获取数据
                   ->where('status = ?', 'publish') // 仅限公开的文章(含未发布的)
                   ->where('type = ?', 'post') // 仅限文章(不包括独立页和附件)
                   ->order('created', Db::SORT_DESC) // 最新发布的文章排在前
            );

            $found = false; // 标记是否有匹配的文章,默认为 false

            foreach($result as $value) {
                $filterValue = Widget::widget('Widget_Abstract_Contents')->filter($value); // 主要为了获取 permalink year month day 等数据

                $year = $filterValue['year']; // 文章发布年
                $month = $filterValue['month']; // 文章发布月
                $day = $filterValue['day']; // 文章发布日

                $title = $filterValue['title']; // 文章标题
                $permalink = $filterValue['permalink']; // 文章链接

                if ($month === $currMonth && $day === $currDay) { // 如果当前(月+日)与文章发布(月+日)都相同,则为(历史上有今天)文章
                    echo '<a class="lopwon_today-item" href="' . $permalink . '">'. $title . ' (' . $year . ')</a>'; // 输出匹配的文章信息
                    $found = true; // 找到匹配的文章则标记为 true
                }
            }

            if (!$found) { // 如果没有找到匹配的文章
                echo '<i>历史上无今天!</i>'; // 输出提示
           }

        }

    }

4. 在主题中调用输出插件处理结果;

    <?php Typecho_Plugin::factory('Lopwon_Today')->Lopwon(); // Lopwon Today ?>

以上演示了插件的基础制作方法,点击以下(我要下载)获取成品插件,当然,使用历史上有今天,需要博客有一定的存量文章,才有显示效果,要不就成了历史上无今天。

如何快速查看插件的效果呢?在启用插件并在主题中调用插件后,找到过往已发布的某篇文章A和B,把其中一篇文章(如:A)的发布日期临时修改为今天的日期(假设为2022年11月09日),那么,文章B则将发布日期临时修改为与文章A相同的月和日,但不同年份(如:2019年11月09日),最后,在前台访问调用插件的页面,即可看到插件效果。

当然 Typecho 内置了丰富的插件接口,可以实现更具场景化的扩展功能,另一款插件 Lopwon Hook 快速构建生成了这些接口,可供开发者使用。


参考来源

插件 Hello World

This is a message

Snapic Plus v5 Lopwon Hub