关于 wordpress-plugin-core
面向 WordPress 插件开发者的开发技能,提供实现指导与安全要点。内容涵盖插件结构、WordPress 规范,以及输入处理、权限、数据库查询和输出的防护。
主要功能
- 01插件结构与架构模式
- 02唯一前缀和 ABSPATH 检查
- 03权限能力与 nonce 校验
- 04输入净化与输出转义
- 05预处理数据库查询及生命周期指导
适用场景
- 01搭建 WordPress 插件
- 02检查插件代码中的常见安全风险
- 03实现表单、AJAX 或 REST 端点
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill wordpress-plugin-core安装后在 Claude Code 中输入/wordpress-plugin-core确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsonassets/example-template.txtreferences/common-hooks.mdexample-reference.mdgithub-auto-updates.mdsecurity-checklist.mdscripts/example-script.shscaffold-plugin.shtemplates/plugin-oop/README.mdtemplates/plugin-psr4/README.mdcomposer.jsontemplates/plugin-simple/README.md详细介绍
WordPress 插件开发(核心)
最后更新:2026-01-21 最新版本:WordPress 6.9+(2025 年 12 月 2 日),建议使用 PHP 8.0+,兼容 PHP 8.5 依赖项:无(最低要求:WordPress 5.9+、PHP 7.4+)
快速入门
架构模式:简单模式(仅使用函数,<5 个函数)| OOP(中型插件)| PSR-4(现代/大型插件,2025 年及以后推荐)
插件头部(只需填写 Plugin Name):
<?php
/**
* Plugin Name: My Plugin
* Version: 1.0.0
* Requires at least: 5.9
* Requires PHP: 7.4
* Text Domain: my-plugin
*/
if ( ! defined( 'ABSPATH' ) ) exit;
安全基础(编写功能前必须落实的 5 项要点):
// 1. Unique Prefix
define( 'MYPL_VERSION', '1.0.0' );
function mypl_init() { /* code */ }
add_action( 'init', 'mypl_init' );
// 2. ABSPATH Check (every PHP file)
if ( ! defined( 'ABSPATH' ) ) exit;
// 3. Nonces
wp_nonce_field( 'mypl_action', 'mypl_nonce' );
wp_verify_nonce( $_POST['mypl_nonce'], 'mypl_action' );
// 4. Sanitize Input, Escape Output
$clean = sanitize_text_field( $_POST['input'] );
echo esc_html( $output );
// 5. Prepared Statements
global $wpdb;
$wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}table WHERE id = %d", $id ) );
安全基础(详细说明)
唯一前缀(至少 4-5 个字符)
应用于:函数、类、常量、选项、瞬态、元键。避免使用:wp_、__、_。
function mypl_function() {} // ✅
class MyPL_Class {} // ✅
function init() {} // ❌ Will conflict
能力检查(不要使用 is_admin())
// ❌ WRONG - Security hole
if ( is_admin() ) { /* delete data */ }
// ✅ CORRECT
if ( current_user_can( 'manage_options' ) ) { /* delete data */ }
常见能力:manage_options(管理员)、edit_posts(编辑者/作者)、read(订阅者)
安全三要素(输入 → 处理 → 输出)
// Sanitize INPUT
$name = sanitize_text_field( $_POST['name'] );
$email = sanitize_email( $_POST['email'] );
$html = wp_kses_post( $_POST['content'] ); // Allow safe HTML
$ids = array_map( 'absint', $_POST['ids'] );
// Validate LOGIC
if ( ! is_email( $email ) ) wp_die( 'Invalid' );
// Escape OUTPUT
echo esc_html( $name );
echo '<a href="' . esc_url( $url ) . '">';
echo '<div class="' . esc_attr( $class ) . '">';
Nonce(CSRF 防护)
// Form
<?php wp_nonce_field( 'mypl_action', 'mypl_nonce' ); ?>
if ( ! wp_verify_nonce( $_POST['mypl_nonce'], 'mypl_action' ) ) wp_die( 'Failed' );
// AJAX
check_ajax_referer( 'mypl-ajax-nonce', 'nonce' );
wp_localize_script( 'mypl-script', 'mypl_ajax_object', array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'mypl-ajax-nonce' ),
) );
预处理语句
// ❌ SQL Injection
$wpdb->get_results( "SELECT * FROM table WHERE id = {$_GET['id']}" );
// ✅ Prepared (%s=String, %d=Integer, %f=Float)
$wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}table WHERE id = %d", $_GET['id'] ) );
// LIKE Queries
$search = '%' . $wpdb->esc_like( $term ) . '%';
$wpdb->get_results( $wpdb->prepare( "... WHERE 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
已知问题预防
此技能可预防 29 个已记录的问题:
问题 #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 插件手册、Permalink Manager Pro 原因:注册 CPT 后忘记刷新重写规则,或在每次页面加载时都调用刷新 预防措施:仅在激活/停用时刷新,绝不要在每次页面加载时刷新
// ✅ CORRECT - Only flush 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' );
// ❌ WRONG - Causes database overload on EVERY page load
add_action( 'init', 'mypl_register_cpt' );
add_action( 'init', 'flush_rewrite_rules' ); // BAD! Performance killer!
// ❌ WRONG - In functions.php
function mypl_register_cpt() {
register_post_type( 'book', ... );
flush_rewrite_rules(); // BAD! Runs every time
}
面向用户的修复方法:如果 CPT 显示 404,请前往“设置”→“固定链接”→“保存更改”手动刷新。
问题 #8:未清理 Transients
错误:数据库中累积了已过期的 Transients 来源:WordPress Transients API 文档 原因:卸载时没有清理 预防措施:在 uninstall.php 中删除 Transients
// 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 手册、Patchstack CVE 数据库
原因:未指定 permission_callback,或敏感端点缺少 show_in_index => false
预防措施:始终添加 permission_callback,并将敏感端点从 REST 索引中隐藏
2025-2026 年的真实漏洞:
- All in One SEO(300 多万个站点):缺少权限检查,导致贡献者级别的用户可以查看全局 AI 访问令牌
- AI Engine Plugin(CVE-2025-11749,CVSS 9.8 严重):未设置
show_in_index => false,导致承载令牌暴露在 /wp-json/ 索引中,未认证攻击者因此获得完整管理员权限 - SureTriggers:披露后 4 小时内便因授权检查不足遭到利用
- Worker for Elementor(CVE-2025-66144):订阅者级别的权限即可调用受限功能
// ❌ VULNERABLE - Missing permission_callback (WordPress 5.5+ requires it!)
register_rest_route( 'myplugin/v1', '/data', array(
'methods' => 'GET',
'callback' => 'my_callback',
) );
// ✅ SECURE - Basic protection
register_rest_route( 'myplugin/v1', '/data', array(
'methods' => 'GET',
'callback' => 'my_callback',
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
) );
// ✅ SECURE - Hide sensitive endpoints from REST index
register_rest_route( 'myplugin/v1', '/admin', array(
'methods' => 'POST',
'callback' => 'my_admin_callback',
'permission_callback' => function() {
return current_user_can( 'manage_options' );
},
'show_in_index' => false, // Don't expose in /wp-json/
) );
2025-2026 年统计数据:共追踪到 64,782 个漏洞,一周内新增 333 个,236 个仍未修补。REST API 身份验证问题占据了相当大的比例。
问题 #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 国际化 原因:文本域与插件 slug 不匹配 预防措施:在所有地方使用完全一致的插件 slug
// 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' );
},
) );
} );
问题 #21:区块编辑器缺少 show_in_rest
错误:自定义文章类型显示的是经典编辑器,而不是 Gutenberg 区块编辑器
来源:WordPress VIP 文档、GitHub 问题 #7595
原因:注册自定义文章类型时忘记设置 show_in_rest => true
预防措施:需要使用区块编辑器的 CPT 始终应包含 show_in_rest
// ❌ WRONG - Block editor won't work
register_post_type( 'book', array(
'public' => true,
'supports' => array('editor'),
// Missing show_in_rest!
) );
// ✅ CORRECT
register_post_type( 'book', array(
'public' => true,
'show_in_rest' => true, // Required for block editor
'supports' => array('editor'),
) );
重要规则:只有注册时设置了 'show_in_rest' => true 的文章类型才兼容区块编辑器。区块编辑器依赖 WordPress REST API。对于与区块编辑器不兼容或设置了 show_in_rest => false 的文章类型,将改为加载经典编辑器。
问题 #22:wpdb::prepare() 表名转义
错误:表名被加引号导致 SQL 语法错误,或硬编码的前缀在不同安装环境中失效 来源:WordPress 编码标准问题 #2442 原因:将表名用作占位符会在表名两侧加上引号 预防措施:表名绝不能放入 prepare() 占位符中
// ❌ WRONG - Adds quotes around table name
$table = $wpdb->prefix . 'my_table';
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM %s WHERE id = %d",
$table, $id
) );
// Result: SELECT * FROM 'wp_my_table' WHERE id = 1
// FAILS - table name is quoted
// ❌ WRONG - Hardcoded prefix
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM wp_my_table WHERE id = %d",
$id
) );
// FAILS if user changed table prefix
// ✅ CORRECT - Table name NOT in prepare()
$table = $wpdb->prefix . 'my_table';
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$table} WHERE id = %d",
$id
) );
// ✅ CORRECT - Using wpdb->prefix for built-in tables
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d",
$id
) );
问题 #23:Nonce 验证边缘情况
错误:Nonce 验证失败导致用户体验困惑,或造成虚假的安全感 来源:MalCare:wp_verify_nonce()、Pressidium:理解 Nonce 发生原因:误解 Nonce 的行为和局限性 预防措施:了解 Nonce 的边缘情况,并始终将其与权限检查结合使用
边缘情况:
- 基于时间的返回值:
$result = wp_verify_nonce( $nonce, 'action' );
// Returns 1: Valid, generated 0-12 hours ago
// Returns 2: Valid, generated 12-24 hours ago
// Returns false: Invalid or expired
-
Nonce 可重复使用:WordPress 不会跟踪某个 Nonce 是否已被使用。在 12-24 小时的有效期内,同一个 Nonce 可以多次使用。
-
会话失效:Nonce 只有在绑定到有效会话时才有效。如果用户注销,所有 Nonce 都会失效;如果用户当时打开着表单,这会造成令人困惑的用户体验。
-
缓存问题:缓存插件提供较旧的 Nonce 时,缓存问题可能导致不匹配。
-
不能替代授权:
// ❌ INSUFFICIENT - Only checks origin, not permission
if ( wp_verify_nonce( $_POST['nonce'], 'delete_user' ) ) {
delete_user( $_POST['user_id'] );
}
// ✅ CORRECT - Combine with capability check
if ( wp_verify_nonce( $_POST['nonce'], 'delete_user' ) &&
current_user_can( 'delete_users' ) ) {
delete_user( absint( $_POST['user_id'] ) );
}
关键原则(2025 年):绝不能依赖 Nonce 进行身份验证或授权。始终假设 Nonce 可能已泄露。使用 current_user_can() 保护函数。
问题 #24:钩子优先级和参数数量
错误:钩子回调未收到预期参数,或运行顺序不正确 来源:Kinsta:WordPress 钩子训练营 发生原因:默认只传递 1 个参数,默认优先级为 10 预防措施:需要时明确指定参数数量和优先级
// ❌ WRONG - Only receives $post_id
add_action( 'save_post', 'my_save_function' );
function my_save_function( $post_id, $post, $update ) {
// $post and $update are NULL!
}
// ✅ CORRECT - Specify argument count
add_action( 'save_post', 'my_save_function', 10, 3 );
function my_save_function( $post_id, $post, $update ) {
// Now all 3 arguments are available
}
// Priority matters (lower number = runs earlier)
add_action( 'init', 'first_function', 5 ); // Runs first
add_action( 'init', 'second_function', 10 ); // Default priority
add_action( 'init', 'third_function', 15 ); // Runs last
最佳实践:
- 始终为自定义钩子名称添加前缀,避免冲突:使用
do_action( 'mypl_data_processed' ),而非do_action( 'data_processed' ) - 过滤器必须 RETURN 修改后的数据,而不是将其输出
- 钩子的位置会影响向后兼容性,因此要谨慎选择
问题 #25:自定义文章类型 URL 冲突
错误:即使刷新了固定链接,单篇 CPT 文章仍返回 404 错误 来源:Permalink Manager Pro:URL 冲突 发生原因:CPT slug 与页面 slug 相同,从而产生 URL 冲突 预防措施:为 CPT 使用不同的 slug,或重命名页面
// ❌ CONFLICT - Page and CPT use same slug
// Page URL: example.com/portfolio/
register_post_type( 'portfolio', array(
'rewrite' => array( 'slug' => 'portfolio' ),
) );
// Individual posts 404: example.com/portfolio/my-project/
// ✅ SOLUTION 1 - Use different slug for CPT
register_post_type( 'portfolio', array(
'rewrite' => array( 'slug' => 'projects' ),
) );
// Posts: example.com/projects/my-project/
// Page: example.com/portfolio/
// ✅ SOLUTION 2 - Use hierarchical slug
register_post_type( 'portfolio', array(
'rewrite' => array( 'slug' => 'work/portfolio' ),
) );
// Posts: example.com/work/portfolio/my-project/
// ✅ SOLUTION 3 - Rename the page slug
// Change page from /portfolio/ to /our-portfolio/
问题 #26:WordPress 6.8 bcrypt 密码哈希迁移
错误:升级 WordPress 6.8 后,自定义密码哈希处理失效 来源:WordPress Core Make、GitHub 问题 #21022 发生原因:WordPress 6.8 及更高版本将密码哈希算法从 phpass 切换为 bcrypt 预防措施:使用 WordPress 密码函数,不要直接处理哈希值
变更内容(WordPress 6.8,2025 年 4 月):
- 默认密码哈希算法从 phpass 更改为 bcrypt
- 新哈希前缀:
$wp$2y$(经过 SHA-384 预哈希的 bcrypt) - 现有密码会在下次登录时自动重新哈希
- 常用的 bcrypt 插件(roots/wp-password-bcrypt)现已多余
// ✅ SAFE - These functions continue to work without changes
wp_hash_password( $password );
wp_check_password( $password, $hash );
// ⚠️ NEEDS UPDATE - Direct phpass hash handling
if ( strpos( $hash, '$P$' ) === 0 ) {
// Custom phpass logic - needs update for bcrypt
}
// ✅ NEW - Detect hash type
if ( strpos( $hash, '$wp$2y$' ) === 0 ) {
// bcrypt hash (WordPress 6.8+)
} elseif ( strpos( $hash, '$P$' ) === 0 ) {
// phpass hash (WordPress <6.8)
}
需要采取的措施:
- 检查直接处理密码哈希的插件
- 升级到 6.8 及更高版本时,移除 bcrypt 插件
- 如果标准使用 wp_hash_password/wp_check_password,则无需采取任何措施
问题 #27:WordPress 6.9 WP_Dependencies 弃用
错误:“弃用:调用 WP_Dependencies->add_data() 时使用了已弃用的参数” 来源:WordPress 6.9 文档,WordPress 支持论坛 发生原因:WordPress 6.9(2025 年 12 月 2 日)弃用了 WP_Dependencies 对象方法 预防措施:在启用 WP_DEBUG 的 WordPress 6.9 上测试插件,替换已弃用的方法
受影响的插件(已确认):
- WooCommerce(已在 10.4.2 中修复)
- Yoast SEO(已在 26.6 中修复)
- Elementor(需要 3.24+)
破坏性变更:WordPress 6.9 移除或修改了若干旧主题和插件所依赖的已弃用函数,导致自定义菜单 walker、经典小工具、媒体模态框和自定义器功能出现问题。
需要采取的措施:
- 在启用 WP_DEBUG 的 WordPress 6.9 上测试插件
- 替换已弃用的 WP_Dependencies 方法
- 检查 debug.log 中的弃用通知
- 虽然排名前 1,000 的插件在数小时内就完成了修补,但无人维护的插件往往会滞后
问题 #28:WordPress 6.7 中的翻译加载变更
错误:翻译未加载或出现调试通知 来源:WooCommerce 开发者博客,WordPress 6.7 指南 发生原因:WordPress 6.7+ 更改了翻译的加载时间和方式 预防措施:在 'init' 优先级 10 之后加载翻译,确保文本域与插件 slug 相符
// ❌ WRONG - Loading too early
add_action( 'init', 'load_plugin_textdomain' );
// ✅ CORRECT - Load after 'init' priority 10
add_action( 'init', 'load_plugin_textdomain', 11 );
// Ensure text domain matches plugin slug EXACTLY
// Plugin header: Text Domain: my-plugin
__( 'Text', 'my-plugin' ); // Must match exactly
需要采取的措施:
- 检查 load_plugin_textdomain() 的调用时机
- 确保文本域与插件 slug 完全相符
- 在启用 WP_DEBUG 的情况下进行测试
问题 #29:wpdb::prepare() 缺少占位符错误
错误:“wpdb::prepare() 的查询参数必须包含占位符” 来源:WordPress $wpdb 文档,SitePoint:使用数据库 发生原因:调用 prepare() 时未使用任何占位符 预防措施:如果没有动态数据,请勿使用 prepare()
// ❌ WRONG
$wpdb->prepare( "SELECT * FROM {$wpdb->posts}" );
// Error: The query argument of wpdb::prepare() must have a placeholder
// ✅ CORRECT - Don't use prepare() if no dynamic data
$wpdb->get_results( "SELECT * FROM {$wpdb->posts}" );
// ✅ CORRECT - Use prepare() for dynamic data
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d",
$post_id
) );
wpdb::prepare() 的其他常见错误:
- 百分号处理:
// ❌ WRONG
$wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE post_title LIKE '%test%'" );
// ✅ CORRECT
$search = '%' . $wpdb->esc_like( $term ) . '%';
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_title LIKE %s",
$search
) );
- 混用参数格式:
// ❌ WRONG - Can't mix individual args and array
$wpdb->prepare( "... WHERE id = %d AND name = %s", $id, array( $name ) );
// ✅ CORRECT - Pick one format
$wpdb->prepare( "... WHERE id = %d AND name = %s", $id, $name );
// OR
$wpdb->prepare( "... WHERE id = %d AND name = %s", array( $id, $name ) );
插件架构模式
简单模式(仅使用函数)
小型插件(少于 5 个函数):
function mypl_init() { /* code */ }
add_action( 'init', 'mypl_init' );
OOP(单例)
中型插件:
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() {
add_action( 'init', array( $this, 'init' ) );
}
}
MyPL_Plugin::get_instance();
PSR-4(现代模式,2025 年及以后推荐)
大型/团队插件:
my-plugin/
├── my-plugin.php
├── composer.json → "psr-4": { "MyPlugin\\": "src/" }
└── src/Admin.php
// my-plugin.php
require_once __DIR__ . '/vendor/autoload.php';
use MyPlugin\Admin;
new Admin();
常见模式
自定义文章类型(重要:激活时刷新重写规则,在区块编辑器中启用 show_in_rest):
// show_in_rest => true REQUIRED for Gutenberg block editor
register_post_type( 'book', array(
'public' => true,
'show_in_rest' => true, // Without this, block editor won't work!
'supports' => array( 'editor', 'title' ),
) );
register_activation_hook( __FILE__, function() {
mypl_register_cpt();
flush_rewrite_rules(); // NEVER call on every page load
} );
自定义分类法:
register_taxonomy( 'genre', 'book', array( 'hierarchical' => true, 'show_in_rest' => true ) );
元数据框:
add_meta_box( 'book_details', 'Book Details', 'mypl_meta_box_html', 'book' );
// Save: Check nonce, DOING_AUTOSAVE, current_user_can('edit_post')
update_post_meta( $post_id, '_book_isbn', sanitize_text_field( $_POST['book_isbn'] ) );
设置 API:
register_setting( 'mypl_options', 'mypl_api_key', array( 'sanitize_callback' => 'sanitize_text_field' ) );
add_settings_section( 'mypl_section', 'API Settings', 'callback', 'my-plugin' );
add_settings_field( 'mypl_api_key', 'API Key', 'field_callback', 'my-plugin', 'mypl_section' );
REST API(比 admin-ajax.php 快 10 倍):
register_rest_route( 'myplugin/v1', '/data', array(
'methods' => 'POST',
'callback' => 'mypl_rest_callback',
'permission_callback' => fn() => current_user_can( 'edit_posts' ),
) );
AJAX(旧版项目,新项目请使用 REST API):
add_action( 'wp_ajax_mypl_action', 'mypl_ajax_handler' );
check_ajax_referer( 'mypl-ajax-nonce', 'nonce' );
wp_send_json_success( array( 'message' => 'Success' ) );
自定义表:
global $wpdb;
$sql = "CREATE TABLE {$wpdb->prefix}mypl_data (id bigint AUTO_INCREMENT PRIMARY KEY, ...)";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
瞬态数据(缓存):
$data = get_transient( 'mypl_data' );
if ( false === $data ) {
$data = expensive_operation();
set_transient( 'mypl_data', $data, 12 * HOUR_IN_SECONDS );
}
附带资源
模板:plugin-simple/、plugin-oop/、plugin-psr4/、examples/meta-box.php、examples/settings-page.php、examples/custom-post-type.php、examples/rest-endpoint.php、examples/ajax-handler.php
脚本:scaffold-plugin.sh、check-security.sh、validate-headers.sh
参考资料:security-checklist.md、hooks-reference.md、sanitization-guide.md、wpdb-patterns.md、common-errors.md
高级主题
i18n(国际化):
load_plugin_textdomain( 'my-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
__( 'Text', 'my-plugin' ); // Return translated
_e( 'Text', 'my-plugin' ); // Echo translated
esc_html__( 'Text', 'my-plugin' ); // Translate + escape
WP-CLI:
if ( defined( 'WP_CLI' ) && WP_CLI ) {
WP_CLI::add_command( 'mypl', 'MyPL_CLI_Command' );
}
Cron 事件:
register_activation_hook( __FILE__, fn() => wp_schedule_event( time(), 'daily', 'mypl_daily_task' ) );
register_deactivation_hook( __FILE__, fn() => wp_clear_scheduled_hook( 'mypl_daily_task' ) );
add_action( 'mypl_daily_task', 'mypl_do_daily_task' );
插件依赖项:
if ( ! class_exists( 'WooCommerce' ) ) {
deactivate_plugins( plugin_basename( __FILE__ ) );
add_action( 'admin_notices', fn() => echo '<div class="error"><p>Requires WooCommerce</p></div>' );
}
分发与自动更新
GitHub 自动更新(YahnisElsts 的 Plugin Update Checker):
// 1. Install: 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'
);
$updateChecker->getVcsApi()->enableReleaseAssets(); // Use GitHub Releases
// Private repos: Define token in wp-config.php
if ( defined( 'YOUR_PLUGIN_GITHUB_TOKEN' ) ) {
$updateChecker->setAuthentication( YOUR_PLUGIN_GITHUB_TOKEN );
}
部署:
git tag 1.0.1 && git push origin main && git push origin 1.0.1
# Create GitHub Release with ZIP (exclude .git, tests)
替代方案:Git Updater(无需编写代码)、自定义更新服务(完全掌控)、Freemius(商业方案)
安全性:使用 HTTPS,切勿将令牌硬编码,验证许可证,并限制更新检查频率
重要:ZIP 中必须包含插件文件夹:plugin.zip/my-plugin/my-plugin.php
资源:参见 references/github-auto-updates.md、examples/github-updater.php
依赖项
必需:
- WordPress 5.9+(推荐 6.7+)
- PHP 7.4+(推荐 8.0+)
可选:
- Composer 2.0+ - 用于 PSR-4 自动加载
- WP-CLI 2.0+ - 用于通过命令行管理插件
- Query Monitor - 用于调试和性能分析
官方文档
- WordPress 插件手册:https://developer.wordpress.org/plugins/
- WordPress 编码标准:https://developer.wordpress.org/coding-standards/
- WordPress REST API:https://developer.wordpress.org/rest-api/
- WordPress 数据库类($wpdb):https://developer.wordpress.org/reference/classes/wpdb/
- WordPress 安全性:https://developer.wordpress.org/apis/security/
- 设置 API:https://developer.wordpress.org/plugins/settings/settings-api/
- 自定义文章类型:https://developer.wordpress.org/plugins/post-types/
- Transients API:https://developer.wordpress.org/apis/transients/
- Context7 库 ID:/websites/developer_wordpress
故障排查
致命错误:启用 WP_DEBUG,检查 wp-content/debug.log,确认名称带有前缀,并检查依赖项
自定义文章类型出现 404:通过“设置”→“固定链接”→“保存”刷新重写规则
Nonce 验证失败:检查 nonce 名称和操作是否匹配,并确认未过期(默认 24 小时)
AJAX 返回 0/-1:确认操作名称与 wp_ajax_{action} 匹配,并检查 nonce 是否已发送和验证
HTML 被移除:安全处理 HTML 时使用 wp_kses_post(),而非 sanitize_text_field()
查询失败:使用 $wpdb->prepare(),检查 $wpdb->prefix,并确认语法正确
完整设置检查清单
使用此检查清单验证你的插件:
- 插件头部信息完整,包含所有字段
- 每个 PHP 文件顶部都有 ABSPATH 检查
- 所有函数/类都使用唯一前缀
- 所有表单都进行 nonce 验证
- 所有用户输入都经过净化
- 所有输出都经过转义
- 所有数据库查询都使用 $wpdb->prepare()
- 检查能力权限(不能只检查 is_admin())
- 自定义文章类型在激活时刷新重写规则
- 停用钩子只清除临时数据
- uninstall.php 处理永久清理
- 文本域与插件 slug 匹配
- 脚本/样式仅在需要的地方加载
- 开发期间启用 WP_DEBUG
- 使用 Query Monitor 测试性能
- 没有已弃用函数的警告
- 兼容最新的 WordPress 版本
有问题?遇到故障?
- 查看
references/common-errors.md,了解更详细的故障排查信息 - 核实安全基础中的所有步骤
- 查看官方文档:https://developer.wordpress.org/plugins/
- 启用 WP_DEBUG 并检查 debug.log
- 使用 Query Monitor 插件调试钩子和查询
最后验证时间:2026-01-21 | 技能版本:2.0.0 | 变更:根据 WordPress 6.7-6.9 研究新增了 9 个问题(bcrypt 迁移、WP_Dependencies 弃用、翻译加载、REST API 2025-2026 年 CVE、wpdb::prepare() 边缘情况、nonce 限制、钩子注意事项、自定义文章类型 URL 冲突)。记录并预防的错误从 20 个增至 29 个。错误数量:20 → 29。版本:1.x → 2.0.0(因新增大量特定于 WordPress 版本的内容,故升级主版本号)。