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 +516 -158 2.6.2 → 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.
@@ -557,9 +722,9 @@
557 722
558 723 $_url = $this->normalize_img_url( $_url );
559 724
560 725 $placeholder = '';
561 - 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 ) ) {
562 727 $lqip_w = '';
563 728 $lqip_h = '';
564 729 if ( isset( $imgopt_w ) && ! empty( $imgopt_w ) ) {
565 730 $lqip_w = ',w_' . $imgopt_w;
@@ -572,8 +737,15 @@
572 737 // then call add_lazyload-function with lpiq placeholder if set.
573 738 $tag = $this->add_lazyload( $tag, $placeholder );
574 739 }
575 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 +
576 748 // and add tag to array for later replacement.
577 749 if ( $tag !== $orig_tag ) {
578 750 $to_replace[ $orig_tag ] = $tag;
579 751 }
@@ -582,43 +754,72 @@
582 754
583 755 // and replace all.
584 756 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
585 757
586 - // img thumbnails in e.g. woocommerce.
587 - 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 ) ) {
588 775 $out = preg_replace_callback(
589 - '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
590 - array( $this, 'replace_data_thumbs' ),
776 + '/style=(?:"|\')[^<>]*?background(?:-image)?:[^;"\'()>]*url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
777 + array( $this, 'replace_img_callback' ),
591 778 $out
592 779 );
593 780 }
594 781
595 - // background-image in inline style.
596 - 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 ) ) {
597 784 $out = preg_replace_callback(
598 - '/style=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
599 - 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' ),
600 787 $out
601 788 );
602 789 }
603 790
604 - // lazyload: restore noscript tags + lazyload picture source tags and bgimage.
791 + // lazyload picture source tags and bgimage.
605 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() ) {
606 801 $out = autoptimizeBase::restore_marked_content(
607 802 'SCRIPT',
608 803 $out
609 804 );
805 + }
610 806
611 - $out = $this->process_picture_tag( $out, true, true );
612 - $out = $this->process_bgimage( $out );
613 - } else {
614 - $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 + }
615 812 }
616 813
814 + if ( ! empty( $to_preload ) ) {
815 + $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
816 + }
817 +
617 818 return $out;
618 819 }
619 820
620 - public function get_size_from_tag( $tag ) {
821 + public static function get_size_from_tag( $tag ) {
621 822 // reusable function to extract widht and height from an image tag
622 823 // enforcing a filterable maximum width and height (default 4999X4999).
623 824 $width = '';
624 825 $height = '';
@@ -637,15 +838,19 @@
637 838 // check for and enforce (filterable) max sizes.
638 839 $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
639 840 if ( $width > $_max_width ) {
640 841 $_width = $_max_width;
641 - $height = $_width / $width * $height;
842 + if ( ! empty( $height ) && is_int( $height ) ) {
843 + $height = $_width / $width * $height;
844 + }
642 845 $width = $_width;
643 846 }
644 847 $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
645 848 if ( $height > $_max_height ) {
646 849 $_height = $_max_height;
647 - $width = $_height / $height * $width;
850 + if ( ! empty( $width ) && is_int( $width ) ) {
851 + $width = $_height / $height * $width;
852 + }
648 853 $height = $_height;
649 854 }
650 855
651 856 return array(
@@ -656,26 +861,32 @@
656 861
657 862 /**
658 863 * Lazyload functions
659 864 */
660 - public static function should_lazyload_wrapper() {
865 + public static function should_lazyload_wrapper( $no_meta = false ) {
661 866 // needed in autoptimizeMain.php.
662 867 $self = new self();
663 - return $self->should_lazyload();
868 + return $self->should_lazyload( '', $no_meta );
664 869 }
665 870
666 - public function should_lazyload( $context = '' ) {
871 + public function should_lazyload( $context = '', $no_meta = false ) {
667 872 if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && false === $this->check_nolazy() ) {
668 873 $lazyload_return = true;
669 874 } else {
670 875 $lazyload_return = false;
671 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 +
672 883 $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return, $context );
673 884
674 885 return $lazyload_return;
675 886 }
676 887
677 - public function check_nolazy() {
888 + public static function check_nolazy() {
678 889 if ( array_key_exists( 'ao_nolazy', $_GET ) && '1' === $_GET['ao_nolazy'] ) {
679 890 return true;
680 891 } else {
681 892 return false;
@@ -685,8 +896,9 @@
685 896 public function filter_lazyload_images( $in )
686 897 {
687 898 // only used is image optimization is NOT active but lazyload is.
688 899 $to_replace = array();
900 + $to_preload = '';
689 901
690 902 // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
691 903 $out = autoptimizeBase::replace_contents_with_marker_if_exists(
692 904 'SCRIPT',
@@ -694,11 +906,20 @@
694 906 '#<(?:no)?script.*?<\/(?:no)?script>#is',
695 907 $in
696 908 );
697 909
698 - // 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.
699 914 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
700 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.
701 922 if ( $this->should_lazyload( $out ) ) {
702 923 $to_replace[ $tag ] = $this->add_lazyload( $tag );
703 924 }
704 925 }
@@ -716,14 +937,33 @@
716 937 'SCRIPT',
717 938 $out
718 939 );
719 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 +
720 952 return $out;
721 953 }
722 954
723 955 public function add_lazyload( $tag, $placeholder = '' ) {
724 956 // adds actual lazyload-attributes to an image node.
725 - 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 ) {
726 966 $tag = $this->maybe_fix_missing_quotes( $tag );
727 967
728 968 // store original tag for use in noscript version.
729 969 $noscript_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>';
@@ -737,13 +977,13 @@
737 977 // get image width & heigth for placeholder fun (and to prevent content reflow).
738 978 $_get_size = $this->get_size_from_tag( $tag );
739 979 $width = $_get_size['width'];
740 980 $height = $_get_size['height'];
741 - if ( false === $width ) {
742 - $widht = 210; // default width for SVG placeholder.
981 + if ( false === $width || empty( $width ) ) {
982 + $width = 210; // default width for SVG placeholder.
743 983 }
744 - if ( false === $height ) {
745 - $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.
746 986 }
747 987
748 988 // insert the actual lazyload stuff.
749 989 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
@@ -749,10 +989,11 @@
749 989 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
750 990 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
751 991 }
752 992
753 - $tag = preg_replace( '/(\s)src=/', ' src=\'' . $placeholder . '\' data-src=', $tag );
754 - $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 );
755 996
756 997 // move sizes to data-sizes unless filter says no.
757 998 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_move_sizes', true ) ) {
758 999 $tag = str_replace( ' sizes=', ' data-sizes=', $tag );
@@ -760,8 +1001,10 @@
760 1001
761 1002 // add the noscript-tag from earlier.
762 1003 $tag = $noscript_tag . $tag;
763 1004 $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
1005 + } else {
1006 + $tag = apply_filters( 'autoptimize_filter_imgopt_not_lazyloaded_img', $tag );
764 1007 }
765 1008
766 1009 return $tag;
767 1010 }
@@ -766,9 +1009,9 @@
766 1009 return $tag;
767 1010 }
768 1011
769 1012 public function add_lazyload_js_footer() {
770 - if ( false === autoptimizeMain::should_buffer() ) {
1013 + if ( false === autoptimizeMain::should_buffer() || autoptimizeMain::is_amp_markup( '' ) ) {
771 1014 return;
772 1015 }
773 1016
774 1017 // The JS will by default be excluded form autoptimization but this can be changed with a filter.
@@ -776,28 +1019,81 @@
776 1019 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
777 1020 $noptimize_flag = ' data-noptimize="1"';
778 1021 }
779 1022
780 - $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js?ao_version=' . AUTOPTIMIZE_PLUGIN_VERSION, __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 +
781 1031 $cdn_url = $this->get_cdn_url();
782 1032 if ( ! empty( $cdn_url ) ) {
1033 + $cdn_url = rtrim( $cdn_url, '/' );
783 1034 $lazysizes_js = str_replace( AUTOPTIMIZE_WP_SITE_URL, $cdn_url, $lazysizes_js );
784 1035 }
785 1036
1037 + $type_js = '';
1038 + if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
1039 + $type_js = ' type="text/javascript"';
1040 + }
1041 +
786 1042 // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
787 - 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>' );
788 - echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
789 - 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 + }
790 1047
791 - // And add webp detection and loading JS.
792 - if ( $this->should_webp() ) {
793 - $_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);";
794 - $_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,'))})});";
795 - 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 '';
796 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;
797 1073 }
798 1074
799 - 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() {
800 1096 // getting CDN url here to avoid having to make bigger changes to autoptimizeBase.
801 1097 static $cdn_url = null;
802 1098
803 1099 if ( null === $cdn_url ) {
@@ -816,9 +1112,9 @@
816 1112 if ( null === $exclude_lazyload_array ) {
817 1113 $options = $this->options;
818 1114
819 1115 // set default exclusions.
820 - $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg', 'loading="eager"' );
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"' );
821 1117
822 1118 // add from setting.
823 1119 if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
824 1120 $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
@@ -837,9 +1133,9 @@
837 1133 public function inject_classes_in_tag( $tag, $target_class ) {
838 1134 if ( strpos( $tag, 'class=' ) !== false ) {
839 1135 $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
840 1136 } else {
841 - $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 );
842 1138 }
843 1139
844 1140 return $tag;
845 1141 }
@@ -847,21 +1143,21 @@
847 1143 public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
848 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';
849 1145 }
850 1146
851 - public function should_webp() {
852 - static $webp_return = null;
1147 + public function should_ngimg() {
1148 + static $ngimg_return = null;
853 1149
854 - if ( is_null( $webp_return ) ) {
855 - // webp only works if imgopt and lazyload are also active.
856 - if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && $this->imgopt_active() ) {
857 - $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;
858 1154 } else {
859 - $webp_return = false;
1155 + $ngimg_return = false;
860 1156 }
861 1157 }
862 1158
863 - return $webp_return;
1159 + return $ngimg_return;
864 1160 }
865 1161
866 1162 public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
867 1163 // check if "<picture" is present and if filter allows us to process <picture>.
@@ -880,9 +1176,9 @@
880 1176 foreach ( $_sources as $_source ) {
881 1177 $_picture_replacement = $_source[0];
882 1178
883 1179 // should we optimize the image?
884 - if ( $imgopt && $this->can_optimize_image( $_source[1] ) ) {
1180 + if ( $imgopt && $this->can_optimize_image( $_source[1], $_picture[0] ) ) {
885 1181 $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
886 1182 }
887 1183 // should we lazy-load?
888 1184 if ( $lazy && $this->should_lazyload() && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
@@ -901,9 +1197,9 @@
901 1197
902 1198 public function process_bgimage( $in ) {
903 1199 if ( strpos( $in, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_lazyload_backgroundimages', true ) ) {
904 1200 $out = preg_replace_callback(
905 - '/(<(?: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\((?:"|\')?([^"\')]*)(?:"|\')?\)[^>]*/',
906 1202 array( $this, 'lazyload_bgimg_callback' ),
907 1203 $in
908 1204 );
909 1205 return $out;
@@ -915,13 +1211,17 @@
915 1211 if ( str_ireplace( $this->get_lazyload_exclusions(), '', $matches[0] ) === $matches[0] ) {
916 1212 // get placeholder & lazyload class strings.
917 1213 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( 500, 300 ) );
918 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] );
919 1217 // replace background-image URL with SVG placeholder.
920 - $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] );
921 1221 // add data-bg attribute with real background-image URL for lazyload to pick up.
922 - $out = str_replace( $matches[1], $matches[1] . ' data-bg="' . trim( str_replace( "\r\n", '', $matches[2] ) ) . '"', $out );
923 - // 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.
924 1224 $out = $this->inject_classes_in_tag( $out, "$lazyload_class " );
925 1225 return $out;
926 1226 }
927 1227 return $matches[0];
@@ -926,12 +1226,18 @@
926 1226 }
927 1227 return $matches[0];
928 1228 }
929 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 +
930 1235 public function maybe_fix_missing_quotes( $tag_in ) {
931 - // 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.
932 - if ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) {
933 - 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;
934 1240 } else {
935 1241 return $tag_in;
936 1242 }
937 1243 }
@@ -943,9 +1249,9 @@
943 1249 {
944 1250 // no acces if multisite and not network admin and no site config allowed.
945 1251 if ( autoptimizeConfig::should_show_menu_tabs() ) {
946 1252 add_submenu_page(
947 - null,
1253 + '',
948 1254 'autoptimize_imgopt',
949 1255 'autoptimize_imgopt',
950 1256 'manage_options',
951 1257 'autoptimize_imgopt',
@@ -957,9 +1263,9 @@
957 1263
958 1264 public function add_imgopt_tab( $in )
959 1265 {
960 1266 if ( autoptimizeConfig::should_show_menu_tabs() ) {
961 - $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' ) ) ) );
962 1268 }
963 1269
964 1270 return $in;
965 1271 }
@@ -965,11 +1271,14 @@
965 1271 }
966 1272
967 1273 public function imgopt_options_page()
968 1274 {
1275 + // phpcs:disable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace
1276 + // phpcs:disable Generic.Formatting.DisallowMultipleStatements.SameLine
1277 +
969 1278 // Check querystring for "refreshCacheChecker" and call cachechecker if so.
970 1279 if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
971 - $this->query_img_provider_stats();
1280 + $this->query_img_provider_stats( true );
972 1281 }
973 1282
974 1283 $options = $this->fetch_options();
975 1284 $sp_url_suffix = $this->get_service_url_suffix();
@@ -977,18 +1286,26 @@
977 1286 <style>
978 1287 #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
979 1288 #ao_settings_form .form-table th {font-weight: normal;}
980 1289 #autoptimize_imgopt_descr{font-size: 120%;}
1290 + #ao_settings_form td p.condition-warning {color: orange;}
981 1291 </style>
982 - <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>
983 1293 <div class="wrap">
984 - <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>
985 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 } ?>
986 1303 <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
987 1304 <div class="notice-warning notice"><p>
988 1305 <?php
989 1306 // translators: "Autoptimize support forum" will appear in a "a href".
990 - 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>' );
991 1308 ?>
992 1309 </p></div>
993 1310 <?php } ?>
994 1311
@@ -993,9 +1310,9 @@
993 1310 <?php } ?>
994 1311
995 1312 <?php if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! autoptimizeImages::instance()->launch_ok() ) { ?>
996 1313 <div class="notice-warning notice"><p>
997 - <?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' ); ?>
998 1315 </p></div>
999 1316 <?php } ?>
1000 1317
1001 1318 <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
@@ -1001,21 +1318,21 @@
1001 1318 <?php if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'get_active_modules' ) && in_array( 'photon', Jetpack::get_active_modules() ) ) { ?>
1002 1319 <div class="notice-warning notice"><p>
1003 1320 <?php
1004 1321 // translators: "disable Jetpack's site accelerator for images" will appear in a "a href" linking to the jetpack settings page.
1005 - 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>' );
1006 1323 ?>
1007 1324 </p></div>
1008 1325 <?php } ?>
1009 1326 <form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
1010 1327 <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
1011 - <h2><?php _e( 'Image optimization', 'autoptimize' ); ?></h2>
1012 - <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>
1013 1330 <table class="form-table">
1014 1331 <tr>
1015 - <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
1332 + <th scope="row"><?php esc_html_e( 'Image optimization & CDN', 'autoptimize' ); ?></th>
1016 1333 <td>
1017 - <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>
1018 1335 <?php
1019 1336 // show shortpixel status.
1020 1337 $_notice = autoptimizeImages::instance()->get_imgopt_status_notice();
1021 1338 if ( $_notice ) {
@@ -1033,31 +1350,37 @@
1033 1350 break;
1034 1351 default:
1035 1352 $_notice_color = 'green';
1036 1353 }
1037 - 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>' );
1038 1355 } else {
1039 1356 // translators: link points to shortpixel.
1040 - $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>' );
1041 1358 if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
1042 - $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>' );
1043 1360 } else {
1044 - // translators: link points to shortpixel.
1045 - $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>' );
1046 1363 }
1047 - 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>' );
1048 1365 }
1049 1366 // translators: link points to shortpixel FAQ.
1050 - $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>' );
1051 - $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' );
1052 1369 // translators: links points to shortpixel TOS & Privacy Policy.
1053 - $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>' );
1054 - 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>' );
1055 1372 ?>
1056 1373 </td>
1057 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>
1058 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"'; } ?>>
1059 - <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
1382 + <th scope="row"><?php esc_html_e( 'Image Optimization quality', 'autoptimize' ); ?></th>
1060 1383 <td>
1061 1384 <label>
1062 1385 <select name='autoptimize_imgopt_settings[autoptimize_imgopt_select_field_2]'>
1063 1386 <?php
@@ -1077,33 +1400,60 @@
1077 1400 </label>
1078 1401 <p>
1079 1402 <?php
1080 1403 // translators: link points to shortpixel image test page.
1081 - 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>' ) );
1082 1405 ?>
1083 1406 </p>
1084 1407 </td>
1085 1408 </tr>
1086 - <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"'; } ?>>
1087 - <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>
1088 1438 <td>
1089 - <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>
1090 1440 </td>
1091 1441 </tr>
1092 - <tr>
1093 - <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>
1094 1444 <td>
1095 - <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>
1096 1446 </td>
1097 1447 </tr>
1098 - <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"'; } ?>>
1099 - <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>
1100 1450 <td>
1101 - <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>
1102 1452 </td>
1103 1453 </tr>
1104 1454 </table>
1105 - <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>
1106 1456 </form>
1107 1457 <script>
1108 1458 jQuery(document).ready(function() {
1109 1459 jQuery("#autoptimize_imgopt_checkbox").change(function() {
@@ -1108,26 +1458,21 @@
1108 1458 jQuery(document).ready(function() {
1109 1459 jQuery("#autoptimize_imgopt_checkbox").change(function() {
1110 1460 if (this.checked) {
1111 1461 jQuery("#autoptimize_imgopt_quality").show("slow");
1112 - jQuery("#autoptimize_imgopt_webp").show("slow");
1462 + jQuery("#autoptimize_imgopt_ngimg").show("slow");
1463 + jQuery("#autoptimize_imgopt_optimization_exclusions").show("slow");
1113 1464 } else {
1114 1465 jQuery("#autoptimize_imgopt_quality").hide("slow");
1115 - jQuery("#autoptimize_imgopt_webp").hide("slow");
1466 + jQuery("#autoptimize_imgopt_ngimg").hide("slow");
1467 + jQuery("#autoptimize_imgopt_optimization_exclusions").hide("slow");
1116 1468 }
1117 1469 });
1118 - jQuery("#autoptimize_imgopt_webp_checkbox").change(function() {
1119 - if (this.checked) {
1120 - jQuery("#autoptimize_imgopt_lazyload_checkbox")[0].checked = true;
1121 - jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1122 - }
1123 - });
1124 1470 jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1125 1471 if (this.checked) {
1126 - jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1472 + jQuery(".autoptimize_lazyload_child").show("slow");
1127 1473 } else {
1128 - jQuery("#autoptimize_imgopt_lazyload_exclusions").hide("slow");
1129 - jQuery("#autoptimize_imgopt_webp_checkbox")[0].checked = false;
1474 + jQuery(".autoptimize_lazyload_child").hide("slow");
1130 1475 }
1131 1476 });
1132 1477 });
1133 1478 </script>
@@ -1137,13 +1482,13 @@
1137 1482 /**
1138 1483 * Ïmg opt status as used on dashboard.
1139 1484 */
1140 1485 public function get_imgopt_status_notice() {
1141 - if ( $this->imgopt_active() ) {
1486 + if ( $this->imgopt_active() && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1142 1487 $_imgopt_notice = '';
1143 1488 $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1144 1489 $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
1145 - $_imgopt_upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
1490 + $_imgopt_upsell = 'https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell';
1146 1491 $_imgopt_assoc = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1147 1492 $_imgopt_unreach = 'https://shortpixel.helpscoutdocs.com/article/148-why-are-my-images-redirected-from-cdn-shortpixel-ai';
1148 1493
1149 1494 if ( is_array( $_stat ) ) {
@@ -1148,37 +1493,40 @@
1148 1493
1149 1494 if ( is_array( $_stat ) ) {
1150 1495 if ( 1 == $_stat['Status'] ) {
1151 1496 // translators: "add more credits" will appear in a "a href".
1152 - $_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>' );
1153 1498 } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1154 1499 // translators: "add more credits" will appear in a "a href".
1155 - $_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>' );
1156 1501 // translators: "associate your domain" will appear in a "a href".
1157 - $_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>' );
1158 1503 } elseif ( -3 == $_stat['Status'] ) {
1159 1504 // translators: "check the documentation here" will appear in a "a href".
1160 - $_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>' );
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>' );
1161 1506 } else {
1162 - $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1507 + $_imgopt_upsell = 'https://misc.optimizingmatters.com/partners/?from=aofree&partner=shortpixelupsell';
1163 1508 // translators: "log in to check your account" will appear in a "a href".
1164 - $_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>' );
1165 1510 }
1166 1511
1167 1512 // add info on freshness + refresh link if status is not 2 (good shape).
1168 1513 if ( 2 != $_stat['Status'] ) {
1169 - $_imgopt_stats_refresh_url = add_query_arg( array(
1170 - 'page' => 'autoptimize_imgopt',
1171 - 'refreshImgProvStats' => '1',
1172 - ), 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 + );
1173 1521 if ( $_stat && array_key_exists( 'timestamp', $_stat ) && ! empty( $_stat['timestamp'] ) ) {
1174 - $_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'] );
1175 1523 } else {
1176 - $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1524 + $_imgopt_stats_last_run = esc_html__( 'based on previously fetched data', 'autoptimize' );
1177 1525 }
1178 1526 $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1179 1527 // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1180 - $_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>).' );
1181 1529 }
1182 1530
1183 1531 // and make the full notice filterable.
1184 1532 $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
@@ -1199,18 +1547,24 @@
1199 1547 }
1200 1548
1201 1549 /**
1202 1550 * Get img provider stats (used to display notice).
1551 + *
1552 + * @param bool $_refresh Should the stats be forcefully refreshed or not.
1203 1553 */
1204 - public function query_img_provider_stats() {
1205 - 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 ) ) {
1206 1556 $url = '';
1207 - $endpoint = $this->get_imgopt_host() . 'read-domain/';
1557 + $stat_dom = 'https://no-cdn.shortpixel.ai/';
1558 + $endpoint = $stat_dom . 'read-domain/';
1208 1559 $domain = AUTOPTIMIZE_SITE_DOMAIN;
1209 1560
1210 1561 // make sure parse_url result makes sense, keeping $url empty if not.
1211 1562 if ( $domain && ! empty( $domain ) ) {
1212 1563 $url = $endpoint . $domain;
1564 + if ( true === $_refresh ) {
1565 + $url = $url . '/refresh';
1566 + }
1213 1567 }
1214 1568
1215 1569 $url = apply_filters(
1216 1570 'autoptimize_filter_imgopt_stat_url',
@@ -1247,9 +1601,13 @@
1247 1601 {
1248 1602 static $launch_status = null;
1249 1603
1250 1604 if ( null === $launch_status ) {
1251 - $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 +
1252 1610 $magic_number = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1253 1611 $has_launched = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_launched', '' );
1254 1612 $launch_status = false;
1255 1613 if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {