diff -r 7b1b88e27a20 -r 48c4eec2b7e6 wp/wp-includes/blocks/image.php --- a/wp/wp-includes/blocks/image.php Thu Sep 29 08:06:27 2022 +0200 +++ b/wp/wp-includes/blocks/image.php Fri Sep 05 18:40:08 2025 +0200 @@ -9,27 +9,307 @@ * Renders the `core/image` block on the server, * adding a data-id attribute to the element if core/gallery has added on pre-render. * - * @param array $attributes The block attributes. - * @param string $content The block content. - * @return string Returns the block content with the data-id attribute added. + * @since 5.9.0 + * + * @param array $attributes The block attributes. + * @param string $content The block content. + * @param WP_Block $block The block object. + * + * @return string The block content with the data-id attribute added. */ -function render_block_core_image( $attributes, $content ) { +function render_block_core_image( $attributes, $content, $block ) { + if ( false === stripos( $content, 'next_tag( 'img' ) || null === $p->get_attribute( 'src' ) ) { + return ''; + } + + $has_id_binding = isset( $attributes['metadata']['bindings']['id'] ) && isset( $attributes['id'] ); + + // Ensure the `wp-image-id` classname on the image block supports block bindings. + if ( $has_id_binding ) { + // If there's a mismatch with the 'wp-image-' class and the actual id, the id was + // probably overridden by block bindings. Update it to the correct value. + // See https://github.com/WordPress/gutenberg/issues/62886 for why this is needed. + $id = $attributes['id']; + $image_classnames = $p->get_attribute( 'class' ); + $class_with_binding_value = "wp-image-$id"; + if ( is_string( $image_classnames ) && ! str_contains( $image_classnames, $class_with_binding_value ) ) { + $image_classnames = preg_replace( '/wp-image-(\d+)/', $class_with_binding_value, $image_classnames ); + $p->set_attribute( 'class', $image_classnames ); + } + } + + // For backwards compatibility, the data-id html attribute is only set for + // image blocks nested in a gallery. Detect if the image is in a gallery by + // checking the data-id attribute. + // See the `block_core_gallery_data_id_backcompatibility` function. if ( isset( $attributes['data-id'] ) ) { - // Add the data-id="$id" attribute to the img element - // to provide backwards compatibility for the Gallery Block, - // which now wraps Image Blocks within innerBlocks. - // The data-id attribute is added in a core/gallery `render_block_data` hook. - $data_id_attribute = 'data-id="' . esc_attr( $attributes['data-id'] ) . '"'; - if ( false === strpos( $content, $data_id_attribute ) ) { - $content = str_replace( 'set_attribute( 'data-id', $data_id ); + } + + $link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none'; + $lightbox_settings = block_core_image_get_lightbox_settings( $block->parsed_block ); + + /* + * If the lightbox is enabled and the image is not linked, adds the filter and + * the JavaScript view file. + */ + if ( + isset( $lightbox_settings ) && + 'none' === $link_destination && + isset( $lightbox_settings['enabled'] ) && + true === $lightbox_settings['enabled'] + ) { + $suffix = wp_scripts_get_suffix(); + if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { + $module_url = gutenberg_url( '/build/interactivity/image.min.js' ); + } + + wp_register_script_module( + '@wordpress/block-library/image', + isset( $module_url ) ? $module_url : includes_url( "blocks/image/view{$suffix}.js" ), + array( '@wordpress/interactivity' ), + defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' ) + ); + + wp_enqueue_script_module( '@wordpress/block-library/image' ); + + /* + * This render needs to happen in a filter with priority 15 to ensure that + * it runs after the duotone filter and that duotone styles are applied to + * the image in the lightbox. Lightbox has to work with any plugins that + * might use filters as well. Removing this can be considered in the future + * if the way the blocks are rendered changes, or if a new kind of filter is + * introduced. + */ + add_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15, 2 ); + } else { + /* + * Remove the filter if previously added by other Image blocks. + */ + remove_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15 ); + } + + return $p->get_updated_html(); +} + +/** + * Adds the lightboxEnabled flag to the block data. + * + * This is used to determine whether the lightbox should be rendered or not. + * + * @since 6.4.0 + * + * @param array $block Block data. + * + * @return array Filtered block data. + */ +function block_core_image_get_lightbox_settings( $block ) { + // Gets the lightbox setting from the block attributes. + if ( isset( $block['attrs']['lightbox'] ) ) { + $lightbox_settings = $block['attrs']['lightbox']; + } + + if ( ! isset( $lightbox_settings ) ) { + $lightbox_settings = wp_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 + // `wp_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 = wp_get_global_settings( array( 'lightbox' ) ); } } - return $content; + + return $lightbox_settings ?? null; } +/** + * Adds the directives and layout needed for the lightbox behavior. + * + * @since 6.4.0 + * + * @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 ) { + /* + * If there's no IMG tag in the block then return the given block content + * as-is. There's nothing that this code can knowingly modify to add the + * lightbox behavior. + */ + $p = new WP_HTML_Tag_Processor( $block_content ); + if ( $p->next_tag( 'figure' ) ) { + $p->set_bookmark( 'figure' ); + } + if ( ! $p->next_tag( 'img' ) ) { + return $block_content; + } + + $alt = $p->get_attribute( 'alt' ); + $img_uploaded_src = $p->get_attribute( 'src' ); + $img_class_names = $p->get_attribute( 'class' ); + $img_styles = $p->get_attribute( 'style' ); + $img_width = 'none'; + $img_height = 'none'; + $aria_label = __( 'Enlarge image' ); + + if ( $alt ) { + /* translators: %s: Image alt text. */ + $aria_label = sprintf( __( 'Enlarge image: %s' ), $alt ); + } + + if ( isset( $block['attrs']['id'] ) ) { + $img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] ); + $img_metadata = wp_get_attachment_metadata( $block['attrs']['id'] ); + $img_width = $img_metadata['width'] ?? 'none'; + $img_height = $img_metadata['height'] ?? 'none'; + } + + // Figure. + $p->seek( 'figure' ); + $figure_class_names = $p->get_attribute( 'class' ); + $figure_styles = $p->get_attribute( 'style' ); + $p->add_class( 'wp-lightbox-container' ); + $p->set_attribute( 'data-wp-interactive', 'core/image' ); + $p->set_attribute( + 'data-wp-context', + wp_json_encode( + array( + 'uploadedSrc' => $img_uploaded_src, + 'figureClassNames' => $figure_class_names, + 'figureStyles' => $figure_styles, + 'imgClassNames' => $img_class_names, + 'imgStyles' => $img_styles, + 'targetWidth' => $img_width, + 'targetHeight' => $img_height, + 'scaleAttr' => $block['attrs']['scale'] ?? false, + 'ariaLabel' => $aria_label, + 'alt' => $alt, + ), + JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP + ) + ); + + // Image. + $p->next_tag( 'img' ); + $p->set_attribute( 'data-wp-init', 'callbacks.setButtonStyles' ); + $p->set_attribute( 'data-wp-on-async--load', 'callbacks.setButtonStyles' ); + $p->set_attribute( 'data-wp-on-async-window--resize', 'callbacks.setButtonStyles' ); + // Sets an event callback on the `img` because the `figure` element can also + // contain a caption, and we don't want to trigger the lightbox when the + // caption is clicked. + $p->set_attribute( 'data-wp-on-async--click', 'actions.showLightbox' ); + + $body_content = $p->get_updated_html(); + + // Adds a button alongside image in the body content. + $img = null; + preg_match( '/]+>/', $body_content, $img ); + + $button = + $img[0] + . ''; + + $body_content = preg_replace( '/]+>/', $button, $body_content ); + + add_action( 'wp_footer', 'block_core_image_print_lightbox_overlay' ); + + return $body_content; +} + +/** + * @since 6.5.0 + */ +function block_core_image_print_lightbox_overlay() { + $close_button_label = esc_attr__( 'Close' ); + + // If the current theme does NOT have a `theme.json`, or the colors are not + // defined, it needs to set the background color & close button color to some + // default values because it can't get them from the Global Styles. + $background_color = '#fff'; + $close_button_color = '#000'; + if ( wp_theme_has_theme_json() ) { + $global_styles_color = wp_get_global_styles( array( 'color' ) ); + if ( ! empty( $global_styles_color['background'] ) ) { + $background_color = esc_attr( $global_styles_color['background'] ); + } + if ( ! empty( $global_styles_color['text'] ) ) { + $close_button_color = esc_attr( $global_styles_color['text'] ); + } + } + + echo << + + + + + + +HTML; +} /** * Registers the `core/image` block on server. + * + * @since 5.9.0 */ function register_block_core_image() { register_block_type_from_metadata(