WordPress 图库区块的幕后英雄:gutenberg_render_block_core_gallery()
源码解析讲座
大家好!今天我们来聊聊 WordPress 古腾堡编辑器中的图库区块,以及隐藏在它背后的关键函数:gutenberg_render_block_core_gallery()
。 别担心,这堂课不会变成枯燥的源码阅读,我会尽量用轻松幽默的方式,带大家深入了解这个“画廊管理员”是如何工作的。
准备好了吗?Let’s dive in!
一、图库区块:美好的开始
首先,让我们简单回顾一下图库区块。在 WordPress 后台,你是否经常使用图库区块来展示你的美图、旅行照片或者产品展示?它能以网格的形式,优雅地呈现多张图片,让你的文章更加生动。
但你知道吗?每次你保存文章,WordPress 都会调用 gutenberg_render_block_core_gallery()
函数来生成最终的 HTML 代码。
二、深入 gutenberg_render_block_core_gallery()
:源码剖析
让我们直接上干货,看看 gutenberg_render_block_core_gallery()
的庐山真面目 (基于 WordPress 6.4):
<?php
/**
* Renders the core/gallery block on the server.
*
* @param array $attributes The block attributes.
* @param string $content The block default content.
* @param WP_Block $block The block instance.
*
* @return string The rendered output.
*/
function gutenberg_render_block_core_gallery( $attributes, $content, $block ) {
static $instance_number = 0;
$instance_number++;
$ids = array_column( $attributes['ids'] ?? array(), 'id' );
if ( empty( $ids ) ) {
return '';
}
$args = array(
'include' => implode( ',', array_map( 'intval', $ids ) ),
'orderby' => 'post__in',
'order' => 'ASC',
'post_type' => 'attachment',
'post_mime_type' => 'image',
'post_status' => 'inherit',
'posts_per_page' => count( $ids ),
);
/**
* Filters the arguments for `get_posts()` when rendering the core/gallery block.
*
* @since 5.3.0
*
* @param array $args The query arguments.
* @param array $attributes The block attributes.
*/
$posts = get_posts( apply_filters( 'render_block_core_gallery_get_posts', $args, $attributes ) );
if ( empty( $posts ) ) {
return '';
}
$gallery_class = 'wp-block-gallery';
if ( isset( $attributes['className'] ) ) {
$gallery_class .= ' ' . esc_attr( $attributes['className'] );
}
if ( isset( $attributes['align'] ) ) {
$gallery_class .= ' align' . esc_attr( $attributes['align'] );
}
if ( isset( $attributes['imageCrop'] ) && false === $attributes['imageCrop'] ) {
$gallery_class .= ' is-cropped'; // Note the intentional negation. `imageCrop` is a misleading name. False === is-cropped.
}
$block_id = uniqid( 'wp-block-gallery-' . $instance_number );
$columns = isset( $attributes['columns'] ) ? absint( $attributes['columns'] ) : 3;
$columns = max( 1, min( 12, $columns ) );
$gallery_style = '';
if ( isset( $attributes['hasFixedLayout'] ) && true === $attributes['hasFixedLayout'] ) {
$gallery_class .= ' is-fixed-layout';
} else {
$gallery_style = 'style="--wp--columns:' . $columns . '"';
}
$output = sprintf(
'<figure class="%1$s"%2$s data-id="%3$s">',
esc_attr( $gallery_class ),
$gallery_style ? ' ' . $gallery_style : '',
esc_attr( $block_id )
);
$output .= '<ul class="blocks-gallery-grid">';
foreach ( $posts as $i => $post ) {
$image_size = isset( $attributes['imageSize'] ) ? $attributes['imageSize'] : 'large';
$image = wp_get_attachment_image( $post->ID, $image_size );
$link_destination = isset( $attributes['linkTo'] ) ? $attributes['linkTo'] : 'none';
$image_attributes = array(
'class' => 'blocks-gallery-item__link',
);
$link_target = ( isset( $attributes['linkTarget'] ) && true === $attributes['linkTarget'] ) ? '_blank' : '';
$rel = ( isset( $attributes['rel'] ) ? 'rel="' . esc_attr( $attributes['rel'] ) . '"' : '' );
$link_output = '';
switch ( $link_destination ) {
case 'media':
$link_url = wp_get_attachment_url( $post->ID );
if ( $link_url ) {
$link_output = sprintf(
'<a href="%1$s" target="%2$s" %3$s>%4$s</a>',
esc_url( $link_url ),
esc_attr( $link_target ),
$rel,
$image
);
}
break;
case 'attachment':
$link_url = get_attachment_link( $post->ID );
if ( $link_url ) {
$link_output = sprintf(
'<a href="%1$s" target="%2$s" %3$s>%4$s</a>',
esc_url( $link_url ),
esc_attr( $link_target ),
$rel,
$image
);
}
break;
default:
$link_output = $image;
break;
}
$output .= '<li class="blocks-gallery-item">';
$output .= $link_output;
if ( ! empty( $post->post_excerpt ) ) {
$output .= '<figcaption>' . wp_kses_post( $post->post_excerpt ) . '</figcaption>';
}
$output .= '</li>';
}
$output .= '</ul>';
if ( isset( $attributes['caption'] ) && ! empty( $attributes['caption'] ) ) {
$output .= '<figcaption>' . wp_kses_post( $attributes['caption'] ) . '</figcaption>';
}
$output .= '</figure>';
return $output;
}
看起来有点长,别怕,我们一步一步来。
三、代码流程分解:步步为营
-
参数接收:
$attributes
: 一个包含区块所有属性的数组,例如列数、图片 ID、链接设置等。这是我们控制图库样式的关键。$content
: 区块的默认内容,通常为空,因为图库内容主要来自图片附件。$block
:WP_Block
对象,包含了区块的各种信息。
-
实例计数:
static $instance_number = 0; $instance_number++;
这是一个小技巧,用于生成唯一的 ID,防止页面上多个图库区块发生冲突。
-
图片 ID 获取:
$ids = array_column( $attributes['ids'] ?? array(), 'id' ); if ( empty( $ids ) ) { return ''; }
这里从
$attributes['ids']
数组中提取图片 ID。array_column()
函数能非常方便地从多维数组中提取指定列的值。 如果没有图片 ID,函数会直接返回空字符串,避免出错。 -
构建查询参数:
$args = array( 'include' => implode( ',', array_map( 'intval', $ids ) ), 'orderby' => 'post__in', 'order' => 'ASC', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => count( $ids ), );
这里构建了一个
$args
数组,用于查询数据库中的图片附件。include
: 包含要查询的图片 ID 列表。implode()
函数将 ID 数组转换为逗号分隔的字符串。array_map( 'intval', $ids )
确保所有ID都是整数,防止SQL注入。orderby
: 按照post__in
排序,保证图片顺序与你在编辑器中设置的一致。order
: 升序排列 (ASC)。post_type
: 指定查询附件 (attachment)。post_mime_type
: 指定查询图片 (image)。post_status
: 指定查询继承状态 (inherit) 的附件。posts_per_page
: 指定查询的图片数量,与 ID 数量一致。
-
应用过滤器:
/** * Filters the arguments for `get_posts()` when rendering the core/gallery block. * * @since 5.3.0 * * @param array $args The query arguments. * @param array $attributes The block attributes. */ $posts = get_posts( apply_filters( 'render_block_core_gallery_get_posts', $args, $attributes ) );
这里使用
apply_filters()
函数,允许开发者通过render_block_core_gallery_get_posts
过滤器修改查询参数。 这为图库区块的定制提供了极大的灵活性。 -
获取图片:
$posts = get_posts( ... );
使用
get_posts()
函数,根据$args
数组查询数据库,获取图片附件的WP_Post
对象。 -
构建 HTML 类名:
$gallery_class = 'wp-block-gallery'; if ( isset( $attributes['className'] ) ) { $gallery_class .= ' ' . esc_attr( $attributes['className'] ); } if ( isset( $attributes['align'] ) ) { $gallery_class .= ' align' . esc_attr( $attributes['align'] ); } if ( isset( $attributes['imageCrop'] ) && false === $attributes['imageCrop'] ) { $gallery_class .= ' is-cropped'; // Note the intentional negation. `imageCrop` is a misleading name. False === is-cropped. }
这段代码构建了图库区块的 HTML 类名。
wp-block-gallery
: 这是基础类名,所有图库区块都有。className
: 如果用户在编辑器中添加了自定义 CSS 类名,这里会将其添加到类名列表中。esc_attr()
函数用于转义属性值,防止 XSS 攻击。align
: 如果用户设置了对齐方式 (例如 "wide" 或 "full"),这里会添加相应的类名。is-cropped
: 注意这里的逻辑!imageCrop
属性的含义实际上是是否禁用裁剪。 如果imageCrop
为false
,则添加is-cropped
类名。 这有点反直觉,但记住就好。
-
生成唯一 ID:
$block_id = uniqid( 'wp-block-gallery-' . $instance_number );
使用
uniqid()
函数生成一个唯一的 ID,格式为wp-block-gallery-{instance_number}{random_string}
。 -
处理列数:
$columns = isset( $attributes['columns'] ) ? absint( $attributes['columns'] ) : 3; $columns = max( 1, min( 12, $columns ) );
获取用户设置的列数。 如果没有设置,则默认为 3 列。
absint()
函数将值转换为绝对整数。max( 1, min( 12, $columns ) )
确保列数在 1 到 12 之间。 -
处理固定布局:
$gallery_style = ''; if ( isset( $attributes['hasFixedLayout'] ) && true === $attributes['hasFixedLayout'] ) { $gallery_class .= ' is-fixed-layout'; } else { $gallery_style = 'style="--wp--columns:' . $columns . '"'; }
如果用户设置了固定布局 (
hasFixedLayout
为true
),则添加is-fixed-layout
类名。 否则,设置 CSS 自定义属性--wp--columns
,用于控制网格列数。 -
构建外层容器:
$output = sprintf( '<figure class="%1$s"%2$s data-id="%3$s">', esc_attr( $gallery_class ), $gallery_style ? ' ' . $gallery_style : '', esc_attr( $block_id ) );
使用
<figure>
元素作为图库区块的外层容器。sprintf()
函数用于格式化字符串,将类名、样式和 ID 插入到 HTML 代码中。 -
构建图片列表:
$output .= '<ul class="blocks-gallery-grid">'; foreach ( $posts as $i => $post ) { // ... (图片处理逻辑) ... } $output .= '</ul>';
使用
<ul>
元素作为图片列表的容器。 循环遍历$posts
数组,处理每一张图片。 -
图片处理逻辑:
$image_size = isset( $attributes['imageSize'] ) ? $attributes['imageSize'] : 'large'; $image = wp_get_attachment_image( $post->ID, $image_size ); $link_destination = isset( $attributes['linkTo'] ) ? $attributes['linkTo'] : 'none'; $image_attributes = array( 'class' => 'blocks-gallery-item__link', ); $link_target = ( isset( $attributes['linkTarget'] ) && true === $attributes['linkTarget'] ) ? '_blank' : ''; $rel = ( isset( $attributes['rel'] ) ? 'rel="' . esc_attr( $attributes['rel'] ) . '"' : ''; $link_output = ''; switch ( $link_destination ) { case 'media': $link_url = wp_get_attachment_url( $post->ID ); if ( $link_url ) { $link_output = sprintf( '<a href="%1$s" target="%2$s" %3$s>%4$s</a>', esc_url( $link_url ), esc_attr( $link_target ), $rel, $image ); } break; case 'attachment': $link_url = get_attachment_link( $post->ID ); if ( $link_url ) { $link_output = sprintf( '<a href="%1$s" target="%2$s" %3$s>%4$s</a>', esc_url( $link_url ), esc_attr( $link_target ), $rel, $image ); } break; default: $link_output = $image; break; } $output .= '<li class="blocks-gallery-item">'; $output .= $link_output; if ( ! empty( $post->post_excerpt ) ) { $output .= '<figcaption>' . wp_kses_post( $post->post_excerpt ) . '</figcaption>'; } $output .= '</li>';
这段代码处理每一张图片,包括:
$image_size
: 获取用户设置的图片尺寸。 如果没有设置,则默认为 "large"。wp_get_attachment_image()
: 获取指定尺寸的图片 HTML 代码。$link_destination
: 获取用户设置的链接目标。- 根据
$link_destination
的值,生成不同的链接 HTML 代码:media
: 链接到媒体文件 (图片本身)。attachment
: 链接到附件页面。none
: 不链接。
$link_target
: 获取用户设置的链接打开方式,是当前窗口打开还是新窗口打开。$rel
: 获取用户设置的rel属性,一般为nofollow,noopener等。- 使用
<li>
元素包裹图片和链接。 - 如果图片有描述 (post_excerpt),则添加
<figcaption>
元素显示描述。
-
添加图库说明:
if ( isset( $attributes['caption'] ) && ! empty( $attributes['caption'] ) ) { $output .= '<figcaption>' . wp_kses_post( $attributes['caption'] ) . '</figcaption>'; }
如果用户设置了图库说明 (caption),则添加
<figcaption>
元素显示说明。wp_kses_post()
函数用于过滤 HTML 代码,防止 XSS 攻击。 -
关闭外层容器:
$output .= '</figure>';
关闭
<figure>
元素。 -
返回 HTML 代码:
return $output;
返回最终生成的 HTML 代码。
四、gutenberg_render_block_core_gallery()
的核心作用
简单总结一下,gutenberg_render_block_core_gallery()
函数的主要作用是:
- 接收图库区块的属性。
- 查询数据库,获取图片附件。
- 根据属性和图片信息,生成 HTML 代码。
- 返回 HTML 代码,用于在页面上渲染图库区块。
五、表格总结:属性与输出
为了更清晰地理解属性和输出之间的关系,我们用一个表格来总结一下:
属性名 | 类型 | 描述 | 影响的 HTML |
---|---|---|---|
ids |
array | 图片 ID 数组 | 查询哪些图片,以及图片的顺序 |
columns |
int | 列数 | --wp--columns CSS 变量 (如果 hasFixedLayout 为 false ) |
imageCrop |
bool | 是否裁剪图片 (实际上是是否禁用裁剪,false 表示裁剪) |
is-cropped 类名 (如果 imageCrop 为 false ) |
imageSize |
string | 图片尺寸 | wp_get_attachment_image() 函数的 $size 参数 |
linkTo |
string | 链接目标 (none, media, attachment) | <a> 标签的 href 属性 |
caption |
string | 图库说明 | <figcaption> 元素的内容 |
className |
string | 自定义 CSS 类名 | 添加到 <figure> 元素的 class 属性 |
align |
string | 对齐方式 (wide, full 等) | 添加到 <figure> 元素的 class 属性 (例如 alignwide , alignfull ) |
hasFixedLayout |
bool | 是否使用固定布局 | is-fixed-layout 类名,以及是否设置 --wp--columns CSS 变量 |
linkTarget |
bool | 链接是否在新窗口打开(true为新窗口) | <a> 标签的 target 属性 |
rel |
string | 链接的 rel 属性(例如 nofollow, noopener) | <a> 标签的 rel 属性 |
六、扩展与定制:无限可能
了解了 gutenberg_render_block_core_gallery()
的源码,你就可以对图库区块进行各种定制:
- 修改查询参数: 使用
render_block_core_gallery_get_posts
过滤器,可以修改图片查询逻辑,例如添加自定义排序、筛选条件等。 - 修改 HTML 结构: 复制
gutenberg_render_block_core_gallery()
函数到你的主题或插件中,修改 HTML 代码,可以完全自定义图库区块的样式和结构。 - 添加自定义属性: 注册自定义区块属性,并在
gutenberg_render_block_core_gallery()
函数中使用这些属性,可以为图库区块添加更多功能。
七、总结:掌握核心,灵活应用
今天我们一起深入了解了 WordPress 图库区块的幕后英雄:gutenberg_render_block_core_gallery()
函数。 希望通过这次源码解析,你不仅对图库区块的实现原理有了更深入的理解,也能在实际开发中更加灵活地应用和定制它。
记住,掌握核心原理,才能更好地应对各种挑战。 祝大家编程愉快!