PluginProbe
Gutenberg / 19.6.1
Gutenberg v19.6.1
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 19.6.1, at build/block-library/blocks/image.php

328 lines 11.9 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' ) || ! $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 wp_enqueue_script_module( '@wordpress/block-library/image/view' );
74
75 /*
76 * This render needs to happen in a filter with priority 15 to ensure that
77 * it runs after the duotone filter and that duotone styles are applied to
78 * the image in the lightbox. Lightbox has to work with any plugins that
79 * might use filters as well. Removing this can be considered in the future
80 * if the way the blocks are rendered changes, or if a new kind of filter is
81 * introduced.
82 */
83 add_filter( 'render_block_core/image', 'gutenberg_block_core_image_render_lightbox', 15, 2 );
84 } else {
85 /*
86 * Remove the filter if previously added by other Image blocks.
87 */
88 remove_filter( 'render_block_core/image', 'gutenberg_block_core_image_render_lightbox', 15 );
89 }
90
91 return $p->get_updated_html();
92 }
93
94 /**
95 * Adds the lightboxEnabled flag to the block data.
96 *
97 * This is used to determine whether the lightbox should be rendered or not.
98 *
99 * @since 6.4.0
100 *
101 * @param array $block Block data.
102 *
103 * @return array Filtered block data.
104 */
105 function gutenberg_block_core_image_get_lightbox_settings( $block ) {
106 // Gets the lightbox setting from the block attributes.
107 if ( isset( $block['attrs']['lightbox'] ) ) {
108 $lightbox_settings = $block['attrs']['lightbox'];
109 }
110
111 if ( ! isset( $lightbox_settings ) ) {
112 $lightbox_settings = gutenberg_get_global_settings( array( 'lightbox' ), array( 'block_name' => 'core/image' ) );
113
114 // If not present in global settings, check the top-level global settings.
115 //
116 // NOTE: If no block-level settings are found, the previous call to
117 // `gutenberg_get_global_settings` will return the whole `theme.json` structure in
118 // which case we can check if the "lightbox" key is present at the top-level
119 // of the global settings and use its value.
120 if ( isset( $lightbox_settings['lightbox'] ) ) {
121 $lightbox_settings = gutenberg_get_global_settings( array( 'lightbox' ) );
122 }
123 }
124
125 return $lightbox_settings ?? null;
126 }
127
128 /**
129 * Adds the directives and layout needed for the lightbox behavior.
130 *
131 * @since 6.4.0
132 *
133 * @param string $block_content Rendered block content.
134 * @param array $block Block object.
135 *
136 * @return string Filtered block content.
137 */
138 function gutenberg_block_core_image_render_lightbox( $block_content, $block ) {
139 /*
140 * If there's no IMG tag in the block then return the given block content
141 * as-is. There's nothing that this code can knowingly modify to add the
142 * lightbox behavior.
143 */
144 $p = new WP_HTML_Tag_Processor( $block_content );
145 if ( $p->next_tag( 'figure' ) ) {
146 $p->set_bookmark( 'figure' );
147 }
148 if ( ! $p->next_tag( 'img' ) ) {
149 return $block_content;
150 }
151
152 $alt = $p->get_attribute( 'alt' );
153 $img_uploaded_src = $p->get_attribute( 'src' );
154 $img_class_names = $p->get_attribute( 'class' );
155 $img_styles = $p->get_attribute( 'style' );
156 $img_width = 'none';
157 $img_height = 'none';
158 $aria_label = __( 'Enlarge image' );
159
160 if ( $alt ) {
161 /* translators: %s: Image alt text. */
162 $aria_label = sprintf( __( 'Enlarge image: %s' ), $alt );
163 }
164
165 if ( isset( $block['attrs']['id'] ) ) {
166 $img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] );
167 $img_metadata = wp_get_attachment_metadata( $block['attrs']['id'] );
168 $img_width = $img_metadata['width'] ?? 'none';
169 $img_height = $img_metadata['height'] ?? 'none';
170 }
171
172 // Figure.
173 $p->seek( 'figure' );
174 $figure_class_names = $p->get_attribute( 'class' );
175 $figure_styles = $p->get_attribute( 'style' );
176
177 // Create unique id and set the image metadata in the state.
178 $unique_image_id = uniqid();
179
180 wp_interactivity_state(
181 'core/image',
182 array(
183 'metadata' => array(
184 $unique_image_id => array(
185 'uploadedSrc' => $img_uploaded_src,
186 'figureClassNames' => $figure_class_names,
187 'figureStyles' => $figure_styles,
188 'imgClassNames' => $img_class_names,
189 'imgStyles' => $img_styles,
190 'targetWidth' => $img_width,
191 'targetHeight' => $img_height,
192 'scaleAttr' => $block['attrs']['scale'] ?? false,
193 'ariaLabel' => $aria_label,
194 'alt' => $alt,
195 ),
196 ),
197 )
198 );
199
200 $p->add_class( 'wp-lightbox-container' );
201 $p->set_attribute( 'data-wp-interactive', 'core/image' );
202 $p->set_attribute(
203 'data-wp-context',
204 wp_json_encode(
205 array(
206 'imageId' => $unique_image_id,
207 ),
208 JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
209 )
210 );
211
212 // Image.
213 $p->next_tag( 'img' );
214 $p->set_attribute( 'data-wp-init', 'callbacks.setButtonStyles' );
215 $p->set_attribute( 'data-wp-on-async--load', 'callbacks.setButtonStyles' );
216 $p->set_attribute( 'data-wp-on-async-window--resize', 'callbacks.setButtonStyles' );
217 // Sets an event callback on the `img` because the `figure` element can also
218 // contain a caption, and we don't want to trigger the lightbox when the
219 // caption is clicked.
220 $p->set_attribute( 'data-wp-on-async--click', 'actions.showLightbox' );
221 $p->set_attribute( 'data-wp-class--hide', 'state.isContentHidden' );
222 $p->set_attribute( 'data-wp-class--show', 'state.isContentVisible' );
223
224 $body_content = $p->get_updated_html();
225
226 // Adds a button alongside image in the body content.
227 $img = null;
228 preg_match( '/<img[^>]+>/', $body_content, $img );
229
230 $button =
231 $img[0]
232 . '<button
233 class="lightbox-trigger"
234 type="button"
235 aria-haspopup="dialog"
236 aria-label="' . esc_attr( $aria_label ) . '"
237 data-wp-init="callbacks.initTriggerButton"
238 data-wp-on-async--click="actions.showLightbox"
239 data-wp-style--right="state.imageButtonRight"
240 data-wp-style--top="state.imageButtonTop"
241 >
242 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
243 <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" />
244 </svg>
245 </button>';
246
247 $body_content = preg_replace( '/<img[^>]+>/', $button, $body_content );
248
249 add_action( 'wp_footer', 'gutenberg_block_core_image_print_lightbox_overlay' );
250
251 return $body_content;
252 }
253
254 /**
255 * @since 6.5.0
256 */
257 function gutenberg_block_core_image_print_lightbox_overlay() {
258 $close_button_label = esc_attr__( 'Close' );
259
260 // If the current theme does NOT have a `theme.json`, or the colors are not
261 // defined, it needs to set the background color & close button color to some
262 // default values because it can't get them from the Global Styles.
263 $background_color = '#fff';
264 $close_button_color = '#000';
265 if ( wp_theme_has_theme_json() ) {
266 $global_styles_color = wp_get_global_styles( array( 'color' ) );
267 if ( ! empty( $global_styles_color['background'] ) ) {
268 $background_color = esc_attr( $global_styles_color['background'] );
269 }
270 if ( ! empty( $global_styles_color['text'] ) ) {
271 $close_button_color = esc_attr( $global_styles_color['text'] );
272 }
273 }
274
275 echo <<<HTML
276 <div
277 class="wp-lightbox-overlay zoom"
278 data-wp-interactive="core/image"
279 data-wp-context='{}'
280 data-wp-bind--role="state.roleAttribute"
281 data-wp-bind--aria-label="state.currentImage.ariaLabel"
282 data-wp-bind--aria-modal="state.ariaModal"
283 data-wp-class--active="state.overlayEnabled"
284 data-wp-class--show-closing-animation="state.showClosingAnimation"
285 data-wp-watch="callbacks.setOverlayFocus"
286 data-wp-on--keydown="actions.handleKeydown"
287 data-wp-on-async--touchstart="actions.handleTouchStart"
288 data-wp-on--touchmove="actions.handleTouchMove"
289 data-wp-on-async--touchend="actions.handleTouchEnd"
290 data-wp-on-async--click="actions.hideLightbox"
291 data-wp-on-async-window--resize="callbacks.setOverlayStyles"
292 data-wp-on-async-window--scroll="actions.handleScroll"
293 tabindex="-1"
294 >
295 <button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="close-button">
296 <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>
297 </button>
298 <div class="lightbox-image-container">
299 <figure data-wp-bind--class="state.currentImage.figureClassNames" data-wp-bind--style="state.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.currentImage.currentSrc">
301 </figure>
302 </div>
303 <div class="lightbox-image-container">
304 <figure data-wp-bind--class="state.currentImage.figureClassNames" data-wp-bind--style="state.figureStyles">
305 <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">
306 </figure>
307 </div>
308 <div class="scrim" style="background-color: $background_color" aria-hidden="true"></div>
309 <style data-wp-text="state.overlayStyles"></style>
310 </div>
311 HTML;
312 }
313
314 /**
315 * Registers the `core/image` block on server.
316 *
317 * @since 5.9.0
318 */
319 function gutenberg_register_block_core_image() {
320 register_block_type_from_metadata(
321 __DIR__ . '/image',
322 array(
323 'render_callback' => 'gutenberg_render_block_core_image',
324 )
325 );
326 }
327 add_action( 'init', 'gutenberg_register_block_core_image', 20 );
328