PluginProbe
Autoptimize / 3.1.5
Autoptimize v3.1.5
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.5, at classes/autoptimizeImages.php

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