PluginProbe
Gutenberg / 23.6.0
Gutenberg v23.6.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / build / scripts / block-library / gallery.php

gallery.php in Gutenberg 23.6.0, at build/scripts/block-library/gallery.php

589 lines 22.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side rendering of the `core/gallery` block.
4 *
5 * @package WordPress
6 */
7
8 /**
9 * Handles backwards compatibility for Gallery Blocks,
10 * whose images feature a `data-id` attribute.
11 *
12 * Now that the Gallery Block contains inner Image Blocks,
13 * we add a custom `data-id` attribute before rendering the gallery
14 * so that the Image Block can pick it up in its render_callback.
15 *
16 * @since 5.9.0
17 *
18 * @param array $parsed_block The block being rendered.
19 * @return array The migrated block object.
20 */
21 function gutenberg_block_core_gallery_data_id_backcompatibility( $parsed_block ) {
22 if ( 'core/gallery' === $parsed_block['blockName'] ) {
23 foreach ( $parsed_block['innerBlocks'] as $key => $inner_block ) {
24 if ( 'core/image' === $inner_block['blockName'] ) {
25 if ( ! isset( $parsed_block['innerBlocks'][ $key ]['attrs']['data-id'] ) && isset( $inner_block['attrs']['id'] ) ) {
26 $parsed_block['innerBlocks'][ $key ]['attrs']['data-id'] = esc_attr( $inner_block['attrs']['id'] );
27 }
28 }
29 }
30 }
31
32 return $parsed_block;
33 }
34
35 add_filter( 'render_block_data', 'gutenberg_block_core_gallery_data_id_backcompatibility' );
36
37 /**
38 * Adds a unique ID to the gallery block context.
39 *
40 * @since 7.0.0
41 *
42 * @param array $context Default context.
43 * @param array $parsed_block Block being rendered, filtered by render_block_data.
44 * @return array Filtered context.
45 */
46 function gutenberg_block_core_gallery_render_context( $context, $parsed_block ) {
47 if ( 'core/gallery' === $parsed_block['blockName'] ) {
48 $context['galleryId'] = uniqid();
49 }
50 return $context;
51 }
52
53 add_filter( 'render_block_context', 'gutenberg_block_core_gallery_render_context', 10, 2 );
54
55 /**
56 * Returns the column gap value used for Gallery image width calculations.
57 *
58 * @since 7.1.0
59 *
60 * @param string|array|null $gap Gallery block gap value.
61 * @param string $fallback_gap Fallback gap value.
62 * @return string Gallery column gap value.
63 */
64 function gutenberg_block_core_gallery_get_column_gap_value( $gap, $fallback_gap ) {
65 if ( is_array( $gap ) ) {
66 $gap = $gap['left'] ?? $fallback_gap;
67 }
68
69 // Make sure $gap is a string to avoid PHP 8.1 deprecation error in preg_match() when the value is null.
70 $gap = is_string( $gap ) ? $gap : '';
71
72 // Skip if gap value contains unsupported characters.
73 // Regex for CSS value borrowed from `safecss_filter_attr`, and used here
74 // because we only want to match against the value, not the CSS attribute.
75 $gap = $gap && preg_match( '%[\\\(&=}]|/\*%', $gap ) ? null : $gap;
76
77 // Get spacing CSS variable from preset value if provided.
78 if ( is_string( $gap ) && str_contains( $gap, 'var:preset|spacing|' ) ) {
79 $index_to_splice = strrpos( $gap, '|' ) + 1;
80 $slug = _wp_to_kebab_case( substr( $gap, $index_to_splice ) );
81 $gap = "var(--wp--preset--spacing--$slug)";
82 }
83
84 $gap_column = ( null !== $gap && '' !== $gap ) ? $gap : $fallback_gap;
85
86 // The unstable gallery gap calculation requires a real value (such as `0px`) and not `0`.
87 return '0' === $gap_column ? '0px' : $gap_column;
88 }
89
90 /**
91 * Resolves a Gallery block's `dynamicContent` to an ordered list of image
92 * attachment IDs.
93 *
94 * The `source` key is the dispatch discriminator and `args` holds the source's
95 * parameters. This `{ source, args }` shape mirrors the Block Bindings metadata
96 * shape so dynamic mode can migrate to an `innerBlocks` binding with minimal
97 * change. `core/attached-media` is a context-relative anchor (the post the gallery is
98 * rendered within); future sources translate their REST-named `args` (`author`,
99 * `categories`, `after`/`before`, `media_type`, etc.) into `WP_Query` arguments
100 * here.
101 *
102 * @since 7.0.0
103 *
104 * @param array $source The gallery's `dynamicContent` attribute.
105 * @param WP_Block $block The gallery block instance being rendered.
106 * @return int[] Ordered list of image attachment IDs.
107 */
108 function gutenberg_block_core_gallery_resolve_dynamic_source( $source, $block ) {
109 if ( ! is_array( $source ) ) {
110 return array();
111 }
112
113 $source_name = $source['source'] ?? null;
114 $args = isset( $source['args'] ) && is_array( $source['args'] ) ? $source['args'] : array();
115
116 switch ( $source_name ) {
117 case 'core/attached-media':
118 // Prefer the post supplied via block context, falling back to the post
119 // being rendered. The fallback is what lets a post-bound template (e.g.
120 // `single`/`page`) resolve against the actual post at render time even
121 // though the editor has no concrete post to preview — the editor gates
122 // the dynamic-mode UI on that same context (see `use-dynamic-gallery.js`).
123 $post_id = $block->context['postId'] ?? get_the_ID();
124 if ( ! $post_id ) {
125 return array();
126 }
127
128 // Map the camelCase `args` (block-attribute convention) to WP_Query
129 // names, defaulting to the same order as the editor preview (see
130 // `dynamic-source.js`). Only REST-supported orderby values are
131 // allowed; `menu_order` is intentionally unsupported (it isn't a
132 // valid media REST `orderby`).
133 $orderby = $args['orderBy'] ?? 'date';
134 if ( ! in_array( $orderby, array( 'date', 'title' ), true ) ) {
135 $orderby = 'date';
136 }
137 $order = strtoupper( $args['order'] ?? 'desc' ) === 'ASC' ? 'ASC' : 'DESC';
138
139 // Bound the number of resolved images until the gallery supports
140 // pagination. Kept in sync with the editor query's `per_page` cap; a
141 // case-insensitive grep for `max_images` finds both this and
142 // `MAX_IMAGES` in `dynamic-source.js`.
143 $max_images = 100;
144
145 $query = new WP_Query(
146 array(
147 'post_parent' => $post_id,
148 'post_type' => 'attachment',
149 'post_status' => 'inherit',
150 'post_mime_type' => 'image',
151 'orderby' => $orderby,
152 'order' => $order,
153 'posts_per_page' => $max_images,
154 'fields' => 'ids',
155 'no_found_rows' => true,
156 )
157 );
158
159 return array_map( 'intval', $query->posts );
160 }
161
162 // Unknown or not-yet-implemented source type.
163 return array();
164 }
165
166 /**
167 * Builds the link-related image block attributes for a dynamically rendered
168 * gallery image, mapping the gallery-wide `linkTo` setting onto a single image.
169 *
170 * Mirrors the editor's `getHrefAndDestination()` (see `gallery/utils.js`).
171 *
172 * @since 7.0.0
173 *
174 * @param int $attachment_id The image attachment ID.
175 * @param array $attributes The gallery block attributes.
176 * @return array Partial image block attributes (`href`, `linkDestination`,
177 * `linkTarget`, `rel`, `lightbox`).
178 */
179 function gutenberg_block_core_gallery_dynamic_image_link_attributes( $attachment_id, $attributes ) {
180 $link_to = $attributes['linkTo'] ?? 'none';
181 $attrs = array();
182
183 switch ( $link_to ) {
184 // Gutenberg uses 'media'/'attachment'; WP Core uses 'file'/'post'.
185 case 'media':
186 case 'file':
187 $attrs['href'] = wp_get_attachment_url( $attachment_id );
188 $attrs['linkDestination'] = 'media';
189 break;
190 case 'attachment':
191 case 'post':
192 $attrs['href'] = get_attachment_link( $attachment_id );
193 $attrs['linkDestination'] = 'attachment';
194 break;
195 case 'lightbox':
196 $attrs['linkDestination'] = 'none';
197 $attrs['lightbox'] = array( 'enabled' => true );
198 break;
199 }
200
201 if ( ! empty( $attrs['href'] ) && '_blank' === ( $attributes['linkTarget'] ?? '' ) ) {
202 $attrs['linkTarget'] = '_blank';
203 $attrs['rel'] = 'noopener';
204 }
205
206 return $attrs;
207 }
208
209 /**
210 * Renders a single `core/image` block for a Gallery block running in dynamic
211 * mode, applying the gallery-wide settings that affect how an image renders.
212 *
213 * The image markup is generated here (via `wp_get_attachment_image()`) and
214 * rendered through a real `core/image` block instance so that the image block's
215 * own render callback and lightbox behavior run, and so the gallery's existing
216 * lightbox/interactivity post-processing can pick it up.
217 *
218 * @since 7.0.0
219 *
220 * @param int $attachment_id The image attachment ID.
221 * @param array $attributes The gallery block attributes.
222 * @param array $context Context to expose to the inner image block.
223 * @return string The rendered image block HTML, or an empty string on failure.
224 */
225 function gutenberg_block_core_gallery_render_dynamic_image( $attachment_id, $attributes, $context ) {
226 $size_slug = $attributes['sizeSlug'] ?? 'large';
227 $aspect_ratio = $attributes['aspectRatio'] ?? 'auto';
228
229 $img_attr = array( 'class' => 'wp-image-' . $attachment_id );
230 if ( $aspect_ratio && 'auto' !== $aspect_ratio ) {
231 // Run the aspect ratio through the same sanitization used for every other
232 // block inline style, so an unsafe value can't break out of the style
233 // attribute or inject additional markup.
234 $img_attr['style'] = safecss_filter_attr(
235 sprintf( 'aspect-ratio:%s;object-fit:cover;', $aspect_ratio )
236 );
237 }
238
239 $image_markup = wp_get_attachment_image( $attachment_id, $size_slug, false, $img_attr );
240 if ( ! $image_markup ) {
241 return '';
242 }
243
244 $image_attributes = array_merge(
245 array(
246 'id' => $attachment_id,
247 'data-id' => (string) $attachment_id,
248 'sizeSlug' => $size_slug,
249 ),
250 gutenberg_block_core_gallery_dynamic_image_link_attributes( $attachment_id, $attributes )
251 );
252
253 if ( $aspect_ratio && 'auto' !== $aspect_ratio ) {
254 $image_attributes['aspectRatio'] = $aspect_ratio;
255 $image_attributes['scale'] = 'cover';
256 }
257
258 // Wrap in a link when the gallery links images somewhere.
259 if ( ! empty( $image_attributes['href'] ) ) {
260 $image_markup = sprintf(
261 '<a href="%1$s"%2$s%3$s>%4$s</a>',
262 esc_url( $image_attributes['href'] ),
263 isset( $image_attributes['linkTarget'] ) ? ' target="' . esc_attr( $image_attributes['linkTarget'] ) . '"' : '',
264 isset( $image_attributes['rel'] ) ? ' rel="' . esc_attr( $image_attributes['rel'] ) . '"' : '',
265 $image_markup
266 );
267 }
268
269 // Use the raw caption (`post_excerpt`) so the frontend mirrors the editor
270 // preview, which builds the caption from the REST `caption.raw` value. Gap:
271 // the REST API exposes no caption run through `wp_get_attachment_caption`, so
272 // that filter isn't applied here either.
273 $attachment = get_post( $attachment_id );
274 $caption = $attachment ? $attachment->post_excerpt : '';
275 if ( '' !== $caption ) {
276 $image_markup .= sprintf(
277 '<figcaption class="wp-element-caption">%s</figcaption>',
278 wp_kses_post( $caption )
279 );
280 }
281
282 $figure = sprintf(
283 '<figure class="wp-block-image size-%1$s">%2$s</figure>',
284 esc_attr( $size_slug ),
285 $image_markup
286 );
287
288 $image_block = array(
289 'blockName' => 'core/image',
290 'attrs' => $image_attributes,
291 'innerBlocks' => array(),
292 'innerHTML' => $figure,
293 'innerContent' => array( $figure ),
294 );
295
296 return ( new WP_Block( $image_block, $context ) )->render();
297 }
298
299 /**
300 * Renders the `core/gallery` block on the server.
301 *
302 * @since 6.0.0
303 *
304 * @param array $attributes Attributes of the block being rendered.
305 * @param string $content Content of the block being rendered.
306 * @param array $block The block instance being rendered.
307 * @return string The content of the block being rendered.
308 */
309 function gutenberg_block_core_gallery_render( $attributes, $content, $block ) {
310 static $global_styles = null;
311
312 // In dynamic mode the gallery's images are resolved at render time instead of
313 // being authored as inner blocks, so `save.js` persists at most the
314 // gallery-level caption — a bare `<figcaption>`, or nothing when there is no
315 // caption. Resolve the configured source to a list of attachments, render an
316 // image block for each, and build the gallery `<figure>` wrapper from scratch.
317 // The gap/randomOrder/lightbox post-processing below then runs over the
318 // constructed markup unchanged.
319 if ( ! empty( $attributes['dynamicContent'] ) ) {
320 $attachment_ids = gutenberg_block_core_gallery_resolve_dynamic_source( $attributes['dynamicContent'], $block );
321
322 // Nothing resolved — no attachments, or an unrecognized source. Render
323 // nothing rather than an empty gallery wrapper; a saved caption is
324 // meaningless without images, so it is intentionally dropped too.
325 if ( empty( $attachment_ids ) ) {
326 return '';
327 }
328
329 // The source query only fetched IDs (`fields => ids`), which skips
330 // WP_Query's cache priming. Each image rendered below reads the
331 // attachment post and its meta (via `wp_get_attachment_image()`,
332 // `get_post()`, etc.), so warm the post and meta caches in a single pair
333 // of queries up front instead of paying ~two queries per attachment.
334 // Term cache is left cold: the render path doesn't read attachment terms.
335 if ( count( $attachment_ids ) > 1 ) {
336 _prime_post_caches( $attachment_ids, false, true );
337 }
338
339 // Expose the gallery's provided context (plus galleryId/postId/postType)
340 // to each image block, since these images are rendered outside the
341 // gallery's real inner-block tree.
342 $image_context = array_merge(
343 is_array( $block->context ) ? $block->context : array(),
344 array(
345 'allowResize' => $attributes['allowResize'] ?? false,
346 'imageCrop' => $attributes['imageCrop'] ?? true,
347 'fixedHeight' => $attributes['fixedHeight'] ?? true,
348 'navigationButtonType' => $attributes['navigationButtonType'] ?? 'icon',
349 )
350 );
351
352 $images_markup = '';
353 foreach ( $attachment_ids as $attachment_id ) {
354 $images_markup .= gutenberg_block_core_gallery_render_dynamic_image( $attachment_id, $attributes, $image_context );
355 }
356
357 // Build the wrapper rather than parsing/splicing saved markup.
358 // `get_block_wrapper_attributes()` supplies the block-support
359 // classes/styles (align, color, border, spacing, anchor id); the layout
360 // render filter adds the flex layout classes downstream — the same way a
361 // static gallery's wrapper is composed (`useBlockProps.save()` plus that
362 // filter). Only the gallery-specific classes are added explicitly, and
363 // they mirror `save.js` (kept in sync deliberately — see that file).
364 $gallery_classes = 'wp-block-gallery has-nested-images';
365 $gallery_classes .= isset( $attributes['columns'] )
366 ? ' columns-' . (int) $attributes['columns']
367 : ' columns-default';
368 if ( $attributes['imageCrop'] ?? true ) {
369 $gallery_classes .= ' is-cropped';
370 }
371 $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $gallery_classes ) );
372
373 // In dynamic mode `save.js` persists only the gallery-level caption, so
374 // `$content` is the saved `<figcaption>` (or empty). Append it after the
375 // resolved images — matching the static gallery's `{images}{caption}`
376 // order — without parsing it.
377 $content = sprintf( '<figure %s>%s%s</figure>', $wrapper_attributes, $images_markup, $content );
378 }
379
380 // Adds a style tag for the --wp--style--unstable-gallery-gap var.
381 // The Gallery block needs to recalculate Image block width based on
382 // the current gap setting in order to maintain the number of flex columns
383 // so a css var is added to allow this.
384
385 $style_attr = is_array( $attributes['style'] ?? null )
386 ? $attributes['style']
387 : array();
388 if (
389 defined( 'IS_GUTENBERG_PLUGIN' ) &&
390 IS_GUTENBERG_PLUGIN &&
391 function_exists( 'gutenberg_resolve_style_state_aliases' )
392 ) {
393 $style_attr = gutenberg_resolve_style_state_aliases( $style_attr, 'core/gallery' );
394 }
395
396 $unique_gallery_classname = wp_unique_id( 'wp-block-gallery-' );
397 $processed_content = new WP_HTML_Tag_Processor( $content );
398 $processed_content->next_tag();
399 $processed_content->add_class( $unique_gallery_classname );
400
401 // --gallery-block--gutter-size is deprecated. --wp--style--gallery-gap-default should be used by themes that want to set a default
402 // gap on the gallery.
403 $fallback_gap = 'var( --wp--style--gallery-gap-default, var( --gallery-block--gutter-size, var( --wp--style--block-gap, 0.5em ) ) )';
404
405 if ( null === $global_styles ) {
406 $global_styles = function_exists( 'gutenberg_get_global_styles' ) ? gutenberg_get_global_styles() : array();
407 }
408
409 $global_gallery_styles = $global_styles['blocks']['core/gallery'] ?? array();
410 $global_gallery_gap = $global_gallery_styles['spacing']['blockGap'] ?? $fallback_gap;
411 $has_block_gap = is_array( $style_attr['spacing'] ?? null ) && array_key_exists( 'blockGap', $style_attr['spacing'] );
412 // Prefer the block's own gap value, then Gallery global styles. Missing
413 // values fall back to the Gallery blockGap default.
414 $block_gap = $has_block_gap
415 ? $style_attr['spacing']['blockGap']
416 : $global_gallery_gap;
417 $gap_column = gutenberg_block_core_gallery_get_column_gap_value( $block_gap, $fallback_gap );
418
419 // Set the CSS variable to the column value for Gallery's flex width calculations.
420 $gallery_styles = array(
421 array(
422 'selector' => ".wp-block-gallery.{$unique_gallery_classname}",
423 'declarations' => array(
424 '--wp--style--unstable-gallery-gap' => $gap_column,
425 ),
426 ),
427 );
428
429 $global_settings = gutenberg_get_global_settings();
430 $viewport_settings = $global_settings['viewport'] ?? null;
431 $responsive_media_queries = array();
432 foreach ( array( 'WP_Theme_JSON_Gutenberg', 'WP_Theme_JSON' ) as $theme_json_class_name ) {
433 if ( method_exists( $theme_json_class_name, 'get_viewport_media_queries' ) ) {
434 $responsive_media_queries = $theme_json_class_name::get_viewport_media_queries( $viewport_settings );
435 break;
436 }
437 }
438
439 foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
440 $viewport_style = $style_attr[ $breakpoint ] ?? null;
441 $has_viewport_block_gap = is_array( $viewport_style ) &&
442 is_array( $viewport_style['spacing'] ?? null ) &&
443 array_key_exists( 'blockGap', $viewport_style['spacing'] );
444 $has_global_viewport_block_gap = is_array( $global_gallery_styles[ $breakpoint ]['spacing'] ?? null ) &&
445 array_key_exists( 'blockGap', $global_gallery_styles[ $breakpoint ]['spacing'] );
446
447 // Viewport-specific block values win. Gallery global viewport values
448 // only apply when the block has no base gap, so they do not override an instance value.
449 if ( $has_viewport_block_gap ) {
450 $viewport_gap = $viewport_style['spacing']['blockGap'];
451 } elseif ( ! $has_block_gap && $has_global_viewport_block_gap ) {
452 $viewport_gap = $global_gallery_styles[ $breakpoint ]['spacing']['blockGap'];
453 } else {
454 continue;
455 }
456
457 if ( null === $viewport_gap ) {
458 continue;
459 }
460
461 $gallery_styles[] = array(
462 'selector' => ".wp-block-gallery.{$unique_gallery_classname}",
463 'declarations' => array(
464 '--wp--style--unstable-gallery-gap' => gutenberg_block_core_gallery_get_column_gap_value(
465 $viewport_gap,
466 $fallback_gap
467 ),
468 ),
469 'rules_group' => $media_query,
470 );
471 }
472
473 gutenberg_style_engine_get_stylesheet_from_css_rules(
474 $gallery_styles,
475 array( 'context' => 'block-supports' )
476 );
477
478 // The WP_HTML_Tag_Processor class calls get_updated_html() internally
479 // when the instance is treated as a string, but here we explicitly
480 // convert it to a string.
481 $updated_content = $processed_content->get_updated_html();
482
483 /*
484 * Randomize the order of image blocks. Ideally we should shuffle
485 * the `$parsed_block['innerBlocks']` via the `render_block_data` hook.
486 * However, this hook doesn't apply inner block updates when blocks are
487 * nested.
488 * @todo In the future, if this hook supports updating innerBlocks in
489 * nested blocks, it should be refactored.
490 *
491 * @see: https://github.com/WordPress/gutenberg/pull/58733
492 */
493 if ( ! empty( $attributes['randomOrder'] ) ) {
494 // This pattern matches figure elements with the `wp-block-image`
495 // class to avoid the gallery's wrapping `figure` element and
496 // extract images only.
497 $pattern = '/<figure[^>]*\bwp-block-image\b[^>]*>.*?<\/figure>/s';
498
499 preg_match_all( $pattern, $updated_content, $matches );
500 if ( $matches ) {
501 $image_blocks = $matches[0];
502 shuffle( $image_blocks );
503
504 $i = 0;
505 $updated_content = preg_replace_callback(
506 $pattern,
507 static function () use ( $image_blocks, &$i ) {
508 return $image_blocks[ $i++ ];
509 },
510 $updated_content
511 );
512 }
513 }
514
515 // Gets all image IDs from the state that match this gallery's ID.
516 $state = wp_interactivity_state( 'core/image' );
517 $gallery_id = $block->context['galleryId'] ?? null;
518 $image_ids = array();
519
520 // Extracts image IDs from state metadata that match the current gallery ID.
521 if ( isset( $gallery_id ) && isset( $state['metadata'] ) ) {
522 foreach ( $state['metadata'] as $image_id => $metadata ) {
523 if ( isset( $metadata['galleryId'] ) && $metadata['galleryId'] === $gallery_id ) {
524 $image_ids[] = $image_id;
525 }
526 }
527 }
528
529 // If there are image IDs associated with this gallery, set interactivity
530 // attributes and order metadata for lightbox navigation.
531 if ( ! empty( $image_ids ) ) {
532 $total = count( $image_ids );
533 $lightbox_index = 0;
534 $processor = new WP_HTML_Tag_Processor( $updated_content );
535 $processor->next_tag();
536 $processor->set_attribute( 'data-wp-interactive', 'core/gallery' );
537 $processor->set_attribute(
538 'data-wp-context',
539 wp_json_encode(
540 array( 'galleryId' => $gallery_id ),
541 JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
542 )
543 );
544 while ( $processor->next_tag( 'figure' ) ) {
545 $wp_key = $processor->get_attribute( 'data-wp-key' );
546 if ( $wp_key && isset( $state['metadata'][ $wp_key ] ) ) {
547 $alt = $state['metadata'][ $wp_key ]['alt'];
548 wp_interactivity_state(
549 'core/image',
550 array(
551 'metadata' => array(
552 $wp_key => array(
553 'customAriaLabel' => empty( $alt )
554 /* translators: %1$s: current image index, %2$s: total number of images */
555 ? sprintf( __( 'Enlarged image %1$s of %2$s' ), $lightbox_index + 1, $total )
556 /* translators: %1$s: current image index, %2$s: total number of images, %3$s: Image alt text */
557 : sprintf( __( 'Enlarged image %1$s of %2$s: %3$s' ), $lightbox_index + 1, $total, $alt ),
558 /* translators: %1$s: current image index, %2$s: total number of images */
559 'triggerButtonAriaLabel' => sprintf( __( 'Enlarge %1$s of %2$s' ), $lightbox_index + 1, $total ),
560 'order' => $lightbox_index,
561 ),
562 ),
563 )
564 );
565 ++$lightbox_index;
566 }
567 }
568 return $processor->get_updated_html();
569 }
570
571 return $updated_content;
572 }
573
574 /**
575 * Registers the `core/gallery` block on server.
576 *
577 * @since 5.9.0
578 */
579 function gutenberg_register_block_core_gallery() {
580 register_block_type_from_metadata(
581 __DIR__ . '/gallery',
582 array(
583 'render_callback' => 'gutenberg_block_core_gallery_render',
584 )
585 );
586 }
587
588 add_action( 'init', 'gutenberg_register_block_core_gallery', 20 );
589