阐述 WordPress `gutenberg_render_block_core_gallery()` 函数的源码:如何渲染图库区块。

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;
}

看起来有点长,别怕,我们一步一步来。

三、代码流程分解:步步为营

  1. 参数接收:

    • $attributes: 一个包含区块所有属性的数组,例如列数、图片 ID、链接设置等。这是我们控制图库样式的关键。
    • $content: 区块的默认内容,通常为空,因为图库内容主要来自图片附件。
    • $block: WP_Block 对象,包含了区块的各种信息。
  2. 实例计数:

    static $instance_number = 0;
    $instance_number++;

    这是一个小技巧,用于生成唯一的 ID,防止页面上多个图库区块发生冲突。

  3. 图片 ID 获取:

    $ids = array_column( $attributes['ids'] ?? array(), 'id' );
    
    if ( empty( $ids ) ) {
      return '';
    }

    这里从 $attributes['ids'] 数组中提取图片 ID。 array_column() 函数能非常方便地从多维数组中提取指定列的值。 如果没有图片 ID,函数会直接返回空字符串,避免出错。

  4. 构建查询参数:

    $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 数量一致。
  5. 应用过滤器:

    /**
     * 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 过滤器修改查询参数。 这为图库区块的定制提供了极大的灵活性。

  6. 获取图片:

    $posts = get_posts( ... );

    使用 get_posts() 函数,根据 $args 数组查询数据库,获取图片附件的 WP_Post 对象。

  7. 构建 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 属性的含义实际上是是否禁用裁剪。 如果 imageCropfalse,则添加 is-cropped 类名。 这有点反直觉,但记住就好。
  8. 生成唯一 ID:

    $block_id = uniqid( 'wp-block-gallery-' . $instance_number );

    使用 uniqid() 函数生成一个唯一的 ID,格式为 wp-block-gallery-{instance_number}{random_string}

  9. 处理列数:

    $columns = isset( $attributes['columns'] ) ? absint( $attributes['columns'] ) : 3;
    $columns = max( 1, min( 12, $columns ) );

    获取用户设置的列数。 如果没有设置,则默认为 3 列。 absint() 函数将值转换为绝对整数。 max( 1, min( 12, $columns ) ) 确保列数在 1 到 12 之间。

  10. 处理固定布局:

    $gallery_style = '';
    if ( isset( $attributes['hasFixedLayout'] ) && true === $attributes['hasFixedLayout'] ) {
      $gallery_class .= ' is-fixed-layout';
    } else {
      $gallery_style = 'style="--wp--columns:' . $columns . '"';
    }

    如果用户设置了固定布局 (hasFixedLayouttrue),则添加 is-fixed-layout 类名。 否则,设置 CSS 自定义属性 --wp--columns,用于控制网格列数。

  11. 构建外层容器:

    $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 代码中。

  12. 构建图片列表:

    $output .= '<ul class="blocks-gallery-grid">';
    
    foreach ( $posts as $i => $post ) {
      // ... (图片处理逻辑) ...
    }
    
    $output .= '</ul>';

    使用 <ul> 元素作为图片列表的容器。 循环遍历 $posts 数组,处理每一张图片。

  13. 图片处理逻辑:

    $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> 元素显示描述。
  14. 添加图库说明:

    if ( isset( $attributes['caption'] ) && ! empty( $attributes['caption'] ) ) {
      $output .= '<figcaption>' . wp_kses_post( $attributes['caption'] ) . '</figcaption>';
    }

    如果用户设置了图库说明 (caption),则添加 <figcaption> 元素显示说明。 wp_kses_post() 函数用于过滤 HTML 代码,防止 XSS 攻击。

  15. 关闭外层容器:

    $output .= '</figure>';

    关闭 <figure> 元素。

  16. 返回 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 变量 (如果 hasFixedLayoutfalse)
imageCrop bool 是否裁剪图片 (实际上是是否禁用裁剪false 表示裁剪) is-cropped 类名 (如果 imageCropfalse)
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() 函数。 希望通过这次源码解析,你不仅对图库区块的实现原理有了更深入的理解,也能在实际开发中更加灵活地应用和定制它。

记住,掌握核心原理,才能更好地应对各种挑战。 祝大家编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注