PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.0.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 All 404 releases
← All changes | build/scripts/block-library/gallery.php +293 -77 23.7.0 → 24.1.0 View file →
@@ -87,8 +87,170 @@
87 87 return '0' === $gap_column ? '0px' : $gap_column;
88 88 }
89 89
90 90 /**
91 + * Returns whether a value can be used as a Gallery aspect ratio.
92 + *
93 + * Aspect ratios are interpolated into a generated stylesheet instead of being
94 + * set as an inline style, so only the numeric forms produced by aspect ratio
95 + * presets (and `auto`) are accepted. A value from saved content that isn't one
96 + * of those is ignored rather than emitted, so it can't close the rule early and
97 + * inject declarations of its own. The editor applies the same restriction in
98 + * `isValidGalleryAspectRatio()`.
99 + *
100 + * @since 7.1.0
101 + *
102 + * @param mixed $value Value to check.
103 + * @return bool Whether the value is a valid aspect ratio.
104 + */
105 +function gutenberg_block_core_gallery_is_valid_aspect_ratio( $value ) {
106 + return is_string( $value ) && 1 === preg_match( '#^(auto|\d+(\.\d+)?(\s*/\s*\d+(\.\d+)?)?)$#', trim( $value ) );
107 +}
108 +
109 +/**
110 + * Returns Gallery-specific responsive aspect ratio rules for a viewport.
111 + *
112 + * Unlike the column and crop rules, these are not scoped to the Flex layout:
113 + * the aspect ratio applies to the images in every Gallery layout. They also
114 + * cover dynamic galleries, whose images are rendered by the Gallery rather than
115 + * carrying their own block attributes.
116 + *
117 + * @since 7.1.0
118 + *
119 + * @param string $selector Gallery block selector.
120 + * @param mixed $viewport_style Viewport style data.
121 + * @param string $media_query Viewport media query.
122 + * @return array[] Gallery responsive aspect ratio rules.
123 + */
124 +function gutenberg_block_core_gallery_get_responsive_aspect_ratio_style_rules( $selector, $viewport_style, $media_query ) {
125 + if ( ! is_array( $viewport_style ) || ! is_string( $media_query ) ) {
126 + return array();
127 + }
128 +
129 + $aspect_ratio = $viewport_style['aspectRatio'] ?? null;
130 + if ( ! gutenberg_block_core_gallery_is_valid_aspect_ratio( $aspect_ratio ) ) {
131 + return array();
132 + }
133 +
134 + $aspect_ratio = trim( $aspect_ratio );
135 +
136 + /*
137 + * The base aspect ratio is an inline style on each image, so these
138 + * declarations have to be important to win for the viewport.
139 + *
140 + * Original cancels the base ratio, which means rolling the declaration out
141 + * of the cascade rather than giving it a value. `auto` - and `initial`,
142 + * `unset` and `revert`, which all compute to it - would override the
143 + * `width`/`height` presentational hint that gives a lazy-loaded image its
144 + * placeholder ratio, collapsing the image to zero height until it loads:
145 + * the Featured Image bug fixed in #80386. `revert-layer` drops the
146 + * declaration instead, so the image falls back to that hint while loading,
147 + * to its natural ratio once loaded, and to any ratio a theme set in a lower
148 + * cascade layer. `object-fit` is left as the base set it, so a cropped
149 + * Gallery still crops.
150 + */
151 + $declarations = 'auto' === $aspect_ratio
152 + ? array( 'aspect-ratio' => 'revert-layer !important' )
153 + : array(
154 + 'aspect-ratio' => "{$aspect_ratio} !important",
155 + 'object-fit' => 'cover !important',
156 + );
157 +
158 + return array(
159 + array(
160 + 'selector' => "{$selector}.wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image) img",
161 + 'declarations' => $declarations,
162 + 'rules_group' => $media_query,
163 + ),
164 + );
165 +}
166 +
167 +/**
168 + * Returns Gallery-specific responsive Flex rules for a viewport.
169 + *
170 + * @since 7.1.0
171 + *
172 + * @param string $selector Gallery block selector.
173 + * @param mixed $viewport_style Viewport style data.
174 + * @param string $media_query Viewport media query.
175 + * @return array[] Gallery responsive Flex rules.
176 + */
177 +function gutenberg_block_core_gallery_get_responsive_flex_style_rules( $selector, $viewport_style, $media_query ) {
178 + if ( ! is_array( $viewport_style ) || ! is_string( $media_query ) ) {
179 + return array();
180 + }
181 +
182 + $rules = array();
183 + $gallery_selector = "{$selector}.wp-block-gallery.has-nested-images:where(.is-layout-flex)";
184 + $image_selector = "{$gallery_selector} figure.wp-block-image:not(#individual-image)";
185 + $columns = $viewport_style['columns'] ?? null;
186 +
187 + if ( is_int( $columns ) && $columns >= 1 && $columns <= 8 ) {
188 + $width = 1 === $columns
189 + ? '100%'
190 + : sprintf(
191 + 'calc((100%% - (var(--wp--style--unstable-gallery-gap, 16px) * %1$d)) / %2$d)',
192 + $columns - 1,
193 + $columns
194 + );
195 + $rules[] = array(
196 + 'selector' => $image_selector,
197 + 'declarations' => array( 'width' => "{$width} !important" ),
198 + 'rules_group' => $media_query,
199 + );
200 + }
201 +
202 + $image_crop = $viewport_style['imageCrop'] ?? null;
203 + if ( ! is_bool( $image_crop ) ) {
204 + return $rules;
205 + }
206 +
207 + $rules[] = array(
208 + 'selector' => $image_selector,
209 + 'declarations' => $image_crop
210 + ? array(
211 + 'align-self' => 'inherit !important',
212 + 'margin-bottom' => '0 !important',
213 + )
214 + : array(
215 + 'align-self' => 'auto !important',
216 + 'margin-top' => '0 !important',
217 + 'margin-bottom' => 'auto !important',
218 + ),
219 + 'rules_group' => $media_query,
220 + );
221 + $rules[] = array(
222 + 'selector' => "{$image_selector} > div:not(.components-drop-zone)",
223 + 'declarations' => array( 'display' => $image_crop ? 'flex !important' : 'block !important' ),
224 + 'rules_group' => $media_query,
225 + );
226 + $rules[] = array(
227 + 'selector' => "{$image_selector} > a",
228 + 'declarations' => array( 'display' => $image_crop ? 'flex !important' : 'inline-block !important' ),
229 + 'rules_group' => $media_query,
230 + );
231 + $rules[] = array(
232 + 'selector' => "{$image_selector} a,{$image_selector} img",
233 + 'declarations' => $image_crop
234 + ? array(
235 + 'width' => '100% !important',
236 + 'flex' => '1 0 0% !important',
237 + 'height' => '100% !important',
238 + 'object-fit' => 'cover !important',
239 + )
240 + : array(
241 + 'width' => 'auto !important',
242 + 'flex' => '0 1 auto !important',
243 + 'height' => 'auto !important',
244 + 'object-fit' => 'fill !important',
245 + ),
246 + 'rules_group' => $media_query,
247 + );
248 +
249 + return $rules;
250 +}
251 +
252 +/**
91 253 * Resolves a Gallery block's `dynamicContent` to an ordered list of image
92 254 * attachment IDs.
93 255 *
94 256 * The `source` key is the dispatch discriminator and `args` holds the source's
@@ -308,10 +470,17 @@
308 470 */
309 471 function gutenberg_block_core_gallery_render( $attributes, $content, $block ) {
310 472 static $global_styles = null;
311 473
474 + // Gallery blocks created before layout variations existed do not have an
475 + // explicit layout attribute. Missing and malformed layout data therefore
476 + // falls back to Flex so existing galleries retain their current appearance.
477 + $layout = is_array( $attributes['layout'] ?? null ) ? $attributes['layout'] : array();
478 + $layout_type = $layout['type'] ?? null;
479 + $is_flex_layout = ! is_string( $layout_type ) || '' === $layout_type || 'flex' === $layout_type;
480 +
312 481 // 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
482 + // being authored as inner blocks, so `save.jsx` persists at most the
314 483 // gallery-level caption — a bare `<figcaption>`, or nothing when there is no
315 484 // caption. Resolve the configured source to a list of attachments, render an
316 485 // image block for each, and build the gallery `<figure>` wrapper from scratch.
317 486 // The gap/randomOrder/lightbox post-processing below then runs over the
@@ -356,22 +525,24 @@
356 525
357 526 // Build the wrapper rather than parsing/splicing saved markup.
358 527 // `get_block_wrapper_attributes()` supplies the block-support
359 528 // classes/styles (align, color, border, spacing, anchor id); the layout
360 - // render filter adds the flex layout classes downstream — the same way a
529 + // render filter adds the active layout classes downstream — the same way a
361 530 // static gallery's wrapper is composed (`useBlockProps.save()` plus that
362 531 // 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';
532 + // they mirror `save.jsx` (kept in sync deliberately — see that file).
533 + $gallery_classes = 'wp-block-gallery has-nested-images';
534 + if ( $is_flex_layout ) {
535 + $gallery_classes .= isset( $attributes['columns'] )
536 + ? ' columns-' . (int) $attributes['columns']
537 + : ' columns-default';
538 + if ( $attributes['imageCrop'] ?? true ) {
539 + $gallery_classes .= ' is-cropped';
540 + }
370 541 }
371 542 $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $gallery_classes ) );
372 543
373 - // In dynamic mode `save.js` persists only the gallery-level caption, so
544 + // In dynamic mode `save.jsx` persists only the gallery-level caption, so
374 545 // `$content` is the saved `<figcaption>` (or empty). Append it after the
375 546 // resolved images — matching the static gallery's `{images}{caption}`
376 547 // order — without parsing it.
377 548 $content = sprintf( '<figure %s>%s%s</figure>', $wrapper_attributes, $images_markup, $content );
@@ -376,12 +547,10 @@
376 547 // order — without parsing it.
377 548 $content = sprintf( '<figure %s>%s%s</figure>', $wrapper_attributes, $images_markup, $content );
378 549 }
379 550
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.
551 + $processed_content = new WP_HTML_Tag_Processor( $content );
552 + $processed_content->next_tag();
384 553
385 554 $style_attr = is_array( $attributes['style'] ?? null )
386 555 ? $attributes['style']
387 556 : array();
@@ -392,41 +561,8 @@
392 561 ) {
393 562 $style_attr = gutenberg_resolve_style_state_aliases( $style_attr, 'core/gallery' );
394 563 }
395 564
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 565 $global_settings = gutenberg_get_global_settings();
430 566 $viewport_settings = $global_settings['viewport'] ?? null;
431 567 $responsive_media_queries = array();
432 568 foreach ( array( 'WP_Theme_JSON_Gutenberg', 'WP_Theme_JSON' ) as $theme_json_class_name ) {
@@ -435,46 +571,126 @@
435 571 break;
436 572 }
437 573 }
438 574
575 + // Columns and cropping are Flex-only, but the aspect ratio applies in every
576 + // layout, so a Gallery with a viewport aspect ratio needs the per-instance
577 + // stylesheet (and the class scoping it) even when it isn't a Flex Gallery.
578 + $has_viewport_aspect_ratio = false;
439 579 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'] );
580 + $viewport_style = $style_attr[ $breakpoint ] ?? null;
581 + if (
582 + is_array( $viewport_style ) &&
583 + gutenberg_block_core_gallery_is_valid_aspect_ratio( $viewport_style['aspectRatio'] ?? null )
584 + ) {
585 + $has_viewport_aspect_ratio = true;
586 + break;
587 + }
588 + }
446 589
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;
590 + /*
591 + * Only generate the gap styles — and, when nothing else needs it, the unique
592 + * classname that exists solely to scope them — if the theme has not opted out
593 + * of layout styles. The responsive aspect ratio rules are not layout styles,
594 + * so they keep rendering either way.
595 + */
596 + $should_generate_gap_styles = $is_flex_layout && ! current_theme_supports( 'disable-layout-styles' );
597 +
598 + if ( $should_generate_gap_styles || $has_viewport_aspect_ratio ) {
599 + $unique_gallery_classname = wp_unique_id( 'wp-block-gallery-' );
600 + $processed_content->add_class( $unique_gallery_classname );
601 + $gallery_styles = array();
602 +
603 + if ( $should_generate_gap_styles ) {
604 + // Add a style tag for the --wp--style--unstable-gallery-gap var. The
605 + // Gallery's custom Flex layout recalculates Image block widths based on
606 + // the current gap so it can maintain the selected number of columns.
607 +
608 + // --gallery-block--gutter-size is deprecated. --wp--style--gallery-gap-default should be used by themes that want to set a default
609 + // gap on the gallery.
610 + $fallback_gap = 'var( --wp--style--gallery-gap-default, var( --gallery-block--gutter-size, var( --wp--style--block-gap, 0.5em ) ) )';
611 +
612 + if ( null === $global_styles ) {
613 + $global_styles = function_exists( 'gutenberg_get_global_styles' ) ? gutenberg_get_global_styles() : array();
614 + }
615 +
616 + $global_gallery_styles = $global_styles['blocks']['core/gallery'] ?? array();
617 + $global_gallery_gap = $global_gallery_styles['spacing']['blockGap'] ?? $fallback_gap;
618 + $has_block_gap = is_array( $style_attr['spacing'] ?? null ) && array_key_exists( 'blockGap', $style_attr['spacing'] );
619 + // Prefer the block's own gap value, then Gallery global styles. Missing
620 + // values fall back to the Gallery blockGap default.
621 + $block_gap = $has_block_gap
622 + ? $style_attr['spacing']['blockGap']
623 + : $global_gallery_gap;
624 + $gap_column = gutenberg_block_core_gallery_get_column_gap_value( $block_gap, $fallback_gap );
625 +
626 + // Set the CSS variable to the column value for Gallery's flex width calculations.
627 + $gallery_styles[] = array(
628 + 'selector' => ".wp-block-gallery.{$unique_gallery_classname}",
629 + 'declarations' => array(
630 + '--wp--style--unstable-gallery-gap' => $gap_column,
631 + ),
632 + );
455 633 }
456 634
457 - if ( null === $viewport_gap ) {
458 - continue;
635 + foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
636 + $viewport_style = $style_attr[ $breakpoint ] ?? null;
637 +
638 + if ( $should_generate_gap_styles ) {
639 + $has_viewport_block_gap = is_array( $viewport_style ) &&
640 + is_array( $viewport_style['spacing'] ?? null ) &&
641 + array_key_exists( 'blockGap', $viewport_style['spacing'] );
642 + $has_global_viewport_block_gap = is_array( $global_gallery_styles[ $breakpoint ]['spacing'] ?? null ) &&
643 + array_key_exists( 'blockGap', $global_gallery_styles[ $breakpoint ]['spacing'] );
644 +
645 + // Viewport-specific block values win. Gallery global viewport values
646 + // only apply when the block has no base gap, so they do not override an instance value.
647 + if ( $has_viewport_block_gap ) {
648 + $viewport_gap = $viewport_style['spacing']['blockGap'];
649 + } elseif ( ! $has_block_gap && $has_global_viewport_block_gap ) {
650 + $viewport_gap = $global_gallery_styles[ $breakpoint ]['spacing']['blockGap'];
651 + } else {
652 + $viewport_gap = null;
653 + }
654 +
655 + if ( null !== $viewport_gap ) {
656 + $gallery_styles[] = array(
657 + 'selector' => ".wp-block-gallery.{$unique_gallery_classname}",
658 + 'declarations' => array(
659 + '--wp--style--unstable-gallery-gap' => gutenberg_block_core_gallery_get_column_gap_value(
660 + $viewport_gap,
661 + $fallback_gap
662 + ),
663 + ),
664 + 'rules_group' => $media_query,
665 + );
666 + }
667 +
668 + $gallery_styles = array_merge(
669 + $gallery_styles,
670 + gutenberg_block_core_gallery_get_responsive_flex_style_rules(
671 + ".{$unique_gallery_classname}",
672 + $viewport_style,
673 + $media_query
674 + )
675 + );
676 + }
677 +
678 + $gallery_styles = array_merge(
679 + $gallery_styles,
680 + gutenberg_block_core_gallery_get_responsive_aspect_ratio_style_rules(
681 + ".{$unique_gallery_classname}",
682 + $viewport_style,
683 + $media_query
684 + )
685 + );
459 686 }
460 687
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,
688 + gutenberg_style_engine_get_stylesheet_from_css_rules(
689 + $gallery_styles,
690 + array( 'context' => 'block-supports' )
470 691 );
471 692 }
472 -
473 - gutenberg_style_engine_get_stylesheet_from_css_rules(
474 - $gallery_styles,
475 - array( 'context' => 'block-supports' )
476 - );
477 693
478 694 // The WP_HTML_Tag_Processor class calls get_updated_html() internally
479 695 // when the instance is treated as a string, but here we explicitly
480 696 // convert it to a string.