Typecho 透過「插件鉤子」讓插件在核心關鍵節點介入執行流程。鉤子本質是物件的公共屬性,儲存可呼叫的回調 (函數、靜態方法、物件方法)。
| 鉤子名稱 | 定義位置 | 觸發時機 | 傳遞參數 |
|---|---|---|---|
| finishPublish | Widget/Contents/Post/Edit.php Widget/Contents/Page/Edit.php | 文章/頁面發布完成 | ($contents, $widget) |
| handle | Widget/Archive.php | 路由匹配後、輸出前 | ($archive) |
| header | Widget/Archive.php | 輸出 HTML Head 前 | ($archive) |
| footer | Widget/Archive.php | 輸出 HTML Foot 前 | ($archive) |
| comment.post | Widget/Feedback.php | 評論提交後 | ($comment, $widget) |
| widgets.render | Widget/Widget.php | 側邊欄 Widget 輸出時 | ($widget) |
<?php
// 在 activate() 中掛載
public static function activate() {
// 靜態方法掛載
Typecho_Plugin::factory('Widget_Contents_Post_Edit')->finishPublish =
array('MyPlugin', 'onPublish');
// 物件方法掛載 (需先實例化)
$instance = new MyPlugin();
Typecho_Plugin::factory('Widget_Archive')->handle =
array($instance, 'onHandle');
// 匿名函數掛載 (PHP 5.3+)
Typecho_Plugin::factory('Widget_Feedback')->comment.post =
function($comment, $widget) {
// 邏輯...
};
return '插件啟用成功,請至設定頁配置';
}
?>
// 簡化版核心原理
class Typecho_Plugin {
public static function factory($widgetClass) {
// 取得或建立 Widget 類別的代理物件
if (!isset(self::$widgets[$widgetClass])) {
self::$widgets[$widgetClass] = new Typecho_Plugin_Widget($widgetClass);
}
return self::$widgets[$widgetClass];
}
}
class Typecho_Plugin_Widget {
private $widgetClass;
private $hooks = [];
public function __set($hookName, $callback) {
// 驗證 callback 可呼叫
if (!is_callable($callback)) {
throw new Typecho_Plugin_Exception("鉤子 $hookName 回調不可呼叫");
}
$this->hooks[$hookName] = $callback;
}
public function __get($hookName) {
return $this->hooks[$hookName] ?? null;
}
// Widget 內部觸發鉤子時:
public function pluginHandle($hookName, ...$args) {
if (isset($this->hooks[$hookName])) {
return call_user_func_array($this->hooks[$hookName], $args);
}
}
}
// Widget/Contents/Post/Edit.php 第 736 行左右
public function finishPublish($contents) {
// ... 存檔邏輯 ...
// 觸發插件鉤子
$this->pluginHandle('finishPublish', $contents, $this);
return $contents;
}
同一鉤子可被多個插件掛載,按掛載順序依序執行。若需控制順序:
// 插入陣列最前 (優先執行)
$hooks = &Typecho_Plugin::factory('Widget_Archive')->handle;
array_unshift($hooks, array('HighPriorityPlugin', 'onHandle'));
// 插入陣列最後 (最後執行)
$hooks[] = array('LowPriorityPlugin', 'onHandle');
public static function deactivate() {
// 手動移除 (通常不需要,停用插件會自動清理)
$factory = Typecho_Plugin::factory('Widget_Contents_Post_Edit');
unset($factory->finishPublish);
return '插件已停用';
}
finishPublish、header、footer)handle 可返回 false 阻止預設處理)function(&$contents, $widget)on (如 onPublish、onHandle)function(array $contents, Widget_Interface $widget)try-catch 包裹,避免單一插件錯誤導致整站崩潰handle) 避免重度運算,必要時快取