wordpress-plugin-core

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WordPress Plugin Development (Core)

WordPress插件开发(核心内容)

Status: Production Ready Last Updated: 2025-11-06 Dependencies: None (WordPress 5.9+, PHP 7.4+) Latest Versions: WordPress 6.7+, PHP 8.0+ recommended

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

Quick Start (10 Minutes)

快速入门(10分钟)

1. Choose Your Plugin Structure

1. 选择插件架构

WordPress plugins can use three architecture patterns:
  • Simple (functions only) - For small plugins with <5 functions
  • OOP (Object-Oriented) - For medium plugins with related functionality
  • PSR-4 (Namespaced + Composer autoload) - For large/modern plugins
Why this matters:
  • Simple plugins are easiest to start but don't scale well
  • OOP provides organization without modern PHP features
  • PSR-4 is the modern standard (2025) and most maintainable
WordPress插件可采用三种架构模式:
  • Simple(仅函数) - 适用于少于5个函数的小型插件
  • OOP(面向对象) - 适用于具有相关功能的中型插件
  • PSR-4(命名空间+Composer自动加载) - 适用于大型/现代插件
重要性
  • Simple插件最易上手,但扩展性差
  • OOP可实现代码组织,无需依赖现代PHP特性
  • PSR-4是2025年的现代标准,可维护性最强

2. Create Plugin Header

2. 创建插件头部

Every plugin MUST have a header comment in the main file:
php
<?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;
}
CRITICAL:
  • Plugin Name is the ONLY required field
  • Text Domain must match plugin slug exactly (for translations)
  • Always add ABSPATH check to prevent direct file access
每个插件的主文件必须包含头部注释:
php
<?php
/**
 * Plugin Name:       My Awesome Plugin
 * Plugin URI:        https://example.com/my-plugin/
 * Description:       插件功能的简要描述。
 * Version:           1.0.0
 * Requires at least: 5.9
 * Requires PHP:      7.4
 * Author:            你的名字
 * 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
 */

// 禁止直接访问文件
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}
关键注意事项
  • Plugin Name是唯一必填字段
  • Text Domain必须与插件slug完全匹配(用于翻译)
  • 务必添加ABSPATH检查以防止直接访问文件

3. Implement The Security Foundation

3. 实现安全基础

Before writing ANY functionality, implement these 5 security essentials:
php
// 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项安全核心内容:
php
// 1. 唯一前缀(至少4-5个字符)
define( 'MYPL_VERSION', '1.0.0' );

function mypl_init() {
    // 你的代码
}
add_action( 'init', 'mypl_init' );

// 2. ABSPATH检查(每个PHP文件都需要)
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// 3. 表单添加nonce
<input type="hidden" name="mypl_nonce" value="<?php echo wp_create_nonce( 'mypl_action' ); ?>" />

// 4. 清理输入,转义输出
$clean = sanitize_text_field( $_POST['input'] );
echo esc_html( $output );

// 5. 数据库使用预处理语句
global $wpdb;
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}table WHERE id = %d",
        $id
    )
);

The 5-Step Security Foundation

五步安全基础

WordPress plugin security has THREE components that must ALL be present:
WordPress插件安全包含三个必须同时满足的组件:

Step 1: Use Unique Prefix for Everything

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

Why: Prevents naming conflicts with other plugins and WordPress core.
Rules:
  • 4-5 characters minimum
  • Apply to: functions, classes, constants, options, transients, meta keys, global variables
  • Avoid:
    wp_
    ,
    __
    ,
    _
    , "WordPress"
php
// 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
原因:避免与其他插件和WordPress核心发生命名冲突。
规则
  • 至少4-5个字符
  • 应用于:函数、类、常量、选项、临时缓存、元键、全局变量
  • 避免使用:
    wp_
    __
    _
    、"WordPress"
php
// 正确示例
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 );

// 错误示例
function function_name() {}  // 无前缀,会发生冲突
class Settings {}  // 过于通用

Step 2: Check Capabilities, Not Just Admin Status

步骤2:检查权限,而非仅管理员状态

ERROR: Using
is_admin()
for permission checks
php
// 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
}
Common Capabilities:
  • manage_options
    - Administrator
  • edit_posts
    - Editor/Author
  • publish_posts
    - Author
  • edit_pages
    - Editor
  • read
    - Subscriber
错误做法:使用
is_admin()
进行权限检查
php
// 错误 - 任何人都可访问后台URL
if ( is_admin() ) {
    // 删除用户数据 - 安全漏洞
}

// 正确 - 检查用户权限
if ( current_user_can( 'manage_options' ) ) {
    // 删除用户数据 - 现在安全了
}
常见权限
  • manage_options
    - 管理员
  • edit_posts
    - 编辑/作者
  • publish_posts
    - 作者
  • edit_pages
    - 编辑
  • read
    - 订阅者

Step 3: The Security Trinity

步骤3:安全三要素

Input → Processing → Output each require different functions:
php
// 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>';
Critical Rule: Sanitize on INPUT, escape on OUTPUT. Never trust user data.
输入→处理→输出每个阶段都需要不同的函数:
php
// 清理(输入)- 清洗用户数据
$name = sanitize_text_field( $_POST['name'] );
$email = sanitize_email( $_POST['email'] );
$url = esc_url_raw( $_POST['url'] );
$html = wp_kses_post( $_POST['content'] );  // 允许安全的HTML
$key = sanitize_key( $_POST['option'] );
$ids = array_map( 'absint', $_POST['ids'] );  // 整数数组

// 验证(逻辑)- 验证是否符合要求
if ( ! is_email( $email ) ) {
    wp_die( '无效邮箱' );
}

// 转义(输出)- 确保显示安全
echo esc_html( $name );
echo '<a href="' . esc_url( $url ) . '">';
echo '<div class="' . esc_attr( $class ) . '">';
echo '<textarea>' . esc_textarea( $content ) . '</textarea>';
关键规则:输入时清理,输出时转义。永远不要信任用户数据。

Step 4: Nonces (CSRF Protection)

步骤4:Nonce(CSRF防护)

What: One-time tokens that prove requests came from your site.
Form Pattern:
php
// 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 Pattern:
javascript
// JavaScript
jQuery.ajax({
    url: ajaxurl,
    data: {
        action: 'mypl_ajax_action',
        nonce: mypl_ajax_object.nonce,
        data: formData
    }
});
php
// 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' ),
) );
作用:一次性令牌,用于验证请求来自你的站点。
表单模式
php
// 在表单中生成nonce
<form method="post">
    <?php wp_nonce_field( 'mypl_action', 'mypl_nonce' ); ?>
    <input type="text" name="data" />
    <button type="submit">提交</button>
</form>

// 在处理器中验证nonce
if ( ! isset( $_POST['mypl_nonce'] ) || ! wp_verify_nonce( $_POST['mypl_nonce'], 'mypl_action' ) ) {
    wp_die( '安全检查失败' );
}

// 现在可以安全执行
$data = sanitize_text_field( $_POST['data'] );
AJAX模式
javascript
// JavaScript代码
jQuery.ajax({
    url: ajaxurl,
    data: {
        action: 'mypl_ajax_action',
        nonce: mypl_ajax_object.nonce,
        data: formData
    }
});
php
// PHP处理器
function mypl_ajax_handler() {
    check_ajax_referer( 'mypl-ajax-nonce', 'nonce' );

    // 安全执行
    wp_send_json_success( array( 'message' => '成功' ) );
}
add_action( 'wp_ajax_mypl_ajax_action', 'mypl_ajax_handler' );

// 本地化脚本并传递nonce
wp_localize_script( 'mypl-script', 'mypl_ajax_object', array(
    'ajaxurl' => admin_url( 'admin-ajax.php' ),
    'nonce'   => wp_create_nonce( 'mypl-ajax-nonce' ),
) );

Step 5: Prepared Statements for Database

步骤5:数据库预处理语句

CRITICAL: Always use
$wpdb->prepare()
for queries with user input.
php
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']
    )
);
Placeholders:
  • %s
    - String
  • %d
    - Integer
  • %f
    - Float
LIKE Queries (Special Case):
php
$search = '%' . $wpdb->esc_like( $term ) . '%';
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}posts WHERE post_title LIKE %s",
        $search
    )
);

关键:只要查询包含用户输入,务必使用
$wpdb->prepare()
php
global $wpdb;

// 错误 - 存在SQL注入漏洞
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}table WHERE id = {$_GET['id']}" );

// 正确 - 使用预处理语句
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}table WHERE id = %d",
        $_GET['id']
    )
);
占位符
  • %s
    - 字符串
  • %d
    - 整数
  • %f
    - 浮点数
LIKE查询(特殊情况)
php
$search = '%' . $wpdb->esc_like( $term ) . '%';
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}posts WHERE post_title LIKE %s",
        $search
    )
);

Critical Rules

关键规则

Always Do

务必执行

Use unique prefix (4-5 chars) for all global code (functions, classes, options, transients) ✅ Add ABSPATH check to every PHP file:
if ( ! defined( 'ABSPATH' ) ) exit;
Check capabilities (
current_user_can()
) not just
is_admin()
Verify nonces for all forms and AJAX requests ✅ Use $wpdb->prepare() for all database queries with user input ✅ Sanitize input with
sanitize_*()
functions before saving ✅ Escape output with
esc_*()
functions before displaying ✅ Flush rewrite rules on activation when registering custom post types ✅ Use uninstall.php for permanent cleanup (not deactivation hook) ✅ Follow WordPress Coding Standards (tabs for indentation, Yoda conditions)
✅ 所有全局代码(函数、类、选项、临时缓存)使用唯一前缀(4-5个字符) ✅ 每个PHP文件添加ABSPATH检查:
if ( ! defined( 'ABSPATH' ) ) exit;
✅ 检查权限(
current_user_can()
)而非仅使用
is_admin()
✅ 所有表单和AJAX请求验证nonce ✅ 所有包含用户输入的数据库查询使用$wpdb->prepare() ✅ 保存前使用
sanitize_*()
函数清理输入 ✅ 显示前使用
esc_*()
函数转义输出 ✅ 注册自定义文章类型时,激活插件后刷新重写规则 ✅ 使用uninstall.php进行永久清理(而非停用钩子) ✅ 遵循WordPress编码标准(制表符缩进、Yoda条件)

Never Do

禁止操作

Never use extract() - Creates security vulnerabilities ❌ Never trust $_POST/$_GET without sanitization ❌ Never concatenate user input into SQL - Always use prepare() ❌ Never use
is_admin()
alone
for permission checks ❌ Never output unsanitized data - Always escape ❌ Never use generic function/class names - Always prefix ❌ Never use short PHP tags
<?
or
<?=
- Use
<?php
only ❌ Never delete user data on deactivation - Only on uninstall ❌ Never register uninstall hook repeatedly - Only once on activation ❌ Never use
register_uninstall_hook()
in main flow
- Use uninstall.php instead

❌ 绝不使用extract() - 会产生安全漏洞 ❌ 未经过滤绝不信任$_POST/$_GET数据 ❌ 绝不将用户输入直接拼接进SQL - 务必使用prepare() ❌ 绝不单独使用
is_admin()
进行权限检查 ❌ 绝不输出未经过滤的数据 - 务必转义 ❌ 绝不使用通用函数/类名 - 务必添加前缀 ❌ 绝不使用短PHP标签
<?
<?=
- 仅使用
<?php
❌ 停用插件时绝不删除用户数据 - 仅在卸载时删除 ❌ 绝不重复注册卸载钩子 - 仅在激活时注册一次 ❌ 绝不在主流程中使用
register_uninstall_hook()
- 改用uninstall.php

Known Issues Prevention

已知问题预防

This skill prevents 20 documented issues:
本指南可预防20个已记录的问题:

Issue #1: SQL Injection

问题1:SQL注入

Error: Database compromised via unescaped user input Source: https://patchstack.com/articles/sql-injection/ (15% of all vulnerabilities) Why It Happens: Direct concatenation of user input into SQL queries Prevention: Always use
$wpdb->prepare()
with placeholders
php
// 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'] ) );
错误:通过未转义的用户输入导致数据库被攻陷 来源https://patchstack.com/articles/sql-injection/(占所有漏洞的15%) 原因:将用户输入直接拼接进SQL查询 预防:始终使用带占位符的
$wpdb->prepare()
php
// 存在漏洞
$wpdb->query( "DELETE FROM {$wpdb->prefix}table WHERE id = {$_GET['id']}" );

// 安全写法
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}table WHERE id = %d", $_GET['id'] ) );

Issue #2: XSS (Cross-Site Scripting)

问题2:XSS(跨站脚本攻击)

Error: Malicious JavaScript executed in user browsers Source: https://patchstack.com (35% of all vulnerabilities) Why It Happens: Outputting unsanitized user data to HTML Prevention: Always escape output with context-appropriate function
php
// VULNERABLE
echo $_POST['name'];
echo '<div class="' . $_POST['class'] . '">';

// SECURE
echo esc_html( $_POST['name'] );
echo '<div class="' . esc_attr( $_POST['class'] ) . '">';
错误:恶意JavaScript在用户浏览器中执行 来源https://patchstack.com(占所有漏洞的35%) 原因:将未经过滤的用户数据输出到HTML中 预防:始终使用适合上下文的函数转义输出
php
// 存在漏洞
echo $_POST['name'];
echo '<div class="' . $_POST['class'] . '">';

// 安全写法
echo esc_html( $_POST['name'] );
echo '<div class="' . esc_attr( $_POST['class'] ) . '">';

Issue #3: CSRF (Cross-Site Request Forgery)

问题3:CSRF(跨站请求伪造)

Error: Unauthorized actions performed on behalf of users Source: https://blog.nintechnet.com/25-wordpress-plugins-vulnerable-to-csrf-attacks/ Why It Happens: No verification that requests originated from your site Prevention: Use nonces with
wp_nonce_field()
and
wp_verify_nonce()
php
// 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'] ) );
错误:代表用户执行未授权操作 来源https://blog.nintechnet.com/25-wordpress-plugins-vulnerable-to-csrf-attacks/ 原因:未验证请求是否来自你的站点 预防:使用
wp_nonce_field()
wp_verify_nonce()
添加nonce
php
// 存在漏洞
if ( $_POST['action'] == 'delete' ) {
    delete_user( $_POST['user_id'] );
}

// 安全写法
if ( ! wp_verify_nonce( $_POST['nonce'], 'mypl_delete_user' ) ) {
    wp_die( '安全检查失败' );
}
delete_user( absint( $_POST['user_id'] ) );

Issue #4: Missing Capability Checks

问题4:缺少权限检查

Error: Regular users can access admin functions Source: WordPress Security Review Guidelines Why It Happens: Using
is_admin()
instead of
current_user_can()
Prevention: Always check capabilities, not just admin context
php
// VULNERABLE
if ( is_admin() ) {
    // Any logged-in user can trigger this
}

// SECURE
if ( current_user_can( 'manage_options' ) ) {
    // Only administrators can trigger this
}
错误:普通用户可访问后台功能 来源:WordPress安全审查指南 原因:使用
is_admin()
而非
current_user_can()
预防:始终检查权限,而非仅检查后台上下文
php
// 存在漏洞
if ( is_admin() ) {
    // 任何登录用户都可触发
}

// 安全写法
if ( current_user_can( 'manage_options' ) ) {
    // 仅管理员可触发
}

Issue #5: Direct File Access

问题5:直接访问文件

Error: PHP files executed outside WordPress context Source: WordPress Plugin Handbook Why It Happens: No ABSPATH check at top of file Prevention: Add ABSPATH check to every PHP file
php
// Add to top of EVERY PHP file
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}
错误:PHP文件在WordPress上下文之外执行 来源:WordPress插件手册 原因:文件顶部未添加ABSPATH检查 预防:每个PHP文件都添加ABSPATH检查
php
// 添加到每个PHP文件的顶部
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

Issue #6: Prefix Collision

问题6:前缀冲突

Error: Functions/classes conflict with other plugins Source: WordPress Coding Standards Why It Happens: Generic names without unique prefix Prevention: Use 4-5 character prefix on ALL global code
php
// CAUSES CONFLICTS
function init() {}
class Settings {}
add_option( 'api_key', $value );

// SAFE
function mypl_init() {}
class MyPL_Settings {}
add_option( 'mypl_api_key', $value );
错误:函数/类与其他插件冲突 来源:WordPress编码标准 原因:使用无唯一前缀的通用名称 预防:所有全局代码使用4-5个字符的前缀
php
// 会导致冲突
function init() {}
class Settings {}
add_option( 'api_key', $value );

// 安全写法
function mypl_init() {}
class MyPL_Settings {}
add_option( 'mypl_api_key', $value );

Issue #7: Rewrite Rules Not Flushed

问题7:未刷新重写规则

Error: Custom post types return 404 errors Source: WordPress Plugin Handbook Why It Happens: Forgot to flush rewrite rules after registering CPT Prevention: Flush on activation, clear on deactivation
php
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' );
错误:自定义文章类型返回404错误 来源:WordPress插件手册 原因:注册自定义文章类型后忘记刷新重写规则 预防:激活时刷新,停用时清理
php
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' );

Issue #8: Transients Not Cleaned

问题8:临时缓存未清理

Error: Database accumulates expired transients Source: WordPress Transients API Documentation Why It Happens: No cleanup on uninstall Prevention: Delete transients in uninstall.php
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_%'" );
错误:数据库累积过期的临时缓存 来源:WordPress临时缓存API文档 原因:卸载时未清理临时缓存 预防:在uninstall.php中删除临时缓存
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_%'" );

Issue #9: Scripts Loaded Everywhere

问题9:脚本全局加载

Error: Performance degraded by unnecessary asset loading Source: WordPress Performance Best Practices Why It Happens: Enqueuing scripts/styles without conditional checks Prevention: Only load assets where needed
php
// 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 );
    }
} );
错误:不必要的资源加载导致性能下降 来源:WordPress性能最佳实践 原因:未添加条件检查就入队脚本/样式 预防:仅在需要的页面加载资源
php
// 错误 - 所有页面都加载
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_script( 'mypl-script', $url );
} );

// 正确 - 仅在特定页面加载
add_action( 'wp_enqueue_scripts', function() {
    if ( is_page( 'my-page' ) ) {
        wp_enqueue_script( 'mypl-script', $url, array( 'jquery' ), '1.0', true );
    }
} );

Issue #10: Missing Sanitization on Save

问题10:保存时未清理数据

Error: Malicious data stored in database Source: WordPress Data Validation Why It Happens: Saving $_POST data without sanitization Prevention: Always sanitize before saving
php
// VULNERABLE
update_option( 'mypl_setting', $_POST['value'] );

// SECURE
update_option( 'mypl_setting', sanitize_text_field( $_POST['value'] ) );
错误:恶意数据存储到数据库 来源:WordPress数据验证 原因:直接保存$_POST数据未清理 预防:保存前务必清理
php
// 存在漏洞
update_option( 'mypl_setting', $_POST['value'] );

// 安全写法
update_option( 'mypl_setting', sanitize_text_field( $_POST['value'] ) );

Issue #11: Incorrect LIKE Queries

问题11:LIKE查询错误

Error: SQL syntax errors or injection vulnerabilities Source: WordPress $wpdb Documentation Why It Happens: LIKE wildcards not escaped properly Prevention: Use
$wpdb->esc_like()
php
// WRONG
$search = '%' . $term . '%';

// CORRECT
$search = '%' . $wpdb->esc_like( $term ) . '%';
$results = $wpdb->get_results( $wpdb->prepare( "... WHERE title LIKE %s", $search ) );
错误:SQL语法错误或注入漏洞 来源:WordPress $wpdb文档 原因:LIKE通配符未正确转义 预防:使用
$wpdb->esc_like()
php
// 错误
$search = '%' . $term . '%';

// 正确
$search = '%' . $wpdb->esc_like( $term ) . '%';
$results = $wpdb->get_results( $wpdb->prepare( "... WHERE title LIKE %s", $search ) );

Issue #12: Using extract()

问题12:使用extract()

Error: Variable collision and security vulnerabilities Source: WordPress Coding Standards Why It Happens: extract() creates variables from array keys Prevention: Never use extract(), access array elements directly
php
// DANGEROUS
extract( $_POST );
// Now $any_array_key becomes a variable

// SAFE
$name = isset( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : '';
错误:变量冲突和安全漏洞 来源:WordPress编码标准 原因:extract()从数组键创建变量 预防:绝不使用extract(),直接访问数组元素
php
// 危险写法
extract( $_POST );
// 此时任何数组键都会成为变量

// 安全写法
$name = isset( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : '';

Issue #13: Missing Permission Callback in REST API

问题13:REST API缺少权限回调

Error: Endpoints accessible to everyone Source: WordPress REST API Handbook Why It Happens: No
permission_callback
specified Prevention: Always add permission_callback
php
// 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' );
    },
) );
错误:端点对所有人开放 来源:WordPress REST API手册 原因:未指定
permission_callback
预防:务必添加permission_callback
php
// 存在漏洞
register_rest_route( 'myplugin/v1', '/data', array(
    'callback' => 'my_callback',
) );

// 安全写法
register_rest_route( 'myplugin/v1', '/data', array(
    'callback'            => 'my_callback',
    'permission_callback' => function() {
        return current_user_can( 'edit_posts' );
    },
) );

Issue #14: Uninstall Hook Registered Repeatedly

问题14:重复注册卸载钩子

Error: Option written on every page load Source: WordPress Plugin Handbook Why It Happens: register_uninstall_hook() called in main flow Prevention: Use uninstall.php file instead
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
错误:每次页面加载都写入选项 来源:WordPress插件手册 原因:在主流程中调用register_uninstall_hook() 预防:改用uninstall.php文件
php
// 错误 - 每次页面加载都执行
register_uninstall_hook( __FILE__, 'mypl_uninstall' );

// 正确 - 使用uninstall.php文件(推荐方式)
// 在插件根目录创建uninstall.php

Issue #15: Data Deleted on Deactivation

问题15:停用插件时删除数据

Error: Users lose data when temporarily disabling plugin Source: WordPress Plugin Development Best Practices Why It Happens: Confusion about deactivation vs uninstall Prevention: Only delete data in uninstall.php, never on deactivation
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
错误:用户临时停用插件时丢失数据 来源:WordPress插件开发最佳实践 原因:混淆停用和卸载的区别 预防:仅在uninstall.php中删除数据,停用绝不删除
php
// 错误 - 停用插件时删除用户数据
register_deactivation_hook( __FILE__, function() {
    delete_option( 'mypl_user_settings' );
} );

// 正确 - 停用仅清理临时数据
register_deactivation_hook( __FILE__, function() {
    delete_transient( 'mypl_cache' );
} );

// 正确 - 在uninstall.php中删除所有数据

Issue #16: Using Deprecated Functions

问题16:使用已弃用函数

Error: Plugin breaks on WordPress updates Source: WordPress Deprecated Functions List Why It Happens: Using functions removed in newer WordPress versions Prevention: Enable WP_DEBUG during development
php
// In wp-config.php (development only)
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
错误:WordPress更新后插件崩溃 来源:WordPress已弃用函数列表 原因:使用新版本WordPress中已移除的函数 预防:开发时启用WP_DEBUG
php
// wp-config.php中设置(仅开发环境)
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Issue #17: Text Domain Mismatch

问题17:文本域不匹配

Error: Translations don't load Source: WordPress Internationalization Why It Happens: Text domain doesn't match plugin slug Prevention: Use exact plugin slug everywhere
php
// Plugin header
// Text Domain: my-plugin

// In code - MUST MATCH EXACTLY
__( 'Text', 'my-plugin' );
_e( 'Text', 'my-plugin' );
错误:翻译无法加载 来源:WordPress国际化 原因:文本域与插件slug不匹配 预防:所有地方使用完全一致的插件slug
php
// 插件头部
// Text Domain: my-plugin

// 代码中 - 必须完全匹配
__( 'Text', 'my-plugin' );
_e( 'Text', 'my-plugin' );

Issue #18: Missing Plugin Dependencies

问题18:缺少插件依赖检查

Error: Fatal error when required plugin is inactive Source: WordPress Plugin Dependencies Why It Happens: No check for required plugins Prevention: Check for dependencies on plugins_loaded
php
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
} );
错误:所需插件未激活时出现致命错误 来源:WordPress插件依赖项 原因:未检查所需插件是否存在 预防:在plugins_loaded时检查依赖
php
add_action( 'plugins_loaded', function() {
    if ( ! class_exists( 'WooCommerce' ) ) {
        add_action( 'admin_notices', function() {
            echo '<div class="error"><p>我的插件需要WooCommerce。</p></div>';
        } );
        return;
    }
    // 初始化插件
} );

Issue #19: Autosave Triggering Meta Save

问题19:自动保存触发元数据保存

Error: Meta saved multiple times, performance issues Source: WordPress Post Meta Why It Happens: No autosave check in save_post hook Prevention: Check for DOING_AUTOSAVE constant
php
add_action( 'save_post', function( $post_id ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // Safe to save meta
} );
错误:元数据多次保存,性能问题 来源:WordPress文章元数据 原因:save_post钩子中未检查自动保存 预防:检查DOING_AUTOSAVE常量
php
add_action( 'save_post', function( $post_id ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // 安全保存元数据
} );

Issue #20: admin-ajax.php Performance

问题20:admin-ajax.php性能问题

Error: Slow AJAX responses Source: https://deliciousbrains.com/comparing-wordpress-rest-api-performance-admin-ajax-php/ Why It Happens: admin-ajax.php loads entire WordPress core Prevention: Use REST API for new projects (10x faster)
php
// 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' );
        },
    ) );
} );

错误:AJAX响应缓慢 来源https://deliciousbrains.com/comparing-wordpress-rest-api-performance-admin-ajax-php/ 原因:admin-ajax.php加载整个WordPress核心 预防:新项目使用REST API(快10倍)
php
// 旧方式:admin-ajax.php(仍可使用但较慢)
add_action( 'wp_ajax_mypl_action', 'mypl_ajax_handler' );

// 新方式:REST API(快10倍,推荐)
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' );
        },
    ) );
} );

Plugin Architecture Patterns

插件架构模式

Pattern 1: Simple Plugin (Functions Only)

模式1:Simple插件(仅函数)

When to use: Small plugins with <5 functions, no complex state
php
<?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
}
适用场景:少于5个函数的小型插件,无复杂状态
php
<?php
/**
 * Plugin Name: Simple Plugin
 */

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

function mypl_init() {
    // 你的代码
}
add_action( 'init', 'mypl_init' );

function mypl_admin_menu() {
    add_options_page(
        '我的插件',
        '我的插件',
        'manage_options',
        'my-plugin',
        'mypl_settings_page'
    );
}
add_action( 'admin_menu', 'mypl_admin_menu' );

function mypl_settings_page() {
    ?>
    <div class="wrap">
        <h1>我的插件设置</h1>
    </div>
    <?php
}

Pattern 2: OOP Plugin

模式2:OOP插件

When to use: Medium plugins with related functionality, need organization
php
<?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();
适用场景:具有相关功能的中型插件,需要代码组织
php
<?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() {
        // 初始化代码
    }

    public function admin_menu() {
        add_options_page(
            '我的插件',
            '我的插件',
            'manage_options',
            'my-plugin',
            array( $this, 'settings_page' )
        );
    }

    public function settings_page() {
        ?>
        <div class="wrap">
            <h1>我的插件设置</h1>
        </div>
        <?php
    }
}

// 初始化插件
function mypl() {
    return MyPL_Plugin::get_instance();
}
mypl();

Pattern 3: PSR-4 Plugin (Modern, Recommended)

模式3:PSR-4插件(现代,推荐)

When to use: Large/modern plugins, team development, 2025+ best practice
Directory Structure:
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:
json
{
    "name": "my-vendor/my-plugin",
    "autoload": {
        "psr-4": {
            "MyPlugin\\": "src/"
        }
    },
    "require": {
        "php": ">=7.4"
    }
}
my-plugin.php:
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
<?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
    }
}

适用场景:大型/现代插件,团队开发,2025+最佳实践
目录结构
my-plugin/
├── my-plugin.php       # 主文件
├── composer.json       # 自动加载配置
├── src/                # PSR-4自动加载类
│   ├── Admin.php
│   ├── Frontend.php
│   └── Settings.php
├── languages/
└── uninstall.php
composer.json
json
{
    "name": "my-vendor/my-plugin",
    "autoload": {
        "psr-4": {
            "MyPlugin\\": "src/"
        }
    },
    "require": {
        "php": ">=7.4"
    }
}
my-plugin.php
php
<?php
/**
 * Plugin Name: PSR-4 Plugin
 */

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

// Composer自动加载器
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
<?php

namespace MyPlugin;

class Admin {

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

    public function add_menu() {
        add_options_page(
            '我的插件',
            '我的插件',
            'manage_options',
            'my-plugin',
            array( $this, 'settings_page' )
        );
    }

    public function settings_page() {
        ?>
        <div class="wrap">
            <h1>我的插件设置</h1>
        </div>
        <?php
    }
}

Common Patterns

常见模式

Pattern 1: Custom Post Types

模式1:自定义文章类型

php
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' );
php
function mypl_register_cpt() {
    register_post_type( 'book', array(
        'labels' => array(
            'name'          => '图书',
            'singular_name' => '图书',
            'add_new_item'  => '添加新图书',
        ),
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,  // 支持Gutenberg编辑器
        'supports'     => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'rewrite'      => array( 'slug' => 'books' ),
        'menu_icon'    => 'dashicons-book',
    ) );
}
add_action( 'init', 'mypl_register_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' );

Pattern 2: Custom Taxonomies

模式2:自定义分类法

php
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' );
php
function mypl_register_taxonomy() {
    register_taxonomy( 'genre', 'book', array(
        'labels' => array(
            'name'          => '分类',
            'singular_name' => '分类',
        ),
        'hierarchical' => true,  // 类似分类目录
        'show_in_rest' => true,
        'rewrite'      => array( 'slug' => 'genre' ),
    ) );
}
add_action( 'init', 'mypl_register_taxonomy' );

Pattern 3: Meta Boxes

模式3:元框

php
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' );
php
function mypl_add_meta_box() {
    add_meta_box(
        '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 ) {
    // 安全检查
    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;
    }

    // 保存数据
    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' );

Pattern 4: Settings API

模式4:Settings API

php
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
}
php
function mypl_add_menu() {
    add_options_page(
        '我的插件设置',
        '我的插件',
        '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设置',
        'mypl_section_callback',
        'my-plugin'
    );

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

function mypl_section_callback() {
    echo '<p>配置你的API设置。</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
}

Pattern 5: REST API Endpoints

模式5:REST API端点

php
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 );
}
php
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' );

    // 处理逻辑...

    return new WP_REST_Response( array(
        'success' => true,
        'data'    => $data,
    ), 200 );
}

Pattern 6: AJAX Handlers (Legacy)

模式6:AJAX处理器(旧版)

php
// 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):
javascript
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);
            }
        });
    });
});
php
// 入队脚本并传递本地化数据
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处理器(登录用户)
function mypl_ajax_handler() {
    check_ajax_referer( 'mypl-ajax-nonce', 'nonce' );

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

    // 处理逻辑...

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

// AJAX处理器(未登录用户)
add_action( 'wp_ajax_nopriv_mypl_action', 'mypl_ajax_handler' );
JavaScript代码(js/ajax.js)
javascript
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);
            }
        });
    });
});

Pattern 7: Custom Database Tables

模式7:自定义数据库表

php
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' );
php
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' );
}

// 激活时创建表
register_activation_hook( __FILE__, 'mypl_create_tables' );

Pattern 8: Transients for Caching

模式8:临时缓存

php
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' );

php
function mypl_get_expensive_data() {
    // 尝试获取缓存数据
    $data = get_transient( 'mypl_expensive_data' );

    if ( false === $data ) {
        // 无缓存 - 重新生成
        $data = perform_expensive_operation();

        // 缓存12小时
        set_transient( 'mypl_expensive_data', $data, 12 * HOUR_IN_SECONDS );
    }

    return $data;
}

// 数据变化时清除缓存
function mypl_clear_cache() {
    delete_transient( 'mypl_expensive_data' );
}
add_action( 'save_post', 'mypl_clear_cache' );

Using Bundled Resources

使用捆绑资源

Templates (templates/)

模板(templates/)

Use these production-ready templates to scaffold plugins quickly:
  • templates/plugin-simple/
    - Simple plugin with functions
  • templates/plugin-oop/
    - Object-oriented plugin structure
  • templates/plugin-psr4/
    - Modern PSR-4 plugin with Composer
  • templates/examples/meta-box.php
    - Meta box implementation
  • templates/examples/settings-page.php
    - Settings API page
  • templates/examples/custom-post-type.php
    - CPT registration
  • templates/examples/rest-endpoint.php
    - REST API endpoint
  • templates/examples/ajax-handler.php
    - AJAX implementation
When Claude should use these: When creating new plugins or implementing specific functionality patterns.
使用这些可用于生产环境的模板快速搭建插件:
  • templates/plugin-simple/
    - 仅函数的Simple插件
  • templates/plugin-oop/
    - 面向对象插件结构
  • templates/plugin-psr4/
    - 带Composer的现代PSR-4插件
  • templates/examples/meta-box.php
    - 元框实现
  • templates/examples/settings-page.php
    - Settings API页面
  • templates/examples/custom-post-type.php
    - 自定义文章类型注册
  • templates/examples/rest-endpoint.php
    - REST API端点
  • templates/examples/ajax-handler.php
    - AJAX实现
使用场景:创建新插件或实现特定功能模式时。

Scripts (scripts/)

脚本(scripts/)

  • scripts/scaffold-plugin.sh
    - Interactive plugin scaffolding
  • scripts/check-security.sh
    - Security audit for common issues
  • scripts/validate-headers.sh
    - Verify plugin headers
Example Usage:
bash
undefined
  • scripts/scaffold-plugin.sh
    - 交互式插件搭建脚本
  • scripts/check-security.sh
    - 常见问题安全审计
  • scripts/validate-headers.sh
    - 验证插件头部
示例用法
bash
undefined

Scaffold new plugin

搭建新插件

./scripts/scaffold-plugin.sh my-plugin simple
./scripts/scaffold-plugin.sh my-plugin simple

Check for security issues

检查安全问题

./scripts/check-security.sh my-plugin.php
./scripts/check-security.sh my-plugin.php

Validate plugin headers

验证插件头部

./scripts/validate-headers.sh my-plugin.php
undefined
./scripts/validate-headers.sh my-plugin.php
undefined

References (references/)

参考资料(references/)

Detailed documentation that Claude can load when needed:
  • references/security-checklist.md
    - Complete security audit checklist
  • references/hooks-reference.md
    - Common WordPress hooks and filters
  • references/sanitization-guide.md
    - All sanitization/escaping functions
  • references/wpdb-patterns.md
    - Database query patterns
  • references/common-errors.md
    - Extended error prevention guide
When Claude should load these: When dealing with security issues, choosing the right hook, sanitizing specific data types, writing database queries, or debugging common errors.

需要时可加载的详细文档:
  • references/security-checklist.md
    - 完整安全审计清单
  • references/hooks-reference.md
    - 常见WordPress钩子和过滤器
  • references/sanitization-guide.md
    - 所有清理/转义函数
  • references/wpdb-patterns.md
    - 数据库查询模式
  • references/common-errors.md
    - 扩展错误预防指南
加载场景:处理安全问题、选择合适钩子、清理特定数据类型、编写数据库查询或调试常见错误时。

Advanced Topics

高级主题

Internationalization (i18n)

国际化(i18n)

php
// 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
php
// 加载文本域
function mypl_load_textdomain() {
    load_plugin_textdomain( 'my-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
add_action( 'plugins_loaded', 'mypl_load_textdomain' );

// 可翻译字符串
__( 'Text', 'my-plugin' );  // 返回翻译后的字符串
_e( 'Text', 'my-plugin' );  // 输出翻译后的字符串
_n( 'One item', '%d items', $count, 'my-plugin' );  // 复数形式
esc_html__( 'Text', 'my-plugin' );  // 翻译并转义
esc_html_e( 'Text', 'my-plugin' );  // 翻译、转义并输出

WP-CLI Commands

WP-CLI命令

php
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' );
}
php
if ( defined( 'WP_CLI' ) && WP_CLI ) {

    class MyPL_CLI_Command {

        /**
         * 处理数据
         *
         * ## 示例
         *
         *     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( "正在处理 $limit 条数据..." );

            // 处理逻辑...

            WP_CLI::success( '处理完成!' );
        }
    }

    WP_CLI::add_command( 'mypl', 'MyPL_CLI_Command' );
}

Scheduled Events (Cron)

定时事件(Cron)

php
// 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' );
php
// 激活时计划事件
function mypl_activate() {
    if ( ! wp_next_scheduled( 'mypl_daily_task' ) ) {
        wp_schedule_event( time(), 'daily', 'mypl_daily_task' );
    }
}
register_activation_hook( __FILE__, 'mypl_activate' );

// 停用时清除事件
function mypl_deactivate() {
    wp_clear_scheduled_hook( 'mypl_daily_task' );
}
register_deactivation_hook( __FILE__, 'mypl_deactivate' );

// 绑定到定时事件
function mypl_do_daily_task() {
    // 执行任务
}
add_action( 'mypl_daily_task', 'mypl_do_daily_task' );

Plugin Dependencies Check

插件依赖检查

php
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'] );
        }
    }
} );

php
add_action( 'admin_init', function() {
    // 检查WooCommerce
    if ( ! class_exists( 'WooCommerce' ) ) {
        deactivate_plugins( plugin_basename( __FILE__ ) );

        add_action( 'admin_notices', function() {
            echo '<div class="error"><p><strong>我的插件</strong>需要安装并激活WooCommerce。</p></div>';
        } );

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

Distribution & Auto-Updates

分发与自动更新

Enabling GitHub Auto-Updates

启用GitHub自动更新

Plugins hosted outside WordPress.org can still provide automatic updates using Plugin Update Checker by YahnisElsts. This is the recommended solution for most use cases.
Quick Start:
php
// 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 );
}
Deployment:
bash
undefined
托管在WordPress.org之外的插件仍可通过YahnisElsts开发的Plugin Update Checker提供自动更新。这是大多数场景的推荐方案。
快速开始
php
// 1. 安装库(git子模块或Composer)
git submodule add https://github.com/YahnisElsts/plugin-update-checker.git

// 2. 添加到主插件文件
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'
);

// 使用GitHub Releases(推荐)
$updateChecker->getVcsApi()->enableReleaseAssets();

// 私有仓库,使用wp-config.php中的令牌
if ( defined( 'YOUR_PLUGIN_GITHUB_TOKEN' ) ) {
    $updateChecker->setAuthentication( YOUR_PLUGIN_GITHUB_TOKEN );
}
部署步骤
bash
undefined

1. Update version in plugin header

1. 更新插件头部的版本

2. Commit and tag

2. 提交并打标签

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
git add my-plugin.php git commit -m "版本升级到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)

3. 创建GitHub Release(可选但推荐)

- Upload pre-built ZIP file (exclude .git, tests, etc.)

- 上传预构建的ZIP文件(排除.git、tests等)

- Add release notes for users

- 添加用户可见的发布说明


**Key Features:**

✅ Works with GitHub, GitLab, BitBucket, or custom servers
✅ Supports public and private repositories
✅ Uses GitHub Releases or tags for versioning
✅ Secure HTTPS-based updates
✅ Optional license key integration
✅ Professional release notes and changelogs
✅ ~100KB library footprint

**Alternative Solutions:**

1. **Git Updater** (user-installable plugin, no coding required)
2. **Custom Update Server** (full control, requires hosting)
3. **Freemius** (commercial, includes licensing and payments)

**Comprehensive Resources:**

- **Complete Guide**: See `references/github-auto-updates.md` (21 pages, all approaches)
- **Implementation Examples**: See `examples/github-updater.php` (10 examples)
- **Security Best Practices**: Checksums, signing, token storage, rate limiting
- **Template Integration**: All 3 plugin templates include setup instructions

**Security Considerations:**

- ✅ Always use HTTPS for repository URLs
- ✅ Never hardcode authentication tokens (use wp-config.php)
- ✅ Implement license validation before offering updates
- ✅ Optional: Add checksums for file verification
- ✅ Rate limit update checks to avoid API throttling
- ✅ Clear cached update data after installation

**When to Use Each Approach:**

| Use Case | Recommended Solution |
|----------|---------------------|
| Open source, public repo | Plugin Update Checker |
| Private plugin, client work | Plugin Update Checker + private repo |
| Commercial plugin | Freemius or Custom Server |
| Multi-platform Git hosting | Git Updater |
| Custom licensing needs | Custom Update Server |

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

Incorrect structure will cause WordPress to create a random folder name and break the plugin!

---

**核心功能**:

✅ 支持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种插件模板都包含设置说明

**安全注意事项**:

- ✅ 始终使用HTTPS作为仓库URL
- ✅ 绝不硬编码认证令牌(使用wp-config.php)
- ✅ 提供更新前验证许可证
- ✅ 可选:添加文件校验和验证
- ✅ 限制更新检查频率以避免API限流
- ✅ 安装后清除缓存的更新数据

**方案选择**:

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

**ZIP结构要求**:
plugin.zip └── my-plugin/ ← 插件文件夹必须在ZIP内部 ├── my-plugin.php ├── readme.txt └── ...

错误的结构会导致WordPress创建随机文件夹名称并使插件无法正常工作!

---

Dependencies

依赖项

Required:
  • WordPress 5.9+ (recommend 6.7+)
  • PHP 7.4+ (recommend 8.0+)
Optional:
  • Composer 2.0+ - For PSR-4 autoloading
  • WP-CLI 2.0+ - For command-line plugin management
  • Query Monitor - For debugging and performance analysis

必需
  • WordPress 5.9+(推荐6.7+)
  • PHP 7.4+(推荐8.0+)
可选
  • Composer 2.0+ - 用于PSR-4自动加载
  • WP-CLI 2.0+ - 用于命令行插件管理
  • Query Monitor - 用于调试和性能分析

Official Documentation

官方文档

Troubleshooting

故障排除

Problem: Plugin causes fatal error

问题:插件导致致命错误

Solution:
  1. Enable WP_DEBUG in wp-config.php
  2. Check error log at wp-content/debug.log
  3. Verify all class/function names are prefixed
  4. Check for missing dependencies
解决方案
  1. 在wp-config.php中启用WP_DEBUG
  2. 查看wp-content/debug.log中的错误日志
  3. 验证所有类/函数名称都添加了前缀
  4. 检查是否缺少依赖项

Problem: 404 errors on custom post type pages

问题:自定义文章类型页面返回404错误

Solution: Flush rewrite rules
php
// Temporarily add to wp-admin
flush_rewrite_rules();
// Remove after visiting wp-admin once
解决方案:刷新重写规则
php
// 临时添加到wp-admin
flush_rewrite_rules();
// 访问wp-admin一次后移除

Problem: Nonce verification always fails

问题:Nonce验证始终失败

Solution:
  1. Check nonce name matches in field and verification
  2. Verify using correct action name
  3. Ensure nonce hasn't expired (24 hour default)
解决方案
  1. 检查字段和验证中的nonce名称是否匹配
  2. 验证使用了正确的动作名称
  3. 确保nonce未过期(默认24小时)

Problem: AJAX returns 0 or -1

问题:AJAX返回0或-1

Solution:
  1. Verify action name matches hook:
    wp_ajax_{action}
  2. Check nonce is being sent and verified
  3. Ensure handler function exists and is hooked correctly
解决方案
  1. 验证动作名称与钩子匹配:
    wp_ajax_{action}
  2. 检查nonce是否已发送并验证
  3. 确保处理器函数存在并正确绑定钩子

Problem: Sanitization stripping HTML

问题:清理操作移除了HTML

Solution: Use
wp_kses_post()
instead of
sanitize_text_field()
to allow safe HTML
解决方案:使用
wp_kses_post()
替代
sanitize_text_field()
以允许安全的HTML

Problem: Database queries not working

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

Solution:
  1. Always use
    $wpdb->prepare()
    for queries with variables
  2. Check table name includes
    $wpdb->prefix
  3. Verify column names and syntax

解决方案
  1. 包含变量的查询始终使用
    $wpdb->prepare()
  2. 检查表名包含
    $wpdb->prefix
  3. 验证列名和语法

Complete Setup Checklist

完整设置清单

Use this checklist to verify your plugin:
  • Plugin header complete with all fields
  • ABSPATH check at top of every PHP file
  • All functions/classes use unique prefix
  • All forms have nonce verification
  • All user input is sanitized
  • All output is escaped
  • All database queries use $wpdb->prepare()
  • Capability checks (not just is_admin())
  • Custom post types flush rewrite rules on activation
  • Deactivation hook only clears temporary data
  • uninstall.php handles permanent cleanup
  • Text domain matches plugin slug
  • Scripts/styles only load where needed
  • WP_DEBUG enabled during development
  • Tested with Query Monitor for performance
  • No deprecated function warnings
  • Works with latest WordPress version

Questions? Issues?
  1. Check
    references/common-errors.md
    for extended troubleshooting
  2. Verify all steps in the security foundation
  3. Check official docs: https://developer.wordpress.org/plugins/
  4. Enable WP_DEBUG and check debug.log
  5. Use Query Monitor plugin to debug hooks and queries
使用此清单验证你的插件:
  • 插件头部包含所有字段
  • 每个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插件调试钩子和查询