WP DEVELOP

EP161. “用 the_content 过滤器与 meta_query 查反向关系”

首页 WordPress 开发课程 新 BLOCK 类型练习 · EP161
约 15 分钟· #EP161#新 BLOCK 类型练习
🔒 登录后可标记已读

Featured Professor 插件的收尾一讲:在 Professor 详情页正文末尾自动追加一段「本教授被以下文章提到」的列表,把 EP160 存好的 featuredprofessor 元数据反向查询出来用上,让这段关系真正变成「双向可查」。做法是用 the_content 过滤器钩子,只在「Professor 详情页 + 在循环里 + 是主查询」三个条件都满足时才往正文末尾追加内容;具体查询用 WP_Querymeta_query 参数——按元数据的 key/value 反查出所有存过「这位 Professor 的 ID」的文章。这也是这门课 JS 章节里 Featured Professor 这个插件的最后一讲,下一讲开始讲 JavaScript 端的多语言/翻译。


涉及文件

  • wp-content/plugins/featured-professor/featured-professor.php (修改)
  • wp-content/plugins/featured-professor/inc/relatedPostsHTML.php (新建)

代码实现

featured-professor.php:新增 the_content 过滤器,条件判断后追加关联文章列表

require_once plugin_dir_path(__FILE__) . 'inc/generateProfessorHTML.php';
require_once plugin_dir_path(__FILE__) . 'inc/relatedPostsHTML.php';

class FeaturedProfessor {
  function __construct() {
    add_action('init', [$this, 'onInit']);
    add_action('rest_api_init', [$this, 'profHTML']);
    add_filter('the_content', [$this, 'addRelatedPosts']);
  }

  function addRelatedPosts($content) {
    if (is_singular('professor') && in_the_loop() && is_main_query()) {
      return $content . relatedPostsHTML(get_the_id());
    }
    return $content;
  }

  // ...profHTML() / getProfHTML() / onInit() / renderCallback() 不变,见 EP157-160...
}

inc/relatedPostsHTML.php(新建):用 meta_query 反查所有提到这位 Professor 的文章

<?php

function relatedPostsHTML($id) {
  $postsAboutThisProf = new WP_Query(array(
    'posts_per_page' => -1,
    'post_type' => 'post',
    'meta_query' => array(
      array(
        'key' => 'featuredprofessor',
        'compare' => '=',
        'value' => $id
      )
    )
  ));

  ob_start();

  if ($postsAboutThisProf->found_posts) { ?>
    <p><?php the_title(); ?> is mentioned in the following posts:</p>
    <ul>
      <?php
      while($postsAboutThisProf->have_posts()) {
        $postsAboutThisProf->the_post(); ?>
        <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
      <?php }
      ?>
    </ul>
  <?php }

  wp_reset_postdata();
  return ob_get_clean();
}

关键改动点:

  • add_filter('the_content', ...)the_content 是 WordPress 里输出文章正文时会经过的一个标准过滤器钩子,回调函数接收「原始正文」当参数,return 的值会替换成最终真正显示的正文——这是「往正文末尾追加内容,又不用直接改模板文件」的标准做法
  • 三重条件缺一不可
    • is_singular('professor'):当前是不是 Professor 这个文章类型的单篇详情页(避免在文章列表页、其他文章类型详情页也误触发)
    • in_the_loop():当前是不是正处于 WordPress 的主循环内部——因为 the_content 过滤器理论上任何用到 the_content()/get_the_content() 的地方都可能触发,包括一些插件在后台或者其他自定义查询里也调用这个函数,加这个判断确保只在真正渲染主循环的正文时生效
    • is_main_query():当前是不是页面的主查询(而不是页面里某个自定义的次要查询,比如侧边栏的「相关文章」小工具也可能内部调用了 the_content)——三个条件一起用是这门课作者一贯的保守写法,避免函数在意料之外的场景被误触发
  • $content . relatedPostsHTML(get_the_id()):用字符串拼接(.)把原始正文和新生成的关联文章列表 HTML 接在一起,get_the_id() 拿到当前 Professor 文章的 ID,传给新写的 relatedPostsHTML() 函数
  • 同样把大段查询 + HTML 拆到独立文件:延续 EP157 的组织习惯,新建 inc/relatedPostsHTML.php,主插件文件顶部再加一行 require_once 引入它
  • WP_Querymeta_query 参数:用来「按元数据筛选文章」——最外层是一个数组,里面可以放多个查询条件数组(这里只用了一个),每个条件数组里:
    • 'key' => 'featuredprofessor':要匹配的元数据名字,必须跟 EP160 register_meta() 注册的名字完全一致
    • 'compare' => '=':比较方式,这里要求精确匹配
    • 'value' => $id':要匹配的值,也就是当前这位 Professor 的文章 ID
    • 效果等同于「查询所有 post 类型的文章里,凡是存过一条 meta_key = featuredprofessormeta_value 等于这位 Professor ID 的文章」
  • 'posts_per_page' => -1:一次拿全部符合条件的结果,不分页
  • if ($postsAboutThisProf->found_posts)found_postsWP_Query 对象上的一个属性,记录查询结果的总条数——只有真的查到至少一篇相关文章时才输出这段「被提到」的介绍文字和列表,避免完全没有关联文章时也显示一段空的「is mentioned in the following posts:」
  • <?php the_title(); ?> is mentioned in the following posts::这里的 the_title() 用的是外层还没进入 while 循环时的全局上下文——此时全局当前文章依然是这个 Professor 详情页本身(因为还没执行 $postsAboutThisProf->the_post()),所以这里输出的是 Professor 自己的名字,不是某篇关联文章的标题
  • while ($postsAboutThisProf->have_posts()) { $postsAboutThisProf->the_post(); ... }:标准的自定义查询循环写法(跟 EP157 的 generateProfessorHTML() 一致),每一轮循环把全局「当前文章」切换成这次遍历到的这篇关联文章,这样循环体内的 the_permalink()/the_title() 才会输出正确的那篇文章的信息
  • wp_reset_postdata():循环结束后照例要重置全局文章状态,避免影响页面剩余部分(这里是 Professor 详情页自身接下来的渲染)

Hook / Function 速查

名称类型用途
the_content(过滤器)WP hook文章正文输出前的标准过滤器,用来动态追加/修改正文内容
is_singular($post_type)WP 内建 function判断当前是否处于指定文章类型的单篇详情页
in_the_loop()WP 内建 function判断当前是否处于 WordPress 主循环内部
is_main_query()WP 内建 function判断当前查询是否为页面的主查询(区分自定义次要查询)
meta_queryWP_Query 参数)WP 查询参数按文章元数据的 key/value 筛选文章
$query->found_postsWP_Query 对象属性查询结果的总条数
get_the_id()WP 内建 function获取当前全局文章的 ID

常见坑

  • 只判断 is_singular('professor') 就直接追加内容,不检查 in_the_loop()/is_main_query()——万一页面其他地方(侧边栏小工具、其他自定义查询)也调用了正文相关的函数,可能在意料之外的地方重复触发这段追加逻辑
  • meta_query 里的 key 拼错或者跟 register_meta() 注册的名字对不上——查询会返回空结果,即使数据库里其实已经正确存了关联数据
  • 忘记判断 found_posts 是否大于零——完全没有关联文章的 Professor 详情页也会显示一句不知所云的「被以下文章提到:」但后面是空列表
  • 自定义查询循环结束后忘记 wp_reset_postdata()——影响 Professor 详情页自身接下来的渲染,导致标题、内容等显示错乱

[截图:Professor 详情页正文末尾自动追加的"is mentioned in the following posts"关联文章列表]


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 26, EP161