PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.1
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.1
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / classes / Webp / Picture / Display.php

Display.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 2.1, at classes/Webp/Picture/Display.php

801 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Imagify\Webp\Picture;
3
4 defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' );
5
6 /**
7 * Display WebP images on the site with <picture> tags.
8 *
9 * @since 1.9
10 * @author Grégory Viguier
11 */
12 class Display {
13 use \Imagify\Traits\InstanceGetterTrait;
14
15 /**
16 * Option value.
17 *
18 * @var string
19 * @since 1.9
20 * @author Grégory Viguier
21 */
22 const OPTION_VALUE = 'picture';
23
24 /**
25 * Filesystem object.
26 *
27 * @var \Imagify_Filesystem
28 * @since 1.9
29 * @access protected
30 * @author Grégory Viguier
31 */
32 protected $filesystem;
33
34 /**
35 * Constructor.
36 *
37 * @since 1.9
38 * @access public
39 * @author Grégory Viguier
40 */
41 public function __construct() {
42 $this->filesystem = \Imagify_Filesystem::get_instance();
43 }
44
45 /**
46 * Init.
47 *
48 * @since 1.9
49 * @access public
50 * @author Grégory Viguier
51 */
52 public function init() {
53 add_action( 'template_redirect', [ $this, 'start_content_process' ], -1000 );
54 add_filter( 'imagify_process_webp_content', [ $this, 'process_content' ] );
55 }
56
57 /** ----------------------------------------------------------------------------------------- */
58 /** ADD <PICTURE> TAGS TO THE PAGE ========================================================== */
59 /** ----------------------------------------------------------------------------------------- */
60
61 /**
62 * Start buffering the page content.
63 *
64 * @since 1.9
65 * @access public
66 * @author Grégory Viguier
67 */
68 public function start_content_process() {
69 if ( ! get_imagify_option( 'display_webp' ) ) {
70 return;
71 }
72
73 if ( self::OPTION_VALUE !== get_imagify_option( 'display_webp_method' ) ) {
74 return;
75 }
76
77 /**
78 * Prevent the replacement of <img> tags into <picture> tags.
79 *
80 * @since 1.9
81 * @author Grégory Viguier
82 *
83 * @param bool $allow True to allow the use of <picture> tags (default). False to prevent their use.
84 */
85 $allow = apply_filters( 'imagify_allow_picture_tags_for_webp', true );
86
87 if ( ! $allow ) {
88 return;
89 }
90
91 ob_start( [ $this, 'maybe_process_buffer' ] );
92 }
93
94 /**
95 * Maybe process the page content.
96 *
97 * @since 1.9
98 * @access public
99 * @author Grégory Viguier
100 *
101 * @param string $buffer The buffer content.
102 * @return string
103 */
104 public function maybe_process_buffer( $buffer ) {
105 if ( ! $this->is_html( $buffer ) ) {
106 return $buffer;
107 }
108
109 if ( strlen( $buffer ) <= 255 ) {
110 // Buffer length must be > 255 (IE does not read pages under 255 c).
111 return $buffer;
112 }
113
114 $buffer = $this->process_content( $buffer );
115
116 /**
117 * Filter the page content after Imagify.
118 *
119 * @since 1.9
120 * @author Grégory Viguier
121 *
122 * @param string $buffer The page content.
123 */
124 $buffer = (string) apply_filters( 'imagify_buffer', $buffer );
125
126 return $buffer;
127 }
128
129 /**
130 * Process the content.
131 *
132 * @since 1.9
133 * @access public
134 * @author Grégory Viguier
135 *
136 * @param string $content The content.
137 * @return string
138 */
139 public function process_content( $content ) {
140 $html_no_picture_tags = $this->remove_picture_tags( $content );
141 $images = $this->get_images( $html_no_picture_tags );
142
143 if ( ! $images ) {
144 return $content;
145 }
146
147 foreach ( $images as $image ) {
148 $tag = $this->build_picture_tag( $image );
149 $content = str_replace( $image['tag'], $tag, $content );
150 }
151
152 return $content;
153 }
154
155 /**
156 * Remove pre-existing <picture> tags.
157 *
158 * We shouldn't replace images already nested inside picture tags
159 * that are already in the page.
160 *
161 * @since 1.10.0
162 *
163 * @param string $html Content of the page.
164 *
165 * @return string HTML content without pre-existing <picture> tags.
166 */
167 private function remove_picture_tags( $html ) {
168 $replace = preg_replace( '#<picture[^>]*>.*?<\/picture\s*>#mis', '', $html );
169
170 if ( null === $replace ) {
171 return $html;
172 }
173
174 return $replace;
175 }
176
177 /** ----------------------------------------------------------------------------------------- */
178 /** BUILD HTML TAGS AND ATTRIBUTES ========================================================== */
179 /** ----------------------------------------------------------------------------------------- */
180
181 /**
182 * Build the <picture> tag to insert.
183 *
184 * @since 1.9
185 * @see $this->process_image()
186 * @access protected
187 * @author Grégory Viguier
188 *
189 * @param array $image An array of data.
190 * @return string A <picture> tag.
191 */
192 protected function build_picture_tag( $image ) {
193 $to_remove = [
194 'alt' => '',
195 'height' => '',
196 'width' => '',
197 'data-lazy-src' => '',
198 'data-src' => '',
199 'src' => '',
200 'data-lazy-srcset' => '',
201 'data-srcset' => '',
202 'srcset' => '',
203 'data-lazy-sizes' => '',
204 'data-sizes' => '',
205 'sizes' => '',
206 ];
207
208 $attributes = array_diff_key( $image['attributes'], $to_remove );
209
210 /**
211 * Filter the attributes to be added to the <picture> tag.
212 *
213 * @since 1.9
214 * @author Grégory Viguier
215 *
216 * @param array $attributes A list of attributes to be added to the <picture> tag.
217 * @param array $data Data built from the originale <img> tag. See $this->process_image().
218 */
219 $attributes = apply_filters( 'imagify_picture_attributes', $attributes, $image );
220
221 /**
222 * Remove Gutenberg specific attributes from picture tag, leave them on img tag.
223 * Optional: $attributes['class'] = 'imagify-webp-cover-wrapper'; for website admin styling ease.
224 */
225 if ( ! empty( $image['attributes']['class'] ) && strpos( $image['attributes']['class'], 'wp-block-cover__image-background' ) !== false ) {
226 unset( $attributes['style'] );
227 unset( $attributes['class'] );
228 unset( $attributes['data-object-fit'] );
229 unset( $attributes['data-object-position'] );
230 }
231
232 $output = '<picture' . $this->build_attributes( $attributes ) . ">\n";
233 /**
234 * Allow to add more <source> tags to the <picture> tag.
235 *
236 * @since 1.9
237 * @author Grégory Viguier
238 *
239 * @param string $more_source_tags Additional <source> tags.
240 * @param array $data Data built from the originale <img> tag. See $this->process_image().
241 */
242 $output .= apply_filters( 'imagify_additional_source_tags', '', $image );
243 $output .= $this->build_source_tag( $image );
244 $output .= $this->build_img_tag( $image );
245 $output .= "</picture>\n";
246
247 return $output;
248 }
249
250 /**
251 * Build the <source> tag to insert in the <picture>.
252 *
253 * @since 1.9
254 * @see $this->process_image()
255 * @access protected
256 * @author Grégory Viguier
257 *
258 * @param array $image An array of data.
259 * @return string A <source> tag.
260 */
261 protected function build_source_tag( $image ) {
262 $srcset_source = ! empty( $image['srcset_attribute'] ) ? $image['srcset_attribute'] : $image['src_attribute'] . 'set';
263 $attributes = [
264 'type' => 'image/webp',
265 $srcset_source => [],
266 ];
267
268 if ( ! empty( $image['srcset'] ) ) {
269 foreach ( $image['srcset'] as $srcset ) {
270 if ( empty( $srcset['webp_url'] ) ) {
271 continue;
272 }
273
274 $attributes[ $srcset_source ][] = $srcset['webp_url'] . ' ' . $srcset['descriptor'];
275 }
276 }
277
278 if ( empty( $attributes[ $srcset_source ] ) ) {
279 $attributes[ $srcset_source ][] = $image['src']['webp_url'];
280 }
281
282 $attributes[ $srcset_source ] = implode( ', ', $attributes[ $srcset_source ] );
283
284 foreach ( [ 'data-lazy-srcset', 'data-srcset', 'srcset' ] as $srcset_attr ) {
285 if ( ! empty( $image['attributes'][ $srcset_attr ] ) && $srcset_attr !== $srcset_source ) {
286 $attributes[ $srcset_attr ] = $image['attributes'][ $srcset_attr ];
287 }
288 }
289
290 if ( 'srcset' !== $srcset_source && empty( $attributes['srcset'] ) && ! empty( $image['attributes']['src'] ) ) {
291 // Lazyload: the "src" attr should contain a placeholder (a data image or a blank.gif ).
292 $attributes['srcset'] = $image['attributes']['src'];
293 }
294
295 foreach ( [ 'data-lazy-sizes', 'data-sizes', 'sizes' ] as $sizes_attr ) {
296 if ( ! empty( $image['attributes'][ $sizes_attr ] ) ) {
297 $attributes[ $sizes_attr ] = $image['attributes'][ $sizes_attr ];
298 }
299 }
300
301 /**
302 * Filter the attributes to be added to the <source> tag.
303 *
304 * @since 1.9
305 * @author Grégory Viguier
306 *
307 * @param array $attributes A list of attributes to be added to the <source> tag.
308 * @param array $data Data built from the original <img> tag. See $this->process_image().
309 */
310 $attributes = apply_filters( 'imagify_picture_source_attributes', $attributes, $image );
311
312 return '<source' . $this->build_attributes( $attributes ) . "/>\n";
313 }
314
315 /**
316 * Build the <img> tag to insert in the <picture>.
317 *
318 * @since 1.9
319 * @see $this->process_image()
320 * @access protected
321 * @author Grégory Viguier
322 *
323 * @param array $image An array of data.
324 * @return string A <img> tag.
325 */
326 protected function build_img_tag( $image ) {
327 /**
328 * Gutenberg fix.
329 * Check for the 'wp-block-cover__image-background' class on the original image, and leave that class and style attributes if found.
330 */
331 if ( ! empty( $image['attributes']['class'] ) && strpos( $image['attributes']['class'], 'wp-block-cover__image-background' ) !== false ) {
332 $to_remove = [
333 'id' => '',
334 'title' => '',
335 ];
336
337 $attributes = array_diff_key( $image['attributes'], $to_remove );
338 } else {
339 $to_remove = [
340 'class' => '',
341 'id' => '',
342 'style' => '',
343 'title' => '',
344 ];
345
346 $attributes = array_diff_key( $image['attributes'], $to_remove );
347 }
348
349 /**
350 * Filter the attributes to be added to the <img> tag.
351 *
352 * @since 1.9
353 * @author Grégory Viguier
354 *
355 * @param array $attributes A list of attributes to be added to the <img> tag.
356 * @param array $data Data built from the originale <img> tag. See $this->process_image().
357 */
358 $attributes = apply_filters( 'imagify_picture_img_attributes', $attributes, $image );
359
360 return '<img' . $this->build_attributes( $attributes ) . "/>\n";
361 }
362
363 /**
364 * Create HTML attributes from an array.
365 *
366 * @since 1.9
367 * @access protected
368 * @author Grégory Viguier
369 *
370 * @param array $attributes A list of attribute pairs.
371 * @return string HTML attributes.
372 */
373 protected function build_attributes( $attributes ) {
374 if ( ! $attributes || ! is_array( $attributes ) ) {
375 return '';
376 }
377
378 $out = '';
379
380 foreach ( $attributes as $attribute => $value ) {
381 $out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"';
382 }
383
384 return $out;
385 }
386
387 /** ----------------------------------------------------------------------------------------- */
388 /** VARIOUS TOOLS =========================================================================== */
389 /** ----------------------------------------------------------------------------------------- */
390
391 /**
392 * Get a list of images in a content.
393 *
394 * @since 1.9
395 * @access protected
396 * @author Grégory Viguier
397 *
398 * @param string $content The content.
399 * @return array
400 */
401 protected function get_images( $content ) {
402 // Remove comments.
403 $content = preg_replace( '/<!--(.*)-->/Uis', '', $content );
404
405 if ( ! preg_match_all( '/<img\s.*>/isU', $content, $matches ) ) {
406 return [];
407 }
408
409 $images = array_map( [ $this, 'process_image' ], $matches[0] );
410 $images = array_filter( $images );
411
412 /**
413 * Filter the images to display with a <picture> tag.
414 *
415 * @since 1.9
416 * @see $this->process_image()
417 * @author Grégory Viguier
418 *
419 * @param array $images A list of arrays.
420 * @param string $content The page content.
421 */
422 $images = apply_filters( 'imagify_webp_picture_images_to_display', $images, $content );
423
424 if ( ! $images || ! is_array( $images ) ) {
425 return [];
426 }
427
428 foreach ( $images as $i => $image ) {
429 if ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) {
430 unset( $images[ $i ] );
431 continue;
432 }
433
434 unset( $images[ $i ]['src']['webp_path'], $images[ $i ]['src']['webp_exists'] );
435
436 if ( empty( $image['srcset'] ) || ! is_array( $image['srcset'] ) ) {
437 unset( $images[ $i ]['srcset'] );
438 continue;
439 }
440
441 foreach ( $image['srcset'] as $j => $srcset ) {
442 if ( ! is_array( $srcset ) ) {
443 continue;
444 }
445
446 if ( empty( $srcset['webp_exists'] ) || empty( $srcset['webp_url'] ) ) {
447 unset( $images[ $i ]['srcset'][ $j ]['webp_url'] );
448 }
449
450 unset( $images[ $i ]['srcset'][ $j ]['webp_path'], $images[ $i ]['srcset'][ $j ]['webp_exists'] );
451 }
452 }
453
454 return $images;
455 }
456
457 /**
458 * Process an image tag and get an array containing some data.
459 *
460 * @since 1.9
461 * @access protected
462 * @author Grégory Viguier
463 *
464 * @param string $image An image html tag.
465 * @return array|false {
466 * An array of data if the image has a WebP version. False otherwise.
467 *
468 * @type string $tag The image tag.
469 * @type array $attributes The image attributes (minus src and srcset).
470 * @type array $src {
471 * @type string $url URL to the original image.
472 * @type string $webp_url URL to the WebP version.
473 * }
474 * @type array $srcset {
475 * An array or arrays. Not set if not applicable.
476 *
477 * @type string $url URL to the original image.
478 * @type string $webp_url URL to the WebP version. Not set if not applicable.
479 * @type string $descriptor A src descriptor.
480 * }
481 * }
482 */
483 protected function process_image( $image ) {
484 static $extensions;
485
486 $atts_pattern = '/(?<name>[^\s"\']+)\s*=\s*(["\'])\s*(?<value>.*?)\s*\2/s';
487
488 if ( ! preg_match_all( $atts_pattern, $image, $tmp_attributes, PREG_SET_ORDER ) ) {
489 // No attributes?
490 return false;
491 }
492
493 $attributes = [];
494
495 foreach ( $tmp_attributes as $attribute ) {
496 $attributes[ $attribute['name'] ] = $attribute['value'];
497 }
498
499 if ( ! empty( $attributes['class'] ) && strpos( $attributes['class'], 'imagify-no-webp' ) !== false ) {
500 // Has the 'imagify-no-webp' class.
501 return false;
502 }
503
504 // Deal with the src attribute.
505 $src_source = false;
506
507 foreach ( [ 'data-lazy-src', 'data-src', 'src' ] as $src_attr ) {
508 if ( ! empty( $attributes[ $src_attr ] ) ) {
509 $src_source = $src_attr;
510 break;
511 }
512 }
513
514 if ( ! $src_source ) {
515 // No src attribute.
516 return false;
517 }
518
519 if ( ! isset( $extensions ) ) {
520 $extensions = imagify_get_mime_types( 'image' );
521 $extensions = array_keys( $extensions );
522 $extensions = implode( '|', $extensions );
523 }
524
525 if ( ! preg_match( '@^(?<src>(?:(?:https?:)?//|/).+\.(?<extension>' . $extensions . '))(?<query>\?.*)?$@i', $attributes[ $src_source ], $src ) ) {
526 // Not a supported image format.
527 return false;
528 }
529
530 $webp_url = imagify_path_to_webp( $src['src'] );
531 $webp_path = $this->url_to_path( $webp_url );
532 $webp_url .= ! empty( $src['query'] ) ? $src['query'] : '';
533
534 $data = [
535 'tag' => $image,
536 'attributes' => $attributes,
537 'src_attribute' => $src_source,
538 'src' => [
539 'url' => $attributes[ $src_source ],
540 'webp_url' => $webp_url,
541 'webp_path' => $webp_path,
542 'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ),
543 ],
544 'srcset_attribute' => false,
545 'srcset' => [],
546 ];
547
548 // Deal with the srcset attribute.
549 $srcset_source = false;
550
551 foreach ( [ 'data-lazy-srcset', 'data-srcset', 'srcset' ] as $srcset_attr ) {
552 if ( ! empty( $attributes[ $srcset_attr ] ) ) {
553 $srcset_source = $srcset_attr;
554 break;
555 }
556 }
557
558 if ( $srcset_source ) {
559 $data['srcset_attribute'] = $srcset_source;
560
561 $srcset = explode( ',', $attributes[ $srcset_source ] );
562
563 foreach ( $srcset as $srcs ) {
564 $srcs = preg_split( '/\s+/', trim( $srcs ) );
565
566 if ( count( $srcs ) > 2 ) {
567 // Not a good idea to have space characters in file name.
568 $descriptor = array_pop( $srcs );
569 $srcs = [ implode( ' ', $srcs ), $descriptor ];
570 }
571
572 if ( empty( $srcs[1] ) ) {
573 $srcs[1] = '1x';
574 }
575
576 if ( ! preg_match( '@^(?<src>(?:https?:)?//.+\.(?<extension>' . $extensions . '))(?<query>\?.*)?$@i', $srcs[0], $src ) ) {
577 // Not a supported image format.
578 $data['srcset'][] = [
579 'url' => $srcs[0],
580 'descriptor' => $srcs[1],
581 ];
582 continue;
583 }
584
585 $webp_url = imagify_path_to_webp( $src['src'] );
586 $webp_path = $this->url_to_path( $webp_url );
587 $webp_url .= ! empty( $src['query'] ) ? $src['query'] : '';
588
589 $data['srcset'][] = [
590 'url' => $srcs[0],
591 'descriptor' => $srcs[1],
592 'webp_url' => $webp_url,
593 'webp_path' => $webp_path,
594 'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ),
595 ];
596 }
597 }
598
599 /**
600 * Filter a processed image tag.
601 *
602 * @since 1.9
603 * @author Grégory Viguier
604 *
605 * @param array $data An array of data for this image.
606 * @param string $image An image html tag.
607 */
608 $data = apply_filters( 'imagify_webp_picture_process_image', $data, $image );
609
610 if ( ! $data || ! is_array( $data ) ) {
611 return false;
612 }
613
614 if ( ! isset( $data['tag'], $data['attributes'], $data['src_attribute'], $data['src'], $data['srcset_attribute'], $data['srcset'] ) ) {
615 return false;
616 }
617
618 return $data;
619 }
620
621 /**
622 * Tell if a content is HTML.
623 *
624 * @since 1.9
625 * @access protected
626 * @author Grégory Viguier
627 *
628 * @param string $content The content.
629 * @return bool
630 */
631 protected function is_html( $content ) {
632 return preg_match( '/<\/html>/i', $content );
633 }
634
635 /**
636 * Convert a file URL to an absolute path.
637 *
638 * @since 1.9
639 * @access protected
640 * @author Grégory Viguier
641 *
642 * @param string $url A file URL.
643 * @return string|bool The file path. False on failure.
644 */
645 protected function url_to_path( $url ) {
646 static $uploads_url;
647 static $uploads_dir;
648 static $root_url;
649 static $root_dir;
650 static $cdn_url;
651 static $domain_url;
652
653 /**
654 * $url, $uploads_url, $root_url, and $cdn_url are passed through `set_url_scheme()` only to make sure `stripos()` doesn't fail over a stupid http/https difference.
655 */
656 if ( ! isset( $uploads_url ) ) {
657 $uploads_url = set_url_scheme( $this->filesystem->get_upload_baseurl() );
658 $uploads_dir = $this->filesystem->get_upload_basedir( true );
659 $root_url = set_url_scheme( $this->filesystem->get_site_root_url() );
660 $root_dir = $this->filesystem->get_site_root();
661 $cdn_url = $this->get_cdn_source();
662 $cdn_url = $cdn_url['url'] ? set_url_scheme( $cdn_url['url'] ) : false;
663 $domain_url = wp_parse_url( $root_url );
664
665 if ( ! empty( $domain_url['scheme'] ) && ! empty( $domain_url['host'] ) ) {
666 $domain_url = $domain_url['scheme'] . '://' . $domain_url['host'] . '/';
667 } else {
668 $domain_url = false;
669 }
670 }
671
672 // Get the right URL format.
673 if ( $domain_url && strpos( $url, '/' ) === 0 ) {
674 // URL like `/path/to/image.jpg.webp`.
675 $url = $domain_url . ltrim( $url, '/' );
676 }
677
678 $url = set_url_scheme( $url );
679
680 if ( $cdn_url && $domain_url && stripos( $url, $cdn_url ) === 0 ) {
681 // CDN.
682 $url = str_ireplace( $cdn_url, $domain_url, $url );
683 }
684
685 // Return the path.
686 if ( stripos( $url, $uploads_url ) === 0 ) {
687 return str_ireplace( $uploads_url, $uploads_dir, $url );
688 }
689
690 if ( stripos( $url, $root_url ) === 0 ) {
691 return str_ireplace( $root_url, $root_dir, $url );
692 }
693
694 return false;
695 }
696
697 /**
698 * Get the CDN "source".
699 *
700 * @since 1.9.3
701 * @access public
702 * @author Grégory Viguier
703 *
704 * @param string $option_url An URL to use instead of the one stored in the option. It is used only if no constant/filter.
705 * @return array {
706 * @type string $source Where does it come from? Possible values are 'constant', 'filter', or 'option'.
707 * @type string $name Who? Can be a constant name, a plugin name, or an empty string.
708 * @type string $url The CDN URL, with a trailing slash. An empty string if no URL is set.
709 * }
710 */
711 public function get_cdn_source( $option_url = '' ) {
712 if ( defined( 'IMAGIFY_CDN_URL' ) && IMAGIFY_CDN_URL && is_string( IMAGIFY_CDN_URL ) ) {
713 // Use a constant.
714 $source = [
715 'source' => 'constant',
716 'name' => 'IMAGIFY_CDN_URL',
717 'url' => IMAGIFY_CDN_URL,
718 ];
719 } else {
720 // Maybe use a filter.
721 $filter_source = [
722 'name' => null,
723 'url' => null,
724 ];
725
726 /**
727 * Provide a custom CDN source.
728 *
729 * @since 1.9.3
730 * @author Grégory Viguier
731 *
732 * @param array $filter_source {
733 * @type $name string The name of which provides the URL (plugin name, etc).
734 * @type $url string The CDN URL.
735 * }
736 */
737 $filter_source = apply_filters( 'imagify_cdn_source', $filter_source );
738
739 if ( ! empty( $filter_source['url'] ) ) {
740 $source = [
741 'source' => 'filter',
742 'name' => ! empty( $filter_source['name'] ) ? $filter_source['name'] : '',
743 'url' => $filter_source['url'],
744 ];
745 }
746 }
747
748 if ( empty( $source['url'] ) ) {
749 // No constant, no filter: use the option.
750 $source = [
751 'source' => 'option',
752 'name' => '',
753 'url' => $option_url && is_string( $option_url ) ? $option_url : get_imagify_option( 'cdn_url' ),
754 ];
755 }
756
757 if ( empty( $source['url'] ) ) {
758 // Nothing set.
759 return [
760 'source' => 'option',
761 'name' => '',
762 'url' => '',
763 ];
764 }
765
766 $source['url'] = $this->sanitize_cdn_url( $source['url'] );
767
768 if ( empty( $source['url'] ) ) {
769 // Not an URL.
770 return [
771 'source' => 'option',
772 'name' => '',
773 'url' => '',
774 ];
775 }
776
777 return $source;
778 }
779
780 /**
781 * Sanitize the CDN URL value.
782 *
783 * @since 1.9.3
784 * @access public
785 * @author Grégory Viguier
786 *
787 * @param string $url The URL to sanitize.
788 * @return string
789 */
790 public function sanitize_cdn_url( $url ) {
791 $url = sanitize_text_field( $url );
792
793 if ( ! $url || ! preg_match( '@^https?://.+\.[^.]+@i', $url ) ) {
794 // Not an URL.
795 return '';
796 }
797
798 return trailingslashit( $url );
799 }
800 }
801