PluginProbe
Autoptimize / 3.1.4
Autoptimize v3.1.4
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
← All changes | classes/autoptimizeImages.php +541 -192 2.5.13.1.4 View file →
@@ -30,8 +30,9 @@
30 30 $options = $this->fetch_options();
31 31 }
32 32
33 33 $this->set_options( $options );
34 + $this->lazyload_counter = 0;
34 35 }
35 36
36 37 public function set_options( array $options )
37 38 {
@@ -41,9 +42,9 @@
41 42 }
42 43
43 44 public static function fetch_options()
44 45 {
45 - $value = get_option( 'autoptimize_imgopt_settings' );
46 + $value = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_settings' );
46 47 if ( empty( $value ) ) {
47 48 // Fallback to returning defaults when no stored option exists yet.
48 49 $value = autoptimizeConfig::get_ao_imgopt_default_options();
49 50 }
@@ -48,12 +49,32 @@
48 49 $value = autoptimizeConfig::get_ao_imgopt_default_options();
49 50 }
50 51
51 52 // get service availability and add it to the options-array.
52 - $value['availabilities'] = get_option( 'autoptimize_service_availablity' );
53 + $value['availabilities'] = autoptimizeOptionWrapper::get_option( 'autoptimize_service_availablity' );
53 54
54 - if ( empty( $value['availabilities'] ) ) {
55 - $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
55 + if ( empty( $value['availabilities'] ) || ! is_array( $value['availabilities'] ) ) {
56 + $value['availabilities'] = null;
57 +
58 + if ( true === autoptimizeImages::imgopt_active() ) {
59 + $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
60 + }
61 +
62 + if ( null === $value['availabilities'] ) {
63 + // We can't seem to check service availability, use mock result with imgopt status UP.
64 + $_mock_settings = array(
65 + 'extra_imgopt' => array(
66 + 'status' => 'up',
67 + 'hosts' => array(
68 + '1' => 'https://sp-ao.shortpixel.ai/',
69 + ),
70 + ),
71 + 'critcss' => array(
72 + 'status' => 'up',
73 + ),
74 + );
75 + $value['availabilities'] = $_mock_settings;
76 + }
56 77 }
57 78
58 79 return $value;
59 80 }
@@ -65,9 +86,9 @@
65 86 // and does not use/ request the availablity data (which could slow things down).
66 87 static $imgopt_active = null;
67 88
68 89 if ( null === $imgopt_active ) {
69 - $opts = get_option( 'autoptimize_imgopt_settings', '' );
90 + $opts = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_settings', '' );
70 91 if ( ! empty( $opts ) && is_array( $opts ) && array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $opts ) && ! empty( $opts['autoptimize_imgopt_checkbox_field_1'] ) && '1' === $opts['autoptimize_imgopt_checkbox_field_1'] ) {
71 92 $imgopt_active = true;
72 93 } else {
73 94 $imgopt_active = false;
@@ -96,12 +117,16 @@
96 117
97 118 public function run()
98 119 {
99 120 if ( is_admin() ) {
100 - add_action( 'admin_menu', array( $this, 'imgopt_admin_menu' ) );
121 + if ( is_multisite() && is_network_admin() && autoptimizeOptionWrapper::is_ao_active_for_network() ) {
122 + add_action( 'network_admin_menu', array( $this, 'imgopt_admin_menu' ) );
123 + } else {
124 + add_action( 'admin_menu', array( $this, 'imgopt_admin_menu' ) );
125 + }
101 126 add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_imgopt_tab' ), 9 );
102 127 } else {
103 - $this->run_on_frontend();
128 + add_action( 'wp', array( $this, 'run_on_frontend' ) );
104 129 }
105 130 }
106 131
107 132 public function run_on_frontend() {
@@ -107,8 +132,14 @@
107 132 public function run_on_frontend() {
108 133 if ( ! $this->should_run() ) {
109 134 if ( $this->should_lazyload() ) {
110 135 add_filter(
136 + 'wp_lazy_loading_enabled',
137 + array( $this, 'should_disable_core_lazyload' ),
138 + 10,
139 + 3
140 + );
141 + add_filter(
111 142 'autoptimize_html_after_minify',
112 143 array( $this, 'filter_lazyload_images' ),
113 144 10,
114 145 1
@@ -154,16 +185,40 @@
154 185 );
155 186 }
156 187
157 188 if ( $this->should_lazyload() ) {
189 + add_filter(
190 + 'wp_lazy_loading_enabled',
191 + array( $this, 'should_disable_core_lazyload' ),
192 + 10,
193 + 3
194 + );
158 195 add_action(
159 196 'wp_footer',
160 - array( $this, 'add_lazyload_js_footer' )
197 + array( $this, 'add_lazyload_js_footer' ),
198 + 10,
199 + 0
161 200 );
162 201 }
163 202 }
164 203
165 204 /**
205 + * Disables core's native lazyload for images, not for iframes.
206 + *
207 + * @param bool $flag Incoming flag (mostly true).
208 + * @param string $tag Tag (img or iframe).
209 + * @param string $context Full context.
210 + *
211 + * @return bool
212 + */
213 + public function should_disable_core_lazyload( $flag = true, $tag = '', $context = '' ) {
214 + if ( 'img' === $tag ) {
215 + return false;
216 + }
217 + return $flag;
218 + }
219 +
220 + /**
166 221 * Basic checks before we can run.
167 222 *
168 223 * @return bool
169 224 */
@@ -174,9 +229,10 @@
174 229 $not_launch_status = ( 'launch' !== $opts['availabilities']['extra_imgopt']['status'] );
175 230
176 231 $do_cdn = true;
177 232 $_userstatus = $this->get_imgopt_provider_userstatus();
178 - if ( -2 == $_userstatus['Status'] ) {
233 + if ( isset( $_userstatus['Status'] ) && ( -2 == $_userstatus['Status'] || -3 == $_userstatus['Status'] ) ) {
234 + // don't even attempt to put images on CDN if heavily exceeded threshold or if site not reachable.
179 235 $do_cdn = false;
180 236 }
181 237
182 238 if (
@@ -194,9 +250,9 @@
194 250 {
195 251 static $imgopt_host = null;
196 252
197 253 if ( null === $imgopt_host ) {
198 - $imgopt_host = 'https://cdn.shortpixel.ai/';
254 + $imgopt_host = 'https://sp-ao.shortpixel.ai/';
199 255 $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
200 256 if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
201 257 $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
202 258 }
@@ -214,9 +270,9 @@
214 270 }
215 271
216 272 public static function get_service_url_suffix()
217 273 {
218 - $suffix = '/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
274 + $suffix = '/af/U0ZIWMK109483/' . AUTOPTIMIZE_SITE_DOMAIN;
219 275
220 276 return $suffix;
221 277 }
222 278
@@ -301,9 +357,9 @@
301 357
302 358 // get CDN domain once.
303 359 static $cdn_domain = null;
304 360 if ( is_null( $cdn_domain ) ) {
305 - $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', get_option( 'autoptimize_cdn_url', '' ) );
361 + $cdn_url = $this->get_cdn_url();
306 362 if ( ! empty( $cdn_url ) ) {
307 363 $cdn_domain = parse_url( $cdn_url, PHP_URL_HOST );
308 364 } else {
309 365 $cdn_domain = '';
@@ -325,20 +381,29 @@
325 381 }
326 382
327 383 // Do the work on cache miss only.
328 384 if ( ! isset( $cache[ $in ] ) ) {
329 - // Default to what was given to us.
385 + // Default to (the trimmed version of) what was given to us.
330 386 $result = trim( $in );
331 - if ( autoptimizeUtils::is_protocol_relative( $in ) ) {
332 - $result = $parsed_site_url['scheme'] . ':' . $in;
333 - } elseif ( 0 === strpos( $in, '/' ) ) {
387 +
388 + // Some silly plugins wrap background images in html-encoded quotes, so remove those from the img url.
389 + $result = $this->fix_silly_bgimg_quotes( $result );
390 +
391 + if ( autoptimizeUtils::is_protocol_relative( $result ) ) {
392 + $result = $parsed_site_url['scheme'] . ':' . $result;
393 + } elseif ( 0 === strpos( $result, '/' ) ) {
334 394 // Root-relative...
335 - $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'];
336 - $result .= $in;
337 - } elseif ( ! empty( $cdn_domain ) && strpos( $in, $cdn_domain ) !== 0 ) {
338 - $result = str_replace( $cdn_domain, $parsed_site_url['host'], $in );
395 + $result = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'] . $result;
396 + } elseif ( ! empty( $cdn_domain ) && false === strpos( $this->get_imgopt_host(), $cdn_domain ) && strpos( $result, $cdn_domain ) !== 0 ) {
397 + // remove CDN except if it is the image optimization one.
398 + $result = str_replace( $cdn_domain, $parsed_site_url['host'], $result );
339 399 }
340 400
401 + // filter (default off) to remove QS from image URL's to avoid eating away optimization credits.
402 + if ( apply_filters( 'autoptimize_filter_imgopt_no_querystring', false ) && strpos( $result, '?' ) !== false ) {
403 + $result = strtok( $result, '?' );
404 + }
405 +
341 406 $result = apply_filters( 'autoptimize_filter_imgopt_normalized_url', $result );
342 407
343 408 // Store in cache.
344 409 $cache[ $in ] = $result;
@@ -350,9 +415,9 @@
350 415 public function filter_optimize_css_images( $in )
351 416 {
352 417 $in = $this->normalize_img_url( $in );
353 418
354 - if ( $this->can_optimize_image( $in ) ) {
419 + if ( $this->can_optimize_image( $in ) && false === strpos( $in, $this->get_imgopt_host() ) ) {
355 420 return $this->build_imgopt_url( $in, '', '' );
356 421 } else {
357 422 return $in;
358 423 }
@@ -365,9 +430,15 @@
365 430 if ( null === $imgopt_base_url ) {
366 431 $imgopt_host = $this->get_imgopt_host();
367 432 $quality = $this->get_img_quality_string();
368 433 $ret_val = apply_filters( 'autoptimize_filter_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
369 - $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
434 + if ( $this->should_ngimg() ) {
435 + $sp_to_string = 'to_auto';
436 + } else {
437 + $sp_to_string = 'to_webp';
438 + }
439 + $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).
440 + $imgopt_base_url = $imgopt_host . 'client/' . $sp_to_string . ',' . $quality . ',' . $ret_val;
370 441 $imgopt_base_url = apply_filters( 'autoptimize_filter_imgopt_base_url', $imgopt_base_url );
371 442 }
372 443
373 444 return $imgopt_base_url;
@@ -372,9 +443,14 @@
372 443
373 444 return $imgopt_base_url;
374 445 }
375 446
376 - private function can_optimize_image( $url )
447 + public static function can_optimize_image_wrapper( $url, $tag = '', $testing = false ) {
448 + $self = new self();
449 + return $self->can_optimize_image( $url, $tag = '', $testing = false );
450 + }
451 +
452 + private function can_optimize_image( $url, $tag = '', $testing = false )
377 453 {
378 454 static $cdn_url = null;
379 455 static $nopti_images = null;
380 456
@@ -380,14 +456,17 @@
380 456
381 457 if ( null === $cdn_url ) {
382 458 $cdn_url = apply_filters(
383 459 'autoptimize_filter_base_cdnurl',
384 - get_option( 'autoptimize_cdn_url', '' )
460 + autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' )
385 461 );
386 462 }
387 463
388 - if ( null === $nopti_images ) {
389 - $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', '' );
464 + if ( null === $nopti_images || $testing ) {
465 + if ( is_array( $this->options ) && array_key_exists( 'autoptimize_imgopt_text_field_6', $this->options ) ) {
466 + $nopti_images = $this->options['autoptimize_imgopt_text_field_6'];
467 + }
468 + $nopti_images = apply_filters( 'autoptimize_filter_imgopt_noptimize', $nopti_images );
390 469 }
391 470
392 471 $site_host = AUTOPTIMIZE_SITE_DOMAIN;
393 472 $url = $this->normalize_img_url( $url );
@@ -394,19 +473,21 @@
394 473 $url_parsed = parse_url( $url );
395 474
396 475 if ( array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
397 476 return false;
477 + } elseif ( autoptimizeUtils::is_local_server() ) {
478 + return false;
398 479 } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && array_key_exists( 'host', $url_parsed ) && $url_parsed['host'] !== $site_host ) {
399 480 return false;
400 481 } elseif ( strpos( $url, '.php' ) !== false ) {
401 482 return false;
402 - } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
483 + } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg', '.webp', '.avif' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
403 484 // fixme: better check against end of string.
404 485 return false;
405 486 } elseif ( ! empty( $nopti_images ) ) {
406 487 $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
407 488 foreach ( $nopti_images_array as $nopti_image ) {
408 - if ( strpos( $url, $nopti_image ) !== false ) {
489 + if ( strpos( $url, $nopti_image ) !== false || ( ( '' !== $tag && strpos( $tag, $nopti_image ) !== false ) ) ) {
409 490 return false;
410 491 }
411 492 }
412 493 }
@@ -412,8 +493,14 @@
412 493 }
413 494 return true;
414 495 }
415 496
497 + // wrapper for reuse in AOPro.
498 + public static function build_imgopt_url_wrapper( $orig_url, $width = 0, $height = 0 ) {
499 + $self = new self();
500 + return $self->build_imgopt_url( $orig_url, $width = 0, $height = 0 );
501 + }
502 +
416 503 private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
417 504 {
418 505 // sanitize width and height.
419 506 if ( strpos( $width, '%' ) !== false ) {
@@ -436,9 +523,15 @@
436 523 if ( $filtered_url !== $orig_url ) {
437 524 return $filtered_url;
438 525 }
439 526
440 - $orig_url = $this->normalize_img_url( $orig_url );
527 + $normalized_url = $this->normalize_img_url( $orig_url );
528 +
529 + // 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.
530 + if ( apply_filters( 'autoptimize_filter_imgopt_check_normalized_url', true ) && ! preg_match( '/[^\x20-\x7e]/', $normalized_url ) && false === filter_var( $normalized_url, FILTER_VALIDATE_URL ) ) {
531 + return $orig_url;
532 + }
533 +
441 534 $imgopt_base_url = $this->get_imgopt_base_url();
442 535 $imgopt_size = '';
443 536
444 537 if ( $width && 0 !== $width ) {
@@ -448,9 +541,9 @@
448 541 if ( $height && 0 !== $height ) {
449 542 $imgopt_size .= ',h_' . $height;
450 543 }
451 544
452 - $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
545 + $url = $imgopt_base_url . $imgopt_size . '/' . $normalized_url;
453 546
454 547 return $url;
455 548 }
456 549
@@ -461,9 +554,9 @@
461 554
462 555 public function replace_img_callback( $matches, $width = 0, $height = 0 )
463 556 {
464 557 $_normalized_img_url = $this->normalize_img_url( $matches[1] );
465 - if ( $this->can_optimize_image( $matches[1] ) ) {
558 + if ( $this->can_optimize_image( $matches[1], $matches[0] ) ) {
466 559 return str_replace( $matches[1], $this->build_imgopt_url( $_normalized_img_url, $width, $height ), $matches[0] );
467 560 } else {
468 561 return $matches[0];
469 562 }
@@ -468,10 +561,29 @@
468 561 return $matches[0];
469 562 }
470 563 }
471 564
472 - public function filter_optimize_images( $in )
565 + public function replace_icon_callback( $matches )
473 566 {
567 + if ( array_key_exists( '2', $matches ) ) {
568 + $sizes = explode( 'x', $matches[2] );
569 + $width = $sizes[0];
570 + $height = $sizes[1];
571 + } else {
572 + $width = 180;
573 + $height = 180;
574 + }
575 +
576 + // make sure we're not trying to optimize a *.ico file.
577 + if ( strpos( $matches[1], '.ico' ) === false ) {
578 + return $this->replace_img_callback( $matches, $width, $height );
579 + } else {
580 + return $matches[0];
581 + }
582 + }
583 +
584 + public function filter_optimize_images( $in, $testing = false )
585 + {
474 586 /*
475 587 * potential future functional improvements:
476 588 *
477 589 * filter for critical CSS.
@@ -476,11 +588,12 @@
476 588 *
477 589 * filter for critical CSS.
478 590 */
479 591 $to_replace = array();
592 + $to_preload = '';
480 593
481 - // hide noscript tags to avoid nesting noscript tags (as lazyloaded images add noscript).
482 - if ( $this->should_lazyload() ) {
594 + // hide (no)script tags to avoid replacing (and potentially breaking) images in script tags.
595 + if ( apply_filters( 'autoptimize_filter_imgopt_hide_script', true ) || $this->should_lazyload() ) {
483 596 $in = autoptimizeBase::replace_contents_with_marker_if_exists(
484 597 'SCRIPT',
485 598 '<script',
486 599 '#<(?:no)?script.*?<\/(?:no)?script>#is',
@@ -487,11 +600,18 @@
487 600 $in
488 601 );
489 602 }
490 603
604 + // get img preloads as set in post metabox, exploding ", " instead of "," because LCP preload
605 + // could be a shortpixel URL, which has comma's and results in way too many preloads.
606 + $metabox_preloads = array_filter( array_map( 'trim', explode( ', ', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
607 + $metabox_preloads = apply_filters( 'autoptimize_filter_images_metabox_preloads', $metabox_preloads );
608 +
491 609 // extract img tags.
492 610 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
493 611 foreach ( $matches[0] as $tag ) {
612 + $tag = apply_filters( 'autoptimize_filter_imgopt_tag_preopt', $tag );
613 +
494 614 $orig_tag = $tag;
495 615 $imgopt_w = '';
496 616 $imgopt_h = '';
497 617
@@ -497,20 +617,22 @@
497 617
498 618 // first do (data-)srcsets.
499 619 if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
500 620 foreach ( $allsrcsets as $srcset ) {
501 - $srcset = $srcset[2];
502 - $srcsets = explode( ',', $srcset );
621 + $srcset = $srcset[2];
622 + $orig_srcset = $srcset;
623 + $srcsets = explode( ',', $srcset );
503 624 foreach ( $srcsets as $indiv_srcset ) {
504 625 $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
505 626 if ( isset( $indiv_srcset_parts[1] ) && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
506 627 $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
507 628 }
508 - if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
629 + if ( $this->can_optimize_image( $indiv_srcset_parts[0], $tag, $testing ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
509 630 $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
510 - $tag = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
631 + $srcset = str_replace( $indiv_srcset_parts[0], $imgopt_url, $srcset );
511 632 }
512 633 }
634 + $tag = str_replace( $orig_srcset, $srcset, $tag );
513 635 }
514 636 }
515 637
516 638 // proceed with img src.
@@ -523,9 +645,9 @@
523 645 if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
524 646 foreach ( $urls as $url ) {
525 647 $full_src_orig = $url[0];
526 648 $url = $url[1];
527 - if ( $this->can_optimize_image( $url ) ) {
649 + if ( $this->can_optimize_image( $url, $tag, $testing ) && false === apply_filters( 'autoptimize_filter_imgopt_do_spai', false ) ) {
528 650 $imgopt_url = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
529 651 $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
530 652 $tag = str_replace( $full_src_orig, $full_imgopt_src, $tag );
531 653 }
@@ -531,10 +653,15 @@
531 653 }
532 654 }
533 655 }
534 656
657 + // check if the image needs to be prelaoded.
658 + if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && str_replace( $metabox_preloads, '', $tag ) !== $tag ) {
659 + $to_preload .= $this->create_img_preload_tag( $tag );
660 + }
661 +
535 662 // do lazyload stuff.
536 - if ( $this->should_lazyload( $in ) ) {
663 + if ( $this->should_lazyload( $in ) && ! empty( $url ) ) {
537 664 // first do lpiq placeholder logic.
538 665 if ( strpos( $url, $this->get_imgopt_host() ) === 0 ) {
539 666 // if all img src have been replaced during srcset, we have to extract the
540 667 // origin url from the imgopt one to be able to set a lqip placeholder.
@@ -542,9 +669,12 @@
542 669 } else {
543 670 $_url = $url;
544 671 }
545 672
546 - if ( $this->can_optimize_image( $_url ) && apply_filters( 'autoptimize_filter_imgopt_lazyload_dolqip', true ) ) {
673 + $_url = $this->normalize_img_url( $_url );
674 +
675 + $placeholder = '';
676 + 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 ) ) {
547 677 $lqip_w = '';
548 678 $lqip_h = '';
549 679 if ( isset( $imgopt_w ) && ! empty( $imgopt_w ) ) {
550 680 $lqip_w = ',w_' . $imgopt_w;
@@ -557,8 +687,15 @@
557 687 // then call add_lazyload-function with lpiq placeholder if set.
558 688 $tag = $this->add_lazyload( $tag, $placeholder );
559 689 }
560 690
691 + // add decoding="async" behind filter, not sure if I'll make it default true yet.
692 + if ( true === apply_filters( 'autoptimize_filter_imgopt_add_decoding', true ) && false === strpos( $tag, ' decoding=' ) ) {
693 + $tag = str_replace( '<img ', '<img decoding="async" ', $tag );
694 + }
695 +
696 + $tag = apply_filters( 'autoptimize_filter_imgopt_tag_postopt', $tag );
697 +
561 698 // and add tag to array for later replacement.
562 699 if ( $tag !== $orig_tag ) {
563 700 $to_replace[ $orig_tag ] = $tag;
564 701 }
@@ -567,42 +704,72 @@
567 704
568 705 // and replace all.
569 706 $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
570 707
571 - // img thumbnails in e.g. woocommerce.
572 - if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_imgopt_datathumbs', true ) ) {
708 + // misc. node attributes that might hold image url's (incl. the previously separate data-thumb).
709 + $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' ) ) );
710 + if ( ! empty( $extra_attr_with_img ) && is_array( $extra_attr_with_img ) ) {
711 + foreach ( $extra_attr_with_img as $candidate ) {
712 + if ( is_array( $candidate ) && strpos( $out, $candidate[1] ) !== false ) {
713 + $_regex = '/\<' . $candidate[0] . '(?:[^>]*)?\s' . $candidate[1] . '=(?:"|\')(.+?)(?:"|\')(?:[^>]*)?>/s';
714 + $out = preg_replace_callback(
715 + $_regex,
716 + array( $this, 'replace_img_callback' ),
717 + $out
718 + );
719 + }
720 + }
721 + }
722 +
723 + // background-image in inline style.
724 + if ( strpos( $out, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
573 725 $out = preg_replace_callback(
574 - '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
575 - array( $this, 'replace_data_thumbs' ),
726 + '/style=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
727 + array( $this, 'replace_img_callback' ),
576 728 $out
577 729 );
578 730 }
579 731
580 - // background-image in inline style.
581 - if ( strpos( $out, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_backgroundimages', true ) ) {
732 + // act on icon links.
733 + if ( ( strpos( $out, '<link rel="icon"' ) !== false || ( strpos( $out, "<link rel='icon'" ) !== false ) ) && apply_filters( 'autoptimize_filter_imgopt_linkicon', true ) ) {
582 734 $out = preg_replace_callback(
583 - '/style=(?:"|\').*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)/',
584 - array( $this, 'replace_img_callback' ),
735 + '/<link\srel=(?:"|\')(?:apple-touch-)?icon(?:"|\').*\shref=(?:"|\')(.*)(?:"|\')(?:\ssizes=(?:"|\')(\d*x\d*)(?:"|\'))?\s\/>/Um',
736 + array( $this, 'replace_icon_callback' ),
585 737 $out
586 738 );
587 739 }
588 740
589 - // lazyload: restore noscript tags + lazyload picture source tags.
741 + // lazyload picture source tags and bgimage.
590 742 if ( $this->should_lazyload() ) {
743 + $out = $this->process_picture_tag( $out, true, true );
744 + $out = $this->process_bgimage( $out );
745 + } else {
746 + $out = $this->process_picture_tag( $out, true, false );
747 + }
748 +
749 + // restore (no)script tags.
750 + if ( apply_filters( 'autoptimize_filter_imgopt_hide_script', true ) || $this->should_lazyload() ) {
591 751 $out = autoptimizeBase::restore_marked_content(
592 752 'SCRIPT',
593 753 $out
594 754 );
755 + }
595 756
596 - $out = $this->process_picture_tag( $out, true, true );
597 - } else {
598 - $out = $this->process_picture_tag( $out, true, false );
757 + if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
758 + // the preload was not in an img tag, so adding a non-responsive preload instead.
759 + foreach ( $metabox_preloads as $img_preload ) {
760 + $to_preload .= '<link rel="preload" href="' . $img_preload . '" as="image">';
761 + }
599 762 }
600 763
764 + if ( ! empty( $to_preload ) ) {
765 + $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
766 + }
767 +
601 768 return $out;
602 769 }
603 770
604 - public function get_size_from_tag( $tag ) {
771 + public static function get_size_from_tag( $tag ) {
605 772 // reusable function to extract widht and height from an image tag
606 773 // enforcing a filterable maximum width and height (default 4999X4999).
607 774 $width = '';
608 775 $height = '';
@@ -621,15 +788,19 @@
621 788 // check for and enforce (filterable) max sizes.
622 789 $_max_width = apply_filters( 'autoptimize_filter_imgopt_max_width', 4999 );
623 790 if ( $width > $_max_width ) {
624 791 $_width = $_max_width;
625 - $height = $_width / $width * $height;
792 + if ( ! empty( $height ) && is_int( $height ) ) {
793 + $height = $_width / $width * $height;
794 + }
626 795 $width = $_width;
627 796 }
628 797 $_max_height = apply_filters( 'autoptimize_filter_imgopt_max_height', 4999 );
629 798 if ( $height > $_max_height ) {
630 799 $_height = $_max_height;
631 - $width = $_height / $height * $width;
800 + if ( ! empty( $width ) && is_int( $width ) ) {
801 + $width = $_height / $height * $width;
802 + }
632 803 $height = $_height;
633 804 }
634 805
635 806 return array(
@@ -647,22 +818,37 @@
647 818 return $self->should_lazyload();
648 819 }
649 820
650 821 public function should_lazyload( $context = '' ) {
651 - if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) ) {
822 + if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && false === $this->check_nolazy() ) {
652 823 $lazyload_return = true;
653 824 } else {
654 825 $lazyload_return = false;
655 826 }
827 +
828 + // If page/ post check post_meta to see if lazyload is off for page.
829 + if ( false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_lazyload' ) ) {
830 + $lazyload_return = false;
831 + }
832 +
656 833 $lazyload_return = apply_filters( 'autoptimize_filter_imgopt_should_lazyload', $lazyload_return, $context );
657 834
658 835 return $lazyload_return;
659 836 }
660 837
838 + public static function check_nolazy() {
839 + if ( array_key_exists( 'ao_nolazy', $_GET ) && '1' === $_GET['ao_nolazy'] ) {
840 + return true;
841 + } else {
842 + return false;
843 + }
844 + }
845 +
661 846 public function filter_lazyload_images( $in )
662 847 {
663 848 // only used is image optimization is NOT active but lazyload is.
664 849 $to_replace = array();
850 + $to_preload = '';
665 851
666 852 // hide (no)script tags to avoid nesting noscript tags (as lazyloaded images add noscript).
667 853 $out = autoptimizeBase::replace_contents_with_marker_if_exists(
668 854 'SCRIPT',
@@ -670,11 +856,20 @@
670 856 '#<(?:no)?script.*?<\/(?:no)?script>#is',
671 857 $in
672 858 );
673 859
674 - // extract img tags and add lazyload attribs.
860 + // get img preloads as set in post metabox.
861 + $metabox_preloads = array_filter( array_map( 'trim', explode( ',', wp_strip_all_tags( autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_preload' ) ) ) ) );
862 +
863 + // extract img tags and add lazyload attribs/ add preloads.
675 864 if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $out, $matches ) ) {
676 865 foreach ( $matches[0] as $tag ) {
866 + // check if image needs to be preloaded.
867 + if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && str_replace( $metabox_preloads, '', $tag ) !== $tag ) {
868 + $to_preload .= $this->create_img_preload_tag( $tag );
869 + }
870 +
871 + // and lazyloaded.
677 872 if ( $this->should_lazyload( $out ) ) {
678 873 $to_replace[ $tag ] = $this->add_lazyload( $tag );
679 874 }
680 875 }
@@ -683,8 +878,11 @@
683 878
684 879 // and also lazyload picture tag.
685 880 $out = $this->process_picture_tag( $out, false, true );
686 881
882 + // and inline style blocks with background-image.
883 + $out = $this->process_bgimage( $out );
884 +
687 885 // restore noscript tags.
688 886 $out = autoptimizeBase::restore_marked_content(
689 887 'SCRIPT',
690 888 $out
@@ -689,21 +887,42 @@
689 887 'SCRIPT',
690 888 $out
691 889 );
692 890
891 + if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
892 + // the preload was not in an img tag, so adding a non-responsive preload instead.
893 + foreach ( $metabox_preloads as $img_preload ) {
894 + $to_preload .= '<link rel="preload" href="' . $img_preload . '" as="image">';
895 + }
896 + }
897 +
898 + if ( ! empty( $to_preload ) ) {
899 + $out = autoptimizeExtra::inject_preloads( $to_preload, $out );
900 + }
901 +
693 902 return $out;
694 903 }
695 904
696 905 public function add_lazyload( $tag, $placeholder = '' ) {
697 906 // adds actual lazyload-attributes to an image node.
698 - if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag ) {
907 + $this->lazyload_counter++;
908 +
909 + $_lazyload_from_nth = '';
910 + if ( array_key_exists( 'autoptimize_imgopt_number_field_7', $this->options ) ) {
911 + $_lazyload_from_nth = $this->options['autoptimize_imgopt_number_field_7'];
912 + }
913 + $_lazyload_from_nth = apply_filters( 'autoptimize_filter_imgopt_lazyload_from_nth', $_lazyload_from_nth );
914 +
915 + if ( str_ireplace( $this->get_lazyload_exclusions(), '', $tag ) === $tag && $this->lazyload_counter >= $_lazyload_from_nth ) {
699 916 $tag = $this->maybe_fix_missing_quotes( $tag );
700 917
701 918 // store original tag for use in noscript version.
702 - $noscript_tag = '<noscript>' . $tag . '</noscript>';
919 + $noscript_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>';
703 920
921 + $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
922 +
704 923 // insert lazyload class.
705 - $tag = $this->inject_classes_in_tag( $tag, 'lazyload ' );
924 + $tag = $this->inject_classes_in_tag( $tag, "$lazyload_class " );
706 925
707 926 if ( ! $placeholder || empty( $placeholder ) ) {
708 927 // get image width & heigth for placeholder fun (and to prevent content reflow).
709 928 $_get_size = $this->get_size_from_tag( $tag );
@@ -708,13 +927,13 @@
708 927 // get image width & heigth for placeholder fun (and to prevent content reflow).
709 928 $_get_size = $this->get_size_from_tag( $tag );
710 929 $width = $_get_size['width'];
711 930 $height = $_get_size['height'];
712 - if ( false === $width ) {
713 - $widht = 210; // default width for SVG placeholder.
931 + if ( false === $width || empty( $width ) ) {
932 + $width = 210; // default width for SVG placeholder.
714 933 }
715 - if ( false === $height ) {
716 - $heigth = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
934 + if ( false === $height || empty( $height ) ) {
935 + $height = $width / 3 * 2; // if no height, base it on width using the 3/2 aspect ratio.
717 936 }
718 937
719 938 // insert the actual lazyload stuff.
720 939 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
@@ -719,19 +938,22 @@
719 938 // insert the actual lazyload stuff.
720 939 // see https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/ for great read on why we're using empty svg's.
721 940 $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( $width, $height ) );
722 941 }
723 - $tag = str_replace( ' src=', ' src=\'' . $placeholder . '\' data-src=', $tag );
724 - $tag = str_replace( ' srcset=', ' data-srcset=', $tag );
725 942
943 + $tag = preg_replace( '/(\s)src=/', ' src=\'' . $placeholder . '\' data-src=', $tag );
944 + $tag = preg_replace( '/(\s)srcset=/', ' data-srcset=', $tag );
945 +
726 946 // move sizes to data-sizes unless filter says no.
727 947 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_move_sizes', true ) ) {
728 - $tag = str_replace( 'sizes=', 'data-sizes=', $tag );
948 + $tag = str_replace( ' sizes=', ' data-sizes=', $tag );
729 949 }
730 950
731 951 // add the noscript-tag from earlier.
732 952 $tag = $noscript_tag . $tag;
733 953 $tag = apply_filters( 'autoptimize_filter_imgopt_lazyloaded_img', $tag );
954 + } else {
955 + $tag = apply_filters( 'autoptimize_filter_imgopt_not_lazyloaded_img', $tag );
734 956 }
735 957
736 958 return $tag;
737 959 }
@@ -736,8 +958,12 @@
736 958 return $tag;
737 959 }
738 960
739 961 public function add_lazyload_js_footer() {
962 + if ( false === autoptimizeMain::should_buffer() || autoptimizeMain::is_amp_markup( '' ) ) {
963 + return;
964 + }
965 +
740 966 // The JS will by default be excluded form autoptimization but this can be changed with a filter.
741 967 $noptimize_flag = '';
742 968 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
743 969 $noptimize_flag = ' data-noptimize="1"';
@@ -742,21 +968,74 @@
742 968 if ( apply_filters( 'autoptimize_filter_imgopt_lazyload_js_noptimize', true ) ) {
743 969 $noptimize_flag = ' data-noptimize="1"';
744 970 }
745 971
972 + $_extra = autoptimizeOptionWrapper::get_option( 'autoptimize_extra_settings', '' );
973 + if ( is_array( $_extra ) && array_key_exists( 'autoptimize_extra_checkbox_field_0', $_extra ) && ! empty( $_extra['autoptimize_extra_checkbox_field_0'] ) ) {
974 + // if "remove query strings" is active in "extra", then let's be consistant and not add one ourselves :-) ?
975 + $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js', __FILE__ );
976 + } else {
977 + $lazysizes_js = plugins_url( 'external/js/lazysizes.min.js?ao_version=' . AUTOPTIMIZE_PLUGIN_VERSION, __FILE__ );
978 + }
979 +
980 + $cdn_url = $this->get_cdn_url();
981 + if ( ! empty( $cdn_url ) ) {
982 + $cdn_url = rtrim( $cdn_url, '/' );
983 + $lazysizes_js = str_replace( AUTOPTIMIZE_WP_SITE_URL, $cdn_url, $lazysizes_js );
984 + }
985 +
986 + $type_js = '';
987 + if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
988 + $type_js = ' type="text/javascript"';
989 + }
990 +
746 991 // Adds lazyload CSS & JS to footer, using echo because wp_enqueue_script seems not to support pushing attributes (async).
747 - echo apply_filters( 'autoptimize_filter_imgopt_lazyload_cssoutput', '<style>.lazyload,.lazyloading{opacity:0;}.lazyloaded{opacity:1;transition:opacity 300ms;}</style><noscript><style>.lazyload{display:none;}</style></noscript>' );
748 - echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
749 - echo '<script async' . $noptimize_flag . ' src=\'' . plugins_url( 'external/js/lazysizes.min.js', __FILE__ ) . '\'></script>';
992 + echo apply_filters( 'autoptimize_filter_imgopt_lazyload_cssoutput', '<noscript><style>.lazyload{display:none;}</style></noscript>' );
993 + echo apply_filters( 'autoptimize_filter_imgopt_lazyload_jsconfig', '<script' . $type_js . $noptimize_flag . '>window.lazySizesConfig=window.lazySizesConfig||{};window.lazySizesConfig.loadMode=1;</script>' );
994 + echo apply_filters( 'autoptimize_filter_imgopt_lazyload_js', '<script async' . $type_js . $noptimize_flag . ' src=\'' . $lazysizes_js . '\'></script>' );
995 + }
750 996
751 - // And add webp detection and loading JS.
752 - if ( $this->should_webp() ) {
753 - $_webp_detect = "function c_webp(A){var n=new Image;n.onload=function(){var e=0<n.width&&0<n.height;A(e)},n.onerror=function(){A(!1)},n.src='data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA=='}function s_webp(e){window.supportsWebP=e}c_webp(s_webp);";
754 - $_webp_load = "document.addEventListener('lazybeforeunveil',function({target:c}){supportsWebP&&['data-src','data-srcset'].forEach(function(a){attr=c.getAttribute(a),null!==attr&&c.setAttribute(a,attr.replace(/\/client\//,'/client/to_webp,'))})});";
755 - echo apply_filters( 'autoptimize_filter_imgopt_webp_js', '<script' . $noptimize_flag . '>' . $_webp_detect . $_webp_load . '</script>' );
997 + public static function create_img_preload_tag( $tag ) {
998 + if ( false === apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {
999 + return '';
756 1000 }
1001 +
1002 + // clean up; remove tabs/ linebreaks/ spaces.
1003 + $tag = preg_replace( '/\s+/', ' ', $tag );
1004 +
1005 + // remove noscript.
1006 + if ( false !== strpos( $tag, '<noscript' ) ) {
1007 + $tag = preg_replace( '/<noscript.*<\/noscript>/mU', '', $tag );
1008 + }
1009 +
1010 + // rewrite img tag to link preload img.
1011 + $_from = array( '<img ', ' src=', ' sizes=', ' srcset=' );
1012 + $_to = array( '<link rel="preload" as="image" ', ' href=', ' imagesizes=', ' imagesrcset=' );
1013 + $tag = str_replace( $_from, $_to, $tag );
1014 +
1015 + // and remove title, alt, class and id.
1016 + $tag = preg_replace( '/ ((?:title|alt|class|id|loading|fetchpriority|decoding|data-no-lazy)=".*")/Um', '', $tag );
1017 + if ( str_replace( array( ' title=', ' class=', ' alt=', ' id=', ' fetchpriority=', ' decoding=', ' data-no-lazy=' ), '', $tag ) !== $tag ) {
1018 + // 2nd regex pass if still title/ class/ alt in case single quotes were used iso doubles.
1019 + $tag = preg_replace( '/ ((?:title|alt|class|id|loading|fetchpriority|decoding|data-no-lazy)=\'.*\')/Um', '', $tag );
1020 + }
1021 +
1022 + return $tag;
757 1023 }
758 1024
1025 + public static function get_cdn_url() {
1026 + // getting CDN url here to avoid having to make bigger changes to autoptimizeBase.
1027 + static $cdn_url = null;
1028 +
1029 + if ( null === $cdn_url ) {
1030 + $cdn_url = autoptimizeOptionWrapper::get_option( 'autoptimize_cdn_url', '' );
1031 + $cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $cdn_url );
1032 + $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
1033 + }
1034 +
1035 + return $cdn_url;
1036 + }
1037 +
759 1038 public function get_lazyload_exclusions() {
760 1039 // returns array of strings that if found in an <img tag will stop the img from being lazy-loaded.
761 1040 static $exclude_lazyload_array = null;
762 1041
@@ -763,9 +1042,9 @@
763 1042 if ( null === $exclude_lazyload_array ) {
764 1043 $options = $this->options;
765 1044
766 1045 // set default exclusions.
767 - $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg' );
1046 + $exclude_lazyload_array = array( 'skip-lazy', 'data-no-lazy', 'notlazy', 'data-src', 'data-srcset', 'data:image/', 'data-lazyload', 'rev-slidebg', 'loading="eager"' );
768 1047
769 1048 // add from setting.
770 1049 if ( array_key_exists( 'autoptimize_imgopt_text_field_5', $options ) ) {
771 1050 $exclude_lazyload_option = $options['autoptimize_imgopt_text_field_5'];
@@ -784,9 +1063,9 @@
784 1063 public function inject_classes_in_tag( $tag, $target_class ) {
785 1064 if ( strpos( $tag, 'class=' ) !== false ) {
786 1065 $tag = preg_replace( '/(\sclass\s?=\s?("|\'))/', '$1' . $target_class, $tag );
787 1066 } else {
788 - $tag = str_replace( '<img ', '<img class="' . trim( $target_class ) . '" ', $tag );
1067 + $tag = preg_replace( '/(<[a-zA-Z]*)\s/', '$1 class="' . trim( $target_class ) . '" ', $tag );
789 1068 }
790 1069
791 1070 return $tag;
792 1071 }
@@ -794,21 +1073,21 @@
794 1073 public function get_default_lazyload_placeholder( $imgopt_w, $imgopt_h ) {
795 1074 return 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20' . $imgopt_w . '%20' . $imgopt_h . '%22%3E%3C/svg%3E';
796 1075 }
797 1076
798 - public function should_webp() {
799 - static $webp_return = null;
1077 + public function should_ngimg() {
1078 + static $ngimg_return = null;
800 1079
801 - if ( is_null( $webp_return ) ) {
802 - // webp only works if imgopt and lazyload are also active.
803 - if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && ! empty( $this->options['autoptimize_imgopt_checkbox_field_3'] ) && $this->imgopt_active() ) {
804 - $webp_return = true;
1080 + if ( is_null( $ngimg_return ) ) {
1081 + // nextgen img only works if imgopt is active.
1082 + if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_4'] ) && $this->imgopt_active() ) {
1083 + $ngimg_return = true;
805 1084 } else {
806 - $webp_return = false;
1085 + $ngimg_return = false;
807 1086 }
808 1087 }
809 1088
810 - return $webp_return;
1089 + return $ngimg_return;
811 1090 }
812 1091
813 1092 public function process_picture_tag( $in, $imgopt = false, $lazy = false ) {
814 1093 // check if "<picture" is present and if filter allows us to process <picture>.
@@ -827,13 +1106,13 @@
827 1106 foreach ( $_sources as $_source ) {
828 1107 $_picture_replacement = $_source[0];
829 1108
830 1109 // should we optimize the image?
831 - if ( $imgopt && $this->can_optimize_image( $_source[1] ) ) {
1110 + if ( $imgopt && $this->can_optimize_image( $_source[1], $_picture[0] ) ) {
832 1111 $_picture_replacement = str_replace( $_source[1], $this->build_imgopt_url( $_source[1] ), $_picture_replacement );
833 1112 }
834 1113 // should we lazy-load?
835 - if ( $lazy && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
1114 + if ( $lazy && $this->should_lazyload() && str_ireplace( $_exclusions, '', $_picture_replacement ) === $_picture_replacement ) {
836 1115 $_picture_replacement = str_replace( ' srcset=', ' data-srcset=', $_picture_replacement );
837 1116 }
838 1117 $to_replace_pict[ $_source[0] ] = $_picture_replacement;
839 1118 }
@@ -845,12 +1124,51 @@
845 1124
846 1125 return $out;
847 1126 }
848 1127
1128 + public function process_bgimage( $in ) {
1129 + if ( strpos( $in, 'background-image:' ) !== false && apply_filters( 'autoptimize_filter_imgopt_lazyload_backgroundimages', true ) ) {
1130 + $out = preg_replace_callback(
1131 + '/(<(?:article|aside|body|div|footer|header|p|section|span|table)[^>]*)\sstyle=(?:"|\')[^<>]*?background-image:\s?url\((?:"|\')?([^"\')]*)(?:"|\')?\)[^>]*/',
1132 + array( $this, 'lazyload_bgimg_callback' ),
1133 + $in
1134 + );
1135 + return $out;
1136 + }
1137 + return $in;
1138 + }
1139 +
1140 + public function lazyload_bgimg_callback( $matches ) {
1141 + if ( str_ireplace( $this->get_lazyload_exclusions(), '', $matches[0] ) === $matches[0] ) {
1142 + // get placeholder & lazyload class strings.
1143 + $placeholder = apply_filters( 'autoptimize_filter_imgopt_lazyload_placeholder', $this->get_default_lazyload_placeholder( 500, 300 ) );
1144 + $lazyload_class = apply_filters( 'autoptimize_filter_imgopt_lazyload_class', 'lazyload' );
1145 + // remove quotes from url() to be able to replace in next step.
1146 + $out = str_replace( array( "url('" . $matches[2] . "')", 'url("' . $matches[2] . '")' ), 'url(' . $matches[2] . ')', $matches[0] );
1147 + // replace background-image URL with SVG placeholder.
1148 + $out = str_replace( 'url(' . $matches[2], 'url(' . $placeholder, $out );
1149 + // sanitize bgimg src for quote sillyness.
1150 + $bgimg_src = $this->fix_silly_bgimg_quotes( $matches[2] );
1151 + // add data-bg attribute with real background-image URL for lazyload to pick up.
1152 + $out = str_replace( $matches[1], $matches[1] . ' data-bg="' . $bgimg_src . '"', $out );
1153 + // and finally add lazyload class to tag.
1154 + $out = $this->inject_classes_in_tag( $out, "$lazyload_class " );
1155 + return $out;
1156 + }
1157 + return $matches[0];
1158 + }
1159 +
1160 + public function fix_silly_bgimg_quotes( $tag_in ) {
1161 + // some themes/ pagebuilders wrap backgroundimages in HTML-encoded quotes (or linebreaks) which breaks imgopt/ lazyloading, this removes them.
1162 + return trim( str_replace( array( "\r\n", '&quot;', '&#034;', '&apos;', '&#039;' ), '', $tag_in ) );
1163 + }
1164 +
849 1165 public function maybe_fix_missing_quotes( $tag_in ) {
850 - // W3TC's Minify_HTML class removes quotes around attribute value, this re-adds them.
1166 + // 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.
851 1167 if ( file_exists( WP_PLUGIN_DIR . '/w3-total-cache/w3-total-cache.php' ) && class_exists( 'Minify_HTML' ) && apply_filters( 'autoptimize_filter_imgopt_fixquotes', true ) ) {
852 - return preg_replace( '/=([^("|\')]*)(\s|>)/U', '=\'$1\'$2', $tag_in );
1168 + $tag_out = preg_replace( '/class\s?=([^("|\')]*)(\s|>)/U', 'class=\'$1\'$2', $tag_in );
1169 + $tag_out = preg_replace( '/\s(width|height)=(?:"|\')?([^\s"\'>]*)(?:"|\')?/', ' $1=\'$2\'', $tag_out );
1170 + return $tag_out;
853 1171 } else {
854 1172 return $tag_in;
855 1173 }
856 1174 }
@@ -859,22 +1177,27 @@
859 1177 * Admin page logic and related functions below.
860 1178 */
861 1179 public function imgopt_admin_menu()
862 1180 {
863 - add_submenu_page(
864 - null,
865 - 'autoptimize_imgopt',
866 - 'autoptimize_imgopt',
867 - 'manage_options',
868 - 'autoptimize_imgopt',
869 - array( $this, 'imgopt_options_page' )
870 - );
1181 + // no acces if multisite and not network admin and no site config allowed.
1182 + if ( autoptimizeConfig::should_show_menu_tabs() ) {
1183 + add_submenu_page(
1184 + '',
1185 + 'autoptimize_imgopt',
1186 + 'autoptimize_imgopt',
1187 + 'manage_options',
1188 + 'autoptimize_imgopt',
1189 + array( $this, 'imgopt_options_page' )
1190 + );
1191 + }
871 1192 register_setting( 'autoptimize_imgopt_settings', 'autoptimize_imgopt_settings' );
872 1193 }
873 1194
874 1195 public function add_imgopt_tab( $in )
875 1196 {
876 - $in = array_merge( $in, array( 'autoptimize_imgopt' => __( 'Images', 'autoptimize' ) ) );
1197 + if ( autoptimizeConfig::should_show_menu_tabs() ) {
1198 + $in = array_merge( $in, array( 'autoptimize_imgopt' => apply_filters( 'autoptimize_filter_imgopt_tab_text', __( 'Images', 'autoptimize' ) ) ) );
1199 + }
877 1200
878 1201 return $in;
879 1202 }
880 1203
@@ -879,11 +1202,14 @@
879 1202 }
880 1203
881 1204 public function imgopt_options_page()
882 1205 {
1206 + // phpcs:disable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace
1207 + // phpcs:disable Generic.Formatting.DisallowMultipleStatements.SameLine
1208 +
883 1209 // Check querystring for "refreshCacheChecker" and call cachechecker if so.
884 1210 if ( array_key_exists( 'refreshImgProvStats', $_GET ) && 1 == $_GET['refreshImgProvStats'] ) {
885 - $this->query_img_provider_stats();
1211 + $this->query_img_provider_stats( true );
886 1212 }
887 1213
888 1214 $options = $this->fetch_options();
889 1215 $sp_url_suffix = $this->get_service_url_suffix();
@@ -892,11 +1218,19 @@
892 1218 #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
893 1219 #ao_settings_form .form-table th {font-weight: normal;}
894 1220 #autoptimize_imgopt_descr{font-size: 120%;}
895 1221 </style>
1222 + <script>document.title = "Autoptimize: <?php _e( 'Images', 'autoptimize' ); ?> " + document.title;</script>
896 1223 <div class="wrap">
897 - <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
1224 + <h1><?php apply_filters( 'autoptimize_filter_settings_is_pro', false ) ? _e( 'Autoptimize Pro Settings', 'autoptimize' ) : _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
898 1225 <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
1226 + <?php if ( autoptimizeUtils::is_local_server() ) { ?>
1227 + <div class="notice-warning notice"><p>
1228 + <?php
1229 + echo __( 'The image optimization service does not work on locally hosted sites or when the server is on a private network.', 'autoptimize' );
1230 + ?>
1231 + </p></div>
1232 + <?php } ?>
899 1233 <?php if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) { ?>
900 1234 <div class="notice-warning notice"><p>
901 1235 <?php
902 1236 // translators: "Autoptimize support forum" will appear in a "a href".
@@ -918,20 +1252,20 @@
918 1252 echo sprintf( __( 'Please %1$sdisable Jetpack\'s site accelerator for images%2$s to be able to use Autoptomize\'s advanced image optimization features below.', 'autoptimize' ), '<a href="admin.php?page=jetpack#/settings">', '</a>' );
919 1253 ?>
920 1254 </p></div>
921 1255 <?php } ?>
922 - <form id='ao_settings_form' action='options.php' method='post'>
1256 + <form id='ao_settings_form' action='<?php echo admin_url( 'options.php' ); ?>' method='post'>
923 1257 <?php settings_fields( 'autoptimize_imgopt_settings' ); ?>
924 1258 <h2><?php _e( 'Image optimization', 'autoptimize' ); ?></h2>
925 - <span id='autoptimize_imgopt_descr'><?php _e( 'Make your site significantly faster by just ticking a couple of checkboxes to optimize and lazy load your images, WebP support included!', 'autoptimize' ); ?></span>
1259 + <span id='autoptimize_imgopt_descr'><?php echo apply_filters( 'autoptimize_filter_imgopt_intro_copy', __( 'Make your site significantly faster by simply ticking a few boxes and start serving CDN powered, optimized images in next-get formats like WebP and AVIF! No additional plugins or services needed.', 'autoptimize' ) ); ?></span>
926 1260 <table class="form-table">
927 1261 <tr>
928 - <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
1262 + <th scope="row"><?php _e( 'Image optimization & CDN', 'autoptimize' ); ?></th>
929 1263 <td>
930 - <label><input id='autoptimize_imgopt_checkbox' type='checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_1]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_1'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Optimize images on the fly and serve them from Shortpixel\'s global CDN.', 'autoptimize' ); ?></label>
1264 + <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', __( 'On-the-fly image optimization and fast delivery via the Shortpixel global CDN.', 'autoptimize' ) ); ?></label>
931 1265 <?php
932 1266 // show shortpixel status.
933 - $_notice = autoptimizeImages::instance()->get_status_notice();
1267 + $_notice = autoptimizeImages::instance()->get_imgopt_status_notice();
934 1268 if ( $_notice ) {
935 1269 switch ( $_notice['status'] ) {
936 1270 case 2:
937 1271 $_notice_color = 'green';
@@ -939,11 +1273,10 @@
939 1273 case 1:
940 1274 $_notice_color = 'orange';
941 1275 break;
942 1276 case -1:
943 - $_notice_color = 'red';
944 - break;
945 1277 case -2:
1278 + case -3:
946 1279 $_notice_color = 'red';
947 1280 break;
948 1281 default:
949 1282 $_notice_color = 'green';
@@ -950,25 +1283,32 @@
950 1283 }
951 1284 echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
952 1285 } else {
953 1286 // translators: link points to shortpixel.
954 - $upsell_msg_1 = '<p>' . sprintf( __( 'Get more Google love and improve your website\'s loading speed by having the images optimized on the fly (also in the "next-gen" WebP image format) by %1$sShortPixel%2$s and then cached and served fast from Shortpixel\'s global CDN.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>' );
1287 + $upsell_msg_1 = '<p>' . sprintf( __( 'Get more Google love by speeding up your website. Start serving on-the-fly optimized images (also in the "next-gen" <strong>WebP</strong> and <strong>AVIF</strong> image formats) 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://autoptimize.shortpixel.com/" target="_blank">', '</a>', '<a href="https://help.shortpixel.com/article/62-where-does-the-cdn-has-pops" target="_blank">' );
955 1288 if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
956 1289 $upsell_msg_2 = __( 'For a limited time only, this service is offered free for all Autoptimize users, <b>don\'t miss the chance to test it</b> and see how much it could improve your site\'s speed.', 'autoptimize' );
957 1290 } else {
958 - // translators: link points to shortpixel.
959 - $upsell_msg_2 = sprintf( __( '%1$sSign-up now%2$s to receive a 1 000 bonus + 50&#37; more image optimization credits regardless of the traffic used. More image optimizations can be purchased starting with $4.99.', 'autoptimize' ), '<a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">', '</a>' );
1291 + // translators: 1st link points to autoptimize.com.pro, 2nd to shortpixel.
1292 + $upsell_msg_2 = sprintf( __( 'For <strong>unlimited image optimizations %1$sbuy Autoptimize Pro%2$s</strong> 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://autoptimize.shortpixel.com/' . $sp_url_suffix . '" target="_blank">', '</a>' );
960 1293 }
961 - echo apply_filters( 'autoptimize_imgopt_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
1294 + echo apply_filters( 'autoptimize_filter_imgopt_settings_copy', $upsell_msg_1 . ' ' . $upsell_msg_2 . '</p>' );
962 1295 }
963 1296 // translators: link points to shortpixel FAQ.
964 - $faqcopy = sprintf( __( '<strong>Questions</strong>? Have a look at the %1$sShortPixel FAQ%2$s!', 'autoptimize' ), '<strong><a href="https://shortpixel.helpscoutdocs.com/category/60-shortpixel-ai-cdn" target="_blank">', '</strong></a>' );
1297 + $faqcopy = sprintf( __( '<strong>Questions</strong>? 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>' );
1298 + $faqcopy = $faqcopy . ' ' . __( 'Only works for websites and images that are publicly available.', 'autoptimize' );
965 1299 // translators: links points to shortpixel TOS & Privacy Policy.
966 - $toscopy = sprintf( __( 'Usage of this feature is subject to Shortpixel\'s %1$sTerms of Use%2$s and %3$sPrivacy policy%4$s.', 'autoptimize' ), '<a href="https://shortpixel.com/tos' . $sp_url_suffix . '" target="_blank">', '</a>', '<a href="https://shortpixel.com/pp' . $sp_url_suffix . '" target="_blank">', '</a>' );
967 - echo apply_filters( 'autoptimize_imgopt_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
1300 + $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/privacy' . $sp_url_suffix . '" target="_blank">', '</a>' );
1301 + echo apply_filters( 'autoptimize_filter_imgopt_settings_tos', '<p>' . $faqcopy . ' ' . $toscopy . '</p>' );
968 1302 ?>
969 1303 </td>
970 1304 </tr>
1305 + <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"'; } ?>>
1306 + <th scope="row"><?php _e( 'Optimization exclusions', 'autoptimize' ); ?></th>
1307 + <td>
1308 + <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 _e( 'Comma-separated list of image classes or filenames that should not be optimized.', 'autoptimize' ); ?></label>
1309 + </td>
1310 + </tr>
971 1311 <tr id='autoptimize_imgopt_quality' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
972 1312 <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
973 1313 <td>
974 1314 <label>
@@ -990,19 +1330,29 @@
990 1330 </label>
991 1331 <p>
992 1332 <?php
993 1333 // translators: link points to shortpixel image test page.
994 - echo apply_filters( 'autoptimize_imgopt_imgopt_quality_copy', sprintf( __( 'You can %1$stest compression levels here%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/oic' . $sp_url_suffix . '" target="_blank">', '</a>' ) );
1334 + echo apply_filters( 'autoptimize_filter_imgopt_quality_copy', sprintf( __( 'You can %1$stest compression levels here%2$s.', 'autoptimize' ), '<a href="https://shortpixel.com/online-image-compression' . $sp_url_suffix . '" target="_blank">', '</a>' ) );
995 1335 ?>
996 1336 </p>
997 1337 </td>
998 1338 </tr>
999 - <tr id='autoptimize_imgopt_webp' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_1', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_1'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_1'] ) ) { echo 'class="hidden"'; } ?>>
1000 - <th scope="row"><?php _e( 'Load WebP in supported browsers?', 'autoptimize' ); ?></th>
1001 - <td>
1002 - <label><input type='checkbox' id='autoptimize_imgopt_webp_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_4]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_4'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_3'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Automatically serve "next-gen" WebP image format to any browser that supports it (requires lazy load to be active).', 'autoptimize' ); ?></label>
1003 - </td>
1004 - </tr>
1339 + <?php
1340 + if ( apply_filters( 'autoptimize_filter_imgopt_settings_show_avif', true ) ) {
1341 + ?>
1342 + <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"'; } ?>>
1343 + <th scope="row"><?php _e( 'Load AVIF in supported browsers?', 'autoptimize' ); ?></th>
1344 + <td>
1345 + <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'><?php _e( 'Automatically serve AVIF image format to any browser that supports it.', 'autoptimize' ); ?></label>
1346 + </td>
1347 + </tr>
1348 + <?php
1349 + } else {
1350 + ?>
1351 + <input type='hidden' id='autoptimize_imgopt_ngimg_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_4]' value='0'>
1352 + <?php
1353 + }
1354 + ?>
1005 1355 <tr>
1006 1356 <th scope="row"><?php _e( 'Lazy-load images?', 'autoptimize' ); ?></th>
1007 1357 <td>
1008 1358 <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>
@@ -1007,14 +1357,20 @@
1007 1357 <td>
1008 1358 <label><input type='checkbox' id='autoptimize_imgopt_lazyload_checkbox' name='autoptimize_imgopt_settings[autoptimize_imgopt_checkbox_field_3]' <?php if ( ! empty( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' === $options['autoptimize_imgopt_checkbox_field_3'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Image lazy-loading will delay the loading of non-visible images to allow the browser to optimally load all resources for the "above the fold"-page first.', 'autoptimize' ); ?></label>
1009 1359 </td>
1010 1360 </tr>
1011 - <tr id='autoptimize_imgopt_lazyload_exclusions' <?php if ( ! array_key_exists( 'autoptimize_imgopt_checkbox_field_3', $options ) || ( isset( $options['autoptimize_imgopt_checkbox_field_3'] ) && '1' !== $options['autoptimize_imgopt_checkbox_field_3'] ) ) { echo 'class="hidden"'; } ?>>
1361 + <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"'; } ?>>
1012 1362 <th scope="row"><?php _e( 'Lazy-load exclusions', 'autoptimize' ); ?></th>
1013 1363 <td>
1014 - <label><input type='text' style='width:80%' id='autoptimize_imgopt_lazyload_exclusions' name='autoptimize_imgopt_settings[autoptimize_imgopt_text_field_5]' value='<?php if ( ! empty( $options['autoptimize_imgopt_text_field_5'] ) ) { echo esc_attr( $options['autoptimize_imgopt_text_field_5'] ); } ?>'><br /><?php _e( 'Comma-separated list of to be excluded image classes or filenames.', 'autoptimize' ); ?></label>
1364 + <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 _e( 'Comma-separated list of to be excluded image classes or filenames.', 'autoptimize' ); ?></label>
1015 1365 </td>
1016 1366 </tr>
1367 + <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"'; } ?>>
1368 + <th scope="row"><?php _e( 'Lazy-load from nth image', 'autoptimize' ); ?></th>
1369 + <td>
1370 + <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 _e( 'Don\'t lazyload the first X images, \'1\' lazyloads all.', 'autoptimize' ); ?></label>
1371 + </td>
1372 + </tr>
1017 1373 </table>
1018 1374 <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
1019 1375 </form>
1020 1376 <script>
@@ -1021,26 +1377,21 @@
1021 1377 jQuery(document).ready(function() {
1022 1378 jQuery("#autoptimize_imgopt_checkbox").change(function() {
1023 1379 if (this.checked) {
1024 1380 jQuery("#autoptimize_imgopt_quality").show("slow");
1025 - jQuery("#autoptimize_imgopt_webp").show("slow");
1381 + jQuery("#autoptimize_imgopt_ngimg").show("slow");
1382 + jQuery("#autoptimize_imgopt_optimization_exclusions").show("slow");
1026 1383 } else {
1027 1384 jQuery("#autoptimize_imgopt_quality").hide("slow");
1028 - jQuery("#autoptimize_imgopt_webp").hide("slow");
1385 + jQuery("#autoptimize_imgopt_ngimg").hide("slow");
1386 + jQuery("#autoptimize_imgopt_optimization_exclusions").hide("slow");
1029 1387 }
1030 1388 });
1031 - jQuery("#autoptimize_imgopt_webp_checkbox").change(function() {
1032 - if (this.checked) {
1033 - jQuery("#autoptimize_imgopt_lazyload_checkbox")[0].checked = true;
1034 - jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1035 - }
1036 - });
1037 1389 jQuery("#autoptimize_imgopt_lazyload_checkbox").change(function() {
1038 1390 if (this.checked) {
1039 - jQuery("#autoptimize_imgopt_lazyload_exclusions").show("slow");
1391 + jQuery(".autoptimize_lazyload_child").show("slow");
1040 1392 } else {
1041 - jQuery("#autoptimize_imgopt_lazyload_exclusions").hide("slow");
1042 - jQuery("#autoptimize_imgopt_webp_checkbox")[0].checked = false;
1393 + jQuery(".autoptimize_lazyload_child").hide("slow");
1043 1394 }
1044 1395 });
1045 1396 });
1046 1397 </script>
@@ -1050,38 +1401,54 @@
1050 1401 /**
1051 1402 * Ïmg opt status as used on dashboard.
1052 1403 */
1053 1404 public function get_imgopt_status_notice() {
1054 - if ( $this->imgopt_active() ) {
1055 - $_imgopt_notice = '';
1056 - $_stat = get_option( 'autoptimize_imgopt_provider_stat', '' );
1057 - $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
1058 - $_imgopt_upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
1405 + if ( $this->imgopt_active() && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1406 + $_imgopt_notice = '';
1407 + $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1408 + $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
1409 + $_imgopt_upsell = 'https://autoptimize.shortpixel.com/' . $_site_host;
1410 + $_imgopt_assoc = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1411 + $_imgopt_unreach = 'https://shortpixel.helpscoutdocs.com/article/148-why-are-my-images-redirected-from-cdn-shortpixel-ai';
1059 1412
1060 1413 if ( is_array( $_stat ) ) {
1061 1414 if ( 1 == $_stat['Status'] ) {
1062 1415 // translators: "add more credits" will appear in a "a href".
1063 - $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota is almost used, make sure you %1$sadd more credits%2$s to avoid slowing down your website.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
1416 + $_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 <strong>or consider using %3$sAutoptimize Pro%2$s which comes with (nearly) unlimited image optimization</strong> 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">' );
1064 1417 } elseif ( -1 == $_stat['Status'] || -2 == $_stat['Status'] ) {
1065 1418 // translators: "add more credits" will appear in a "a href".
1066 - $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota was used, %1$sadd more credits%2$s to keep fast serving optimized images on your site', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
1067 - $_imgopt_stats_refresh_url = add_query_arg( array(
1068 - 'page' => 'autoptimize_imgopt',
1069 - 'refreshImgProvStats' => '1',
1070 - ), admin_url( 'options-general.php' ) );
1419 + $_imgopt_notice = sprintf( __( '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 <strong>or consider using %3$sAutoptimize Pro%2$s which comes with (nearly) unlimited image optimization</strong> 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">' );
1420 + // translators: "associate your domain" will appear in a "a href".
1421 + $_imgopt_notice = $_imgopt_notice . ' ' . sprintf( __( '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>' );
1422 + } elseif ( -3 == $_stat['Status'] ) {
1423 + // translators: "check the documentation here" will appear in a "a href".
1424 + $_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>' );
1425 + } else {
1426 + $_imgopt_upsell = 'https://autoptimize.shortpixel.com/';
1427 + // translators: "log in to check your account" will appear in a "a href".
1428 + $_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>' );
1429 + }
1430 +
1431 + // add info on freshness + refresh link if status is not 2 (good shape).
1432 + if ( 2 != $_stat['Status'] ) {
1433 + $_imgopt_stats_refresh_url = add_query_arg(
1434 + array(
1435 + 'page' => 'autoptimize_imgopt',
1436 + 'refreshImgProvStats' => '1',
1437 + ),
1438 + admin_url( 'options-general.php' )
1439 + );
1071 1440 if ( $_stat && array_key_exists( 'timestamp', $_stat ) && ! empty( $_stat['timestamp'] ) ) {
1072 - $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( get_option( 'time_format' ), $_stat['timestamp'] );
1441 + $_imgopt_stats_last_run = __( 'based on status at ', 'autoptimize' ) . date_i18n( autoptimizeOptionWrapper::get_option( 'time_format' ), $_stat['timestamp'] );
1073 1442 } else {
1074 1443 $_imgopt_stats_last_run = __( 'based on previously fetched data', 'autoptimize' );
1075 1444 }
1076 1445 $_imgopt_notice .= ' (' . $_imgopt_stats_last_run . ', ';
1077 1446 // translators: "here to refresh" links to the Autoptimize Extra page and forces a refresh of the img opt stats.
1078 - $_imgopt_notice .= sprintf( __( 'click %1$shere to refresh%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1079 - } else {
1080 - $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1081 - // translators: "log in to check your account" will appear in a "a href".
1082 - $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
1447 + $_imgopt_notice .= sprintf( __( 'you can click %1$shere to refresh your quota status%2$s', 'autoptimize' ), '<a href="' . $_imgopt_stats_refresh_url . '">', '</a>).' );
1083 1448 }
1449 +
1450 + // and make the full notice filterable.
1084 1451 $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
1085 1452
1086 1453 return array(
1087 1454 'status' => $_stat['Status'],
@@ -1099,18 +1466,24 @@
1099 1466 }
1100 1467
1101 1468 /**
1102 1469 * Get img provider stats (used to display notice).
1470 + *
1471 + * @param bool $_refresh Should the stats be forcefully refreshed or not.
1103 1472 */
1104 - public function query_img_provider_stats() {
1105 - if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) ) {
1473 + public function query_img_provider_stats( $_refresh = false ) {
1474 + if ( ! empty( $this->options['autoptimize_imgopt_checkbox_field_1'] ) && apply_filters( 'autoptimize_filter_imgopt_status_shortpixel', true ) ) {
1106 1475 $url = '';
1107 - $endpoint = $this->get_imgopt_host() . 'read-domain/';
1476 + $stat_dom = 'https://no-cdn.shortpixel.ai/';
1477 + $endpoint = $stat_dom . 'read-domain/';
1108 1478 $domain = AUTOPTIMIZE_SITE_DOMAIN;
1109 1479
1110 1480 // make sure parse_url result makes sense, keeping $url empty if not.
1111 1481 if ( $domain && ! empty( $domain ) ) {
1112 1482 $url = $endpoint . $domain;
1483 + if ( true === $_refresh ) {
1484 + $url = $url . '/refresh';
1485 + }
1113 1486 }
1114 1487
1115 1488 $url = apply_filters(
1116 1489 'autoptimize_filter_imgopt_stat_url',
@@ -1123,9 +1496,9 @@
1123 1496 $response = wp_remote_get( $url );
1124 1497 if ( ! is_wp_error( $response ) ) {
1125 1498 if ( '200' == wp_remote_retrieve_response_code( $response ) ) {
1126 1499 $stats = json_decode( wp_remote_retrieve_body( $response ), true );
1127 - update_option( 'autoptimize_imgopt_provider_stat', $stats );
1500 + autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_provider_stat', $stats );
1128 1501 }
1129 1502 }
1130 1503 }
1131 1504 }
@@ -1147,16 +1520,20 @@
1147 1520 {
1148 1521 static $launch_status = null;
1149 1522
1150 1523 if ( null === $launch_status ) {
1151 - $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
1524 + $avail_imgopt = '';
1525 + if ( is_array( $this->options ) && array_key_exists( 'availabilities', $this->options ) && is_array( $this->options['availabilities'] ) && array_key_exists( 'extra_imgopt', $this->options['availabilities'] ) ) {
1526 + $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
1527 + }
1528 +
1152 1529 $magic_number = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
1153 - $has_launched = get_option( 'autoptimize_imgopt_launched', '' );
1530 + $has_launched = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_launched', '' );
1154 1531 $launch_status = false;
1155 1532 if ( $has_launched || ( is_array( $avail_imgopt ) && array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
1156 1533 $launch_status = true;
1157 1534 if ( ! $has_launched ) {
1158 - update_option( 'autoptimize_imgopt_launched', 'on' );
1535 + autoptimizeOptionWrapper::update_option( 'autoptimize_imgopt_launched', 'on' );
1159 1536 }
1160 1537 }
1161 1538 }
1162 1539
@@ -1172,9 +1549,9 @@
1172 1549 public function get_imgopt_provider_userstatus() {
1173 1550 static $_provider_userstatus = null;
1174 1551
1175 1552 if ( is_null( $_provider_userstatus ) ) {
1176 - $_stat = get_option( 'autoptimize_imgopt_provider_stat', '' );
1553 + $_stat = autoptimizeOptionWrapper::get_option( 'autoptimize_imgopt_provider_stat', '' );
1177 1554 if ( is_array( $_stat ) ) {
1178 1555 if ( array_key_exists( 'Status', $_stat ) ) {
1179 1556 $_provider_userstatus['Status'] = $_stat['Status'];
1180 1557 } else {
@@ -1186,42 +1563,14 @@
1186 1563 } else {
1187 1564 // if no timestamp then we return "".
1188 1565 $_provider_userstatus['timestamp'] = '';
1189 1566 }
1567 + } else {
1568 + // no provider_stat yet, assume/ return all OK.
1569 + $_provider_userstatus['Status'] = 2;
1570 + $_provider_userstatus['timestamp'] = '';
1190 1571 }
1191 1572 }
1192 1573
1193 1574 return $_provider_userstatus;
1194 - }
1195 -
1196 - public function get_status_notice() {
1197 - if ( $this->imgopt_active() ) {
1198 - $notice = '';
1199 - $stat = $this->get_imgopt_provider_userstatus();
1200 - $upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
1201 - $assoc = 'https://shortpixel.helpscoutdocs.com/article/94-how-to-associate-a-domain-to-my-account';
1202 -
1203 - if ( is_array( $stat ) ) {
1204 - if ( 1 == $stat['Status'] ) {
1205 - // translators: "add more credits" will appear in a "a href".
1206 - $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota is almost used, make sure you %1$sadd more credits%2$s to avoid slowing down your website.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1207 - } elseif ( -1 == $stat['Status'] || -2 == $stat['Status'] ) {
1208 - // translators: "add more credits" will appear in a "a href".
1209 - $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota was used, %1$sadd more credits%2$s to keep fast serving optimized images on your site.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1210 - // translators: "associate your domain" will appear in a "a href".
1211 - $notice = $notice . ' ' . sprintf( __( 'If you already have enough credits then you may need to %1$sassociate your domain%2$s to your Shortpixel account.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $assoc . '" target="_blank">', '</a>' );
1212 - } else {
1213 - $upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
1214 - // translators: "log in to check your account" will appear in a "a href".
1215 - $notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a rel="noopener noreferrer" href="' . $upsell . '" target="_blank">', '</a>' );
1216 - }
1217 - $notice = apply_filters( 'autoptimize_filter_imgopt_notice', $notice );
1218 -
1219 - return array(
1220 - 'status' => $stat['Status'],
1221 - 'notice' => $notice,
1222 - );
1223 - }
1224 - }
1225 - return false;
1226 1575 }
1227 1576 }