PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
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.1 → trunk 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.