PluginProbe
Autoptimize / 2.5.1
Autoptimize v2.5.1
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 2.5.1, at classes/autoptimizeImages.php

1,228 lines 53.9 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 }
35
36 public function set_options( array $options )
37 {
38 $this->options = $options;
39
40 return $this;
41 }
42
43 public static function fetch_options()
44 {
45 $value = get_option( 'autoptimize_imgopt_settings' );
46 if ( empty( $value ) ) {
47 // Fallback to returning defaults when no stored option exists yet.
48 $value = autoptimizeConfig::get_ao_imgopt_default_options();
49 }
50
51 // get service availability and add it to the options-array.
52 $value['availabilities'] = get_option( 'autoptimize_service_availablity' );
53
54 if ( empty( $value['availabilities'] ) ) {
55 $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
56 }
57
58 return $value;
59 }
60
61 public static function imgopt_active()
62 {
63 // function to quickly check if imgopt is active, used below but also in
64 // autoptimizeMain.php to start ob_ even if no HTML, JS or CSS optimizing is done
65 // and does not use/ request the availablity data (which could slow things down).
66 static $imgopt_active = null;
67
68 if ( null === $imgopt_active ) {
69 $opts = get_option( 'autoptimize_imgopt_settings', '' );
70 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'] ) {
71 $imgopt_active = true;
72 } else {
73 $imgopt_active = false;
74 }
75 }
76
77 return $imgopt_active;
78 }
79
80 /**
81 * Helper for getting a singleton instance. While being an
82 * anti-pattern generally, it comes in handy for now from a
83 * readability/maintainability perspective, until we get some
84 * proper dependency injection going.
85 *
86 * @return self
87 */
88 public static function instance()
89 {
90 if ( null === self::$instance ) {
91 self::$instance = new self();
92 }
93
94 return self::$instance;
95 }
96
97 public function run()
98 {
99 if ( is_admin() ) {
100 add_action( 'admin_menu', array( $this, 'imgopt_admin_menu' ) );
101 add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_imgopt_tab' ), 9 );
102 } else {
103 $this->run_on_frontend();
104 }
105 }
106
107 public function run_on_frontend() {
108 if ( ! $this->should_run() ) {
109 if ( $this->should_lazyload() ) {
110 add_filter(
111 'autoptimize_html_after_minify',
112 array( $this, 'filter_lazyload_images' ),
113 10,
114 1
115 );
116 add_action(
117 'wp_footer',
118 array( $this, 'add_lazyload_js_footer' ),
119 10,
120 0
121 );
122 }
123 return;
124 }
125
126 $active = false;
127
128 if ( apply_filters( 'autoptimize_filter_imgopt_do', true ) ) {
129 add_filter(
130 'autoptimize_html_after_minify',
131 array( $this, 'filter_optimize_images' ),
132 10,
133 1
134 );
135 $active = true;
136 }
137
138 if ( apply_filters( 'autoptimize_filter_imgopt_do_css', true ) ) {
139 add_filter(
140 'autoptimize_filter_base_replace_cdn',
141 array( $this, 'filter_optimize_css_images' ),
142 10,
143 1
144 );
145 $active = true;
146 }
147
148 if ( $active ) {
149 add_filter(
150 'autoptimize_extra_filter_tobepreconn',
151 array( $this, 'filter_preconnect_imgopt_url' ),
152 10,
153 1
154 );
155 }
156
157 if ( $this->should_lazyload() ) {
158 add_action(
159 'wp_footer',
160 array( $this, 'add_lazyload_js_footer' )
161 );
162 }
163 }
164
165 /**
166 * Basic checks before we can run.
167 *
168 * @return bool
169 */
170 protected function should_run()
171 {
172 $opts = $this->options;
173 $service_not_down = ( 'down' !== $opts['availabilities']['extra_imgopt']['status'] );
174 $not_launch_status = ( 'launch' !== $opts['availabilities']['extra_imgopt']['status'] );
175
176 $do_cdn = true;
177 $_userstatus = $this->get_imgopt_provider_userstatus();
178 if ( -2 == $_userstatus['Status'] ) {
179 $do_cdn = false;
180 }
181
182 if (
183 $this->imgopt_active()
184 && $do_cdn
185 && $service_not_down
186 && ( $not_launch_status || $this->launch_ok() )
187 ) {
188 return true;
189 }
190 return false;
191 }
192
193 public function get_imgopt_host()
194 {
195 static $imgopt_host = null;
196
197 if ( null === $imgopt_host ) {
198 $imgopt_host = 'https://cdn.shortpixel.ai/';
199 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
200 if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
201 $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
202 }
203 $imgopt_host = apply_filters( 'autoptimize_filter_imgopt_host', $imgopt_host );
204 }
205
206 return $imgopt_host;
207 }
208
209 public static function get_imgopt_host_wrapper()
210 {
211 // needed for CI tests.
212 $self = new self();
213 return $self->get_imgopt_host();
214 }
215
216 public static function get_service_url_suffix()
217 {
218 $suffix = '/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
219
220 return $suffix;
221 }
222
223 public function get_img_quality_string()
224 {
225 static $quality = null;
226
227 if ( null === $quality ) {
228 $q_array = $this->get_img_quality_array();
229 $setting = $this->get_img_quality_setting();
230 $quality = apply_filters(
231 'autoptimize_filter_imgopt_quality',
232 'q_' . $q_array[ $setting ]
233 );
234 }
235
236 return $quality;
237 }
238
239 public function get_img_quality_array()
240 {
241 static $map = null;
242
243 if ( null === $map ) {
244 $map = array(
245 '1' => 'lossy',
246 '2' => 'glossy',
247 '3' => 'lossless',
248 );
249 $map = apply_filters(
250 'autoptimize_filter_imgopt_quality_array',
251 $map
252 );
253 }
254
255 return $map;
256 }
257
258 public function get_img_quality_setting()
259 {
260 static $q = null;
261
262 if ( null === $q ) {
263 if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_select_field_2', $this->options ) ) {
264 $setting = $this->options['autoptimize_imgopt_select_field_2'];
265 }
266
267 if ( ! isset( $setting ) || empty( $setting ) || ( '1' !== $setting && '3' !== $setting ) ) {
268 // default image opt. value is 2 ("glossy").
269 $q = '2';
270 } else {
271 $q = $setting;
272 }
273 }
274
275 return $q;
276 }
277
278 public function filter_preconnect_imgopt_url( array $in )
279 {
280 $url_parts = parse_url( $this->get_imgopt_base_url() );
281 $in[] = $url_parts['scheme'] . '://' . $url_parts['host'];
282
283 return $in;
284 }
285
286 /**
287 * Makes sure given url contains the full scheme and hostname
288 * in case they're not present already.
289 *
290 * @param string $in Image url to normalize.
291 *
292 * @return string
293 */
294 private function normalize_img_url( $in )
295 {
296 // Only parse the site url once.
297 static $parsed_site_url = null;
298 if ( null === $parsed_site_url ) {
299 $parsed_site_url = parse_url( site_url() );
300 }
301
302 // get CDN domain once.
303 static $cdn_domain = null;
304 if ( is_null( $cdn_domain ) ) {
305 $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', get_option( 'autoptimize_cdn_url', '' ) );
306 if ( ! empty( $cdn_url ) ) {
307 $cdn_domain = parse_url( $cdn_url, PHP_URL_HOST );
308 } else {
309 $cdn_domain = '';
310 }
311 }
312
313 /**
314 * This method gets called a lot, often for identical urls it seems.
315 * `filter_optimize_css_images()` calls us, uses the resulting url and
316 * gives it to `can_optimize_image()`, and if that returns trueish
317 * then `build_imgopt_url()` is called (which, again, calls this method).
318 * Until we dig deeper into whether this all must really happen that
319 * way, having an internal cache here helps (to avoid doing repeated
320 * identical string operations).
321 */
322 static $cache = null;
323 if ( null === $cache ) {
324 $cache = array();
325 }
326
327 // Do the work on cache miss only.
328 if ( ! isset( $cache[ $in ] ) ) {
329 // Default to what was given to us.
330 $result = trim( $in );
331 if ( autoptimizeUtils::is_protocol_relative( $in ) ) {
332 $result = $parsed_site_url['scheme'] . ':' . $in;
333 } elseif ( 0 === strpos( $in, '/' ) ) {
334 // Root-relative...
335 $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'];
336 $result .= $in;
337 } elseif ( ! empty( $cdn_domain ) && strpos( $in, $cdn_domain ) !== 0 ) {
338 $result = str_replace( $cdn_domain, $parsed_site_url['host'], $in );
339 }
340
341 $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
342
343 // Store in cache.
344 $cache[ $in ] = $result;
345 }
346
347 return $cache[ $in ];
348 }
349
350 public function filter_optimize_css_images( $in )
351 {
352 $in = $this->normalize_img_url( $in );
353
354 if ( $this->can_optimize_image( $in ) ) {
355 return $this->build_imgopt_url( $in, '', '' );
356 } else {
357 return $in;
358 }
359 }
360
361 private function get_imgopt_base_url()
362 {
363 static $imgopt_base_url = null;
364
365 if ( null === $imgopt_base_url ) {
366 $imgopt_host = $this->get_imgopt_host();
367 $quality = $this->get_img_quality_string();
368 $ret_val = apply_filters( 'autoptimize_filter_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
369 $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
370 $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
371 }
372
373 return $imgopt_base_url;
374 }
375
376 private function can_optimize_image( $url )
377 {
378 static $cdn_url = null;
379 static $nopti_images = null;
380
381 if ( null === $cdn_url ) {
382 $cdn_url = apply_filters(
383 'autoptimize_filter_base_cdnurl',
384 get_option( 'autoptimize_cdn_url', '' )
385 );
386 }
387
388 if ( null === $nopti_images ) {
389 $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', '' );
390 }
391
392 $site_host = AUTOPTIMIZE_SITE_DOMAIN;
393 $url = $this->normalize_img_url( $url );
394 $url_parsed = parse_url( $url );
395
396 if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
397 return false;
398 } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
399 return false;
400 } elseif ( strpos( $url, '.php' ) !== false ) {
401 return false;
402 } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
403 // fixme: better check against end of string.
404 return false;
405 } elseif ( ! empty( $nopti_images ) ) {
406 $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
407 foreach ( $nopti_images_array as $nopti_image ) {
408 if ( strpos( $url, $nopti_image ) !== false ) {
409 return false;
410 }
411 }
412 }
413 return true;
414 }
415
416 private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
417 {
418 // sanitize width and height.
419 if ( strpos( $width, '%' ) !== false ) {
420 $width = 0;
421 }
422 if ( strpos( $height, '%' ) !== false ) {
423 $height = 0;
424 }
425 $width = (int) $width;
426 $height = (int) $height;
427
428 $filtered_url = apply_filters(
429 'autoptimize_filter_imgopt_build_url',
430 $orig_url,
431 $width,
432 $height
433 );
434
435 // If filter modified the url, return that.
436 if ( $filtered_url !== $orig_url ) {
437 return $filtered_url;
438 }
439
440 $orig_url = $this->normalize_img_url( $orig_url );
441 $imgopt_base_url = $this->get_imgopt_base_url();
442 $imgopt_size = '';
443
444 if ( $width && 0 !== $width ) {
445 $imgopt_size = ',w_' . $width;
446 }
447
448 if ( $height && 0 !== $height ) {
449 $imgopt_size .= ',h_' . $height;
450 }
451
452 $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
453
454 return $url;
455 }
456
457 public function replace_data_thumbs( $matches )
458 {
459 return $this->replace_img_callback( $matches, 150, 150 );
460 }
461
462 public function replace_img_callback( $matches, $width = 0, $height = 0 )
463 {
464 $_normalized_img_url = $this->normalize_img_url( $matches[1] );
465 if ( $this->can_optimize_image( $matches[1] ) ) {
466 return str_replace( $matches[1], $this->build_imgopt_url( $_normalized_img_url, $width, $height ), $matches[0] );
467 } else {
468 return $matches[0];
469 }
470 }
471
472 public function filter_optimize_images( $in )
473 {
474 /*
475 * potential future functional improvements:
476 *
477 * filter for critical CSS.
478 */
479 $to_replace = array();
480
481 // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
482 if ( $this->should_lazyload() ) {
483 $in = autoptimizeBase::replace_contents_with_marker_if_exists(
484 'SCRIPT',
485 '<script',
486 '#<(?:no)?script.*?<\/(?:no)?script>#is',
487 $in
488 );
489 }
490
491 // extract img tags.
492 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
493 foreach ( $matches[0] as $tag ) {
494 $orig_tag = $tag;
495 $imgopt_w = '';
496 $imgopt_h = '';
497
498 // first do (data-)srcsets.
499 if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
500 foreach ( $allsrcsets as $srcset ) {
501 $srcset = $srcset[2];
502 $srcsets = explode( ',', $srcset );
503 foreach ( $srcsets as $indiv_srcset ) {
504 $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
505 if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
506 $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
507 }
508 if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
509 $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
510 $tag = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
511 }
512 }
513 }
514 }
515
516 // proceed with img src.
517 // get width and height and add to $imgopt_size.
518 $_get_size = $this->get_size_from_tag( $tag );
519 $imgopt_w = $_get_size['width'];
520 $imgopt_h = $_get_size['height'];
521
522 // then start replacing images src.
523 if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
524 foreach ( $urls as $url ) {
525 $full_src_orig = $url[0];
526 $url = $url[1];
527 if ( $this->can_optimize_image( $url ) ) {
528 $imgopt_url = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
529 $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
530 $tag = str_replace( $full_src_orig, $full_imgopt_src, $tag );
531 }
532 }
533 }
534
535 // do lazyload stuff.
536 if ( $this->should_lazyload( $in ) ) {
537 // first do lpiq placeholder logic.
538 if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
539 // if all img src have been replaced during srcset, we have to extract the
540 // origin url from the imgopt one to be able to set a lqip placeholder.
541 $_url = substr( $url, strpos( $url, '/http' ) + 1 );
542 } else {
543 $_url = $url;
544 }
545
546 if ( $this->can_optimize_image( $_url ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', true ) ) {
547 $lqip_w = '';
548 $lqip_h = '';
549 if ( isset( $imgopt_w ) && ! empty( $imgopt_w ) ) {
550 $lqip_w = ',w_' . $imgopt_w;
551 }
552 if ( isset( $imgopt_h ) && ! empty( $imgopt_h ) ) {
553 $lqip_h = ',h_' . $imgopt_h;
554 }
555 $placeholder = $this->get_imgopt_host() . 'client/q_lqip,ret_wait' . $lqip_w . $lqip_h . '/' . $_url;
556 }
557 // then call add_lazyload-function with lpiq placeholder if set.
558 $tag = $this->add_lazyload( $tag, $placeholder );
559 }
560
561 // and add tag to array for later replacement.
562 if ( $tag !== $orig_tag ) {
563 $to_replace[ $orig_tag ] = $tag;
564 }
565 }
566 }
567
568 // and replace all.
569 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
570
571 // img thumbnails in e.g. woocommerce.
572 if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_imgopt_datathumbs', true ) ) {
573 $out = preg_replace_callback(
574 '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
575 array( $this, 'replace_data_thumbs' ),
576 $out
577 );
578 }
579
580 // background-image in inline style.
581 if ( strpos( $out, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
582 $out = preg_replace_callback(
583 '/style=(?:"|\').*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
584 array( $this, 'replace_img_callback' ),
585 $out
586 );
587 }
588
589 // lazyload: restore noscript tags + lazyload picture source tags.
590 if ( $this->should_lazyload() ) {
591 $out = autoptimizeBase::restore_marked_content(
592 'SCRIPT',
593 $out
594 );
595
596 $out = $this->process_picture_tag( $out, true, true );
597 } else {
598 $out = $this->process_picture_tag( $out, true, false );
599 }
600
601 return $out;
602 }
603
604 public function get_size_from_tag( $tag ) {
605 // reusable function to extract widht and height from an image tag
606 // enforcing a filterable maximum width and height (default 4999X4999).
607 $width = '';
608 $height = '';
609
610 if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $_width ) ) {
611 if ( strpos( $_width[2], '%' ) === false ) {
612 $width = (int) $_width[2];
613 }
614 }
615 if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $_height ) ) {
616 if ( strpos( $_height[2], '%' ) === false ) {
617 $height = (int) $_height[2];
618 }
619 }
620
621 // check for and enforce (filterable) max sizes.
622 $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
623 if ( $width > $_max_width ) {
624 $_width = $_max_width;
625 $height = $_width / $width * $height;
626 $width = $_width;
627 }
628 $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
629 if ( $height > $_max_height ) {
630 $_height = $_max_height;
631 $width = $_height / $height * $width;
632 $height = $_height;
633 }
634
635 return array(
636 'width' => $width,
637 'height' => $height,
638 );
639 }
640
641 /**
642 * Lazyload functions
643 */
644 public static function should_lazyload_wrapper() {
645 // needed in autoptimizeMain.php.
646 $self = new self();
647 return $self->should_lazyload();
648 }
649
650 public function should_lazyload( $context = '' ) {
651 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) ) {
652 $lazyload_return = true;
653 } else {
654 $lazyload_return = false;
655 }
656 $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return, $context );
657
658 return $lazyload_return;
659 }
660
661 public function filter_lazyload_images( $in )
662 {
663 // only used is image optimization is NOT active but lazyload is.
664 $to_replace = array();
665
666 // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
667 $out = autoptimizeBase::replace_contents_with_marker_if_exists(
668 'SCRIPT',
669 '<script',
670 '#<(?:no)?script.*?<\/(?:no)?script>#is',
671 $in
672 );
673
674 // extract img tags and add lazyload attribs.
675 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
676 foreach ( $matches[0] as $tag ) {
677 if ( $this->should_lazyload( $out ) ) {
678 $to_replace[ $tag ] = $this->add_lazyload( $tag );
679 }
680 }
681 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $out );
682 }
683
684 // and also lazyload picture tag.
685 $out = $this->process_picture_tag( $out, false, true );
686
687 // restore noscript tags.
688 $out = autoptimizeBase::restore_marked_content(
689 'SCRIPT',
690 $out
691 );
692
693 return $out;
694 }
695
696 public function add_lazyload( $tag, $placeholder = '' ) {
697 // adds actual lazyload-attributes to an image node.
698 if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
699 $tag = $this->maybe_fix_missing_quotes( $tag );
700
701 // store original tag for use in noscript version.
702 $noscript_tag = '<noscript>' . $tag . '</noscript>';
703
704 // insert lazyload class.
705 $tag = $this->inject_classes_in_tag( $tag, 'lazyload ' );
706
707 if ( ! $placeholder || empty( $placeholder ) ) {
708 // get image width & heigth for placeholder fun (and to prevent content reflow).
709 $_get_size = $this->get_size_from_tag( $tag );
710 $width = $_get_size['width'];
711 $height = $_get_size['height'];
712 if ( false === $width ) {
713 $widht = 210; // default width for SVG placeholder.
714 }
715 if ( false === $height ) {
716 $heigth = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
717 }
718
719 // insert the actual lazyload stuff.
720 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
721 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
722 }
723 $tag = str_replace( ' src=', ' src=\'' . $placeholder . '\' data-src=', $tag );
724 $tag = str_replace( ' srcset=', ' data-srcset=', $tag );
725
726 // move sizes to data-sizes unless filter says no.
727 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_move_sizes', true ) ) {
728 $tag = str_replace( 'sizes=', 'data-sizes=', $tag );
729 }
730
731 // add the noscript-tag from earlier.
732 $tag = $noscript_tag . $tag;
733 $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
734 }
735
736 return $tag;
737 }
738
739 public function add_lazyload_js_footer() {
740 // The JS will by default be excluded form autoptimization but this can be changed with a filter.
741 $noptimize_flag = '';
742 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
743 $noptimize_flag = ' data-noptimize="1"';
744 }
745
746 // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
747 echo apply_filters( 'autoptimize_filter_imgopt_lazyload_cssoutput', '<style>.lazyload,.lazyloading{opacity:0;}.lazyloaded{opacity:1;transition:opacity 300ms;}</style><noscript><style>.lazyload{display:none;}</style></noscript>' );
748 echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
749 echo '<script async' . $noptimize_flag . ' src=\'' . plugins_url( 'external/js/lazysizes.min.js', __FILE__ ) . '\'></script>';
750
751 // And add webp detection and loading JS.
752 if ( $this->should_webp() ) {
753 $_webp_detect = "function c_webp(A){var n=new Image;n.onload=function(){var e=0<n.width&&0<n.height;A(e)},n.onerror=function(){A(!1)},n.src='data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA=='}function s_webp(e){window.supportsWebP=e}c_webp(s_webp);";
754 $_webp_load = "document.addEventListener('lazybeforeunveil',function({target:c}){supportsWebP&&['data-src','data-srcset'].forEach(function(a){attr=c.getAttribute(a),null!==attr&&c.setAttribute(a,attr.replace(/\/client\//,'/client/to_webp,'))})});";
755 echo apply_filters( 'autoptimize_filter_imgopt_webp_js', '<script' . $noptimize_flag . '>' . $_webp_detect . $_webp_load . '</script>' );
756 }
757 }
758
759 public function get_lazyload_exclusions() {
760 // returns array of strings that if found in an <img tag will stop the img from being lazy-loaded.
761 static $exclude_lazyload_array = null;
762
763 if ( null === $exclude_lazyload_array ) {
764 $options = $this->options;
765
766 // set default exclusions.
767 $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg' );
768
769 // add from setting.
770 if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
771 $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
772 if ( ! empty( $exclude_lazyload_option ) ) {
773 $exclude_lazyload_array = array_merge( $exclude_lazyload_array, array_filter( array_map( 'trim', explode( ',', $options['autoptimize_imgopt_text_field_5'] ) ) ) );
774 }
775 }
776
777 // and filter for developer-initiated changes.
778 $exclude_lazyload_array = apply_filters( 'autoptimize_filter_imgopt_lazyload_exclude_array', $exclude_lazyload_array );
779 }
780
781 return $exclude_lazyload_array;
782 }
783
784 public function inject_classes_in_tag( $tag, $target_class ) {
785 if ( strpos( $tag, 'class=' ) !== false ) {
786 $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
787 } else {
788 $tag = str_replace( '<img ', '<img class="' . trim( $target_class ) . '" ', $tag );
789 }
790
791 return $tag;
792 }
793
794 public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
795 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';
796 }
797
798 public function should_webp() {
799 static $webp_return = null;
800
801 if ( is_null( $webp_return ) ) {
802 // webp only works if imgopt and lazyload are also active.
803 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && $this->imgopt_active() ) {
804 $webp_return = true;
805 } else {
806 $webp_return = false;
807 }
808 }
809
810 return $webp_return;
811 }
812
813 public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
814 // check if "<picture" is present and if filter allows us to process <picture>.
815 if ( strpos( $in, '<picture' ) === false || apply_filters( 'autoptimize_filter_imgopt_dopicture', true ) === false ) {
816 return $in;
817 }
818
819 $_exclusions = $this->get_lazyload_exclusions();
820 $to_replace_pict = array();
821
822 // extract and process each picture-node.
823 preg_match_all( '#<picture.*</picture>#Usmi', $in, $_pictures, PREG_SET_ORDER );
824 foreach ( $_pictures as $_picture ) {
825 $_picture = $this->maybe_fix_missing_quotes( $_picture );
826 if ( strpos( $_picture[0], '<source ' ) !== false && preg_match_all( '#<source .*srcset=(?:"|\')(?!data)(.*)(?:"|\').*>#Usmi', $_picture[0], $_sources, PREG_SET_ORDER ) !== false ) {
827 foreach ( $_sources as $_source ) {
828 $_picture_replacement = $_source[0];
829
830 // should we optimize the image?
831 if ( $imgopt && $this->can_optimize_image( $_source[1] ) ) {
832 $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
833 }
834 // should we lazy-load?
835 if ( $lazy && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
836 $_picture_replacement = str_replace( ' srcset=', ' data-srcset=', $_picture_replacement );
837 }
838 $to_replace_pict[ $_source[0] ] = $_picture_replacement;
839 }
840 }
841 }
842
843 // and return the fully procesed $in.
844 $out = str_replace( array_keys( $to_replace_pict ), array_values( $to_replace_pict ), $in );
845
846 return $out;
847 }
848
849 public function maybe_fix_missing_quotes( $tag_in ) {
850 // W3TC's Minify_HTML class removes quotes around attribute value, this re-adds them.
851 if ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) {
852 return preg_replace( '/=([^("|\')]*)(\s|>)/U', '=\'$1\'$2', $tag_in );
853 } else {
854 return $tag_in;
855 }
856 }
857
858 /**
859 * Admin page logic and related functions below.
860 */
861 public function imgopt_admin_menu()
862 {
863 add_submenu_page(
864 null,
865 'autoptimize_imgopt',
866 'autoptimize_imgopt',
867 'manage_options',
868 'autoptimize_imgopt',
869 array( $this, 'imgopt_options_page' )
870 );
871 register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
872 }
873
874 public function add_imgopt_tab( $in )
875 {
876 $in = array_merge( $in, array( 'autoptimize_imgopt' => __( 'Images', 'autoptimize' ) ) );
877
878 return $in;
879 }
880
881 public function imgopt_options_page()
882 {
883 // Check querystring for "refreshCacheChecker" and call cachechecker if so.
884 if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
885 $this->query_img_provider_stats();
886 }
887
888 $options = $this->fetch_options();
889 $sp_url_suffix = $this->get_service_url_suffix();
890 ?>
891 <style>
892 #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
893 #ao_settings_form .form-table th {font-weight: normal;}
894 #autoptimize_imgopt_descr{font-size: 120%;}
895 </style>
896 <div class="wrap">
897 <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
898 <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
899 <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
900 <div class="notice-warning notice"><p>
901 <?php
902 // translators: "Autoptimize support forum" will appear in a "a href".
903 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>' );
904 ?>
905 </p></div>
906 <?php } ?>
907
908 <?php if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! autoptimizeImages::instance()->launch_ok() ) { ?>
909 <div class="notice-warning notice"><p>
910 <?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' ); ?>
911 </p></div>
912 <?php } ?>
913
914 <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
915 <div class="notice-warning notice"><p>
916 <?php
917 // translators: "disable Jetpack's site accelerator for images" will appear in a "a href" linking to the jetpack settings page.
918 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>' );
919 ?>
920 </p></div>
921 <?php } ?>
922 <form id='ao_settings_form' action='options.php' method='post'>
923 <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
924 <h2><?php _e( 'Image optimization', 'autoptimize' ); ?></h2>
925 <span id='autoptimize_imgopt_descr'><?php _e( 'Make your site significantly faster by just ticking a couple of checkboxes to optimize and lazy load your images, WebP support included!', 'autoptimize' ); ?></span>
926 <table class="form-table">
927 <tr>
928 <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
929 <td>
930 <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 _e( 'Optimize images on the fly and serve them from Shortpixel\'s global CDN.', 'autoptimize' ); ?></label>
931 <?php
932 // show shortpixel status.
933 $_notice = autoptimizeImages::instance()->get_status_notice();
934 if ( $_notice ) {
935 switch ( $_notice['status'] ) {
936 case 2:
937 $_notice_color = 'green';
938 break;
939 case 1:
940 $_notice_color = 'orange';
941 break;
942 case -1:
943 $_notice_color = 'red';
944 break;
945 case -2:
946 $_notice_color = 'red';
947 break;
948 default:
949 $_notice_color = 'green';
950 }
951 echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
952 } else {
953 // translators: link points to shortpixel.
954 $upsell_msg_1 = '<p>' . sprintf( __( 'Get more Google love and improve your website\'s loading speed by having the images optimized on the fly (also in the "next-gen" WebP image format) by %1$sShortPixel%2$s and then cached and served fast from Shortpixel\'s global CDN.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>' );
955 if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
956 $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' );
957 } else {
958 // translators: link points to shortpixel.
959 $upsell_msg_2 = sprintf( __( '%1$sSign-up now%2$s to receive a 1 000 bonus + 50&#37; more image optimization credits regardless of the traffic used. More image optimizations can be purchased starting with $4.99.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>' );
960 }
961 echo apply_filters( 'autoptimize_imgopt_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
962 }
963 // translators: link points to shortpixel FAQ.
964 $faqcopy = sprintf( __( '<strong>Questions</strong>? Have a look at the %1$sShortPixel FAQ%2$s!', 'autoptimize' ), '<strong><a href="https://shortpixel.helpscoutdocs.com/category/60-shortpixel-ai-cdn" target="_blank">', '</strong></a>' );
965 // translators: links points to shortpixel TOS & Privacy Policy.
966 $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/pp' . $sp_url_suffix . '" target="_blank">', '</a>' );
967 echo apply_filters( 'autoptimize_imgopt_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
968 ?>
969 </td>
970 </tr>
971 <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"'; } ?>>
972 <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
973 <td>
974 <label>
975 <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
976 <?php
977 $_imgopt_array = autoptimizeImages::instance()->get_img_quality_array();
978 $_imgopt_val = autoptimizeImages::instance()->get_img_quality_setting();
979
980 foreach ( $_imgopt_array as $key => $value ) {
981 echo '<option value="' . $key . '"';
982 if ( $_imgopt_val == $key ) {
983 echo ' selected';
984 }
985 echo '>' . ucfirst( $value ) . '</option>';
986 }
987 echo "\n";
988 ?>
989 </select>
990 </label>
991 <p>
992 <?php
993 // translators: link points to shortpixel image test page.
994 echo apply_filters( 'autoptimize_imgopt_imgopt_quality_copy', sprintf( __( 'You can %1$stest compression levels here%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/oic' . $sp_url_suffix . '" target="_blank">', '</a>' ) );
995 ?>
996 </p>
997 </td>
998 </tr>
999 <tr id='autoptimize_imgopt_webp' <?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"'; } ?>>
1000 <th scope="row"><?php _e( 'Load WebP in supported browsers?', 'autoptimize' ); ?></th>
1001 <td>
1002 <label><input type='checkbox' id='autoptimize_imgopt_webp_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_3'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Automatically serve "next-gen" WebP image format to any browser that supports it (requires lazy load to be active).', 'autoptimize' ); ?></label>
1003 </td>
1004 </tr>
1005 <tr>
1006 <th scope="row"><?php _e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1007 <td>
1008 <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>
1009 </td>
1010 </tr>
1011 <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="hidden"'; } ?>>
1012 <th scope="row"><?php _e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
1013 <td>
1014 <label><input type='text' style='width:80%' id='autoptimize_imgopt_lazyload_exclusions' 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>
1015 </td>
1016 </tr>
1017 </table>
1018 <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1019 </form>
1020 <script>
1021 jQuery(document).ready(function() {
1022 jQuery("#autoptimize_imgopt_checkbox").change(function() {
1023 if (this.checked) {
1024 jQuery("#autoptimize_imgopt_quality").show("slow");
1025 jQuery("#autoptimize_imgopt_webp").show("slow");
1026 } else {
1027 jQuery("#autoptimize_imgopt_quality").hide("slow");
1028 jQuery("#autoptimize_imgopt_webp").hide("slow");
1029 }
1030 });
1031 jQuery("#autoptimize_imgopt_webp_checkbox").change(function() {
1032 if (this.checked) {
1033 jQuery("#autoptimize_imgopt_lazyload_checkbox")[0].checked = true;
1034 jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1035 }
1036 });
1037 jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1038 if (this.checked) {
1039 jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1040 } else {
1041 jQuery("#autoptimize_imgopt_lazyload_exclusions").hide("slow");
1042 jQuery("#autoptimize_imgopt_webp_checkbox")[0].checked = false;
1043 }
1044 });
1045 });
1046 </script>
1047 <?php
1048 }
1049
1050 /**
1051 * Ïmg opt status as used on dashboard.
1052 */
1053 public function get_imgopt_status_notice() {
1054 if ( $this->imgopt_active() ) {
1055 $_imgopt_notice = '';
1056 $_stat = get_option( 'autoptimize_imgopt_provider_stat', '' );
1057 $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
1058 $_imgopt_upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
1059
1060 if ( is_array( $_stat ) ) {
1061 if ( 1 == $_stat['Status'] ) {
1062 // translators: "add more credits" will appear in a "a href".
1063 $_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.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
1064 } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1065 // translators: "add more credits" will appear in a "a href".
1066 $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota was used, %1$sadd more credits%2$s to keep fast serving optimized images on your site', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
1067 $_imgopt_stats_refresh_url = add_query_arg( array(
1068 'page' => 'autoptimize_imgopt',
1069 'refreshImgProvStats' => '1',
1070 ), admin_url( 'options-general.php' ) );
1071 if ( $_stat && array_key_exists( 'timestamp', $_stat ) && ! empty( $_stat['timestamp'] ) ) {
1072 $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( get_option( 'time_format' ), $_stat['timestamp'] );
1073 } else {
1074 $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1075 }
1076 $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1077 // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1078 $_imgopt_notice .= sprintf( __( 'click %1$shere to refresh%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1079 } else {
1080 $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1081 // translators: "log in to check your account" will appear in a "a href".
1082 $_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>' );
1083 }
1084 $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
1085
1086 return array(
1087 'status' => $_stat['Status'],
1088 'notice' => $_imgopt_notice,
1089 );
1090 }
1091 }
1092 return false;
1093 }
1094
1095 public static function get_imgopt_status_notice_wrapper() {
1096 // needed for notice being shown in autoptimizeCacheChecker.php.
1097 $self = new self();
1098 return $self->get_imgopt_status_notice();
1099 }
1100
1101 /**
1102 * Get img provider stats (used to display notice).
1103 */
1104 public function query_img_provider_stats() {
1105 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) ) {
1106 $url = '';
1107 $endpoint = $this->get_imgopt_host() . 'read-domain/';
1108 $domain = AUTOPTIMIZE_SITE_DOMAIN;
1109
1110 // make sure parse_url result makes sense, keeping $url empty if not.
1111 if ( $domain && ! empty( $domain ) ) {
1112 $url = $endpoint . $domain;
1113 }
1114
1115 $url = apply_filters(
1116 'autoptimize_filter_imgopt_stat_url',
1117 $url
1118 );
1119
1120 // only do the remote call if $url is not empty to make sure no parse_url
1121 // weirdness results in useless calls.
1122 if ( ! empty( $url ) ) {
1123 $response = wp_remote_get( $url );
1124 if ( ! is_wp_error( $response ) ) {
1125 if ( '200' == wp_remote_retrieve_response_code( $response ) ) {
1126 $stats = json_decode( wp_remote_retrieve_body( $response ), true );
1127 update_option( 'autoptimize_imgopt_provider_stat', $stats );
1128 }
1129 }
1130 }
1131 }
1132 }
1133
1134 public static function get_img_provider_stats()
1135 {
1136 // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
1137 $self = new self();
1138 return $self->query_img_provider_stats();
1139 }
1140
1141 /**
1142 * Determines and returns the service launch status.
1143 *
1144 * @return bool
1145 */
1146 public function launch_ok()
1147 {
1148 static $launch_status = null;
1149
1150 if ( null === $launch_status ) {
1151 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
1152 $magic_number = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1153 $has_launched = get_option( 'autoptimize_imgopt_launched', '' );
1154 $launch_status = false;
1155 if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
1156 $launch_status = true;
1157 if ( ! $has_launched ) {
1158 update_option( 'autoptimize_imgopt_launched', 'on' );
1159 }
1160 }
1161 }
1162
1163 return $launch_status;
1164 }
1165
1166 public static function launch_ok_wrapper() {
1167 // needed for "plug" notice in autoptimizeMain.php.
1168 $self = new self();
1169 return $self->launch_ok();
1170 }
1171
1172 public function get_imgopt_provider_userstatus() {
1173 static $_provider_userstatus = null;
1174
1175 if ( is_null( $_provider_userstatus ) ) {
1176 $_stat = get_option( 'autoptimize_imgopt_provider_stat', '' );
1177 if ( is_array( $_stat ) ) {
1178 if ( array_key_exists( 'Status', $_stat ) ) {
1179 $_provider_userstatus['Status'] = $_stat['Status'];
1180 } else {
1181 // if no stats then we assume all is well.
1182 $_provider_userstatus['Status'] = 2;
1183 }
1184 if ( array_key_exists( 'timestamp', $_stat ) ) {
1185 $_provider_userstatus['timestamp'] = $_stat['timestamp'];
1186 } else {
1187 // if no timestamp then we return "".
1188 $_provider_userstatus['timestamp'] = '';
1189 }
1190 }
1191 }
1192
1193 return $_provider_userstatus;
1194 }
1195
1196 public function get_status_notice() {
1197 if ( $this->imgopt_active() ) {
1198 $notice = '';
1199 $stat = $this->get_imgopt_provider_userstatus();
1200 $upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
1201 $assoc = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1202
1203 if ( is_array( $stat ) ) {
1204 if ( 1 == $stat['Status'] ) {
1205 // translators: "add more credits" will appear in a "a href".
1206 $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.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1207 } elseif ( -1 == $stat['Status'] || -2 == $stat['Status'] ) {
1208 // translators: "add more credits" will appear in a "a href".
1209 $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota was used, %1$sadd more credits%2$s to keep fast serving optimized images on your site.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1210 // translators: "associate your domain" will appear in a "a href".
1211 $notice = $notice . ' ' . sprintf( __( 'If you already have enough credits then you may need to %1$sassociate your domain%2$s to your Shortpixel account.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $assoc . '" target="_blank">', '</a>' );
1212 } else {
1213 $upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1214 // translators: "log in to check your account" will appear in a "a href".
1215 $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1216 }
1217 $notice = apply_filters( 'autoptimize_filter_imgopt_notice', $notice );
1218
1219 return array(
1220 'status' => $stat['Status'],
1221 'notice' => $notice,
1222 );
1223 }
1224 }
1225 return false;
1226 }
1227 }
1228