PluginProbe
Autoptimize / 2.6.1
Autoptimize v2.6.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.6.1, at classes/autoptimizeImages.php

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