PluginProbe
Autoptimize / trunk
Autoptimize vtrunk
3.1.16 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 All 108 releases
← All changes | classes/autoptimizeImages.php +520 -160 2.6.0 → trunk View file →
@@ -21,8 +21,15 @@
21 21 *
22 22 * @var self|null
23 23 */
24 24 protected static $instance = null;
25 +
26 + /**
27 + * lazyload counter.
28 + *
29 + * @var int
30 + */
31 + protected $lazyload_counter = 0;
25 32
26 33 public function __construct( array $options = array() )
27 34 {
28 35 // If options are not provided, fetch them.
@@ -50,10 +57,30 @@
50 57
51 58 // get service availability and add it to the options-array.
52 59 $value['availabilities'] = autoptimizeOptionWrapper::get_option( 'autoptimize_service_availablity' );
53 60
54 - if ( empty( $value['availabilities'] ) ) {
55 - $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
61 + if ( empty( $value['availabilities'] ) || ! is_array( $value['availabilities'] ) ) {
62 + $value['availabilities'] = null;
63 +
64 + if ( true === autoptimizeImages::imgopt_active() ) {
65 + $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
66 + }
67 +
68 + if ( null === $value['availabilities'] ) {
69 + // We can't seem to check service availability, use mock result with imgopt status UP.
70 + $_mock_settings = array(
71 + 'extra_imgopt' => array(
72 + 'status' => 'up',
73 + 'hosts' => array(
74 + '1' => 'https://sp-ao.shortpixel.ai/',
75 + ),
76 + ),
77 + 'critcss' => array(
78 + 'status' => 'up',
79 + ),
80 + );
81 + $value['availabilities'] = $_mock_settings;
82 + }
56 83 }
57 84
58 85 return $value;
59 86 }
@@ -111,8 +138,14 @@
111 138 public function run_on_frontend() {
112 139 if ( ! $this->should_run() ) {
113 140 if ( $this->should_lazyload() ) {
114 141 add_filter(
142 + 'wp_lazy_loading_enabled',
143 + array( $this, 'should_disable_core_lazyload' ),
144 + 10,
145 + 3
146 + );
147 + add_filter(
115 148 'autoptimize_html_after_minify',
116 149 array( $this, 'filter_lazyload_images' ),
117 150 10,
118 151 1
@@ -139,8 +172,9 @@
139 172 $active = true;
140 173 }
141 174
142 175 if ( apply_filters( 'autoptimize_filter_imgopt_do_css', true ) ) {
176 + // fixme: also act on already minified CSS!
143 177 add_filter(
144 178 'autoptimize_filter_base_replace_cdn',
145 179 array( $this, 'filter_optimize_css_images' ),
146 180 10,
@@ -145,8 +179,16 @@
145 179 array( $this, 'filter_optimize_css_images' ),
146 180 10,
147 181 1
148 182 );
183 +
184 + add_filter(
185 + 'autoptimize_html_after_minify',
186 + array( $this, 'filter_optimize_inline_css_images' ),
187 + 10,
188 + 1
189 + );
190 +
149 191 $active = true;
150 192 }
151 193
152 194 if ( $active ) {
@@ -158,8 +200,14 @@
158 200 );
159 201 }
160 202
161 203 if ( $this->should_lazyload() ) {
204 + add_filter(
205 + 'wp_lazy_loading_enabled',
206 + array( $this, 'should_disable_core_lazyload' ),
207 + 10,
208 + 3
209 + );
162 210 add_action(
163 211 'wp_footer',
164 212 array( $this, 'add_lazyload_js_footer' ),
165 213 10,
@@ -168,8 +216,24 @@
168 216 }
169 217 }
170 218
171 219 /**
220 + * Disables core's native lazyload for images, not for iframes.
221 + *
222 + * @param bool $flag Incoming flag (mostly true).
223 + * @param string $tag Tag (img or iframe).
224 + * @param string $context Full context.
225 + *
226 + * @return bool
227 + */
228 + public function should_disable_core_lazyload( $flag = true, $tag = '', $context = '' ) {
229 + if ( 'img' === $tag ) {
230 + return false;
231 + }
232 + return $flag;
233 + }
234 +
235 + /**
172 236 * Basic checks before we can run.
173 237 *
174 238 * @return bool
175 239 */
@@ -201,9 +265,9 @@
201 265 {
202 266 static $imgopt_host = null;
203 267
204 268 if ( null === $imgopt_host ) {
205 - $imgopt_host = 'https://cdn.shortpixel.ai/';
269 + $imgopt_host = 'https://sp-ao.shortpixel.ai/';
206 270 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
207 271 if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
208 272 $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
209 273 }
@@ -221,9 +285,9 @@
221 285 }
222 286
223 287 public static function get_service_url_suffix()
224 288 {
225 - $suffix = '/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
289 + $suffix = '/af/ZQXTBY0109483/' . AUTOPTIMIZE_SITE_DOMAIN;
226 290
227 291 return $suffix;
228 292 }
229 293
@@ -336,11 +400,9 @@
336 400 // Default to (the trimmed version of) what was given to us.
337 401 $result = trim( $in );
338 402
339 403 // Some silly plugins wrap background images in html-encoded quotes, so remove those from the img url.
340 - if ( strpos( $result, '"' ) !== false ) {
341 - $result = str_replace( '"', '', $result );
342 - }
404 + $result = $this->fix_silly_bgimg_quotes( $result );
343 405
344 406 if ( autoptimizeUtils::is_protocol_relative( $result ) ) {
345 407 $result = $parsed_site_url['scheme'] . ':' . $result;
346 408 } elseif ( 0 === strpos( $result, '/' ) ) {
@@ -345,12 +407,18 @@
345 407 $result = $parsed_site_url['scheme'] . ':' . $result;
346 408 } elseif ( 0 === strpos( $result, '/' ) ) {
347 409 // Root-relative...
348 410 $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'] . $result;
349 - } elseif ( ! empty( $cdn_domain ) && strpos( $result, $cdn_domain ) !== 0 ) {
411 + } elseif ( ! empty( $cdn_domain ) && false === strpos( $this->get_imgopt_host(), $cdn_domain ) && strpos( $result, $cdn_domain ) !== 0 ) {
412 + // remove CDN except if it is the image optimization one.
350 413 $result = str_replace( $cdn_domain, $parsed_site_url['host'], $result );
351 414 }
352 415
416 + // filter (default off) to remove QS from image URL's to avoid eating away optimization credits.
417 + if ( apply_filters( 'autoptimize_filter_imgopt_no_querystring', false ) && strpos( $result, '?' ) !== false ) {
418 + $result = strtok( $result, '?' );
419 + }
420 +
353 421 $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
354 422
355 423 // Store in cache.
356 424 $cache[ $in ] = $result;
@@ -362,15 +430,41 @@
362 430 public function filter_optimize_css_images( $in )
363 431 {
364 432 $in = $this->normalize_img_url( $in );
365 433
366 - if ( $this->can_optimize_image( $in ) ) {
434 + if ( $this->can_optimize_image( $in ) && false === strpos( $in, $this->get_imgopt_host() ) ) {
367 435 return $this->build_imgopt_url( $in, '', '' );
368 436 } else {
369 437 return $in;
370 438 }
371 439 }
440 +
441 + public function filter_optimize_inline_css_images( $html ) {
442 + preg_match_all( '#<style[^>]*>([^<]*)</style>#Um', $html, $inline_css_blocks, PREG_SET_ORDER );
443 + foreach ( $inline_css_blocks as $inline_css_block ) {
444 + if ( false !== strpos( $inline_css_block[0], 'background' ) ) {
445 + $inline_css_block_new = $this->replace_background_img_css( $inline_css_block[0] );
446 + if ( $inline_css_block_new !== $inline_css_block[0] ) {
447 + $html = str_replace( $inline_css_block[0], $inline_css_block_new, $html );
448 + }
449 + }
450 + }
451 + return $html;
452 + }
372 453
454 + public static function replace_background_img_css( $css ) {
455 + // fixme; can/ should we cache these?
456 + preg_match_all( '#background[^;}]*url\((.*)\)#Ui', $css, $backgrounds, PREG_SET_ORDER );
457 + if ( is_array( $backgrounds ) && ! empty( $backgrounds ) ) {
458 + foreach ( $backgrounds as $background ) {
459 + if ( autoptimizeImages::can_optimize_image_wrapper( $background[1] ) ) {
460 + $css = str_replace( $background[1], autoptimizeImages::build_imgopt_url_wrapper( $background[1] ), $css );
461 + }
462 + }
463 + }
464 + return $css;
465 + }
466 +
373 467 private function get_imgopt_base_url()
374 468 {
375 469 static $imgopt_base_url = null;
376 470
@@ -377,9 +471,15 @@
377 471 if ( null === $imgopt_base_url ) {
378 472 $imgopt_host = $this->get_imgopt_host();
379 473 $quality = $this->get_img_quality_string();
380 474 $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;
475 + if ( $this->should_ngimg() ) {
476 + $sp_to_string = 'to_auto';
477 + } else {
478 + $sp_to_string = 'to_webp';
479 + }
480 + $sp_to_string = apply_filters( 'autoptimize_filter_imgopt_format', $sp_to_string ); // values: empty (= jpeg), to_webp (smart; webp or fallback), to_avif (avif or fallback) or to_auto (smart avif, webp or fallback).
481 + $imgopt_base_url = $imgopt_host . 'client/' . $sp_to_string . ',' . $quality . ',' . $ret_val;
382 482 $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
383 483 }
384 484
385 485 return $imgopt_base_url;
@@ -384,9 +484,14 @@
384 484
385 485 return $imgopt_base_url;
386 486 }
387 487
388 - private function can_optimize_image( $url )
488 + public static function can_optimize_image_wrapper( $url, $tag = '', $testing = false ) {
489 + $self = new self();
490 + return $self->can_optimize_image( $url, $tag = '', $testing = false );
491 + }
492 +
493 + private function can_optimize_image( $url, $tag = '', $testing = false )
389 494 {
390 495 static $cdn_url = null;
391 496 static $nopti_images = null;
392 497
@@ -396,10 +501,13 @@
396 501 autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' )
397 502 );
398 503 }
399 504
400 - if ( null === $nopti_images ) {
401 - $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', '' );
505 + if ( null === $nopti_images || $testing ) {
506 + if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_text_field_6', $this->options ) ) {
507 + $nopti_images = $this->options['autoptimize_imgopt_text_field_6'];
508 + }
509 + $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', $nopti_images );
402 510 }
403 511
404 512 $site_host = AUTOPTIMIZE_SITE_DOMAIN;
405 513 $url = $this->normalize_img_url( $url );
@@ -404,21 +512,25 @@
404 512 $site_host = AUTOPTIMIZE_SITE_DOMAIN;
405 513 $url = $this->normalize_img_url( $url );
406 514 $url_parsed = parse_url( $url );
407 515
408 - if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
516 + if ( false === is_array( $url_parsed ) ) {
409 517 return false;
518 + } elseif ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
519 + return false;
520 + } elseif ( autoptimizeUtils::is_local_server() ) {
521 + return false;
410 522 } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
411 523 return false;
412 524 } elseif ( strpos( $url, '.php' ) !== false ) {
413 525 return false;
414 - } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
526 + } elseif ( false === array_key_exists( 'path', $url_parsed ) || str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp', '.avif' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
415 527 // fixme: better check against end of string.
416 528 return false;
417 529 } elseif ( ! empty( $nopti_images ) ) {
418 530 $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
419 531 foreach ( $nopti_images_array as $nopti_image ) {
420 - if ( strpos( $url, $nopti_image ) !== false ) {
532 + if ( strpos( $url, $nopti_image ) !== false || ( ( '' !== $tag && strpos( $tag, $nopti_image ) !== false ) ) ) {
421 533 return false;
422 534 }
423 535 }
424 536 }
@@ -424,8 +536,14 @@
424 536 }
425 537 return true;
426 538 }
427 539
540 + // wrapper for reuse in AOPro.
541 + public static function build_imgopt_url_wrapper( $orig_url, $width = 0, $height = 0 ) {
542 + $self = new self();
543 + return $self->build_imgopt_url( $orig_url, $width = 0, $height = 0 );
544 + }
545 +
428 546 private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
429 547 {
430 548 // sanitize width and height.
431 549 if ( strpos( $width, '%' ) !== false ) {
@@ -448,9 +566,15 @@
448 566 if ( $filtered_url !== $orig_url ) {
449 567 return $filtered_url;
450 568 }
451 569
452 - $orig_url = $this->normalize_img_url( $orig_url );
570 + $normalized_url = $this->normalize_img_url( $orig_url );
571 +
572 + // if the URL is ascii we check if we have a real URL with filter_var (which only works on ascii url's) and if not a real URL we return the original one.
573 + if ( apply_filters( 'autoptimize_filter_imgopt_check_normalized_url', true ) && ! preg_match( '/[^\x20-\x7e]/', $normalized_url ) && false === filter_var( $normalized_url, FILTER_VALIDATE_URL ) ) {
574 + return $orig_url;
575 + }
576 +
453 577 $imgopt_base_url = $this->get_imgopt_base_url();
454 578 $imgopt_size = '';
455 579
456 580 if ( $width && 0 !== $width ) {
@@ -460,9 +584,10 @@
460 584 if ( $height && 0 !== $height ) {
461 585 $imgopt_size .= ',h_' . $height;
462 586 }
463 587
464 - $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
588 + $url = $imgopt_base_url . $imgopt_size . '/' . $normalized_url;
589 + $url = apply_filters( 'autoptimize_filter_imgopt_after_build_imgopt_url', $url );
465 590
466 591 return $url;
467 592 }
468 593
@@ -473,9 +598,9 @@
473 598
474 599 public function replace_img_callback( $matches, $width = 0, $height = 0 )
475 600 {
476 601 $_normalized_img_url = $this->normalize_img_url( $matches[1] );
477 - if ( $this->can_optimize_image( $matches[1] ) ) {
602 + if ( $this->can_optimize_image( $matches[1], $matches[0] ) ) {
478 603 return str_replace( $matches[1], $this->build_imgopt_url( $_normalized_img_url, $width, $height ), $matches[0] );
479 604 } else {
480 605 return $matches[0];
481 606 }
@@ -480,10 +605,29 @@
480 605 return $matches[0];
481 606 }
482 607 }
483 608
484 - public function filter_optimize_images( $in )
609 + public function replace_icon_callback( $matches )
485 610 {
611 + if ( array_key_exists( '2', $matches ) ) {
612 + $sizes = explode( 'x', $matches[2] );
613 + $width = $sizes[0];
614 + $height = $sizes[1];
615 + } else {
616 + $width = 180;
617 + $height = 180;
618 + }
619 +
620 + // make sure we're not trying to optimize a *.ico file.
621 + if ( strpos( $matches[1], '.ico' ) === false ) {
622 + return $this->replace_img_callback( $matches, $width, $height );
623 + } else {
624 + return $matches[0];
625 + }
626 + }
627 +
628 + public function filter_optimize_images( $in, $testing = false )
629 + {
486 630 /*
487 631 * potential future functional improvements:
488 632 *
489 633 * filter for critical CSS.
@@ -488,11 +632,12 @@
488 632 *
489 633 * filter for critical CSS.
490 634 */
491 635 $to_replace = array();
636 + $to_preload = '';
492 637
493 - // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
494 - if ( $this->should_lazyload() ) {
638 + // hide (no)script tags to avoid replacing (and potentially breaking) images in script tags.
639 + if ( apply_filters( 'autoptimize_filter_imgopt_hide_script', true ) || $this->should_lazyload() ) {
495 640 $in = autoptimizeBase::replace_contents_with_marker_if_exists(
496 641 'SCRIPT',
497 642 '<script',
498 643 '#<(?:no)?script.*?<\/(?:no)?script>#is',
@@ -499,11 +644,18 @@
499 644 $in
500 645 );
501 646 }
502 647
648 + // get img preloads as set in post metabox, exploding ", " instead of "," because LCP preload
649 + // could be a shortpixel URL, which has comma's and results in way too many preloads.
650 + $metabox_preloads = array_filter( array_map( 'trim', explode( ', ', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
651 + $metabox_preloads = apply_filters( 'autoptimize_filter_images_metabox_preloads', $metabox_preloads );
652 +
503 653 // extract img tags.
504 654 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
505 655 foreach ( $matches[0] as $tag ) {
656 + $tag = apply_filters( 'autoptimize_filter_imgopt_tag_preopt', $tag );
657 +
506 658 $orig_tag = $tag;
507 659 $imgopt_w = '';
508 660 $imgopt_h = '';
509 661
@@ -509,20 +661,22 @@
509 661
510 662 // first do (data-)srcsets.
511 663 if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
512 664 foreach ( $allsrcsets as $srcset ) {
513 - $srcset = $srcset[2];
514 - $srcsets = explode( ',', $srcset );
665 + $srcset = $srcset[2];
666 + $orig_srcset = $srcset;
667 + $srcsets = explode( ',', $srcset );
515 668 foreach ( $srcsets as $indiv_srcset ) {
516 669 $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
517 670 if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
518 671 $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
519 672 }
520 - if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
673 + if ( $this->can_optimize_image( $indiv_srcset_parts[0], $tag, $testing ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
521 674 $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
522 - $tag = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
675 + $srcset = str_replace( $indiv_srcset_parts[0], $imgopt_url, $srcset );
523 676 }
524 677 }
678 + $tag = str_replace( $orig_srcset, $srcset, $tag );
525 679 }
526 680 }
527 681
528 682 // proceed with img src.
@@ -531,22 +685,33 @@
531 685 $imgopt_w = $_get_size['width'];
532 686 $imgopt_h = $_get_size['height'];
533 687
534 688 // 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 - }
689 + $tag = autoptimizeAttributeParser::modify_attributes( $tag, 'img', '/src$/i', function( $name, $url ) use ( $tag, $testing, $imgopt_w, $imgopt_h ) {
690 + // 1. Skip if empty or a data-URI
691 + if ( ! $url || strpos( $url, 'data:' ) === 0 ) {
692 + return $url;
544 693 }
694 +
695 + // 2. Run Autoptimize logic
696 + if ( $this->can_optimize_image( $url, $tag, $testing ) && ! apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
697 + return $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
698 + }
699 +
700 + // 3. No changes needed
701 + return $url;
702 + }, false, true );
703 +
704 + $_url = autoptimizeAttributeParser::get( $tag, 'img', '/src$/i' );
705 + $url = $_url['src'];
706 +
707 + // check if the image needs to be prelaoded.
708 + if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && str_replace( $metabox_preloads, '', $tag ) !== $tag ) {
709 + $to_preload .= $this->create_img_preload_tag( $tag );
545 710 }
546 711
547 712 // do lazyload stuff.
548 - if ( $this->should_lazyload( $in ) ) {
713 + if ( $this->should_lazyload( $in ) && ! empty( $url ) ) {
549 714 // first do lpiq placeholder logic.
550 715 if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
551 716 // if all img src have been replaced during srcset, we have to extract the
552 717 // origin url from the imgopt one to be able to set a lqip placeholder.
@@ -554,10 +719,12 @@
554 719 } else {
555 720 $_url = $url;
556 721 }
557 722
723 + $_url = $this->normalize_img_url( $_url );
724 +
558 725 $placeholder = '';
559 - if ( $this->can_optimize_image( $_url ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', true ) ) {
726 + if ( $this->can_optimize_image( $_url, $tag ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', false, $_url ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
560 727 $lqip_w = '';
561 728 $lqip_h = '';
562 729 if ( isset( $imgopt_w ) && ! empty( $imgopt_w ) ) {
563 730 $lqip_w = ',w_' . $imgopt_w;
@@ -570,8 +737,15 @@
570 737 // then call add_lazyload-function with lpiq placeholder if set.
571 738 $tag = $this->add_lazyload( $tag, $placeholder );
572 739 }
573 740
741 + // add decoding="async" behind filter, not sure if I'll make it default true yet.
742 + if ( true === apply_filters( 'autoptimize_filter_imgopt_add_decoding', true ) && false === strpos( $tag, ' decoding=' ) ) {
743 + $tag = str_replace( '<img ', '<img decoding="async" ', $tag );
744 + }
745 +
746 + $tag = apply_filters( 'autoptimize_filter_imgopt_tag_postopt', $tag );
747 +
574 748 // and add tag to array for later replacement.
575 749 if ( $tag !== $orig_tag ) {
576 750 $to_replace[ $orig_tag ] = $tag;
577 751 }
@@ -580,43 +754,72 @@
580 754
581 755 // and replace all.
582 756 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
583 757
584 - // img thumbnails in e.g. woocommerce.
585 - if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_imgopt_datathumbs', true ) ) {
758 + // misc. node attributes that might hold image url's (incl. the previously separate data-thumb).
759 + $extra_attr_with_img = apply_filters( 'autoptimize_filter_imgopt_attr_with_img', array( array( 'div', 'data-thumb'), array( 'div', 'data-background' ), array( 'img', 'data-retina' ) ) );
760 + if ( ! empty( $extra_attr_with_img ) && is_array( $extra_attr_with_img ) ) {
761 + foreach ( $extra_attr_with_img as $candidate ) {
762 + if ( is_array( $candidate ) && strpos( $out, $candidate[1] ) !== false ) {
763 + $_regex = '/\<' . $candidate[0] . '(?:[^>]*)?\s' . $candidate[1] . '=(?:"|\')(.+?)(?:"|\')(?:[^>]*)?>/s';
764 + $out = preg_replace_callback(
765 + $_regex,
766 + array( $this, 'replace_img_callback' ),
767 + $out
768 + );
769 + }
770 + }
771 + }
772 +
773 + // background-image in inline style.
774 + if ( ( strpos( $out, 'background-image:' ) !== false || strpos( $out, 'background:' ) !== false ) && strpos( $out, 'url(' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
586 775 $out = preg_replace_callback(
587 - '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
588 - array( $this, 'replace_data_thumbs' ),
776 + '/style=(?:"|\')[^<>]*?background(?:-image)?:[^;"\'()>]*url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
777 + array( $this, 'replace_img_callback' ),
589 778 $out
590 779 );
591 780 }
592 781
593 - // background-image in inline style.
594 - if ( strpos( $out, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
782 + // act on icon links.
783 + if ( ( strpos( $out, '<link rel="icon"' ) !== false || ( strpos( $out, "<link rel='icon'" ) !== false ) ) && apply_filters( 'autoptimize_filter_imgopt_linkicon', true ) ) {
595 784 $out = preg_replace_callback(
596 - '/style=(?:"|\').*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
597 - array( $this, 'replace_img_callback' ),
785 + '/<link\srel=(?:"|\')(?:apple-touch-)?icon(?:-precomposed)?(?:"|\').*\shref=(?:"|\')(.*)(?:"|\')(?:\ssizes=(?:"|\')(\d*x\d*)(?:"|\'))?\s?\/?>/Um',
786 + array( $this, 'replace_icon_callback' ),
598 787 $out
599 788 );
600 789 }
601 790
602 - // lazyload: restore noscript tags + lazyload picture source tags and bgimage.
791 + // lazyload picture source tags and bgimage.
603 792 if ( $this->should_lazyload() ) {
793 + $out = $this->process_picture_tag( $out, true, true );
794 + $out = $this->process_bgimage( $out );
795 + } else {
796 + $out = $this->process_picture_tag( $out, true, false );
797 + }
798 +
799 + // restore (no)script tags.
800 + if ( apply_filters( 'autoptimize_filter_imgopt_hide_script', true ) || $this->should_lazyload() ) {
604 801 $out = autoptimizeBase::restore_marked_content(
605 802 'SCRIPT',
606 803 $out
607 804 );
805 + }
608 806
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 );
807 + if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
808 + // the preload was not in an img tag, so adding a non-responsive preload instead.
809 + foreach ( $metabox_preloads as $img_preload ) {
810 + $to_preload .= apply_filters( 'autoptimize_filter_imgopt_preload_tag_result', $this->kses_preload_link( '<link fetchpriority="high" rel="preload" href="' . $img_preload . '" as="image">' ) );
811 + }
613 812 }
614 813
814 + if ( ! empty( $to_preload ) ) {
815 + $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
816 + }
817 +
615 818 return $out;
616 819 }
617 820
618 - public function get_size_from_tag( $tag ) {
821 + public static function get_size_from_tag( $tag ) {
619 822 // reusable function to extract widht and height from an image tag
620 823 // enforcing a filterable maximum width and height (default 4999X4999).
621 824 $width = '';
622 825 $height = '';
@@ -635,15 +838,19 @@
635 838 // check for and enforce (filterable) max sizes.
636 839 $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
637 840 if ( $width > $_max_width ) {
638 841 $_width = $_max_width;
639 - $height = $_width / $width * $height;
842 + if ( ! empty( $height ) && is_int( $height ) ) {
843 + $height = $_width / $width * $height;
844 + }
640 845 $width = $_width;
641 846 }
642 847 $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
643 848 if ( $height > $_max_height ) {
644 849 $_height = $_max_height;
645 - $width = $_height / $height * $width;
850 + if ( ! empty( $width ) && is_int( $width ) ) {
851 + $width = $_height / $height * $width;
852 + }
646 853 $height = $_height;
647 854 }
648 855
649 856 return array(
@@ -654,26 +861,32 @@
654 861
655 862 /**
656 863 * Lazyload functions
657 864 */
658 - public static function should_lazyload_wrapper() {
865 + public static function should_lazyload_wrapper( $no_meta = false ) {
659 866 // needed in autoptimizeMain.php.
660 867 $self = new self();
661 - return $self->should_lazyload();
868 + return $self->should_lazyload( '', $no_meta );
662 869 }
663 870
664 - public function should_lazyload( $context = '' ) {
871 + public function should_lazyload( $context = '', $no_meta = false ) {
665 872 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && false === $this->check_nolazy() ) {
666 873 $lazyload_return = true;
667 874 } else {
668 875 $lazyload_return = false;
669 876 }
877 +
878 + // If page/ post check post_meta to see if lazyload is off for page.
879 + if ( false === $no_meta && false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_lazyload' ) ) {
880 + $lazyload_return = false;
881 + }
882 +
670 883 $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return, $context );
671 884
672 885 return $lazyload_return;
673 886 }
674 887
675 - public function check_nolazy() {
888 + public static function check_nolazy() {
676 889 if ( array_key_exists( 'ao_nolazy', $_GET ) && '1' === $_GET['ao_nolazy'] ) {
677 890 return true;
678 891 } else {
679 892 return false;
@@ -683,8 +896,9 @@
683 896 public function filter_lazyload_images( $in )
684 897 {
685 898 // only used is image optimization is NOT active but lazyload is.
686 899 $to_replace = array();
900 + $to_preload = '';
687 901
688 902 // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
689 903 $out = autoptimizeBase::replace_contents_with_marker_if_exists(
690 904 'SCRIPT',
@@ -692,11 +906,20 @@
692 906 '#<(?:no)?script.*?<\/(?:no)?script>#is',
693 907 $in
694 908 );
695 909
696 - // extract img tags and add lazyload attribs.
910 + // get img preloads as set in post metabox.
911 + $metabox_preloads = array_filter( array_map( 'trim', explode( ',', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
912 +
913 + // extract img tags and add lazyload attribs/ add preloads.
697 914 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
698 915 foreach ( $matches[0] as $tag ) {
916 + // check if image needs to be preloaded.
917 + if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && str_replace( $metabox_preloads, '', $tag ) !== $tag ) {
918 + $to_preload .= $this->create_img_preload_tag( $tag );
919 + }
920 +
921 + // and lazyloaded.
699 922 if ( $this->should_lazyload( $out ) ) {
700 923 $to_replace[ $tag ] = $this->add_lazyload( $tag );
701 924 }
702 925 }
@@ -714,14 +937,33 @@
714 937 'SCRIPT',
715 938 $out
716 939 );
717 940
941 + if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
942 + // the preload was not in an img tag, so adding a non-responsive preload instead.
943 + foreach ( $metabox_preloads as $img_preload ) {
944 + $to_preload .= apply_filters( 'autoptimize_filter_imgopt_preload_tag_result', $this->kses_preload_link( '<link fetchpriority="high" rel="preload" href="' . $img_preload . '" as="image">' ) );
945 + }
946 + }
947 +
948 + if ( ! empty( $to_preload ) ) {
949 + $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
950 + }
951 +
718 952 return $out;
719 953 }
720 954
721 955 public function add_lazyload( $tag, $placeholder = '' ) {
722 956 // adds actual lazyload-attributes to an image node.
723 - if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
957 + $this->lazyload_counter++;
958 +
959 + $_lazyload_from_nth = '';
960 + if ( array_key_exists( 'autoptimize_imgopt_number_field_7', $this->options ) ) {
961 + $_lazyload_from_nth = $this->options['autoptimize_imgopt_number_field_7'];
962 + }
963 + $_lazyload_from_nth = apply_filters( 'autoptimize_filter_imgopt_lazyload_from_nth', $_lazyload_from_nth );
964 +
965 + if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag && $this->lazyload_counter >= $_lazyload_from_nth ) {
724 966 $tag = $this->maybe_fix_missing_quotes( $tag );
725 967
726 968 // store original tag for use in noscript version.
727 969 $noscript_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>';
@@ -735,13 +977,13 @@
735 977 // get image width & heigth for placeholder fun (and to prevent content reflow).
736 978 $_get_size = $this->get_size_from_tag( $tag );
737 979 $width = $_get_size['width'];
738 980 $height = $_get_size['height'];
739 - if ( false === $width ) {
740 - $widht = 210; // default width for SVG placeholder.
981 + if ( false === $width || empty( $width ) ) {
982 + $width = 210; // default width for SVG placeholder.
741 983 }
742 - if ( false === $height ) {
743 - $heigth = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
984 + if ( false === $height || empty( $height ) ) {
985 + $height = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
744 986 }
745 987
746 988 // insert the actual lazyload stuff.
747 989 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
@@ -747,10 +989,11 @@
747 989 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
748 990 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
749 991 }
750 992
751 - $tag = preg_replace( '/(\s)src=/', ' src=\'' . $placeholder . '\' data-src=', $tag );
752 - $tag = preg_replace( '/(\s)srcset=/', ' data-srcset=', $tag );
993 + $tag = str_replace( ' src=', ' data-src=', $tag );
994 + $tag = str_replace( ' srcset=', ' data-srcset=', $tag );
995 + $tag = str_replace( '<img ', '<img src=\'' . $placeholder . '\' ', $tag );
753 996
754 997 // move sizes to data-sizes unless filter says no.
755 998 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_move_sizes', true ) ) {
756 999 $tag = str_replace( ' sizes=', ' data-sizes=', $tag );
@@ -758,8 +1001,10 @@
758 1001
759 1002 // add the noscript-tag from earlier.
760 1003 $tag = $noscript_tag . $tag;
761 1004 $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
1005 + } else {
1006 + $tag = apply_filters( 'autoptimize_filter_imgopt_not_lazyloaded_img', $tag );
762 1007 }
763 1008
764 1009 return $tag;
765 1010 }
@@ -764,9 +1009,9 @@
764 1009 return $tag;
765 1010 }
766 1011
767 1012 public function add_lazyload_js_footer() {
768 - if ( false === autoptimizeMain::should_buffer() ) {
1013 + if ( false === autoptimizeMain::should_buffer() || autoptimizeMain::is_amp_markup( '' ) ) {
769 1014 return;
770 1015 }
771 1016
772 1017 // The JS will by default be excluded form autoptimization but this can be changed with a filter.
@@ -774,28 +1019,81 @@
774 1019 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
775 1020 $noptimize_flag = ' data-noptimize="1"';
776 1021 }
777 1022
778 - $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js', __FILE__ );
1023 + $_extra = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings', '' );
1024 + if ( is_array( $_extra ) && array_key_exists( 'autoptimize_extra_checkbox_field_0', $_extra ) && ! empty( $_extra['autoptimize_extra_checkbox_field_0'] ) ) {
1025 + // if "remove query strings" is active in "extra", then let's be consistant and not add one ourselves :-) ?
1026 + $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js', __FILE__ );
1027 + } else {
1028 + $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js?ao_version=' . AUTOPTIMIZE_PLUGIN_VERSION, __FILE__ );
1029 + }
1030 +
779 1031 $cdn_url = $this->get_cdn_url();
780 1032 if ( ! empty( $cdn_url ) ) {
1033 + $cdn_url = rtrim( $cdn_url, '/' );
781 1034 $lazysizes_js = str_replace( AUTOPTIMIZE_WP_SITE_URL, $cdn_url, $lazysizes_js );
782 1035 }
783 1036
1037 + $type_js = '';
1038 + if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
1039 + $type_js = ' type="text/javascript"';
1040 + }
1041 +
784 1042 // 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>' );
1043 + echo apply_filters( 'autoptimize_filter_imgopt_lazyload_cssoutput', '<noscript><style>.lazyload{display:none;}</style></noscript>' );
1044 + echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $type_js . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
1045 + echo apply_filters( 'autoptimize_filter_imgopt_lazyload_js', '<script async' . $type_js . $noptimize_flag . ' src=\'' . $lazysizes_js . '\'></script>' );
1046 + }
788 1047
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>' );
1048 + public function create_img_preload_tag( $tag ) {
1049 + if ( false === apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
1050 + return '';
794 1051 }
1052 +
1053 + // clean up; remove tabs/ linebreaks/ spaces.
1054 + $tag = preg_replace( '/\s+/', ' ', $tag );
1055 +
1056 + // remove noscript.
1057 + if ( false !== strpos( $tag, '<noscript' ) ) {
1058 + $tag = preg_replace( '/<noscript.*<\/noscript>/mU', '', $tag );
1059 + }
1060 +
1061 + // rewrite img tag to link preload img.
1062 + $_from = array( '<img ', ' src=', ' sizes=', ' srcset=' );
1063 + $_to = array( '<link fetchpriority="high" rel="preload" as="image" ', ' href=', ' imagesizes=', ' imagesrcset=' );
1064 + $tag = str_replace( $_from, $_to, $tag );
1065 +
1066 + // sanitize output
1067 + $tag = $this->kses_preload_link( $tag );
1068 +
1069 + // and provide filter for late changes.
1070 + $tag = apply_filters( 'autoptimize_filter_imgopt_preload_tag_result', $tag );
1071 +
1072 + return $tag;
795 1073 }
796 1074
797 - public function get_cdn_url() {
1075 + public function kses_preload_link( $_preload ) {
1076 + // using kses, remove all unneeded attributes
1077 + // keeping only those we *know* are OK and/ or needed.
1078 + $allowed_html = array(
1079 + 'link' => array(
1080 + 'rel' => true,
1081 + 'as' => true,
1082 + 'href' => true,
1083 + 'imagesizes' => true,
1084 + 'imagesrcset' => true,
1085 + 'type' => true,
1086 + 'media' => true,
1087 + 'fetchpriority' => true,
1088 + ),
1089 + );
1090 + $_preload = wp_kses( $_preload, $allowed_html );
1091 +
1092 + return $_preload;
1093 + }
1094 +
1095 + public static function get_cdn_url() {
798 1096 // getting CDN url here to avoid having to make bigger changes to autoptimizeBase.
799 1097 static $cdn_url = null;
800 1098
801 1099 if ( null === $cdn_url ) {
@@ -814,9 +1112,9 @@
814 1112 if ( null === $exclude_lazyload_array ) {
815 1113 $options = $this->options;
816 1114
817 1115 // set default exclusions.
818 - $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg' );
1116 + $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg', 'loading="eager"', 'fetchpriority="high"' );
819 1117
820 1118 // add from setting.
821 1119 if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
822 1120 $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
@@ -835,9 +1133,9 @@
835 1133 public function inject_classes_in_tag( $tag, $target_class ) {
836 1134 if ( strpos( $tag, 'class=' ) !== false ) {
837 1135 $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
838 1136 } else {
839 - $tag = preg_replace( '/(<img)\s/', '$1 class="' . trim( $target_class ) . '" ', $tag );
1137 + $tag = preg_replace( '/(<[a-zA-Z]*)\s/', '$1 class="' . trim( $target_class ) . '" ', $tag );
840 1138 }
841 1139
842 1140 return $tag;
843 1141 }
@@ -845,21 +1143,21 @@
845 1143 public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
846 1144 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 1145 }
848 1146
849 - public function should_webp() {
850 - static $webp_return = null;
1147 + public function should_ngimg() {
1148 + static $ngimg_return = null;
851 1149
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;
1150 + if ( is_null( $ngimg_return ) ) {
1151 + // nextgen img only works if imgopt is active.
1152 + if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && $this->imgopt_active() ) {
1153 + $ngimg_return = true;
856 1154 } else {
857 - $webp_return = false;
1155 + $ngimg_return = false;
858 1156 }
859 1157 }
860 1158
861 - return $webp_return;
1159 + return $ngimg_return;
862 1160 }
863 1161
864 1162 public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
865 1163 // check if "<picture" is present and if filter allows us to process <picture>.
@@ -878,9 +1176,9 @@
878 1176 foreach ( $_sources as $_source ) {
879 1177 $_picture_replacement = $_source[0];
880 1178
881 1179 // should we optimize the image?
882 - if ( $imgopt && $this->can_optimize_image( $_source[1] ) ) {
1180 + if ( $imgopt && $this->can_optimize_image( $_source[1], $_picture[0] ) ) {
883 1181 $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
884 1182 }
885 1183 // should we lazy-load?
886 1184 if ( $lazy && $this->should_lazyload() && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
@@ -899,9 +1197,9 @@
899 1197
900 1198 public function process_bgimage( $in ) {
901 1199 if ( strpos( $in, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_lazyload_backgroundimages', true ) ) {
902 1200 $out = preg_replace_callback(
903 - '/(<(?:article|aside|body|div|footer|header|p|section|table)[^>]*)\sstyle=(?:"|\').*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)[^>]*/',
1201 + '/(<(?:article|aside|body|div|footer|header|p|section|span|table)[^>]*)\sstyle=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)[^>]*/',
904 1202 array( $this, 'lazyload_bgimg_callback' ),
905 1203 $in
906 1204 );
907 1205 return $out;
@@ -913,13 +1211,17 @@
913 1211 if ( str_ireplace( $this->get_lazyload_exclusions(), '', $matches[0] ) === $matches[0] ) {
914 1212 // get placeholder & lazyload class strings.
915 1213 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( 500, 300 ) );
916 1214 $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
1215 + // remove quotes from url() to be able to replace in next step.
1216 + $out = str_replace( array( "url('" . $matches[2] . "')", 'url("' . $matches[2] . '")' ), 'url(' . $matches[2] . ')', $matches[0] );
917 1217 // replace background-image URL with SVG placeholder.
918 - $out = str_replace( $matches[2], $placeholder, $matches[0] );
1218 + $out = str_replace( 'url(' . $matches[2], 'url(' . $placeholder, $out );
1219 + // sanitize bgimg src for quote sillyness.
1220 + $bgimg_src = $this->fix_silly_bgimg_quotes( $matches[2] );
919 1221 // add data-bg attribute with real background-image URL for lazyload to pick up.
920 - $out = str_replace( $matches[1], $matches[1] . ' data-bg="' . $matches[2] . '"', $out );
921 - // add lazyload class to tag.
1222 + $out = str_replace( $matches[1], $matches[1] . ' data-bg="' . $bgimg_src . '"', $out );
1223 + // and finally add lazyload class to tag.
922 1224 $out = $this->inject_classes_in_tag( $out, "$lazyload_class " );
923 1225 return $out;
924 1226 }
925 1227 return $matches[0];
@@ -924,12 +1226,18 @@
924 1226 }
925 1227 return $matches[0];
926 1228 }
927 1229
1230 + public function fix_silly_bgimg_quotes( $tag_in ) {
1231 + // some themes/ pagebuilders wrap backgroundimages in HTML-encoded quotes (or linebreaks) which breaks imgopt/ lazyloading, this removes them.
1232 + return trim( str_replace( array( "\r\n", '"', '&quot;', '&#034;', '&apos;', '&#039;' ), '', $tag_in ) );
1233 + }
1234 +
928 1235 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 );
1236 + // 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.
1237 + if ( ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) ) {
1238 + $tag_out = autoptimizeAttributeParser::rebuild( $tag_in );
1239 + return $tag_out;
932 1240 } else {
933 1241 return $tag_in;
934 1242 }
935 1243 }
@@ -941,9 +1249,9 @@
941 1249 {
942 1250 // no acces if multisite and not network admin and no site config allowed.
943 1251 if ( autoptimizeConfig::should_show_menu_tabs() ) {
944 1252 add_submenu_page(
945 - null,
1253 + '',
946 1254 'autoptimize_imgopt',
947 1255 'autoptimize_imgopt',
948 1256 'manage_options',
949 1257 'autoptimize_imgopt',
@@ -948,16 +1256,16 @@
948 1256 'manage_options',
949 1257 'autoptimize_imgopt',
950 1258 array( $this, 'imgopt_options_page' )
951 1259 );
952 - register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
953 1260 }
1261 + register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
954 1262 }
955 1263
956 1264 public function add_imgopt_tab( $in )
957 1265 {
958 1266 if ( autoptimizeConfig::should_show_menu_tabs() ) {
959 - $in = array_merge( $in, array( 'autoptimize_imgopt' => __( 'Images', 'autoptimize' ) ) );
1267 + $in = array_merge( $in, array( 'autoptimize_imgopt' => apply_filters( 'autoptimize_filter_imgopt_tab_text', esc_html__( 'Images', 'autoptimize' ) ) ) );
960 1268 }
961 1269
962 1270 return $in;
963 1271 }
@@ -963,11 +1271,14 @@
963 1271 }
964 1272
965 1273 public function imgopt_options_page()
966 1274 {
1275 + // phpcs:disable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace
1276 + // phpcs:disable Generic.Formatting.DisallowMultipleStatements.SameLine
1277 +
967 1278 // Check querystring for "refreshCacheChecker" and call cachechecker if so.
968 1279 if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
969 - $this->query_img_provider_stats();
1280 + $this->query_img_provider_stats( true );
970 1281 }
971 1282
972 1283 $options = $this->fetch_options();
973 1284 $sp_url_suffix = $this->get_service_url_suffix();
@@ -975,18 +1286,26 @@
975 1286 <style>
976 1287 #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
977 1288 #ao_settings_form .form-table th {font-weight: normal;}
978 1289 #autoptimize_imgopt_descr{font-size: 120%;}
1290 + #ao_settings_form td p.condition-warning {color: orange;}
979 1291 </style>
980 - <script>document.title = "Autoptimize: <?php _e( 'Images', 'autoptimize' ); ?> " + document.title;</script>
1292 + <script>document.title = "Autoptimize: <?php esc_html_e( 'Images', 'autoptimize' ); ?> " + document.title;</script>
981 1293 <div class="wrap">
982 - <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
1294 + <h1><?php apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? esc_html_e( 'Autoptimize Pro Settings', 'autoptimize' ) : esc_html_e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
983 1295 <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
1296 + <?php if ( autoptimizeUtils::is_local_server() ) { ?>
1297 + <div class="notice-warning notice"><p>
1298 + <?php
1299 + echo esc_html__( 'The image optimization service does not work on locally hosted sites or when the server is on a private network.', 'autoptimize' );
1300 + ?>
1301 + </p></div>
1302 + <?php } ?>
984 1303 <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
985 1304 <div class="notice-warning notice"><p>
986 1305 <?php
987 1306 // 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>' );
1307 + echo sprintf( esc_html__( 'The image optimization service is currently down, image optimization will be skipped until further notice. Check the %1$sAutoptimize support forum%2$s for more info.', 'autoptimize' ), '<a href="https://wordpress.org/support/plugin/autoptimize/" target="_blank">', '</a>' );
989 1308 ?>
990 1309 </p></div>
991 1310 <?php } ?>
992 1311
@@ -991,9 +1310,9 @@
991 1310 <?php } ?>
992 1311
993 1312 <?php if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! autoptimizeImages::instance()->launch_ok() ) { ?>
994 1313 <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' ); ?>
1314 + <?php esc_html_e( 'The image optimization service is launching, but not yet available for this domain, it should become available in the next couple of days.', 'autoptimize' ); ?>
996 1315 </p></div>
997 1316 <?php } ?>
998 1317
999 1318 <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
@@ -999,21 +1318,21 @@
999 1318 <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
1000 1319 <div class="notice-warning notice"><p>
1001 1320 <?php
1002 1321 // 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>' );
1322 + echo sprintf( esc_html__( 'Please %1$sdisable Jetpack\'s site accelerator for images%2$s to be able to use Autoptomize\'s advanced image optimization features below.', 'autoptimize' ), '<a href="admin.php?page=jetpack#/settings">', '</a>' );
1004 1323 ?>
1005 1324 </p></div>
1006 1325 <?php } ?>
1007 1326 <form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
1008 1327 <?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>
1328 + <h2><?php esc_html_e( 'Image optimization', 'autoptimize' ); ?></h2>
1329 + <span id='autoptimize_imgopt_descr'><?php echo apply_filters( 'autoptimize_filter_imgopt_intro_copy', esc_html__( 'Make your site significantly faster by simply ticking a few boxes and start serving CDN powered, optimized images in the next-gen WebP image format! No additional plugins or services needed.', 'autoptimize' ) ); ?></span>
1011 1330 <table class="form-table">
1012 1331 <tr>
1013 - <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
1332 + <th scope="row"><?php esc_html_e( 'Image optimization & CDN', 'autoptimize' ); ?></th>
1014 1333 <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>
1334 + <label><input id='autoptimize_imgopt_checkbox' type='checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_1]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_1'] ) { echo 'checked="checked"'; } ?> value='1'><?php echo apply_filters( 'autoptimize_filter_imgopt_main_setting_copy', esc_html__( 'On-the-fly image optimization and fast delivery via the Shortpixel global CDN.', 'autoptimize' ) ); ?></label>
1016 1335 <?php
1017 1336 // show shortpixel status.
1018 1337 $_notice = autoptimizeImages::instance()->get_imgopt_status_notice();
1019 1338 if ( $_notice ) {
@@ -1031,31 +1350,37 @@
1031 1350 break;
1032 1351 default:
1033 1352 $_notice_color = 'green';
1034 1353 }
1035 - echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
1354 + echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . esc_html__( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
1036 1355 } else {
1037 1356 // 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>' );
1357 + $upsell_msg_1 = '<p>' . sprintf( esc_html__( 'Get more Google love by speeding up your website. Start serving on-the-fly optimized images (also in the "next-gen" %4$sWebP%5$s image format) by %1$sShortPixel%2$s. No additional image optimization plugins are needed: your images are optimized, cached and served from %3$sShortPixel\'s global CDN%2$s.', 'autoptimize' ), '<a href="https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell" target="_blank">', '</a>', '<a href="https://help.shortpixel.com/article/62-where-does-the-cdn-has-pops" target="_blank">', '<strong>', '</strong>' );
1039 1358 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' );
1359 + $upsell_msg_2 = sprintf( esc_html__( 'For a limited time only, this service is offered free for all Autoptimize users, %1$sdon\'t miss the chance to test it%2$s and see how much it could improve your site\'s speed.', 'autoptimize' ), '<strong>', '</strong>' );
1041 1360 } 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>' );
1361 + // translators: 1st link points to autoptimize.com.pro, 2nd to shortpixel.
1362 + $upsell_msg_2 = sprintf( esc_html__( 'For (nearly) %5$sunlimited image optimizations %1$sbuy Autoptimize Pro%2$s%6$s which also includes Critical CSS and extra "booster" options or %3$ssign up at Shortpixel%4$s.', 'autoptimize' ), '<a href="https://autoptimize.com/pro/" target="_blank">', '</a>', '<a href="https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell" target="_blank">', '</a>', '<strong>', '</strong>' );
1044 1363 }
1045 - echo apply_filters( 'autoptimize_imgopt_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
1364 + echo apply_filters( 'autoptimize_filter_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
1046 1365 }
1047 1366 // 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' );
1367 + $faqcopy = sprintf( esc_html__( '%3$sQuestions%4$s? Take a look at the %1$sAutoptimize + ShortPixel FAQ%2$s!', 'autoptimize' ), '<strong><a href="https://help.shortpixel.com/category/405-autoptimize" target="_blank">', '</strong></a>', '<strong>', '</strong>' );
1368 + $faqcopy = $faqcopy . ' ' . esc_html__( 'Only works for websites and images that are publicly available.', 'autoptimize' );
1050 1369 // 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>' );
1370 + $toscopy = sprintf( esc_html__( 'Usage of this feature is subject to Shortpixel\'s %1$sTerms of Use%2$s and %3$sPrivacy policy%4$s.', 'autoptimize' ), '<a href="https://shortpixel.com/tos' . $sp_url_suffix . '" target="_blank">', '</a>', '<a href="https://shortpixel.com/privacy' . $sp_url_suffix . '" target="_blank">', '</a>' );
1371 + echo apply_filters( 'autoptimize_filter_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
1053 1372 ?>
1054 1373 </td>
1055 1374 </tr>
1375 + <tr id='autoptimize_imgopt_optimization_exclusions' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
1376 + <th scope="row"><?php esc_html_e( 'Optimization exclusions', 'autoptimize' ); ?></th>
1377 + <td>
1378 + <label><input type='text' style='width:80%' id='autoptimize_imgopt_optimization_exclusions' name='autoptimize_imgopt_settings[autoptimize_imgopt_text_field_6]' value='<?php if ( ! empty( $options['autoptimize_imgopt_text_field_6'] ) ) { echo esc_attr( $options['autoptimize_imgopt_text_field_6'] ); } ?>'><br /><?php esc_html_e( 'Comma-separated list of image classes or filenames that should not be optimized.', 'autoptimize' ); ?></label>
1379 + </td>
1380 + </tr>
1056 1381 <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>
1382 + <th scope="row"><?php esc_html_e( 'Image Optimization quality', 'autoptimize' ); ?></th>
1058 1383 <td>
1059 1384 <label>
1060 1385 <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
1061 1386 <?php
@@ -1075,33 +1400,60 @@
1075 1400 </label>
1076 1401 <p>
1077 1402 <?php
1078 1403 // 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>' ) );
1404 + echo apply_filters( 'autoptimize_filter_imgopt_quality_copy', sprintf( esc_html__( 'You can %1$stest compression levels here%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/online-image-compression' . $sp_url_suffix . '" target="_blank">', '</a>' ) );
1080 1405 ?>
1081 1406 </p>
1082 1407 </td>
1083 1408 </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>
1409 + <?php
1410 + if ( apply_filters( 'autoptimize_filter_imgopt_settings_show_avif', false ) || $this->should_ngimg() ) {
1411 + ?>
1412 + <tr id='autoptimize_imgopt_ngimg' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
1413 + <th scope="row">
1414 + <?php esc_html_e( 'Load AVIF in supported browsers?', 'autoptimize' ); ?>
1415 + </th>
1416 + <td>
1417 + <p class="condition-warning">
1418 + <?php
1419 + if ( '3' === autoptimizeImages::instance()->get_img_quality_setting() && $this->should_ngimg() ) {
1420 + _e( 'Your Image optimization quality is set to "lossless", but AVIF files can end up being bigger than jpeg or webp for lossless images, so consider choosing a different quality or disable the AVIF option.', 'autoptimize' );
1421 + }
1422 + ?>
1423 + </p>
1424 + <label><input type='checkbox' id='autoptimize_imgopt_ngimg_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_4]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_4'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_4'] ) { echo 'checked="checked"'; } ?> value='1'>
1425 + <?php esc_html_e( 'Automatically serve AVIF image format to any browser that supports it.', 'autoptimize' ); ?> <?php printf( esc_html__( '%1$sImportant caveat%2$s; although it %5$scan result in even smaller image sizes for lossy images%4$s it should be taken into account that %3$s%1$sAVIF can require more resources%2$s to decode/ display%4$s and as such %1$smight be slower%2$s on low-end (mobile) devices.', 'autoptimize' ), '<strong>', '</strong>', '<a href="https://storage.googleapis.com/avif-comparison/decode-timing.html" target="_blank">', '</a>', '<a href="https://tsev.dev/posts/2023-11-10-should-avif-be-the-dominant-image-format/" target="_blank">' ); ?>
1426 + </label>
1427 + </td>
1428 + </tr>
1429 + <?php
1430 + } else {
1431 + ?>
1432 + <input type='hidden' id='autoptimize_imgopt_ngimg_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_4]' value='0'>
1433 + <?php
1434 + }
1435 + ?>
1436 + <tr>
1437 + <th scope="row"><?php esc_html_e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1086 1438 <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>
1439 + <label><input type='checkbox' id='autoptimize_imgopt_lazyload_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_3]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_3'] ) { echo 'checked="checked"'; } ?> value='1'><?php esc_html_e( 'Image lazy-loading will delay the loading of non-visible images to allow the browser to optimally load all resources for the "above the fold"-page first.', 'autoptimize' ); ?></label>
1088 1440 </td>
1089 1441 </tr>
1090 - <tr>
1091 - <th scope="row"><?php _e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1442 + <tr id='autoptimize_imgopt_lazyload_exclusions' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_3', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_3'] ) ) { echo 'class="autoptimize_lazyload_child hidden"'; } else { echo 'class="autoptimize_lazyload_child"'; } ?>>
1443 + <th scope="row"><?php esc_html_e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
1092 1444 <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>
1445 + <label><input type='text' style='width:80%' id='autoptimize_imgopt_lazyload_exclusions_text' name='autoptimize_imgopt_settings[autoptimize_imgopt_text_field_5]' value='<?php if ( ! empty( $options['autoptimize_imgopt_text_field_5'] ) ) { echo esc_attr( $options['autoptimize_imgopt_text_field_5'] ); } ?>'><br /><?php esc_html_e( 'Comma-separated list of to be excluded image classes or filenames.', 'autoptimize' ); ?></label>
1094 1446 </td>
1095 1447 </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>
1448 + <tr id='autoptimize_imgopt_lazyload_from_nth_image' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_3', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_3'] ) ) { echo 'class="autoptimize_lazyload_child hidden"'; } else { echo 'class="autoptimize_lazyload_child"'; } ?>>
1449 + <th scope="row"><?php esc_html_e( 'Lazy-load from nth image', 'autoptimize' ); ?></th>
1098 1450 <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>
1451 + <label><input type='number' min='0' max='50' style='width:80%' id='autoptimize_imgopt_lazyload_from_nth_image_number' name='autoptimize_imgopt_settings[autoptimize_imgopt_number_field_7]' value='<?php if ( ! empty( $options['autoptimize_imgopt_number_field_7'] ) ) { echo esc_attr( $options['autoptimize_imgopt_number_field_7'] ); } else { echo '1'; } ?>'><br /><?php esc_html_e( 'Don\'t lazyload the first X images, \'1\' lazyloads all.', 'autoptimize' ); ?></label>
1100 1452 </td>
1101 1453 </tr>
1102 1454 </table>
1103 - <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1455 + <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1104 1456 </form>
1105 1457 <script>
1106 1458 jQuery(document).ready(function() {
1107 1459 jQuery("#autoptimize_imgopt_checkbox").change(function() {
@@ -1106,26 +1458,21 @@
1106 1458 jQuery(document).ready(function() {
1107 1459 jQuery("#autoptimize_imgopt_checkbox").change(function() {
1108 1460 if (this.checked) {
1109 1461 jQuery("#autoptimize_imgopt_quality").show("slow");
1110 - jQuery("#autoptimize_imgopt_webp").show("slow");
1462 + jQuery("#autoptimize_imgopt_ngimg").show("slow");
1463 + jQuery("#autoptimize_imgopt_optimization_exclusions").show("slow");
1111 1464 } else {
1112 1465 jQuery("#autoptimize_imgopt_quality").hide("slow");
1113 - jQuery("#autoptimize_imgopt_webp").hide("slow");
1466 + jQuery("#autoptimize_imgopt_ngimg").hide("slow");
1467 + jQuery("#autoptimize_imgopt_optimization_exclusions").hide("slow");
1114 1468 }
1115 1469 });
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 1470 jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1123 1471 if (this.checked) {
1124 - jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1472 + jQuery(".autoptimize_lazyload_child").show("slow");
1125 1473 } else {
1126 - jQuery("#autoptimize_imgopt_lazyload_exclusions").hide("slow");
1127 - jQuery("#autoptimize_imgopt_webp_checkbox")[0].checked = false;
1474 + jQuery(".autoptimize_lazyload_child").hide("slow");
1128 1475 }
1129 1476 });
1130 1477 });
1131 1478 </script>
@@ -1135,13 +1482,13 @@
1135 1482 /**
1136 1483 * Ïmg opt status as used on dashboard.
1137 1484 */
1138 1485 public function get_imgopt_status_notice() {
1139 - if ( $this->imgopt_active() ) {
1486 + if ( $this->imgopt_active() && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1140 1487 $_imgopt_notice = '';
1141 1488 $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1142 1489 $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
1143 - $_imgopt_upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
1490 + $_imgopt_upsell = 'https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell';
1144 1491 $_imgopt_assoc = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1145 1492 $_imgopt_unreach = 'https://shortpixel.helpscoutdocs.com/article/148-why-are-my-images-redirected-from-cdn-shortpixel-ai';
1146 1493
1147 1494 if ( is_array( $_stat ) ) {
@@ -1146,37 +1493,40 @@
1146 1493
1147 1494 if ( is_array( $_stat ) ) {
1148 1495 if ( 1 == $_stat['Status'] ) {
1149 1496 // 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>' );
1497 + $_imgopt_notice = sprintf( esc_html__( 'Your ShortPixel image optimization and CDN quota is almost used, make sure you %1$sadd more credits%2$s to avoid slowing down your website %4$sor consider using %3$sAutoptimize Pro%2$s which comes with (nearly) unlimited image optimization%5$s but also automated critical CSS and extra booster options.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>', '<a href="https://autoptimize.com/pro/" target="_blank">', '<strong>', '</strong>' );
1151 1498 } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1152 1499 // 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>' );
1500 + $_imgopt_notice = sprintf( esc_html__( 'Your ShortPixel image optimization and CDN quota has been exhausted, %1$sadd more credits%2$s to continue to quickly deliver optimized images on your website %4$sor consider using %3$sAutoptimize Pro%2$s which comes with (nearly) unlimited image optimization%5$s but also automated critical CSS and extra booster options.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>', '<a href="https://autoptimize.com/pro/" target="_blank">', '<strong>', '</strong>' );
1154 1501 // 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>' );
1502 + $_imgopt_notice = $_imgopt_notice . ' ' . sprintf( esc_html__( 'If you have enough CDN quota remaining, then you may need to %1$sassociate your domain%2$s to your Shortpixel account.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $_imgopt_assoc . '" target="_blank">', '</a>' );
1156 1503 } 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>' );
1504 + // translators: "check the documentation here" will appear in a "a href".
1505 + $_imgopt_notice = sprintf( esc_html__( 'It seems ShortPixel image optimization is not able to fetch images from your site, %1$scheck the documentation here%2$s for more information', 'autoptimize' ), '<a href="' . $_imgopt_unreach . '" target="_blank">', '</a>' );
1159 1506 } else {
1160 - $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1507 + $_imgopt_upsell = 'https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell';
1161 1508 // 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>' );
1509 + $_imgopt_notice = sprintf( esc_html__( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
1163 1510 }
1164 1511
1165 1512 // add info on freshness + refresh link if status is not 2 (good shape).
1166 1513 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' ) );
1514 + $_imgopt_stats_refresh_url = add_query_arg(
1515 + array(
1516 + 'page' => 'autoptimize_imgopt',
1517 + 'refreshImgProvStats' => '1',
1518 + ),
1519 + admin_url( 'options-general.php' )
1520 + );
1171 1521 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'] );
1522 + $_imgopt_stats_last_run = esc_html__( 'based on status at ', 'autoptimize' ) . date_i18n( autoptimizeOptionWrapper::get_option( 'time_format' ), $_stat['timestamp'] );
1173 1523 } else {
1174 - $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1524 + $_imgopt_stats_last_run = esc_html__( 'based on previously fetched data', 'autoptimize' );
1175 1525 }
1176 1526 $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1177 1527 // 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>).' );
1528 + $_imgopt_notice .= sprintf( esc_html__( 'you can click %1$shere to refresh your quota status%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1179 1529 }
1180 1530
1181 1531 // and make the full notice filterable.
1182 1532 $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
@@ -1197,18 +1547,24 @@
1197 1547 }
1198 1548
1199 1549 /**
1200 1550 * Get img provider stats (used to display notice).
1551 + *
1552 + * @param bool $_refresh Should the stats be forcefully refreshed or not.
1201 1553 */
1202 - public function query_img_provider_stats() {
1203 - if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) ) {
1554 + public function query_img_provider_stats( $_refresh = false ) {
1555 + if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1204 1556 $url = '';
1205 - $endpoint = $this->get_imgopt_host() . 'read-domain/';
1557 + $stat_dom = 'https://no-cdn.shortpixel.ai/';
1558 + $endpoint = $stat_dom . 'read-domain/';
1206 1559 $domain = AUTOPTIMIZE_SITE_DOMAIN;
1207 1560
1208 1561 // make sure parse_url result makes sense, keeping $url empty if not.
1209 1562 if ( $domain && ! empty( $domain ) ) {
1210 1563 $url = $endpoint . $domain;
1564 + if ( true === $_refresh ) {
1565 + $url = $url . '/refresh';
1566 + }
1211 1567 }
1212 1568
1213 1569 $url = apply_filters(
1214 1570 'autoptimize_filter_imgopt_stat_url',
@@ -1245,9 +1601,13 @@
1245 1601 {
1246 1602 static $launch_status = null;
1247 1603
1248 1604 if ( null === $launch_status ) {
1249 - $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
1605 + $avail_imgopt = '';
1606 + if ( is_array( $this->options ) && array_key_exists( 'availabilities', $this->options ) && is_array( $this->options['availabilities'] ) && array_key_exists( 'extra_imgopt', $this->options['availabilities'] ) ) {
1607 + $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
1608 + }
1609 +
1250 1610 $magic_number = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1251 1611 $has_launched = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_launched', '' );
1252 1612 $launch_status = false;
1253 1613 if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {