PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.5.1
Jetpack – WP Security, Backup, Speed, & Growth v15.5.1
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 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 / slideshow.php
jetpack / extensions / blocks / slideshow Last commit date
slideshow.php 7 months ago
slideshow.php
446 lines
1 <?php
2 /**
3 * Slideshow Block.
4 *
5 * @since 7.1.0
6 *
7 * @package automattic/jetpack
8 */
9
10 namespace Automattic\Jetpack\Extensions\Slideshow;
11
12 use Automattic\Jetpack\Blocks;
13 use Jetpack_Gutenberg;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit( 0 );
17 }
18
19 /**
20 * Registers the block for use in Gutenberg
21 * This is done via an action so that we can disable
22 * registration if we need to.
23 */
24 function register_block() {
25 Blocks::jetpack_register_block(
26 __DIR__,
27 array(
28 'render_callback' => __NAMESPACE__ . '\load_assets',
29 'render_email_callback' => __NAMESPACE__ . '\render_email',
30 )
31 );
32 }
33 add_action( 'init', __NAMESPACE__ . '\register_block' );
34
35 /**
36 * Slideshow block registration/dependency declaration.
37 *
38 * @param array $attr Array containing the slideshow block attributes.
39 * @param string $content String containing the slideshow block content.
40 *
41 * @return string
42 */
43 function load_assets( $attr, $content ) {
44 Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
45 if ( Blocks::is_amp_request() ) {
46 return render_amp( $attr );
47 }
48
49 // Enqueue Swiper bundle for dynamic loading
50 if ( ! is_admin() && ! Blocks::is_amp_request() ) {
51 enqueue_swiper_library();
52 }
53
54 // Fix for lazy loading conflict with slideshow images.
55 if ( ! is_admin() ) {
56 add_filter(
57 'wp_content_img_tag',
58 function ( $image ) {
59 if ( str_contains( $image, 'loading="lazy"' ) && str_contains( $image, 'wp-block-jetpack-slideshow_image' ) ) {
60 $image = str_replace( 'sizes="auto, ', 'sizes="', $image );
61 }
62 return $image;
63 }
64 );
65 }
66
67 return $content;
68 }
69
70 /**
71 * Render slideshow block for email.
72 *
73 * @since 15.0
74 *
75 * @param string $block_content The original block HTML content.
76 * @param array $parsed_block The parsed block data including attributes.
77 * @param object $rendering_context Email rendering context.
78 *
79 * @return string
80 */
81 function render_email( $block_content, array $parsed_block, $rendering_context ) {
82 // Validate input parameters and required dependencies
83 if ( ! isset( $parsed_block['attrs'] ) || ! is_array( $parsed_block['attrs'] ) ||
84 ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Gallery' ) ) {
85 return '';
86 }
87
88 $attributes = $parsed_block['attrs'];
89
90 // Process images for email rendering
91 $images = process_slideshow_images_for_email( $attributes );
92
93 if ( empty( $images ) ) {
94 return '';
95 }
96
97 // Build innerBlocks that WooCommerce's gallery renderer expects
98 // The renderer looks for innerBlocks with blockName 'core/image' and innerHTML
99 $inner_blocks = array();
100 foreach ( $images as $image ) {
101 // Build image HTML in the format that extract_image_from_html() can parse
102 $img_html = sprintf(
103 '<img src="%s" alt="%s"',
104 esc_url( $image['url'] ),
105 esc_attr( $image['alt'] )
106 );
107
108 if ( ! empty( $image['id'] ) ) {
109 $img_html .= sprintf( ' class="wp-image-%d"', absint( $image['id'] ) );
110 }
111
112 $img_html .= ' />';
113
114 // Add caption if available (extract_image_from_html looks for figcaption)
115 // Preserve HTML in captions - the gallery renderer will sanitize it
116 if ( ! empty( $image['caption'] ) ) {
117 $img_html .= sprintf(
118 '<figcaption>%s</figcaption>',
119 wp_kses_post( $image['caption'] )
120 );
121 }
122
123 // Create inner block structure that the gallery renderer expects
124 // The renderer only uses innerHTML, not attrs
125 $inner_blocks[] = array(
126 'blockName' => 'core/image',
127 'innerHTML' => $img_html,
128 );
129 }
130
131 // Build block content HTML for gallery caption extraction (if needed)
132 // The renderer uses $block_content parameter to extract gallery-level captions
133 $block_content_html = '<figure class="wp-block-gallery has-nested-images columns-default is-cropped"><ul class="blocks-gallery-grid"></ul></figure>';
134
135 // Create a mock parsed block that WooCommerce's gallery renderer can handle
136 // The renderer uses columns from attrs (defaults to 3, but 2 works better for email)
137 $mock_parsed_block = array(
138 'innerBlocks' => $inner_blocks,
139 'attrs' => array(
140 'columns' => 2,
141 ),
142 );
143
144 // Preserve email_attrs if present (used for width calculation)
145 if ( ! empty( $parsed_block['email_attrs'] ) ) {
146 $mock_parsed_block['email_attrs'] = $parsed_block['email_attrs'];
147 }
148
149 // Use WooCommerce's core gallery renderer
150 $woo_gallery_renderer = new \Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Gallery();
151
152 return $woo_gallery_renderer->render( $block_content_html, $mock_parsed_block, $rendering_context );
153 }
154
155 /**
156 * Render slideshow block for AMP
157 *
158 * @param array $attr Array containing the slideshow block attributes.
159 *
160 * @return string
161 */
162 function render_amp( $attr ) {
163 if ( empty( $attr['ids'] ) ) {
164 return '';
165 }
166
167 static $wp_block_jetpack_slideshow_id = 0;
168 ++$wp_block_jetpack_slideshow_id;
169
170 $ids = $attr['ids'];
171 $autoplay = empty( $attr['autoplay'] ) ? false : true;
172 $extras = array(
173 'wp-amp-block',
174 $autoplay ? 'wp-block-jetpack-slideshow__autoplay' : null,
175 $autoplay ? 'wp-block-jetpack-slideshow__autoplay-playing' : null,
176 );
177 $classes = Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attr, $extras );
178
179 return sprintf(
180 '<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>',
181 esc_attr( $classes ),
182 absint( $wp_block_jetpack_slideshow_id ),
183 amp_carousel( $attr, $wp_block_jetpack_slideshow_id ),
184 $autoplay ? autoplay_ui( $wp_block_jetpack_slideshow_id ) : '',
185 render_paginator( $ids, $wp_block_jetpack_slideshow_id )
186 );
187 }
188
189 /**
190 * Generate amp-carousel markup
191 *
192 * @param array $attr Array of block attributes.
193 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
194 *
195 * @return string amp-carousel markup.
196 */
197 function amp_carousel( $attr, $block_ordinal ) {
198 $ids = empty( $attr['ids'] ) ? array() : $attr['ids'];
199 $first_image = wp_get_attachment_metadata( $ids[0] );
200 $delay = empty( $attr['delay'] ) ? 3 : absint( $attr['delay'] );
201 $autoplay = empty( $attr['autoplay'] ) ? false : $attr['autoplay'];
202 $width = empty( $first_image['width'] ) ? 800 : $first_image['width'];
203 $height = empty( $first_image['height'] ) ? 600 : $first_image['height'];
204 return sprintf(
205 '<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>',
206 esc_attr( $width ),
207 esc_attr( $height ),
208 esc_attr__( 'Next Slide', 'jetpack' ),
209 esc_attr__( 'Previous Slide', 'jetpack' ),
210 $autoplay ? 'autoplay delay=' . esc_attr( $delay * 1000 ) : '',
211 absint( $block_ordinal ),
212 implode( '', slides( $ids, $width, $height ) )
213 );
214 }
215
216 /**
217 * Generate array of slides markup
218 *
219 * @param array $ids Array of image ids.
220 * @param int $width Width of the container.
221 * @param int $height Height of the container.
222 *
223 * @return array Array of slides markup.
224 */
225 function slides( $ids = array(), $width = 400, $height = 300 ) {
226 return array_map(
227 function ( $id ) use ( $width, $height ) {
228 $caption = wp_get_attachment_caption( $id );
229 $figcaption = $caption ? sprintf(
230 '<figcaption class="wp-block-jetpack-slideshow_caption gallery-caption">%s</figcaption>',
231 wp_kses_post( $caption )
232 ) : '';
233 $image = wp_get_attachment_image(
234 $id,
235 array( $width, $height ),
236 false,
237 array(
238 'class' => 'wp-block-jetpack-slideshow_image',
239 'object-fit' => 'contain',
240 )
241 );
242 return sprintf(
243 '<div class="wp-block-jetpack-slideshow_slide"><figure>%s%s</figure></div>',
244 $image,
245 $figcaption
246 );
247 },
248 $ids
249 );
250 }
251
252 /**
253 * Render blocks paginator section
254 *
255 * @param array $ids Array of image ids.
256 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
257 *
258 * @return array Array of bullets markup.
259 */
260 function render_paginator( $ids = array(), $block_ordinal = 0 ) {
261 $total = count( $ids );
262
263 if ( $total < 6 ) {
264 return bullets( $ids, $block_ordinal );
265 }
266
267 return sprintf(
268 '<div class="swiper-pagination-simple">%s / %s</div>',
269 absint( $block_ordinal ),
270 absint( $total )
271 );
272 }
273
274 /**
275 * Generate array of bullets markup
276 *
277 * @param array $ids Array of image ids.
278 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
279 *
280 * @return array Array of bullets markup.
281 */
282 function bullets( $ids = array(), $block_ordinal = 0 ) {
283 $buttons = array_map(
284 function ( $index ) {
285 $aria_label = sprintf(
286 /* translators: %d: Slide number. */
287 __( 'Go to slide %d', 'jetpack' ),
288 absint( $index + 1 )
289 );
290 return sprintf(
291 '<button option="%d" class="swiper-pagination-bullet" tabindex="0" role="button" aria-label="%s" %s></button>',
292 absint( $index ),
293 esc_attr( $aria_label ),
294 0 === $index ? 'selected' : ''
295 );
296 },
297 array_keys( $ids )
298 );
299
300 return sprintf(
301 '<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>',
302 absint( $block_ordinal ),
303 implode( '', $buttons )
304 );
305 }
306
307 /**
308 * Generate autoplay play/pause UI.
309 *
310 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
311 *
312 * @return string Autoplay UI markup.
313 */
314 function autoplay_ui( $block_ordinal = 0 ) {
315 $block_id = sprintf(
316 'wp-block-jetpack-slideshow__%d',
317 absint( $block_ordinal )
318 );
319 $amp_carousel_id = sprintf(
320 'wp-block-jetpack-slideshow__amp-carousel__%d',
321 absint( $block_ordinal )
322 );
323 $autoplay_pause = sprintf(
324 '<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>',
325 esc_attr__( 'Pause Slideshow', 'jetpack' ),
326 esc_attr( $amp_carousel_id ),
327 esc_attr( $block_id )
328 );
329 $autoplay_play = sprintf(
330 '<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>',
331 esc_attr__( 'Play Slideshow', 'jetpack' ),
332 esc_attr( $amp_carousel_id ),
333 esc_attr( $block_id )
334 );
335 return $autoplay_pause . $autoplay_play;
336 }
337
338 /**
339 * Enqueue Swiper library assets for dynamic loading.
340 *
341 * @return void
342 */
343 function enqueue_swiper_library() {
344 $swiper_js_path = Jetpack_Gutenberg::get_blocks_directory() . 'swiper.js';
345 $swiper_css_path = Jetpack_Gutenberg::get_blocks_directory() . 'swiper' . ( is_rtl() ? '.rtl' : '' ) . '.css';
346
347 if ( Jetpack_Gutenberg::block_has_asset( $swiper_js_path ) ) {
348 wp_enqueue_script(
349 'jetpack-swiper-library',
350 plugins_url( $swiper_js_path, JETPACK__PLUGIN_FILE ),
351 array(),
352 JETPACK__VERSION,
353 true
354 );
355 }
356
357 if ( Jetpack_Gutenberg::block_has_asset( $swiper_css_path ) ) {
358 wp_enqueue_style(
359 'jetpack-swiper-library',
360 plugins_url( $swiper_css_path, JETPACK__PLUGIN_FILE ),
361 array(),
362 JETPACK__VERSION
363 );
364 }
365 }
366
367 /**
368 * Process slideshow images for email rendering.
369 *
370 * @param array $attr Block attributes containing image data.
371 * @return array Processed image data for email rendering.
372 */
373 function process_slideshow_images_for_email( $attr ) {
374 $images = array();
375
376 // Get images from IDs (primary data source)
377 if ( ! empty( $attr['ids'] ) && is_array( $attr['ids'] ) ) {
378 foreach ( $attr['ids'] as $index => $id ) {
379 // Validate ID is a positive integer
380 $id = absint( $id );
381 if ( ! $id ) {
382 continue;
383 }
384
385 $image_url = wp_get_attachment_image_url( $id, 'medium' );
386
387 // Sanitize alt text from post meta
388 $alt_text = get_post_meta( $id, '_wp_attachment_image_alt', true );
389 $alt_text = sanitize_text_field( $alt_text );
390
391 // Get caption from attachment post (stored in post_excerpt)
392 $attachment_post = get_post( $id );
393 $caption = '';
394
395 // First try to get caption from images array if available (with validation)
396 // Preserve HTML in captions - the gallery renderer will sanitize it
397 if ( ! empty( $attr['images'] ) && is_array( $attr['images'] ) && isset( $attr['images'][ $index ]['caption'] ) ) {
398 $caption = wp_kses_post( $attr['images'][ $index ]['caption'] );
399 }
400
401 // If no caption in images array, get it from attachment post
402 if ( empty( $caption ) && $attachment_post && ! empty( $attachment_post->post_excerpt ) ) {
403 $caption = wp_kses_post( $attachment_post->post_excerpt );
404 }
405
406 if ( $image_url ) {
407 $images[] = array(
408 'url' => $image_url,
409 'alt' => $alt_text,
410 'caption' => $caption,
411 'id' => $id,
412 );
413 }
414 }
415 } elseif ( ! empty( $attr['images'] ) && is_array( $attr['images'] ) ) {
416 // Fall back to images array if IDs aren't available (for edge cases/testing)
417 foreach ( $attr['images'] as $image_data ) {
418 if ( ! empty( $image_data['url'] ) ) {
419 // Sanitize URL - esc_url_raw returns empty string for invalid URLs
420 $url = esc_url_raw( $image_data['url'] );
421 if ( ! $url ) {
422 continue;
423 }
424
425 // Sanitize alt text
426 $alt_text = ! empty( $image_data['alt'] ) ? sanitize_text_field( $image_data['alt'] ) : '';
427
428 // Preserve HTML in captions - the gallery renderer will sanitize it
429 $caption = ! empty( $image_data['caption'] ) ? wp_kses_post( $image_data['caption'] ) : '';
430
431 // Validate ID if present
432 $id = ! empty( $image_data['id'] ) ? absint( $image_data['id'] ) : 0;
433
434 $images[] = array(
435 'url' => $url,
436 'alt' => $alt_text,
437 'caption' => $caption,
438 'id' => $id,
439 );
440 }
441 }
442 }
443
444 return $images;
445 }
446