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

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

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