PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / extensions / blocks / slideshow / render.php
jetpack / extensions / blocks / slideshow Last commit date
render.php 2 weeks ago slideshow.php 2 weeks ago
render.php
429 lines
1 <?php
2 /**
3 * Slideshow block render implementation.
4 *
5 * Loaded lazily from slideshow.php only when the block is rendered, to keep the
6 * render body out of the eager front-end PHP/opcache footprint.
7 *
8 * @package automattic/jetpack
9 */
10
11 namespace Automattic\Jetpack\Extensions\Slideshow;
12
13 use Automattic\Jetpack\Blocks;
14 use Jetpack_Gutenberg;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit( 0 );
18 }
19
20 /**
21 * Slideshow block render.
22 *
23 * @param array $attr Array containing the slideshow block attributes.
24 * @param string $content String containing the slideshow block content.
25 *
26 * @return string
27 */
28 function render( $attr, $content ) {
29 Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
30 if ( Blocks::is_amp_request() ) {
31 return render_amp( $attr );
32 }
33
34 // Enqueue Swiper bundle for dynamic loading
35 if ( ! is_admin() && ! Blocks::is_amp_request() ) {
36 enqueue_swiper_library();
37 }
38
39 // Fix for lazy loading conflict with slideshow images.
40 if ( ! is_admin() ) {
41 add_filter(
42 'wp_content_img_tag',
43 function ( $image ) {
44 if ( str_contains( $image, 'loading="lazy"' ) && str_contains( $image, 'wp-block-jetpack-slideshow_image' ) ) {
45 $image = str_replace( 'sizes="auto, ', 'sizes="', $image );
46 }
47 return $image;
48 }
49 );
50 }
51
52 return $content;
53 }
54
55 /**
56 * Render slideshow block for email.
57 *
58 * @param string $block_content The original block HTML content.
59 * @param array $parsed_block The parsed block data including attributes.
60 * @param object $rendering_context Email rendering context.
61 *
62 * @return string
63 */
64 function render_email_implementation( $block_content, array $parsed_block, $rendering_context ) {
65 // Validate input parameters and required dependencies
66 if ( ! isset( $parsed_block['attrs'] ) || ! is_array( $parsed_block['attrs'] ) ||
67 ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Gallery' ) ) {
68 return '';
69 }
70
71 $attributes = $parsed_block['attrs'];
72
73 // Process images for email rendering
74 $images = process_slideshow_images_for_email( $attributes );
75
76 if ( empty( $images ) ) {
77 return '';
78 }
79
80 // Build innerBlocks that WooCommerce's gallery renderer expects
81 // The renderer looks for innerBlocks with blockName 'core/image' and innerHTML
82 $inner_blocks = array();
83 foreach ( $images as $image ) {
84 // Build image HTML in the format that extract_image_from_html() can parse
85 $img_html = sprintf(
86 '<img src="%s" alt="%s"',
87 esc_url( $image['url'] ),
88 esc_attr( $image['alt'] )
89 );
90
91 if ( ! empty( $image['id'] ) ) {
92 $img_html .= sprintf( ' class="wp-image-%d"', absint( $image['id'] ) );
93 }
94
95 $img_html .= ' />';
96
97 // Add caption if available (extract_image_from_html looks for figcaption)
98 // Preserve HTML in captions - the gallery renderer will sanitize it
99 if ( ! empty( $image['caption'] ) ) {
100 $img_html .= sprintf(
101 '<figcaption>%s</figcaption>',
102 wp_kses_post( $image['caption'] )
103 );
104 }
105
106 // Create inner block structure that the gallery renderer expects
107 // The renderer only uses innerHTML, not attrs
108 $inner_blocks[] = array(
109 'blockName' => 'core/image',
110 'innerHTML' => $img_html,
111 );
112 }
113
114 // Build block content HTML for gallery caption extraction (if needed)
115 // The renderer uses $block_content parameter to extract gallery-level captions
116 $block_content_html = '<figure class="wp-block-gallery has-nested-images columns-default is-cropped"><ul class="blocks-gallery-grid"></ul></figure>';
117
118 // Create a mock parsed block that WooCommerce's gallery renderer can handle
119 // The renderer uses columns from attrs (defaults to 3, but 2 works better for email)
120 $mock_parsed_block = array(
121 'innerBlocks' => $inner_blocks,
122 'attrs' => array(
123 'columns' => 2,
124 ),
125 );
126
127 // Preserve email_attrs if present (used for width calculation)
128 if ( ! empty( $parsed_block['email_attrs'] ) ) {
129 $mock_parsed_block['email_attrs'] = $parsed_block['email_attrs'];
130 }
131
132 // Use WooCommerce's core gallery renderer
133 $woo_gallery_renderer = new \Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Gallery();
134
135 return $woo_gallery_renderer->render( $block_content_html, $mock_parsed_block, $rendering_context );
136 }
137
138 /**
139 * Render slideshow block for AMP
140 *
141 * @param array $attr Array containing the slideshow block attributes.
142 *
143 * @return string
144 */
145 function render_amp( $attr ) {
146 if ( empty( $attr['ids'] ) ) {
147 return '';
148 }
149
150 static $wp_block_jetpack_slideshow_id = 0;
151 ++$wp_block_jetpack_slideshow_id;
152
153 $ids = $attr['ids'];
154 $autoplay = ! empty( $attr['autoplay'] );
155 $extras = array(
156 'wp-amp-block',
157 $autoplay ? 'wp-block-jetpack-slideshow__autoplay' : null,
158 $autoplay ? 'wp-block-jetpack-slideshow__autoplay-playing' : null,
159 );
160 $classes = Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attr, $extras );
161
162 return sprintf(
163 '<div class="%1$s" id="wp-block-jetpack-slideshow__%2$d"><div class="wp-block-jetpack-slideshow_container swiper">%3$s%4$s%5$s</div></div>',
164 esc_attr( $classes ),
165 absint( $wp_block_jetpack_slideshow_id ),
166 amp_carousel( $attr, $wp_block_jetpack_slideshow_id ),
167 $autoplay ? autoplay_ui( $wp_block_jetpack_slideshow_id ) : '',
168 render_paginator( $ids, $wp_block_jetpack_slideshow_id )
169 );
170 }
171
172 /**
173 * Generate amp-carousel markup
174 *
175 * @param array $attr Array of block attributes.
176 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
177 *
178 * @return string amp-carousel markup.
179 */
180 function amp_carousel( $attr, $block_ordinal ) {
181 $ids = empty( $attr['ids'] ) ? array() : $attr['ids'];
182 $first_image = wp_get_attachment_metadata( $ids[0] );
183 $delay = empty( $attr['delay'] ) ? 3 : absint( $attr['delay'] );
184 $autoplay = empty( $attr['autoplay'] ) ? false : $attr['autoplay'];
185 $width = empty( $first_image['width'] ) ? 800 : $first_image['width'];
186 $height = empty( $first_image['height'] ) ? 600 : $first_image['height'];
187 return sprintf(
188 '<amp-carousel width="%1$d" height="%2$d" layout="responsive" type="slides" data-next-button-aria-label="%3$s" data-prev-button-aria-label="%4$s" controls loop %5$s id="wp-block-jetpack-slideshow__amp-carousel__%6$s" on="slideChange:wp-block-jetpack-slideshow__amp-pagination__%6$s.toggle(index=event.index, value=true)">%7$s</amp-carousel>',
189 esc_attr( $width ),
190 esc_attr( $height ),
191 esc_attr__( 'Next Slide', 'jetpack' ),
192 esc_attr__( 'Previous Slide', 'jetpack' ),
193 $autoplay ? 'autoplay delay=' . esc_attr( $delay * 1000 ) : '',
194 absint( $block_ordinal ),
195 implode( '', slides( $ids, $width, $height ) )
196 );
197 }
198
199 /**
200 * Generate array of slides markup
201 *
202 * @param array $ids Array of image ids.
203 * @param int $width Width of the container.
204 * @param int $height Height of the container.
205 *
206 * @return array Array of slides markup.
207 */
208 function slides( $ids = array(), $width = 400, $height = 300 ) {
209 return array_map(
210 function ( $id ) use ( $width, $height ) {
211 $caption = wp_get_attachment_caption( $id );
212 $figcaption = $caption ? sprintf(
213 '<figcaption class="wp-block-jetpack-slideshow_caption gallery-caption">%s</figcaption>',
214 wp_kses_post( $caption )
215 ) : '';
216 $image = wp_get_attachment_image(
217 $id,
218 array( $width, $height ),
219 false,
220 array(
221 'class' => 'wp-block-jetpack-slideshow_image',
222 'object-fit' => 'contain',
223 )
224 );
225 return sprintf(
226 '<div class="wp-block-jetpack-slideshow_slide"><figure>%s%s</figure></div>',
227 $image,
228 $figcaption
229 );
230 },
231 $ids
232 );
233 }
234
235 /**
236 * Render blocks paginator section
237 *
238 * @param array $ids Array of image ids.
239 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
240 *
241 * @return string Bullets or count markup.
242 */
243 function render_paginator( $ids = array(), $block_ordinal = 0 ) {
244 $total = count( $ids );
245
246 if ( $total < 6 ) {
247 return bullets( $ids, $block_ordinal );
248 }
249
250 return sprintf(
251 '<div class="swiper-pagination-simple">%s / %s</div>',
252 absint( $block_ordinal ),
253 absint( $total )
254 );
255 }
256
257 /**
258 * Generate array of bullets markup
259 *
260 * @param array $ids Array of image ids.
261 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
262 *
263 * @return string Bullets markup.
264 */
265 function bullets( $ids = array(), $block_ordinal = 0 ) {
266 $buttons = array_map(
267 function ( $index ) {
268 $aria_label = sprintf(
269 /* translators: %d: Slide number. */
270 __( 'Go to slide %d', 'jetpack' ),
271 absint( $index + 1 )
272 );
273 return sprintf(
274 '<button option="%d" class="swiper-pagination-bullet" tabindex="0" role="button" aria-label="%s" %s></button>',
275 absint( $index ),
276 esc_attr( $aria_label ),
277 0 === $index ? 'selected' : ''
278 );
279 },
280 array_keys( $ids )
281 );
282
283 return sprintf(
284 '<amp-selector id="wp-block-jetpack-slideshow__amp-pagination__%1$d" class="wp-block-jetpack-slideshow_pagination swiper-pagination swiper-pagination-custom amp-pagination" on="select:wp-block-jetpack-slideshow__amp-carousel__%1$d.goToSlide(index=event.targetOption)" layout="container">%2$s</amp-selector>',
285 absint( $block_ordinal ),
286 implode( '', $buttons )
287 );
288 }
289
290 /**
291 * Generate autoplay play/pause UI.
292 *
293 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
294 *
295 * @return string Autoplay UI markup.
296 */
297 function autoplay_ui( $block_ordinal = 0 ) {
298 $block_id = sprintf(
299 'wp-block-jetpack-slideshow__%d',
300 absint( $block_ordinal )
301 );
302 $amp_carousel_id = sprintf(
303 'wp-block-jetpack-slideshow__amp-carousel__%d',
304 absint( $block_ordinal )
305 );
306 $autoplay_pause = sprintf(
307 '<a aria-label="%s" class="wp-block-jetpack-slideshow_button-pause" role="button" on="tap:%s.toggleAutoplay(toggleOn=false),%s.toggleClass(class=wp-block-jetpack-slideshow__autoplay-playing,force=false)"></a>',
308 esc_attr__( 'Pause Slideshow', 'jetpack' ),
309 esc_attr( $amp_carousel_id ),
310 esc_attr( $block_id )
311 );
312 $autoplay_play = sprintf(
313 '<a aria-label="%s" class="wp-block-jetpack-slideshow_button-play" role="button" on="tap:%s.toggleAutoplay(toggleOn=true),%s.toggleClass(class=wp-block-jetpack-slideshow__autoplay-playing,force=true)"></a>',
314 esc_attr__( 'Play Slideshow', 'jetpack' ),
315 esc_attr( $amp_carousel_id ),
316 esc_attr( $block_id )
317 );
318 return $autoplay_pause . $autoplay_play;
319 }
320
321 /**
322 * Enqueue Swiper library assets for dynamic loading.
323 *
324 * @return void
325 */
326 function enqueue_swiper_library() {
327 $swiper_js_path = Jetpack_Gutenberg::get_blocks_directory() . 'swiper.js';
328 $swiper_css_path = Jetpack_Gutenberg::get_blocks_directory() . 'swiper' . ( is_rtl() ? '.rtl' : '' ) . '.css';
329
330 if ( Jetpack_Gutenberg::block_has_asset( $swiper_js_path ) ) {
331 wp_enqueue_script(
332 'jetpack-swiper-library',
333 plugins_url( $swiper_js_path, JETPACK__PLUGIN_FILE ),
334 array(),
335 JETPACK__VERSION,
336 true
337 );
338 }
339
340 if ( Jetpack_Gutenberg::block_has_asset( $swiper_css_path ) ) {
341 wp_enqueue_style(
342 'jetpack-swiper-library',
343 plugins_url( $swiper_css_path, JETPACK__PLUGIN_FILE ),
344 array(),
345 JETPACK__VERSION
346 );
347 }
348 }
349
350 /**
351 * Process slideshow images for email rendering.
352 *
353 * @param array $attr Block attributes containing image data.
354 * @return array Processed image data for email rendering.
355 */
356 function process_slideshow_images_for_email( $attr ) {
357 $images = array();
358
359 // Get images from IDs (primary data source)
360 if ( ! empty( $attr['ids'] ) && is_array( $attr['ids'] ) ) {
361 foreach ( $attr['ids'] as $index => $id ) {
362 // Validate ID is a positive integer
363 $id = absint( $id );
364 if ( ! $id ) {
365 continue;
366 }
367
368 $image_url = wp_get_attachment_image_url( $id, 'medium' );
369
370 // Sanitize alt text from post meta
371 $alt_text = get_post_meta( $id, '_wp_attachment_image_alt', true );
372 $alt_text = sanitize_text_field( $alt_text );
373
374 // Get caption from attachment post (stored in post_excerpt)
375 $attachment_post = get_post( $id );
376 $caption = '';
377
378 // First try to get caption from images array if available (with validation)
379 // Preserve HTML in captions - the gallery renderer will sanitize it
380 if ( ! empty( $attr['images'] ) && is_array( $attr['images'] ) && isset( $attr['images'][ $index ]['caption'] ) ) {
381 $caption = wp_kses_post( $attr['images'][ $index ]['caption'] );
382 }
383
384 // If no caption in images array, get it from attachment post
385 if ( empty( $caption ) && $attachment_post && ! empty( $attachment_post->post_excerpt ) ) {
386 $caption = wp_kses_post( $attachment_post->post_excerpt );
387 }
388
389 if ( $image_url ) {
390 $images[] = array(
391 'url' => $image_url,
392 'alt' => $alt_text,
393 'caption' => $caption,
394 'id' => $id,
395 );
396 }
397 }
398 } elseif ( ! empty( $attr['images'] ) && is_array( $attr['images'] ) ) {
399 // Fall back to images array if IDs aren't available (for edge cases/testing)
400 foreach ( $attr['images'] as $image_data ) {
401 if ( ! empty( $image_data['url'] ) ) {
402 // Sanitize URL - esc_url_raw returns empty string for invalid URLs
403 $url = esc_url_raw( $image_data['url'] );
404 if ( ! $url ) {
405 continue;
406 }
407
408 // Sanitize alt text
409 $alt_text = ! empty( $image_data['alt'] ) ? sanitize_text_field( $image_data['alt'] ) : '';
410
411 // Preserve HTML in captions - the gallery renderer will sanitize it
412 $caption = ! empty( $image_data['caption'] ) ? wp_kses_post( $image_data['caption'] ) : '';
413
414 // Validate ID if present
415 $id = ! empty( $image_data['id'] ) ? absint( $image_data['id'] ) : 0;
416
417 $images[] = array(
418 'url' => $url,
419 'alt' => $alt_text,
420 'caption' => $caption,
421 'id' => $id,
422 );
423 }
424 }
425 }
426
427 return $images;
428 }
429