PluginProbe
Gutenberg / 18.9.0
Gutenberg v18.9.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 12.6.0 All 403 releases
gutenberg / build / block-library / blocks / image.php

image.php in Gutenberg 18.9.0, at build/block-library/blocks/image.php

340 lines 12.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 gutenberg_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 = gutenberg_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', 'gutenberg_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', 'gutenberg_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 gutenberg_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 = gutenberg_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 // `gutenberg_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 = gutenberg_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 gutenberg_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
189 // Create unique id and set the image metadata in the state.
190 $unique_image_id = uniqid();
191
192 wp_interactivity_state(
193 'core/image',
194 array(
195 'metadata' => array(
196 $unique_image_id => array(
197 'uploadedSrc' => $img_uploaded_src,
198 'figureClassNames' => $figure_class_names,
199 'figureStyles' => $figure_styles,
200 'imgClassNames' => $img_class_names,
201 'imgStyles' => $img_styles,
202 'targetWidth' => $img_width,
203 'targetHeight' => $img_height,
204 'scaleAttr' => $block['attrs']['scale'] ?? false,
205 'ariaLabel' => $aria_label,
206 'alt' => $alt,
207 ),
208 ),
209 )
210 );
211
212 $p->add_class( 'wp-lightbox-container' );
213 $p->set_attribute( 'data-wp-interactive', 'core/image' );
214 $p->set_attribute(
215 'data-wp-context',
216 wp_json_encode(
217 array(
218 'imageId' => $unique_image_id,
219 ),
220 JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
221 )
222 );
223
224 // Image.
225 $p->next_tag( 'img' );
226 $p->set_attribute( 'data-wp-init', 'callbacks.setButtonStyles' );
227 $p->set_attribute( 'data-wp-on-async--load', 'callbacks.setButtonStyles' );
228 $p->set_attribute( 'data-wp-on-async-window--resize', 'callbacks.setButtonStyles' );
229 // Sets an event callback on the `img` because the `figure` element can also
230 // contain a caption, and we don't want to trigger the lightbox when the
231 // caption is clicked.
232 $p->set_attribute( 'data-wp-on-async--click', 'actions.showLightbox' );
233 $p->set_attribute( 'data-wp-class--hide', 'state.isContentHidden' );
234 $p->set_attribute( 'data-wp-class--show', 'state.isContentVisible' );
235
236 $body_content = $p->get_updated_html();
237
238 // Adds a button alongside image in the body content.
239 $img = null;
240 preg_match( '/<img[^>]+>/', $body_content, $img );
241
242 $button =
243 $img[0]
244 . '<button
245 class="lightbox-trigger"
246 type="button"
247 aria-haspopup="dialog"
248 aria-label="' . esc_attr( $aria_label ) . '"
249 data-wp-init="callbacks.initTriggerButton"
250 data-wp-on-async--click="actions.showLightbox"
251 data-wp-style--right="state.imageButtonRight"
252 data-wp-style--top="state.imageButtonTop"
253 >
254 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
255 <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" />
256 </svg>
257 </button>';
258
259 $body_content = preg_replace( '/<img[^>]+>/', $button, $body_content );
260
261 add_action( 'wp_footer', 'gutenberg_block_core_image_print_lightbox_overlay' );
262
263 return $body_content;
264 }
265
266 /**
267 * @since 6.5.0
268 */
269 function gutenberg_block_core_image_print_lightbox_overlay() {
270 $close_button_label = esc_attr__( 'Close' );
271
272 // If the current theme does NOT have a `theme.json`, or the colors are not
273 // defined, it needs to set the background color & close button color to some
274 // default values because it can't get them from the Global Styles.
275 $background_color = '#fff';
276 $close_button_color = '#000';
277 if ( wp_theme_has_theme_json() ) {
278 $global_styles_color = wp_get_global_styles( array( 'color' ) );
279 if ( ! empty( $global_styles_color['background'] ) ) {
280 $background_color = esc_attr( $global_styles_color['background'] );
281 }
282 if ( ! empty( $global_styles_color['text'] ) ) {
283 $close_button_color = esc_attr( $global_styles_color['text'] );
284 }
285 }
286
287 echo <<<HTML
288 <div
289 class="wp-lightbox-overlay zoom"
290 data-wp-interactive="core/image"
291 data-wp-context='{}'
292 data-wp-bind--role="state.roleAttribute"
293 data-wp-bind--aria-label="state.currentImage.ariaLabel"
294 data-wp-bind--aria-modal="state.ariaModal"
295 data-wp-class--active="state.overlayEnabled"
296 data-wp-class--show-closing-animation="state.showClosingAnimation"
297 data-wp-watch="callbacks.setOverlayFocus"
298 data-wp-on--keydown="actions.handleKeydown"
299 data-wp-on-async--touchstart="actions.handleTouchStart"
300 data-wp-on--touchmove="actions.handleTouchMove"
301 data-wp-on-async--touchend="actions.handleTouchEnd"
302 data-wp-on-async--click="actions.hideLightbox"
303 data-wp-on-async-window--resize="callbacks.setOverlayStyles"
304 data-wp-on-async-window--scroll="actions.handleScroll"
305 tabindex="-1"
306 >
307 <button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="close-button">
308 <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.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"></path></svg>
309 </button>
310 <div class="lightbox-image-container">
311 <figure data-wp-bind--class="state.currentImage.figureClassNames" data-wp-bind--style="state.figureStyles">
312 <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">
313 </figure>
314 </div>
315 <div class="lightbox-image-container">
316 <figure data-wp-bind--class="state.currentImage.figureClassNames" data-wp-bind--style="state.figureStyles">
317 <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">
318 </figure>
319 </div>
320 <div class="scrim" style="background-color: $background_color" aria-hidden="true"></div>
321 <style data-wp-text="state.overlayStyles"></style>
322 </div>
323 HTML;
324 }
325
326 /**
327 * Registers the `core/image` block on server.
328 *
329 * @since 5.9.0
330 */
331 function gutenberg_register_block_core_image() {
332 register_block_type_from_metadata(
333 __DIR__ . '/image',
334 array(
335 'render_callback' => 'gutenberg_render_block_core_image',
336 )
337 );
338 }
339 add_action( 'init', 'gutenberg_register_block_core_image', 20 );
340