LiteLog 插件开发规范 v1.0

2026-10-05 分类:Lite 作者:zhangwei 浏览 42

只需阅读这一个文件,就能开发出一个完整可用的 LiteLog 插件。
本文档覆盖目录结构、钩子机制、可用 API、生命周期、开发示例、常见问题。


目录

  1. 快速开始
  2. 目录结构
  3. 头部注释规范
  4. 生命周期回调
  5. 钩子机制
  6. 可用 API
  7. 插件设置页
  8. 插件前台页面
  9. 数据存储
  10. 完整示例
  11. 开发规范
  12. 常见问题
  13. 附录:钩子速查表

一、快速开始

最小可运行插件(5 行代码)

在 content/plugins/hello/hello.php 创建文件:

<?php
/**
 * Plugin Name: Hello World
 * Description: 我的第一个插件
 * Version: 1.0.0
 * Author: 你的名字
 */
if (!defined('LITELOG_ROOT')) exit('error!');

Hook::addAction('template_footer', function() {
    echo '<div style="text-align:center;padding:10px;color:#999">Hello from my plugin!</div>';
});

启用:后台 → 插件 → 找到"Hello World" → 点击"启用"。
效果:打开前台任意页面,页脚出现一行文字。


二、目录结构

强制约定

插件的目录名和主文件名必须完全一致,否则无法被系统识别。

content/plugins/
└── my_plugin/              ← 目录名
    ├── my_plugin.php       ← 主文件(名字必须与目录名一致)
    ├── my_plugin_callback.php   ← 生命周期回调(可选)
    ├── my_plugin_setting.php    ← 后台设置页(可选)
    ├── my_plugin_show.php       ← 前台页面(可选)
    ├── assets/                   ← 静态资源(可选)
    │   ├── style.css
    │   └── script.js
    └── preview.jpg               ← 插件图标(可选)

命名规则

项规则示例
目录名小写字母、数字、下划线、短横线site_stats、read-progress
主文件名与目录名一致site_stats.php
函数名建议用 前缀_函数名 避免冲突SiteStats_render()
类名建议用 CamelCaseSiteStats_Helper

禁止事项

  • ❌ 不要用中文、空格、特殊符号命名
  • ❌ 不要把插件文件直接放在 content/plugins/ 根目录
  • ❌ 不要修改 LiteLog 内核文件(include/、admin/)

三、头部注释规范

主文件(插件名.php)最顶部必须包含注释头:

<?php
/**
 * Plugin Name: 插件显示名称
 * Description: 一句话描述插件功能
 * Version: 1.0.0
 * Author: 作者名
 * Author URI: https://example.com
 */

字段说明

字段必填说明
Plugin Name✅后台显示的名称(也接受 Name)
Description否一句话描述
Version否版本号,建议语义化
Author否作者名
Author URI否作者主页(也接受 Author URL)

格式要求

  • 冒号必须是英文 :
  • 注释块必须在文件最前面(前面不能有任何输出)
  • 用 /** ... / 或 / ... */
  • 描述中不要含 */

四、生命周期回调

在 插件名_callback.php 里定义以下函数,系统会在对应时机自动调用:

<?php
if (!defined('LITELOG_ROOT')) exit('error!');

/**
 * 插件启用时调用(后台点击"启用")
 * 适合:初始化默认配置、建表、写缓存
 */
function callback_init() {
    // 示例:写入默认配置
    if (Option::get('my_plugin_installed', '') === '') {
        Option::set('my_plugin_installed', time());
        Option::set('my_plugin_count', 0);
    }
}

/**
 * 插件更新时调用(后台点击"更新",目前仅在切换插件版本时触发)
 * 适合:升级数据结构
 */
function callback_up() {
    // 版本迁移逻辑
}

/**
 * 插件删除时调用(后台删除插件目录后)
 * 适合:清理数据
 */
function callback_rm() {
    // 谨慎使用:这里可以清理你写入 lt_option 的配置
    // 但不要在 init 里主动删,避免用户重新启用后丢数据
}

注意:callback_init() 里不要直接操作数据库结构(避免频繁建表),推荐只做配置初始化。


五、钩子机制

5.1 基本用法

LiteLog 使用经典的 Hook 机制,只有两个 API:

// 注册钩子:把我的函数挂到某个钩子上
Hook::addAction($hookName, $callback, $priority = 10);

// 触发钩子:由内核在特定位置调用
Hook::doAction($hookName, ...$args);

5.2 注册示例

// 匿名函数
Hook::addAction('template_footer', function() {
    echo '<p>页脚附加内容</p>';
});

// 普通函数
Hook::addAction('log_content', 'MyPlugin_renderLogFooter');
function MyPlugin_renderLogFooter($log) {
    echo '<div>文章 ID:' . (int)$log['gid'] . '</div>';
}

// 带优先级(数字小的先执行)
Hook::addAction('template_head', 'MyPlugin_css', 5);   // 先执行
Hook::addAction('template_head', 'MyPlugin_js',  20);  // 后执行

5.3 修改数据(引用传参)

很多钩子参数是通过引用传递的,你可以在插件里直接修改:

Hook::addAction('log_list_query', function(&$cond, $page, $perPage) {
    // 首页只显示指定分类的文章
    if (empty($cond['sortid'])) {
        $cond['sortid'] = 2;
    }
});

5.4 移除钩子

Hook::removeAction('template_footer', 'MyPlugin_renderLogFooter');  // 移除指定函数
Hook::removeAction('template_footer');                              // 移除该钩子所有

5.5 钩子清单(共 47 个)

见文末附录。


六、可用 API

6.1 常量

常量说明
BLOG_URL站点根 URL,带末尾斜杠,如 https://example.com/
TEMPLATE_URL当前主题的 URL,如 https://example.com/content/templates/default/
CONTENT_PATHcontent/ 目录的绝对路径(末尾带 /)
INCLUDE_PATHinclude/ 目录绝对路径
ADMIN_PATHadmin/ 目录绝对路径
LITELOG_ROOT项目根目录绝对路径
DB_PREFIX数据表前缀,默认 lt_

6.2 配置:Option

// 读取(带默认值)
$val = Option::get('site_title', 'Default');

// 写入(存在则更新,不存在则创建)
Option::set('my_plugin_count', 42);

// 读取所有 autoload=y 的配置
$all = Option::all();

约定:插件配置项命名建议加插件前缀,如 my_plugin_xxx,避免与系统配置冲突。

6.3 数据库:Database

global $DB;

// 查询(预处理防注入)
$rows = $DB->fetchAll("SELECT * FROM " . DB_PREFIX . "blog WHERE hide='n' LIMIT 10");
$row  = $DB->fetchOne("SELECT * FROM " . DB_PREFIX . "blog WHERE gid=?", [1]);
$cnt  = $DB->fetchColumn("SELECT COUNT(*) FROM " . DB_PREFIX . "comment");

// 插入
$id = $DB->insert('blog', ['title' => '标题', 'date' => time()]);

// 更新
$DB->update('blog', ['title' => '新标题'], 'gid=?', [1]);

// 删除
$DB->delete('blog', 'gid=?', [1]);

// 原始查询(返回 PDOStatement)
$stmt = $DB->query("SELECT * FROM " . DB_PREFIX . "user WHERE role=?", ['admin']);

注意:表名不要写 lt_blog,永远用 DB_PREFIX 拼接。

6.4 内置数据模型

LogModel(文章)

LogModel::query($page, $perPage, $cond, &$total);   // 分页查询
LogModel::count();                                   // 总文章数
LogModel::getOne($gid);                              // 按 ID 查
LogModel::getByAlias($alias);                        // 按别名查
LogModel::addView($gid);                             // 浏览数 +1
LogModel::add($data);                                // 新增,返回 gid
LogModel::update($gid, $data);                       // 更新
LogModel::delete($gid);                              // 删除
LogModel::latest($limit = 10);                       // 最新 N 篇
LogModel::prev($gid);                                // 上一篇
LogModel::next($gid);                                // 下一篇

SortModel(分类)

SortModel::all();
SortModel::getOne($sid);
SortModel::getByAlias($alias);
SortModel::countInSort($sid);
SortModel::add($data);
SortModel::update($sid, $data);
SortModel::delete($sid);

TagModel(标签)

TagModel::all();
TagModel::getByName($name);
TagModel::getGidsByTag($tid);
TagModel::getByGid($gid);
TagModel::attach($gid, 'tag1,tag2');    // 绑定标签
TagModel::detach($gid);                  // 解绑全部

CommentModel(评论)

CommentModel::byLog($gid);
CommentModel::countByLog($gid);
CommentModel::add($data);
CommentModel::latest($limit = 10);
CommentModel::delete($cid);

UserModel(用户)

UserModel::getOne($uid);
UserModel::getByUsername($username);
UserModel::login($username, $password);  // 返回 user 数组或 false
UserModel::update($uid, $data);

LinkModel(链接)

LinkModel::all();

6.5 URL 生成

Url::log($gid, $alias);    // https://example.com/post/123.html
Url::sort($sid, $alias);   // https://example.com/sort/tech
Url::tag($name);           // https://example.com/tag/php
Url::author($uid);         // https://example.com/author/1
Url::record('2026-10');    // https://example.com/record/2026-10

6.6 通用函数

h($str);                     // HTML 转义输出
redirect($url);              // 302 跳转并退出
makeAlias($str);             // 生成 URL 友好字符串
dateFmt($timestamp);         // 时间戳 → 'Y-m-d'
datetimeFmt($timestamp);     // 时间戳 → 'Y-m-d H:i'
stripTags($html, $len);      // 去标签 + 截断
getExcerpt($html, $len);     // 提取摘要
isLoggedIn();                // 是否已登录
checkLogin();                // 未登录则跳转到登录页
getPageNumber();             // 获取当前页码(?page=)
pagination($page, $total, $perPage);  // 生成分页 HTML

6.7 数据表结构(带 DB_PREFIX 前缀)

表主要字段
optionoption_name, option_value, autoload
useruid, username, password, nickname, email, role
sortsid, sortname, alias, pid, description
bloggid, title, date, content, content_md, excerpt, alias, author, sortid, type, views, comnum, top, hide, allow_remark
commentcid, gid, pid, date, poster, comment, email, url, ip, hide
tagtid, tagname
tag_bloggid, tid
linkid, sitename, siteurl, description
attachmentaid, gid, filename, filesize, date

七、插件设置页

在后台插件列表中,如果插件目录下有 插件名_setting.php,会自动出现"设置"入口。

约定

  • 文件路径:content/plugins/插件名/插件名_setting.php
  • 文件顶部必须 require init.php 并检查登录
  • 页面用后台的 layout.php 作为外壳
  • 提交后自己处理保存逻辑

完整示例:my_plugin_setting.php

<?php
define('LITELOG_ROOT', dirname(__DIR__, 3) . '/');
require_once LITELOG_ROOT . 'init.php';
require_once ADMIN_PATH . 'helpers.php';

checkLogin();

// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    Option::set('my_plugin_color', trim($_POST['color'] ?? '#2563eb'));
    Option::set('my_plugin_text',  trim($_POST['text']  ?? ''));
    redirect(BLOG_URL . 'admin/?action=plugins&saved=1');
}

// 读取当前值
$color = Option::get('my_plugin_color', '#2563eb');
$text  = Option::get('my_plugin_text',  '欢迎访问本站');

ob_start();
?>
<div class="card" style="max-width:640px">
  <div class="card-title">Hello 插件设置</div>

  <form method="post" class="form">
    <label>主题色</label>
    <input type="text" name="color" value="<?= h($color) ?>" placeholder="#2563eb">

    <label>展示文案</label>
    <input type="text" name="text" value="<?= h($text) ?>" placeholder="欢迎访问本站">

    <div style="margin-top:20px">
      <button class="btn" type="submit">保存设置</button>
    </div>
  </form>
</div>
<?php
$content = ob_get_clean();
include ADMIN_PATH . 'views/layout.php';

关键点

  • dirname(DIR, 3) 从 content/plugins/插件名/ 往上 3 层到项目根目录
  • 必须 checkLogin() 防止未登录访问
  • 用 admin/helpers.php 提供的 adminIcon()、navLink() 保持风格一致
  • 保存后 redirect() 防止表单重复提交

八、插件前台页面

如果插件需要前台页面(如"留言板"、"统计页"),用 ?action=xxx 触发。

8.1 前端页面钩子

系统在 Dispatcher 里会触发:

Hook::doAction('public_action_' . $act);

其中 $act 是 URL 中 ?action= 的值(只保留 [a-z0-9_])。

8.2 注册示例

// 访问 https://example.com/?action=my_plugin_show 时触发
Hook::addAction('public_action_my_plugin_show', function() {
    // 输出你的页面 HTML
    header('Content-Type: text/html; charset=utf-8');
    echo '<!DOCTYPE html><html><body>';
    echo '<h1>这是插件自定义页面</h1>';
    echo '</body></html>';
    exit;
});

8.3 更规范的做法:独立渲染文件

my_plugin.php:

Hook::addAction('public_action_my_plugin_show', 'MyPlugin_showPage');

function MyPlugin_showPage() {
    include __DIR__ . '/my_plugin_show.php';
    exit;
}

my_plugin_show.php:

<?php
if (!defined('LITELOG_ROOT')) exit('error!');
$page_title = '留言板 - ' . Option::get('site_title');
include View::resolve('header');   // 复用主题 header
?>
<div class="layout">
  <div class="main-col">
    <div class="post-single">
      <h1>留言板</h1>
      <p>这是插件自定义的前台页面。</p>
    </div>
  </div>
  <aside class="side-col">
    <?php include View::resolve('side'); ?>
  </aside>
</div>
<?php include View::resolve('footer'); ?>

访问:https://example.com/?action=my_plugin_show


九、数据存储

9.1 使用 Option(推荐)

适合:简单的键值配置(颜色、开关、文本)

Option::set('my_plugin_key', 'value');
$val = Option::get('my_plugin_key', 'default');

优点:不需要建表;缺点:不适合大量数据。

9.2 使用自定义表

适合:需要保存大量记录(如点赞记录、浏览日志)

在 callback_init() 里建表:

function callback_init() {
    global $DB;
    $sql = "CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "my_plugin_log` (
        `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        `gid` INT UNSIGNED NOT NULL,
        `ip` VARCHAR(45) DEFAULT '',
        `created_at` BIGINT DEFAULT 0,
        KEY `idx_gid` (`gid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
    $DB->query($sql);
}

优点:结构清晰、查询高效;缺点:需要处理建表和清理。

9.3 存储路径

  • 文件:CONTENT_PATH . 'uploads/插件名/'
  • 缓存:CONTENT_PATH . 'cache/插件名_xxx.php'(用 $CACHE 对象或直接文件读写)

十、完整示例

功能:文章点赞插件。前台显示点赞按钮,点击后计数 +1,同一 IP 24 小时内只能点一次。

目录结构

content/plugins/like/
├── like.php
└── like_callback.php

like.php

<?php
/**
 * Plugin Name: 文章点赞
 * Description: 为每篇文章添加点赞按钮,防刷
 * Version: 1.0.0
 * Author: LiteLog
 * Author URI: https://example.com
 */

if (!defined('LITELOG_ROOT')) exit('error!');

// 1. 在文章内容后插入点赞按钮
Hook::addAction('log_content_after', 'Like_renderButton');

// 2. 处理 AJAX 请求
Hook::addAction('public_action_like_do', 'Like_handleAjax');


function Like_renderButton($log) {
    global $DB;
    $gid = (int)$log['gid'];
    $count = (int)$DB->fetchColumn(
        "SELECT COUNT(*) FROM " . DB_PREFIX . "my_like WHERE gid=?",
        [$gid]
    );
    $liked = Like_hasLiked($gid);
    ?>
    <div class="like-wrap" data-gid="<?= $gid ?>" style="margin:24px 0;text-align:center">
      <button class="like-btn<?= $liked ? ' liked' : '' ?>"
              data-gid="<?= $gid ?>"
              style="padding:10px 28px;border:1px solid <?= $liked ? '#2563eb' : '#e5e9f0' ?>;
                     background:<?= $liked ? '#2563eb' : '#fff' ?>;
                     color:<?= $liked ? '#fff' : '#475569' ?>;
                     border-radius:24px;cursor:pointer;font-size:15px;font-family:inherit;
                     transition:all .15s">
        ❤ 点赞 <span class="like-count"><?= $count ?></span>
      </button>
      <p style="color:#94a3b8;font-size:12.5px;margin:8px 0 0">每 24 小时可点赞一次</p>
    </div>
    <script>
    (function(){
      var btn = document.querySelector('.like-btn[data-gid="<?= $gid ?>"]');
      if (!btn) return;
      btn.addEventListener('click', function () {
        if (btn.disabled) return;
        btn.disabled = true;
        fetch('<?= BLOG_URL ?>?action=like_do&gid=<?= $gid ?>', { credentials: 'same-origin' })
          .then(function(r){ return r.json(); })
          .then(function(res){
            if (res.ok) {
              btn.classList.add('liked');
              btn.style.background = '#2563eb';
              btn.style.color = '#fff';
              btn.style.borderColor = '#2563eb';
              btn.querySelector('.like-count').textContent = res.count;
            } else {
              alert(res.msg || '点赞失败');
            }
          })
          .catch(function(){ alert('网络错误'); })
          .finally(function(){ btn.disabled = false; });
      });
    })();
    </script>
    <?php
}


function Like_handleAjax() {
    header('Content-Type: application/json; charset=utf-8');

    $gid = (int)($_GET['gid'] ?? 0);
    if (!$gid) {
        echo json_encode(['ok' => false, 'msg' => '参数错误'], JSON_UNESCAPED_UNICODE);
        exit;
    }

    global $DB;

    if (Like_hasLiked($gid)) {
        $count = (int)$DB->fetchColumn("SELECT COUNT(*) FROM " . DB_PREFIX . "my_like WHERE gid=?", [$gid]);
        echo json_encode(['ok' => false, 'msg' => '24 小时内已经点过啦', 'count' => $count], JSON_UNESCAPED_UNICODE);
        exit;
    }

    $DB->insert('my_like', [
        'gid'        => $gid,
        'ip'         => $_SERVER['REMOTE_ADDR'] ?? '',
        'created_at' => time(),
    ]);

    $count = (int)$DB->fetchColumn("SELECT COUNT(*) FROM " . DB_PREFIX . "my_like WHERE gid=?", [$gid]);

    // 记录到 session 防刷
    $_SESSION['like_' . $gid] = time();

    echo json_encode(['ok' => true, 'count' => $count], JSON_UNESCAPED_UNICODE);
    exit;
}


/**
 * 判断当前用户对该文章是否已点赞
 */
function Like_hasLiked($gid) {
    global $DB;
    $ip = $_SERVER['REMOTE_ADDR'] ?? '';
    $row = $DB->fetchOne(
        "SELECT created_at FROM " . DB_PREFIX . "my_like WHERE gid=? AND ip=? ORDER BY id DESC LIMIT 1",
        [$gid, $ip]
    );
    if (!$row) return false;
    return time() - (int)$row['created_at'] < 86400;
}

like_callback.php

<?php
if (!defined('LITELOG_ROOT')) exit('error!');

function callback_init() {
    global $DB;
    $sql = "CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "my_like` (
        `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        `gid` INT UNSIGNED NOT NULL,
        `ip` VARCHAR(45) DEFAULT '',
        `created_at` BIGINT DEFAULT 0,
        KEY `idx_gid` (`gid`),
        KEY `idx_ip` (`ip`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
    $DB->query($sql);
}

function callback_rm() {
    // 用户删除插件时不自动删表(防止误删数据)
    // 若需要,可在此处提示用户在数据库中手动清理:lt_my_like
}

使用

  1. 上传 content/plugins/like/ 目录
  2. 后台 → 插件 → 启用"文章点赞"
  3. 打开前台任意文章,看到点赞按钮
  4. 点击 → 数字 +1

十一、开发规范

11.1 命名规范

类型规则示例
钩子名小写 + 下划线template_footer
函数名插件名首字母大写_功能Like_renderButton
类名CamelCaseLikeHelper
常量PLUGIN_NAME_XXXLIKE_CACHE_TTL
Option 键插件名_xxxlike_color
数据表带前缀,建议 my_xxxlt_my_like

11.2 安全规范

  • ✅ 所有输出用 h() 转义:echo h($userInput)
  • ✅ 所有 SQL 用参数化:$DB->fetchOne("...", [$param])
  • ✅ 检查登录:敏感操作前调用 checkLogin()
  • ✅ POST 提交加校验:$_SERVER['REQUEST_METHOD'] === 'POST'
  • ❌ 不要直接 echo $_GET['x']
  • ❌ 不要把用户输入拼进 SQL
  • ❌ 不要在插件里 eval()、exec()、system()

11.3 性能规范

  • 避免在每个请求里都 SELECT * 查大表,用 COUNT 或加 LIMIT
  • 频繁读取的配置用 Option::get()(有内存缓存)
  • 频繁查询的结果用 $CACHE 或 $_SESSION 缓存
  • 图片/JS/CSS 放 assets/ 目录,用 BLOG_URL 引用

11.4 兼容规范

  • 不要修改内核文件(include/、admin/)
  • 不要覆盖主题文件(除非是主题插件)
  • 不要用 exit() 之外的输出终止:钩子里 echo 就直接输出;public_action_* 里要 exit
  • 不要在钩子里抛异常:用 try/catch 包起来,避免整个页面 500

十二、常见问题

Q1:插件启用后没反应

  1. 检查目录结构:content/plugins/你的插件名/你的插件名.php
  2. 检查后台插件列表是否显示"已启用"
  3. 临时打开 init.php 里的 display_errors 看有没有报错
  4. 检查钩子名拼写是否与附录一致

Q2:钩子函数没被调用

  1. 钩子名是否完全一致(大小写敏感)
  2. 参数列表是否匹配(引用参数用 &$var)
  3. 插件是否真的启用了(后台查看)

Q3:想在钩子里修改数据,但没生效

钩子参数中标注 &$var 的是引用传参,可以修改;其他是值传递。检查你注册的回调里参数是否带了 &。

Q4:插件和主题都想用钩子

可以。主题文件里同样可以 Hook::addAction(),也能主动调用 Hook::doAction('自定义钩子')。若你的主题想提供专属钩子,用 主题名_xxx 命名,避免与内核冲突。

Q5:如何调试插件?

临时打开调试:编辑 init.php,把

error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_WARNING);
ini_set('display_errors', 0);

改为

error_reporting(E_ALL);
ini_set('display_errors', 1);

然后在插件里 var_dump($data); exit; 查看数据。

Q6:插件和插件之间怎么交互?

两个插件可以互相触发自定义钩子:

// 插件 A:触发
Hook::doAction('my_shared_hook', $data);

// 插件 B:监听
Hook::addAction('my_shared_hook', 'PluginB_handler');

约定:自定义钩子名建议 插件名_xxx 前缀,避免与内核冲突。


附录:钩子速查表

请求生命周期

钩子参数说明
dispatch_start—请求最开始
dispatch_matched&$matched路由匹配后,可改写 controller/method
dispatch_end—请求结束
public_action_{name}—?action=name 触发

首页 / 文章列表

钩子参数说明
index_head—首页头部
log_list_query&$cond, $page, $perPage查询条件(可改)
log_list_result&$logs, $page, $total查询结果(可改)
list_page_before$pageType列表页顶部
list_item_before$log, $pageType每个文章项之前
list_item_after$log, $pageType每个文章项之后
list_page_after$pageType列表页底部

文章详情

钩子参数说明
log_display_before&$log详情页数据加载后、渲染前
log_display_after&$log详情页渲染前的最后一步
log_view_before$log正文区前(HTML 中)
log_view_after$log正文区后
log_content$log内容区(通用)
log_content_before$log正文 HTML 之前
log_content_after$log正文 HTML 之后

分类 / 标签 / 作者 / 归档

钩子参数说明
sort_display_query$sort, &$cond, $page, $perPage分类页查询前
sort_display$sort, &$logs分类页渲染前
tag_display_query$tag, &$cond, $page, $perPage标签页查询前
tag_display$tag, &$logs标签页渲染前
author_display_query$author, &$cond, $page, $perPage作者页查询前
author_display$author, &$logs作者页渲染前
record_display_query$year, $month, &$cond, $page, $perPage归档页查询前
record_display$year, $month, &$logs归档页渲染前

评论

钩子参数说明
comment_save_before&$data提交前(可拦截)
comment_save_after$cid, $data入库后
comment_display_before$comment前台每条评论前
comment_display_after$comment前台每条评论后
comment_form_before$log评论表单之前
comment_form_fields$log表单内字段区
comment_form_after$log评论表单之后

模板(前台)

钩子参数说明
template_head—<head> 内,CSS 后
header_meta—meta 标签之后
header_after—</header> 之后
before_content—主内容区之前
after_content—主内容区之后
sidebar_before—侧栏之前
sidebar_after—侧栏之后
footer_before—页脚之前
template_footer—页脚内部(脚本)

后台

钩子参数说明
admin_request$action后台请求开始
admin_head—后台 <head>
admin_menu$activeKey侧栏菜单(可追加)
admin_footer—后台 </body> 前
admin_dashboard_widgets—概览页小工具区
log_save_before&$data, $gid文章保存前(前台也触发)
log_save_after$gid, $data文章保存后
admin_log_save_before&$data, $gid后台保存文章前
admin_log_save_after$gid, $data后台保存文章后
log_delete_before$gid文章删除前
log_delete_after$gid文章删除后

用户 / 扩展管理

钩子参数说明
user_login$user登录成功
user_logout$uid登出前
plugin_activate$name插件启用后
plugin_deactivate$name插件停用后
theme_activate$name, $old主题切换后
option_save$key, $value系统配置保存时

结语

开发一个 LiteLog 插件的步骤:

  1. 在 content/plugins/ 下建目录,名字用英文小写
  2. 创建同名主文件,顶部写 Plugin Name 等注释
  3. 用 Hook::addAction('钩子名', '函数名') 挂载功能
  4. 需要初始化数据 → 加 插件名_callback.php 的 callback_init()
  5. 需要配置界面 → 加 插件名_setting.php
  6. 需要前台页面 → 挂 public_action_xxx,或加 插件名_show.php
  7. 后台 → 插件 → 启用

调试三件套:

  • 临时打开 display_errors
  • var_dump() / error_log()
  • 浏览器 F12 看 Console / Network

记住三不要:

  • ❌ 不改内核
  • ❌ 不裸拼 SQL
  • ❌ 不直接 echo 用户输入

评论 (0)

还没有评论,来说点什么吧~

发表评论

正在回复
已有账号?登录后评论