在主题中构建基于块的动态附件模板
自 WordPress 6.4 起,前端附件(媒体)页面视图对于新的 WordPress 安装已被禁用。这一变化酝酿已久——附件页面多年来已逐渐失宠。但它们仍然可以发挥作用,尤其是在图片库或摄影网站上。
无论你喜欢还是讨厌它们,附件都是主题开发中必须面对的现实,特别是如果你要公开发布你的主题。仅仅因为新安装默认禁用它们,并不意味着没有数百万个 WordPress 网站仍然启用着它们,或者新用户不会启用它们。
我百分之百确定我是少数派,但我碰巧仍然喜欢前端附件视图。也许这是对 WordPress 早期时代的怀念。或许只是我在回忆过去为客户构建完整图片库网站的日子。
无论你自己是否使用它们,确保附件视图正常工作并符合你的主题设计都很重要。
在构建块主题时,这是一个问题,特别是如果你计划包含自定义附件模板。WordPress 目前没有允许你将动态数据(如当前附件 ID)附加到其上的媒体块。
这个问题可能会通过块绑定/连接 API以及将自定义字段连接到块的能力得到解决。只有时间能证明,未来的功能无法解决我们今天的问题。但在重构时记住它们是好的。
在本教程中,你将了解在设计附件模板时会面临哪些问题,以及我推荐的解决方法。我还将提供一些你可以探索的替代路径。
你可以通过Dynamic Attachments 仓库来学习本教程中的代码示例。
基于块的附件模板存在的问题
首先,让我们看看使用Twenty Twenty-Four主题时,默认附件页面的样子。在下面的图库中,你可以看到图片和视频附件页面:
作为主题设计师,你可能立即想解决一些问题:
- 图片附件显示的是小缩略图,而不是至少能填满内容区域宽度的较大尺寸。
- 视频附件使用旧的 MediaElement.js 播放器,而不是核心视频块使用的浏览器默认播放器。
- 作者和日期显示在标题下方,这对于附件来说通常是不需要的。这些也通常不是媒体文件本身的作者和日期。
- 对于大多数用例来说,不需要评论表单。
这里发生了几件事。因为 Twenty Twenty-Four 主题不包含自定义的 templates/attachment.html 文件,WordPress 会自动回退到 templates/single.html 模板,该模板是专门为博客文章设计的。
有关如何为前端视图选择模板的更多信息,请查看主题手册中的模板层次结构文档。
当主题不包含附件模板时,WordPress 会自动过滤内容,在其前面添加媒体输出。但这并不总是与每个主题都搭配得很好。
在经典主题中,通过在 attachment.php 模板中编写一些自定义的 HTML 和 PHP 代码,处理起来很简单。
对于块主题,问题在于 WordPress 没有为附件模板提供块支持。使用块模板获得完美的附件页面设计有点棘手,但并非无法克服。
所以,让我们继续前进,以一种适合你主题的方式来解决这个问题。
自定义前端附件视图
要创建更好的附件模板,你需要一种方法来使用带有动态媒体数据的核心 WordPress 块。例如,对于图片附件,你可能希望获取图片文件 URL、替代文本和标题,并将它们与核心图片块关联起来。
我发现的最佳方法是在前端渲染内容时对其进行过滤。这样,你可以让用户在 UI 中控制编辑附件模板。并且你不必担心他们会破坏你漂亮的媒体功能。
本教程的其余部分将假设你正在使用 Twenty Twenty-Four 或基于它的子主题。当然,你会希望将这些技术应用到你自己的主题中,但目前我们需要一个共同的基础来工作。
这里有两条路可以走:
- 创建一个自定义的
templates/attachment.html模板来完全控制其外观。 - 避免创建
templates/attachment.html模板,只自定义媒体输出。
两者都是有效的选择,但你将在以下部分中了解到每种情况需要做什么。
启用附件页面进行测试
在继续之前,让我们确保你实际上可以在开发环境中测试附件。请记住,新的 WordPress 安装会禁用附件页面,因此如果你正在启动一个新站点进行开发,则需要打开它们。
将此代码添加到你的测试站点的自定义插件中:
add_filter( 'pre_option_wp_attachment_pages_enabled', '__return_true' );
在紧急情况下,你可以将其放入主题的 functions.php 文件中,但在将主题分发给他人之前不要忘记删除它。你不想覆盖用户的偏好设置。
创建附件模板
创建自定义附件模板的最简单方法是复制 Twenty Twenty-Four 主题的 templates/single.html 文件的内容,并将其粘贴到新的 templates/attachment.html 文件中。
唯一的要求是在模板中包含对 <!-- wp:post-content /--> 块的调用。除此之外,任何你想做的设计都是可以的。
我在我的附件模板中移除了大部分额外的块标记,只留下了页眉和页脚模板部件、几个包装组块以及对文章标题和文章内容块的调用。
欢迎借用我的 attachment.html 文件并按原样使用:
<!-- wp:template-part {"slug":"header","area":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","align":"full"} -->
<main class="wp-block-group alignfull">
<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|50"},"margin":{"bottom":"var:preset|spacing|40"}}},"layout":{"type":"constrained"}} -->
<div class="wp-block-group" style="margin-bottom:var(--wp--preset--spacing--40);padding-top:var(--wp--preset--spacing--50)">
<!-- wp:post-title {"level":1,"fontSize":"x-large"} /-->
</div>
<!-- /wp:group -->
<!-- wp:post-content {"lock":{"move":false,"remove":true},"align":"full","layout":{"type":"constrained"}} /-->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","area":"footer","tagName":"footer"} /-->
现在检查以确保模板正确出现在站点编辑器中。你可以通过访问 WordPress 管理后台的 外观 > 编辑器 > 模板 > 附件页面 来做到这一点。
在这里你根本看不到任何媒体输出或媒体块。这些必须在前端动态插入。
禁用 WordPress 的默认过滤器
如果你没有构建自定义附件模板,WordPress 会自动插入一些默认的媒体输出。它通过对内容运行 prepend_attachment 过滤器来实现这一点。
对于这种情况,将此代码添加到你的 functions.php 中:
remove_filter( 'the_content', 'prepend_attachment' );
如果你正在构建自定义附件模板,则不需要执行此步骤。但无论如何,移除过滤器不会对你的模板产生负面影响。
输出动态媒体块标记
现在我们需要一种在前端插入自定义媒体输出的方法。我在这里考虑了几个选项,但我认为最好的方法是过滤 render_block_core/post-content 钩子的输出。
从现在开始,你将在主题的 functions.php 文件中构建一个自定义过滤器函数。我们将逐步讲解该函数的每个部分,以便你了解它在做什么——也许它甚至会给你一些关于未来其他自定义过滤器的想法。
首先在你的 functions.php 文件中添加过滤器调用和函数:
add_filter( 'render_block_core/post-content', 'themeslug_render_block', 10, 3 );
function themeslug_render_block( $block_content, $block, $instance ) {
// 自定义代码将放在这里。
}
本节的其余代码片段应放在函数内部。
每当文章内容块在前端渲染时,render_block_core/post-content 钩子就会运行。你当然不希望你的过滤器在每个此块的实例上都运行,因此你需要在运行代码之前检查是否满足以下条件:
- 必须通过块上下文传递文章 ID。
- 我们必须正在查看给定文章 ID 的前端附件页面(通过
is_attachment()条件函数检查)。
如果任何条件为 false,你必须返回原始的、未更改的块内容。
现在将此代码添加到你的函数中:
if (
empty( $instance->context['postId'] )
|| ! is_attachment( $instance->context['postId'] )
) {
return $block_content;
}
现在你需要为输出附件页面的媒体部分创建一个局部模板(即小的 PHP 模板文件)层次结构。
为此,你需要四个用于处理不同类型媒体的局部文件。将这些空文件添加到主题的自定义 /partials 文件夹中:
attachment-media-audio.phpattachment-media-image.phpattachment-media-video.phpattachment-media.php(回退文件)
你还不需要担心这些文件中放什么内容。我们会讲到那一步,但在构建层次结构时,了解我们正在寻找哪些文件是有帮助的。
下一段代码中最重要的部分是 wp_attachment_is() 函数。你用它来确定附件页面关联的媒体类型。
要构建你的局部模板层次结构,请将此代码添加到你的 functions.php 文件中的 themeslug_render_block() 函数内:
$partials = [];
$html = '';
foreach ( [ 'image', 'video', 'audio' ] as $type ) {
if ( wp_attachment_is( $type, $instance->context['postId'] ) ) {
$partials[] = "partials/attachment-media-{$type}.php";
break;
}
}
$partials[] = 'partials/attachment-media.php';
我们开始接近真正神奇的部分了。但首先,有几个重要的步骤。你的函数需要:
- 使用
locate_template()函数定位并包含局部模板文件。你还需要将文章 ID 传递给$args参数,以便它在局部模板中可用。 - 使用 PHP 输出缓冲来捕获局部模板的输出。
- 如果没有捕获到任何内容,则返回块内容。
将此代码添加到你的函数中:
ob_start();
locate_template( $partials, true, false, [
'post_id' => $instance->context['postId']
] );
$block_markup = ob_get_clean();
if ( ! $block_markup ) {
return $block_content;
}
你可以通过直接文件路径包含局部模板文件,但使用 locate_template() 可以让子主题覆盖它。
到目前为止,这个函数已经包含了很多内容,但我们到了最后阶段。最后的任务是:
- 使用
parse_blocks()函数解析从局部模板返回的块标记。 - 使用
render_block()函数渲染每个块,并将其附加到$html变量。 - 返回块内容,并在其前面添加媒体输出。
将此最终代码添加到你的 themeslug_render_block() 函数中:
foreach ( parse_blocks( $block_markup ) as $parsed_block ) {
$html .= render_block( $parsed_block );
}
return $html . $block_content;
通过访问你站点上的任何附件页面并确保没有损坏来测试你的代码。
你还不会看到任何媒体输出,因为你的局部模板中没有任何块标记。所以现在让我们来做这件事。
构建动态附件局部模板
在上一节中,你向主题的 /partials 文件夹添加了四个空文件:
attachment-media-audio.phpattachment-media-image.phpattachment-media-video.phpattachment-media.php(回退文件)
你将使用这些局部模板来编写有效的块标记。你之前编写的函数将解析该标记,并在用户访问附件页面时将其注入到文章内容块中。
你从自定义 PHP 文件而不是基于 HTML 的模板部件或模式中执行此操作,是因为你需要能够通过 PHP 动态获取所需数据(如媒体 URL)。
你使用哪些块来构建局部模板完全取决于你,但下表应该给你一些可以使用的块:
| 文件 | 块 |
|---|---|
attachment-media-audio.php | 音频 |
attachment-media-image.php | 图片、封面、媒体与文本 |
attachment-media-video.php | 视频 |
attachment-media.php | 文件 |
获取所需块标记的最简单方法是在编辑器中插入一个你选择的空块,然后点击工具栏中的复制按钮。
这将为你提供图片块的以下代码:
<!-- wp:image -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image -->
你需要为你想要在局部模板中使用的每个块重复此过程。
在接下来的步骤中,你会注意到我还使用了一个包装组块。这是为了更好地控制页面的布局。请随意尝试不同的东西,走你自己的路。毕竟,你构建这个是为了适应你的主题。
构建回退局部模板
对于我的回退局部模板,我决定使用核心内置的文件块。将此代码复制并粘贴到你的 partials/attachment-media.php 文件中:
<?php
// 获取动态附件数据。
$url = wp_get_attachment_url( $args['post_id'] );
?>
<!-- wp:group {"align":"full","layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull">
<!-- wp:file {"id":<?php echo absint( $args['post_id'] ); ?>,"href":"<?php echo esc_url( $url ); ?>"} -->
<div class="wp-block-file">
<a href="<?php echo esc_url( $url ); ?>"><?php the_title(); ?></a>
<a href="<?php echo esc_url( $url ) ?>" class="wp-block-file__button wp-element-button" download><?php esc_html_e( 'Download', 'x3p0-ideas' ); ?></a>
</div>
<!-- /wp:file -->
</div>
<!-- /wp:group -->
现在查看你站点前端的任何附件页面。你应该看到类似这样的内容: