外掛是 WordPress 擴充功能的主要方式。當功能不應該跟著主題一起消失,就應該考慮寫成外掛。本章從最小外掛開始,介紹外掛檔頭、啟用、Hook 與內容過濾範例。

本章學習目標

能建立可啟用的外掛,理解 Action 與 Filter,並用外掛修改文章內容。

11.1 WordPress 外掛入門

外掛至少需要一個 PHP 檔案與外掛檔頭。放在 wp-content/plugins 後,WordPress 會讀取檔頭並顯示在外掛列表。

<?php
/*
Plugin Name: WP2026 Demo Plugin
Description: 課程示範外掛。
Version: 1.0.0
Author: minhuangyuntech
*/

外掛與主題的分工

主題負責外觀,外掛負責功能。若功能與網站資料、商業邏輯、後台管理或 API 有關,通常應放在外掛。

建議的外掛資料夾結構

當外掛只有一個小功能時,一個 PHP 檔案就足夠;但只要開始加入後台頁面、前台樣式、短代碼或 AJAX,就應該拆分檔案,避免主檔變得難以維護。

wp2026-demo-plugin/
├── wp2026-demo-plugin.php
├── includes/
│   ├── class-assets.php
│   ├── class-shortcodes.php
│   └── class-admin-page.php
├── assets/
│   ├── css/
│   └── js/
└── languages/

主檔通常負責檢查安全常數、定義版本與路徑常數、載入必要檔案,並啟動外掛。

if (!defined('ABSPATH')) {
    exit;
}

define('WP2026_PLUGIN_VERSION', '1.0.0');
define('WP2026_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WP2026_PLUGIN_URL', plugin_dir_url(__FILE__));

啟用、停用與移除

外掛可在啟用、停用或移除時執行特定工作。例如建立預設選項、清除排程事件或刪除暫存資料。

register_activation_hook(__FILE__, 'wp2026_activate_plugin');
function wp2026_activate_plugin() {
    add_option('wp2026_enabled', 'yes');
}

register_deactivation_hook(__FILE__, 'wp2026_deactivate_plugin');
function wp2026_deactivate_plugin() {
    wp_clear_scheduled_hook('wp2026_daily_event');
}

若要在外掛刪除時清除資料,應建立 uninstall.php,但要小心不要誤刪使用者仍想保留的資料。

外掛安全基本原則

  • 檔案開頭檢查 ABSPATH,避免被直接執行。
  • 後台操作檢查 current_user_can()
  • 表單與 AJAX 操作檢查 nonce。
  • 所有輸入先 sanitize,所有輸出再 escape。
  • 資料庫查詢使用 WordPress API 或 $wpdb->prepare()

11.2 Hooks 簡介與應用

Hook 是 WordPress 的擴充機制。Action 用來在特定時間點執行動作,Filter 用來接收資料、修改後再回傳。

Action 範例

add_action('wp_footer', 'wp2026_footer_note');
function wp2026_footer_note() {
    echo '<div class="wp2026-note">感謝閱讀 WP2026</div>';
}

Action 的重點是「在某個時機做某件事」。常見 action 包含:

  • init:註冊 CPT、taxonomy 或初始化功能。
  • wp_enqueue_scripts:載入前台樣式與腳本。
  • admin_menu:加入後台選單。
  • save_post:文章儲存時處理自訂欄位。

Filter 範例

add_filter('the_content', 'wp2026_append_message');
function wp2026_append_message($content) {
    if (is_single()) {
        $content .= '<p>本文由 WP2026 外掛加入。</p>';
    }
    return $content;
}

Filter 的重點是「接收資料、修改資料、回傳資料」。如果忘記回傳,原本內容可能消失。Filter callback 的參數數量可透過 add_filter() 第四個參數指定。

add_filter('the_title', 'wp2026_prefix_title', 10, 2);
function wp2026_prefix_title($title, $post_id) {
    if (is_admin()) {
        return $title;
    }
    return 'WP2026:' . $title;
}

優先順序與移除 Hook

add_action()add_filter() 的第三個參數是優先順序,數字越小越早執行,預設是 10。若要移除 hook,必須使用相同 callback 與 priority。

add_filter('the_content', 'wp2026_append_message', 20);
remove_filter('the_content', 'wp2026_append_message', 20);

在大型專案中,清楚記錄 hook 的 priority 能避免多個外掛互相覆蓋輸出。

11.3 使用外掛過濾文章的內容範例

過濾文章內容時要注意只在合適頁面執行,並避免影響後台、摘要或 REST API 回應。

加入條件與安全輸出

function wp2026_append_course_cta($content) {
    if (!is_singular('post') || !in_the_loop() || !is_main_query()) {
        return $content;
    }
    $cta = '<aside class="course-cta">繼續學習下一章</aside>';
    return $content . $cta;
}
add_filter('the_content', 'wp2026_append_course_cta');

把 HTML 組合抽成函式

當附加內容變複雜時,不建議直接在 filter callback 中串接一大段 HTML。可以把 HTML 產生邏輯抽成另一個函式,讓主流程更清楚。

function wp2026_render_course_cta() {
    $next_url = home_url('/lessons/');
    return sprintf(
        '<aside class="course-cta"><a href="%s">%s</a></aside>',
        esc_url($next_url),
        esc_html('查看所有課程')
    );
}

避免重複加入內容

某些頁面建構器或特殊模板可能多次呼叫 the_content。如果你的外掛不能重複輸出,可使用靜態變數保護。

function wp2026_append_once($content) {
    static $added = false;
    if ($added) {
        return $content;
    }
    $added = true;
    return $content . wp2026_render_course_cta();
}

本章練習

  1. 建立一個可啟用外掛。
  2. 在文章結尾加入課程提示區塊。
  3. 限制提示只出現在單篇文章頁。
  4. 停用外掛並確認提示消失。