EP037. “建立 Program 文章类型与关联字段”
🔒 登录后可标记已读新建一个 program 自定义文章类型(math、biology、English 这种专业/学系),做法是直接复制 Section 7 建立 event CPT 时的 register_post_type() 调用来改,省得从头写。同时新建 single-program.php、archive-program.php 两个模板文件(分别从 single-event.php、archive-event.php 复制改造),并且调整了 program 归档页的默认查询:按标题字母排序、posts_per_page 设成 -1 一次性列出所有 program。最后用 ACF 建一个 "Related Program" 关系字段组,让 event 文章可以选择关联一个或多个 program,这一讲只把字段建好,前端怎么显示这个关系留到 EP038。
涉及文件
wp-content/mu-plugins/university-post-types.php(修改,新增 program CPT 注册)wp-content/themes/fictional-university-theme/single-program.php(新建,从single-event.php复制改造)wp-content/themes/fictional-university-theme/archive-program.php(新建,从archive-event.php复制改造)wp-content/themes/fictional-university-theme/functions.php(修改,university_adjust_queries()里新增 program 归档查询调整)- ACF 字段组 "Related Program"(后台 Custom Fields 里配置,不是 PHP 代码)
代码实现
wp-content/mu-plugins/university-post-types.php (修改)
在原本注册 event 的函数里,直接复制那一整段 register_post_type(),改掉几个关键值,加在 event 后面:
function university_post_types() {
// Event Post Type
register_post_type('event', array(
// ...原有 event 参数不变...
));
// 新增:Program Post Type
register_post_type('program', array(
'show_in_rest' => true,
'supports' => array('title', 'editor'), // 不需要 excerpt,比 event 少一项
'rewrite' => array('slug' => 'programs'),
'has_archive' => true,
'public' => true,
'labels' => array(
'name' => 'Programs',
'add_new_item' => 'Add New Program',
'edit_item' => 'Edit Program',
'all_items' => 'All Programs',
'singular_name' => 'Program'
),
'menu_icon' => 'dashicons-awards'
));
}
要点:
supports里没有excerpt(event 有),因为 program 用不上摘要。menu_icon用的是dashicons-awards(在 developer.wordpress.org 的 Dashicons 页面能查到图标名)。- 保存后台后台侧边栏就会多出 Programs 菜单,但这时候点开新建的 program 文章预览会 404(
page not found),因为注册了新 CPT/新的 URL 结构后,WordPress 的 permalink 规则没有自动刷新——要去后台「设置 → 固定链接(Permalinks)」随便点一次「保存更改」,强制 WordPress 重新生成 permalink 规则才会正常。
wp-content/themes/fictional-university-theme/single-program.php (新建)
从 single-event.php 整份复制过来,只改了 metabox 那行链接的文字和 href(原本是 "Events Home" 链接回 event 归档页,改成 "All Programs" 链接回 program 归档页):
<?php
get_header();
while(have_posts()) {
the_post(); ?>
<div class="page-banner">
<div class="page-banner__bg-image" style="background-image: url(<?php echo get_theme_file_uri('/images/ocean.jpg') ?>);"></div>
<div class="page-banner__content container container--narrow">
<h1 class="page-banner__title"><?php the_title(); ?></h1>
<div class="page-banner__intro">
<p>DONT FORGET TO REPLACE ME LATER</p>
</div>
</div>
</div>
<div class="container container--narrow page-section">
<div class="metabox metabox--position-up metabox--with-home-link">
<p><a class="metabox__blog-home-link" href="<?php echo get_post_type_archive_link('program'); ?>"><i class="fa fa-home" aria-hidden="true"></i> All Programs</a> <span class="metabox__main"><?php the_title(); ?></span></p>
</div>
<div class="generic-content"><?php the_content(); ?></div>
</div>
<?php }
get_footer();
?>
wp-content/themes/fictional-university-theme/archive-program.php (新建)
从 archive-event.php 复制过来,但显示方式完全不一样:event 归档页是卡片式的 event-summary,program 归档页只需要一个纯标题的列表,所以把原本 while 循环里那一大坨 event-summary div 删掉,改成简单的 <li><a>,并且删掉了底部关于「往期活动」的说明文字和分隔线:
<?php
get_header(); ?>
<div class="page-banner">
<div class="page-banner__bg-image" style="background-image: url(<?php echo get_theme_file_uri('/images/ocean.jpg') ?>);"></div>
<div class="page-banner__content container container--narrow">
<h1 class="page-banner__title">All Programs</h1>
<div class="page-banner__intro">
<p>There is something for everyone. Have a look around.</p>
</div>
</div>
</div>
<div class="container container--narrow page-section">
<ul class="link-list min-list">
<?php
while(have_posts()) {
the_post(); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php }
echo paginate_links();
?>
</ul>
</div>
<?php get_footer(); ?>
link-list min-list 这两个 class 纯粹是排版用,跟功能无关。
wp-content/themes/fictional-university-theme/functions.php (修改)
university_adjust_queries() 里已经有一段专门调整 event 归档页查询的 if 判断(Section 7 建立的),这一讲在它上面新增一段结构一样、条件换成 program 的判断:
function university_adjust_queries($query) {
// 新增:调整 program 归档页的默认查询
if (!is_admin() AND is_post_type_archive('program') AND $query->is_main_query()) {
$query->set('orderby', 'title');
$query->set('order', 'ASC');
$query->set('posts_per_page', -1);
}
if (!is_admin() AND is_post_type_archive('event') AND $query->is_main_query()) {
// ...原有 event 归档查询调整不变...
}
}
不需要在模板文件里另外写查询,直接在 pre_get_posts 这个已有的调整函数里加条件即可:program 归档页要按标题字母顺序排、而且不分页一次性列出全部(哪怕以后有 80、90 个 program)。
ACF 字段组 "Related Program"(后台配置)
在后台「Custom Fields」新建一个字段组,名字叫 "Related Program":
- 只加一个字段,字段类型选 Relationship(在 Relational 分类下面,不能选默认的 Text)。
- Filter by Post Type 勾选
program——这一步很关键,不然这个关系字段会让你选任何文章类型,而不只是 program。 - Filter by Taxonomy 留空(去掉 Post Type / Taxonomy 筛选框,只留搜索框)。
- Return Format 保持默认的 Post Object。
- Location 规则设成「Post Type 等于
event」——也就是这个字段只会出现在编辑 event 文章的页面,编辑普通 post/page 时不会出现。 - 字段一开始起名 "Related Program(s)",后来觉得打字麻烦把括号去掉;实际存进数据库、代码里用来取值的字段名是
related_programs(复数),EP038 会用get_field('related_programs')取这个字段。
保存字段组后,去编辑一篇 event(比如 "The Science of Cats"),下面就会多出这个 "Related Programs" 字段,左边是所有 program 文章可选,右边是已选中要关联的。
[截图:ACF Relationship 字段类型的设置画面(Filter by Post Type 选 program、Location 规则设为 Post Type = event)]
[截图:编辑某篇 event 文章时,下方出现的 Related Program(s) 关系字段,左侧可选 program 列表、右侧已选中列表]
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
| ACF Relationship 字段类型 | ACF 字段类型(后台配置,非 PHP 代码) | 让当前文章类型(这里是 event)可以选择关联一个或多个其他文章类型(program)的文章,返回值是 post object 数组 |
| Filter by Post Type(Relationship 字段设置项) | ACF 字段设置 | 限制这个关系字段只能选某个文章类型,不设置的话默认可以选任何类型的文章 |
register_post_type()、init、pre_get_posts里$query->set()这些函数用法已经在 Section 7 建 event CPT/调整 event 归档查询时出现过,这里是直接复用同一套模式,不重复列。
常见坑
- 注册新 CPT 后直接访问文章 permalink 会 404、标签页标题显示 "Page not found"——需要去后台「设置 → 固定链接」点一次「保存更改」强制刷新 permalink 规则。
- 如果不建
single-program.php,program 文章的详情页会退回用主题的通用single.php模板显示(页面上会看到不该出现的博客相关元素,比如返回博客首页的链接)。 - ACF 关系字段如果没设置 Filter by Post Type = program,会让用户误选到其他文章类型;Location 规则如果没限制成
event,这个字段会出现在编辑普通文章/页面的页面上,造成困扰。
延伸 / 后续讲座会用到
这一讲只是把 related_programs 关系字段建好并在后台测试了赋值,前端怎么显示这个关系(从 event 页面显示关联的 program,以及反过来从 program 页面显示关联的 event)留到 EP038。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 8, EP037