diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 3d5c9c344e9ce..2c54c7d044c64 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -2,10 +2,9 @@ This page lists the blocks included in the block-library package. -- Items marked with a strikeout (~~strikeout~~) are explicitly disabled. -- Blocks marked with **Experimental:** true are only available when Gutenberg is active. -- Blocks marked with **Experimental:** fse are only available in the Site Editor. - +- Items marked with a strikeout (~~strikeout~~) are explicitly disabled. +- Blocks marked with **Experimental:** true are only available when Gutenberg is active. +- Blocks marked with **Experimental:** fse are only available in the Site Editor. @@ -340,7 +339,7 @@ Insert an image to make a visual statement. ([Source](https://github.com/WordPre - **Name:** core/image - **Category:** media - **Supports:** anchor, color (~~background~~, ~~text~~), filter (duotone) -- **Attributes:** align, alt, aspectRatio, caption, height, href, id, linkClass, linkDestination, linkTarget, rel, scale, sizeSlug, title, url, width +- **Attributes:** align, alt, aspectRatio, caption, height, href, id, lightbox, linkClass, linkDestination, linkTarget, rel, scale, sizeSlug, title, url, width ## Latest Comments diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index fe7dab112b2a4..4890386ca8333 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -130,6 +130,17 @@ Settings related to layout. --- +### lightbox + +Settings related to the lightbox. + +| Property | Type | Default | Props | +| --- | --- | --- |--- | +| enabled | boolean | | | +| allowEditing | boolean | | | + +--- + ### position Settings related to position. diff --git a/lib/block-supports/behaviors.php b/lib/block-supports/behaviors.php index b6692195eb5fb..cf668ed22d887 100644 --- a/lib/block-supports/behaviors.php +++ b/lib/block-supports/behaviors.php @@ -2,6 +2,10 @@ /** * Behaviors block support flag. * + * This file will NOT be backported to Core. It exists to provide a + * migration path for theme.json files that used the deprecated "behaviors". + * This file will be removed from Gutenberg in version 17.0.0. + * * @package gutenberg */ @@ -37,13 +41,23 @@ function gutenberg_register_behaviors_support( $block_type ) { /** * Add the directives and layout needed for the lightbox behavior. - * This functions shouldn't be in this file. It should be moved to a package (or somewhere else), where all the behaviors logic is defined. * * @param string $block_content Rendered block content. * @param array $block Block object. * @return string Filtered block content. */ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { + + // We've deprecated the lightbox implementation via behaviors. + // While we may continue to explore behaviors in the future, the lightbox + // logic seems very specific to the image and will likely never be a part + // of behaviors, even in the future. With that in mind, we've rewritten the lightbox + // to be a feature of the image block and will also soon remove the block_supports. + // *Note: This logic for generating the lightbox markup has been duplicated and moved + // to the image block's index.php.* + // See https://github.com/WordPress/gutenberg/issues/53403. + _deprecated_function( 'gutenberg_render_behaviors_support_lightbox', 'Gutenberg 17.0.0', '' ); + $link_destination = isset( $block['attrs']['linkDestination'] ) ? $block['attrs']['linkDestination'] : 'none'; // Get the lightbox setting from the block attributes. if ( isset( $block['attrs']['behaviors']['lightbox'] ) ) { diff --git a/lib/blocks.php b/lib/blocks.php index c54f5df68489a..537fa9ce4b45e 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -431,4 +431,33 @@ function gutenberg_legacy_wp_block_post_meta( $value, $object_id, $meta_key, $si return $value; } + add_filter( 'default_post_metadata', 'gutenberg_legacy_wp_block_post_meta', 10, 4 ); + +/** + * Complements the lightbox implementation for the 'core/image' block. + * + * This function is INTENTIONALLY left out of core as it only provides + * backwards compatibility for the legacy lightbox syntax that was only + * introduced in Gutenberg. The legacy syntax was using the `behaviors` key in + * the block attrbutes and the `theme.json` file. + * + * @since 16.7.0 + * + * @param array $block The block to check. + * @return array The block with the legacyLightboxSettings set if available. + */ +function gutenberg_should_render_lightbox( $block ) { + + if ( 'core/image' !== $block['blockName'] ) { + return $block; + } + + if ( isset( $block['attrs']['behaviors']['lightbox'] ) ) { + $block['legacyLightboxSettings'] = $block['attrs']['behaviors']['lightbox']; + } + + return $block; +} + +add_filter( 'render_block_data', 'gutenberg_should_render_lightbox', 15, 1 ); diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 330b336bbd0a2..bf7a706923a12 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -346,6 +346,7 @@ class WP_Theme_JSON_Gutenberg { * `position.fixed` and `position.sticky`. * @since 6.3.0 Removed `layout.definitions`. Added `typography.writingMode`. * @since 6.4.0 Added `layout.allowEditing`. + * @since 6.4.0 Added `lightbox`. * @var array */ const VALID_SETTINGS = array( @@ -386,6 +387,10 @@ class WP_Theme_JSON_Gutenberg { 'wideSize' => null, 'allowEditing' => null, ), + 'lightbox' => array( + 'enabled' => null, + 'allowEditing' => null, + ), 'position' => array( 'fixed' => null, 'sticky' => null, @@ -616,7 +621,7 @@ public function __construct( $theme_json = array(), $origin = 'theme' ) { $origin = 'theme'; } - $this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); + $this->theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json ); $registry = WP_Block_Type_Registry::get_instance(); $valid_block_names = array_keys( $registry->get_all_registered() ); $valid_element_names = array_keys( static::ELEMENTS ); @@ -2859,7 +2864,7 @@ protected static function filter_slugs( $node, $slugs ) { public static function remove_insecure_properties( $theme_json ) { $sanitized = array(); - $theme_json = WP_Theme_JSON_Schema::migrate( $theme_json ); + $theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json ); $valid_block_names = array_keys( static::get_blocks_metadata() ); $valid_element_names = array_keys( static::ELEMENTS ); diff --git a/lib/class-wp-theme-json-schema-gutenberg.php b/lib/class-wp-theme-json-schema-gutenberg.php new file mode 100644 index 0000000000000..d11545751af36 --- /dev/null +++ b/lib/class-wp-theme-json-schema-gutenberg.php @@ -0,0 +1,202 @@ + 'border.radius', + 'spacing.customMargin' => 'spacing.margin', + 'spacing.customPadding' => 'spacing.padding', + 'typography.customLineHeight' => 'typography.lineHeight', + ); + + /** + * Function that migrates a given theme.json structure to the last version. + * + * @since 5.9.0 + * + * @param array $theme_json The structure to migrate. + * + * @return array The structure in the last version. + */ + public static function migrate( $theme_json ) { + if ( ! isset( $theme_json['version'] ) ) { + $theme_json = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + ); + } + + if ( 1 === $theme_json['version'] ) { + $theme_json = self::migrate_v1_to_v2( $theme_json ); + } + + if ( 2 === $theme_json['version'] ) { + $theme_json = self::migrate_deprecated_lightbox_behaviors( $theme_json ); + } + + return $theme_json; + } + + /** + * Removes the custom prefixes for a few properties + * that were part of v1: + * + * 'border.customRadius' => 'border.radius', + * 'spacing.customMargin' => 'spacing.margin', + * 'spacing.customPadding' => 'spacing.padding', + * 'typography.customLineHeight' => 'typography.lineHeight', + * + * @since 5.9.0 + * + * @param array $old Data to migrate. + * + * @return array Data without the custom prefixes. + */ + private static function migrate_v1_to_v2( $old ) { + // Copy everything. + $new = $old; + + // Overwrite the things that changed. + if ( isset( $old['settings'] ) ) { + $new['settings'] = self::rename_paths( $old['settings'], self::V1_TO_V2_RENAMED_PATHS ); + } + + // Set the new version. + $new['version'] = 2; + + return $new; + } + + + /** + * Migrate away from the previous syntax that used a top-level "behaviors" key + * in the `theme.json` to a new "lightbox" setting. + * + * This function SHOULD NOT be ported to Core!!! + * + * It is a temporary migration that will be removed in Gutenberg 17.0.0 + * + * @since 16.7.0 + * + * @param array $old Data with (potentially) behaviors. + * @return array Data with behaviors removed. + */ + private static function migrate_deprecated_lightbox_behaviors( $old ) { + // Copy everything. + $new = $old; + + // Migrate the old behaviors syntax to the new "lightbox" syntax. + if ( isset( $old['behaviors']['blocks']['core/image']['lightbox']['enabled'] ) ) { + _wp_array_set( + $new, + array( 'settings', 'blocks', 'core/image', 'lightbox', 'enabled' ), + $old['behaviors']['blocks']['core/image']['lightbox']['enabled'] + ); + } + + // Migrate the behaviors setting to the new syntax. This setting controls + // whether the Lightbox UI shows up in the block editor. + if ( isset( $old['settings']['blocks']['core/image']['behaviors']['lightbox'] ) ) { + _wp_array_set( + $new, + array( 'settings', 'blocks', 'core/image', 'lightbox', 'allowEditing' ), + $old['settings']['blocks']['core/image']['behaviors']['lightbox'] + ); + } + + return $new; + } + + /** + * Processes the settings subtree. + * + * @since 5.9.0 + * + * @param array $settings Array to process. + * @param array $paths_to_rename Paths to rename. + * + * @return array The settings in the new format. + */ + private static function rename_paths( $settings, $paths_to_rename ) { + $new_settings = $settings; + + // Process any renamed/moved paths within default settings. + self::rename_settings( $new_settings, $paths_to_rename ); + + // Process individual block settings. + if ( isset( $new_settings['blocks'] ) && is_array( $new_settings['blocks'] ) ) { + foreach ( $new_settings['blocks'] as &$block_settings ) { + self::rename_settings( $block_settings, $paths_to_rename ); + } + } + + return $new_settings; + } + + /** + * Processes a settings array, renaming or moving properties. + * + * @since 5.9.0 + * + * @param array $settings Reference to settings either defaults or an individual block's. + * @param array $paths_to_rename Paths to rename. + */ + private static function rename_settings( &$settings, $paths_to_rename ) { + foreach ( $paths_to_rename as $original => $renamed ) { + $original_path = explode( '.', $original ); + $renamed_path = explode( '.', $renamed ); + $current_value = _wp_array_get( $settings, $original_path, null ); + + if ( null !== $current_value ) { + _wp_array_set( $settings, $renamed_path, $current_value ); + self::unset_setting_by_path( $settings, $original_path ); + } + } + } + + /** + * Removes a property from within the provided settings by its path. + * + * @since 5.9.0 + * + * @param array $settings Reference to the current settings array. + * @param array $path Path to the property to be removed. + */ + private static function unset_setting_by_path( &$settings, $path ) { + $tmp_settings = &$settings; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $last_key = array_pop( $path ); + foreach ( $path as $key ) { + $tmp_settings = &$tmp_settings[ $key ]; + } + + unset( $tmp_settings[ $last_key ] ); + } +} diff --git a/lib/load.php b/lib/load.php index c749294003012..bb216069eccb5 100644 --- a/lib/load.php +++ b/lib/load.php @@ -201,6 +201,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/global-styles-and-settings.php'; require __DIR__ . '/class-wp-theme-json-data-gutenberg.php'; require __DIR__ . '/class-wp-theme-json-gutenberg.php'; +require __DIR__ . '/class-wp-theme-json-schema-gutenberg.php'; require __DIR__ . '/class-wp-theme-json-resolver-gutenberg.php'; require __DIR__ . '/class-wp-duotone-gutenberg.php'; require __DIR__ . '/blocks.php'; @@ -231,4 +232,5 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/dimensions.php'; require __DIR__ . '/block-supports/duotone.php'; require __DIR__ . '/block-supports/shadow.php'; +require __DIR__ . '/block-supports/behaviors.php'; require __DIR__ . '/block-supports/background.php'; diff --git a/lib/theme.json b/lib/theme.json index 91990215470b9..671dd50227852 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -273,6 +273,11 @@ "radius": true } }, + "core/image": { + "lightbox": { + "allowEditing": true + } + }, "core/pullquote": { "border": { "color": true, diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index 7d81e0d4d224d..4bbc9c40588e0 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -50,6 +50,8 @@ const VALID_SETTINGS = [ 'layout.contentSize', 'layout.definitions', 'layout.wideSize', + 'lightbox.enabled', + 'lightbox.allowEditing', 'position.fixed', 'position.sticky', 'spacing.customSpacingSize', diff --git a/packages/block-editor/src/components/global-styles/image-settings-panel.js b/packages/block-editor/src/components/global-styles/image-settings-panel.js new file mode 100644 index 0000000000000..d3850e5fc9722 --- /dev/null +++ b/packages/block-editor/src/components/global-styles/image-settings-panel.js @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { + __experimentalToolsPanel as ToolsPanel, + __experimentalToolsPanelItem as ToolsPanelItem, + ToggleControl, +} from '@wordpress/components'; +import { __, _x } from '@wordpress/i18n'; + +export function useHasImageSettingsPanel( name, settings, userSettings ) { + // Note: If lightbox userSettings exists, that means + // they were defined via the Global Styles UI and + // will NOT be a boolean value or contain the `allowEditing` + // property, so we should show the settings panel in those cases. + return ( + ( name === 'core/image' && settings?.lightbox?.allowEditing ) || + !! userSettings?.lightbox + ); +} + +export default function ImageSettingsPanel( { + onChange, + userSettings, + settings, + panelId, +} ) { + const resetLightbox = () => { + onChange( undefined ); + }; + + const onChangeLightbox = ( newSetting ) => { + onChange( { + enabled: newSetting, + } ); + }; + + let lightboxChecked = false; + + if ( settings?.lightbox?.enabled ) { + lightboxChecked = settings.lightbox.enabled; + } + + return ( + <> + + !! userSettings?.lightbox } + label={ __( 'Expand on Click' ) } + onDeselect={ resetLightbox } + isShownByDefault={ true } + panelId={ panelId } + > + + + + + ); +} diff --git a/packages/block-editor/src/components/global-styles/index.js b/packages/block-editor/src/components/global-styles/index.js index 24bab543b9ada..76a95357ba52b 100644 --- a/packages/block-editor/src/components/global-styles/index.js +++ b/packages/block-editor/src/components/global-styles/index.js @@ -23,5 +23,9 @@ export { default as BorderPanel, useHasBorderPanel } from './border-panel'; export { default as ColorPanel, useHasColorPanel } from './color-panel'; export { default as EffectsPanel, useHasEffectsPanel } from './effects-panel'; export { default as FiltersPanel, useHasFiltersPanel } from './filters-panel'; +export { + default as ImageSettingsPanel, + useHasImageSettingsPanel, +} from './image-settings-panel'; export { default as AdvancedPanel } from './advanced-panel'; export { areGlobalStyleConfigsEqual } from './utils'; diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index a360e59528126..b8c0432866d96 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -33,6 +33,12 @@ "selector": "figcaption", "__experimentalRole": "content" }, + "lightbox": { + "type": "object", + "enabled": { + "type": "boolean" + } + }, "title": { "type": "string", "source": "attribute", diff --git a/packages/block-library/src/image/deprecated.js b/packages/block-library/src/image/deprecated.js index 83816e1e3f74e..4205da8e117b9 100644 --- a/packages/block-library/src/image/deprecated.js +++ b/packages/block-library/src/image/deprecated.js @@ -941,4 +941,213 @@ const v7 = { }, }; -export default [ v7, v6, v5, v4, v3, v2, v1 ]; +const v8 = { + attributes: { + align: { + type: 'string', + }, + behaviors: { + type: 'object', + }, + url: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'src', + __experimentalRole: 'content', + }, + alt: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'alt', + default: '', + __experimentalRole: 'content', + }, + caption: { + type: 'string', + source: 'html', + selector: 'figcaption', + __experimentalRole: 'content', + }, + title: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'title', + __experimentalRole: 'content', + }, + href: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'href', + __experimentalRole: 'content', + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'rel', + }, + linkClass: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'class', + }, + id: { + type: 'number', + __experimentalRole: 'content', + }, + width: { + type: 'string', + }, + height: { + type: 'string', + }, + aspectRatio: { + type: 'string', + }, + scale: { + type: 'string', + }, + sizeSlug: { + type: 'string', + }, + linkDestination: { + type: 'string', + }, + linkTarget: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'target', + }, + }, + supports: { + anchor: true, + color: { + text: false, + background: false, + }, + filter: { + duotone: true, + }, + __experimentalBorder: { + color: true, + radius: true, + width: true, + __experimentalSkipSerialization: true, + __experimentalDefaultControls: { + color: true, + radius: true, + width: true, + }, + }, + }, + migrate( { width, height, ...attributes } ) { + const { + behaviors: { + lightbox: { enabled }, + }, + } = attributes; + const newAttributes = { + ...attributes, + lightbox: { + enabled, + }, + }; + delete newAttributes.behaviors; + return newAttributes; + }, + isEligible( attributes ) { + return !! attributes.behaviors; + }, + save( { attributes } ) { + const { + url, + alt, + caption, + align, + href, + rel, + linkClass, + width, + height, + aspectRatio, + scale, + id, + linkTarget, + sizeSlug, + title, + } = attributes; + + const newRel = ! rel ? undefined : rel; + const borderProps = getBorderClassesAndStyles( attributes ); + + const classes = classnames( { + [ `align${ align }` ]: align, + [ `size-${ sizeSlug }` ]: sizeSlug, + 'is-resized': width || height, + 'has-custom-border': + !! borderProps.className || + ( borderProps.style && + Object.keys( borderProps.style ).length > 0 ), + } ); + + const imageClasses = classnames( borderProps.className, { + [ `wp-image-${ id }` ]: !! id, + } ); + + const image = ( + { + ); + + const figure = ( + <> + { href ? ( + + { image } + + ) : ( + image + ) } + { ! RichText.isEmpty( caption ) && ( + + ) } + + ); + + return ( +
+ { figure } +
+ ); + }, +}; + +export default [ v8, v7, v6, v5, v4, v3, v2, v1 ]; diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 02c0319db6c98..04839a47b880a 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -7,6 +7,7 @@ import { ResizableBox, Spinner, TextareaControl, + ToggleControl, TextControl, ToolbarButton, ToolbarGroup, @@ -23,6 +24,7 @@ import { __experimentalImageURLInputUI as ImageURLInputUI, MediaReplaceFlow, store as blockEditorStore, + useSetting, BlockAlignmentControl, __experimentalImageEditor as ImageEditor, __experimentalGetElementClassName, @@ -113,6 +115,7 @@ export default function Image( { scale, linkTarget, sizeSlug, + lightbox, } = attributes; // The only supported unit is px, so we can parseInt to strip the px here. @@ -171,6 +174,7 @@ export default function Image( { }, [ clientId ] ); + const { replaceBlocks, toggleSelection } = useDispatch( blockEditorStore ); const { createErrorNotice, createSuccessNotice } = useDispatch( noticesStore ); @@ -365,6 +369,14 @@ export default function Image( { availableUnits: [ 'px' ], } ); + const lightboxSetting = useSetting( 'lightbox' ); + + const showLightboxToggle = + lightboxSetting === true || lightboxSetting?.allowEditing === true; + + const lightboxChecked = + lightbox?.enabled || ( ! lightbox && lightboxSetting?.enabled ); + const dimensionsControl = ( + { showLightboxToggle && ( + !! lightbox } + label={ __( 'Expand on Click' ) } + onDeselect={ () => { + setAttributes( { lightbox: undefined } ); + } } + isShownByDefault={ true } + > + { + setAttributes( { + lightbox: { enabled: newValue }, + } ); + } } + /> + + ) } diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index fceba38244033..1bf93bd81a9d0 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -31,67 +31,91 @@ function render_block_core_image( $attributes, $content, $block ) { $processor->set_attribute( 'data-id', $attributes['data-id'] ); } - $should_load_view_script = false; - $link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none'; + $lightbox_enabled = false; + $link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none'; + $lightbox_settings = block_core_image_get_lightbox_settings( $block->parsed_block ); - // Get the lightbox setting from the block attributes. - if ( isset( $attributes['lightbox'] ) ) { - $lightbox_settings = $attributes['lightbox']; - } + // If the lightbox is enabled and the image is not linked, flag the lightbox to be rendered. + if ( isset( $lightbox_settings ) && 'none' === $link_destination ) { - // If the lightbox is enabled, the image is not linked, and the Interactivity API is enabled, load the view script. - if ( isset( $lightbox_settings['enabled'] ) && - true === $lightbox_settings['enabled'] && - 'none' === $link_destination - ) { - $should_load_view_script = true; + if ( isset( $lightbox_settings['enabled'] ) && true === $lightbox_settings['enabled'] ) { + $lightbox_enabled = true; + } } // If at least one block in the page has the lightbox, mark the block type as interactive. - if ( $should_load_view_script ) { + if ( $lightbox_enabled ) { $block->block_type->supports['interactivity'] = true; } + // Determine whether the view script should be enqueued or not. $view_js_file = 'wp-block-image-view'; if ( ! wp_script_is( $view_js_file ) ) { $script_handles = $block->block_type->view_script_handles; // If the script is not needed, and it is still in the `view_script_handles`, remove it. - if ( ! $should_load_view_script && in_array( $view_js_file, $script_handles, true ) ) { + if ( ! $lightbox_enabled && in_array( $view_js_file, $script_handles, true ) ) { $block->block_type->view_script_handles = array_diff( $script_handles, array( $view_js_file ) ); } // If the script is needed, but it was previously removed, add it again. - if ( $should_load_view_script && ! in_array( $view_js_file, $script_handles, true ) ) { + if ( $lightbox_enabled && ! in_array( $view_js_file, $script_handles, true ) ) { $block->block_type->view_script_handles = array_merge( $script_handles, array( $view_js_file ) ); } } + if ( $lightbox_enabled ) { + return block_core_image_render_lightbox( $processor->get_updated_html(), $block->parsed_block ); + } + return $processor->get_updated_html(); } - /** - * Add the directives and layout needed for the lightbox behavior. + * Add the lightboxEnabled flag to the block data. * - * @param string $block_content Rendered block content. - * @param array $block Block object. - * @return string Filtered block content. + * This is used to determine whether the lightbox should be rendered or not. + * + * @param array $block Block data. + * @return array Filtered block data. */ -function block_core_image_render_lightbox( $block_content, $block ) { - $link_destination = isset( $block['attrs']['linkDestination'] ) ? $block['attrs']['linkDestination'] : 'none'; +function block_core_image_get_lightbox_settings( $block ) { // Get the lightbox setting from the block attributes. if ( isset( $block['attrs']['lightbox'] ) ) { $lightbox_settings = $block['attrs']['lightbox']; + // If the lightbox setting is not set in the block attributes, + // check the legacy lightbox settings that are set using the + // `gutenberg_should_render_lightbox` filter. + // We can remove this elseif statement when the legacy lightbox settings are removed. + } elseif ( isset( $block['legacyLightboxSettings'] ) ) { + $lightbox_settings = $block['legacyLightboxSettings']; } - if ( ! isset( $lightbox_settings ) || 'none' !== $link_destination ) { - return $block_content; + if ( ! isset( $lightbox_settings ) ) { + $lightbox_settings = gutenberg_get_global_settings( array( 'lightbox' ), array( 'block_name' => 'core/image' ) ); + + // If not present in global settings, check the top-level global settings. + // + // NOTE: If no block-level settings are found, the previous call to + // `gutenberg_get_global_settings` will return the whole `theme.json` + // structure in which case we can check if the "lightbox" key is present at + // the top-level of the global settings and use its value. + if ( isset( $lightbox_settings['lightbox'] ) ) { + $lightbox_settings = gutenberg_get_global_settings( array( 'lightbox' ) ); + } } - if ( isset( $lightbox_settings['enabled'] ) && false === $lightbox_settings['enabled'] ) { - return $block_content; - } + return $lightbox_settings ?? null; +} + +/** + * Add the directives and layout needed for the lightbox behavior. + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function block_core_image_render_lightbox( $block_content, $block ) { $processor = new WP_HTML_Tag_Processor( $block_content ); @@ -109,7 +133,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { } $content = $processor->get_updated_html(); - // Currently, the only supported animation is 'zoom'. + // Currently, we are only enabling the zoom animation. $lightbox_animation = 'zoom'; // We want to store the src in the context so we can set it dynamically when the lightbox is opened. @@ -258,16 +282,9 @@ function block_core_image_render_lightbox( $block_content, $block ) { return str_replace( '', $lightbox_html . '', $body_content ); } -// TODO: We should not be adding a separate filter but rather move the -// the lightbox rendering to the `render_block_core_image` function. - -// Use priority 15 to run this hook after other hooks/plugins. -// They could use the `render_block_{$this->name}` filter to modify the markup. -add_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15, 2 ); - - /** - * Registers the `core/image` block on server. - */ +/** + * Registers the `core/image` block on server. + */ function register_block_core_image() { register_block_type_from_metadata( __DIR__ . '/image', @@ -276,4 +293,4 @@ function register_block_core_image() { ) ); } - add_action( 'init', 'register_block_core_image' ); +add_action( 'init', 'register_block_core_image' ); diff --git a/packages/edit-site/src/components/global-styles/screen-block.js b/packages/edit-site/src/components/global-styles/screen-block.js index f683c559e59bc..f2bc5e920067d 100644 --- a/packages/edit-site/src/components/global-styles/screen-block.js +++ b/packages/edit-site/src/components/global-styles/screen-block.js @@ -66,6 +66,7 @@ const { useHasColorPanel, useHasEffectsPanel, useHasFiltersPanel, + useHasImageSettingsPanel, useGlobalStyle, BorderPanel: StylesBorderPanel, ColorPanel: StylesColorPanel, @@ -73,6 +74,7 @@ const { DimensionsPanel: StylesDimensionsPanel, EffectsPanel: StylesEffectsPanel, FiltersPanel: StylesFiltersPanel, + ImageSettingsPanel, AdvancedPanel: StylesAdvancedPanel, } = unlock( blockEditorPrivateApis ); @@ -90,6 +92,7 @@ function ScreenBlock( { name, variation } ) { shouldDecodeEncode: false, } ); const [ rawSettings, setSettings ] = useGlobalSetting( '', name ); + const [ userSettings ] = useGlobalSetting( '', name, 'user' ); const settings = useSettingsForBlockElement( rawSettings, name ); const blockType = getBlockType( name ); @@ -113,6 +116,11 @@ function ScreenBlock( { name, variation } ) { const hasDimensionsPanel = useHasDimensionsPanel( settings ); const hasEffectsPanel = useHasEffectsPanel( settings ); const hasFiltersPanel = useHasFiltersPanel( settings ); + const hasImageSettingsPanel = useHasImageSettingsPanel( + name, + settings, + userSettings + ); const hasVariationsPanel = !! blockVariations?.length && ! variation; const { canEditCSS } = useSelect( ( select ) => { const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = @@ -158,6 +166,27 @@ function ScreenBlock( { name, variation } ) { } ); } }; + const onChangeLightbox = ( newSetting ) => { + // If the newSetting is undefined, this means that the user has deselected + // (reset) the lightbox setting. + if ( newSetting === undefined ) { + setSettings( { + ...rawSettings, + lightbox: undefined, + } ); + + // Otherwise, we simply set the lightbox setting to the new value but + // taking care of not overriding the other lightbox settings. + } else { + setSettings( { + ...rawSettings, + lightbox: { + ...rawSettings.lightbox, + ...newSetting, + }, + } ); + } + }; const onChangeBorders = ( newStyle ) => { if ( ! newStyle?.border ) { setStyle( newStyle ); @@ -265,6 +294,14 @@ function ScreenBlock( { name, variation } ) { includeLayoutControls /> ) } + { hasImageSettingsPanel && ( + + ) } + { canEditCSS && (

diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 11e36df5c1ef6..e0f3efc01eb1f 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -296,6 +296,26 @@ } } }, + "settingsPropertiesLightbox": { + "type": "object", + "additionalProperties": false, + "properties": { + "lightbox": { + "description": "Settings related to the lightbox.", + "type": "object", + "properties": { + "enabled": { + "description": "Defines whether the lightbox is enabled or not.", + "type": "boolean" + }, + "allowEditing": { + "description": "Defines whether to show the Lightbox UI in the block editor. If set to `false`, the user won't be able to change the lightbox settings in the block editor.", + "type": "boolean" + } + } + } + } + }, "settingsPropertiesPosition": { "type": "object", "properties": { @@ -688,6 +708,7 @@ { "$ref": "#/definitions/settingsPropertiesDimensions" }, { "$ref": "#/definitions/settingsPropertiesShadow" }, { "$ref": "#/definitions/settingsPropertiesLayout" }, + { "$ref": "#/definitions/settingsPropertiesLightbox" }, { "$ref": "#/definitions/settingsPropertiesPosition" }, { "$ref": "#/definitions/settingsPropertiesSpacing" }, { "$ref": "#/definitions/settingsPropertiesTypography" }, @@ -708,6 +729,7 @@ "color": {}, "dimensions": {}, "layout": {}, + "lightbox": {}, "position": {}, "shadow": {}, "spacing": {}, @@ -2177,6 +2199,7 @@ "background": {}, "color": {}, "layout": {}, + "lightbox": {}, "spacing": {}, "typography": {}, "border": {}, diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.html b/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.html new file mode 100644 index 0000000000000..9feeb105951da --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.html @@ -0,0 +1,3 @@ + +

+ diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.json b/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.json new file mode 100644 index 0000000000000..a32f031dd34f4 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.json @@ -0,0 +1,18 @@ +[ + { + "name": "core/image", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "lightbox": { + "enabled": true + }, + "id": 8, + "sizeSlug": "large", + "linkDestination": "none" + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.parsed.json b/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.parsed.json new file mode 100644 index 0000000000000..0ca652ff77f83 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.parsed.json @@ -0,0 +1,18 @@ +[ + { + "blockName": "core/image", + "attrs": { + "lightbox": { + "enabled": true + }, + "id": 8, + "sizeSlug": "large", + "linkDestination": "none" + }, + "innerBlocks": [], + "innerHTML": "\n
\"\"
\n", + "innerContent": [ + "\n
\"\"
\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.serialized.html new file mode 100644 index 0000000000000..9feeb105951da --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v8-deprecate-behaviors-lightbox.serialized.html @@ -0,0 +1,3 @@ + +
+