wp/wp-includes/blocks/image.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
     7 
     7 
     8 /**
     8 /**
     9  * Renders the `core/image` block on the server,
     9  * Renders the `core/image` block on the server,
    10  * adding a data-id attribute to the element if core/gallery has added on pre-render.
    10  * adding a data-id attribute to the element if core/gallery has added on pre-render.
    11  *
    11  *
    12  * @param  array  $attributes The block attributes.
    12  * @since 5.9.0
    13  * @param  string $content    The block content.
    13  *
    14  * @return string Returns the block content with the data-id attribute added.
    14  * @param array    $attributes The block attributes.
    15  */
    15  * @param string   $content    The block content.
    16 function render_block_core_image( $attributes, $content ) {
    16  * @param WP_Block $block      The block object.
       
    17  *
       
    18  * @return string The block content with the data-id attribute added.
       
    19  */
       
    20 function render_block_core_image( $attributes, $content, $block ) {
       
    21 	if ( false === stripos( $content, '<img' ) ) {
       
    22 		return '';
       
    23 	}
       
    24 
       
    25 	$p = new WP_HTML_Tag_Processor( $content );
       
    26 
       
    27 	if ( ! $p->next_tag( 'img' ) || null === $p->get_attribute( 'src' ) ) {
       
    28 		return '';
       
    29 	}
       
    30 
       
    31 	$has_id_binding = isset( $attributes['metadata']['bindings']['id'] ) && isset( $attributes['id'] );
       
    32 
       
    33 	// Ensure the `wp-image-id` classname on the image block supports block bindings.
       
    34 	if ( $has_id_binding ) {
       
    35 		// If there's a mismatch with the 'wp-image-' class and the actual id, the id was
       
    36 		// probably overridden by block bindings. Update it to the correct value.
       
    37 		// See https://github.com/WordPress/gutenberg/issues/62886 for why this is needed.
       
    38 		$id                       = $attributes['id'];
       
    39 		$image_classnames         = $p->get_attribute( 'class' );
       
    40 		$class_with_binding_value = "wp-image-$id";
       
    41 		if ( is_string( $image_classnames ) && ! str_contains( $image_classnames, $class_with_binding_value ) ) {
       
    42 			$image_classnames = preg_replace( '/wp-image-(\d+)/', $class_with_binding_value, $image_classnames );
       
    43 			$p->set_attribute( 'class', $image_classnames );
       
    44 		}
       
    45 	}
       
    46 
       
    47 	// For backwards compatibility, the data-id html attribute is only set for
       
    48 	// image blocks nested in a gallery. Detect if the image is in a gallery by
       
    49 	// checking the data-id attribute.
       
    50 	// See the `block_core_gallery_data_id_backcompatibility` function.
    17 	if ( isset( $attributes['data-id'] ) ) {
    51 	if ( isset( $attributes['data-id'] ) ) {
    18 		// Add the data-id="$id" attribute to the img element
    52 		// If there's a binding for the `id`, the `id` attribute is used for the
    19 		// to provide backwards compatibility for the Gallery Block,
    53 		// value, since `data-id` does not support block bindings.
    20 		// which now wraps Image Blocks within innerBlocks.
    54 		// Else the `data-id` is used for backwards compatibility, since
    21 		// The data-id attribute is added in a core/gallery `render_block_data` hook.
    55 		// third parties may be filtering its value.
    22 		$data_id_attribute = 'data-id="' . esc_attr( $attributes['data-id'] ) . '"';
    56 		$data_id = $has_id_binding ? $attributes['id'] : $attributes['data-id'];
    23 		if ( false === strpos( $content, $data_id_attribute ) ) {
    57 		$p->set_attribute( 'data-id', $data_id );
    24 			$content = str_replace( '<img', '<img ' . $data_id_attribute . ' ', $content );
    58 	}
    25 		}
    59 
    26 	}
    60 	$link_destination  = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none';
    27 	return $content;
    61 	$lightbox_settings = block_core_image_get_lightbox_settings( $block->parsed_block );
    28 }
    62 
    29 
    63 	/*
       
    64 	 * If the lightbox is enabled and the image is not linked, adds the filter and
       
    65 	 * the JavaScript view file.
       
    66 	 */
       
    67 	if (
       
    68 		isset( $lightbox_settings ) &&
       
    69 		'none' === $link_destination &&
       
    70 		isset( $lightbox_settings['enabled'] ) &&
       
    71 		true === $lightbox_settings['enabled']
       
    72 	) {
       
    73 		$suffix = wp_scripts_get_suffix();
       
    74 		if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) {
       
    75 			$module_url = gutenberg_url( '/build/interactivity/image.min.js' );
       
    76 		}
       
    77 
       
    78 		wp_register_script_module(
       
    79 			'@wordpress/block-library/image',
       
    80 			isset( $module_url ) ? $module_url : includes_url( "blocks/image/view{$suffix}.js" ),
       
    81 			array( '@wordpress/interactivity' ),
       
    82 			defined( 'GUTENBERG_VERSION' ) ? GUTENBERG_VERSION : get_bloginfo( 'version' )
       
    83 		);
       
    84 
       
    85 		wp_enqueue_script_module( '@wordpress/block-library/image' );
       
    86 
       
    87 		/*
       
    88 		 * This render needs to happen in a filter with priority 15 to ensure that
       
    89 		 * it runs after the duotone filter and that duotone styles are applied to
       
    90 		 * the image in the lightbox. Lightbox has to work with any plugins that
       
    91 		 * might use filters as well. Removing this can be considered in the future
       
    92 		 * if the way the blocks are rendered changes, or if a new kind of filter is
       
    93 		 * introduced.
       
    94 		 */
       
    95 		add_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15, 2 );
       
    96 	} else {
       
    97 		/*
       
    98 		 * Remove the filter if previously added by other Image blocks.
       
    99 		 */
       
   100 		remove_filter( 'render_block_core/image', 'block_core_image_render_lightbox', 15 );
       
   101 	}
       
   102 
       
   103 	return $p->get_updated_html();
       
   104 }
       
   105 
       
   106 /**
       
   107  * Adds the lightboxEnabled flag to the block data.
       
   108  *
       
   109  * This is used to determine whether the lightbox should be rendered or not.
       
   110  *
       
   111  * @since 6.4.0
       
   112  *
       
   113  * @param array $block Block data.
       
   114  *
       
   115  * @return array Filtered block data.
       
   116  */
       
   117 function block_core_image_get_lightbox_settings( $block ) {
       
   118 	// Gets the lightbox setting from the block attributes.
       
   119 	if ( isset( $block['attrs']['lightbox'] ) ) {
       
   120 		$lightbox_settings = $block['attrs']['lightbox'];
       
   121 	}
       
   122 
       
   123 	if ( ! isset( $lightbox_settings ) ) {
       
   124 		$lightbox_settings = wp_get_global_settings( array( 'lightbox' ), array( 'block_name' => 'core/image' ) );
       
   125 
       
   126 		// If not present in global settings, check the top-level global settings.
       
   127 		//
       
   128 		// NOTE: If no block-level settings are found, the previous call to
       
   129 		// `wp_get_global_settings` will return the whole `theme.json` structure in
       
   130 		// which case we can check if the "lightbox" key is present at the top-level
       
   131 		// of the global settings and use its value.
       
   132 		if ( isset( $lightbox_settings['lightbox'] ) ) {
       
   133 			$lightbox_settings = wp_get_global_settings( array( 'lightbox' ) );
       
   134 		}
       
   135 	}
       
   136 
       
   137 	return $lightbox_settings ?? null;
       
   138 }
       
   139 
       
   140 /**
       
   141  * Adds the directives and layout needed for the lightbox behavior.
       
   142  *
       
   143  * @since 6.4.0
       
   144  *
       
   145  * @param string $block_content Rendered block content.
       
   146  * @param array  $block         Block object.
       
   147  *
       
   148  * @return string Filtered block content.
       
   149  */
       
   150 function block_core_image_render_lightbox( $block_content, $block ) {
       
   151 	/*
       
   152 	 * If there's no IMG tag in the block then return the given block content
       
   153 	 * as-is. There's nothing that this code can knowingly modify to add the
       
   154 	 * lightbox behavior.
       
   155 	 */
       
   156 	$p = new WP_HTML_Tag_Processor( $block_content );
       
   157 	if ( $p->next_tag( 'figure' ) ) {
       
   158 		$p->set_bookmark( 'figure' );
       
   159 	}
       
   160 	if ( ! $p->next_tag( 'img' ) ) {
       
   161 		return $block_content;
       
   162 	}
       
   163 
       
   164 	$alt              = $p->get_attribute( 'alt' );
       
   165 	$img_uploaded_src = $p->get_attribute( 'src' );
       
   166 	$img_class_names  = $p->get_attribute( 'class' );
       
   167 	$img_styles       = $p->get_attribute( 'style' );
       
   168 	$img_width        = 'none';
       
   169 	$img_height       = 'none';
       
   170 	$aria_label       = __( 'Enlarge image' );
       
   171 
       
   172 	if ( $alt ) {
       
   173 		/* translators: %s: Image alt text. */
       
   174 		$aria_label = sprintf( __( 'Enlarge image: %s' ), $alt );
       
   175 	}
       
   176 
       
   177 	if ( isset( $block['attrs']['id'] ) ) {
       
   178 		$img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] );
       
   179 		$img_metadata     = wp_get_attachment_metadata( $block['attrs']['id'] );
       
   180 		$img_width        = $img_metadata['width'] ?? 'none';
       
   181 		$img_height       = $img_metadata['height'] ?? 'none';
       
   182 	}
       
   183 
       
   184 	// Figure.
       
   185 	$p->seek( 'figure' );
       
   186 	$figure_class_names = $p->get_attribute( 'class' );
       
   187 	$figure_styles      = $p->get_attribute( 'style' );
       
   188 	$p->add_class( 'wp-lightbox-container' );
       
   189 	$p->set_attribute( 'data-wp-interactive', 'core/image' );
       
   190 	$p->set_attribute(
       
   191 		'data-wp-context',
       
   192 		wp_json_encode(
       
   193 			array(
       
   194 				'uploadedSrc'      => $img_uploaded_src,
       
   195 				'figureClassNames' => $figure_class_names,
       
   196 				'figureStyles'     => $figure_styles,
       
   197 				'imgClassNames'    => $img_class_names,
       
   198 				'imgStyles'        => $img_styles,
       
   199 				'targetWidth'      => $img_width,
       
   200 				'targetHeight'     => $img_height,
       
   201 				'scaleAttr'        => $block['attrs']['scale'] ?? false,
       
   202 				'ariaLabel'        => $aria_label,
       
   203 				'alt'              => $alt,
       
   204 			),
       
   205 			JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
       
   206 		)
       
   207 	);
       
   208 
       
   209 	// Image.
       
   210 	$p->next_tag( 'img' );
       
   211 	$p->set_attribute( 'data-wp-init', 'callbacks.setButtonStyles' );
       
   212 	$p->set_attribute( 'data-wp-on-async--load', 'callbacks.setButtonStyles' );
       
   213 	$p->set_attribute( 'data-wp-on-async-window--resize', 'callbacks.setButtonStyles' );
       
   214 	// Sets an event callback on the `img` because the `figure` element can also
       
   215 	// contain a caption, and we don't want to trigger the lightbox when the
       
   216 	// caption is clicked.
       
   217 	$p->set_attribute( 'data-wp-on-async--click', 'actions.showLightbox' );
       
   218 
       
   219 	$body_content = $p->get_updated_html();
       
   220 
       
   221 	// Adds a button alongside image in the body content.
       
   222 	$img = null;
       
   223 	preg_match( '/<img[^>]+>/', $body_content, $img );
       
   224 
       
   225 	$button =
       
   226 		$img[0]
       
   227 		. '<button
       
   228 			class="lightbox-trigger"
       
   229 			type="button"
       
   230 			aria-haspopup="dialog"
       
   231 			aria-label="' . esc_attr( $aria_label ) . '"
       
   232 			data-wp-init="callbacks.initTriggerButton"
       
   233 			data-wp-on-async--click="actions.showLightbox"
       
   234 			data-wp-style--right="context.imageButtonRight"
       
   235 			data-wp-style--top="context.imageButtonTop"
       
   236 		>
       
   237 			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
       
   238 				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
       
   239 			</svg>
       
   240 		</button>';
       
   241 
       
   242 	$body_content = preg_replace( '/<img[^>]+>/', $button, $body_content );
       
   243 
       
   244 	add_action( 'wp_footer', 'block_core_image_print_lightbox_overlay' );
       
   245 
       
   246 	return $body_content;
       
   247 }
       
   248 
       
   249 /**
       
   250  * @since 6.5.0
       
   251  */
       
   252 function block_core_image_print_lightbox_overlay() {
       
   253 	$close_button_label = esc_attr__( 'Close' );
       
   254 
       
   255 	// If the current theme does NOT have a `theme.json`, or the colors are not
       
   256 	// defined, it needs to set the background color & close button color to some
       
   257 	// default values because it can't get them from the Global Styles.
       
   258 	$background_color   = '#fff';
       
   259 	$close_button_color = '#000';
       
   260 	if ( wp_theme_has_theme_json() ) {
       
   261 		$global_styles_color = wp_get_global_styles( array( 'color' ) );
       
   262 		if ( ! empty( $global_styles_color['background'] ) ) {
       
   263 			$background_color = esc_attr( $global_styles_color['background'] );
       
   264 		}
       
   265 		if ( ! empty( $global_styles_color['text'] ) ) {
       
   266 			$close_button_color = esc_attr( $global_styles_color['text'] );
       
   267 		}
       
   268 	}
       
   269 
       
   270 	echo <<<HTML
       
   271 		<div
       
   272 			class="wp-lightbox-overlay zoom"
       
   273 			data-wp-interactive="core/image"
       
   274 			data-wp-context='{}'
       
   275 			data-wp-bind--role="state.roleAttribute"
       
   276 			data-wp-bind--aria-label="state.currentImage.ariaLabel"
       
   277 			data-wp-bind--aria-modal="state.ariaModal"
       
   278 			data-wp-class--active="state.overlayEnabled"
       
   279 			data-wp-class--show-closing-animation="state.showClosingAnimation"
       
   280 			data-wp-watch="callbacks.setOverlayFocus"
       
   281 			data-wp-on--keydown="actions.handleKeydown"
       
   282 			data-wp-on-async--touchstart="actions.handleTouchStart"
       
   283 			data-wp-on--touchmove="actions.handleTouchMove"
       
   284 			data-wp-on-async--touchend="actions.handleTouchEnd"
       
   285 			data-wp-on-async--click="actions.hideLightbox"
       
   286 			data-wp-on-async-window--resize="callbacks.setOverlayStyles"
       
   287 			data-wp-on-async-window--scroll="actions.handleScroll"
       
   288 			tabindex="-1"
       
   289 			>
       
   290 				<button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="close-button">
       
   291 					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false"><path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path></svg>
       
   292 				</button>
       
   293 				<div class="lightbox-image-container">
       
   294 					<figure data-wp-bind--class="state.currentImage.figureClassNames" data-wp-bind--style="state.currentImage.figureStyles">
       
   295 						<img data-wp-bind--alt="state.currentImage.alt" data-wp-bind--class="state.currentImage.imgClassNames" data-wp-bind--style="state.imgStyles" data-wp-bind--src="state.currentImage.currentSrc">
       
   296 					</figure>
       
   297 				</div>
       
   298 				<div class="lightbox-image-container">
       
   299 					<figure data-wp-bind--class="state.currentImage.figureClassNames" data-wp-bind--style="state.currentImage.figureStyles">
       
   300 						<img data-wp-bind--alt="state.currentImage.alt" data-wp-bind--class="state.currentImage.imgClassNames" data-wp-bind--style="state.imgStyles" data-wp-bind--src="state.enlargedSrc">
       
   301 					</figure>
       
   302 				</div>
       
   303 				<div class="scrim" style="background-color: $background_color" aria-hidden="true"></div>
       
   304 				<style data-wp-text="state.overlayStyles"></style>
       
   305 		</div>
       
   306 HTML;
       
   307 }
    30 
   308 
    31 /**
   309 /**
    32  * Registers the `core/image` block on server.
   310  * Registers the `core/image` block on server.
       
   311  *
       
   312  * @since 5.9.0
    33  */
   313  */
    34 function register_block_core_image() {
   314 function register_block_core_image() {
    35 	register_block_type_from_metadata(
   315 	register_block_type_from_metadata(
    36 		__DIR__ . '/image',
   316 		__DIR__ . '/image',
    37 		array(
   317 		array(