PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.3.2
Jetpack – WP Security, Backup, Speed, & Growth v15.3.2
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 9 months ago
slideshow.php
478 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\Utils\Styles_Helper' ) ||
85 ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper' ) ) {
86 return '';
87 }
88
89 // Email rendering configuration - only extract values used multiple times
90 $email_grid_padding_margin = 20; // Total padding/margin space for grid layout
91 $email_common_margin = 16; // Common margin/padding value used multiple times
92 $email_cell_padding = 8; // Cell padding used in multiple places
93
94 $attr = $parsed_block['attrs'];
95
96 // Process images for email rendering
97 $images = process_slideshow_images_for_email( $attr );
98
99 if ( empty( $images ) ) {
100 return '';
101 }
102
103 // Determine target width from the email layout if available
104 $target_width = get_email_target_width( $rendering_context );
105
106 // Build grid content
107 $grid_content = '';
108 $images_per_row = 2; // Two images per row for better email compatibility
109 $image_width = floor( ( $target_width - $email_grid_padding_margin ) / $images_per_row ); // Account for padding/margins
110
111 // Create rows
112 $image_chunks = array_chunk( $images, $images_per_row );
113
114 foreach ( $image_chunks as $row_images ) {
115 $grid_content .= '<table role="presentation" style="width: 100%; border-collapse: collapse; margin: 0 0 ' . $email_common_margin . 'px 0; table-layout: fixed;"><tr>';
116
117 foreach ( $row_images as $image ) {
118 $grid_content .= sprintf(
119 '<td style="width: %dpx; padding: %dpx; vertical-align: top; text-align: center; font-family: Arial, sans-serif;">',
120 $image_width,
121 $email_cell_padding
122 );
123
124 // Build individual image content
125 $grid_content .= sprintf(
126 '<img src="%s" alt="%s" style="width: 100%%; max-width: %dpx; height: auto; display: block; border: 0; margin: 0 auto; border-radius: 4px;" />',
127 esc_url( $image['url'] ),
128 esc_attr( $image['alt'] ),
129 $image_width - $email_common_margin // Account for padding
130 );
131
132 // Add caption if available
133 if ( ! empty( $image['caption'] ) ) {
134 $grid_content .= sprintf(
135 '<p style="margin: 12px 0 0 0; padding: 0; font-size: 14px; color: #666666; line-height: 1.4; text-align: center; font-family: Arial, sans-serif;">%s</p>',
136 esc_html( wp_strip_all_tags( $image['caption'] ) )
137 );
138 }
139
140 $grid_content .= '</td>';
141 }
142
143 // Fill remaining cells if odd number of images in last row
144 $remaining_cells = $images_per_row - count( $row_images );
145 for ( $i = 0; $i < $remaining_cells; $i++ ) {
146 $grid_content .= sprintf( '<td style="width: %dpx; padding: %dpx;"></td>', $image_width, $email_cell_padding );
147 }
148
149 $grid_content .= '</tr></table>';
150 }
151
152 // Use Table_Wrapper_Helper for consistent email rendering
153 $image_table_attrs = array(
154 'style' => 'margin: ' . $email_common_margin . 'px 0; padding: 0; border-collapse: collapse;',
155 'width' => $target_width,
156 );
157
158 $html = \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper::render_table_wrapper( $grid_content, $image_table_attrs );
159
160 // Add margin below the block
161 $html .= '<div style="margin-bottom: 2em;"></div>';
162
163 return $html;
164 }
165
166 /**
167 * Render slideshow block for AMP
168 *
169 * @param array $attr Array containing the slideshow block attributes.
170 *
171 * @return string
172 */
173 function render_amp( $attr ) {
174 if ( empty( $attr['ids'] ) ) {
175 return '';
176 }
177
178 static $wp_block_jetpack_slideshow_id = 0;
179 ++$wp_block_jetpack_slideshow_id;
180
181 $ids = $attr['ids'];
182 $autoplay = empty( $attr['autoplay'] ) ? false : true;
183 $extras = array(
184 'wp-amp-block',
185 $autoplay ? 'wp-block-jetpack-slideshow__autoplay' : null,
186 $autoplay ? 'wp-block-jetpack-slideshow__autoplay-playing' : null,
187 );
188 $classes = Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attr, $extras );
189
190 return sprintf(
191 '<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>',
192 esc_attr( $classes ),
193 absint( $wp_block_jetpack_slideshow_id ),
194 amp_carousel( $attr, $wp_block_jetpack_slideshow_id ),
195 $autoplay ? autoplay_ui( $wp_block_jetpack_slideshow_id ) : '',
196 render_paginator( $ids, $wp_block_jetpack_slideshow_id )
197 );
198 }
199
200 /**
201 * Generate amp-carousel markup
202 *
203 * @param array $attr Array of block attributes.
204 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
205 *
206 * @return string amp-carousel markup.
207 */
208 function amp_carousel( $attr, $block_ordinal ) {
209 $ids = empty( $attr['ids'] ) ? array() : $attr['ids'];
210 $first_image = wp_get_attachment_metadata( $ids[0] );
211 $delay = empty( $attr['delay'] ) ? 3 : absint( $attr['delay'] );
212 $autoplay = empty( $attr['autoplay'] ) ? false : $attr['autoplay'];
213 $width = empty( $first_image['width'] ) ? 800 : $first_image['width'];
214 $height = empty( $first_image['height'] ) ? 600 : $first_image['height'];
215 return sprintf(
216 '<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>',
217 esc_attr( $width ),
218 esc_attr( $height ),
219 esc_attr__( 'Next Slide', 'jetpack' ),
220 esc_attr__( 'Previous Slide', 'jetpack' ),
221 $autoplay ? 'autoplay delay=' . esc_attr( $delay * 1000 ) : '',
222 absint( $block_ordinal ),
223 implode( '', slides( $ids, $width, $height ) )
224 );
225 }
226
227 /**
228 * Generate array of slides markup
229 *
230 * @param array $ids Array of image ids.
231 * @param int $width Width of the container.
232 * @param int $height Height of the container.
233 *
234 * @return array Array of slides markup.
235 */
236 function slides( $ids = array(), $width = 400, $height = 300 ) {
237 return array_map(
238 function ( $id ) use ( $width, $height ) {
239 $caption = wp_get_attachment_caption( $id );
240 $figcaption = $caption ? sprintf(
241 '<figcaption class="wp-block-jetpack-slideshow_caption gallery-caption">%s</figcaption>',
242 wp_kses_post( $caption )
243 ) : '';
244 $image = wp_get_attachment_image(
245 $id,
246 array( $width, $height ),
247 false,
248 array(
249 'class' => 'wp-block-jetpack-slideshow_image',
250 'object-fit' => 'contain',
251 )
252 );
253 return sprintf(
254 '<div class="wp-block-jetpack-slideshow_slide"><figure>%s%s</figure></div>',
255 $image,
256 $figcaption
257 );
258 },
259 $ids
260 );
261 }
262
263 /**
264 * Render blocks paginator section
265 *
266 * @param array $ids Array of image ids.
267 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
268 *
269 * @return array Array of bullets markup.
270 */
271 function render_paginator( $ids = array(), $block_ordinal = 0 ) {
272 $total = count( $ids );
273
274 if ( $total < 6 ) {
275 return bullets( $ids, $block_ordinal );
276 }
277
278 return sprintf(
279 '<div class="swiper-pagination-simple">%s / %s</div>',
280 absint( $block_ordinal ),
281 absint( $total )
282 );
283 }
284
285 /**
286 * Generate array of bullets markup
287 *
288 * @param array $ids Array of image ids.
289 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
290 *
291 * @return array Array of bullets markup.
292 */
293 function bullets( $ids = array(), $block_ordinal = 0 ) {
294 $buttons = array_map(
295 function ( $index ) {
296 $aria_label = sprintf(
297 /* translators: %d: Slide number. */
298 __( 'Go to slide %d', 'jetpack' ),
299 absint( $index + 1 )
300 );
301 return sprintf(
302 '<button option="%d" class="swiper-pagination-bullet" tabindex="0" role="button" aria-label="%s" %s></button>',
303 absint( $index ),
304 esc_attr( $aria_label ),
305 0 === $index ? 'selected' : ''
306 );
307 },
308 array_keys( $ids )
309 );
310
311 return sprintf(
312 '<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>',
313 absint( $block_ordinal ),
314 implode( '', $buttons )
315 );
316 }
317
318 /**
319 * Generate autoplay play/pause UI.
320 *
321 * @param int $block_ordinal The ordinal number of the block, used in unique ID.
322 *
323 * @return string Autoplay UI markup.
324 */
325 function autoplay_ui( $block_ordinal = 0 ) {
326 $block_id = sprintf(
327 'wp-block-jetpack-slideshow__%d',
328 absint( $block_ordinal )
329 );
330 $amp_carousel_id = sprintf(
331 'wp-block-jetpack-slideshow__amp-carousel__%d',
332 absint( $block_ordinal )
333 );
334 $autoplay_pause = sprintf(
335 '<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>',
336 esc_attr__( 'Pause Slideshow', 'jetpack' ),
337 esc_attr( $amp_carousel_id ),
338 esc_attr( $block_id )
339 );
340 $autoplay_play = sprintf(
341 '<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>',
342 esc_attr__( 'Play Slideshow', 'jetpack' ),
343 esc_attr( $amp_carousel_id ),
344 esc_attr( $block_id )
345 );
346 return $autoplay_pause . $autoplay_play;
347 }
348
349 /**
350 * Enqueue Swiper library assets for dynamic loading.
351 *
352 * @return void
353 */
354 function enqueue_swiper_library() {
355 $swiper_js_path = Jetpack_Gutenberg::get_blocks_directory() . 'swiper.js';
356 $swiper_css_path = Jetpack_Gutenberg::get_blocks_directory() . 'swiper' . ( is_rtl() ? '.rtl' : '' ) . '.css';
357
358 if ( Jetpack_Gutenberg::block_has_asset( $swiper_js_path ) ) {
359 wp_enqueue_script(
360 'jetpack-swiper-library',
361 plugins_url( $swiper_js_path, JETPACK__PLUGIN_FILE ),
362 array(),
363 JETPACK__VERSION,
364 true
365 );
366 }
367
368 if ( Jetpack_Gutenberg::block_has_asset( $swiper_css_path ) ) {
369 wp_enqueue_style(
370 'jetpack-swiper-library',
371 plugins_url( $swiper_css_path, JETPACK__PLUGIN_FILE ),
372 array(),
373 JETPACK__VERSION
374 );
375 }
376 }
377
378 /**
379 * Get target width for email rendering.
380 *
381 * @param object $rendering_context Email rendering context.
382 * @return int Target width in pixels.
383 */
384 function get_email_target_width( $rendering_context ) {
385 $target_width = 600; // Default
386
387 if ( ! empty( $rendering_context ) && is_object( $rendering_context ) && method_exists( $rendering_context, 'get_layout_width_without_padding' ) ) {
388 $layout_width_px = $rendering_context->get_layout_width_without_padding();
389 if ( is_string( $layout_width_px ) ) {
390 $parsed_width = \Automattic\WooCommerce\EmailEditor\Integrations\Utils\Styles_Helper::parse_value( $layout_width_px );
391 if ( $parsed_width > 0 ) {
392 $target_width = $parsed_width;
393 }
394 }
395 }
396
397 return $target_width;
398 }
399
400 /**
401 * Process slideshow images for email rendering.
402 *
403 * @param array $attr Block attributes containing image data.
404 * @return array Processed image data for email rendering.
405 */
406 function process_slideshow_images_for_email( $attr ) {
407 $images = array();
408
409 // Get images from IDs (primary data source)
410 if ( ! empty( $attr['ids'] ) && is_array( $attr['ids'] ) ) {
411 foreach ( $attr['ids'] as $index => $id ) {
412 // Validate ID is a positive integer
413 $id = absint( $id );
414 if ( ! $id ) {
415 continue;
416 }
417
418 $image_url = wp_get_attachment_image_url( $id, 'medium' );
419
420 // Sanitize alt text from post meta
421 $alt_text = get_post_meta( $id, '_wp_attachment_image_alt', true );
422 $alt_text = sanitize_text_field( $alt_text );
423
424 // Get caption from attachment post (stored in post_excerpt)
425 $attachment_post = get_post( $id );
426 $caption = '';
427
428 // First try to get caption from images array if available (with validation)
429 if ( ! empty( $attr['images'] ) && is_array( $attr['images'] ) && isset( $attr['images'][ $index ]['caption'] ) ) {
430 $caption = wp_strip_all_tags( $attr['images'][ $index ]['caption'] );
431 }
432
433 // If no caption in images array, get it from attachment post
434 if ( empty( $caption ) && $attachment_post && ! empty( $attachment_post->post_excerpt ) ) {
435 $caption = wp_strip_all_tags( $attachment_post->post_excerpt );
436 }
437
438 if ( $image_url ) {
439 $images[] = array(
440 'url' => $image_url,
441 'alt' => $alt_text,
442 'caption' => $caption,
443 'id' => $id,
444 );
445 }
446 }
447 } elseif ( ! empty( $attr['images'] ) && is_array( $attr['images'] ) ) {
448 // Fall back to images array if IDs aren't available (for testing)
449 foreach ( $attr['images'] as $image_data ) {
450 if ( ! empty( $image_data['url'] ) ) {
451 // Validate and sanitize URL
452 $url = esc_url_raw( $image_data['url'] );
453 if ( ! $url || ! wp_http_validate_url( $url ) ) {
454 continue;
455 }
456
457 // Sanitize alt text
458 $alt_text = ! empty( $image_data['alt'] ) ? sanitize_text_field( $image_data['alt'] ) : '';
459
460 // Sanitize caption
461 $caption = ! empty( $image_data['caption'] ) ? wp_strip_all_tags( $image_data['caption'] ) : '';
462
463 // Validate ID if present
464 $id = ! empty( $image_data['id'] ) ? absint( $image_data['id'] ) : 0;
465
466 $images[] = array(
467 'url' => $url,
468 'alt' => $alt_text,
469 'caption' => $caption,
470 'id' => $id,
471 );
472 }
473 }
474 }
475
476 return $images;
477 }
478