[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Server-side rendering of the `core/image` block. 4 * 5 * @package WordPress 6 */ 7 8 /** 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. 11 * 12 * @since 5.9.0 13 * 14 * @param array $attributes The block attributes. 15 * @param string $content The block 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. 51 if ( isset( $attributes['data-id'] ) ) { 52 // If there's a binding for the `id`, the `id` attribute is used for the 53 // value, since `data-id` does not support block bindings. 54 // Else the `data-id` is used for backwards compatibility, since 55 // third parties may be filtering its value. 56 $data_id = $has_id_binding ? $attributes['id'] : $attributes['data-id']; 57 $p->set_attribute( 'data-id', $data_id ); 58 } 59 60 $link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none'; 61 $lightbox_settings = block_core_image_get_lightbox_settings( $block->parsed_block ); 62 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 } 308 309 /** 310 * Registers the `core/image` block on server. 311 * 312 * @since 5.9.0 313 */ 314 function register_block_core_image() { 315 register_block_type_from_metadata( 316 __DIR__ . '/image', 317 array( 318 'render_callback' => 'render_block_core_image', 319 ) 320 ); 321 } 322 add_action( 'init', 'register_block_core_image' );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sat Sep 14 08:20:02 2024 | Cross-referenced by PHPXref |