WordPress 文章上下方批量注入固定内容 functions.php 写法
WordPress通过修改主题functions.php添加zm_content_insert和zm_content_filter函数,利用the_content过滤器在每篇文章内容上方或下方插入广告代码、版权声明、打赏二维码等指定固定内容,比直接编辑single.php模板更便捷高效。
保哥做 WordPress 站点维护这些年,最常被问到的一个需求就是:怎么在每篇文章的开头或结尾自动加一段固定内容?比如版权声明、阅读引导、相关推荐、广告位、打赏二维码、订阅号引流框,甚至是一段不希望被搜索引擎追踪的免责声明。这种需求看似简单,但真要稳定落地、不影响 SEO、不破坏 Feed、不踩到 Gutenberg 的坑,里面的细节比很多人想的多。
这篇文章我把自己在多个项目里反复打磨的方案完整写下来,从最原始的 the_content 钩子写法,到带条件判断、带缓存友好、带 AMP 兼容的进阶版本,再到一些容易踩坑的细节,希望能帮到正在折腾 WordPress 的朋友。
为什么不建议直接改 single.php
很多教程一上来就让你打开 single.php,在 the_content() 函数前后写死一段 HTML,这种做法在小站确实能跑通,但保哥不推荐,原因有三个。
第一,主题更新会覆盖。只要你用的是市面上常见的主题,作者一旦发版,single.php 大概率会被覆写,你辛苦加的那段代码全部清零。哪怕你做了子主题,single.php 也属于经常被父主题改动的文件之一,维护成本不低。
第二,插入位置不灵活。直接写在模板里,等于把样式、内容、逻辑全部硬编码,以后想给某个分类、某种文章类型单独换一段内容,就得到处加 if 判断,模板会变得很乱。
第三,影响 RSS 和 REST API。模板层的代码只在前台页面执行,而通过钩子注入的内容会随 the_content 一起进入 RSS Feed、JSON REST API、AMP 输出。如果你不想让 Feed 里也出现广告,反而是钩子方式更可控,因为我们可以用 is_feed() 精确判断。
所以保哥的原则是:内容相关的注入,全部走 functions.php 的过滤器钩子,模板文件保持干净。
基础写法:用 the_content 过滤器统一注入
核心思路是注册一个 the_content 过滤器,在过滤器里拼好要插入的 HTML,然后根据需要拼到原 $content 的前面或后面。下面这段是我项目里在用的基础版,已经处理了 Feed、首页、归档、搜索页、附件页这些不该插入的场景。
<?php
/**
* 在文章正文上方或下方注入固定内容
* 放到当前主题的 functions.php 即可
*/
function zwb_extra_block( $position = 'after' ) {
$html = '<div class="zwb-extra-block zwb-' . esc_attr( $position ) . '">';
$html .= '<h4>关注保哥笔记</h4>';
$html .= '<p>更多 WordPress、Typecho、SEO 实战,欢迎访问 <a href="https://zhangwenbao.com/" rel="nofollow">zhangwenbao.com</a>。</p>';
$html .= '</div>';
return $html;
}
function zwb_inject_extra( $content ) {
// 仅在前台单篇文章主查询里注入
if ( is_singular( 'post' ) && in_the_loop() && is_main_query() && ! is_feed() ) {
$before = zwb_extra_block( 'before' );
$after = zwb_extra_block( 'after' );
$content = $before . $content . $after;
}
return $content;
}
add_filter( 'the_content', 'zwb_inject_extra', 20 );几个细节解释一下。is_singular('post') 限定只在文章详情页执行,自定义文章类型不受影响;in_the_loop() 和 is_main_query() 是为了避免一些插件在侧边栏里调用 the_content 时被重复注入;过滤器优先级写 20,是为了让自动段落 wpautop(默认 10)先执行完,我们再追加,HTML 结构最稳。
只在前面或只在后面插入
原始需求里经常是只想加一处。比如阅读引导放最前,版权声明放最后。把上面的函数稍微改一下就行:
function zwb_inject_before_only( $content ) {
if ( is_singular( 'post' ) && in_the_loop() && is_main_query() && ! is_feed() ) {
$content = zwb_extra_block( 'before' ) . $content;
}
return $content;
}
add_filter( 'the_content', 'zwb_inject_before_only', 20 );后置同理,把拼接顺序换一下即可。保哥更推荐用一个统一函数加参数控制,而不是写两个独立函数,后期维护改样式只要改一个地方。
按分类、标签、文章类型差异化注入
实际项目里很少所有文章都用同一段内容。比如 WordPress 教程下的文章想引导加微信,主机评测下的文章想推荐促销链接。可以这样做:
function zwb_inject_by_category( $content ) {
if ( ! ( is_singular( 'post' ) && in_the_loop() && is_main_query() && ! is_feed() ) ) {
return $content;
}
if ( has_category( 'wordpress' ) ) {
$extra = '<div class="zwb-tip">想系统学 WordPress,可以看看保哥整理的入门到精通系列。</div>';
} elseif ( has_category( 'host-review' ) ) {
$extra = '<div class="zwb-tip">本文涉及的主机当前有促销,下单前请核对官网价格。</div>';
} else {
$extra = '';
}
return $content . $extra;
}
add_filter( 'the_content', 'zwb_inject_by_category', 20 );如果分类策略复杂,建议把分类 slug 和对应 HTML 抽到一个数组里,循环匹配,functions.php 不至于堆成几百行。
不影响 Feed、AMP、REST API 的细节
保哥早年踩过一个坑:版权声明里加了一个表单,结果在 RSS 阅读器里彻底乱掉,AMP 页面还因为不合法标签被 Google Search Console 报错。所以这里专门讲讲场景判断。
is_feed() 用来排除 RSS、Atom 输出,被各种 RSS 同步、聚合阅读用到,里面尽量保持纯净。
rest API 的判断稍微麻烦,因为 the_content 也会在 wp/v2/posts 里被调用。可以用 defined('REST_REQUEST') && REST_REQUEST 判断;如果你接入了无头前端、小程序、App,建议直接 return $content,让 API 拿到的是干净正文,注入逻辑放到前端去做。
AMP 由 amp 插件接管时,可以用 function_exists('amp_is_request') && amp_is_request() 判断,AMP 模式下要么完全跳过,要么换一段不含 JS、不含表单的纯净 HTML。
合在一起就是这样:
function zwb_should_inject() {
if ( ! ( is_singular( 'post' ) && in_the_loop() && is_main_query() ) ) {
return false;
}
if ( is_feed() ) {
return false;
}
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
return false;
}
if ( function_exists( 'amp_is_request' ) && amp_is_request() ) {
return false;
}
return true;
}之后在每个注入函数最前面调一下 zwb_should_inject() 即可,不用重复写一长串判断。
SEO 友好的写法和容易忽略的细节
保哥做 SEO 这些年,看到很多人在文章上下方塞一堆站外链接,结果整站权重被稀释,甚至被算法识别成软文站。这里给几条经验。
站外链接尽量加 rel="nofollow",尤其是广告、合作、互推。如果是自己的小程序或公众号引导,可以保留 dofollow。
避免在每篇文章前后塞超过 200 字的固定内容。重复内容比例过高会拖累每篇文章的相对权重,Google 对模版化重复内容容忍度逐年降低。短小精悍的引导框比大段文字更安全。
如果你的固定内容里包含 H 标签,记得用 H4 或 H5,不要用 H2。H2 会被搜索引擎当成正文小标题,破坏文章原本的语义结构,Featured Snippets 抓取也会变得不可控。
表格、图片、二维码这种富媒体,建议用懒加载属性 loading="lazy",避免每篇文章都因为这块固定内容多一次首屏请求。
进阶:从后台可视化编辑这段内容
硬编码在 functions.php 里有个问题,每次想改文案都要登 SSH 或 FTP。保哥的做法是把内容存到 wp_options 里,后台开一个简单的设置页让运营同学自己改。
add_action( 'admin_menu', function() {
add_options_page( '正文注入设置', '正文注入', 'manage_options', 'zwb-inject', 'zwb_inject_page' );
});
function zwb_inject_page() {
if ( isset( $_POST['zwb_inject_html'] ) && check_admin_referer( 'zwb_inject_save' ) ) {
update_option( 'zwb_inject_html', wp_kses_post( wp_unslash( $_POST['zwb_inject_html'] ) ) );
echo '<div class="updated"><p>已保存</p></div>';
}
$value = get_option( 'zwb_inject_html', '' );
echo '<div class="wrap"><h1>文章下方注入内容</h1><form method="post">';
wp_nonce_field( 'zwb_inject_save' );
echo '<textarea name="zwb_inject_html" rows="10" style="width:100%">' . esc_textarea( $value ) . '</textarea>';
submit_button();
echo '</form></div>';
}注入逻辑里把硬编码 HTML 换成 get_option('zwb_inject_html', '') 即可。wp_kses_post 是关键,它会按 WordPress 默认白名单过滤掉危险标签,避免运营同学误粘贴 script 把站点搞挂。
调试和缓存相关的常见问题
做完上线,经常听到反馈说没生效。保哥总结了几个排查顺序。
第一步,确认主题没有被某些极简模板覆盖 the_content。有些主题在 single.php 里直接 echo $post->post_content,这样会绕过过滤器。改成 the_content() 或 apply_filters('the_content', $post->post_content) 才会触发钩子。
第二步,确认页面缓存。如果你用了 WP Super Cache、WP Rocket、LiteSpeed Cache,改完代码要清一次全站缓存,否则 HTML 还是旧的。CDN 层(Cloudflare、又拍云)也要刷新一次。
第三步,确认没有冲突插件。一些 SEO 插件、广告插件也在挂 the_content,并且把 $content 整个替换掉,导致你后挂的过滤器看似执行了但被它覆盖。可以临时把过滤器优先级调到 99 看看效果。
第四步,浏览器禁用扩展。AdBlock、隐私插件可能直接在前端把广告位 div 隐藏掉,看起来像没生效,其实 HTML 是渲染了的。
FAQ 常见问题
Q1:functions.php 改坏了导致整站白屏怎么办?
用 FTP 或 SSH 把 functions.php 还原到上一个版本即可。保哥的习惯是每次动 functions.php 之前先备份一份,并且开 WP_DEBUG 和 WP_DEBUG_LOG,错误会写进 wp-content/debug.log,不会直接暴露在前台。
Q2:我用了 Gutenberg 区块编辑器,注入内容会进区块结构吗?
不会。the_content 过滤器作用在区块解析后的 HTML 上,注入内容只是渲染时拼接,不会写入数据库的 post_content 字段,也不会被区块编辑器识别成区块,运营改文章不会受影响。
Q3:怎么排除某几篇不想插入的文章?
在文章里加一个自定义字段,比如 no_extra=1,然后在 zwb_should_inject 里加一句 if ( get_post_meta( get_the_ID(), 'no_extra', true ) ) return false; 即可。也可以按文章 ID 写白名单或黑名单。
Q4:和 SEO 插件、广告插件冲突怎么排查?
把所有第三方插件挂到 the_content 的优先级列出来。可以临时在 functions.php 里加一句 var_dump( $GLOBALS'wp_filter' ); exit; 查看挂载顺序,再决定自己的过滤器要不要调高优先级。优先级数字越大越后执行。
以上就是保哥这些年在 WordPress 文章上下方注入固定内容的全部经验。希望这套从基础到进阶的写法能帮你把这个看似简单的需求做扎实。如果你的项目里还有更复杂的差异化注入需求,欢迎到保哥笔记留言一起讨论。
本文标题:《WordPress 文章上下方批量注入固定内容 functions.php 写法》
版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0