PluginProbe
Autoptimize / 3.1.11
Autoptimize v3.1.11
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.11, at classes/autoptimizeImages.php

1,621 lines 76.5 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 ( false === is_array( $url_parsed ) ) {
517 return false;
518 } elseif ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
519 return false;
520 } elseif ( autoptimizeUtils::is_local_server() ) {
521 return false;
522 } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
523 return false;
524 } elseif ( strpos( $url, '.php' ) !== false ) {
525 return false;
526 } elseif ( false === array_key_exists( 'path', $url_parsed ) || str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp', '.avif' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
527 // fixme: better check against end of string.
528 return false;
529 } elseif ( ! empty( $nopti_images ) ) {
530 $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
531 foreach ( $nopti_images_array as $nopti_image ) {
532 if ( strpos( $url, $nopti_image ) !== false || ( ( '' !== $tag && strpos( $tag, $nopti_image ) !== false ) ) ) {
533 return false;
534 }
535 }
536 }
537 return true;
538 }
539
540 // wrapper for reuse in AOPro.
541 public static function build_imgopt_url_wrapper( $orig_url, $width = 0, $height = 0 ) {
542 $self = new self();
543 return $self->build_imgopt_url( $orig_url, $width = 0, $height = 0 );
544 }
545
546 private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
547 {
548 // sanitize width and height.
549 if ( strpos( $width, '%' ) !== false ) {
550 $width = 0;
551 }
552 if ( strpos( $height, '%' ) !== false ) {
553 $height = 0;
554 }
555 $width = (int) $width;
556 $height = (int) $height;
557
558 $filtered_url = apply_filters(
559 'autoptimize_filter_imgopt_build_url',
560 $orig_url,
561 $width,
562 $height
563 );
564
565 // If filter modified the url, return that.
566 if ( $filtered_url !== $orig_url ) {
567 return $filtered_url;
568 }
569
570 $normalized_url = $this->normalize_img_url( $orig_url );
571
572 // 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.
573 if ( apply_filters( 'autoptimize_filter_imgopt_check_normalized_url', true ) && ! preg_match( '/[^\x20-\x7e]/', $normalized_url ) && false === filter_var( $normalized_url, FILTER_VALIDATE_URL ) ) {
574 return $orig_url;
575 }
576
577 $imgopt_base_url = $this->get_imgopt_base_url();
578 $imgopt_size = '';
579
580 if ( $width && 0 !== $width ) {
581 $imgopt_size = ',w_' . $width;
582 }
583
584 if ( $height && 0 !== $height ) {
585 $imgopt_size .= ',h_' . $height;
586 }
587
588 $url = $imgopt_base_url . $imgopt_size . '/' . $normalized_url;
589 $url = apply_filters( 'autoptimize_filter_imgopt_after_build_imgopt_url', $url );
590
591 return $url;
592 }
593
594 public function replace_data_thumbs( $matches )
595 {
596 return $this->replace_img_callback( $matches, 150, 150 );
597 }
598
599 public function replace_img_callback( $matches, $width = 0, $height = 0 )
600 {
601 $_normalized_img_url = $this->normalize_img_url( $matches[1] );
602 if ( $this->can_optimize_image( $matches[1], $matches[0] ) ) {
603 return str_replace( $matches[1], $this->build_imgopt_url( $_normalized_img_url, $width, $height ), $matches[0] );
604 } else {
605 return $matches[0];
606 }
607 }
608
609 public function replace_icon_callback( $matches )
610 {
611 if ( array_key_exists( '2', $matches ) ) {
612 $sizes = explode( 'x', $matches[2] );
613 $width = $sizes[0];
614 $height = $sizes[1];
615 } else {
616 $width = 180;
617 $height = 180;
618 }
619
620 // make sure we're not trying to optimize a *.ico file.
621 if ( strpos( $matches[1], '.ico' ) === false ) {
622 return $this->replace_img_callback( $matches, $width, $height );
623 } else {
624 return $matches[0];
625 }
626 }
627
628 public function filter_optimize_images( $in, $testing = false )
629 {
630 /*
631 * potential future functional improvements:
632 *
633 * filter for critical CSS.
634 */
635 $to_replace = array();
636 $to_preload = '';
637
638 // hide (no)script tags to avoid replacing (and potentially breaking) images in script tags.
639 if ( apply_filters( 'autoptimize_filter_imgopt_hide_script', true ) || $this->should_lazyload() ) {
640 $in = autoptimizeBase::replace_contents_with_marker_if_exists(
641 'SCRIPT',
642 '<script',
643 '#<(?:no)?script.*?<\/(?:no)?script>#is',
644 $in
645 );
646 }
647
648 // get img preloads as set in post metabox, exploding ", " instead of "," because LCP preload
649 // could be a shortpixel URL, which has comma's and results in way too many preloads.
650 $metabox_preloads = array_filter( array_map( 'trim', explode( ', ', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
651 $metabox_preloads = apply_filters( 'autoptimize_filter_images_metabox_preloads', $metabox_preloads );
652
653 // extract img tags.
654 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
655 foreach ( $matches[0] as $tag ) {
656 $tag = apply_filters( 'autoptimize_filter_imgopt_tag_preopt', $tag );
657
658 $orig_tag = $tag;
659 $imgopt_w = '';
660 $imgopt_h = '';
661
662 // first do (data-)srcsets.
663 if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
664 foreach ( $allsrcsets as $srcset ) {
665 $srcset = $srcset[2];
666 $orig_srcset = $srcset;
667 $srcsets = explode( ',', $srcset );
668 foreach ( $srcsets as $indiv_srcset ) {
669 $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
670 if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
671 $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
672 }
673 if ( $this->can_optimize_image( $indiv_srcset_parts[0], $tag, $testing ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
674 $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
675 $srcset = str_replace( $indiv_srcset_parts[0], $imgopt_url, $srcset );
676 }
677 }
678 $tag = str_replace( $orig_srcset, $srcset, $tag );
679 }
680 }
681
682 // proceed with img src.
683 // get width and height and add to $imgopt_size.
684 $_get_size = $this->get_size_from_tag( $tag );
685 $imgopt_w = $_get_size['width'];
686 $imgopt_h = $_get_size['height'];
687
688 // then start replacing images src.
689 if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
690 foreach ( $urls as $url ) {
691 $full_src_orig = $url[0];
692 $url = $url[1];
693 if ( $this->can_optimize_image( $url, $tag, $testing ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
694 $imgopt_url = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
695 $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
696 $tag = str_replace( $full_src_orig, $full_imgopt_src, $tag );
697 }
698 }
699 }
700
701 // check if the image needs to be prelaoded.
702 if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && str_replace( $metabox_preloads, '', $tag ) !== $tag ) {
703 $to_preload .= $this->create_img_preload_tag( $tag );
704 }
705
706 // do lazyload stuff.
707 if ( $this->should_lazyload( $in ) && ! empty( $url ) ) {
708 // first do lpiq placeholder logic.
709 if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
710 // if all img src have been replaced during srcset, we have to extract the
711 // origin url from the imgopt one to be able to set a lqip placeholder.
712 $_url = substr( $url, strpos( $url, '/http' ) + 1 );
713 } else {
714 $_url = $url;
715 }
716
717 $_url = $this->normalize_img_url( $_url );
718
719 $placeholder = '';
720 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 ) ) {
721 $lqip_w = '';
722 $lqip_h = '';
723 if ( isset( $imgopt_w ) && ! empty( $imgopt_w ) ) {
724 $lqip_w = ',w_' . $imgopt_w;
725 }
726 if ( isset( $imgopt_h ) && ! empty( $imgopt_h ) ) {
727 $lqip_h = ',h_' . $imgopt_h;
728 }
729 $placeholder = $this->get_imgopt_host() . 'client/q_lqip,ret_wait' . $lqip_w . $lqip_h . '/' . $_url;
730 }
731 // then call add_lazyload-function with lpiq placeholder if set.
732 $tag = $this->add_lazyload( $tag, $placeholder );
733 }
734
735 // add decoding="async" behind filter, not sure if I'll make it default true yet.
736 if ( true === apply_filters( 'autoptimize_filter_imgopt_add_decoding', true ) && false === strpos( $tag, ' decoding=' ) ) {
737 $tag = str_replace( '<img ', '<img decoding="async" ', $tag );
738 }
739
740 $tag = apply_filters( 'autoptimize_filter_imgopt_tag_postopt', $tag );
741
742 // and add tag to array for later replacement.
743 if ( $tag !== $orig_tag ) {
744 $to_replace[ $orig_tag ] = $tag;
745 }
746 }
747 }
748
749 // and replace all.
750 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
751
752 // misc. node attributes that might hold image url's (incl. the previously separate data-thumb).
753 $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' ) ) );
754 if ( ! empty( $extra_attr_with_img ) && is_array( $extra_attr_with_img ) ) {
755 foreach ( $extra_attr_with_img as $candidate ) {
756 if ( is_array( $candidate ) && strpos( $out, $candidate[1] ) !== false ) {
757 $_regex = '/\<' . $candidate[0] . '(?:[^>]*)?\s' . $candidate[1] . '=(?:"|\')(.+?)(?:"|\')(?:[^>]*)?>/s';
758 $out = preg_replace_callback(
759 $_regex,
760 array( $this, 'replace_img_callback' ),
761 $out
762 );
763 }
764 }
765 }
766
767 // background-image in inline style.
768 if ( ( strpos( $out, 'background-image:' ) !== false || strpos( $out, 'background:' ) !== false ) && strpos( $out, 'url(' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
769 $out = preg_replace_callback(
770 '/style=(?:"|\')[^<>]*?background(?:-image)?:[^;"\'()>]*url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
771 array( $this, 'replace_img_callback' ),
772 $out
773 );
774 }
775
776 // act on icon links.
777 if ( ( strpos( $out, '<link rel="icon"' ) !== false || ( strpos( $out, "<link rel='icon'" ) !== false ) ) && apply_filters( 'autoptimize_filter_imgopt_linkicon', true ) ) {
778 $out = preg_replace_callback(
779 '/<link\srel=(?:"|\')(?:apple-touch-)?icon(?:"|\').*\shref=(?:"|\')(.*)(?:"|\')(?:\ssizes=(?:"|\')(\d*x\d*)(?:"|\'))?\s\/>/Um',
780 array( $this, 'replace_icon_callback' ),
781 $out
782 );
783 }
784
785 // lazyload picture source tags and bgimage.
786 if ( $this->should_lazyload() ) {
787 $out = $this->process_picture_tag( $out, true, true );
788 $out = $this->process_bgimage( $out );
789 } else {
790 $out = $this->process_picture_tag( $out, true, false );
791 }
792
793 // restore (no)script tags.
794 if ( apply_filters( 'autoptimize_filter_imgopt_hide_script', true ) || $this->should_lazyload() ) {
795 $out = autoptimizeBase::restore_marked_content(
796 'SCRIPT',
797 $out
798 );
799 }
800
801 if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
802 // the preload was not in an img tag, so adding a non-responsive preload instead.
803 foreach ( $metabox_preloads as $img_preload ) {
804 $to_preload .= '<link rel="preload" href="' . $img_preload . '" as="image">';
805 }
806 }
807
808 if ( ! empty( $to_preload ) ) {
809 $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
810 }
811
812 return $out;
813 }
814
815 public static function get_size_from_tag( $tag ) {
816 // reusable function to extract widht and height from an image tag
817 // enforcing a filterable maximum width and height (default 4999X4999).
818 $width = '';
819 $height = '';
820
821 if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $_width ) ) {
822 if ( strpos( $_width[2], '%' ) === false ) {
823 $width = (int) $_width[2];
824 }
825 }
826 if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $_height ) ) {
827 if ( strpos( $_height[2], '%' ) === false ) {
828 $height = (int) $_height[2];
829 }
830 }
831
832 // check for and enforce (filterable) max sizes.
833 $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
834 if ( $width > $_max_width ) {
835 $_width = $_max_width;
836 if ( ! empty( $height ) && is_int( $height ) ) {
837 $height = $_width / $width * $height;
838 }
839 $width = $_width;
840 }
841 $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
842 if ( $height > $_max_height ) {
843 $_height = $_max_height;
844 if ( ! empty( $width ) && is_int( $width ) ) {
845 $width = $_height / $height * $width;
846 }
847 $height = $_height;
848 }
849
850 return array(
851 'width' => $width,
852 'height' => $height,
853 );
854 }
855
856 /**
857 * Lazyload functions
858 */
859 public static function should_lazyload_wrapper( $no_meta = false ) {
860 // needed in autoptimizeMain.php.
861 $self = new self();
862 return $self->should_lazyload( '', $no_meta );
863 }
864
865 public function should_lazyload( $context = '', $no_meta = false ) {
866 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && false === $this->check_nolazy() ) {
867 $lazyload_return = true;
868 } else {
869 $lazyload_return = false;
870 }
871
872 // If page/ post check post_meta to see if lazyload is off for page.
873 if ( false === $no_meta && false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_lazyload' ) ) {
874 $lazyload_return = false;
875 }
876
877 $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return, $context );
878
879 return $lazyload_return;
880 }
881
882 public static function check_nolazy() {
883 if ( array_key_exists( 'ao_nolazy', $_GET ) && '1' === $_GET['ao_nolazy'] ) {
884 return true;
885 } else {
886 return false;
887 }
888 }
889
890 public function filter_lazyload_images( $in )
891 {
892 // only used is image optimization is NOT active but lazyload is.
893 $to_replace = array();
894 $to_preload = '';
895
896 // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
897 $out = autoptimizeBase::replace_contents_with_marker_if_exists(
898 'SCRIPT',
899 '<script',
900 '#<(?:no)?script.*?<\/(?:no)?script>#is',
901 $in
902 );
903
904 // get img preloads as set in post metabox.
905 $metabox_preloads = array_filter( array_map( 'trim', explode( ',', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
906
907 // extract img tags and add lazyload attribs/ add preloads.
908 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
909 foreach ( $matches[0] as $tag ) {
910 // check if image needs to be preloaded.
911 if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && str_replace( $metabox_preloads, '', $tag ) !== $tag ) {
912 $to_preload .= $this->create_img_preload_tag( $tag );
913 }
914
915 // and lazyloaded.
916 if ( $this->should_lazyload( $out ) ) {
917 $to_replace[ $tag ] = $this->add_lazyload( $tag );
918 }
919 }
920 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $out );
921 }
922
923 // and also lazyload picture tag.
924 $out = $this->process_picture_tag( $out, false, true );
925
926 // and inline style blocks with background-image.
927 $out = $this->process_bgimage( $out );
928
929 // restore noscript tags.
930 $out = autoptimizeBase::restore_marked_content(
931 'SCRIPT',
932 $out
933 );
934
935 if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
936 // the preload was not in an img tag, so adding a non-responsive preload instead.
937 foreach ( $metabox_preloads as $img_preload ) {
938 $to_preload .= '<link rel="preload" href="' . $img_preload . '" as="image">';
939 }
940 }
941
942 if ( ! empty( $to_preload ) ) {
943 $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
944 }
945
946 return $out;
947 }
948
949 public function add_lazyload( $tag, $placeholder = '' ) {
950 // adds actual lazyload-attributes to an image node.
951 $this->lazyload_counter++;
952
953 $_lazyload_from_nth = '';
954 if ( array_key_exists( 'autoptimize_imgopt_number_field_7', $this->options ) ) {
955 $_lazyload_from_nth = $this->options['autoptimize_imgopt_number_field_7'];
956 }
957 $_lazyload_from_nth = apply_filters( 'autoptimize_filter_imgopt_lazyload_from_nth', $_lazyload_from_nth );
958
959 if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag && $this->lazyload_counter >= $_lazyload_from_nth ) {
960 $tag = $this->maybe_fix_missing_quotes( $tag );
961
962 // store original tag for use in noscript version.
963 $noscript_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>';
964
965 $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
966
967 // insert lazyload class.
968 $tag = $this->inject_classes_in_tag( $tag, "$lazyload_class " );
969
970 if ( ! $placeholder || empty( $placeholder ) ) {
971 // get image width & heigth for placeholder fun (and to prevent content reflow).
972 $_get_size = $this->get_size_from_tag( $tag );
973 $width = $_get_size['width'];
974 $height = $_get_size['height'];
975 if ( false === $width || empty( $width ) ) {
976 $width = 210; // default width for SVG placeholder.
977 }
978 if ( false === $height || empty( $height ) ) {
979 $height = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
980 }
981
982 // insert the actual lazyload stuff.
983 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
984 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
985 }
986
987 $tag = preg_replace( '/(\s)src=/', ' src=\'' . $placeholder . '\' data-src=', $tag );
988 $tag = preg_replace( '/(\s)srcset=/', ' data-srcset=', $tag );
989
990 // move sizes to data-sizes unless filter says no.
991 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_move_sizes', true ) ) {
992 $tag = str_replace( ' sizes=', ' data-sizes=', $tag );
993 }
994
995 // add the noscript-tag from earlier.
996 $tag = $noscript_tag . $tag;
997 $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
998 } else {
999 $tag = apply_filters( 'autoptimize_filter_imgopt_not_lazyloaded_img', $tag );
1000 }
1001
1002 return $tag;
1003 }
1004
1005 public function add_lazyload_js_footer() {
1006 if ( false === autoptimizeMain::should_buffer() || autoptimizeMain::is_amp_markup( '' ) ) {
1007 return;
1008 }
1009
1010 // The JS will by default be excluded form autoptimization but this can be changed with a filter.
1011 $noptimize_flag = '';
1012 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
1013 $noptimize_flag = ' data-noptimize="1"';
1014 }
1015
1016 $_extra = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings', '' );
1017 if ( is_array( $_extra ) && array_key_exists( 'autoptimize_extra_checkbox_field_0', $_extra ) && ! empty( $_extra['autoptimize_extra_checkbox_field_0'] ) ) {
1018 // if "remove query strings" is active in "extra", then let's be consistant and not add one ourselves :-) ?
1019 $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js', __FILE__ );
1020 } else {
1021 $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js?ao_version=' . AUTOPTIMIZE_PLUGIN_VERSION, __FILE__ );
1022 }
1023
1024 $cdn_url = $this->get_cdn_url();
1025 if ( ! empty( $cdn_url ) ) {
1026 $cdn_url = rtrim( $cdn_url, '/' );
1027 $lazysizes_js = str_replace( AUTOPTIMIZE_WP_SITE_URL, $cdn_url, $lazysizes_js );
1028 }
1029
1030 $type_js = '';
1031 if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
1032 $type_js = ' type="text/javascript"';
1033 }
1034
1035 // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
1036 echo apply_filters( 'autoptimize_filter_imgopt_lazyload_cssoutput', '<noscript><style>.lazyload{display:none;}</style></noscript>' );
1037 echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $type_js . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
1038 echo apply_filters( 'autoptimize_filter_imgopt_lazyload_js', '<script async' . $type_js . $noptimize_flag . ' src=\'' . $lazysizes_js . '\'></script>' );
1039 }
1040
1041 public static function create_img_preload_tag( $tag ) {
1042 if ( false === apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
1043 return '';
1044 }
1045
1046 // clean up; remove tabs/ linebreaks/ spaces.
1047 $tag = preg_replace( '/\s+/', ' ', $tag );
1048
1049 // remove noscript.
1050 if ( false !== strpos( $tag, '<noscript' ) ) {
1051 $tag = preg_replace( '/<noscript.*<\/noscript>/mU', '', $tag );
1052 }
1053
1054 // rewrite img tag to link preload img.
1055 $_from = array( '<img ', ' src=', ' sizes=', ' srcset=' );
1056 $_to = array( '<link rel="preload" as="image" ', ' href=', ' imagesizes=', ' imagesrcset=' );
1057 $tag = str_replace( $_from, $_to, $tag );
1058
1059 // and remove title, alt, class and id.
1060 $tag = preg_replace( '/ ((?:title|alt|class|id|loading|fetchpriority|decoding|data-no-lazy|width|height)=".*")/Um', '', $tag );
1061 if ( str_replace( array( ' title=', ' class=', ' alt=', ' id=', ' fetchpriority=', ' decoding=', ' data-no-lazy=' ), '', $tag ) !== $tag ) {
1062 // 2nd regex pass if still title/ class/ alt in case single quotes were used iso doubles.
1063 $tag = preg_replace( '/ ((?:title|alt|class|id|loading|fetchpriority|decoding|data-no-lazy)=\'.*\')/Um', '', $tag );
1064 }
1065
1066 return $tag;
1067 }
1068
1069 public static function get_cdn_url() {
1070 // getting CDN url here to avoid having to make bigger changes to autoptimizeBase.
1071 static $cdn_url = null;
1072
1073 if ( null === $cdn_url ) {
1074 $cdn_url = autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' );
1075 $cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $cdn_url );
1076 $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
1077 }
1078
1079 return $cdn_url;
1080 }
1081
1082 public function get_lazyload_exclusions() {
1083 // returns array of strings that if found in an <img tag will stop the img from being lazy-loaded.
1084 static $exclude_lazyload_array = null;
1085
1086 if ( null === $exclude_lazyload_array ) {
1087 $options = $this->options;
1088
1089 // set default exclusions.
1090 $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg', 'loading="eager"', 'fetchpriority="high"' );
1091
1092 // add from setting.
1093 if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
1094 $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
1095 if ( ! empty( $exclude_lazyload_option ) ) {
1096 $exclude_lazyload_array = array_merge( $exclude_lazyload_array, array_filter( array_map( 'trim', explode( ',', $options['autoptimize_imgopt_text_field_5'] ) ) ) );
1097 }
1098 }
1099
1100 // and filter for developer-initiated changes.
1101 $exclude_lazyload_array = apply_filters( 'autoptimize_filter_imgopt_lazyload_exclude_array', $exclude_lazyload_array );
1102 }
1103
1104 return $exclude_lazyload_array;
1105 }
1106
1107 public function inject_classes_in_tag( $tag, $target_class ) {
1108 if ( strpos( $tag, 'class=' ) !== false ) {
1109 $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
1110 } else {
1111 $tag = preg_replace( '/(<[a-zA-Z]*)\s/', '$1 class="' . trim( $target_class ) . '" ', $tag );
1112 }
1113
1114 return $tag;
1115 }
1116
1117 public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
1118 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';
1119 }
1120
1121 public function should_ngimg() {
1122 static $ngimg_return = null;
1123
1124 if ( is_null( $ngimg_return ) ) {
1125 // nextgen img only works if imgopt is active.
1126 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && $this->imgopt_active() ) {
1127 $ngimg_return = true;
1128 } else {
1129 $ngimg_return = false;
1130 }
1131 }
1132
1133 return $ngimg_return;
1134 }
1135
1136 public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
1137 // check if "<picture" is present and if filter allows us to process <picture>.
1138 if ( strpos( $in, '<picture' ) === false || apply_filters( 'autoptimize_filter_imgopt_dopicture', true ) === false ) {
1139 return $in;
1140 }
1141
1142 $_exclusions = $this->get_lazyload_exclusions();
1143 $to_replace_pict = array();
1144
1145 // extract and process each picture-node.
1146 preg_match_all( '#<picture.*</picture>#Usmi', $in, $_pictures, PREG_SET_ORDER );
1147 foreach ( $_pictures as $_picture ) {
1148 $_picture = $this->maybe_fix_missing_quotes( $_picture );
1149 if ( strpos( $_picture[0], '<source ' ) !== false && preg_match_all( '#<source .*srcset=(?:"|\')(?!data)(.*)(?:"|\').*>#Usmi', $_picture[0], $_sources, PREG_SET_ORDER ) !== false ) {
1150 foreach ( $_sources as $_source ) {
1151 $_picture_replacement = $_source[0];
1152
1153 // should we optimize the image?
1154 if ( $imgopt && $this->can_optimize_image( $_source[1], $_picture[0] ) ) {
1155 $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
1156 }
1157 // should we lazy-load?
1158 if ( $lazy && $this->should_lazyload() && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
1159 $_picture_replacement = str_replace( ' srcset=', ' data-srcset=', $_picture_replacement );
1160 }
1161 $to_replace_pict[ $_source[0] ] = $_picture_replacement;
1162 }
1163 }
1164 }
1165
1166 // and return the fully procesed $in.
1167 $out = str_replace( array_keys( $to_replace_pict ), array_values( $to_replace_pict ), $in );
1168
1169 return $out;
1170 }
1171
1172 public function process_bgimage( $in ) {
1173 if ( strpos( $in, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_lazyload_backgroundimages', true ) ) {
1174 $out = preg_replace_callback(
1175 '/(<(?:article|aside|body|div|footer|header|p|section|span|table)[^>]*)\sstyle=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)[^>]*/',
1176 array( $this, 'lazyload_bgimg_callback' ),
1177 $in
1178 );
1179 return $out;
1180 }
1181 return $in;
1182 }
1183
1184 public function lazyload_bgimg_callback( $matches ) {
1185 if ( str_ireplace( $this->get_lazyload_exclusions(), '', $matches[0] ) === $matches[0] ) {
1186 // get placeholder & lazyload class strings.
1187 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( 500, 300 ) );
1188 $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
1189 // remove quotes from url() to be able to replace in next step.
1190 $out = str_replace( array( "url('" . $matches[2] . "')", 'url("' . $matches[2] . '")' ), 'url(' . $matches[2] . ')', $matches[0] );
1191 // replace background-image URL with SVG placeholder.
1192 $out = str_replace( 'url(' . $matches[2], 'url(' . $placeholder, $out );
1193 // sanitize bgimg src for quote sillyness.
1194 $bgimg_src = $this->fix_silly_bgimg_quotes( $matches[2] );
1195 // add data-bg attribute with real background-image URL for lazyload to pick up.
1196 $out = str_replace( $matches[1], $matches[1] . ' data-bg="' . $bgimg_src . '"', $out );
1197 // and finally add lazyload class to tag.
1198 $out = $this->inject_classes_in_tag( $out, "$lazyload_class " );
1199 return $out;
1200 }
1201 return $matches[0];
1202 }
1203
1204 public function fix_silly_bgimg_quotes( $tag_in ) {
1205 // some themes/ pagebuilders wrap backgroundimages in HTML-encoded quotes (or linebreaks) which breaks imgopt/ lazyloading, this removes them.
1206 return trim( str_replace( array( "\r\n", '"', '&quot;', '&#034;', '&apos;', '&#039;' ), '', $tag_in ) );
1207 }
1208
1209 public function maybe_fix_missing_quotes( $tag_in ) {
1210 // 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.
1211 if ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) {
1212 $tag_out = preg_replace( '/class\s?=([^("|\')]*)(\s|>)/U', 'class=\'$1\'$2', $tag_in );
1213 $tag_out = preg_replace( '/\s(width|height)=(?:"|\')?([^\s"\'>]*)(?:"|\')?/', ' $1=\'$2\'', $tag_out );
1214 return $tag_out;
1215 } else {
1216 return $tag_in;
1217 }
1218 }
1219
1220 /**
1221 * Admin page logic and related functions below.
1222 */
1223 public function imgopt_admin_menu()
1224 {
1225 // no acces if multisite and not network admin and no site config allowed.
1226 if ( autoptimizeConfig::should_show_menu_tabs() ) {
1227 add_submenu_page(
1228 '',
1229 'autoptimize_imgopt',
1230 'autoptimize_imgopt',
1231 'manage_options',
1232 'autoptimize_imgopt',
1233 array( $this, 'imgopt_options_page' )
1234 );
1235 }
1236 register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
1237 }
1238
1239 public function add_imgopt_tab( $in )
1240 {
1241 if ( autoptimizeConfig::should_show_menu_tabs() ) {
1242 $in = array_merge( $in, array( 'autoptimize_imgopt' => apply_filters( 'autoptimize_filter_imgopt_tab_text', esc_html__( 'Images', 'autoptimize' ) ) ) );
1243 }
1244
1245 return $in;
1246 }
1247
1248 public function imgopt_options_page()
1249 {
1250 // phpcs:disable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace
1251 // phpcs:disable Generic.Formatting.DisallowMultipleStatements.SameLine
1252
1253 // Check querystring for "refreshCacheChecker" and call cachechecker if so.
1254 if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
1255 $this->query_img_provider_stats( true );
1256 }
1257
1258 $options = $this->fetch_options();
1259 $sp_url_suffix = $this->get_service_url_suffix();
1260 ?>
1261 <style>
1262 #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
1263 #ao_settings_form .form-table th {font-weight: normal;}
1264 #autoptimize_imgopt_descr{font-size: 120%;}
1265 </style>
1266 <script>document.title = "Autoptimize: <?php esc_html_e( 'Images', 'autoptimize' ); ?> " + document.title;</script>
1267 <div class="wrap">
1268 <h1><?php apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? esc_html_e( 'Autoptimize Pro Settings', 'autoptimize' ) : esc_html_e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
1269 <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
1270 <?php if ( autoptimizeUtils::is_local_server() ) { ?>
1271 <div class="notice-warning notice"><p>
1272 <?php
1273 echo esc_html__( 'The image optimization service does not work on locally hosted sites or when the server is on a private network.', 'autoptimize' );
1274 ?>
1275 </p></div>
1276 <?php } ?>
1277 <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
1278 <div class="notice-warning notice"><p>
1279 <?php
1280 // translators: "Autoptimize support forum" will appear in a "a href".
1281 echo sprintf( esc_html__( '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>' );
1282 ?>
1283 </p></div>
1284 <?php } ?>
1285
1286 <?php if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! autoptimizeImages::instance()->launch_ok() ) { ?>
1287 <div class="notice-warning notice"><p>
1288 <?php esc_html_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' ); ?>
1289 </p></div>
1290 <?php } ?>
1291
1292 <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
1293 <div class="notice-warning notice"><p>
1294 <?php
1295 // translators: "disable Jetpack's site accelerator for images" will appear in a "a href" linking to the jetpack settings page.
1296 echo sprintf( esc_html__( '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>' );
1297 ?>
1298 </p></div>
1299 <?php } ?>
1300 <form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
1301 <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
1302 <h2><?php esc_html_e( 'Image optimization', 'autoptimize' ); ?></h2>
1303 <span id='autoptimize_imgopt_descr'><?php echo apply_filters( 'autoptimize_filter_imgopt_intro_copy', esc_html__( '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>
1304 <table class="form-table">
1305 <tr>
1306 <th scope="row"><?php esc_html_e( 'Image optimization & CDN', 'autoptimize' ); ?></th>
1307 <td>
1308 <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', esc_html__( 'On-the-fly image optimization and fast delivery via the Shortpixel global CDN.', 'autoptimize' ) ); ?></label>
1309 <?php
1310 // show shortpixel status.
1311 $_notice = autoptimizeImages::instance()->get_imgopt_status_notice();
1312 if ( $_notice ) {
1313 switch ( $_notice['status'] ) {
1314 case 2:
1315 $_notice_color = 'green';
1316 break;
1317 case 1:
1318 $_notice_color = 'orange';
1319 break;
1320 case -1:
1321 case -2:
1322 case -3:
1323 $_notice_color = 'red';
1324 break;
1325 default:
1326 $_notice_color = 'green';
1327 }
1328 echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . esc_html__( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
1329 } else {
1330 // translators: link points to shortpixel.
1331 $upsell_msg_1 = '<p>' . sprintf( esc_html__( 'Get more Google love by speeding up your website. Start serving on-the-fly optimized images (also in the "next-gen" %4$sWebP%5$s and %4$sAVIF%5$s 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">', '<strong>', '</strong>' );
1332 if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
1333 $upsell_msg_2 = sprintf( esc_html__( 'For a limited time only, this service is offered free for all Autoptimize users, %1$sdon\'t miss the chance to test it%2$s and see how much it could improve your site\'s speed.', 'autoptimize' ), '<strong>', '</strong>' );
1334 } else {
1335 // translators: 1st link points to autoptimize.com.pro, 2nd to shortpixel.
1336 $upsell_msg_2 = sprintf( esc_html__( 'For (nearly) %5$sunlimited image optimizations %1$sbuy Autoptimize Pro%2$s%6$s 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://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell" target="_blank">', '</a>', '<strong>', '</strong>' );
1337 }
1338 echo apply_filters( 'autoptimize_filter_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
1339 }
1340 // translators: link points to shortpixel FAQ.
1341 $faqcopy = sprintf( esc_html__( '%3$sQuestions%4$s? 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>', '<strong>', '</strong>' );
1342 $faqcopy = $faqcopy . ' ' . esc_html__( 'Only works for websites and images that are publicly available.', 'autoptimize' );
1343 // translators: links points to shortpixel TOS & Privacy Policy.
1344 $toscopy = sprintf( esc_html__( '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>' );
1345 echo apply_filters( 'autoptimize_filter_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
1346 ?>
1347 </td>
1348 </tr>
1349 <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"'; } ?>>
1350 <th scope="row"><?php esc_html_e( 'Optimization exclusions', 'autoptimize' ); ?></th>
1351 <td>
1352 <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 esc_html_e( 'Comma-separated list of image classes or filenames that should not be optimized.', 'autoptimize' ); ?></label>
1353 </td>
1354 </tr>
1355 <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"'; } ?>>
1356 <th scope="row"><?php esc_html_e( 'Image Optimization quality', 'autoptimize' ); ?></th>
1357 <td>
1358 <label>
1359 <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
1360 <?php
1361 $_imgopt_array = autoptimizeImages::instance()->get_img_quality_array();
1362 $_imgopt_val = autoptimizeImages::instance()->get_img_quality_setting();
1363
1364 foreach ( $_imgopt_array as $key => $value ) {
1365 echo '<option value="' . $key . '"';
1366 if ( $_imgopt_val == $key ) {
1367 echo ' selected';
1368 }
1369 echo '>' . ucfirst( $value ) . '</option>';
1370 }
1371 echo "\n";
1372 ?>
1373 </select>
1374 </label>
1375 <p>
1376 <?php
1377 // translators: link points to shortpixel image test page.
1378 echo apply_filters( 'autoptimize_filter_imgopt_quality_copy', sprintf( esc_html__( 'You can %1$stest compression levels here%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/online-image-compression' . $sp_url_suffix . '" target="_blank">', '</a>' ) );
1379 ?>
1380 </p>
1381 </td>
1382 </tr>
1383 <?php
1384 if ( apply_filters( 'autoptimize_filter_imgopt_settings_show_avif', true ) ) {
1385 ?>
1386 <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"'; } ?>>
1387 <th scope="row"><?php esc_html_e( 'Load AVIF in supported browsers?', 'autoptimize' ); ?></th>
1388 <td>
1389 <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 esc_html_e( 'Automatically serve AVIF image format to any browser that supports it.', 'autoptimize' ); ?></label>
1390 </td>
1391 </tr>
1392 <?php
1393 } else {
1394 ?>
1395 <input type='hidden' id='autoptimize_imgopt_ngimg_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_4]' value='0'>
1396 <?php
1397 }
1398 ?>
1399 <tr>
1400 <th scope="row"><?php esc_html_e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1401 <td>
1402 <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 esc_html_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>
1403 </td>
1404 </tr>
1405 <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"'; } ?>>
1406 <th scope="row"><?php esc_html_e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
1407 <td>
1408 <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 esc_html_e( 'Comma-separated list of to be excluded image classes or filenames.', 'autoptimize' ); ?></label>
1409 </td>
1410 </tr>
1411 <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"'; } ?>>
1412 <th scope="row"><?php esc_html_e( 'Lazy-load from nth image', 'autoptimize' ); ?></th>
1413 <td>
1414 <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 esc_html_e( 'Don\'t lazyload the first X images, \'1\' lazyloads all.', 'autoptimize' ); ?></label>
1415 </td>
1416 </tr>
1417 </table>
1418 <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1419 </form>
1420 <script>
1421 jQuery(document).ready(function() {
1422 jQuery("#autoptimize_imgopt_checkbox").change(function() {
1423 if (this.checked) {
1424 jQuery("#autoptimize_imgopt_quality").show("slow");
1425 jQuery("#autoptimize_imgopt_ngimg").show("slow");
1426 jQuery("#autoptimize_imgopt_optimization_exclusions").show("slow");
1427 } else {
1428 jQuery("#autoptimize_imgopt_quality").hide("slow");
1429 jQuery("#autoptimize_imgopt_ngimg").hide("slow");
1430 jQuery("#autoptimize_imgopt_optimization_exclusions").hide("slow");
1431 }
1432 });
1433 jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1434 if (this.checked) {
1435 jQuery(".autoptimize_lazyload_child").show("slow");
1436 } else {
1437 jQuery(".autoptimize_lazyload_child").hide("slow");
1438 }
1439 });
1440 });
1441 </script>
1442 <?php
1443 }
1444
1445 /**
1446 * Ïmg opt status as used on dashboard.
1447 */
1448 public function get_imgopt_status_notice() {
1449 if ( $this->imgopt_active() && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1450 $_imgopt_notice = '';
1451 $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1452 $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
1453 $_imgopt_upsell = 'https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell';
1454 $_imgopt_assoc = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1455 $_imgopt_unreach = 'https://shortpixel.helpscoutdocs.com/article/148-why-are-my-images-redirected-from-cdn-shortpixel-ai';
1456
1457 if ( is_array( $_stat ) ) {
1458 if ( 1 == $_stat['Status'] ) {
1459 // translators: "add more credits" will appear in a "a href".
1460 $_imgopt_notice = sprintf( esc_html__( '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 %4$sor consider using %3$sAutoptimize Pro%2$s which comes with (nearly) unlimited image optimization%5$s 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">', '<strong>', '</strong>' );
1461 } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1462 // translators: "add more credits" will appear in a "a href".
1463 $_imgopt_notice = sprintf( esc_html__( '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 %4$sor consider using %3$sAutoptimize Pro%2$s which comes with (nearly) unlimited image optimization%5$s 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">', '<strong>', '</strong>' );
1464 // translators: "associate your domain" will appear in a "a href".
1465 $_imgopt_notice = $_imgopt_notice . ' ' . sprintf( esc_html__( '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>' );
1466 } elseif ( -3 == $_stat['Status'] ) {
1467 // translators: "check the documentation here" will appear in a "a href".
1468 $_imgopt_notice = sprintf( esc_html__( '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>' );
1469 } else {
1470 $_imgopt_upsell = 'https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell';
1471 // translators: "log in to check your account" will appear in a "a href".
1472 $_imgopt_notice = sprintf( esc_html__( '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>' );
1473 }
1474
1475 // add info on freshness + refresh link if status is not 2 (good shape).
1476 if ( 2 != $_stat['Status'] ) {
1477 $_imgopt_stats_refresh_url = add_query_arg(
1478 array(
1479 'page' => 'autoptimize_imgopt',
1480 'refreshImgProvStats' => '1',
1481 ),
1482 admin_url( 'options-general.php' )
1483 );
1484 if ( $_stat && array_key_exists( 'timestamp', $_stat ) && ! empty( $_stat['timestamp'] ) ) {
1485 $_imgopt_stats_last_run = esc_html__( 'based on status at ', 'autoptimize' ) . date_i18n( autoptimizeOptionWrapper::get_option( 'time_format' ), $_stat['timestamp'] );
1486 } else {
1487 $_imgopt_stats_last_run = esc_html__( 'based on previously fetched data', 'autoptimize' );
1488 }
1489 $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1490 // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1491 $_imgopt_notice .= sprintf( esc_html__( 'you can click %1$shere to refresh your quota status%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1492 }
1493
1494 // and make the full notice filterable.
1495 $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
1496
1497 return array(
1498 'status' => $_stat['Status'],
1499 'notice' => $_imgopt_notice,
1500 );
1501 }
1502 }
1503 return false;
1504 }
1505
1506 public static function get_imgopt_status_notice_wrapper() {
1507 // needed for notice being shown in autoptimizeCacheChecker.php.
1508 $self = new self();
1509 return $self->get_imgopt_status_notice();
1510 }
1511
1512 /**
1513 * Get img provider stats (used to display notice).
1514 *
1515 * @param bool $_refresh Should the stats be forcefully refreshed or not.
1516 */
1517 public function query_img_provider_stats( $_refresh = false ) {
1518 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1519 $url = '';
1520 $stat_dom = 'https://no-cdn.shortpixel.ai/';
1521 $endpoint = $stat_dom . 'read-domain/';
1522 $domain = AUTOPTIMIZE_SITE_DOMAIN;
1523
1524 // make sure parse_url result makes sense, keeping $url empty if not.
1525 if ( $domain && ! empty( $domain ) ) {
1526 $url = $endpoint . $domain;
1527 if ( true === $_refresh ) {
1528 $url = $url . '/refresh';
1529 }
1530 }
1531
1532 $url = apply_filters(
1533 'autoptimize_filter_imgopt_stat_url',
1534 $url
1535 );
1536
1537 // only do the remote call if $url is not empty to make sure no parse_url
1538 // weirdness results in useless calls.
1539 if ( ! empty( $url ) ) {
1540 $response = wp_remote_get( $url );
1541 if ( ! is_wp_error( $response ) ) {
1542 if ( '200' == wp_remote_retrieve_response_code( $response ) ) {
1543 $stats = json_decode( wp_remote_retrieve_body( $response ), true );
1544 autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_provider_stat', $stats );
1545 }
1546 }
1547 }
1548 }
1549 }
1550
1551 public static function get_img_provider_stats()
1552 {
1553 // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
1554 $self = new self();
1555 return $self->query_img_provider_stats();
1556 }
1557
1558 /**
1559 * Determines and returns the service launch status.
1560 *
1561 * @return bool
1562 */
1563 public function launch_ok()
1564 {
1565 static $launch_status = null;
1566
1567 if ( null === $launch_status ) {
1568 $avail_imgopt = '';
1569 if ( is_array( $this->options ) && array_key_exists( 'availabilities', $this->options ) && is_array( $this->options['availabilities'] ) && array_key_exists( 'extra_imgopt', $this->options['availabilities'] ) ) {
1570 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
1571 }
1572
1573 $magic_number = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1574 $has_launched = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_launched', '' );
1575 $launch_status = false;
1576 if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
1577 $launch_status = true;
1578 if ( ! $has_launched ) {
1579 autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_launched', 'on' );
1580 }
1581 }
1582 }
1583
1584 return $launch_status;
1585 }
1586
1587 public static function launch_ok_wrapper() {
1588 // needed for "plug" notice in autoptimizeMain.php.
1589 $self = new self();
1590 return $self->launch_ok();
1591 }
1592
1593 public function get_imgopt_provider_userstatus() {
1594 static $_provider_userstatus = null;
1595
1596 if ( is_null( $_provider_userstatus ) ) {
1597 $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1598 if ( is_array( $_stat ) ) {
1599 if ( array_key_exists( 'Status', $_stat ) ) {
1600 $_provider_userstatus['Status'] = $_stat['Status'];
1601 } else {
1602 // if no stats then we assume all is well.
1603 $_provider_userstatus['Status'] = 2;
1604 }
1605 if ( array_key_exists( 'timestamp', $_stat ) ) {
1606 $_provider_userstatus['timestamp'] = $_stat['timestamp'];
1607 } else {
1608 // if no timestamp then we return "".
1609 $_provider_userstatus['timestamp'] = '';
1610 }
1611 } else {
1612 // no provider_stat yet, assume/ return all OK.
1613 $_provider_userstatus['Status'] = 2;
1614 $_provider_userstatus['timestamp'] = '';
1615 }
1616 }
1617
1618 return $_provider_userstatus;
1619 }
1620 }
1621