本文约 5,493 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:本地跑得好好的,提交就被打回
把主题提交到 WordPress 官方目录,最打击人的不是被拒,而是拒的理由看起来都很琐碎:「找不到 wp_footer」「检测到 query_posts」「文本域与主题 slug 不一致」。这些在本地跑起来完全没问题——站点能用、样式正常、客户也满意——但它们触犯的是规范,不是功能。
好消息是这些检查可以提前自己做。官方提供了 Theme Check 插件(作者 Otto42,也是主题审核团队成员),装上就能对你的主题跑一遍完整检查,逐条列出 REQUIRED(必须修)、RECOMMENDED(建议修)、INFO(提示)。在本地把 REQUIRED 清零,提交时通过率会高很多。即便你不上架,这套检查也值得跑——它查出的 escaping 缺失、硬编码路径、过时函数,正是主题长期维护中最容易埋雷的地方。
原理分析:Theme Check 到底在查什么
Theme Check 是一套静态分析:它读取主题目录下的所有 PHP/CSS/JS 文件,用正则与词法分析匹配一组规则库,规则来自官方的《Theme Review Guidelines》。大致分四类:
| 类别 | 检查内容 | 典型报错 |
|---|---|---|
| 结构 | 必需文件、钩子调用 | Could not find wp_head、Could not find wp_footer |
| 安全 | 输出转义、输入过滤 | echo 未使用 esc_*、$_GET 未 sanitize |
| 规范 | 弃用函数、硬编码 | query_posts()、bloginfo('url')、硬编码 <script src> |
| 国际化 | 文本域、加载方式 | 文本域与 slug 不一致、缺 load_theme_textdomain |
与之互补的是 WPCS(WordPress Coding Standards):Theme Check 查「规范与合规」,phpcs 查「编码风格」。上架前两个都跑一遍,能省掉几轮来回。
注意一个常见误解:Theme Check 报的 REQUIRED 项里,有些在自用主题场景下其实无害(比如某些 INFO 提示)。判断标准是——凡涉及安全(escaping/sanitize)与兼容性(弃用函数)的,一律修;纯风格类提示可按需处理。
保姆级实操
第一步:安装并运行
wp plugin install theme-check --activate
后台「外观 → Theme Check」选择要检查的主题,点检查。命令行党可以用 WPCS 配合:
composer global require wp-coding-standards/wpcs
phpcs --standard=WordPress --extensions=php --ignore=vendor,node_modules,assets .
第二步:修「找不到 wp_head / wp_footer」
header.php 结尾前必须有 wp_head(),footer.php 结尾前必须有 wp_footer():
<?php
/**
* header.php
*/
?>
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="https://gmpg.org/xfn/11">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<?php
/**
* footer.php
*/
?>
<?php wp_footer(); ?>
</body>
</html>
wp_body_open()(WP 5.2+)也别忘了,缺了它会报 RECOMMENDED。
第三步:修输出转义(escaping)
这是被拒最多的一类。规则是所有输出动态数据的地方都要转义,且转义函数要与上下文匹配:
// 错误:直接输出
<h2><?php the_title(); ?></h2>
<a href="<?php echo get_permalink(); ?>">
<img src="<?php echo $image_url; ?>">
// 正确
<h2><?php the_title(); ?></h2>
<a href="<?php echo esc_url( get_permalink() ); ?>">
<img src="<?php echo esc_url( $image_url ); ?>" alt="<?php echo esc_attr( $alt ); ?>">
the_title()、the_content()、the_excerpt() 这类核心模板标签内部已做处理,Theme Check 不会报错;报错的通常是自己 echo 的变量。常见对应关系:
- HTML 文本 →
esc_html() - 属性值 →
esc_attr() - URL →
esc_url()/esc_url_raw()(存储时) - 富文本 HTML →
wp_kses_post() - JS 内联 →
esc_js()或wp_json_encode()
第四步:修输入过滤(sanitize)
// 错误
$color = $_POST['color'];
update_option( 'mf_color', $color );
// 正确
$color = isset( $_POST['color'] ) ? sanitize_hex_color( wp_unslash( $_POST['color'] ) ) : '';
if ( '' !== $color ) {
update_option( 'mf_color', $color );
}
常用 sanitize 函数:sanitize_text_field、sanitize_hex_color、sanitize_email、absint、esc_url_raw、wp_filter_post_kses。配合 nonce 校验更稳妥:
if ( ! isset( $_POST['mf_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['mf_nonce'] ) ), 'mf_save' ) ) {
return;
}
第五步:替换弃用/禁用写法
// 禁用:会污染主查询
query_posts( 'posts_per_page=5' );
// 改用 WP_Query 或 pre_get_posts
$query = new WP_Query( array( 'posts_per_page' => 5 ) );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
// ...
}
wp_reset_postdata();
}
其他高频替换:bloginfo( 'url' ) → echo esc_url( home_url( '/' ) );get_bloginfo( 'template_directory' ) → get_template_directory_uri();硬编码 jQuery → wp_enqueue_script( 'jquery' );wp_deregister_script( 'jquery' )(禁用核心脚本,上架必拒)。
第六步:修国际化问题
// 文本域必须等于主题 slug,且不能用变量
__( 'Read More', 'mimangfei' );
esc_html_e( 'Search', 'mimangfei' );
// 禁止变量文本域
__( 'Read More', $domain ); // Theme Check 报错
add_action( 'after_setup_theme', 'mimangfei_setup' );
function mimangfei_setup() {
load_theme_textdomain( 'mimangfei', get_template_directory() . '/languages' );
}
还有一条容易忽略:翻译函数里不要做字符串拼接,__( 'Hello ' . $name, 'mimangfei' ) 会被报错,应写成带占位符的形式并用 sprintf()。
第七步:补上必需文件与常见 REQUIRED
# 主题目录最小结构检查
ls wp-content/themes/mimangfei
# style.css(含完整头部)index.php functions.php screenshot.png readme.txt
comments.php 里要有 wp_list_comments() 与 comment_form();搜索表单要用 get_search_form();分页要用 the_posts_pagination() 或 paginate_links();style.css 里禁止出现 TimThumb、eval(、base64_decode(。另外所有函数、类、全局变量、常量、脚本句柄都要加主题前缀,避免与其他插件冲突。
常见踩坑
- 只修了报错行,没修同源问题:Theme Check 一次列出 30 条 escaping,逐条改容易漏,建议按文件从头过一遍。
esc_html用在 URL 上:虽然能过检查,但链接里会出现&之外的错误编码,用错函数比不转义更隐蔽。- 变量文本域:为了「灵活」把文本域写成变量,Theme Check 直接判 REQUIRED,翻译工具也无法解析。
- 把 jQuery 换成 CDN 版本:会报「 deregister 核心脚本」,上架必拒,改用核心自带版本。
- screenshot.png 尺寸不对:官方要求 1200×900,非此尺寸会被要求重传(见
主题打包与发布规范)。 - 案例:某主题在本地用 Theme Check 检查通过,提交后审核员指出「存在未转义的
$_SERVER['REQUEST_URI']」。开发者只在表单里做了转义,却漏了搜索框的action="<?php echo $_SERVER['REQUEST_URI']; ?>"——这个写法会把整个 URI 原样输出到属性里,属于典型的 XSS 入口。改成esc_url( home_url( '/' ) )后通过。 WP_DEBUG没开:Theme Check 建议开着检查,很多 PHP Notice(未定义索引、未定义变量)只在WP_DEBUG下暴露,审核时会被当成质量问题。- 只查了 PHP 没查 JS/CSS:Theme Check 也会扫资源文件里的可疑代码,压缩过的第三方库(如混淆的 JS)会被标记。
总结
上线前的固定动作:装 Theme Check 跑一遍 → REQUIRED 清零 → 开 WP_DEBUG 全站点一遍看有无 Notice → phpcs 用 WordPress 标准跑一遍(报错数控制在可接受范围)→ 用 Theme Unit Test 数据导入后检查各类内容样式 → 最后核对 style.css 头部、readme.txt 与截图。这套流程走完,无论上架还是交付客户,心里都有底。安全部分是重点,若你主题里有表单或设置项,务必回头对照 Customizer API 添加设置项 里的 sanitize 要求;而 escaping 的习惯,最好在 用 Underscores 从零做主题 阶段就养成。
