PluginProbe
Autoptimize / 3.1.7
Autoptimize v3.1.7
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / autoptimizeImages.php

autoptimizeImages.php in Autoptimize 3.1.7, at classes/autoptimizeImages.php

1,618 lines 75.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles optimizing images.
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit;
8 }
9
10 class autoptimizeImages
11 {
12 /**
13 * Options.
14 *
15 * @var array
16 */
17 protected $options = array();
18
19 /**
20 * Singleton instance.
21 *
22 * @var self|null
23 */
24 protected static $instance = null;
25
26 /**
27 * lazyload counter.
28 *
29 * @var int
30 */
31 protected $lazyload_counter = 0;
32
33 public function __construct( array $options = array() )
34 {
35 // If options are not provided, fetch them.
36 if ( empty( $options ) ) {
37 $options = $this->fetch_options();
38 }
39
40 $this->set_options( $options );
41 }
42
43 public function set_options( array $options )
44 {
45 $this->options = $options;
46
47 return $this;
48 }
49
50 public static function fetch_options()
51 {
52 $value = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_settings' );
53 if ( empty( $value ) ) {
54 // Fallback to returning defaults when no stored option exists yet.
55 $value = autoptimizeConfig::get_ao_imgopt_default_options();
56 }
57
58 // get service availability and add it to the options-array.
59 $value['availabilities'] = autoptimizeOptionWrapper::get_option( 'autoptimize_service_availablity' );
60
61 if ( empty( $value['availabilities'] ) || ! is_array( $value['availabilities'] ) ) {
62 $value['availabilities'] = null;
63
64 if ( true === autoptimizeImages::imgopt_active() ) {
65 $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
66 }
67
68 if ( null === $value['availabilities'] ) {
69 // We can't seem to check service availability, use mock result with imgopt status UP.
70 $_mock_settings = array(
71 'extra_imgopt' => array(
72 'status' => 'up',
73 'hosts' => array(
74 '1' => 'https://sp-ao.shortpixel.ai/',
75 ),
76 ),
77 'critcss' => array(
78 'status' => 'up',
79 ),
80 );
81 $value['availabilities'] = $_mock_settings;
82 }
83 }
84
85 return $value;
86 }
87
88 public static function imgopt_active()
89 {
90 // function to quickly check if imgopt is active, used below but also in
91 // autoptimizeMain.php to start ob_ even if no HTML, JS or CSS optimizing is done
92 // and does not use/ request the availablity data (which could slow things down).
93 static $imgopt_active = null;
94
95 if ( null === $imgopt_active ) {
96 $opts = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_settings', '' );
97 if ( ! empty( $opts ) && is_array( $opts ) && array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $opts ) && ! empty( $opts['autoptimize_imgopt_checkbox_field_1'] ) && '1' === $opts['autoptimize_imgopt_checkbox_field_1'] ) {
98 $imgopt_active = true;
99 } else {
100 $imgopt_active = false;
101 }
102 }
103
104 return $imgopt_active;
105 }
106
107 /**
108 * Helper for getting a singleton instance. While being an
109 * anti-pattern generally, it comes in handy for now from a
110 * readability/maintainability perspective, until we get some
111 * proper dependency injection going.
112 *
113 * @return self
114 */
115 public static function instance()
116 {
117 if ( null === self::$instance ) {
118 self::$instance = new self();
119 }
120
121 return self::$instance;
122 }
123
124 public function run()
125 {
126 if ( is_admin() ) {
127 if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
128 add_action( 'network_admin_menu', array( $this, 'imgopt_admin_menu' ) );
129 } else {
130 add_action( 'admin_menu', array( $this, 'imgopt_admin_menu' ) );
131 }
132 add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_imgopt_tab' ), 9 );
133 } else {
134 add_action( 'wp', array( $this, 'run_on_frontend' ) );
135 }
136 }
137
138 public function run_on_frontend() {
139 if ( ! $this->should_run() ) {
140 if ( $this->should_lazyload() ) {
141 add_filter(
142 'wp_lazy_loading_enabled',
143 array( $this, 'should_disable_core_lazyload' ),
144 10,
145 3
146 );
147 add_filter(
148 'autoptimize_html_after_minify',
149 array( $this, 'filter_lazyload_images' ),
150 10,
151 1
152 );
153 add_action(
154 'wp_footer',
155 array( $this, 'add_lazyload_js_footer' ),
156 10,
157 0
158 );
159 }
160 return;
161 }
162
163 $active = false;
164
165 if ( apply_filters( 'autoptimize_filter_imgopt_do', true ) ) {
166 add_filter(
167 'autoptimize_html_after_minify',
168 array( $this, 'filter_optimize_images' ),
169 10,
170 1
171 );
172 $active = true;
173 }
174
175 if ( apply_filters( 'autoptimize_filter_imgopt_do_css', true ) ) {
176 // fixme: also act on already minified CSS!
177 add_filter(
178 'autoptimize_filter_base_replace_cdn',
179 array( $this, 'filter_optimize_css_images' ),
180 10,
181 1
182 );
183
184 add_filter(
185 'autoptimize_html_after_minify',
186 array( $this, 'filter_optimize_inline_css_images' ),
187 10,
188 1
189 );
190
191 $active = true;
192 }
193
194 if ( $active ) {
195 add_filter(
196 'autoptimize_extra_filter_tobepreconn',
197 array( $this, 'filter_preconnect_imgopt_url' ),
198 10,
199 1
200 );
201 }
202
203 if ( $this->should_lazyload() ) {
204 add_filter(
205 'wp_lazy_loading_enabled',
206 array( $this, 'should_disable_core_lazyload' ),
207 10,
208 3
209 );
210 add_action(
211 'wp_footer',
212 array( $this, 'add_lazyload_js_footer' ),
213 10,
214 0
215 );
216 }
217 }
218
219 /**
220 * Disables core's native lazyload for images, not for iframes.
221 *
222 * @param bool $flag Incoming flag (mostly true).
223 * @param string $tag Tag (img or iframe).
224 * @param string $context Full context.
225 *
226 * @return bool
227 */
228 public function should_disable_core_lazyload( $flag = true, $tag = '', $context = '' ) {
229 if ( 'img' === $tag ) {
230 return false;
231 }
232 return $flag;
233 }
234
235 /**
236 * Basic checks before we can run.
237 *
238 * @return bool
239 */
240 protected function should_run()
241 {
242 $opts = $this->options;
243 $service_not_down = ( 'down' !== $opts['availabilities']['extra_imgopt']['status'] );
244 $not_launch_status = ( 'launch' !== $opts['availabilities']['extra_imgopt']['status'] );
245
246 $do_cdn = true;
247 $_userstatus = $this->get_imgopt_provider_userstatus();
248 if ( isset( $_userstatus['Status'] ) && ( -2 == $_userstatus['Status'] || -3 == $_userstatus['Status'] ) ) {
249 // don't even attempt to put images on CDN if heavily exceeded threshold or if site not reachable.
250 $do_cdn = false;
251 }
252
253 if (
254 $this->imgopt_active()
255 && $do_cdn
256 && $service_not_down
257 && ( $not_launch_status || $this->launch_ok() )
258 ) {
259 return true;
260 }
261 return false;
262 }
263
264 public function get_imgopt_host()
265 {
266 static $imgopt_host = null;
267
268 if ( null === $imgopt_host ) {
269 $imgopt_host = 'https://sp-ao.shortpixel.ai/';
270 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
271 if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
272 $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
273 }
274 $imgopt_host = apply_filters( 'autoptimize_filter_imgopt_host', $imgopt_host );
275 }
276
277 return $imgopt_host;
278 }
279
280 public static function get_imgopt_host_wrapper()
281 {
282 // needed for CI tests.
283 $self = new self();
284 return $self->get_imgopt_host();
285 }
286
287 public static function get_service_url_suffix()
288 {
289 $suffix = '/af/U0ZIWMK109483/' . AUTOPTIMIZE_SITE_DOMAIN;
290
291 return $suffix;
292 }
293
294 public function get_img_quality_string()
295 {
296 static $quality = null;
297
298 if ( null === $quality ) {
299 $q_array = $this->get_img_quality_array();
300 $setting = $this->get_img_quality_setting();
301 $quality = apply_filters(
302 'autoptimize_filter_imgopt_quality',
303 'q_' . $q_array[ $setting ]
304 );
305 }
306
307 return $quality;
308 }
309
310 public function get_img_quality_array()
311 {
312 static $map = null;
313
314 if ( null === $map ) {
315 $map = array(
316 '1' => 'lossy',
317 '2' => 'glossy',
318 '3' => 'lossless',
319 );
320 $map = apply_filters(
321 'autoptimize_filter_imgopt_quality_array',
322 $map
323 );
324 }
325
326 return $map;
327 }
328
329 public function get_img_quality_setting()
330 {
331 static $q = null;
332
333 if ( null === $q ) {
334 if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_select_field_2', $this->options ) ) {
335 $setting = $this->options['autoptimize_imgopt_select_field_2'];
336 }
337
338 if ( ! isset( $setting ) || empty( $setting ) || ( '1' !== $setting && '3' !== $setting ) ) {
339 // default image opt. value is 2 ("glossy").
340 $q = '2';
341 } else {
342 $q = $setting;
343 }
344 }
345
346 return $q;
347 }
348
349 public function filter_preconnect_imgopt_url( array $in )
350 {
351 $url_parts = parse_url( $this->get_imgopt_base_url() );
352 $in[] = $url_parts['scheme'] . '://' . $url_parts['host'];
353
354 return $in;
355 }
356
357 /**
358 * Makes sure given url contains the full scheme and hostname
359 * in case they're not present already.
360 *
361 * @param string $in Image url to normalize.
362 *
363 * @return string
364 */
365 private function normalize_img_url( $in )
366 {
367 // Only parse the site url once.
368 static $parsed_site_url = null;
369 if ( null === $parsed_site_url ) {
370 $parsed_site_url = parse_url( site_url() );
371 }
372
373 // get CDN domain once.
374 static $cdn_domain = null;
375 if ( is_null( $cdn_domain ) ) {
376 $cdn_url = $this->get_cdn_url();
377 if ( ! empty( $cdn_url ) ) {
378 $cdn_domain = parse_url( $cdn_url, PHP_URL_HOST );
379 } else {
380 $cdn_domain = '';
381 }
382 }
383
384 /**
385 * This method gets called a lot, often for identical urls it seems.
386 * `filter_optimize_css_images()` calls us, uses the resulting url and
387 * gives it to `can_optimize_image()`, and if that returns trueish
388 * then `build_imgopt_url()` is called (which, again, calls this method).
389 * Until we dig deeper into whether this all must really happen that
390 * way, having an internal cache here helps (to avoid doing repeated
391 * identical string operations).
392 */
393 static $cache = null;
394 if ( null === $cache ) {
395 $cache = array();
396 }
397
398 // Do the work on cache miss only.
399 if ( ! isset( $cache[ $in ] ) ) {
400 // Default to (the trimmed version of) what was given to us.
401 $result = trim( $in );
402
403 // Some silly plugins wrap background images in html-encoded quotes, so remove those from the img url.
404 $result = $this->fix_silly_bgimg_quotes( $result );
405
406 if ( autoptimizeUtils::is_protocol_relative( $result ) ) {
407 $result = $parsed_site_url['scheme'] . ':' . $result;
408 } elseif ( 0 === strpos( $result, '/' ) ) {
409 // Root-relative...
410 $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'] . $result;
411 } elseif ( ! empty( $cdn_domain ) && false === strpos( $this->get_imgopt_host(), $cdn_domain ) && strpos( $result, $cdn_domain ) !== 0 ) {
412 // remove CDN except if it is the image optimization one.
413 $result = str_replace( $cdn_domain, $parsed_site_url['host'], $result );
414 }
415
416 // filter (default off) to remove QS from image URL's to avoid eating away optimization credits.
417 if ( apply_filters( 'autoptimize_filter_imgopt_no_querystring', false ) && strpos( $result, '?' ) !== false ) {
418 $result = strtok( $result, '?' );
419 }
420
421 $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
422
423 // Store in cache.
424 $cache[ $in ] = $result;
425 }
426
427 return $cache[ $in ];
428 }
429
430 public function filter_optimize_css_images( $in )
431 {
432 $in = $this->normalize_img_url( $in );
433
434 if ( $this->can_optimize_image( $in ) && false === strpos( $in, $this->get_imgopt_host() ) ) {
435 return $this->build_imgopt_url( $in, '', '' );
436 } else {
437 return $in;
438 }
439 }
440
441 public function filter_optimize_inline_css_images( $html ) {
442 preg_match_all( '#<style[^>]*>([^<]*)</style>#Um', $html, $inline_css_blocks, PREG_SET_ORDER );
443 foreach ( $inline_css_blocks as $inline_css_block ) {
444 if ( false !== strpos( $inline_css_block[0], 'background' ) ) {
445 $inline_css_block_new = $this->replace_background_img_css( $inline_css_block[0] );
446 if ( $inline_css_block_new !== $inline_css_block[0] ) {
447 $html = str_replace( $inline_css_block[0], $inline_css_block_new, $html );
448 }
449 }
450 }
451 return $html;
452 }
453
454 public static function replace_background_img_css( $css ) {
455 // fixme; can/ should we cache these?
456 preg_match_all( '#background[^;}]*url\((.*)\)#Ui', $css, $backgrounds, PREG_SET_ORDER );
457 if ( is_array( $backgrounds ) && ! empty( $backgrounds ) ) {
458 foreach ( $backgrounds as $background ) {
459 if ( autoptimizeImages::can_optimize_image_wrapper( $background[1] ) ) {
460 $css = str_replace( $background[1], autoptimizeImages::build_imgopt_url_wrapper( $background[1] ), $css );
461 }
462 }
463 }
464 return $css;
465 }
466
467 private function get_imgopt_base_url()
468 {
469 static $imgopt_base_url = null;
470
471 if ( null === $imgopt_base_url ) {
472 $imgopt_host = $this->get_imgopt_host();
473 $quality = $this->get_img_quality_string();
474 $ret_val = apply_filters( 'autoptimize_filter_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
475 if ( $this->should_ngimg() ) {
476 $sp_to_string = 'to_auto';
477 } else {
478 $sp_to_string = 'to_webp';
479 }
480 $sp_to_string = apply_filters( 'autoptimize_filter_imgopt_format', $sp_to_string ); // values: empty (= jpeg), to_webp (smart; webp or fallback), to_avif (avif or fallback) or to_auto (smart avif, webp or fallback).
481 $imgopt_base_url = $imgopt_host . 'client/' . $sp_to_string . ',' . $quality . ',' . $ret_val;
482 $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
483 }
484
485 return $imgopt_base_url;
486 }
487
488 public static function can_optimize_image_wrapper( $url, $tag = '', $testing = false ) {
489 $self = new self();
490 return $self->can_optimize_image( $url, $tag = '', $testing = false );
491 }
492
493 private function can_optimize_image( $url, $tag = '', $testing = false )
494 {
495 static $cdn_url = null;
496 static $nopti_images = null;
497
498 if ( null === $cdn_url ) {
499 $cdn_url = apply_filters(
500 'autoptimize_filter_base_cdnurl',
501 autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' )
502 );
503 }
504
505 if ( null === $nopti_images || $testing ) {
506 if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_text_field_6', $this->options ) ) {
507 $nopti_images = $this->options['autoptimize_imgopt_text_field_6'];
508 }
509 $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', $nopti_images );
510 }
511
512 $site_host = AUTOPTIMIZE_SITE_DOMAIN;
513 $url = $this->normalize_img_url( $url );
514 $url_parsed = parse_url( $url );
515
516 if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
517 return false;
518 } elseif ( autoptimizeUtils::is_local_server() ) {
519 return false;
520 } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
521 return false;
522 } elseif ( strpos( $url, '.php' ) !== false ) {
523 return false;
524 } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp', '.avif' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
525 // fixme: better check against end of string.
526 return false;
527 } elseif ( ! empty( $nopti_images ) ) {
528 $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
529 foreach ( $nopti_images_array as $nopti_image ) {
530 if ( strpos( $url, $nopti_image ) !== false || ( ( '' !== $tag && strpos( $tag, $nopti_image ) !== false ) ) ) {
531 return false;
532 }
533 }
534 }
535 return true;
536 }
537
538 // wrapper for reuse in AOPro.
539 public static function build_imgopt_url_wrapper( $orig_url, $width = 0, $height = 0 ) {
540 $self = new self();
541 return $self->build_imgopt_url( $orig_url, $width = 0, $height = 0 );
542 }
543
544 private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
545 {
546 // sanitize width and height.
547 if ( strpos( $width, '%' ) !== false ) {
548 $width = 0;
549 }
550 if ( strpos( $height, '%' ) !== false ) {
551 $height = 0;
552 }
553 $width = (int) $width;
554 $height = (int) $height;
555
556 $filtered_url = apply_filters(
557 'autoptimize_filter_imgopt_build_url',
558 $orig_url,
559 $width,
560 $height
561 );
562
563 // If filter modified the url, return that.
564 if ( $filtered_url !== $orig_url ) {
565 return $filtered_url;
566 }
567
568 $normalized_url = $this->normalize_img_url( $orig_url );
569
570 // if the URL is ascii we check if we have a real URL with filter_var (which only works on ascii url's) and if not a real URL we return the original one.
571 if ( apply_filters( 'autoptimize_filter_imgopt_check_normalized_url', true ) && ! preg_match( '/[^\x20-\x7e]/', $normalized_url ) && false === filter_var( $normalized_url, FILTER_VALIDATE_URL ) ) {
572 return $orig_url;
573 }
574
575 $imgopt_base_url = $this->get_imgopt_base_url();
576 $imgopt_size = '';
577
578 if ( $width && 0 !== $width ) {
579 $imgopt_size = ',w_' . $width;
580 }
581
582 if ( $height && 0 !== $height ) {
583 $imgopt_size .= ',h_' . $height;
584 }
585
586 $url = $imgopt_base_url . $imgopt_size . '/' . $normalized_url;
587
588 return $url;
589 }
590
591 public function replace_data_thumbs( $matches )
592 {
593 return $this->replace_img_callback( $matches, 150, 150 );
594 }
595
596 public function replace_img_callback( $matches, $width = 0, $height = 0 )
597 {
598 $_normalized_img_url = $this->normalize_img_url( $matches[1] );
599 if ( $this->can_optimize_image( $matches[1], $matches[0] ) ) {
600 return str_replace( $matches[1], $this->build_imgopt_url( $_normalized_img_url, $width, $height ), $matches[0] );
601 } else {
602 return $matches[0];
603 }
604 }
605
606 public function replace_icon_callback( $matches )
607 {
608 if ( array_key_exists( '2', $matches ) ) {
609 $sizes = explode( 'x', $matches[2] );
610 $width = $sizes[0];
611 $height = $sizes[1];
612 } else {
613 $width = 180;
614 $height = 180;
615 }
616
617 // make sure we're not trying to optimize a *.ico file.
618 if ( strpos( $matches[1], '.ico' ) === false ) {
619 return $this->replace_img_callback( $matches, $width, $height );
620 } else {
621 return $matches[0];
622 }
623 }
624
625 public function filter_optimize_images( $in, $testing = false )
626 {
627 /*
628 * potential future functional improvements:
629 *
630 * filter for critical CSS.
631 */
632 $to_replace = array();
633 $to_preload = '';
634
635 // hide (no)script tags to avoid replacing (and potentially breaking) images in script tags.
636 if ( apply_filters( 'autoptimize_filter_imgopt_hide_script', true ) || $this->should_lazyload() ) {
637 $in = autoptimizeBase::replace_contents_with_marker_if_exists(
638 'SCRIPT',
639 '<script',
640 '#<(?:no)?script.*?<\/(?:no)?script>#is',
641 $in
642 );
643 }
644
645 // get img preloads as set in post metabox, exploding ", " instead of "," because LCP preload
646 // could be a shortpixel URL, which has comma's and results in way too many preloads.
647 $metabox_preloads = array_filter( array_map( 'trim', explode( ', ', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
648 $metabox_preloads = apply_filters( 'autoptimize_filter_images_metabox_preloads', $metabox_preloads );
649
650 // extract img tags.
651 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
652 foreach ( $matches[0] as $tag ) {
653 $tag = apply_filters( 'autoptimize_filter_imgopt_tag_preopt', $tag );
654
655 $orig_tag = $tag;
656 $imgopt_w = '';
657 $imgopt_h = '';
658
659 // first do (data-)srcsets.
660 if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
661 foreach ( $allsrcsets as $srcset ) {
662 $srcset = $srcset[2];
663 $orig_srcset = $srcset;
664 $srcsets = explode( ',', $srcset );
665 foreach ( $srcsets as $indiv_srcset ) {
666 $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
667 if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
668 $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
669 }
670 if ( $this->can_optimize_image( $indiv_srcset_parts[0], $tag, $testing ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
671 $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
672 $srcset = str_replace( $indiv_srcset_parts[0], $imgopt_url, $srcset );
673 }
674 }
675 $tag = str_replace( $orig_srcset, $srcset, $tag );
676 }
677 }
678
679 // proceed with img src.
680 // get width and height and add to $imgopt_size.
681 $_get_size = $this->get_size_from_tag( $tag );
682 $imgopt_w = $_get_size['width'];
683 $imgopt_h = $_get_size['height'];
684
685 // then start replacing images src.
686 if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
687 foreach ( $urls as $url ) {
688 $full_src_orig = $url[0];
689 $url = $url[1];
690 if ( $this->can_optimize_image( $url, $tag, $testing ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
691 $imgopt_url = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
692 $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
693 $tag = str_replace( $full_src_orig, $full_imgopt_src, $tag );
694 }
695 }
696 }
697
698 // check if the image needs to be prelaoded.
699 if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && str_replace( $metabox_preloads, '', $tag ) !== $tag ) {
700 $to_preload .= $this->create_img_preload_tag( $tag );
701 }
702
703 // do lazyload stuff.
704 if ( $this->should_lazyload( $in ) && ! empty( $url ) ) {
705 // first do lpiq placeholder logic.
706 if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
707 // if all img src have been replaced during srcset, we have to extract the
708 // origin url from the imgopt one to be able to set a lqip placeholder.
709 $_url = substr( $url, strpos( $url, '/http' ) + 1 );
710 } else {
711 $_url = $url;
712 }
713
714 $_url = $this->normalize_img_url( $_url );
715
716 $placeholder = '';
717 if ( $this->can_optimize_image( $_url, $tag ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', false, $_url ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
718 $lqip_w = '';
719 $lqip_h = '';
720 if ( isset( $imgopt_w ) && ! empty( $imgopt_w ) ) {
721 $lqip_w = ',w_' . $imgopt_w;
722 }
723 if ( isset( $imgopt_h ) && ! empty( $imgopt_h ) ) {
724 $lqip_h = ',h_' . $imgopt_h;
725 }
726 $placeholder = $this->get_imgopt_host() . 'client/q_lqip,ret_wait' . $lqip_w . $lqip_h . '/' . $_url;
727 }
728 // then call add_lazyload-function with lpiq placeholder if set.
729 $tag = $this->add_lazyload( $tag, $placeholder );
730 }
731
732 // add decoding="async" behind filter, not sure if I'll make it default true yet.
733 if ( true === apply_filters( 'autoptimize_filter_imgopt_add_decoding', true ) && false === strpos( $tag, ' decoding=' ) ) {
734 $tag = str_replace( '<img ', '<img decoding="async" ', $tag );
735 }
736
737 $tag = apply_filters( 'autoptimize_filter_imgopt_tag_postopt', $tag );
738
739 // and add tag to array for later replacement.
740 if ( $tag !== $orig_tag ) {
741 $to_replace[ $orig_tag ] = $tag;
742 }
743 }
744 }
745
746 // and replace all.
747 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
748
749 // misc. node attributes that might hold image url's (incl. the previously separate data-thumb).
750 $extra_attr_with_img = apply_filters( 'autoptimize_filter_imgopt_attr_with_img', array( array( 'div', 'data-thumb'), array( 'div', 'data-background' ), array( 'img', 'data-retina' ) ) );
751 if ( ! empty( $extra_attr_with_img ) && is_array( $extra_attr_with_img ) ) {
752 foreach ( $extra_attr_with_img as $candidate ) {
753 if ( is_array( $candidate ) && strpos( $out, $candidate[1] ) !== false ) {
754 $_regex = '/\<' . $candidate[0] . '(?:[^>]*)?\s' . $candidate[1] . '=(?:"|\')(.+?)(?:"|\')(?:[^>]*)?>/s';
755 $out = preg_replace_callback(
756 $_regex,
757 array( $this, 'replace_img_callback' ),
758 $out
759 );
760 }
761 }
762 }
763
764 // background-image in inline style.
765 if ( strpos( $out, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
766 $out = preg_replace_callback(
767 '/style=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
768 array( $this, 'replace_img_callback' ),
769 $out
770 );
771 }
772
773 // act on icon links.
774 if ( ( strpos( $out, '<link rel="icon"' ) !== false || ( strpos( $out, "<link rel='icon'" ) !== false ) ) && apply_filters( 'autoptimize_filter_imgopt_linkicon', true ) ) {
775 $out = preg_replace_callback(
776 '/<link\srel=(?:"|\')(?:apple-touch-)?icon(?:"|\').*\shref=(?:"|\')(.*)(?:"|\')(?:\ssizes=(?:"|\')(\d*x\d*)(?:"|\'))?\s\/>/Um',
777 array( $this, 'replace_icon_callback' ),
778 $out
779 );
780 }
781
782 // lazyload picture source tags and bgimage.
783 if ( $this->should_lazyload() ) {
784 $out = $this->process_picture_tag( $out, true, true );
785 $out = $this->process_bgimage( $out );
786 } else {
787 $out = $this->process_picture_tag( $out, true, false );
788 }
789
790 // restore (no)script tags.
791 if ( apply_filters( 'autoptimize_filter_imgopt_hide_script', true ) || $this->should_lazyload() ) {
792 $out = autoptimizeBase::restore_marked_content(
793 'SCRIPT',
794 $out
795 );
796 }
797
798 if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
799 // the preload was not in an img tag, so adding a non-responsive preload instead.
800 foreach ( $metabox_preloads as $img_preload ) {
801 $to_preload .= '<link rel="preload" href="' . $img_preload . '" as="image">';
802 }
803 }
804
805 if ( ! empty( $to_preload ) ) {
806 $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
807 }
808
809 return $out;
810 }
811
812 public static function get_size_from_tag( $tag ) {
813 // reusable function to extract widht and height from an image tag
814 // enforcing a filterable maximum width and height (default 4999X4999).
815 $width = '';
816 $height = '';
817
818 if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $_width ) ) {
819 if ( strpos( $_width[2], '%' ) === false ) {
820 $width = (int) $_width[2];
821 }
822 }
823 if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $_height ) ) {
824 if ( strpos( $_height[2], '%' ) === false ) {
825 $height = (int) $_height[2];
826 }
827 }
828
829 // check for and enforce (filterable) max sizes.
830 $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
831 if ( $width > $_max_width ) {
832 $_width = $_max_width;
833 if ( ! empty( $height ) && is_int( $height ) ) {
834 $height = $_width / $width * $height;
835 }
836 $width = $_width;
837 }
838 $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
839 if ( $height > $_max_height ) {
840 $_height = $_max_height;
841 if ( ! empty( $width ) && is_int( $width ) ) {
842 $width = $_height / $height * $width;
843 }
844 $height = $_height;
845 }
846
847 return array(
848 'width' => $width,
849 'height' => $height,
850 );
851 }
852
853 /**
854 * Lazyload functions
855 */
856 public static function should_lazyload_wrapper( $no_meta = false ) {
857 // needed in autoptimizeMain.php.
858 $self = new self();
859 return $self->should_lazyload( '', $no_meta );
860 }
861
862 public function should_lazyload( $context = '', $no_meta = false ) {
863 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && false === $this->check_nolazy() ) {
864 $lazyload_return = true;
865 } else {
866 $lazyload_return = false;
867 }
868
869 // If page/ post check post_meta to see if lazyload is off for page.
870 if ( false === $no_meta && false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_lazyload' ) ) {
871 $lazyload_return = false;
872 }
873
874 $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return, $context );
875
876 return $lazyload_return;
877 }
878
879 public static function check_nolazy() {
880 if ( array_key_exists( 'ao_nolazy', $_GET ) && '1' === $_GET['ao_nolazy'] ) {
881 return true;
882 } else {
883 return false;
884 }
885 }
886
887 public function filter_lazyload_images( $in )
888 {
889 // only used is image optimization is NOT active but lazyload is.
890 $to_replace = array();
891 $to_preload = '';
892
893 // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
894 $out = autoptimizeBase::replace_contents_with_marker_if_exists(
895 'SCRIPT',
896 '<script',
897 '#<(?:no)?script.*?<\/(?:no)?script>#is',
898 $in
899 );
900
901 // get img preloads as set in post metabox.
902 $metabox_preloads = array_filter( array_map( 'trim', explode( ',', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
903
904 // extract img tags and add lazyload attribs/ add preloads.
905 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
906 foreach ( $matches[0] as $tag ) {
907 // check if image needs to be preloaded.
908 if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && str_replace( $metabox_preloads, '', $tag ) !== $tag ) {
909 $to_preload .= $this->create_img_preload_tag( $tag );
910 }
911
912 // and lazyloaded.
913 if ( $this->should_lazyload( $out ) ) {
914 $to_replace[ $tag ] = $this->add_lazyload( $tag );
915 }
916 }
917 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $out );
918 }
919
920 // and also lazyload picture tag.
921 $out = $this->process_picture_tag( $out, false, true );
922
923 // and inline style blocks with background-image.
924 $out = $this->process_bgimage( $out );
925
926 // restore noscript tags.
927 $out = autoptimizeBase::restore_marked_content(
928 'SCRIPT',
929 $out
930 );
931
932 if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
933 // the preload was not in an img tag, so adding a non-responsive preload instead.
934 foreach ( $metabox_preloads as $img_preload ) {
935 $to_preload .= '<link rel="preload" href="' . $img_preload . '" as="image">';
936 }
937 }
938
939 if ( ! empty( $to_preload ) ) {
940 $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
941 }
942
943 return $out;
944 }
945
946 public function add_lazyload( $tag, $placeholder = '' ) {
947 // adds actual lazyload-attributes to an image node.
948 $this->lazyload_counter++;
949
950 $_lazyload_from_nth = '';
951 if ( array_key_exists( 'autoptimize_imgopt_number_field_7', $this->options ) ) {
952 $_lazyload_from_nth = $this->options['autoptimize_imgopt_number_field_7'];
953 }
954 $_lazyload_from_nth = apply_filters( 'autoptimize_filter_imgopt_lazyload_from_nth', $_lazyload_from_nth );
955
956 if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag && $this->lazyload_counter >= $_lazyload_from_nth ) {
957 $tag = $this->maybe_fix_missing_quotes( $tag );
958
959 // store original tag for use in noscript version.
960 $noscript_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>';
961
962 $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
963
964 // insert lazyload class.
965 $tag = $this->inject_classes_in_tag( $tag, "$lazyload_class " );
966
967 if ( ! $placeholder || empty( $placeholder ) ) {
968 // get image width & heigth for placeholder fun (and to prevent content reflow).
969 $_get_size = $this->get_size_from_tag( $tag );
970 $width = $_get_size['width'];
971 $height = $_get_size['height'];
972 if ( false === $width || empty( $width ) ) {
973 $width = 210; // default width for SVG placeholder.
974 }
975 if ( false === $height || empty( $height ) ) {
976 $height = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
977 }
978
979 // insert the actual lazyload stuff.
980 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
981 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
982 }
983
984 $tag = preg_replace( '/(\s)src=/', ' src=\'' . $placeholder . '\' data-src=', $tag );
985 $tag = preg_replace( '/(\s)srcset=/', ' data-srcset=', $tag );
986
987 // move sizes to data-sizes unless filter says no.
988 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_move_sizes', true ) ) {
989 $tag = str_replace( ' sizes=', ' data-sizes=', $tag );
990 }
991
992 // add the noscript-tag from earlier.
993 $tag = $noscript_tag . $tag;
994 $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
995 } else {
996 $tag = apply_filters( 'autoptimize_filter_imgopt_not_lazyloaded_img', $tag );
997 }
998
999 return $tag;
1000 }
1001
1002 public function add_lazyload_js_footer() {
1003 if ( false === autoptimizeMain::should_buffer() || autoptimizeMain::is_amp_markup( '' ) ) {
1004 return;
1005 }
1006
1007 // The JS will by default be excluded form autoptimization but this can be changed with a filter.
1008 $noptimize_flag = '';
1009 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
1010 $noptimize_flag = ' data-noptimize="1"';
1011 }
1012
1013 $_extra = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings', '' );
1014 if ( is_array( $_extra ) && array_key_exists( 'autoptimize_extra_checkbox_field_0', $_extra ) && ! empty( $_extra['autoptimize_extra_checkbox_field_0'] ) ) {
1015 // if "remove query strings" is active in "extra", then let's be consistant and not add one ourselves :-) ?
1016 $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js', __FILE__ );
1017 } else {
1018 $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js?ao_version=' . AUTOPTIMIZE_PLUGIN_VERSION, __FILE__ );
1019 }
1020
1021 $cdn_url = $this->get_cdn_url();
1022 if ( ! empty( $cdn_url ) ) {
1023 $cdn_url = rtrim( $cdn_url, '/' );
1024 $lazysizes_js = str_replace( AUTOPTIMIZE_WP_SITE_URL, $cdn_url, $lazysizes_js );
1025 }
1026
1027 $type_js = '';
1028 if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
1029 $type_js = ' type="text/javascript"';
1030 }
1031
1032 // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
1033 echo apply_filters( 'autoptimize_filter_imgopt_lazyload_cssoutput', '<noscript><style>.lazyload{display:none;}</style></noscript>' );
1034 echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $type_js . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
1035 echo apply_filters( 'autoptimize_filter_imgopt_lazyload_js', '<script async' . $type_js . $noptimize_flag . ' src=\'' . $lazysizes_js . '\'></script>' );
1036 }
1037
1038 public static function create_img_preload_tag( $tag ) {
1039 if ( false === apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
1040 return '';
1041 }
1042
1043 // clean up; remove tabs/ linebreaks/ spaces.
1044 $tag = preg_replace( '/\s+/', ' ', $tag );
1045
1046 // remove noscript.
1047 if ( false !== strpos( $tag, '<noscript' ) ) {
1048 $tag = preg_replace( '/<noscript.*<\/noscript>/mU', '', $tag );
1049 }
1050
1051 // rewrite img tag to link preload img.
1052 $_from = array( '<img ', ' src=', ' sizes=', ' srcset=' );
1053 $_to = array( '<link rel="preload" as="image" ', ' href=', ' imagesizes=', ' imagesrcset=' );
1054 $tag = str_replace( $_from, $_to, $tag );
1055
1056 // and remove title, alt, class and id.
1057 $tag = preg_replace( '/ ((?:title|alt|class|id|loading|fetchpriority|decoding|data-no-lazy|width|height)=".*")/Um', '', $tag );
1058 if ( str_replace( array( ' title=', ' class=', ' alt=', ' id=', ' fetchpriority=', ' decoding=', ' data-no-lazy=' ), '', $tag ) !== $tag ) {
1059 // 2nd regex pass if still title/ class/ alt in case single quotes were used iso doubles.
1060 $tag = preg_replace( '/ ((?:title|alt|class|id|loading|fetchpriority|decoding|data-no-lazy)=\'.*\')/Um', '', $tag );
1061 }
1062
1063 return $tag;
1064 }
1065
1066 public static function get_cdn_url() {
1067 // getting CDN url here to avoid having to make bigger changes to autoptimizeBase.
1068 static $cdn_url = null;
1069
1070 if ( null === $cdn_url ) {
1071 $cdn_url = autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' );
1072 $cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $cdn_url );
1073 $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
1074 }
1075
1076 return $cdn_url;
1077 }
1078
1079 public function get_lazyload_exclusions() {
1080 // returns array of strings that if found in an <img tag will stop the img from being lazy-loaded.
1081 static $exclude_lazyload_array = null;
1082
1083 if ( null === $exclude_lazyload_array ) {
1084 $options = $this->options;
1085
1086 // set default exclusions.
1087 $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg', 'loading="eager"' );
1088
1089 // add from setting.
1090 if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
1091 $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
1092 if ( ! empty( $exclude_lazyload_option ) ) {
1093 $exclude_lazyload_array = array_merge( $exclude_lazyload_array, array_filter( array_map( 'trim', explode( ',', $options['autoptimize_imgopt_text_field_5'] ) ) ) );
1094 }
1095 }
1096
1097 // and filter for developer-initiated changes.
1098 $exclude_lazyload_array = apply_filters( 'autoptimize_filter_imgopt_lazyload_exclude_array', $exclude_lazyload_array );
1099 }
1100
1101 return $exclude_lazyload_array;
1102 }
1103
1104 public function inject_classes_in_tag( $tag, $target_class ) {
1105 if ( strpos( $tag, 'class=' ) !== false ) {
1106 $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
1107 } else {
1108 $tag = preg_replace( '/(<[a-zA-Z]*)\s/', '$1 class="' . trim( $target_class ) . '" ', $tag );
1109 }
1110
1111 return $tag;
1112 }
1113
1114 public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
1115 return 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20' . $imgopt_w . '%20' . $imgopt_h . '%22%3E%3C/svg%3E';
1116 }
1117
1118 public function should_ngimg() {
1119 static $ngimg_return = null;
1120
1121 if ( is_null( $ngimg_return ) ) {
1122 // nextgen img only works if imgopt is active.
1123 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && $this->imgopt_active() ) {
1124 $ngimg_return = true;
1125 } else {
1126 $ngimg_return = false;
1127 }
1128 }
1129
1130 return $ngimg_return;
1131 }
1132
1133 public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
1134 // check if "<picture" is present and if filter allows us to process <picture>.
1135 if ( strpos( $in, '<picture' ) === false || apply_filters( 'autoptimize_filter_imgopt_dopicture', true ) === false ) {
1136 return $in;
1137 }
1138
1139 $_exclusions = $this->get_lazyload_exclusions();
1140 $to_replace_pict = array();
1141
1142 // extract and process each picture-node.
1143 preg_match_all( '#<picture.*</picture>#Usmi', $in, $_pictures, PREG_SET_ORDER );
1144 foreach ( $_pictures as $_picture ) {
1145 $_picture = $this->maybe_fix_missing_quotes( $_picture );
1146 if ( strpos( $_picture[0], '<source ' ) !== false && preg_match_all( '#<source .*srcset=(?:"|\')(?!data)(.*)(?:"|\').*>#Usmi', $_picture[0], $_sources, PREG_SET_ORDER ) !== false ) {
1147 foreach ( $_sources as $_source ) {
1148 $_picture_replacement = $_source[0];
1149
1150 // should we optimize the image?
1151 if ( $imgopt && $this->can_optimize_image( $_source[1], $_picture[0] ) ) {
1152 $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
1153 }
1154 // should we lazy-load?
1155 if ( $lazy && $this->should_lazyload() && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
1156 $_picture_replacement = str_replace( ' srcset=', ' data-srcset=', $_picture_replacement );
1157 }
1158 $to_replace_pict[ $_source[0] ] = $_picture_replacement;
1159 }
1160 }
1161 }
1162
1163 // and return the fully procesed $in.
1164 $out = str_replace( array_keys( $to_replace_pict ), array_values( $to_replace_pict ), $in );
1165
1166 return $out;
1167 }
1168
1169 public function process_bgimage( $in ) {
1170 if ( strpos( $in, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_lazyload_backgroundimages', true ) ) {
1171 $out = preg_replace_callback(
1172 '/(<(?:article|aside|body|div|footer|header|p|section|span|table)[^>]*)\sstyle=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)[^>]*/',
1173 array( $this, 'lazyload_bgimg_callback' ),
1174 $in
1175 );
1176 return $out;
1177 }
1178 return $in;
1179 }
1180
1181 public function lazyload_bgimg_callback( $matches ) {
1182 if ( str_ireplace( $this->get_lazyload_exclusions(), '', $matches[0] ) === $matches[0] ) {
1183 // get placeholder & lazyload class strings.
1184 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( 500, 300 ) );
1185 $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
1186 // remove quotes from url() to be able to replace in next step.
1187 $out = str_replace( array( "url('" . $matches[2] . "')", 'url("' . $matches[2] . '")' ), 'url(' . $matches[2] . ')', $matches[0] );
1188 // replace background-image URL with SVG placeholder.
1189 $out = str_replace( 'url(' . $matches[2], 'url(' . $placeholder, $out );
1190 // sanitize bgimg src for quote sillyness.
1191 $bgimg_src = $this->fix_silly_bgimg_quotes( $matches[2] );
1192 // add data-bg attribute with real background-image URL for lazyload to pick up.
1193 $out = str_replace( $matches[1], $matches[1] . ' data-bg="' . $bgimg_src . '"', $out );
1194 // and finally add lazyload class to tag.
1195 $out = $this->inject_classes_in_tag( $out, "$lazyload_class " );
1196 return $out;
1197 }
1198 return $matches[0];
1199 }
1200
1201 public function fix_silly_bgimg_quotes( $tag_in ) {
1202 // some themes/ pagebuilders wrap backgroundimages in HTML-encoded quotes (or linebreaks) which breaks imgopt/ lazyloading, this removes them.
1203 return trim( str_replace( array( "\r\n", '&quot;', '&#034;', '&apos;', '&#039;' ), '', $tag_in ) );
1204 }
1205
1206 public function maybe_fix_missing_quotes( $tag_in ) {
1207 // W3TC's Minify_HTML class removes quotes around attribute value, this re-adds them for the class and width/height attributes so we can lazyload properly.
1208 if ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) {
1209 $tag_out = preg_replace( '/class\s?=([^("|\')]*)(\s|>)/U', 'class=\'$1\'$2', $tag_in );
1210 $tag_out = preg_replace( '/\s(width|height)=(?:"|\')?([^\s"\'>]*)(?:"|\')?/', ' $1=\'$2\'', $tag_out );
1211 return $tag_out;
1212 } else {
1213 return $tag_in;
1214 }
1215 }
1216
1217 /**
1218 * Admin page logic and related functions below.
1219 */
1220 public function imgopt_admin_menu()
1221 {
1222 // no acces if multisite and not network admin and no site config allowed.
1223 if ( autoptimizeConfig::should_show_menu_tabs() ) {
1224 add_submenu_page(
1225 '',
1226 'autoptimize_imgopt',
1227 'autoptimize_imgopt',
1228 'manage_options',
1229 'autoptimize_imgopt',
1230 array( $this, 'imgopt_options_page' )
1231 );
1232 }
1233 register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
1234 }
1235
1236 public function add_imgopt_tab( $in )
1237 {
1238 if ( autoptimizeConfig::should_show_menu_tabs() ) {
1239 $in = array_merge( $in, array( 'autoptimize_imgopt' => apply_filters( 'autoptimize_filter_imgopt_tab_text', __( 'Images', 'autoptimize' ) ) ) );
1240 }
1241
1242 return $in;
1243 }
1244
1245 public function imgopt_options_page()
1246 {
1247 // phpcs:disable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace
1248 // phpcs:disable Generic.Formatting.DisallowMultipleStatements.SameLine
1249
1250 // Check querystring for "refreshCacheChecker" and call cachechecker if so.
1251 if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
1252 $this->query_img_provider_stats( true );
1253 }
1254
1255 $options = $this->fetch_options();
1256 $sp_url_suffix = $this->get_service_url_suffix();
1257 ?>
1258 <style>
1259 #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
1260 #ao_settings_form .form-table th {font-weight: normal;}
1261 #autoptimize_imgopt_descr{font-size: 120%;}
1262 </style>
1263 <script>document.title = "Autoptimize: <?php _e( 'Images', 'autoptimize' ); ?> " + document.title;</script>
1264 <div class="wrap">
1265 <h1><?php apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? _e( 'Autoptimize Pro Settings', 'autoptimize' ) : _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
1266 <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
1267 <?php if ( autoptimizeUtils::is_local_server() ) { ?>
1268 <div class="notice-warning notice"><p>
1269 <?php
1270 echo __( 'The image optimization service does not work on locally hosted sites or when the server is on a private network.', 'autoptimize' );
1271 ?>
1272 </p></div>
1273 <?php } ?>
1274 <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
1275 <div class="notice-warning notice"><p>
1276 <?php
1277 // translators: "Autoptimize support forum" will appear in a "a href".
1278 echo sprintf( __( 'The image optimization service is currently down, image optimization will be skipped until further notice. Check the %1$sAutoptimize support forum%2$s for more info.', 'autoptimize' ), '<a href="https://wordpress.org/support/plugin/autoptimize/" target="_blank">', '</a>' );
1279 ?>
1280 </p></div>
1281 <?php } ?>
1282
1283 <?php if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! autoptimizeImages::instance()->launch_ok() ) { ?>
1284 <div class="notice-warning notice"><p>
1285 <?php _e( 'The image optimization service is launching, but not yet available for this domain, it should become available in the next couple of days.', 'autoptimize' ); ?>
1286 </p></div>
1287 <?php } ?>
1288
1289 <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
1290 <div class="notice-warning notice"><p>
1291 <?php
1292 // translators: "disable Jetpack's site accelerator for images" will appear in a "a href" linking to the jetpack settings page.
1293 echo sprintf( __( 'Please %1$sdisable Jetpack\'s site accelerator for images%2$s to be able to use Autoptomize\'s advanced image optimization features below.', 'autoptimize' ), '<a href="admin.php?page=jetpack#/settings">', '</a>' );
1294 ?>
1295 </p></div>
1296 <?php } ?>
1297 <form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
1298 <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
1299 <h2><?php _e( 'Image optimization', 'autoptimize' ); ?></h2>
1300 <span id='autoptimize_imgopt_descr'><?php echo apply_filters( 'autoptimize_filter_imgopt_intro_copy', __( 'Make your site significantly faster by simply ticking a few boxes and start serving CDN powered, optimized images in next-get formats like WebP and AVIF! No additional plugins or services needed.', 'autoptimize' ) ); ?></span>
1301 <table class="form-table">
1302 <tr>
1303 <th scope="row"><?php _e( 'Image optimization & CDN', 'autoptimize' ); ?></th>
1304 <td>
1305 <label><input id='autoptimize_imgopt_checkbox' type='checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_1]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_1'] ) { echo 'checked="checked"'; } ?> value='1'><?php echo apply_filters( 'autoptimize_filter_imgopt_main_setting_copy', __( 'On-the-fly image optimization and fast delivery via the Shortpixel global CDN.', 'autoptimize' ) ); ?></label>
1306 <?php
1307 // show shortpixel status.
1308 $_notice = autoptimizeImages::instance()->get_imgopt_status_notice();
1309 if ( $_notice ) {
1310 switch ( $_notice['status'] ) {
1311 case 2:
1312 $_notice_color = 'green';
1313 break;
1314 case 1:
1315 $_notice_color = 'orange';
1316 break;
1317 case -1:
1318 case -2:
1319 case -3:
1320 $_notice_color = 'red';
1321 break;
1322 default:
1323 $_notice_color = 'green';
1324 }
1325 echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
1326 } else {
1327 // translators: link points to shortpixel.
1328 $upsell_msg_1 = '<p>' . sprintf( __( 'Get more Google love by speeding up your website. Start serving on-the-fly optimized images (also in the "next-gen" <strong>WebP</strong> and <strong>AVIF</strong> image formats) by %1$sShortPixel%2$s. No additional image optimization plugins are needed: your images are optimized, cached and served from %3$sShortPixel\'s global CDN%2$s.', 'autoptimize' ), '<a href="https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell" target="_blank">', '</a>', '<a href="https://help.shortpixel.com/article/62-where-does-the-cdn-has-pops" target="_blank">' );
1329 if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
1330 $upsell_msg_2 = __( 'For a limited time only, this service is offered free for all Autoptimize users, <b>don\'t miss the chance to test it</b> and see how much it could improve your site\'s speed.', 'autoptimize' );
1331 } else {
1332 // translators: 1st link points to autoptimize.com.pro, 2nd to shortpixel.
1333 $upsell_msg_2 = sprintf( __( 'For <strong>unlimited image optimizations %1$sbuy Autoptimize Pro%2$s</strong> which also includes Critical CSS and extra "booster" options or %3$ssign up at Shortpixel%4$s.', 'autoptimize' ), '<a href="https://autoptimize.com/pro/" target="_blank">', '</a>', '<a href="https://autoptimize.shortpixel.com/' . $sp_url_suffix . '" target="_blank">', '</a>' );
1334 }
1335 echo apply_filters( 'autoptimize_filter_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
1336 }
1337 // translators: link points to shortpixel FAQ.
1338 $faqcopy = sprintf( __( '<strong>Questions</strong>? Take a look at the %1$sAutoptimize + ShortPixel FAQ%2$s!', 'autoptimize' ), '<strong><a href="https://help.shortpixel.com/category/405-autoptimize" target="_blank">', '</strong></a>' );
1339 $faqcopy = $faqcopy . ' ' . __( 'Only works for websites and images that are publicly available.', 'autoptimize' );
1340 // translators: links points to shortpixel TOS & Privacy Policy.
1341 $toscopy = sprintf( __( 'Usage of this feature is subject to Shortpixel\'s %1$sTerms of Use%2$s and %3$sPrivacy policy%4$s.', 'autoptimize' ), '<a href="https://shortpixel.com/tos' . $sp_url_suffix . '" target="_blank">', '</a>', '<a href="https://shortpixel.com/privacy' . $sp_url_suffix . '" target="_blank">', '</a>' );
1342 echo apply_filters( 'autoptimize_filter_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
1343 ?>
1344 </td>
1345 </tr>
1346 <tr id='autoptimize_imgopt_optimization_exclusions' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
1347 <th scope="row"><?php _e( 'Optimization exclusions', 'autoptimize' ); ?></th>
1348 <td>
1349 <label><input type='text' style='width:80%' id='autoptimize_imgopt_optimization_exclusions' name='autoptimize_imgopt_settings[autoptimize_imgopt_text_field_6]' value='<?php if ( ! empty( $options['autoptimize_imgopt_text_field_6'] ) ) { echo esc_attr( $options['autoptimize_imgopt_text_field_6'] ); } ?>'><br /><?php _e( 'Comma-separated list of image classes or filenames that should not be optimized.', 'autoptimize' ); ?></label>
1350 </td>
1351 </tr>
1352 <tr id='autoptimize_imgopt_quality' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
1353 <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
1354 <td>
1355 <label>
1356 <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
1357 <?php
1358 $_imgopt_array = autoptimizeImages::instance()->get_img_quality_array();
1359 $_imgopt_val = autoptimizeImages::instance()->get_img_quality_setting();
1360
1361 foreach ( $_imgopt_array as $key => $value ) {
1362 echo '<option value="' . $key . '"';
1363 if ( $_imgopt_val == $key ) {
1364 echo ' selected';
1365 }
1366 echo '>' . ucfirst( $value ) . '</option>';
1367 }
1368 echo "\n";
1369 ?>
1370 </select>
1371 </label>
1372 <p>
1373 <?php
1374 // translators: link points to shortpixel image test page.
1375 echo apply_filters( 'autoptimize_filter_imgopt_quality_copy', sprintf( __( 'You can %1$stest compression levels here%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/online-image-compression' . $sp_url_suffix . '" target="_blank">', '</a>' ) );
1376 ?>
1377 </p>
1378 </td>
1379 </tr>
1380 <?php
1381 if ( apply_filters( 'autoptimize_filter_imgopt_settings_show_avif', true ) ) {
1382 ?>
1383 <tr id='autoptimize_imgopt_ngimg' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
1384 <th scope="row"><?php _e( 'Load AVIF in supported browsers?', 'autoptimize' ); ?></th>
1385 <td>
1386 <label><input type='checkbox' id='autoptimize_imgopt_ngimg_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_4]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_4'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_4'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Automatically serve AVIF image format to any browser that supports it.', 'autoptimize' ); ?></label>
1387 </td>
1388 </tr>
1389 <?php
1390 } else {
1391 ?>
1392 <input type='hidden' id='autoptimize_imgopt_ngimg_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_4]' value='0'>
1393 <?php
1394 }
1395 ?>
1396 <tr>
1397 <th scope="row"><?php _e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1398 <td>
1399 <label><input type='checkbox' id='autoptimize_imgopt_lazyload_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_3]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_3'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Image lazy-loading will delay the loading of non-visible images to allow the browser to optimally load all resources for the "above the fold"-page first.', 'autoptimize' ); ?></label>
1400 </td>
1401 </tr>
1402 <tr id='autoptimize_imgopt_lazyload_exclusions' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_3', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_3'] ) ) { echo 'class="autoptimize_lazyload_child hidden"'; } else { echo 'class="autoptimize_lazyload_child"'; } ?>>
1403 <th scope="row"><?php _e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
1404 <td>
1405 <label><input type='text' style='width:80%' id='autoptimize_imgopt_lazyload_exclusions_text' name='autoptimize_imgopt_settings[autoptimize_imgopt_text_field_5]' value='<?php if ( ! empty( $options['autoptimize_imgopt_text_field_5'] ) ) { echo esc_attr( $options['autoptimize_imgopt_text_field_5'] ); } ?>'><br /><?php _e( 'Comma-separated list of to be excluded image classes or filenames.', 'autoptimize' ); ?></label>
1406 </td>
1407 </tr>
1408 <tr id='autoptimize_imgopt_lazyload_from_nth_image' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_3', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_3'] ) ) { echo 'class="autoptimize_lazyload_child hidden"'; } else { echo 'class="autoptimize_lazyload_child"'; } ?>>
1409 <th scope="row"><?php _e( 'Lazy-load from nth image', 'autoptimize' ); ?></th>
1410 <td>
1411 <label><input type='number' min='0' max='50' style='width:80%' id='autoptimize_imgopt_lazyload_from_nth_image_number' name='autoptimize_imgopt_settings[autoptimize_imgopt_number_field_7]' value='<?php if ( ! empty( $options['autoptimize_imgopt_number_field_7'] ) ) { echo esc_attr( $options['autoptimize_imgopt_number_field_7'] ); } else { echo '1'; } ?>'><br /><?php _e( 'Don\'t lazyload the first X images, \'1\' lazyloads all.', 'autoptimize' ); ?></label>
1412 </td>
1413 </tr>
1414 </table>
1415 <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1416 </form>
1417 <script>
1418 jQuery(document).ready(function() {
1419 jQuery("#autoptimize_imgopt_checkbox").change(function() {
1420 if (this.checked) {
1421 jQuery("#autoptimize_imgopt_quality").show("slow");
1422 jQuery("#autoptimize_imgopt_ngimg").show("slow");
1423 jQuery("#autoptimize_imgopt_optimization_exclusions").show("slow");
1424 } else {
1425 jQuery("#autoptimize_imgopt_quality").hide("slow");
1426 jQuery("#autoptimize_imgopt_ngimg").hide("slow");
1427 jQuery("#autoptimize_imgopt_optimization_exclusions").hide("slow");
1428 }
1429 });
1430 jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1431 if (this.checked) {
1432 jQuery(".autoptimize_lazyload_child").show("slow");
1433 } else {
1434 jQuery(".autoptimize_lazyload_child").hide("slow");
1435 }
1436 });
1437 });
1438 </script>
1439 <?php
1440 }
1441
1442 /**
1443 * Ïmg opt status as used on dashboard.
1444 */
1445 public function get_imgopt_status_notice() {
1446 if ( $this->imgopt_active() && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1447 $_imgopt_notice = '';
1448 $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1449 $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
1450 $_imgopt_upsell = 'https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell';
1451 $_imgopt_assoc = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1452 $_imgopt_unreach = 'https://shortpixel.helpscoutdocs.com/article/148-why-are-my-images-redirected-from-cdn-shortpixel-ai';
1453
1454 if ( is_array( $_stat ) ) {
1455 if ( 1 == $_stat['Status'] ) {
1456 // translators: "add more credits" will appear in a "a href".
1457 $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota is almost used, make sure you %1$sadd more credits%2$s to avoid slowing down your website <strong>or consider using %3$sAutoptimize Pro%2$s which comes with (nearly) unlimited image optimization</strong> but also automated critical CSS and extra booster options.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>', '<a href="https://autoptimize.com/pro/" target="_blank">' );
1458 } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1459 // translators: "add more credits" will appear in a "a href".
1460 $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota has been exhausted, %1$sadd more credits%2$s to continue to quickly deliver optimized images on your website <strong>or consider using %3$sAutoptimize Pro%2$s which comes with (nearly) unlimited image optimization</strong> but also automated critical CSS and extra booster options.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>', '<a href="https://autoptimize.com/pro/" target="_blank">' );
1461 // translators: "associate your domain" will appear in a "a href".
1462 $_imgopt_notice = $_imgopt_notice . ' ' . sprintf( __( 'If you have enough CDN quota remaining, then you may need to %1$sassociate your domain%2$s to your Shortpixel account.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $_imgopt_assoc . '" target="_blank">', '</a>' );
1463 } elseif ( -3 == $_stat['Status'] ) {
1464 // translators: "check the documentation here" will appear in a "a href".
1465 $_imgopt_notice = sprintf( __( 'It seems ShortPixel image optimization is not able to fetch images from your site, %1$scheck the documentation here%2$s for more information', 'autoptimize' ), '<a href="' . $_imgopt_unreach . '" target="_blank">', '</a>' );
1466 } else {
1467 $_imgopt_upsell = 'https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell';
1468 // translators: "log in to check your account" will appear in a "a href".
1469 $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
1470 }
1471
1472 // add info on freshness + refresh link if status is not 2 (good shape).
1473 if ( 2 != $_stat['Status'] ) {
1474 $_imgopt_stats_refresh_url = add_query_arg(
1475 array(
1476 'page' => 'autoptimize_imgopt',
1477 'refreshImgProvStats' => '1',
1478 ),
1479 admin_url( 'options-general.php' )
1480 );
1481 if ( $_stat && array_key_exists( 'timestamp', $_stat ) && ! empty( $_stat['timestamp'] ) ) {
1482 $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( autoptimizeOptionWrapper::get_option( 'time_format' ), $_stat['timestamp'] );
1483 } else {
1484 $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1485 }
1486 $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1487 // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1488 $_imgopt_notice .= sprintf( __( 'you can click %1$shere to refresh your quota status%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1489 }
1490
1491 // and make the full notice filterable.
1492 $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
1493
1494 return array(
1495 'status' => $_stat['Status'],
1496 'notice' => $_imgopt_notice,
1497 );
1498 }
1499 }
1500 return false;
1501 }
1502
1503 public static function get_imgopt_status_notice_wrapper() {
1504 // needed for notice being shown in autoptimizeCacheChecker.php.
1505 $self = new self();
1506 return $self->get_imgopt_status_notice();
1507 }
1508
1509 /**
1510 * Get img provider stats (used to display notice).
1511 *
1512 * @param bool $_refresh Should the stats be forcefully refreshed or not.
1513 */
1514 public function query_img_provider_stats( $_refresh = false ) {
1515 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1516 $url = '';
1517 $stat_dom = 'https://no-cdn.shortpixel.ai/';
1518 $endpoint = $stat_dom . 'read-domain/';
1519 $domain = AUTOPTIMIZE_SITE_DOMAIN;
1520
1521 // make sure parse_url result makes sense, keeping $url empty if not.
1522 if ( $domain && ! empty( $domain ) ) {
1523 $url = $endpoint . $domain;
1524 if ( true === $_refresh ) {
1525 $url = $url . '/refresh';
1526 }
1527 }
1528
1529 $url = apply_filters(
1530 'autoptimize_filter_imgopt_stat_url',
1531 $url
1532 );
1533
1534 // only do the remote call if $url is not empty to make sure no parse_url
1535 // weirdness results in useless calls.
1536 if ( ! empty( $url ) ) {
1537 $response = wp_remote_get( $url );
1538 if ( ! is_wp_error( $response ) ) {
1539 if ( '200' == wp_remote_retrieve_response_code( $response ) ) {
1540 $stats = json_decode( wp_remote_retrieve_body( $response ), true );
1541 autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_provider_stat', $stats );
1542 }
1543 }
1544 }
1545 }
1546 }
1547
1548 public static function get_img_provider_stats()
1549 {
1550 // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
1551 $self = new self();
1552 return $self->query_img_provider_stats();
1553 }
1554
1555 /**
1556 * Determines and returns the service launch status.
1557 *
1558 * @return bool
1559 */
1560 public function launch_ok()
1561 {
1562 static $launch_status = null;
1563
1564 if ( null === $launch_status ) {
1565 $avail_imgopt = '';
1566 if ( is_array( $this->options ) && array_key_exists( 'availabilities', $this->options ) && is_array( $this->options['availabilities'] ) && array_key_exists( 'extra_imgopt', $this->options['availabilities'] ) ) {
1567 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
1568 }
1569
1570 $magic_number = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1571 $has_launched = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_launched', '' );
1572 $launch_status = false;
1573 if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
1574 $launch_status = true;
1575 if ( ! $has_launched ) {
1576 autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_launched', 'on' );
1577 }
1578 }
1579 }
1580
1581 return $launch_status;
1582 }
1583
1584 public static function launch_ok_wrapper() {
1585 // needed for "plug" notice in autoptimizeMain.php.
1586 $self = new self();
1587 return $self->launch_ok();
1588 }
1589
1590 public function get_imgopt_provider_userstatus() {
1591 static $_provider_userstatus = null;
1592
1593 if ( is_null( $_provider_userstatus ) ) {
1594 $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1595 if ( is_array( $_stat ) ) {
1596 if ( array_key_exists( 'Status', $_stat ) ) {
1597 $_provider_userstatus['Status'] = $_stat['Status'];
1598 } else {
1599 // if no stats then we assume all is well.
1600 $_provider_userstatus['Status'] = 2;
1601 }
1602 if ( array_key_exists( 'timestamp', $_stat ) ) {
1603 $_provider_userstatus['timestamp'] = $_stat['timestamp'];
1604 } else {
1605 // if no timestamp then we return "".
1606 $_provider_userstatus['timestamp'] = '';
1607 }
1608 } else {
1609 // no provider_stat yet, assume/ return all OK.
1610 $_provider_userstatus['Status'] = 2;
1611 $_provider_userstatus['timestamp'] = '';
1612 }
1613 }
1614
1615 return $_provider_userstatus;
1616 }
1617 }
1618