mcpField
W
wordpress-plugin-core

wordpress-plugin-core

github.com/jackspace开发1639 次安装12 周前更新
代码仓库

关于 wordpress-plugin-core

wordpress-plugin-core 是面向 WordPress 插件开发者的开发技能,涵盖插件结构、核心实现模式及安全实践。它通过示例说明如何处理权限、输入输出、请求验证和数据库查询。

主要功能

  1. 01介绍简单、面向对象和 PSR-4 结构
  2. 02讲解插件头部与安全基础
  3. 03覆盖权限检查及 nonce 验证
  4. 04说明输入清理与输出转义
  5. 05提供安全的数据库查询模式
  6. 06整理常见缺陷及预防方式

适用场景

  1. 01规划 WordPress 插件代码结构
  2. 02实现表单或 AJAX 请求处理
  3. 03为用户输入和页面输出添加安全处理
  4. 04编写带用户输入的数据库查询

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jackspace/claudeskillz --skill wordpress-plugin-core

安装后在 Claude Code 中输入/wordpress-plugin-core确认已加载。

附带文件README.mdSKILL.json

详细介绍

WordPress 插件开发(核心)

状态:已可用于生产环境 最近更新:2025-11-06 依赖项:无(WordPress 5.9+、PHP 7.4+) 最新版本:推荐使用 WordPress 6.7+、PHP 8.0+


快速入门(10 分钟)

1. 选择插件结构

WordPress 插件可以采用三种架构模式:

  • 简单型(仅函数)——适用于函数少于 5 个的小型插件
  • OOP(面向对象)——适用于功能相关的中型插件
  • PSR-4(命名空间 + Composer 自动加载)——适用于大型或现代插件

为什么这很重要:

  • 简单型插件最容易上手,但扩展性不佳
  • OOP 提供了组织结构,但不使用现代 PHP 特性
  • PSR-4 是现代标准(2025 年),也是最易维护的方式
2. 创建插件头部信息

每个插件的主文件中都必须包含头部注释:

<?php
/**
 * Plugin Name:       My Awesome Plugin
 * Plugin URI:        https://example.com/my-plugin/
 * Description:       Brief description of what this plugin does.
 * Version:           1.0.0
 * Requires at least: 5.9
 * Requires PHP:      7.4
 * Author:            Your Name
 * Author URI:        https://yoursite.com/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       my-plugin
 * Domain Path:       /languages
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

重要提示:

  • Plugin Name 是唯一必填字段
  • Text Domain 必须与插件 slug 完全一致(用于翻译)
  • 务必添加 ABSPATH 检查,防止直接访问文件
3. 构建安全基础

在编写任何功能之前,先落实以下 5 项基本安全措施:

// 1. Unique Prefix (4-5 chars minimum)
define( 'MYPL_VERSION', '1.0.0' );

function mypl_init() {
    // Your code
}
add_action( 'init', 'mypl_init' );

// 2. ABSPATH Check (every PHP file)
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// 3. Nonces for Forms
<input type="hidden" name="mypl_nonce" value="<?php echo wp_create_nonce( 'mypl_action' ); ?>" />

// 4. Sanitize Input, Escape Output
$clean = sanitize_text_field( $_POST['input'] );
echo esc_html( $output );

// 5. Prepared Statements for Database
global $wpdb;
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}table WHERE id = %d",
        $id
    )
);

安全基础的 5 个步骤

WordPress 插件安全包含三个必须全部落实的组成部分:

步骤 1:所有内容都使用唯一前缀

原因:避免与其他插件和 WordPress 核心发生命名冲突。

规则:

  • 至少使用 4-5 个字符
  • 应用于:函数、类、常量、选项、瞬态、元数据键、全局变量
  • 避免使用:wp_、__、_、"WordPress"
// GOOD
function mypl_function_name() {}
class MyPL_Class_Name {}
define( 'MYPL_CONSTANT', 'value' );
add_option( 'mypl_option', 'value' );
set_transient( 'mypl_cache', $data, HOUR_IN_SECONDS );

// BAD
function function_name() {}  // No prefix, will conflict
class Settings {}  // Too generic
步骤 2:检查权限,而不只是管理员状态

错误做法:使用 is_admin() 检查权限

// WRONG - Anyone can access admin area URLs
if ( is_admin() ) {
    // Delete user data - SECURITY HOLE
}

// CORRECT - Check user capability
if ( current_user_can( 'manage_options' ) ) {
    // Delete user data - Now secure
}

常见权限:

  • manage_options - 管理员
  • edit_posts - 编辑者/作者
  • publish_posts - 作者
  • edit_pages - 编辑者
  • read - 订阅者
步骤 3:安全三要素

输入 → 处理 → 输出,每个阶段都需要使用不同的函数:

// SANITIZATION (Input) - Clean user data
$name = sanitize_text_field( $_POST['name'] );
$email = sanitize_email( $_POST['email'] );
$url = esc_url_raw( $_POST['url'] );
$html = wp_kses_post( $_POST['content'] );  // Allow safe HTML
$key = sanitize_key( $_POST['option'] );
$ids = array_map( 'absint', $_POST['ids'] );  // Array of integers

// VALIDATION (Logic) - Verify it meets requirements
if ( ! is_email( $email ) ) {
    wp_die( 'Invalid email' );
}

// ESCAPING (Output) - Make safe for display
echo esc_html( $name );
echo '<a href="' . esc_url( $url ) . '">';
echo '<div class="' . esc_attr( $class ) . '">';
echo '<textarea>' . esc_textarea( $content ) . '</textarea>';

关键规则:对输入进行清理,对输出进行转义。绝不信任用户数据。

步骤 4:Nonce(CSRF 防护)

说明:一次性令牌,用于证明请求来自你的网站。

表单模式:

// Generate nonce in form
<form method="post">
    <?php wp_nonce_field( 'mypl_action', 'mypl_nonce' ); ?>
    <input type="text" name="data" />
    <button type="submit">Submit</button>
</form>

// Verify nonce in handler
if ( ! isset( $_POST['mypl_nonce'] ) || ! wp_verify_nonce( $_POST['mypl_nonce'], 'mypl_action' ) ) {
    wp_die( 'Security check failed' );
}

// Now safe to proceed
$data = sanitize_text_field( $_POST['data'] );

AJAX 模式:

// JavaScript
jQuery.ajax({
    url: ajaxurl,
    data: {
        action: 'mypl_ajax_action',
        nonce: mypl_ajax_object.nonce,
        data: formData
    }
});
// PHP Handler
function mypl_ajax_handler() {
    check_ajax_referer( 'mypl-ajax-nonce', 'nonce' );

    // Safe to proceed
    wp_send_json_success( array( 'message' => 'Success' ) );
}
add_action( 'wp_ajax_mypl_ajax_action', 'mypl_ajax_handler' );

// Localize script with nonce
wp_localize_script( 'mypl-script', 'mypl_ajax_object', array(
    'ajaxurl' => admin_url( 'admin-ajax.php' ),
    'nonce'   => wp_create_nonce( 'mypl-ajax-nonce' ),
) );
步骤 5:使用数据库预处理语句

重要提示:对包含用户输入的查询,务必使用 $wpdb->prepare()。

global $wpdb;

// WRONG - SQL Injection vulnerability
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}table WHERE id = {$_GET['id']}" );

// CORRECT - Prepared statement
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}table WHERE id = %d",
        $_GET['id']
    )
);

占位符:

  • %s - 字符串
  • %d - 整数
  • %f - 浮点数

LIKE 查询(特殊情况):

$search = '%' . $wpdb->esc_like( $term ) . '%';
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}posts WHERE post_title LIKE %s",
        $search
    )
);

关键规则

始终做到

✅ 使用唯一前缀(4-5 个字符)标识所有全局代码(函数、类、选项、瞬态) ✅ 在每个 PHP 文件中添加 ABSPATH 检查:if ( ! defined( 'ABSPATH' ) ) exit; ✅ 检查权限(current_user_can()),而不只是调用 is_admin() ✅ 验证所有表单和 AJAX 请求的 nonce ✅ 对所有包含用户输入的数据库查询使用 $wpdb->prepare() ✅ 保存前使用 sanitize_*() 函数清理输入 ✅ 显示前使用 esc_*() 函数转义输出 ✅ 注册自定义文章类型时,在激活时刷新重写规则 ✅ 使用 uninstall.php 进行永久清理(而非停用钩子) ✅ 遵循 WordPress 编码标准(使用制表符缩进、采用 Yoda 条件)

绝不这样做

❌ 绝不使用 extract() ——这会造成安全漏洞 ❌ 绝不在未清理的情况下信任 $_POST/$_GET ❌ 绝不将用户输入拼接到 SQL 中——务必使用 prepare() ❌ 绝不单独使用 is_admin() 检查权限 ❌ 绝不输出未经清理的数据——务必转义 ❌ 绝不使用通用函数名或类名——务必添加前缀 ❌ 绝不使用短 PHP 标签 <? 或 <?= ——只能使用 <?php ❌ 绝不在停用时删除用户数据——仅在卸载时删除 ❌ 绝不重复注册卸载钩子——仅在激活时注册一次 ❌ 绝不在主流程中使用 register_uninstall_hook()——改用 uninstall.php


预防已知问题

此技能可预防 20 个已有文档记录的问题:

问题 #1:SQL 注入

错误:因未转义的用户输入导致数据库遭到入侵 来源:https://patchstack.com/articles/sql-injection/(占所有漏洞的 15%) 发生原因:将用户输入直接拼接到 SQL 查询中 预防措施:始终使用带占位符的 $wpdb->prepare()

// VULNERABLE
$wpdb->query( "DELETE FROM {$wpdb->prefix}table WHERE id = {$_GET['id']}" );

// SECURE
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}table WHERE id = %d", $_GET['id'] ) );
问题 #2:XSS(跨站脚本攻击)

错误:恶意 JavaScript 在用户浏览器中执行 来源:https://patchstack.com(占所有漏洞的 35%) 发生原因:将未经净化的用户数据输出到 HTML 中 预防措施:始终使用适合上下文的函数转义输出

// VULNERABLE
echo $_POST['name'];
echo '<div class="' . $_POST['class'] . '">';

// SECURE
echo esc_html( $_POST['name'] );
echo '<div class="' . esc_attr( $_POST['class'] ) . '">';
问题 #3:CSRF(跨站请求伪造)

错误:未经授权的操作以用户名义执行 来源:https://blog.nintechnet.com/25-wordpress-plugins-vulnerable-to-csrf-attacks/ 发生原因:未验证请求是否来自你的网站 预防措施:使用 wp_nonce_field() 和 wp_verify_nonce() 创建并验证 nonce

// VULNERABLE
if ( $_POST['action'] == 'delete' ) {
    delete_user( $_POST['user_id'] );
}

// SECURE
if ( ! wp_verify_nonce( $_POST['nonce'], 'mypl_delete_user' ) ) {
    wp_die( 'Security check failed' );
}
delete_user( absint( $_POST['user_id'] ) );
问题 #4:缺少能力检查

错误:普通用户可以访问管理功能 来源:WordPress 安全审查指南 发生原因:使用 is_admin() 而不是 current_user_can() 预防措施:始终检查用户能力,而不只是管理上下文

// VULNERABLE
if ( is_admin() ) {
    // Any logged-in user can trigger this
}

// SECURE
if ( current_user_can( 'manage_options' ) ) {
    // Only administrators can trigger this
}
问题 #5:直接访问文件

错误:PHP 文件在 WordPress 上下文之外执行 来源:WordPress 插件手册 发生原因:文件顶部没有 ABSPATH 检查 预防措施:在每个 PHP 文件中添加 ABSPATH 检查

// Add to top of EVERY PHP file
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}
问题 #6:前缀冲突

错误:函数或类与其他插件发生冲突 来源:WordPress 编码标准 发生原因:使用没有唯一前缀的通用名称 预防措施:为所有全局代码使用 4–5 个字符的前缀

// CAUSES CONFLICTS
function init() {}
class Settings {}
add_option( 'api_key', $value );

// SAFE
function mypl_init() {}
class MyPL_Settings {}
add_option( 'mypl_api_key', $value );
问题 #7:未刷新重写规则

错误:自定义文章类型返回 404 错误 来源:WordPress 插件手册 发生原因:注册 CPT 后忘记刷新重写规则 预防措施:激活时刷新,停用时清除

function mypl_activate() {
    mypl_register_cpt();
    flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'mypl_activate' );

function mypl_deactivate() {
    flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'mypl_deactivate' );
问题 #8:未清理临时数据

错误:数据库中累积了过期的临时数据 来源:WordPress Transients API 文档 发生原因:卸载时没有进行清理 预防措施:在 uninstall.php 中删除临时数据

// uninstall.php
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    exit;
}

global $wpdb;
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_mypl_%'" );
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_mypl_%'" );
问题 #9:脚本在所有页面加载

错误:不必要地加载资源导致性能下降 来源:WordPress 性能最佳实践 发生原因:添加脚本或样式时没有进行条件检查 预防措施:仅在需要的地方加载资源

// BAD - Loads on every page
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_script( 'mypl-script', $url );
} );

// GOOD - Only loads on specific page
add_action( 'wp_enqueue_scripts', function() {
    if ( is_page( 'my-page' ) ) {
        wp_enqueue_script( 'mypl-script', $url, array( 'jquery' ), '1.0', true );
    }
} );
问题 #10:保存时缺少数据净化

错误:恶意数据被存入数据库 来源:WordPress 数据验证 发生原因:保存未经净化的 $_POST 数据 预防措施:保存前始终净化数据

// VULNERABLE
update_option( 'mypl_setting', $_POST['value'] );

// SECURE
update_option( 'mypl_setting', sanitize_text_field( $_POST['value'] ) );
问题 #11:LIKE 查询不正确

错误:SQL 语法错误或注入漏洞 来源:WordPress $wpdb 文档 发生原因:LIKE 通配符未正确转义 预防措施:使用 $wpdb->esc_like()

// WRONG
$search = '%' . $term . '%';

// CORRECT
$search = '%' . $wpdb->esc_like( $term ) . '%';
$results = $wpdb->get_results( $wpdb->prepare( "... WHERE title LIKE %s", $search ) );
问题 #12:使用 extract()

错误:变量冲突和安全漏洞 来源:WordPress 编码标准 发生原因:extract() 会根据数组键创建变量 预防措施:切勿使用 extract(),应直接访问数组元素

// DANGEROUS
extract( $_POST );
// Now $any_array_key becomes a variable

// SAFE
$name = isset( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : '';
问题 #13:REST API 缺少权限回调

错误:任何人都可以访问端点 来源:WordPress REST API 手册 发生原因:未指定 permission_callback 预防措施:始终添加 permission_callback

// VULNERABLE
register_rest_route( 'myplugin/v1', '/data', array(
    'callback' => 'my_callback',
) );

// SECURE
register_rest_route( 'myplugin/v1', '/data', array(
    'callback'            => 'my_callback',
    'permission_callback' => function() {
        return current_user_can( 'edit_posts' );
    },
) );
问题 #14:重复注册卸载钩子

错误:每次加载页面都会写入选项 来源:WordPress 插件手册 发生原因:在主流程中调用 register_uninstall_hook() 预防措施:改用 uninstall.php 文件

// BAD - Runs on every page load
register_uninstall_hook( __FILE__, 'mypl_uninstall' );

// GOOD - Use uninstall.php file (preferred method)
// Create uninstall.php in plugin root
问题 #15:停用时数据被删除

错误:用户暂时停用插件时丢失数据 来源:WordPress 插件开发最佳实践 原因:混淆了停用与卸载 预防:仅在 uninstall.php 中删除数据,切勿在停用时删除

// WRONG - Deletes user data on deactivation
register_deactivation_hook( __FILE__, function() {
    delete_option( 'mypl_user_settings' );
} );

// CORRECT - Only clear temporary data on deactivation
register_deactivation_hook( __FILE__, function() {
    delete_transient( 'mypl_cache' );
} );

// CORRECT - Delete all data in uninstall.php
问题 #16:使用已弃用的函数

错误:WordPress 更新后插件无法正常运行 来源:WordPress 已弃用函数列表 原因:使用了在较新版本 WordPress 中移除的函数 预防:在开发期间启用 WP_DEBUG

// In wp-config.php (development only)
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
问题 #17:文本域不匹配

错误:翻译无法加载 来源:WordPress 国际化 原因:文本域与插件别名不匹配 预防:始终使用完全一致的插件别名

// Plugin header
// Text Domain: my-plugin

// In code - MUST MATCH EXACTLY
__( 'Text', 'my-plugin' );
_e( 'Text', 'my-plugin' );
问题 #18:缺少插件依赖项

错误:所需插件未启用时发生致命错误 来源:WordPress 插件依赖项 原因:未检查所需插件 预防:在 plugins_loaded 上检查依赖项

add_action( 'plugins_loaded', function() {
    if ( ! class_exists( 'WooCommerce' ) ) {
        add_action( 'admin_notices', function() {
            echo '<div class="error"><p>My Plugin requires WooCommerce.</p></div>';
        } );
        return;
    }
    // Initialize plugin
} );
问题 #19:自动保存触发元数据保存

错误:元数据被多次保存,导致性能问题 来源:WordPress 文章元数据 原因:save_post 钩子中未检查自动保存 预防:检查 DOING_AUTOSAVE 常量

add_action( 'save_post', function( $post_id ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // Safe to save meta
} );
问题 #20:admin-ajax.php 性能

错误:AJAX 响应缓慢 来源:https://deliciousbrains.com/comparing-wordpress-rest-api-performance-admin-ajax-php/ 原因:admin-ajax.php 会加载整个 WordPress 核心 预防:新项目使用 REST API(速度快 10 倍)

// OLD: admin-ajax.php (still works but slower)
add_action( 'wp_ajax_mypl_action', 'mypl_ajax_handler' );

// NEW: REST API (10x faster, recommended)
add_action( 'rest_api_init', function() {
    register_rest_route( 'myplugin/v1', '/endpoint', array(
        'methods'             => 'POST',
        'callback'            => 'mypl_rest_handler',
        'permission_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
    ) );
} );

插件架构模式

模式 1:简单插件(仅函数)

适用场景:函数少于 5 个、没有复杂状态的小型插件

<?php
/**
 * Plugin Name: Simple Plugin
 */

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

function mypl_init() {
    // Your code here
}
add_action( 'init', 'mypl_init' );

function mypl_admin_menu() {
    add_options_page(
        'My Plugin',
        'My Plugin',
        'manage_options',
        'my-plugin',
        'mypl_settings_page'
    );
}
add_action( 'admin_menu', 'mypl_admin_menu' );

function mypl_settings_page() {
    ?>
    <div class="wrap">
        <h1>My Plugin Settings</h1>
    </div>
    <?php
}
模式 2:OOP 插件

适用场景:功能相关且需要组织结构的中型插件

<?php
/**
 * Plugin Name: OOP Plugin
 */

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

class MyPL_Plugin {

    private static $instance = null;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->define_constants();
        $this->init_hooks();
    }

    private function define_constants() {
        define( 'MYPL_VERSION', '1.0.0' );
        define( 'MYPL_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
        define( 'MYPL_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    }

    private function init_hooks() {
        add_action( 'init', array( $this, 'init' ) );
        add_action( 'admin_menu', array( $this, 'admin_menu' ) );
    }

    public function init() {
        // Initialization code
    }

    public function admin_menu() {
        add_options_page(
            'My Plugin',
            'My Plugin',
            'manage_options',
            'my-plugin',
            array( $this, 'settings_page' )
        );
    }

    public function settings_page() {
        ?>
        <div class="wrap">
            <h1>My Plugin Settings</h1>
        </div>
        <?php
    }
}

// Initialize plugin
function mypl() {
    return MyPL_Plugin::get_instance();
}
mypl();
模式 3:PSR-4 插件(现代,推荐)

适用场景:大型/现代插件、团队开发、2025 年及以后的最佳实践

目录结构:

my-plugin/
├── my-plugin.php       # Main file
├── composer.json       # Autoloading config
├── src/                # PSR-4 autoloaded classes
│   ├── Admin.php
│   ├── Frontend.php
│   └── Settings.php
├── languages/
└── uninstall.php

composer.json:

{
    "name": "my-vendor/my-plugin",
    "autoload": {
        "psr-4": {
            "MyPlugin\\": "src/"
        }
    },
    "require": {
        "php": ">=7.4"
    }
}

my-plugin.php:

<?php
/**
 * Plugin Name: PSR-4 Plugin
 */

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

// Composer autoloader
require_once __DIR__ . '/vendor/autoload.php';

use MyPlugin\Admin;
use MyPlugin\Frontend;

class MyPlugin {

    private static $instance = null;

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->init();
    }

    private function init() {
        new Admin();
        new Frontend();
    }
}

MyPlugin::get_instance();

src/Admin.php:

<?php

namespace MyPlugin;

class Admin {

    public function __construct() {
        add_action( 'admin_menu', array( $this, 'add_menu' ) );
    }

    public function add_menu() {
        add_options_page(
            'My Plugin',
            'My Plugin',
            'manage_options',
            'my-plugin',
            array( $this, 'settings_page' )
        );
    }

    public function settings_page() {
        ?>
        <div class="wrap">
            <h1>My Plugin Settings</h1>
        </div>
        <?php
    }
}

常见模式

模式 1:自定义文章类型
function mypl_register_cpt() {
    register_post_type( 'book', array(
        'labels' => array(
            'name'          => 'Books',
            'singular_name' => 'Book',
            'add_new_item'  => 'Add New Book',
        ),
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,  // Gutenberg support
        'supports'     => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'rewrite'      => array( 'slug' => 'books' ),
        'menu_icon'    => 'dashicons-book',
    ) );
}
add_action( 'init', 'mypl_register_cpt' );

// CRITICAL: Flush rewrite rules on activation
function mypl_activate() {
    mypl_register_cpt();
    flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'mypl_activate' );

function mypl_deactivate() {
    flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'mypl_deactivate' );
模式 2:自定义分类法
function mypl_register_taxonomy() {
    register_taxonomy( 'genre', 'book', array(
        'labels' => array(
            'name'          => 'Genres',
            'singular_name' => 'Genre',
        ),
        'hierarchical' => true,  // Like categories
        'show_in_rest' => true,
        'rewrite'      => array( 'slug' => 'genre' ),
    ) );
}
add_action( 'init', 'mypl_register_taxonomy' );
模式 3:元数据框
function mypl_add_meta_box() {
    add_meta_box(
        'book_details',
        'Book Details',
        'mypl_meta_box_html',
        'book',
        'normal',
        'high'
    );
}
add_action( 'add_meta_boxes', 'mypl_add_meta_box' );

function mypl_meta_box_html( $post ) {
    $isbn = get_post_meta( $post->ID, '_book_isbn', true );

    wp_nonce_field( 'mypl_save_meta', 'mypl_meta_nonce' );
    ?>
    <label for="book_isbn">ISBN:</label>
    <input type="text" id="book_isbn" name="book_isbn" value="<?php echo esc_attr( $isbn ); ?>" />
    <?php
}

function mypl_save_meta( $post_id ) {
    // Security checks
    if ( ! isset( $_POST['mypl_meta_nonce'] )
         || ! wp_verify_nonce( $_POST['mypl_meta_nonce'], 'mypl_save_meta' ) ) {
        return;
    }

    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    // Save data
    if ( isset( $_POST['book_isbn'] ) ) {
        update_post_meta(
            $post_id,
            '_book_isbn',
            sanitize_text_field( $_POST['book_isbn'] )
        );
    }
}
add_action( 'save_post_book', 'mypl_save_meta' );
模式 4:设置 API
function mypl_add_menu() {
    add_options_page(
        'My Plugin Settings',
        'My Plugin',
        'manage_options',
        'my-plugin',
        'mypl_settings_page'
    );
}
add_action( 'admin_menu', 'mypl_add_menu' );

function mypl_register_settings() {
    register_setting( 'mypl_options', 'mypl_api_key', array(
        'type'              => 'string',
        'sanitize_callback' => 'sanitize_text_field',
        'default'           => '',
    ) );

    add_settings_section(
        'mypl_section',
        'API Settings',
        'mypl_section_callback',
        'my-plugin'
    );

    add_settings_field(
        'mypl_api_key',
        'API Key',
        'mypl_field_callback',
        'my-plugin',
        'mypl_section'
    );
}
add_action( 'admin_init', 'mypl_register_settings' );

function mypl_section_callback() {
    echo '<p>Configure your API settings.</p>';
}

function mypl_field_callback() {
    $value = get_option( 'mypl_api_key' );
    ?>
    <input type="text" name="mypl_api_key" value="<?php echo esc_attr( $value ); ?>" />
    <?php
}

function mypl_settings_page() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form method="post" action="options.php">
            <?php
            settings_fields( 'mypl_options' );
            do_settings_sections( 'my-plugin' );
            submit_button();
            ?>
        </form>
    </div>
    <?php
}
模式 5:REST API 端点
add_action( 'rest_api_init', function() {
    register_rest_route( 'myplugin/v1', '/data', array(
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => 'mypl_rest_callback',
        'permission_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
        'args'                => array(
            'id' => array(
                'required'          => true,
                'validate_callback' => function( $param ) {
                    return is_numeric( $param );
                },
                'sanitize_callback' => 'absint',
            ),
        ),
    ) );
} );

function mypl_rest_callback( $request ) {
    $id = $request->get_param( 'id' );

    // Process...

    return new WP_REST_Response( array(
        'success' => true,
        'data'    => $data,
    ), 200 );
}
模式 6:AJAX 处理程序(旧版)
// Enqueue script with localized data
function mypl_enqueue_ajax_script() {
    wp_enqueue_script( 'mypl-ajax', plugins_url( 'js/ajax.js', __FILE__ ), array( 'jquery' ), '1.0', true );

    wp_localize_script( 'mypl-ajax', 'mypl_ajax_object', array(
        'ajaxurl' => admin_url( 'admin-ajax.php' ),
        'nonce'   => wp_create_nonce( 'mypl-ajax-nonce' ),
    ) );
}
add_action( 'wp_enqueue_scripts', 'mypl_enqueue_ajax_script' );

// AJAX handler (logged-in users)
function mypl_ajax_handler() {
    check_ajax_referer( 'mypl-ajax-nonce', 'nonce' );

    $data = sanitize_text_field( $_POST['data'] );

    // Process...

    wp_send_json_success( array( 'message' => 'Success' ) );
}
add_action( 'wp_ajax_mypl_action', 'mypl_ajax_handler' );

// AJAX handler (logged-out users)
add_action( 'wp_ajax_nopriv_mypl_action', 'mypl_ajax_handler' );

JavaScript(js/ajax.js):

jQuery(document).ready(function($) {
    $('#my-button').on('click', function() {
        $.ajax({
            url: mypl_ajax_object.ajaxurl,
            type: 'POST',
            data: {
                action: 'mypl_action',
                nonce: mypl_ajax_object.nonce,
                data: 'value'
            },
            success: function(response) {
                console.log(response.data.message);
            }
        });
    });
});
模式 7:自定义数据库表
function mypl_create_tables() {
    global $wpdb;

    $table_name = $wpdb->prefix . 'mypl_data';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table_name (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        user_id bigint(20) NOT NULL,
        data text NOT NULL,
        created datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY  (id),
        KEY user_id (user_id)
    ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );

    add_option( 'mypl_db_version', '1.0' );
}

// Create tables on activation
register_activation_hook( __FILE__, 'mypl_create_tables' );
模式 8:用于缓存的瞬态数据
function mypl_get_expensive_data() {
    // Try to get cached data
    $data = get_transient( 'mypl_expensive_data' );

    if ( false === $data ) {
        // Not cached - regenerate
        $data = perform_expensive_operation();

        // Cache for 12 hours
        set_transient( 'mypl_expensive_data', $data, 12 * HOUR_IN_SECONDS );
    }

    return $data;
}

// Clear cache when data changes
function mypl_clear_cache() {
    delete_transient( 'mypl_expensive_data' );
}
add_action( 'save_post', 'mypl_clear_cache' );

使用随附资源

模板(templates/)

使用这些可用于生产环境的模板快速搭建插件:

  • templates/plugin-simple/ - 仅包含函数的简单插件
  • templates/plugin-oop/ - 面向对象的插件结构
  • templates/plugin-psr4/ - 使用 Composer 的现代 PSR-4 插件
  • templates/examples/meta-box.php - 元数据框实现
  • templates/examples/settings-page.php - 设置 API 页面
  • templates/examples/custom-post-type.php - CPT 注册
  • templates/examples/rest-endpoint.php - REST API 端点
  • templates/examples/ajax-handler.php - AJAX 实现

Claude 应在以下情况使用:创建新插件或实现特定功能模式时。

脚本(scripts/)
  • scripts/scaffold-plugin.sh - 交互式插件搭建
  • scripts/check-security.sh - 检查常见问题的安全审计
  • scripts/validate-headers.sh - 验证插件标头

使用示例:

# Scaffold new plugin
./scripts/scaffold-plugin.sh my-plugin simple

# Check for security issues
./scripts/check-security.sh my-plugin.php

# Validate plugin headers
./scripts/validate-headers.sh my-plugin.php
参考资料(references/)

Claude 可在需要时加载的详细文档:

  • references/security-checklist.md - 完整的安全审计清单
  • references/hooks-reference.md - 常用的 WordPress 钩子和筛选器
  • references/sanitization-guide.md - 所有数据清理和转义函数
  • references/wpdb-patterns.md - 数据库查询模式
  • references/common-errors.md - 扩展版错误预防指南

Claude 何时应加载这些资料:处理安全问题、选择合适的钩子、清理特定数据类型、编写数据库查询或调试常见错误时。


高级主题

国际化(i18n)
// Load text domain
function mypl_load_textdomain() {
    load_plugin_textdomain( 'my-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
add_action( 'plugins_loaded', 'mypl_load_textdomain' );

// Translatable strings
__( 'Text', 'my-plugin' );  // Returns translated string
_e( 'Text', 'my-plugin' );  // Echoes translated string
_n( 'One item', '%d items', $count, 'my-plugin' );  // Plural forms
esc_html__( 'Text', 'my-plugin' );  // Translate and escape
esc_html_e( 'Text', 'my-plugin' );  // Translate, escape, and echo
WP-CLI 命令
if ( defined( 'WP_CLI' ) && WP_CLI ) {

    class MyPL_CLI_Command {

        /**
         * Process data
         *
         * ## EXAMPLES
         *
         *     wp mypl process --limit=100
         *
         * @param array $args
         * @param array $assoc_args
         */
        public function process( $args, $assoc_args ) {
            $limit = isset( $assoc_args['limit'] ) ? absint( $assoc_args['limit'] ) : 10;

            WP_CLI::line( "Processing $limit items..." );

            // Process...

            WP_CLI::success( 'Processing complete!' );
        }
    }

    WP_CLI::add_command( 'mypl', 'MyPL_CLI_Command' );
}
定时事件(Cron)
// Schedule event on activation
function mypl_activate() {
    if ( ! wp_next_scheduled( 'mypl_daily_task' ) ) {
        wp_schedule_event( time(), 'daily', 'mypl_daily_task' );
    }
}
register_activation_hook( __FILE__, 'mypl_activate' );

// Clear event on deactivation
function mypl_deactivate() {
    wp_clear_scheduled_hook( 'mypl_daily_task' );
}
register_deactivation_hook( __FILE__, 'mypl_deactivate' );

// Hook to scheduled event
function mypl_do_daily_task() {
    // Perform task
}
add_action( 'mypl_daily_task', 'mypl_do_daily_task' );
插件依赖项检查
add_action( 'admin_init', function() {
    // Check for WooCommerce
    if ( ! class_exists( 'WooCommerce' ) ) {
        deactivate_plugins( plugin_basename( __FILE__ ) );

        add_action( 'admin_notices', function() {
            echo '<div class="error"><p><strong>My Plugin</strong> requires WooCommerce to be installed and active.</p></div>';
        } );

        if ( isset( $_GET['activate'] ) ) {
            unset( $_GET['activate'] );
        }
    }
} );

分发与自动更新

启用 GitHub 自动更新

托管在 WordPress.org 之外的插件仍可使用 YahnisElsts 开发的 Plugin Update Checker 提供自动更新。这是大多数使用场景的推荐方案。

快速入门:

// 1. Install library (git submodule or Composer)
git submodule add https://github.com/YahnisElsts/plugin-update-checker.git

// 2. Add to main plugin file
require plugin_dir_path( __FILE__ ) . 'plugin-update-checker/plugin-update-checker.php';
use YahnisElsts\PluginUpdateChecker\v5\PucFactory;

$updateChecker = PucFactory::buildUpdateChecker(
    'https://github.com/yourusername/your-plugin/',
    __FILE__,
    'your-plugin-slug'
);

// Use GitHub Releases (recommended)
$updateChecker->getVcsApi()->enableReleaseAssets();

// For private repos, use token from wp-config.php
if ( defined( 'YOUR_PLUGIN_GITHUB_TOKEN' ) ) {
    $updateChecker->setAuthentication( YOUR_PLUGIN_GITHUB_TOKEN );
}

部署:

# 1. Update version in plugin header
# 2. Commit and tag
git add my-plugin.php
git commit -m "Bump version to 1.0.1"
git tag 1.0.1
git push origin main
git push origin 1.0.1

# 3. Create GitHub Release (optional but recommended)
# - Upload pre-built ZIP file (exclude .git, tests, etc.)
# - Add release notes for users

主要功能:

✅ 适用于 GitHub、GitLab、BitBucket 或自定义服务 ✅ 支持公共和私有代码库 ✅ 使用 GitHub Releases 或标签进行版本管理 ✅ 基于安全的 HTTPS 更新 ✅ 可选的许可证密钥集成 ✅ 专业的发布说明和变更日志 ✅ 库体积约为 100KB

替代方案:

  1. Git Updater(用户可安装的插件,无需编写代码)
  2. 自定义更新服务(完全掌控,但需要托管服务)
  3. Freemius(商业方案,包含许可证管理和付款功能)

完整资源:

  • 完整指南:参见 references/github-auto-updates.md(21 页,涵盖所有方案)
  • 实现示例:参见 examples/github-updater.php(10 个示例)
  • 安全最佳实践:校验和、签名、令牌存储、速率限制
  • 模板集成:所有 3 个插件模板都包含设置说明

安全注意事项:

  • ✅ 仓库 URL 始终使用 HTTPS
  • ✅ 切勿将身份验证令牌硬编码(请使用 wp-config.php)
  • ✅ 提供更新前实施许可证验证
  • ✅ 可选:添加校验和以验证文件
  • ✅ 对更新检查进行速率限制,避免 API 限流
  • ✅ 安装后清除缓存的更新数据

各方案的适用场景:

使用场景推荐方案
开源、公共代码库Plugin Update Checker
私有插件、客户项目Plugin Update Checker + 私有代码库
商业插件Freemius 或自定义服务
多平台 Git 托管Git Updater
自定义许可证需求自定义更新服务

ZIP 结构要求:

plugin.zip
└── my-plugin/       ← Plugin folder MUST be inside ZIP
    ├── my-plugin.php
    ├── readme.txt
    └── ...

结构不正确会导致 WordPress 创建随机文件夹名称,进而破坏插件!


依赖项

必需:

  • WordPress 5.9+(推荐 6.7+)
  • PHP 7.4+(推荐 8.0+)

可选:

  • Composer 2.0+ - 用于 PSR-4 自动加载
  • WP-CLI 2.0+ - 用于通过命令行管理插件
  • Query Monitor - 用于调试和性能分析

官方文档


故障排除

问题:插件导致致命错误

解决方案:

  1. 在 wp-config.php 中启用 WP_DEBUG
  2. 检查 wp-content/debug.log 中的错误日志
  3. 确认所有类名和函数名都带有前缀
  4. 检查是否缺少依赖项
问题:自定义文章类型页面出现 404 错误

解决方案:刷新重写规则

// Temporarily add to wp-admin
flush_rewrite_rules();
// Remove after visiting wp-admin once
问题:Nonce 验证始终失败

解决方案:

  1. 检查字段和验证中的 nonce 名称是否一致
  2. 确认使用了正确的操作名称
  3. 确保 nonce 尚未过期(默认有效期为 24 小时)
问题:AJAX 返回 0 或 -1

解决方案:

  1. 确认操作名称与钩子匹配:wp_ajax_{action}
  2. 检查是否发送并验证了 nonce
  3. 确保处理函数存在且已正确挂接
问题:清理输入时移除了 HTML

解决方案:使用 wp_kses_post() 而不是 sanitize_text_field(),以允许安全的 HTML

问题:数据库查询无法正常工作

解决方案:

  1. 对包含变量的查询,始终使用 $wpdb->prepare()
  2. 检查表名是否包含 $wpdb->prefix
  3. 确认列名和语法正确

完整设置检查清单

使用此检查清单验证你的插件:

  • 插件头信息完整,包含所有字段
  • 每个 PHP 文件顶部都有 ABSPATH 检查
  • 所有函数和类都使用唯一前缀
  • 所有表单都有 nonce 验证
  • 所有用户输入都经过清理
  • 所有输出都经过转义
  • 所有数据库查询都使用 $wpdb->prepare()
  • 检查用户权限(不只是 is_admin())
  • 自定义文章类型在激活时刷新重写规则
  • 停用钩子仅清除临时数据
  • uninstall.php 处理永久清理
  • 文本域与插件 slug 一致
  • 仅在需要时加载脚本和样式
  • 开发期间启用 WP_DEBUG
  • 使用 Query Monitor 测试性能
  • 没有已弃用函数的警告
  • 兼容最新版本的 WordPress

有问题?遇到故障?

  1. 查看 references/common-errors.md 了解更多故障排除信息
  2. 确认已完成安全基础中的所有步骤
  3. 查看官方文档:https://developer.wordpress.org/plugins/
  4. 启用 WP_DEBUG 并检查 debug.log
  5. 使用 Query Monitor 插件调试钩子和查询

评论

支持 Markdown