EWWW Image Optimizer - Version 4.9.3

Version Description

  • fixed: ExactDN incorrectly scales Elementor background images rather than cropping
  • fixed: ExactDN cannot work with Divi/Elementor background images due to use of external CSS files
  • fixed: JS WebP rewriting picture tags that already have WebP markup in Force WebP mode
  • fixed: JS WebP incorrectly parses GIF/SVG images in Force WebP mode
  • fixed: JS WebP does not support lazy load + infinite scroll
  • fixed: Lazy Load auto-scaling breaks if background image is enclosed in encoded quotes
  • fixed: GRAND FlaGallery integration broken by hook suffix change
Download this release

Release Info

Developer nosilver4u
Plugin Icon 128x128 EWWW Image Optimizer
Version 4.9.3
Comparing to
See all releases

Code changes from version 4.8.1 to 4.9.3

aux-optimize.php CHANGED
@@ -73,7 +73,7 @@ function ewww_image_optimizer_aux_images() {
73
  $help_instructions = esc_html__( 'Enable the Debugging option and refresh this page to include debugging information with your question.', 'ewww-image-optimizer' ) . ' ' .
74
  esc_html__( 'This will allow us to assist you more quickly.', 'ewww-image-optimizer' );
75
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
76
- global $ewww_debug;
77
  ewww_image_optimizer_options( 'debug-silent' );
78
  $output .= '<p style="clear:both"><b>' . esc_html__( 'Debugging Information', 'ewww-image-optimizer' ) . ':</b> <button id="ewww-copy-debug" class="button button-secondary" type="button">' . esc_html__( 'Copy', 'ewww-image-optimizer' ) . '</button>';
79
  if ( is_file( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'debug.log' ) ) {
@@ -81,7 +81,7 @@ function ewww_image_optimizer_aux_images() {
81
  $output .= "&emsp;<a href='$debug_log_url'>" . esc_html( 'View Debug Log', 'ewww-image-optimizer' ) . "</a> - <a href='admin.php?action=ewww_image_optimizer_delete_debug_log'>" . esc_html( 'Remove Debug Log', 'ewww-image-optimizer' ) . '</a>';
82
  }
83
  $output .= '</p>';
84
- $output .= '<div id="ewww-debug-info" style="border:1px solid #e5e5e5;background:#fff;overflow:auto;height:300px;width:800px;" contenteditable="true">' . $ewww_debug . '</div>';
85
  $help_instructions = esc_html__( 'Debugging information will be included with your message automatically.', 'ewww-image-optimizer' ) . ' ' .
86
  esc_html__( 'This will allow us to assist you more quickly.', 'ewww-image-optimizer' );
87
  }
@@ -104,11 +104,11 @@ function ewww_image_optimizer_aux_images() {
104
  'email' => utf8_encode( $help_email ),
105
  );
106
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
107
- $ewww_debug_array = explode( '<br>', $ewww_debug );
108
- $ewww_debug_i = 0;
109
- foreach ( $ewww_debug_array as $ewww_debug_line ) {
110
- $hs_identify[ 'debug_info_' . $ewww_debug_i ] = $ewww_debug_line;
111
- $ewww_debug_i++;
112
  }
113
  }
114
  ?>
@@ -123,7 +123,7 @@ function ewww_image_optimizer_aux_images() {
123
  </script>
124
  <?php
125
  }
126
- $ewww_debug = '';
127
  ewwwio_memory( __FUNCTION__ );
128
  }
129
 
73
  $help_instructions = esc_html__( 'Enable the Debugging option and refresh this page to include debugging information with your question.', 'ewww-image-optimizer' ) . ' ' .
74
  esc_html__( 'This will allow us to assist you more quickly.', 'ewww-image-optimizer' );
75
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
76
+ global $eio_debug;
77
  ewww_image_optimizer_options( 'debug-silent' );
78
  $output .= '<p style="clear:both"><b>' . esc_html__( 'Debugging Information', 'ewww-image-optimizer' ) . ':</b> <button id="ewww-copy-debug" class="button button-secondary" type="button">' . esc_html__( 'Copy', 'ewww-image-optimizer' ) . '</button>';
79
  if ( is_file( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'debug.log' ) ) {
81
  $output .= "&emsp;<a href='$debug_log_url'>" . esc_html( 'View Debug Log', 'ewww-image-optimizer' ) . "</a> - <a href='admin.php?action=ewww_image_optimizer_delete_debug_log'>" . esc_html( 'Remove Debug Log', 'ewww-image-optimizer' ) . '</a>';
82
  }
83
  $output .= '</p>';
84
+ $output .= '<div id="ewww-debug-info" style="border:1px solid #e5e5e5;background:#fff;overflow:auto;height:300px;width:800px;" contenteditable="true">' . $eio_debug . '</div>';
85
  $help_instructions = esc_html__( 'Debugging information will be included with your message automatically.', 'ewww-image-optimizer' ) . ' ' .
86
  esc_html__( 'This will allow us to assist you more quickly.', 'ewww-image-optimizer' );
87
  }
104
  'email' => utf8_encode( $help_email ),
105
  );
106
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
107
+ $eio_debug_array = explode( '<br>', $eio_debug );
108
+ $eio_debug_i = 0;
109
+ foreach ( $eio_debug_array as $eio_debug_line ) {
110
+ $hs_identify[ 'debug_info_' . $eio_debug_i ] = $eio_debug_line;
111
+ $eio_debug_i++;
112
  }
113
  }
114
  ?>
123
  </script>
124
  <?php
125
  }
126
+ $eio_debug = '';
127
  ewwwio_memory( __FUNCTION__ );
128
  }
129
 
bulk.php CHANGED
@@ -1609,10 +1609,10 @@ function ewww_image_optimizer_bulk_loop( $hook = '', $delay = 0 ) {
1609
  // Store the updated list of attachment IDs back in the 'bulk_attachments' option.
1610
  // update_option( 'ewww_image_optimizer_bulk_attachments', $attachments, false );.
1611
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
1612
- global $ewww_debug;
1613
  $debug_button = esc_html__( 'Show Debug Output', 'ewww-image-optimizer' );
1614
  $debug_id = uniqid();
1615
- $output['results'] .= "<button type='button' class='ewww-show-debug-meta button button-secondary' data-id='$debug_id'>$debug_button</button><div class='ewww-debug-meta-$debug_id' style='background-color:#f1f1f1;display:none;'>$ewww_debug</div>";
1616
  }
1617
  if ( ! empty( $next_image->file ) ) {
1618
  $next_file = esc_html( $next_image->file );
1609
  // Store the updated list of attachment IDs back in the 'bulk_attachments' option.
1610
  // update_option( 'ewww_image_optimizer_bulk_attachments', $attachments, false );.
1611
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
1612
+ global $eio_debug;
1613
  $debug_button = esc_html__( 'Show Debug Output', 'ewww-image-optimizer' );
1614
  $debug_id = uniqid();
1615
+ $output['results'] .= "<button type='button' class='ewww-show-debug-meta button button-secondary' data-id='$debug_id'>$debug_button</button><div class='ewww-debug-meta-$debug_id' style='background-color:#f1f1f1;display:none;'>$eio_debug</div>";
1616
  }
1617
  if ( ! empty( $next_image->file ) ) {
1618
  $next_file = esc_html( $next_image->file );
changelog.txt CHANGED
@@ -1,3 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  = 4.8.1 =
2
  * added: Lazy Load background image support added for span elements
3
  * changed: constrain by height for background images that are taller than they are wide
1
+ = 4.9.3 =
2
+ * fixed: ExactDN incorrectly scales Elementor background images rather than cropping
3
+ * fixed: ExactDN cannot work with Divi/Elementor background images due to use of external CSS files
4
+ * fixed: JS WebP rewriting picture tags that already have WebP markup in Force WebP mode
5
+ * fixed: JS WebP incorrectly parses GIF/SVG images in Force WebP mode
6
+ * fixed: JS WebP does not support lazy load + infinite scroll
7
+ * fixed: Lazy Load auto-scaling breaks if background image is enclosed in encoded quotes
8
+ * fixed: GRAND FlaGallery integration broken by hook suffix change
9
+
10
+ = 4.9.2 =
11
+ * fixed: generating lazy load PNG placeholders with large heights causes 500 errors
12
+ * fixed: error when importing media via WordPress Importer plugin
13
+ * fixed: error with WP/LR Sync
14
+
15
+ = 4.9.1 =
16
+ * fixed: error on settings screen when JS WebP is enabled
17
+
18
+ = 4.9.0 =
19
+ * added: Lazy Load background image support for section elements
20
+ * added: ExactDN background image support for li,span, and section elements
21
+ * added: lazysizes print plugin, enable via EWWW_IMAGE_OPTIMIZER_LAZY_PRINT constant
22
+ * added: compatibility with upcoming Easy Image Optimizer
23
+ * changed: automatic compression disabled during WP/LR Sync with admin notice
24
+ * changed: Lazy Load PNG placeholders capped at 1920px wide to prevent errors
25
+ * changed: use ExactDN, when active, for Lazy Load PNG placeholders
26
+ * changed: EWWW_MEMORY_LIMIT renamed to EIO_MEMORY_LIMIT for setting plugin memory limit
27
+ * fixed: WebP test image not refreshing after inserting .htaccess rules
28
+ * fixed: errors when manually adding lazysizes script
29
+
30
  = 4.8.1 =
31
  * added: Lazy Load background image support added for span elements
32
  * changed: constrain by height for background images that are taller than they are wide
classes/{class-ewwwio-alt-webp.php → class-eio-alt-webp.php} RENAMED
@@ -3,7 +3,7 @@
3
  * Implements WebP rewriting using page parsing and JS functionality.
4
  *
5
  * @link https://ewww.io
6
- * @package EWWW_Image_Optimizer
7
  */
8
 
9
  if ( ! defined( 'ABSPATH' ) ) {
@@ -13,7 +13,7 @@ if ( ! defined( 'ABSPATH' ) ) {
13
  /**
14
  * Enables EWWW IO to filter the page content and replace img elements with WebP markup.
15
  */
16
- class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
17
 
18
  /**
19
  * The Alt WebP inline script contents. Current length 11704.
@@ -43,8 +43,8 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
43
  * Register (once) actions and filters for Alt WebP.
44
  */
45
  function __construct() {
46
- global $ewwwio_alt_webp;
47
- if ( is_object( $ewwwio_alt_webp ) ) {
48
  return 'you are doing it wrong';
49
  }
50
  if ( ewww_image_optimizer_ce_webp_enabled() ) {
@@ -297,7 +297,9 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
297
  is_feed() ||
298
  is_preview() ||
299
  ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
300
- preg_match( '/^<\?xml/', $buffer )
 
 
301
  ) {
302
  if ( empty( $buffer ) ) {
303
  ewwwio_debug_message( 'empty buffer' );
@@ -338,8 +340,12 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
338
  /* TODO: detect non-utf8 encoding and convert the buffer (if necessary). */
339
 
340
  $images = $this->get_images_from_html( preg_replace( '/<noscript.*?\/noscript>/s', '', $buffer ), false );
341
- if ( ewww_image_optimizer_iterable( $images[0] ) ) {
342
  foreach ( $images[0] as $index => $image ) {
 
 
 
 
343
  $file = $images['img_url'][ $index ];
344
  ewwwio_debug_message( "parsing an image: $file" );
345
  if ( strpos( $image, 'jetpack-lazy-image' ) && $this->validate_image_url( $file ) ) {
@@ -457,12 +463,16 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
457
  } // End if().
458
  // Now we will look for any lazy images that don't have a src attribute (this search returns ALL img elements though).
459
  $images = $this->get_images_from_html( preg_replace( '/<noscript.*?\/noscript>/s', '', $buffer ), false, false );
460
- if ( ewww_image_optimizer_iterable( $images[0] ) ) {
461
  ewwwio_debug_message( 'parsing images without requiring src' );
462
  foreach ( $images[0] as $index => $image ) {
463
  if ( $this->get_attribute( $image, 'src' ) ) {
464
  continue;
465
  }
 
 
 
 
466
  ewwwio_debug_message( 'found img without src' );
467
  if ( strpos( $image, 'data-src=' ) && strpos( $image, 'data-srcset=' ) && strpos( $image, 'lazyload' ) ) {
468
  // EWWW IO Lazy Load.
@@ -515,6 +525,9 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
515
  $pictures = $this->get_picture_tags_from_html( $buffer );
516
  if ( ewww_image_optimizer_iterable( $pictures ) ) {
517
  foreach ( $pictures as $index => $picture ) {
 
 
 
518
  $sources = $this->get_elements_from_html( $picture, 'source' );
519
  if ( ewww_image_optimizer_iterable( $sources ) ) {
520
  foreach ( $sources as $source ) {
@@ -642,9 +655,6 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
642
  }
643
  }
644
  ewwwio_debug_message( 'all done parsing page for alt webp' );
645
- if ( true ) { // Set to true for extra logging.
646
- ewww_image_optimizer_debug_log();
647
- }
648
  return $buffer;
649
  }
650
 
@@ -689,7 +699,7 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
689
  /**
690
  * Checks if the path is a valid "forced" WebP image.
691
  *
692
- * @param string $image The image file.
693
  * @return bool True if the file matches a forced path, false otherwise.
694
  */
695
  function validate_image_url( $image ) {
@@ -702,6 +712,20 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
702
  ewwwio_debug_message( 'lazy load placeholder' );
703
  return false;
704
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
  if ( $this->parsing_exactdn && false !== strpos( $image, $this->exactdn_domain ) ) {
706
  ewwwio_debug_message( 'exactdn image' );
707
  return true;
@@ -788,5 +812,5 @@ class EWWWIO_Alt_Webp extends EWWWIO_Page_Parser {
788
  }
789
  }
790
 
791
- global $ewwwio_alt_webp;
792
- $ewwwio_alt_webp = new EWWWIO_Alt_Webp();
3
  * Implements WebP rewriting using page parsing and JS functionality.
4
  *
5
  * @link https://ewww.io
6
+ * @package EIO
7
  */
8
 
9
  if ( ! defined( 'ABSPATH' ) ) {
13
  /**
14
  * Enables EWWW IO to filter the page content and replace img elements with WebP markup.
15
  */
16
+ class EIO_Alt_Webp extends EIO_Page_Parser {
17
 
18
  /**
19
  * The Alt WebP inline script contents. Current length 11704.
43
  * Register (once) actions and filters for Alt WebP.
44
  */
45
  function __construct() {
46
+ global $eio_alt_webp;
47
+ if ( is_object( $eio_alt_webp ) ) {
48
  return 'you are doing it wrong';
49
  }
50
  if ( ewww_image_optimizer_ce_webp_enabled() ) {
297
  is_feed() ||
298
  is_preview() ||
299
  ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
300
+ preg_match( '/^<\?xml/', $buffer ) ||
301
+ strpos( $buffer, 'amp-boilerplate' ) ||
302
+ ewww_image_optimizer_ce_webp_enabled()
303
  ) {
304
  if ( empty( $buffer ) ) {
305
  ewwwio_debug_message( 'empty buffer' );
340
  /* TODO: detect non-utf8 encoding and convert the buffer (if necessary). */
341
 
342
  $images = $this->get_images_from_html( preg_replace( '/<noscript.*?\/noscript>/s', '', $buffer ), false );
343
+ if ( ! empty( $images[0] ) && $this->is_iterable( $images[0] ) ) {
344
  foreach ( $images[0] as $index => $image ) {
345
+ // Ignore 0-size Pinterest schema images.
346
+ if ( strpos( $image, 'data-pin-description=' ) && strpos( $image, 'width="0" height="0"' ) ) {
347
+ continue;
348
+ }
349
  $file = $images['img_url'][ $index ];
350
  ewwwio_debug_message( "parsing an image: $file" );
351
  if ( strpos( $image, 'jetpack-lazy-image' ) && $this->validate_image_url( $file ) ) {
463
  } // End if().
464
  // Now we will look for any lazy images that don't have a src attribute (this search returns ALL img elements though).
465
  $images = $this->get_images_from_html( preg_replace( '/<noscript.*?\/noscript>/s', '', $buffer ), false, false );
466
+ if ( ! empty( $images[0] ) && $this->is_iterable( $images[0] ) ) {
467
  ewwwio_debug_message( 'parsing images without requiring src' );
468
  foreach ( $images[0] as $index => $image ) {
469
  if ( $this->get_attribute( $image, 'src' ) ) {
470
  continue;
471
  }
472
+ // Ignore 0-size Pinterest schema images.
473
+ if ( strpos( $image, 'data-pin-description=' ) && strpos( $image, 'width="0" height="0"' ) ) {
474
+ continue;
475
+ }
476
  ewwwio_debug_message( 'found img without src' );
477
  if ( strpos( $image, 'data-src=' ) && strpos( $image, 'data-srcset=' ) && strpos( $image, 'lazyload' ) ) {
478
  // EWWW IO Lazy Load.
525
  $pictures = $this->get_picture_tags_from_html( $buffer );
526
  if ( ewww_image_optimizer_iterable( $pictures ) ) {
527
  foreach ( $pictures as $index => $picture ) {
528
+ if ( strpos( $picture, 'image/webp' ) ) {
529
+ continue;
530
+ }
531
  $sources = $this->get_elements_from_html( $picture, 'source' );
532
  if ( ewww_image_optimizer_iterable( $sources ) ) {
533
  foreach ( $sources as $source ) {
655
  }
656
  }
657
  ewwwio_debug_message( 'all done parsing page for alt webp' );
 
 
 
658
  return $buffer;
659
  }
660
 
699
  /**
700
  * Checks if the path is a valid "forced" WebP image.
701
  *
702
+ * @param string $image The image URL.
703
  * @return bool True if the file matches a forced path, false otherwise.
704
  */
705
  function validate_image_url( $image ) {
712
  ewwwio_debug_message( 'lazy load placeholder' );
713
  return false;
714
  }
715
+ $extension = '';
716
+ $image_path = $this->parse_url( $image, PHP_URL_PATH );
717
+ if ( ! is_null( $image_path ) && $image_path ) {
718
+ $extension = strtolower( pathinfo( $image_path, PATHINFO_EXTENSION ) );
719
+ }
720
+ if ( $extension && 'gif' === $extension ) {
721
+ return false;
722
+ }
723
+ if ( $extension && 'svg' === $extension ) {
724
+ return false;
725
+ }
726
+ if ( $extension && 'webp' === $extension ) {
727
+ return false;
728
+ }
729
  if ( $this->parsing_exactdn && false !== strpos( $image, $this->exactdn_domain ) ) {
730
  ewwwio_debug_message( 'exactdn image' );
731
  return true;
812
  }
813
  }
814
 
815
+ global $eio_alt_webp;
816
+ $eio_alt_webp = new EIO_Alt_Webp();
classes/class-eio-base.php ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Implements basic and common utility functions for all sub-classes.
4
+ *
5
+ * @link https://ewww.io
6
+ * @package EIO
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ if ( ! class_exists( 'EIO_Base' ) ) {
14
+ /**
15
+ * HTML element and attribute parsing, replacing, etc.
16
+ */
17
+ class EIO_Base {
18
+
19
+ /**
20
+ * Content directory (URL) for the plugin to use.
21
+ *
22
+ * @access protected
23
+ * @var string $content_url
24
+ */
25
+ protected $content_url = WP_CONTENT_URL . 'ewww/';
26
+
27
+ /**
28
+ * Content directory (path) for the plugin to use.
29
+ *
30
+ * @access protected
31
+ * @var string $content_dir
32
+ */
33
+ protected $content_dir = WP_CONTENT_DIR . '/ewww/';
34
+
35
+ /**
36
+ * Plugin version for the plugin.
37
+ *
38
+ * @access protected
39
+ * @var float $version
40
+ */
41
+ protected $version = 1.1;
42
+
43
+ /**
44
+ * Prefix to be used by plugin in option and hook names.
45
+ *
46
+ * @access protected
47
+ * @var string $prefix
48
+ */
49
+ protected $prefix = 'ewww_image_optimizer_';
50
+
51
+ /**
52
+ * Set class properties for children.
53
+ *
54
+ * @param string $child_class_path The location of the child class extending the base class.
55
+ */
56
+ function __construct( $child_class_path = '' ) {
57
+ if ( strpos( $child_class_path, 'ewww' ) ) {
58
+ $this->content_url = content_url( 'ewww/' );
59
+ $this->content_dir = WP_CONTENT_DIR . '/ewww/';
60
+ $this->version = EWWW_IMAGE_OPTIMIZER_VERSION;
61
+ } elseif ( strpos( $child_class_path, 'easy' ) ) {
62
+ $this->content_url = content_url( 'easyio/' );
63
+ $this->content_dir = WP_CONTENT_DIR . '/easyio/';
64
+ $this->version = EASYIO_VERSION;
65
+ $this->prefix = 'easyio_';
66
+ } else {
67
+ $this->content_url = content_url( 'ewww/' );
68
+ }
69
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
70
+ }
71
+
72
+ /**
73
+ * Saves the in-memory debug log to a logfile in the plugin folder.
74
+ *
75
+ * @global string $eio_debug The in-memory debug log.
76
+ */
77
+ function debug_log() {
78
+ global $eio_debug;
79
+ global $ewwwio_temp_debug;
80
+ global $easyio_temp_debug;
81
+ $debug_log = $this->content_dir . 'debug.log';
82
+ if ( is_writable( WP_CONTENT_DIR ) && ! is_dir( $this->content_dir ) ) {
83
+ mkdir( $this->content_dir );
84
+ }
85
+ $debug_enabled = $this->get_option( $this->prefix . 'debug' );
86
+ if ( ! empty( $eio_debug ) && empty( $ewwwio_temp_debug ) && empty( $easyio_temp_debug ) && $debug_enabled && is_writable( $this->content_dir ) ) {
87
+ $memory_limit = $this->memory_limit();
88
+ clearstatcache();
89
+ $timestamp = date( 'Y-m-d H:i:s' ) . "\n";
90
+ if ( ! file_exists( $debug_log ) ) {
91
+ touch( $debug_log );
92
+ } else {
93
+ if ( filesize( $debug_log ) + 4000000 + memory_get_usage( true ) > $memory_limit ) {
94
+ unlink( $debug_log );
95
+ touch( $debug_log );
96
+ }
97
+ }
98
+ if ( filesize( $debug_log ) + strlen( $eio_debug ) + 4000000 + memory_get_usage( true ) <= $memory_limit && is_writable( $debug_log ) ) {
99
+ $eio_debug = str_replace( '<br>', "\n", $eio_debug );
100
+ file_put_contents( $debug_log, $timestamp . $eio_debug, FILE_APPEND );
101
+ }
102
+ }
103
+ $eio_debug = '';
104
+ }
105
+
106
+ /**
107
+ * Adds information to the in-memory debug log.
108
+ *
109
+ * @global string $eio_debug The in-memory debug log.
110
+ * @global bool $easyio_temp_debug Indicator that we are temporarily debugging on the wp-admin.
111
+ * @global bool $ewwwio_temp_debug Indicator that we are temporarily debugging on the wp-admin.
112
+ *
113
+ * @param string $message Debug information to add to the log.
114
+ */
115
+ function debug_message( $message ) {
116
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
117
+ WP_CLI::debug( $message );
118
+ return;
119
+ }
120
+ global $ewwwio_temp_debug;
121
+ global $easyio_temp_debug;
122
+ if ( $easyio_temp_debug || $ewwwio_temp_debug || $this->get_option( $this->prefix . 'debug' ) ) {
123
+ $memory_limit = $this->memory_limit();
124
+ if ( strlen( $message ) + 4000000 + memory_get_usage( true ) <= $memory_limit ) {
125
+ global $eio_debug;
126
+ $message = str_replace( "\n\n\n", '<br>', $message );
127
+ $message = str_replace( "\n\n", '<br>', $message );
128
+ $message = str_replace( "\n", '<br>', $message );
129
+ $eio_debug .= "$message<br>";
130
+ } else {
131
+ global $eio_debug;
132
+ $eio_debug = "not logging message, memory limit is $memory_limit";
133
+ }
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Checks if a function is disabled or does not exist.
139
+ *
140
+ * @param string $function The name of a function to test.
141
+ * @param bool $debug Whether to output debugging.
142
+ * @return bool True if the function is available, False if not.
143
+ */
144
+ function function_exists( $function, $debug = false ) {
145
+ if ( function_exists( 'ini_get' ) ) {
146
+ $disabled = @ini_get( 'disable_functions' );
147
+ if ( $debug ) {
148
+ easyio_debug_message( "disable_functions: $disabled" );
149
+ }
150
+ }
151
+ if ( extension_loaded( 'suhosin' ) && function_exists( 'ini_get' ) ) {
152
+ $suhosin_disabled = @ini_get( 'suhosin.executor.func.blacklist' );
153
+ if ( $debug ) {
154
+ easyio_debug_message( "suhosin_blacklist: $suhosin_disabled" );
155
+ }
156
+ if ( ! empty( $suhosin_disabled ) ) {
157
+ $suhosin_disabled = explode( ',', $suhosin_disabled );
158
+ $suhosin_disabled = array_map( 'trim', $suhosin_disabled );
159
+ $suhosin_disabled = array_map( 'strtolower', $suhosin_disabled );
160
+ if ( function_exists( $function ) && ! in_array( $function, $suhosin_disabled, true ) ) {
161
+ return true;
162
+ }
163
+ return false;
164
+ }
165
+ }
166
+ return function_exists( $function );
167
+ }
168
+
169
+ /**
170
+ * Check for GD support.
171
+ *
172
+ * @return bool Debug True if GD support detected.
173
+ */
174
+ function gd_support() {
175
+ $this->debug_message( '<b>' . __FUNCTION__ . '()</b>' );
176
+ if ( function_exists( 'gd_info' ) ) {
177
+ $gd_support = gd_info();
178
+ $this->debug_message( 'GD found, supports:' );
179
+ if ( $this->is_iterable( $gd_support ) ) {
180
+ foreach ( $gd_support as $supports => $supported ) {
181
+ $this->debug_message( "$supports: $supported" );
182
+ }
183
+ if ( ( ! empty( $gd_support['JPEG Support'] ) || ! empty( $gd_support['JPG Support'] ) ) && ! empty( $gd_support['PNG Support'] ) ) {
184
+ return true;
185
+ }
186
+ }
187
+ }
188
+ return false;
189
+ }
190
+
191
+ /**
192
+ * Retrieve option: use 'site' setting if plugin is network activated, otherwise use 'blog' setting.
193
+ *
194
+ * Retrieves multi-site and single-site options as appropriate as well as allowing overrides with
195
+ * same-named constant. Overrides are only available for integer and boolean options.
196
+ *
197
+ * @param string $option_name The name of the option to retrieve.
198
+ * @return mixed The value of the option.
199
+ */
200
+ function get_option( $option_name ) {
201
+ $constant_name = strtoupper( $option_name );
202
+ if ( defined( $constant_name ) && ( is_int( constant( $constant_name ) ) || is_bool( constant( $constant_name ) ) ) ) {
203
+ return constant( $constant_name );
204
+ }
205
+ if ( ! function_exists( 'is_plugin_active_for_network' ) && is_multisite() ) {
206
+ // Need to include the plugin library for the is_plugin_active function.
207
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
208
+ }
209
+ if (
210
+ is_multisite() &&
211
+ is_plugin_active_for_network( constant( strtoupper( $this->prefix ) . 'PLUGIN_FILE_REL' ) ) &&
212
+ ! get_site_option( $this->prefix . 'allow_multisite_override' )
213
+ ) {
214
+ $option_value = get_site_option( $option_name );
215
+ } else {
216
+ $option_value = get_option( $option_name );
217
+ }
218
+ return $option_value;
219
+ }
220
+
221
+ /**
222
+ * Implode a multi-dimensional array without throwing errors. Arguments can be reverse order, same as implode().
223
+ *
224
+ * @param string $delimiter The character to put between the array items (the glue).
225
+ * @param array $data The array to output with the glue.
226
+ * @return string The array values, separated by the delimiter.
227
+ */
228
+ function implode( $delimiter, $data = '' ) {
229
+ if ( is_array( $delimiter ) ) {
230
+ $temp_data = $delimiter;
231
+ $delimiter = $data;
232
+ $data = $temp_data;
233
+ }
234
+ if ( is_array( $delimiter ) ) {
235
+ return '';
236
+ }
237
+ $output = '';
238
+ foreach ( $data as $value ) {
239
+ if ( is_string( $value ) || is_numeric( $value ) ) {
240
+ $output .= $value . $delimiter;
241
+ } elseif ( is_bool( $value ) ) {
242
+ $output .= ( $value ? 'true' : 'false' ) . $delimiter;
243
+ } elseif ( is_array( $value ) ) {
244
+ $output .= 'Array,';
245
+ }
246
+ }
247
+ return rtrim( $output, ',' );
248
+ }
249
+
250
+ /**
251
+ * Checks to see if the current page being output is an AMP page.
252
+ *
253
+ * @return bool True for an AMP endpoint, false otherwise.
254
+ */
255
+ function is_amp() {
256
+ if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
257
+ return true;
258
+ }
259
+ if ( function_exists( 'ampforwp_is_amp_endpoint' ) && ampforwp_is_amp_endpoint() ) {
260
+ return true;
261
+ }
262
+ return false;
263
+ }
264
+
265
+ /**
266
+ * Make sure an array/object can be parsed by a foreach().
267
+ *
268
+ * @param mixed $var A variable to test for iteration ability.
269
+ * @return bool True if the variable is iterable.
270
+ */
271
+ function is_iterable( $var ) {
272
+ return ! empty( $var ) && ( is_array( $var ) || $var instanceof Traversable );
273
+ }
274
+
275
+ /**
276
+ * Finds the current PHP memory limit or a reasonable default.
277
+ *
278
+ * @return int The memory limit in bytes.
279
+ */
280
+ function memory_limit() {
281
+ if ( defined( 'EIO_MEMORY_LIMIT' ) && EIO_MEMORY_LIMIT ) {
282
+ $memory_limit = EIO_MEMORY_LIMIT;
283
+ } elseif ( function_exists( 'ini_get' ) ) {
284
+ $memory_limit = ini_get( 'memory_limit' );
285
+ } else {
286
+ if ( ! defined( 'EIO_MEMORY_LIMIT' ) ) {
287
+ // Conservative default, current usage + 16M.
288
+ $current_memory = memory_get_usage( true );
289
+ $memory_limit = round( $current_memory / ( 1024 * 1024 ) ) + 16;
290
+ define( 'EIO_MEMORY_LIMIT', $memory_limit );
291
+ }
292
+ }
293
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
294
+ WP_CLI::debug( "memory limit is set at $memory_limit" );
295
+ }
296
+ if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
297
+ // Unlimited, set to 32GB.
298
+ $memory_limit = '32000M';
299
+ }
300
+ if ( strpos( $memory_limit, 'G' ) ) {
301
+ $memory_limit = intval( $memory_limit ) * 1024 * 1024 * 1024;
302
+ } else {
303
+ $memory_limit = intval( $memory_limit ) * 1024 * 1024;
304
+ }
305
+ return $memory_limit;
306
+ }
307
+
308
+ /**
309
+ * Set an option: use 'site' setting if plugin is network activated, otherwise use 'blog' setting.
310
+ *
311
+ * @param string $option_name The name of the option to save.
312
+ * @param mixed $option_value The value to save for the option.
313
+ * @return bool True if the operation was successful.
314
+ */
315
+ function set_option( $option_name, $option_value ) {
316
+ if ( ! function_exists( 'is_plugin_active_for_network' ) && is_multisite() ) {
317
+ // Need to include the plugin library for the is_plugin_active function.
318
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
319
+ }
320
+ if (
321
+ is_multisite() &&
322
+ is_plugin_active_for_network( constant( strtoupper( $this->prefix ) . 'PLUGIN_FILE_REL' ) ) &&
323
+ ! get_site_option( $this->prefix . 'allow_multisite_override' )
324
+ ) {
325
+ $success = update_site_option( $option_name, $option_value );
326
+ } else {
327
+ $success = update_option( $option_name, $option_value );
328
+ }
329
+ return $success;
330
+ }
331
+
332
+ }
333
+ }
classes/class-eio-hs-beacon.php ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Help integration functions for embedding the HS Beacon for users that have opted in.
4
+ *
5
+ * @package EIO
6
+ * @since 3.5.1
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ if ( ! class_exists( 'EIO_HS_Beacon' ) ) {
14
+ /**
15
+ * Embed HS Beacon to give users more help when they need it.
16
+ *
17
+ * @since 3.5.1
18
+ */
19
+ class EIO_HS_Beacon extends EIO_Base {
20
+
21
+ /**
22
+ * Get things going
23
+ */
24
+ public function __construct() {
25
+ parent::__construct( __FILE__ );
26
+ add_action( 'admin_action_eio_opt_into_hs_beacon', array( $this, 'check_for_optin' ) );
27
+ add_action( 'admin_action_eio_opt_out_of_hs_beacon', array( $this, 'check_for_optout' ) );
28
+ }
29
+
30
+ /**
31
+ * Check for a new opt-in on settings save
32
+ *
33
+ * @param bool $input The enable_help setting.
34
+ * @return bool The unaltered setting.
35
+ */
36
+ public function check_for_settings_optin( $input ) {
37
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
38
+ if ( isset( $_POST[ $this->prefix . 'enable_help' ] ) && $_POST[ $this->prefix . 'enable_help' ] ) {
39
+ $this->set_option( $this->prefix . 'enable_help_notice', 1 );
40
+ }
41
+ return $input;
42
+ }
43
+
44
+ /**
45
+ * Check for a new opt-in via the admin notice
46
+ */
47
+ public function check_for_optin() {
48
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
49
+ $this->set_option( $this->prefix . 'enable_help', 1 );
50
+ $this->set_option( $this->prefix . 'enable_help_notice', 1 );
51
+ wp_redirect( remove_query_arg( 'action', wp_get_referer() ) );
52
+ exit;
53
+ }
54
+
55
+ /**
56
+ * Check for a new opt-out via the admin notice
57
+ */
58
+ public function check_for_optout() {
59
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
60
+ delete_option( $this->prefix . 'enable_help' );
61
+ delete_network_option( null, $this->prefix . 'enable_help' );
62
+ $this->set_option( $this->prefix . 'enable_help_notice', 1 );
63
+ wp_redirect( remove_query_arg( 'action', wp_get_referer() ) );
64
+ exit;
65
+ }
66
+
67
+ /**
68
+ * Display the admin notice to users that have not opted-in or out
69
+ *
70
+ * @access public
71
+ * @param string $network_class A string that indicates where this is being displayed.
72
+ * @return void
73
+ */
74
+ public function admin_notice( $network_class = '' ) {
75
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
76
+ $hide_notice = $this->get_option( $this->prefix . 'enable_help_notice' );
77
+ if ( 'network-multisite' === $network_class && get_site_option( $this->prefix . 'allow_multisite_override' ) ) {
78
+ return;
79
+ }
80
+ if ( 'network-singlesite' === $network_class && ! get_site_option( $this->prefix . 'allow_multisite_override' ) ) {
81
+ return;
82
+ }
83
+
84
+ if ( $hide_notice ) {
85
+ return;
86
+ }
87
+ if ( $this->get_option( $this->prefix . 'enable_help' ) ) {
88
+ return;
89
+ }
90
+
91
+ if ( ! current_user_can( 'manage_options' ) ) {
92
+ return;
93
+ }
94
+
95
+ if ( ! function_exists( 'is_plugin_active_for_network' ) && is_multisite() ) {
96
+ // Need to include the plugin library for the is_plugin_active function.
97
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
98
+ }
99
+ if (
100
+ is_multisite() &&
101
+ is_plugin_active_for_network( constant( strtoupper( $this->prefix ) . 'PLUGIN_FILE_REL' ) ) &&
102
+ ! current_user_can( 'manage_network_options' )
103
+ ) {
104
+ return;
105
+ }
106
+ if ( strpos( __FILE__, 'ewww' ) ) {
107
+ ewww_image_optimizer_notice_beacon();
108
+ } elseif ( strpos( __FILE__, 'easy' ) ) {
109
+ easyio_notice_beacon();
110
+ }
111
+ }
112
+ }
113
+
114
+ global $eio_hs_beacon;
115
+ $eio_hs_beacon = new EIO_HS_Beacon;
116
+ }
classes/class-eio-lazy-load.php ADDED
@@ -0,0 +1,672 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Implements Lazy Loading using page parsing and JS functionality.
4
+ *
5
+ * @link https://ewww.io
6
+ * @package EIO
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ if ( ! class_exists( 'EIO_Lazy_Load' ) ) {
14
+ /**
15
+ * Enables plugin to filter the page content and replace img elements with Lazy Load markup.
16
+ */
17
+ class EIO_Lazy_Load extends EIO_Page_Parser {
18
+
19
+ /**
20
+ * Base64-encoded placeholder image.
21
+ *
22
+ * @access protected
23
+ * @var string $placeholder_src
24
+ */
25
+ protected $placeholder_src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
26
+
27
+ /**
28
+ * Indicates if we are filtering ExactDN urls.
29
+ *
30
+ * @access protected
31
+ * @var bool $parsing_exactdn
32
+ */
33
+ protected $parsing_exactdn = false;
34
+
35
+ /**
36
+ * The folder to store any PIIPs.
37
+ *
38
+ * @access protected
39
+ * @var string $piip_folder
40
+ */
41
+ protected $piip_folder = '';
42
+
43
+ /**
44
+ * Whether to allow PIIPs.
45
+ *
46
+ * @access public
47
+ * @var bool $allow_piip
48
+ */
49
+ public $allow_piip = true;
50
+
51
+ /**
52
+ * Register (once) actions and filters for Lazy Load.
53
+ */
54
+ function __construct() {
55
+ parent::__construct( __FILE__ );
56
+ $this->piip_folder = $this->content_dir . 'lazy/';
57
+ $this->debug_message( 'firing up lazy load' );
58
+ global $eio_lazy_load;
59
+ if ( is_object( $eio_lazy_load ) ) {
60
+ $this->debug_message( 'you are doing it wrong' );
61
+ return 'you are doing it wrong';
62
+ }
63
+
64
+ add_action( 'wp_head', array( $this, 'no_js_css' ) );
65
+
66
+ add_filter( $this->prefix . 'filter_page_output', array( $this, 'filter_page_output' ), 15 );
67
+
68
+ if ( class_exists( 'ExactDN' ) && $this->get_option( $this->prefix . 'exactdn' ) ) {
69
+ global $exactdn;
70
+ $this->exactdn_domain = $exactdn->get_exactdn_domain();
71
+ if ( $this->exactdn_domain ) {
72
+ $this->parsing_exactdn = true;
73
+ $this->debug_message( 'parsing an exactdn page' );
74
+ }
75
+ }
76
+
77
+ if ( $this->parsing_exactdn ) {
78
+ $this->allow_piip = true;
79
+ } elseif ( ! is_dir( $this->piip_folder ) ) {
80
+ $this->allow_piip = wp_mkdir_p( $this->piip_folder ) && $this->gd_support();
81
+ } else {
82
+ $this->allow_piip = is_writable( $this->piip_folder ) && $this->gd_support();
83
+ }
84
+
85
+ // Filter early, so that others at the default priority take precendence.
86
+ add_filter( 'eio_use_lqip', array( $this, 'maybe_lqip' ), 9 );
87
+ add_filter( 'eio_use_piip', array( $this, 'maybe_piip' ), 9 );
88
+ add_filter( 'eio_use_siip', array( $this, 'maybe_siip' ), 9 );
89
+
90
+ // Overrides for admin-ajax images.
91
+ add_filter( 'eio_allow_admin_lazyload', array( $this, 'allow_admin_lazyload' ) );
92
+
93
+ // Load the appropriate JS.
94
+ if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
95
+ // Load the non-minified and separate versions of the lazy load scripts.
96
+ add_action( 'wp_enqueue_scripts', array( $this, 'debug_script' ) );
97
+ } else {
98
+ // Load the minified, combined version of the lazy load script.
99
+ add_action( 'wp_enqueue_scripts', array( $this, 'min_script' ) );
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Starts an output buffer and registers the callback function to do WebP replacement.
105
+ */
106
+ function buffer_start() {
107
+ ob_start( array( $this, 'filter_page_output' ) );
108
+ }
109
+
110
+ /**
111
+ * Replaces images within a srcset attribute, just a placeholder at the moment.
112
+ *
113
+ * @param string $srcset A valid srcset attribute from an img element.
114
+ * @return bool|string False if no changes were made, or the new srcset if any WebP images replaced the originals.
115
+ */
116
+ function srcset_replace( $srcset ) {
117
+ return $srcset;
118
+ }
119
+
120
+ /**
121
+ * Search for img elements and rewrite them for Lazy Load with fallback to noscript elements.
122
+ *
123
+ * @param string $buffer The full HTML page generated since the output buffer was started.
124
+ * @return string The altered buffer containing the full page with Lazy Load attributes.
125
+ */
126
+ function filter_page_output( $buffer ) {
127
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
128
+ // Don't foul up the admin side of things, unless a plugin needs to.
129
+ if ( is_admin() &&
130
+ /**
131
+ * Provide plugins a way of running Lazy Load for images in the WordPress Dashboard (wp-admin).
132
+ *
133
+ * @param bool false Allow Lazy Load to run on the Dashboard. Default to false.
134
+ */
135
+ false === apply_filters( 'eio_allow_admin_lazyload', false )
136
+ ) {
137
+ $this->debug_message( 'is_admin' );
138
+ return $buffer;
139
+ }
140
+ // Don't lazy load in these cases...
141
+ $uri = $_SERVER['REQUEST_URI'];
142
+ if (
143
+ empty( $buffer ) ||
144
+ ! empty( $_GET['cornerstone'] ) ||
145
+ strpos( $uri, 'cornerstone-endpoint' ) !== false ||
146
+ ! empty( $_GET['ct_builder'] ) ||
147
+ ! empty( $_GET['elementor-preview'] ) ||
148
+ ! empty( $_GET['et_fb'] ) ||
149
+ ! empty( $_GET['tatsu'] ) ||
150
+ ( ! empty( $_POST['action'] ) && 'tatsu_get_concepts' === $_POST['action'] ) ||
151
+ ! apply_filters( 'eio_do_lazyload', true ) ||
152
+ is_feed() ||
153
+ is_preview() ||
154
+ ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
155
+ wp_script_is( 'twentytwenty-twentytwenty', 'enqueued' ) ||
156
+ preg_match( '/^<\?xml/', $buffer ) ||
157
+ strpos( $buffer, 'amp-boilerplate' )
158
+ ) {
159
+ if ( empty( $buffer ) ) {
160
+ $this->debug_message( 'empty buffer' );
161
+ }
162
+ if ( ! empty( $_GET['cornerstone'] ) || strpos( $uri, 'cornerstone-endpoint' ) !== false ) {
163
+ $this->debug_message( 'cornerstone editor' );
164
+ }
165
+ if ( ! empty( $_GET['ct_builder'] ) ) {
166
+ $this->debug_message( 'oxygen builder' );
167
+ }
168
+ if ( ! empty( $_GET['elementor-preview'] ) ) {
169
+ $this->debug_message( 'elementor preview' );
170
+ }
171
+ if ( ! empty( $_GET['et_fb'] ) ) {
172
+ $this->debug_message( 'et_fb' );
173
+ }
174
+ if ( ! empty( $_GET['tatsu'] ) || ( ! empty( $_POST['action'] ) && 'tatsu_get_concepts' === $_POST['action'] ) ) {
175
+ $this->debug_message( 'tatsu' );
176
+ }
177
+ if ( ! apply_filters( 'eio_do_lazyload', true ) ) {
178
+ $this->debug_message( 'do_lazyload short-circuit' );
179
+ }
180
+ if ( is_feed() ) {
181
+ $this->debug_message( 'is_feed' );
182
+ }
183
+ if ( is_preview() ) {
184
+ $this->debug_message( 'is_preview' );
185
+ }
186
+ if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
187
+ $this->debug_message( 'rest request' );
188
+ }
189
+ if ( wp_script_is( 'twentytwenty-twentytwenty', 'enqueued' ) ) {
190
+ $this->debug_message( 'twentytwenty enqueued' );
191
+ }
192
+ if ( preg_match( '/^<\?xml/', $buffer ) ) {
193
+ $this->debug_message( 'not html, xml tag found' );
194
+ }
195
+ if ( strpos( $buffer, 'amp-boilerplate' ) ) {
196
+ $this->debug_message( 'AMP page processing' );
197
+ }
198
+ return $buffer;
199
+ }
200
+
201
+ global $exactdn;
202
+ $above_the_fold = apply_filters( 'eio_lazy_fold', 0 );
203
+ $images_processed = 0;
204
+
205
+ // Clean the buffer of incompatible sections.
206
+ $search_buffer = preg_replace( '/<div id="footer_photostream".*?\/div>/s', '', $buffer );
207
+ $search_buffer = preg_replace( '/<(noscript|script).*?\/\1>/s', '', $search_buffer );
208
+
209
+ $images = $this->get_images_from_html( $search_buffer, false );
210
+ if ( ! empty( $images[0] ) && $this->is_iterable( $images[0] ) ) {
211
+ foreach ( $images[0] as $index => $image ) {
212
+ $images_processed++;
213
+ if ( $images_processed <= $above_the_fold ) {
214
+ continue;
215
+ }
216
+ $file = $images['img_url'][ $index ];
217
+ $this->debug_message( "parsing an image: $file" );
218
+ if ( $this->validate_image_tag( $image ) ) {
219
+ $this->debug_message( 'found a valid image tag' );
220
+ $this->debug_message( "original image tag: $image" );
221
+ $orig_img = $image;
222
+ $noscript = '<noscript>' . $orig_img . '</noscript>';
223
+ $this->set_attribute( $image, 'data-src', $file, true );
224
+ $srcset = $this->get_attribute( $image, 'srcset' );
225
+
226
+ $placeholder_src = $this->placeholder_src;
227
+ if ( false === strpos( $file, 'nggid' ) && ! preg_match( '#\.svg(\?|$)#', $file ) && apply_filters( 'eio_use_lqip', true, $file ) && $this->parsing_exactdn && strpos( $file, $this->exactdn_domain ) ) {
228
+ $this->debug_message( 'using lqip' );
229
+ list( $width, $height ) = $this->get_dimensions_from_filename( $file );
230
+ if ( $width && $height && $width < 201 && $height < 201 ) {
231
+ $placeholder_src = $exactdn->generate_url( $this->content_url . 'lazy/placeholder-' . $width . 'x' . $height . '.png' );
232
+ } else {
233
+ $placeholder_src = add_query_arg( array( 'lazy' => 1 ), $file );
234
+ }
235
+ } elseif ( $this->allow_piip && $srcset && apply_filters( 'eio_use_piip', true, $file ) ) {
236
+ $this->debug_message( 'trying piip' );
237
+ // Get image dimensions for PNG placeholder.
238
+ list( $width, $height ) = $this->get_dimensions_from_filename( $file );
239
+
240
+ $width_attr = $this->get_attribute( $image, 'width' );
241
+ $height_attr = $this->get_attribute( $image, 'height' );
242
+
243
+ // Can't use a relative width or height, so unset the dimensions in favor of not breaking things.
244
+ if ( false !== strpos( $width_attr, '%' ) || false !== strpos( $height_attr, '%' ) ) {
245
+ $width_attr = false;
246
+ $height_attr = false;
247
+ }
248
+
249
+ if ( false === $width || false === $height ) {
250
+ $width = $width_attr;
251
+ $height = $height_attr;
252
+ }
253
+
254
+ // Falsify them if empty.
255
+ $width = $width ? (int) $width : false;
256
+ $height = $height ? (int) $height : false;
257
+ if ( $width && $height ) {
258
+ $this->debug_message( "creating piip of $width x $height" );
259
+ $placeholder_src = $this->create_piip( $width, $height );
260
+ }
261
+ } elseif ( apply_filters( 'eio_use_siip', true, $file ) ) {
262
+ $this->debug_message( 'trying siip' );
263
+ $width = $this->get_attribute( $image, 'width' );
264
+ $height = $this->get_attribute( $image, 'height' );
265
+
266
+ // Can't use a relative width or height, so unset the dimensions in favor of not breaking things.
267
+ if ( false !== strpos( $width, '%' ) || false !== strpos( $height, '%' ) ) {
268
+ $width = false;
269
+ $height = false;
270
+ }
271
+
272
+ // Falsify them if empty.
273
+ $width = $width ? (int) $width : false;
274
+ $height = $height ? (int) $height : false;
275
+ if ( $width && $height ) {
276
+ $placeholder_src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 $width $height'%3E%3C/svg%3E";
277
+ }
278
+ }
279
+ $this->debug_message( "current placeholder is $placeholder_src" );
280
+
281
+ if ( $srcset ) {
282
+ $placeholder_src = apply_filters( 'eio_lazy_placeholder', $placeholder_src, $image );
283
+ if ( strpos( $placeholder_src, '64,R0lGOD' ) ) {
284
+ $this->set_attribute( $image, 'srcset', $placeholder_src, true );
285
+ $this->remove_attribute( $image, 'src' );
286
+ } else {
287
+ $this->set_attribute( $image, 'src', $placeholder_src, true );
288
+ $this->remove_attribute( $image, 'srcset' );
289
+ }
290
+ $this->set_attribute( $image, 'data-srcset', $srcset, true );
291
+ $srcset_sizes = $this->get_attribute( $image, 'sizes' );
292
+ // Return false on this filter to disable automatic sizes calculation,
293
+ // or use the sizes value passed via the filter to conditionally disable it.
294
+ if ( apply_filters( 'eio_lazy_responsive', $srcset_sizes ) ) {
295
+ $this->set_attribute( $image, 'data-sizes', 'auto', true );
296
+ $this->remove_attribute( $image, 'sizes' );
297
+ }
298
+ } else {
299
+ $this->set_attribute( $image, 'src', $placeholder_src, true );
300
+ }
301
+ $this->set_attribute( $image, 'class', $this->get_attribute( $image, 'class' ) . ' lazyload', true );
302
+ $buffer = str_replace( $orig_img, $image . $noscript, $buffer );
303
+ }
304
+ } // End foreach().
305
+ } // End if().
306
+ // Process background images on div elements.
307
+ $buffer = $this->parse_background_images( $buffer, 'div' );
308
+ // Process background images on li elements.
309
+ $buffer = $this->parse_background_images( $buffer, 'li' );
310
+ // Process background images on span elements.
311
+ $buffer = $this->parse_background_images( $buffer, 'span' );
312
+ // Process background images on section elements.
313
+ $buffer = $this->parse_background_images( $buffer, 'section' );
314
+ // Images listed as picture/source elements. Mostly for NextGEN, but should work anywhere.
315
+ $pictures = $this->get_picture_tags_from_html( $buffer );
316
+ if ( $this->is_iterable( $pictures ) ) {
317
+ foreach ( $pictures as $index => $picture ) {
318
+ $sources = $this->get_elements_from_html( $picture, 'source' );
319
+ if ( $this->is_iterable( $sources ) ) {
320
+ foreach ( $sources as $source ) {
321
+ if ( false !== strpos( $source, 'data-src' ) ) {
322
+ continue;
323
+ }
324
+ $this->debug_message( "parsing a picture source: $source" );
325
+ $srcset = $this->get_attribute( $source, 'srcset' );
326
+ if ( $srcset ) {
327
+ $this->debug_message( 'found srcset in source' );
328
+ $lazy_source = $source;
329
+ $this->set_attribute( $lazy_source, 'data-srcset', $srcset );
330
+ $this->set_attribute( $lazy_source, 'srcset', $this->placeholder_src, true );
331
+ $picture = str_replace( $source, $lazy_source, $picture );
332
+ }
333
+ }
334
+ if ( $picture !== $pictures[ $index ] ) {
335
+ $this->debug_message( 'lazified sources for picture element' );
336
+ $buffer = str_replace( $pictures[ $index ], $picture, $buffer );
337
+ }
338
+ }
339
+ }
340
+ }
341
+ // Video elements, looking for poster attributes that are images.
342
+ /* $videos = $this->get_elements_from_html( $buffer, 'video' ); */
343
+ $videos = '';
344
+ if ( $this->is_iterable( $videos ) ) {
345
+ foreach ( $videos as $index => $video ) {
346
+ $this->debug_message( 'parsing a video element' );
347
+ $file = $this->get_attribute( $video, 'poster' );
348
+ if ( $file ) {
349
+ $this->debug_message( "checking webp for video poster: $file" );
350
+ if ( $this->validate_image_tag( $file ) ) {
351
+ $this->set_attribute( $video, 'data-poster-webp', $this->placeholder_src );
352
+ $this->set_attribute( $video, 'data-poster-image', $file );
353
+ $this->remove_attribute( $video, 'poster' );
354
+ $this->debug_message( "found webp for video poster: $file" );
355
+ $buffer = str_replace( $videos[ $index ], $video, $buffer );
356
+ }
357
+ }
358
+ }
359
+ }
360
+ $this->debug_message( 'all done parsing page for lazy' );
361
+ return $buffer;
362
+ }
363
+
364
+ /**
365
+ * Parse elements of a given type for inline CSS background images.
366
+ *
367
+ * @param string $buffer The HTML content to parse.
368
+ * @param string $tag_type The type of HTML tag to look for.
369
+ * @return string The modified content with LL markup.
370
+ */
371
+ function parse_background_images( $buffer, $tag_type ) {
372
+ $elements = $this->get_elements_from_html( $buffer, $tag_type );
373
+ if ( $this->is_iterable( $elements ) ) {
374
+ foreach ( $elements as $index => $element ) {
375
+ $this->debug_message( "parsing a $tag_type" );
376
+ if ( false === strpos( $element, 'background:' ) && false === strpos( $element, 'background-image:' ) ) {
377
+ continue;
378
+ }
379
+ $this->debug_message( 'element contains background/background-image:' );
380
+ if ( ! $this->validate_bgimage_tag( $element ) ) {
381
+ continue;
382
+ }
383
+ $this->debug_message( 'element is valid' );
384
+ $style = $this->get_attribute( $element, 'style' );
385
+ if ( empty( $style ) ) {
386
+ continue;
387
+ }
388
+ $this->debug_message( "checking style attr for background-image: $style" );
389
+ $bg_image_url = $this->get_background_image_url( $style );
390
+ if ( $bg_image_url ) {
391
+ $this->debug_message( 'bg-image url found' );
392
+ $new_style = $this->remove_background_image( $style );
393
+ if ( $style !== $new_style ) {
394
+ $this->debug_message( 'style modified, continuing' );
395
+ $this->set_attribute( $element, 'class', $this->get_attribute( $element, 'class' ) . ' lazyload', true );
396
+ $this->set_attribute( $element, 'data-bg', $bg_image_url );
397
+ $element = str_replace( $style, $new_style, $element );
398
+ }
399
+ }
400
+ if ( $element !== $elements[ $index ] ) {
401
+ $this->debug_message( "$tag_type modified, replacing in html source" );
402
+ $buffer = str_replace( $elements[ $index ], $element, $buffer );
403
+ }
404
+ }
405
+ }
406
+ return $buffer;
407
+ }
408
+
409
+ /**
410
+ * Checks if the tag is allowed to be lazy loaded.
411
+ *
412
+ * @param string $image The image (img) tag.
413
+ * @return bool True if the tag is allowed, false otherwise.
414
+ */
415
+ function validate_image_tag( $image ) {
416
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
417
+ if (
418
+ strpos( $image, 'base64,R0lGOD' ) ||
419
+ strpos( $image, 'lazy-load/images/1x1' ) ||
420
+ strpos( $image, '/assets/images/' )
421
+ ) {
422
+ $this->debug_message( 'lazy load placeholder detected' );
423
+ return false;
424
+ }
425
+
426
+ // Skip inline data URIs.
427
+ $image_src = $this->get_attribute( $image, 'src' );
428
+ if ( false !== strpos( $image_src, 'data:image' ) ) {
429
+ return false;
430
+ }
431
+ if ( false !== strpos( $image, 'data:image' ) && false !== strpos( $image, 'lazyload' ) ) {
432
+ return false;
433
+ }
434
+ // Ignore 0-size Pinterest schema images.
435
+ if ( strpos( $image, 'data-pin-description=' ) && strpos( $image, 'width="0" height="0"' ) ) {
436
+ return false;
437
+ }
438
+
439
+ // Ignore native lazy loading images.
440
+ $loading_attr = $this->get_attribute( $image, 'loading' );
441
+ if ( $loading_attr && in_array( trim( $loading_attr ), array( 'auto', 'eager', 'lazy' ), true ) ) {
442
+ return false;
443
+ }
444
+
445
+ $exclusions = apply_filters(
446
+ 'eio_lazy_exclusions',
447
+ array(
448
+ 'class="ls-bg',
449
+ 'class="ls-l',
450
+ 'class="rev-slidebg',
451
+ 'data-bgposition=',
452
+ 'data-envira-src=',
453
+ 'data-lazy=',
454
+ 'data-lazy-original=',
455
+ 'data-lazy-src=',
456
+ 'data-lazy-srcset=',
457
+ 'data-lazyload=',
458
+ 'data-lazysrc=',
459
+ 'data-no-lazy=',
460
+ 'data-src=',
461
+ 'data-srcset=',
462
+ 'ewww_webp_lazy_load',
463
+ 'fullurl=',
464
+ 'gazette-featured-content-thumbnail',
465
+ 'lazy-slider-img=',
466
+ 'mgl-lazy',
467
+ 'skip-lazy',
468
+ 'timthumb.php?',
469
+ 'wpcf7_captcha/',
470
+ ),
471
+ $image
472
+ );
473
+ foreach ( $exclusions as $exclusion ) {
474
+ if ( false !== strpos( $image, $exclusion ) ) {
475
+ return false;
476
+ }
477
+ }
478
+ return true;
479
+ }
480
+
481
+ /**
482
+ * Checks if a tag with a background image is allowed to be lazy loaded.
483
+ *
484
+ * @param string $tag The tag.
485
+ * @return bool True if the tag is allowed, false otherwise.
486
+ */
487
+ function validate_bgimage_tag( $tag ) {
488
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
489
+ $exclusions = apply_filters(
490
+ 'eio_lazy_bg_image_exclusions',
491
+ array(
492
+ 'data-no-lazy=',
493
+ 'header-gallery-wrapper ',
494
+ 'lazyload',
495
+ 'skip-lazy',
496
+ 'avia-bg-style-fixed',
497
+ ),
498
+ $tag
499
+ );
500
+ foreach ( $exclusions as $exclusion ) {
501
+ if ( false !== strpos( $tag, $exclusion ) ) {
502
+ return false;
503
+ }
504
+ }
505
+ return true;
506
+ }
507
+
508
+ /**
509
+ * Build a PNG inline image placeholder.
510
+ *
511
+ * @param int $width The width of the placeholder image.
512
+ * @param int $height The height of the placeholder image.
513
+ * @return string The PNG placeholder link.
514
+ */
515
+ function create_piip( $width = 1, $height = 1 ) {
516
+ $width = (int) $width;
517
+ $height = (int) $height;
518
+ if ( ( 1 === $width && 1 === $height ) || ! $width || ! $height ) {
519
+ return $this->placeholder_src;
520
+ }
521
+
522
+ if ( $width > 1920 ) {
523
+ $ratio = $height / $width;
524
+ $width = 1920;
525
+ $height = round( 1920 * $ratio );
526
+ }
527
+ $height = min( $height, 1920 );
528
+
529
+ $piip_path = $this->piip_folder . 'placeholder-' . $width . 'x' . $height . '.png';
530
+ if ( $this->parsing_exactdn ) {
531
+ global $exactdn;
532
+ return $exactdn->generate_url( $this->content_url . 'lazy/placeholder-' . $width . 'x' . $height . '.png' );
533
+ } elseif ( ! is_file( $piip_path ) ) {
534
+ $img = imagecreatetruecolor( $width, $height );
535
+ $color = imagecolorallocatealpha( $img, 0, 0, 0, 127 );
536
+ imagefill( $img, 0, 0, $color );
537
+ imagesavealpha( $img, true );
538
+ imagecolortransparent( $img, imagecolorat( $img, 0, 0 ) );
539
+ imagetruecolortopalette( $img, false, 1 );
540
+ imagepng( $img, $piip_path, 9 );
541
+ }
542
+ if ( is_file( $piip_path ) ) {
543
+ return $this->content_url . 'lazy/placeholder-' . $width . 'x' . $height . '.png';
544
+ }
545
+ return $this->placeholder_src;
546
+ }
547
+ /**
548
+ * Allow lazy loading of images for some admin-ajax requests.
549
+ *
550
+ * @param bool $allow Will normally be false, unless already modified by another function.
551
+ * @return bool True if it's an allowable admin-ajax request, false for all other admin requests.
552
+ */
553
+ function allow_admin_lazyload( $allow ) {
554
+ if ( ! wp_doing_ajax() ) {
555
+ return $allow;
556
+ }
557
+ if ( ! empty( $_POST['action'] ) && 'vc_get_vc_grid_data' === $_POST['action'] ) {
558
+ $this->debug_message( 'allowing lazy on vc grid' );
559
+ return true;
560
+ }
561
+ if ( ! empty( $_POST['action'] ) && 'Essential_Grid_Front_request_ajax' === $_POST['action'] ) {
562
+ /* return true; */
563
+ }
564
+ return $allow;
565
+ }
566
+
567
+ /**
568
+ * Check if LQIP should be used, but allow filters to alter the option.
569
+ *
570
+ * @param bool $use_lqip Whether LL should use low-quality image placeholders.
571
+ * @return bool True to use LQIP, false to skip them.
572
+ */
573
+ function maybe_lqip( $use_lqip ) {
574
+ if ( defined( 'EWWW_IMAGE_OPTIMIZER_USE_LQIP' ) && ! EWWW_IMAGE_OPTIMIZER_USE_LQIP ) {
575
+ return false;
576
+ }
577
+ if ( defined( 'EASYIO_USE_LQIP' ) && ! EASYIO_USE_LQIP ) {
578
+ return false;
579
+ }
580
+ return $use_lqip;
581
+ }
582
+
583
+ /**
584
+ * Check if PIIP should be used, but allow filters to alter the option.
585
+ *
586
+ * @param bool $use_piip Whether LL should use PNG inline image placeholders.
587
+ * @return bool True to use PIIP, false to skip them.
588
+ */
589
+ function maybe_piip( $use_piip ) {
590
+ if ( defined( 'EWWW_IMAGE_OPTIMIZER_USE_PIIP' ) && ! EWWW_IMAGE_OPTIMIZER_USE_PIIP ) {
591
+ return false;
592
+ }
593
+ if ( defined( 'EASYIO_USE_PIIP' ) && ! EASYIO_USE_PIIP ) {
594
+ return false;
595
+ }
596
+ return $use_piip;
597
+ }
598
+
599
+ /**
600
+ * Check if SIIP should be used, but allow filters to alter the option.
601
+ *
602
+ * @param bool $use_siip Whether LL should use SVG inline image placeholders.
603
+ * @return bool True to use SIIP, false to skip them.
604
+ */
605
+ function maybe_siip( $use_siip ) {
606
+ if ( defined( 'EWWW_IMAGE_OPTIMIZER_USE_SIIP' ) && ! EWWW_IMAGE_OPTIMIZER_USE_SIIP ) {
607
+ return false;
608
+ }
609
+ if ( defined( 'EASYIO_USE_SIIP' ) && ! EASYIO_USE_SIIP ) {
610
+ return false;
611
+ }
612
+ return $use_siip;
613
+ }
614
+
615
+ /**
616
+ * Adds a small CSS block to hide lazyload elements for no-JS browsers.
617
+ */
618
+ function no_js_css() {
619
+ echo '<noscript><style>.lazyload[data-src]{display:none !important;}</style></noscript>';
620
+ }
621
+
622
+ /**
623
+ * Load full lazysizes script when SCRIPT_DEBUG is enabled.
624
+ */
625
+ function debug_script() {
626
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
627
+ if ( $this->is_amp() ) {
628
+ return;
629
+ }
630
+ $plugin_file = constant( strtoupper( $this->prefix ) . 'PLUGIN_FILE' );
631
+ wp_enqueue_script( 'eio-lazy-load-pre', plugins_url( '/includes/lazysizes-pre.js', $plugin_file ), array(), $this->version );
632
+ wp_enqueue_script( 'eio-lazy-load', plugins_url( '/includes/lazysizes.js', $plugin_file ), array(), $this->version );
633
+ wp_enqueue_script( 'eio-lazy-load-post', plugins_url( '/includes/lazysizes-post.js', $plugin_file ), array(), $this->version );
634
+ wp_enqueue_script( 'eio-lazy-load-uvh', plugins_url( '/includes/ls.unveilhooks.js', $plugin_file ), array(), $this->version );
635
+ if ( defined( strtoupper( $this->prefix ) . 'LAZY_PRINT' ) && constant( strtoupper( $this->prefix ) . 'LAZY_PRINT' ) ) {
636
+ wp_enqueue_script( 'eio-lazy-load-print', plugins_url( '/includes/ls.print.js', $plugin_file ), array(), $this->version );
637
+ }
638
+ wp_localize_script(
639
+ 'eio-lazy-load',
640
+ 'eio_lazy_vars',
641
+ array(
642
+ 'exactdn_domain' => ( $this->parsing_exactdn ? $this->exactdn_domain : '' ),
643
+ )
644
+ );
645
+ }
646
+
647
+ /**
648
+ * Load minified lazysizes script.
649
+ */
650
+ function min_script() {
651
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
652
+ if ( $this->is_amp() ) {
653
+ return;
654
+ }
655
+ $plugin_file = constant( strtoupper( $this->prefix ) . 'PLUGIN_FILE' );
656
+ wp_enqueue_script( 'eio-lazy-load', plugins_url( '/includes/lazysizes.min.js', $plugin_file ), array(), $this->version );
657
+ if ( defined( strtoupper( $this->prefix ) . 'LAZY_PRINT' ) && constant( strtoupper( $this->prefix ) . 'LAZY_PRINT' ) ) {
658
+ wp_enqueue_script( 'eio-lazy-load-print', plugins_url( '/includes/ls.print.min.js', $plugin_file ), array(), $this->version );
659
+ }
660
+ wp_localize_script(
661
+ 'eio-lazy-load',
662
+ 'eio_lazy_vars',
663
+ array(
664
+ 'exactdn_domain' => ( $this->parsing_exactdn ? $this->exactdn_domain : '' ),
665
+ )
666
+ );
667
+ }
668
+ }
669
+
670
+ global $eio_lazy_load;
671
+ $eio_lazy_load = new EIO_Lazy_Load();
672
+ }
classes/class-eio-page-parser.php ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Implements basic page parsing functions.
4
+ *
5
+ * @link https://ewww.io
6
+ * @package EIO
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ if ( ! class_exists( 'EIO_Page_Parser' ) ) {
14
+ /**
15
+ * HTML element and attribute parsing, replacing, etc.
16
+ */
17
+ class EIO_Page_Parser extends EIO_Base {
18
+
19
+ /**
20
+ * Allowed image extensions.
21
+ *
22
+ * @access protected
23
+ * @var array $extensions
24
+ */
25
+ protected $extensions = array(
26
+ 'gif',
27
+ 'jpg',
28
+ 'jpeg',
29
+ 'jpe',
30
+ 'png',
31
+ );
32
+
33
+ /**
34
+ * Match all images and any relevant <a> tags in a block of HTML.
35
+ *
36
+ * The hyperlinks param implies that the src attribute is required, but not the other way around.
37
+ *
38
+ * @param string $content Some HTML.
39
+ * @param bool $hyperlinks Default true. Should we include encasing hyperlinks in our search.
40
+ * @param bool $src_required Default true. Should we look only for images with src attributes.
41
+ * @return array An array of $images matches, where $images[0] is
42
+ * an array of full matches, and the link_url, img_tag,
43
+ * and img_url keys are arrays of those matches.
44
+ */
45
+ function get_images_from_html( $content, $hyperlinks = true, $src_required = true ) {
46
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
47
+ $images = array();
48
+ $unquoted_images = array();
49
+
50
+ $unquoted_pattern = '';
51
+ $search_pattern = '#(?P<img_tag><img\s[^>]*?>)#is';
52
+ if ( $hyperlinks ) {
53
+ $this->debug_message( 'using figure+hyperlink(a) patterns with src required' );
54
+ $search_pattern = '#(?:<figure[^>]*?\s+?class\s*=\s*["\'](?P<figure_class>[\w\s-]+?)["\'][^>]*?>\s*)?(?:<a[^>]*?\s+?href\s*=\s*["\'](?P<link_url>[^\s]+?)["\'][^>]*?>\s*)?(?P<img_tag><img[^>]*?\s+?src\s*=\s*("|\')(?P<img_url>(?!\4).+?)\4[^>]*?>){1}(?:\s*</a>)?#is';
55
+ $unquoted_pattern = '#(?:<figure[^>]*?\s+?class\s*=\s*(?P<figure_class>[\w-]+)[^>]*?>\s*)?(?:<a[^>]*?\s+?href\s*=\s*(?P<link_url>[^"\'][^\s>]+)[^>]*?>\s*)?(?P<img_tag><img[^>]*?\s+?src\s*=\s*(?P<img_url>[^"\'][^\s>]+)[^>]*?>){1}(?:\s*</a>)?#is';
56
+ } elseif ( $src_required ) {
57
+ $this->debug_message( 'using plain img pattern, src still required' );
58
+ $search_pattern = '#(?P<img_tag><img[^>]*?\s+?src\s*=\s*("|\')(?P<img_url>(?!\2).+?)\2[^>]*?>)#is';
59
+ $unquoted_pattern = '#(?P<img_tag><img[^>]*?\s+?src\s*=\s*(?P<img_url>[^"\'][^\s>]+)[^>]*?>)#is';
60
+ }
61
+ if ( preg_match_all( $search_pattern, $content, $images ) ) {
62
+ $this->debug_message( 'found ' . count( $images[0] ) . ' image elements with quoted pattern' );
63
+ foreach ( $images as $key => $unused ) {
64
+ // Simplify the output as much as possible.
65
+ if ( is_numeric( $key ) && $key > 0 ) {
66
+ unset( $images[ $key ] );
67
+ }
68
+ }
69
+ /* $this->debug_message( print_r( $images, true ) ); */
70
+ }
71
+ $images = array_filter( $images );
72
+ if ( $unquoted_pattern && preg_match_all( $unquoted_pattern, $content, $unquoted_images ) ) {
73
+ $this->debug_message( 'found ' . count( $unquoted_images[0] ) . ' image elements with unquoted pattern' );
74
+ foreach ( $unquoted_images as $key => $unused ) {
75
+ // Simplify the output as much as possible.
76
+ if ( is_numeric( $key ) && $key > 0 ) {
77
+ unset( $unquoted_images[ $key ] );
78
+ }
79
+ }
80
+ /* $this->debug_message( print_r( $unquoted_images, true ) ); */
81
+ }
82
+ $unquoted_images = array_filter( $unquoted_images );
83
+ if ( ! empty( $images ) && ! empty( $unquoted_images ) ) {
84
+ $this->debug_message( 'both patterns found results, merging' );
85
+ /* $this->debug_message( print_r( $images, true ) ); */
86
+ $images = array_merge_recursive( $images, $unquoted_images );
87
+ /* $this->debug_message( print_r( $images, true ) ); */
88
+ if ( ! empty( $images[0] ) && ! empty( $images[1] ) ) {
89
+ $images[0] = array_merge( $images[0], $images[1] );
90
+ unset( $images[1] );
91
+ }
92
+ } elseif ( empty( $images ) && ! empty( $unquoted_images ) ) {
93
+ $this->debug_message( 'unquoted results only, subbing in' );
94
+ $images = $unquoted_images;
95
+ }
96
+ /* $this->debug_message( print_r( $images, true ) ); */
97
+ return $images;
98
+ }
99
+
100
+ /**
101
+ * Match all images wrapped in <noscript> tags in a block of HTML.
102
+ *
103
+ * @param string $content Some HTML.
104
+ * @return array An array of $images matches, where $images[0] is
105
+ * an array of full matches, and the noscript_tag, img_tag,
106
+ * and img_url keys are arrays of those matches.
107
+ */
108
+ function get_noscript_images_from_html( $content ) {
109
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
110
+ $images = array();
111
+
112
+ if ( preg_match_all( '#(?P<noscript_tag><noscript[^>]*?>\s*)(?P<img_tag><img[^>]*?\s+?src\s*=\s*["\'](?P<img_url>[^\s]+?)["\'][^>]*?>){1}(?:\s*</noscript>)?#is', $content, $images ) ) {
113
+ foreach ( $images as $key => $unused ) {
114
+ // Simplify the output as much as possible, mostly for confirming test results.
115
+ if ( is_numeric( $key ) && $key > 0 ) {
116
+ unset( $images[ $key ] );
117
+ }
118
+ }
119
+ return $images;
120
+ }
121
+ return array();
122
+ }
123
+
124
+ /**
125
+ * Match all sources wrapped in <picture> tags in a block of HTML.
126
+ *
127
+ * @param string $content Some HTML.
128
+ * @return array An array of $pictures matches, containing full elements with ending tags.
129
+ */
130
+ function get_picture_tags_from_html( $content ) {
131
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
132
+ $pictures = array();
133
+ if ( preg_match_all( '#(?:<picture[^>]*?>\s*)(?:<source[^>]*?>)+(?:.*?</picture>)?#is', $content, $pictures ) ) {
134
+ return $pictures[0];
135
+ }
136
+ return array();
137
+ }
138
+
139
+ /**
140
+ * Match all elements by tag name in a block of HTML. Does not retrieve contents or closing tags.
141
+ *
142
+ * @param string $content Some HTML.
143
+ * @param string $tag_name The name of the elements to retrieve.
144
+ * @return array An array of $elements.
145
+ */
146
+ function get_elements_from_html( $content, $tag_name ) {
147
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
148
+ if ( ! ctype_alpha( $tag_name ) ) {
149
+ return array();
150
+ }
151
+ if ( preg_match_all( '#<' . $tag_name . '\s[^>]+?>#is', $content, $elements ) ) {
152
+ return $elements[0];
153
+ }
154
+ return array();
155
+ }
156
+
157
+ /**
158
+ * Try to determine height and width from strings WP appends to resized image filenames.
159
+ *
160
+ * @param string $src The image URL.
161
+ * @return array An array consisting of width and height.
162
+ */
163
+ function get_dimensions_from_filename( $src ) {
164
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
165
+ $width_height_string = array();
166
+ $this->debug_message( "looking for dimensions in $src" );
167
+ if ( preg_match( '#-(\d+)x(\d+)(@2x)?\.(?:' . implode( '|', $this->extensions ) . '){1}(?:\?.+)?$#i', $src, $width_height_string ) ) {
168
+ $width = (int) $width_height_string[1];
169
+ $height = (int) $width_height_string[2];
170
+
171
+ if ( strpos( $src, '@2x' ) ) {
172
+ $width = 2 * $width;
173
+ $height = 2 * $height;
174
+ }
175
+ if ( $width && $height ) {
176
+ $this->debug_message( "found w$width h$height" );
177
+ return array( $width, $height );
178
+ }
179
+ }
180
+ return array( false, false );
181
+ }
182
+
183
+ /**
184
+ * Get the width from an image element.
185
+ *
186
+ * @param string $img The full image element.
187
+ * @return string The width found or an empty string.
188
+ */
189
+ public function get_img_width( $img ) {
190
+ $width = $this->get_attribute( $img, 'width' );
191
+ // Then check for an inline max-width directive.
192
+ $style = $this->get_attribute( $img, 'style' );
193
+ if ( $style && preg_match( '#max-width:\s?(\d+)px#', $style, $max_width_string ) ) {
194
+ if ( $max_width_string[1] && ( ! $width || $max_width_string[1] < $width ) ) {
195
+ $width = $max_width_string[1];
196
+ }
197
+ }
198
+ return $width;
199
+ }
200
+
201
+ /**
202
+ * Get an attribute from an HTML element.
203
+ *
204
+ * @param string $element The HTML element to parse.
205
+ * @param string $name The name of the attribute to search for.
206
+ * @return string The value of the attribute, or an empty string if not found.
207
+ */
208
+ function get_attribute( $element, $name ) {
209
+ // Don't forget, back references cannot be used in character classes.
210
+ if ( preg_match( '#\s' . $name . '\s*=\s*("|\')((?!\1).+?)\1#is', $element, $attr_matches ) ) {
211
+ if ( ! empty( $attr_matches[2] ) ) {
212
+ return $attr_matches[2];
213
+ }
214
+ }
215
+ // If there were not any matches with quotes, look for unquoted attributes, no spaces or quotes allowed.
216
+ if ( preg_match( '#\s' . $name . '\s*=\s*([^"\'][^\s>]+)#is', $element, $attr_matches ) ) {
217
+ if ( ! empty( $attr_matches[1] ) ) {
218
+ return $attr_matches[1];
219
+ }
220
+ }
221
+ return '';
222
+ }
223
+
224
+ /**
225
+ * Get a CSS background-image URL.
226
+ *
227
+ * @param string $attribute An element's style attribute. Do not pass a full HTML element.
228
+ * @return string The URL from the background/background-image property.
229
+ */
230
+ function get_background_image_url( $attribute ) {
231
+ if ( ( false !== strpos( $attribute, 'background:' ) || false !== strpos( $attribute, 'background-image:' ) ) && false !== strpos( $attribute, 'url(' ) ) {
232
+ if ( preg_match( '#url\(([^)]+)\)#', $attribute, $prop_match ) ) {
233
+ return trim( html_entity_decode( $prop_match[1], ENT_QUOTES | ENT_HTML401 ), "'\"\t\n\r " );
234
+ }
235
+ }
236
+ return '';
237
+ }
238
+
239
+ /**
240
+ * Set an attribute on an HTML element.
241
+ *
242
+ * @param string $element The HTML element to modify. Passed by reference.
243
+ * @param string $name The name of the attribute to set.
244
+ * @param string $value The value of the attribute to set.
245
+ * @param bool $replace Default false. True to replace, false to append.
246
+ */
247
+ function set_attribute( &$element, $name, $value, $replace = false ) {
248
+ if ( 'class' === $name ) {
249
+ $element = preg_replace( "#\s$name\s+[^=]#", ' ', $element );
250
+ }
251
+ $element = preg_replace( "#\s$name=\"\"#", ' ', $element );
252
+ $value = trim( $value );
253
+ if ( $replace ) {
254
+ // Don't forget, back references cannot be used in character classes.
255
+ $new_element = preg_replace( '#\s' . $name . '\s*=\s*("|\')(?!\1).*?\1#is', " $name=$1$value$1", $element );
256
+ if ( strpos( $new_element, "$name=" ) ) {
257
+ $element = $new_element;
258
+ return;
259
+ }
260
+ $element = preg_replace( '#\s' . $name . '\s*=\s*[^"\'][^\s>]+#is', ' ', $element );
261
+ }
262
+ $closing = ' />';
263
+ if ( false === strpos( $element, '/>' ) ) {
264
+ $closing = '>';
265
+ }
266
+ if ( false === strpos( $value, '"' ) ) {
267
+ $element = rtrim( $element, $closing ) . " $name=\"$value\"$closing";
268
+ return;
269
+ }
270
+ $element = rtrim( $element, $closing ) . " $name='$value'$closing";
271
+ }
272
+
273
+ /**
274
+ * Remove an attribute from an HTML element.
275
+ *
276
+ * @param string $element The HTML element to modify. Passed by reference.
277
+ * @param string $name The name of the attribute to remove.
278
+ */
279
+ function remove_attribute( &$element, $name ) {
280
+ // Don't forget, back references cannot be used in character classes.
281
+ $element = preg_replace( '#\s' . $name . '\s*=\s*("|\')(?!\1).+?\1#is', ' ', $element );
282
+ $element = preg_replace( '#\s' . $name . '\s*=\s*[^"\'][^\s>]+#is', ' ', $element );
283
+ }
284
+
285
+ /**
286
+ * Remove the background image URL from a style attribute.
287
+ *
288
+ * @param string $attribute The element's style attribute to modify.
289
+ * @return string The style attribute with any image url removed.
290
+ */
291
+ function remove_background_image( $attribute ) {
292
+ if ( false !== strpos( $attribute, 'background:' ) && false !== strpos( $attribute, 'url(' ) ) {
293
+ $attribute = preg_replace( '#\s?url\([^)]+\)#', '', $attribute );
294
+ }
295
+ if ( false !== strpos( $attribute, 'background-image:' ) && false !== strpos( $attribute, 'url(' ) ) {
296
+ $attribute = preg_replace( '#background-image:\s*url\([^)]+\);?#', '', $attribute );
297
+ }
298
+ return $attribute;
299
+ }
300
+
301
+ /**
302
+ * A wrapper for PHP's parse_url, prepending assumed scheme for network path
303
+ * URLs. PHP versions 5.4.6 and earlier do not correctly parse without scheme.
304
+ *
305
+ * @param string $url The URL to parse.
306
+ * @param integer $component Retrieve specific URL component.
307
+ * @return mixed Result of parse_url.
308
+ */
309
+ function parse_url( $url, $component = -1 ) {
310
+ if ( 0 === strpos( $url, '//' ) ) {
311
+ $url = ( is_ssl() ? 'https:' : 'http:' ) . $url;
312
+ }
313
+ if ( false === strpos( $url, 'http' ) && '/' !== substr( $url, 0, 1 ) ) {
314
+ $url = ( is_ssl() ? 'https://' : 'http://' ) . $url;
315
+ }
316
+ // Because encoded ampersands in the filename break things.
317
+ $url = str_replace( '&#038;', '&', $url );
318
+ return parse_url( $url, $component );
319
+ }
320
+ }
321
+ }
classes/class-ewww-flag.php CHANGED
@@ -170,6 +170,38 @@ if ( ! class_exists( 'EWWW_Flag' ) ) {
170
  echo '</div></div>';
171
  }
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  /**
174
  * Prepares the bulk operation and includes the necessary javascript files.
175
  *
@@ -181,15 +213,15 @@ if ( ! class_exists( 'EWWW_Flag' ) ) {
181
  function ewww_flag_bulk_script( $hook ) {
182
  ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
183
  // Make sure we are being hooked from a valid location.
184
- if ( 'flagallery_page_flag-bulk-optimize' !== $hook && 'flagallery_page_flag-manage-gallery' !== $hook ) {
185
  return;
186
  }
187
  // If there is no requested bulk action, do nothing.
188
- if ( 'flagallery_page_flag-manage-gallery' === $hook && ( empty( $_REQUEST['bulkaction'] ) || 0 === strpos( $_REQUEST['bulkaction'], 'bulk_optimize' ) ) ) {
189
  return;
190
  }
191
  // If there is no media to optimize, do nothing.
192
- if ( 'flagallery_page_flag-manage-gallery' === $hook && ( empty( $_REQUEST['doaction'] ) || ! is_array( $_REQUEST['doaction'] ) ) ) {
193
  return;
194
  }
195
  $ids = null;
@@ -234,7 +266,7 @@ if ( ! class_exists( 'EWWW_Flag' ) ) {
234
  } elseif ( ! empty( $resume ) ) {
235
  // If there is an operation to resume, get those IDs from the db.
236
  $ids = get_option( 'ewww_image_optimizer_bulk_flag_attachments' );
237
- } elseif ( 'flagallery_page_flag-bulk-optimize' === $hook ) {
238
  // Otherwise, if we are on the main bulk optimize page, just get all the IDs available.
239
  global $wpdb;
240
  $ids = $wpdb->get_col( "SELECT pid FROM $wpdb->flagpictures ORDER BY sortorder ASC" );
@@ -636,7 +668,7 @@ if ( ! class_exists( 'EWWW_Flag' ) ) {
636
  */
637
  function ewww_flag_manual_actions_script( $hook ) {
638
  ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
639
- if ( 'flagallery_page_flag-manage-gallery' !== $hook ) {
640
  return;
641
  }
642
  if ( ! current_user_can( apply_filters( 'ewww_image_optimizer_manual_permissions', '' ) ) ) {
170
  echo '</div></div>';
171
  }
172
 
173
+ /**
174
+ * Checks the hook suffix to see if this is the individual gallery management page.
175
+ *
176
+ * @param string $hook The hook suffix of the page.
177
+ * @returns boolean True for the gallery page, false anywhere else.
178
+ */
179
+ function is_gallery_page( $hook ) {
180
+ if ( 'flagallery_page_flag-manage-gallery' === $hook ) {
181
+ return true;
182
+ }
183
+ if ( false !== strpos( $hook, 'page_flag-manage-gallery' ) ) {
184
+ return true;
185
+ }
186
+ return false;
187
+ }
188
+
189
+ /**
190
+ * Checks the hook suffix to see if this is the bulk optimize page.
191
+ *
192
+ * @param string $hook The hook suffix of the page.
193
+ * @returns boolean True for the bulk page, false anywhere else.
194
+ */
195
+ function is_bulk_page( $hook ) {
196
+ if ( 'flagallery_page_flag-bulk-optimize' === $hook ) {
197
+ return true;
198
+ }
199
+ if ( false !== strpos( $hook, 'page_flag-bulk-optimize' ) ) {
200
+ return true;
201
+ }
202
+ return false;
203
+ }
204
+
205
  /**
206
  * Prepares the bulk operation and includes the necessary javascript files.
207
  *
213
  function ewww_flag_bulk_script( $hook ) {
214
  ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
215
  // Make sure we are being hooked from a valid location.
216
+ if ( ! $this->is_bulk_page( $hook ) && ! $this->is_gallery_page( $hook ) ) {
217
  return;
218
  }
219
  // If there is no requested bulk action, do nothing.
220
+ if ( $this->is_gallery_page( $hook ) && ( empty( $_REQUEST['bulkaction'] ) || 0 === strpos( $_REQUEST['bulkaction'], 'bulk_optimize' ) ) ) {
221
  return;
222
  }
223
  // If there is no media to optimize, do nothing.
224
+ if ( $this->is_gallery_page( $hook ) && ( empty( $_REQUEST['doaction'] ) || ! is_array( $_REQUEST['doaction'] ) ) ) {
225
  return;
226
  }
227
  $ids = null;
266
  } elseif ( ! empty( $resume ) ) {
267
  // If there is an operation to resume, get those IDs from the db.
268
  $ids = get_option( 'ewww_image_optimizer_bulk_flag_attachments' );
269
+ } elseif ( $this->is_bulk_page( $hook ) ) {
270
  // Otherwise, if we are on the main bulk optimize page, just get all the IDs available.
271
  global $wpdb;
272
  $ids = $wpdb->get_col( "SELECT pid FROM $wpdb->flagpictures ORDER BY sortorder ASC" );
668
  */
669
  function ewww_flag_manual_actions_script( $hook ) {
670
  ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
671
+ if ( ! $this->is_gallery_page( $hook ) ) {
672
  return;
673
  }
674
  if ( ! current_user_can( apply_filters( 'ewww_image_optimizer_manual_permissions', '' ) ) ) {
classes/class-ewww-nextgen.php CHANGED
@@ -588,7 +588,7 @@ if ( ! class_exists( 'EWWW_Nextgen' ) ) {
588
  /**
589
  * Output the html for the bulk optimize page.
590
  *
591
- * @global string $ewww_debug In-memory debug log.
592
  */
593
  function ewww_ngg_bulk_preview() {
594
  if ( ! empty( $_REQUEST['doaction'] ) ) {
@@ -679,8 +679,8 @@ if ( ! class_exists( 'EWWW_Nextgen' ) ) {
679
  }
680
  echo '</div></div>';
681
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
682
- global $ewww_debug;
683
- echo '<p><strong>' . esc_html__( 'Debugging Information', 'ewww-image-optimizer' ) . ':</strong></p><div style="border:1px solid #e5e5e5;background:#fff;overflow:auto;height:300px;width:800px;">' . $ewww_debug . '</div>';
684
  }
685
  return;
686
  }
588
  /**
589
  * Output the html for the bulk optimize page.
590
  *
591
+ * @global string $eio_debug In-memory debug log.
592
  */
593
  function ewww_ngg_bulk_preview() {
594
  if ( ! empty( $_REQUEST['doaction'] ) ) {
679
  }
680
  echo '</div></div>';
681
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
682
+ global $eio_debug;
683
+ echo '<p><strong>' . esc_html__( 'Debugging Information', 'ewww-image-optimizer' ) . ':</strong></p><div style="border:1px solid #e5e5e5;background:#fff;overflow:auto;height:300px;width:800px;">' . $eio_debug . '</div>';
684
  }
685
  return;
686
  }
classes/class-ewwwio-hs-beacon.php DELETED
@@ -1,110 +0,0 @@
1
- <?php
2
- /**
3
- * Help integration functions for embedding the HS Beacon for users that have opted in.
4
- *
5
- * @package EWWW_Image_Optimizer
6
- * @since 3.5.1
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * Embed HS Beacon to give users more help when they need it.
15
- *
16
- * @since 3.5.1
17
- */
18
- class EWWWIO_HS_Beacon {
19
-
20
- /**
21
- * Get things going
22
- */
23
- public function __construct() {
24
- add_action( 'admin_action_ewww_opt_into_hs_beacon', array( $this, 'check_for_optin' ) );
25
- add_action( 'admin_action_ewww_opt_out_of_hs_beacon', array( $this, 'check_for_optout' ) );
26
- }
27
-
28
- /**
29
- * Check for a new opt-in on settings save
30
- *
31
- * @param bool $input The enable_help setting.
32
- * @return bool The unaltered setting.
33
- */
34
- public function check_for_settings_optin( $input ) {
35
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
36
- if ( isset( $_POST['ewww_image_optimizer_enable_help'] ) && $_POST['ewww_image_optimizer_enable_help'] ) {
37
- ewww_image_optimizer_set_option( 'ewww_image_optimizer_tracking_notice', 1 );
38
- }
39
- return $input;
40
- }
41
-
42
- /**
43
- * Check for a new opt-in via the admin notice
44
- */
45
- public function check_for_optin() {
46
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
47
- ewww_image_optimizer_set_option( 'ewww_image_optimizer_enable_help', 1 );
48
- ewww_image_optimizer_set_option( 'ewww_image_optimizer_enable_help_notice', 1 );
49
- wp_redirect( remove_query_arg( 'action', wp_get_referer() ) );
50
- exit;
51
- }
52
-
53
- /**
54
- * Check for a new opt-out via the admin notice
55
- */
56
- public function check_for_optout() {
57
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
58
- delete_option( 'ewww_image_optimizer_enable_help' );
59
- delete_network_option( null, 'ewww_image_optimizer_enable_help' );
60
- ewww_image_optimizer_set_option( 'ewww_image_optimizer_enable_help_notice', 1 );
61
- wp_redirect( remove_query_arg( 'action', wp_get_referer() ) );
62
- exit;
63
- }
64
-
65
- /**
66
- * Display the admin notice to users that have not opted-in or out
67
- *
68
- * @access public
69
- * @param string $network_class A string that indicates where this is being displayed.
70
- * @return void
71
- */
72
- public function admin_notice( $network_class = '' ) {
73
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
74
- $hide_notice = ewww_image_optimizer_get_option( 'ewww_image_optimizer_enable_help_notice' );
75
- if ( 'network-multisite' === $network_class && get_site_option( 'ewww_image_optimizer_allow_multisite_override' ) ) {
76
- return;
77
- }
78
- if ( 'network-singlesite' === $network_class && ! get_site_option( 'ewww_image_optimizer_allow_multisite_override' ) ) {
79
- return;
80
- }
81
-
82
- if ( $hide_notice ) {
83
- return;
84
- }
85
- if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_enable_help' ) ) {
86
- return;
87
- }
88
-
89
- if ( ! current_user_can( 'manage_options' ) ) {
90
- return;
91
- }
92
-
93
- if ( ! function_exists( 'is_plugin_active_for_network' ) && is_multisite() ) {
94
- // Need to include the plugin library for the is_plugin_active function.
95
- require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
96
- }
97
- if ( is_multisite() && is_plugin_active_for_network( EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE_REL ) && ! current_user_can( 'manage_network_options' ) ) {
98
- return;
99
- }
100
- $optin_url = 'admin.php?action=ewww_opt_into_hs_beacon';
101
- $optout_url = 'admin.php?action=ewww_opt_out_of_hs_beacon';
102
- echo '<div class="updated"><p>';
103
- esc_html_e( 'Enable the support beacon, which gives you access to documentation and our support team right from your WordPress dashboard. To assist you more efficiently, we may collect the current url, IP address, browser/device information, and debugging information.', 'ewww-image-optimizer' );
104
- echo '&nbsp;<a href="' . esc_url( $optin_url ) . '" class="button-secondary">' . esc_html__( 'Allow', 'ewww-image-optimizer' ) . '</a>';
105
- echo '&nbsp;<a href="' . esc_url( $optout_url ) . '" class="button-secondary">' . esc_html__( 'Do not allow', 'ewww-image-optimizer' ) . '</a>';
106
- echo '</p></div>';
107
- }
108
-
109
- }
110
- $ewwwio_hs_beacon = new EWWWIO_HS_Beacon;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/class-ewwwio-lazy-load.php DELETED
@@ -1,623 +0,0 @@
1
- <?php
2
- /**
3
- * Implements Lazy Loading using page parsing and JS functionality.
4
- *
5
- * @link https://ewww.io
6
- * @package EWWW_Image_Optimizer
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * Enables EWWW IO to filter the page content and replace img elements with Lazy Load markup.
15
- */
16
- class EWWWIO_Lazy_Load extends EWWWIO_Page_Parser {
17
-
18
- /**
19
- * Base64-encoded placeholder image.
20
- *
21
- * @access protected
22
- * @var string $placeholder_src
23
- */
24
- protected $placeholder_src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
25
-
26
- /**
27
- * Indicates if we are filtering ExactDN urls.
28
- *
29
- * @access protected
30
- * @var bool $parsing_exactdn
31
- */
32
- protected $parsing_exactdn = false;
33
-
34
- /**
35
- * The folder to store any PIIPs.
36
- *
37
- * @access protected
38
- * @var string $piip_folder
39
- */
40
- protected $piip_folder = WP_CONTENT_DIR . '/ewww/lazy/';
41
-
42
- /**
43
- * Whether to allow PIIPs.
44
- *
45
- * @access public
46
- * @var bool $allow_piip
47
- */
48
- public $allow_piip = true;
49
-
50
- /**
51
- * Register (once) actions and filters for Lazy Load.
52
- */
53
- function __construct() {
54
- ewwwio_debug_message( 'firing up lazy load' );
55
- global $ewwwio_lazy_load;
56
- if ( is_object( $ewwwio_lazy_load ) ) {
57
- ewwwio_debug_message( 'you are doing it wrong' );
58
- return 'you are doing it wrong';
59
- }
60
-
61
- add_action( 'wp_head', array( $this, 'no_js_css' ) );
62
- add_filter( 'ewww_image_optimizer_filter_page_output', array( $this, 'filter_page_output' ), 15 );
63
-
64
- if ( class_exists( 'ExactDN' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
65
- global $exactdn;
66
- $this->exactdn_domain = $exactdn->get_exactdn_domain();
67
- if ( $this->exactdn_domain ) {
68
- $this->parsing_exactdn = true;
69
- ewwwio_debug_message( 'parsing an exactdn page' );
70
- }
71
- }
72
-
73
- if ( ! is_dir( $this->piip_folder ) ) {
74
- $this->allow_piip = wp_mkdir_p( $this->piip_folder ) && ewww_image_optimizer_gd_support();
75
- } else {
76
- $this->allow_piip = is_writable( $this->piip_folder ) && ewww_image_optimizer_gd_support();
77
- }
78
-
79
- // Filter early, so that others at the default priority take precendence.
80
- add_filter( 'ewww_image_optimizer_use_lqip', array( $this, 'maybe_lqip' ), 9 );
81
- add_filter( 'ewww_image_optimizer_use_piip', array( $this, 'maybe_piip' ), 9 );
82
- add_filter( 'ewww_image_optimizer_use_siip', array( $this, 'maybe_siip' ), 9 );
83
-
84
- // Overrides for admin-ajax images.
85
- add_filter( 'ewww_image_optimizer_admin_allow_lazyload', array( $this, 'allow_admin_lazyload' ) );
86
-
87
- // Load the appropriate JS.
88
- if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
89
- // Load the non-minified and separate versions of the lazy load scripts.
90
- add_action( 'wp_enqueue_scripts', array( $this, 'debug_script' ) );
91
- } else {
92
- // Load the minified, combined version of the lazy load script.
93
- add_action( 'wp_enqueue_scripts', array( $this, 'min_script' ) );
94
- }
95
- }
96
-
97
- /**
98
- * Starts an output buffer and registers the callback function to do WebP replacement.
99
- */
100
- function buffer_start() {
101
- ob_start( array( $this, 'filter_page_output' ) );
102
- }
103
-
104
- /**
105
- * Replaces images within a srcset attribute, just a placeholder at the moment.
106
- *
107
- * @param string $srcset A valid srcset attribute from an img element.
108
- * @return bool|string False if no changes were made, or the new srcset if any WebP images replaced the originals.
109
- */
110
- function srcset_replace( $srcset ) {
111
- return $srcset;
112
- }
113
-
114
- /**
115
- * Search for img elements and rewrite them for Lazy Load with fallback to noscript elements.
116
- *
117
- * @param string $buffer The full HTML page generated since the output buffer was started.
118
- * @return string The altered buffer containing the full page with Lazy Load attributes.
119
- */
120
- function filter_page_output( $buffer ) {
121
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
122
- // Don't foul up the admin side of things, unless a plugin needs to.
123
- if ( is_admin() &&
124
- /**
125
- * Provide plugins a way of running Lazy Load for images in the WordPress Dashboard (wp-admin).
126
- *
127
- * @param bool false Allow Lazy Load to run on the Dashboard. Default to false.
128
- */
129
- false === apply_filters( 'ewww_image_optimizer_admin_allow_lazyload', false )
130
- ) {
131
- ewwwio_debug_message( 'is_admin' );
132
- return $buffer;
133
- }
134
- // Don't lazy load in these cases...
135
- $uri = $_SERVER['REQUEST_URI'];
136
- if (
137
- empty( $buffer ) ||
138
- ! empty( $_GET['cornerstone'] ) ||
139
- strpos( $uri, 'cornerstone-endpoint' ) !== false ||
140
- ! empty( $_GET['ct_builder'] ) ||
141
- ! empty( $_GET['elementor-preview'] ) ||
142
- ! empty( $_GET['et_fb'] ) ||
143
- ! empty( $_GET['tatsu'] ) ||
144
- ( ! empty( $_POST['action'] ) && 'tatsu_get_concepts' === $_POST['action'] ) ||
145
- ! apply_filters( 'ewww_image_optimizer_do_lazyload', true ) ||
146
- is_feed() ||
147
- is_preview() ||
148
- ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
149
- wp_script_is( 'twentytwenty-twentytwenty', 'enqueued' ) ||
150
- preg_match( '/^<\?xml/', $buffer )
151
- ) {
152
- if ( empty( $buffer ) ) {
153
- ewwwio_debug_message( 'empty buffer' );
154
- }
155
- if ( ! empty( $_GET['cornerstone'] ) || strpos( $uri, 'cornerstone-endpoint' ) !== false ) {
156
- ewwwio_debug_message( 'cornerstone editor' );
157
- }
158
- if ( ! empty( $_GET['ct_builder'] ) ) {
159
- ewwwio_debug_message( 'oxygen builder' );
160
- }
161
- if ( ! empty( $_GET['elementor-preview'] ) ) {
162
- ewwwio_debug_message( 'elementor preview' );
163
- }
164
- if ( ! empty( $_GET['et_fb'] ) ) {
165
- ewwwio_debug_message( 'et_fb' );
166
- }
167
- if ( ! empty( $_GET['tatsu'] ) || ( ! empty( $_POST['action'] ) && 'tatsu_get_concepts' === $_POST['action'] ) ) {
168
- ewwwio_debug_message( 'tatsu' );
169
- }
170
- if ( ! apply_filters( 'ewww_image_optimizer_do_lazyload', true ) ) {
171
- ewwwio_debug_message( 'do_lazyload short-circuit' );
172
- }
173
- if ( is_feed() ) {
174
- ewwwio_debug_message( 'is_feed' );
175
- }
176
- if ( is_preview() ) {
177
- ewwwio_debug_message( 'is_preview' );
178
- }
179
- if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
180
- ewwwio_debug_message( 'rest request' );
181
- }
182
- if ( wp_script_is( 'twentytwenty-twentytwenty', 'enqueued' ) ) {
183
- ewwwio_debug_message( 'twentytwenty enqueued' );
184
- }
185
- if ( preg_match( '/^<\?xml/', $buffer ) ) {
186
- ewwwio_debug_message( 'not html, xml tag found' );
187
- }
188
- if ( strpos( $buffer, 'amp-boilerplate' ) ) {
189
- ewwwio_debug_message( 'AMP page processing' );
190
- }
191
- return $buffer;
192
- }
193
-
194
- $above_the_fold = apply_filters( 'ewww_image_optimizer_lazy_fold', 0 );
195
- $images_processed = 0;
196
-
197
- $images = $this->get_images_from_html( preg_replace( '/<(noscript|script).*?\/\1>/s', '', $buffer ), false );
198
- if ( ! empty( $images[0] ) && ewww_image_optimizer_iterable( $images[0] ) ) {
199
- foreach ( $images[0] as $index => $image ) {
200
- $images_processed++;
201
- if ( $images_processed <= $above_the_fold ) {
202
- continue;
203
- }
204
- $file = $images['img_url'][ $index ];
205
- ewwwio_debug_message( "parsing an image: $file" );
206
- if ( $this->validate_image_tag( $image ) ) {
207
- ewwwio_debug_message( 'found a valid image tag' );
208
- $orig_img = $image;
209
- $noscript = '<noscript>' . $orig_img . '</noscript>';
210
- $this->set_attribute( $image, 'data-src', $file, true );
211
- $srcset = $this->get_attribute( $image, 'srcset' );
212
-
213
- $placeholder_src = $this->placeholder_src;
214
- if ( false === strpos( $file, 'nggid' ) && ! preg_match( '#\.svg(\?|$)#', $file ) && apply_filters( 'ewww_image_optimizer_use_lqip', true, $file ) && $this->parsing_exactdn && strpos( $file, $this->exactdn_domain ) ) {
215
- ewwwio_debug_message( 'using lqip' );
216
- $placeholder_src = add_query_arg( array( 'lazy' => 1 ), $file );
217
- } elseif ( $this->allow_piip && $srcset && apply_filters( 'ewww_image_optimizer_use_piip', true, $file ) ) {
218
- ewwwio_debug_message( 'trying piip' );
219
- // Get image dimensions for PNG placeholder.
220
- list( $width, $height ) = $this->get_dimensions_from_filename( $file );
221
-
222
- $width_attr = $this->get_attribute( $image, 'width' );
223
- $height_attr = $this->get_attribute( $image, 'height' );
224
-
225
- // Can't use a relative width or height, so unset the dimensions in favor of not breaking things.
226
- if ( false !== strpos( $width_attr, '%' ) || false !== strpos( $height_attr, '%' ) ) {
227
- $width_attr = false;
228
- $height_attr = false;
229
- }
230
-
231
- if ( false === $width || false === $height ) {
232
- $width = $width_attr;
233
- $height = $height_attr;
234
- }
235
-
236
- // Falsify them if empty.
237
- $width = $width ? (int) $width : false;
238
- $height = $height ? (int) $height : false;
239
- if ( $width && $height ) {
240
- ewwwio_debug_message( "creating piip of $width x $height" );
241
- $placeholder_src = $this->create_piip( $width, $height );
242
- }
243
- } elseif ( apply_filters( 'ewww_image_optimizer_use_siip', true, $file ) ) {
244
- ewwwio_debug_message( 'trying siip' );
245
- $width = $this->get_attribute( $image, 'width' );
246
- $height = $this->get_attribute( $image, 'height' );
247
-
248
- // Can't use a relative width or height, so unset the dimensions in favor of not breaking things.
249
- if ( false !== strpos( $width, '%' ) || false !== strpos( $height, '%' ) ) {
250
- $width = false;
251
- $height = false;
252
- }
253
-
254
- // Falsify them if empty.
255
- $width = $width ? (int) $width : false;
256
- $height = $height ? (int) $height : false;
257
- if ( $width && $height ) {
258
- $placeholder_src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 $width $height'%3E%3C/svg%3E";
259
- }
260
- }
261
- ewwwio_debug_message( "current placeholder is $placeholder_src" );
262
-
263
- if ( $srcset ) {
264
- $placeholder_src = apply_filters( 'ewww_image_optimizer_lazy_placeholder', $placeholder_src, $image );
265
- if ( strpos( $placeholder_src, '64,R0lGOD' ) ) {
266
- $this->set_attribute( $image, 'srcset', $placeholder_src, true );
267
- $this->remove_attribute( $image, 'src' );
268
- } else {
269
- $this->set_attribute( $image, 'src', $placeholder_src, true );
270
- $this->remove_attribute( $image, 'srcset' );
271
- }
272
- $this->set_attribute( $image, 'data-srcset', $srcset, true );
273
- $srcset_sizes = $this->get_attribute( $image, 'sizes' );
274
- // Return false on this filter to disable automatic sizes calculation,
275
- // or use the sizes value passed via the filter to conditionally disable it.
276
- if ( apply_filters( 'ewww_image_optimizer_lazy_responsive', $srcset_sizes ) ) {
277
- $this->set_attribute( $image, 'data-sizes', 'auto', true );
278
- $this->remove_attribute( $image, 'sizes' );
279
- }
280
- } else {
281
- $this->set_attribute( $image, 'src', $placeholder_src, true );
282
- }
283
- $this->set_attribute( $image, 'class', $this->get_attribute( $image, 'class' ) . ' lazyload', true );
284
- $buffer = str_replace( $orig_img, $image . $noscript, $buffer );
285
- }
286
- } // End foreach().
287
- } // End if().
288
- // Process background images on div elements.
289
- $buffer = $this->parse_background_images( $buffer, 'div' );
290
- // Process background images on li elements.
291
- $buffer = $this->parse_background_images( $buffer, 'li' );
292
- // Process background images on li elements.
293
- $buffer = $this->parse_background_images( $buffer, 'span' );
294
- // Images listed as picture/source elements. Mostly for NextGEN, but should work anywhere.
295
- $pictures = $this->get_picture_tags_from_html( $buffer );
296
- if ( ewww_image_optimizer_iterable( $pictures ) ) {
297
- foreach ( $pictures as $index => $picture ) {
298
- $sources = $this->get_elements_from_html( $picture, 'source' );
299
- if ( ewww_image_optimizer_iterable( $sources ) ) {
300
- foreach ( $sources as $source ) {
301
- if ( false !== strpos( $source, 'data-src' ) ) {
302
- continue;
303
- }
304
- ewwwio_debug_message( "parsing a picture source: $source" );
305
- $srcset = $this->get_attribute( $source, 'srcset' );
306
- if ( $srcset ) {
307
- ewwwio_debug_message( 'found srcset in source' );
308
- $lazy_source = $source;
309
- $this->set_attribute( $lazy_source, 'data-srcset', $srcset );
310
- $this->set_attribute( $lazy_source, 'srcset', $this->placeholder_src, true );
311
- $picture = str_replace( $source, $lazy_source, $picture );
312
- }
313
- }
314
- if ( $picture !== $pictures[ $index ] ) {
315
- ewwwio_debug_message( 'lazified sources for picture element' );
316
- $buffer = str_replace( $pictures[ $index ], $picture, $buffer );
317
- }
318
- }
319
- }
320
- }
321
- // Video elements, looking for poster attributes that are images.
322
- /* $videos = $this->get_elements_from_html( $buffer, 'video' ); */
323
- $videos = '';
324
- if ( ewww_image_optimizer_iterable( $videos ) ) {
325
- foreach ( $videos as $index => $video ) {
326
- ewwwio_debug_message( 'parsing a video element' );
327
- $file = $this->get_attribute( $video, 'poster' );
328
- if ( $file ) {
329
- ewwwio_debug_message( "checking webp for video poster: $file" );
330
- if ( $this->validate_image_tag( $file ) ) {
331
- $this->set_attribute( $video, 'data-poster-webp', $this->placeholder_src );
332
- $this->set_attribute( $video, 'data-poster-image', $file );
333
- $this->remove_attribute( $video, 'poster' );
334
- ewwwio_debug_message( "found webp for video poster: $file" );
335
- $buffer = str_replace( $videos[ $index ], $video, $buffer );
336
- }
337
- }
338
- }
339
- }
340
- ewwwio_debug_message( 'all done parsing page for lazy' );
341
- if ( true ) { // Set to true for extra logging.
342
- ewww_image_optimizer_debug_log();
343
- }
344
- return $buffer;
345
- }
346
-
347
- /**
348
- * Parse elements of a given type for inline CSS background images.
349
- *
350
- * @param string $buffer The HTML content to parse.
351
- * @param string $tag_type The type of HTML tag to look for.
352
- * @return string The modified content with LL markup.
353
- */
354
- function parse_background_images( $buffer, $tag_type ) {
355
- $elements = $this->get_elements_from_html( $buffer, $tag_type );
356
- if ( ewww_image_optimizer_iterable( $elements ) ) {
357
- foreach ( $elements as $index => $element ) {
358
- ewwwio_debug_message( "parsing a $tag_type" );
359
- if ( false === strpos( $element, 'background:' ) && false === strpos( $element, 'background-image:' ) ) {
360
- continue;
361
- }
362
- ewwwio_debug_message( 'element contains background/background-image:' );
363
- if ( ! $this->validate_bgimage_tag( $element ) ) {
364
- continue;
365
- }
366
- ewwwio_debug_message( 'element is valid' );
367
- $style = $this->get_attribute( $element, 'style' );
368
- if ( empty( $style ) ) {
369
- continue;
370
- }
371
- ewwwio_debug_message( "checking style attr for background-image: $style" );
372
- $bg_image_url = $this->get_background_image_url( $style );
373
- if ( $bg_image_url ) {
374
- ewwwio_debug_message( 'bg-image url found' );
375
- $new_style = $this->remove_background_image( $style );
376
- if ( $style !== $new_style ) {
377
- ewwwio_debug_message( 'style modified, continuing' );
378
- $this->set_attribute( $element, 'class', $this->get_attribute( $element, 'class' ) . ' lazyload', true );
379
- $this->set_attribute( $element, 'data-bg', $bg_image_url );
380
- $element = str_replace( $style, $new_style, $element );
381
- }
382
- }
383
- if ( $element !== $elements[ $index ] ) {
384
- ewwwio_debug_message( "$tag_type modified, replacing in html source" );
385
- $buffer = str_replace( $elements[ $index ], $element, $buffer );
386
- }
387
- }
388
- }
389
- return $buffer;
390
- }
391
-
392
- /**
393
- * Checks if the tag is allowed to be lazy loaded.
394
- *
395
- * @param string $image The image (img) tag.
396
- * @return bool True if the tag is allowed, false otherwise.
397
- */
398
- function validate_image_tag( $image ) {
399
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
400
- if (
401
- strpos( $image, 'base64,R0lGOD' ) ||
402
- strpos( $image, 'lazy-load/images/1x1' ) ||
403
- strpos( $image, '/assets/images/' )
404
- ) {
405
- ewwwio_debug_message( 'lazy load placeholder detected' );
406
- return false;
407
- }
408
-
409
- // Skip inline data URIs.
410
- $image_src = $this->get_attribute( $image, 'src' );
411
- if ( false !== strpos( $image_src, 'data:image' ) ) {
412
- return false;
413
- }
414
- // Ignore 0-size Pinterest schema images.
415
- if ( strpos( $image, 'data-pin-description=' ) && strpos( $image, 'width="0" height="0"' ) ) {
416
- return false;
417
- }
418
-
419
- // Ignore native lazy loading images.
420
- $loading_attr = $this->get_attribute( $image, 'loading' );
421
- if ( $loading_attr && in_array( trim( $loading_attr ), array( 'auto', 'eager', 'lazy' ), true ) ) {
422
- return false;
423
- }
424
-
425
- $exclusions = apply_filters(
426
- 'ewww_image_optimizer_lazy_exclusions',
427
- array(
428
- 'class="ls-bg',
429
- 'class="ls-l',
430
- 'class="rev-slidebg',
431
- 'data-bgposition=',
432
- 'data-envira-src=',
433
- 'data-lazy=',
434
- 'data-lazy-original=',
435
- 'data-lazy-src=',
436
- 'data-lazy-srcset=',
437
- 'data-lazyload=',
438
- 'data-lazysrc=',
439
- 'data-no-lazy=',
440
- 'data-src=',
441
- 'data-srcset=',
442
- 'ewww_webp_lazy_load',
443
- 'fullurl=',
444
- 'gazette-featured-content-thumbnail',
445
- 'lazy-slider-img=',
446
- 'mgl-lazy',
447
- 'skip-lazy',
448
- 'timthumb.php?',
449
- 'wpcf7_captcha/',
450
- ),
451
- $image
452
- );
453
- foreach ( $exclusions as $exclusion ) {
454
- if ( false !== strpos( $image, $exclusion ) ) {
455
- return false;
456
- }
457
- }
458
- return true;
459
- }
460
-
461
- /**
462
- * Checks if a tag with a background image is allowed to be lazy loaded.
463
- *
464
- * @param string $tag The tag.
465
- * @return bool True if the tag is allowed, false otherwise.
466
- */
467
- function validate_bgimage_tag( $tag ) {
468
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
469
- $exclusions = apply_filters(
470
- 'ewww_image_optimizer_lazy_bg_image_exclusions',
471
- array(
472
- 'data-no-lazy=',
473
- 'header-gallery-wrapper ',
474
- 'lazyload',
475
- 'skip-lazy',
476
- ),
477
- $tag
478
- );
479
- foreach ( $exclusions as $exclusion ) {
480
- if ( false !== strpos( $tag, $exclusion ) ) {
481
- return false;
482
- }
483
- }
484
- return true;
485
- }
486
-
487
- /**
488
- * Build a PNG inline image placeholder.
489
- *
490
- * @param int $width The width of the placeholder image.
491
- * @param int $height The height of the placeholder image.
492
- * @return string The PNG placeholder link.
493
- */
494
- function create_piip( $width = 1, $height = 1 ) {
495
- $width = (int) $width;
496
- $height = (int) $height;
497
- if ( 1 === $width && 1 === $height ) {
498
- return $this->placeholder_src;
499
- }
500
-
501
- $piip_path = $this->piip_folder . 'placeholder-' . $width . 'x' . $height . '.png';
502
- if ( ! is_file( $piip_path ) ) {
503
- $img = imagecreatetruecolor( $width, $height );
504
- $color = imagecolorallocatealpha( $img, 0, 0, 0, 127 );
505
- imagefill( $img, 0, 0, $color );
506
- imagesavealpha( $img, true );
507
- imagecolortransparent( $img, imagecolorat( $img, 0, 0 ) );
508
- imagetruecolortopalette( $img, false, 1 );
509
- imagepng( $img, $piip_path, 9 );
510
- }
511
- if ( is_file( $piip_path ) ) {
512
- return content_url( 'ewww/lazy/placeholder-' . $width . 'x' . $height . '.png' );
513
- }
514
- return $this->placeholder_src;
515
- }
516
- /**
517
- * Allow lazy loading of images for some admin-ajax requests.
518
- *
519
- * @param bool $allow Will normally be false, unless already modified by another function.
520
- * @return bool True if it's an allowable admin-ajax request, false for all other admin requests.
521
- */
522
- function allow_admin_lazyload( $allow ) {
523
- if ( ! wp_doing_ajax() ) {
524
- return $allow;
525
- }
526
- if ( ! empty( $_POST['action'] ) && 'vc_get_vc_grid_data' === $_POST['action'] ) {
527
- ewwwio_debug_message( 'allowing lazy on vc grid' );
528
- return true;
529
- }
530
- if ( ! empty( $_POST['action'] ) && 'Essential_Grid_Front_request_ajax' === $_POST['action'] ) {
531
- /* return true; */
532
- }
533
- return $allow;
534
- }
535
-
536
- /**
537
- * Check if LQIP should be used, but allow filters to alter the option.
538
- *
539
- * @param bool $use_lqip Whether LL should use low-quality image placeholders.
540
- * @return bool True to use LQIP, false to skip them.
541
- */
542
- function maybe_lqip( $use_lqip ) {
543
- if ( defined( 'EWWW_IMAGE_OPTIMIZER_USE_LQIP' ) && ! EWWW_IMAGE_OPTIMIZER_USE_LQIP ) {
544
- return false;
545
- }
546
- return $use_lqip;
547
- }
548
-
549
- /**
550
- * Check if PIIP should be used, but allow filters to alter the option.
551
- *
552
- * @param bool $use_piip Whether LL should use PNG inline image placeholders.
553
- * @return bool True to use PIIP, false to skip them.
554
- */
555
- function maybe_piip( $use_piip ) {
556
- if ( defined( 'EWWW_IMAGE_OPTIMIZER_USE_PIIP' ) && ! EWWW_IMAGE_OPTIMIZER_USE_PIIP ) {
557
- return false;
558
- }
559
- return $use_piip;
560
- }
561
-
562
- /**
563
- * Check if SIIP should be used, but allow filters to alter the option.
564
- *
565
- * @param bool $use_siip Whether LL should use SVG inline image placeholders.
566
- * @return bool True to use SIIP, false to skip them.
567
- */
568
- function maybe_siip( $use_siip ) {
569
- if ( defined( 'EWWW_IMAGE_OPTIMIZER_USE_SIIP' ) && ! EWWW_IMAGE_OPTIMIZER_USE_SIIP ) {
570
- return false;
571
- }
572
- return $use_siip;
573
- }
574
-
575
- /**
576
- * Adds a small CSS block to hide lazyload elements for no-JS browsers.
577
- */
578
- function no_js_css() {
579
- echo '<noscript><style>.lazyload[data-src]{display:none !important;}</style></noscript>';
580
- }
581
-
582
- /**
583
- * Load full lazysizes script when SCRIPT_DEBUG is enabled.
584
- */
585
- function debug_script() {
586
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
587
- if ( ewww_image_optimizer_is_amp() ) {
588
- return;
589
- }
590
- wp_enqueue_script( 'ewww-lazy-load-pre', plugins_url( '/includes/lazysizes-pre.js', EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE ), array(), EWWW_IMAGE_OPTIMIZER_VERSION );
591
- wp_enqueue_script( 'ewww-lazy-load', plugins_url( '/includes/lazysizes.js', EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE ), array(), EWWW_IMAGE_OPTIMIZER_VERSION );
592
- wp_enqueue_script( 'ewww-lazy-load-post', plugins_url( '/includes/lazysizes-post.js', EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE ), array(), EWWW_IMAGE_OPTIMIZER_VERSION );
593
- wp_enqueue_script( 'ewww-lazy-load-uvh', plugins_url( '/includes/ls.unveilhooks.js', EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE ), array(), EWWW_IMAGE_OPTIMIZER_VERSION );
594
- wp_localize_script(
595
- 'ewww-lazy-load',
596
- 'ewww_lazy_vars',
597
- array(
598
- 'exactdn_domain' => ( $this->parsing_exactdn ? $this->exactdn_domain : '' ),
599
- )
600
- );
601
- }
602
-
603
- /**
604
- * Load minified lazysizes script.
605
- */
606
- function min_script() {
607
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
608
- if ( ewww_image_optimizer_is_amp() ) {
609
- return;
610
- }
611
- wp_enqueue_script( 'ewww-lazy-load', plugins_url( '/includes/lazysizes.min.js', EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE ), array(), EWWW_IMAGE_OPTIMIZER_VERSION );
612
- wp_localize_script(
613
- 'ewww-lazy-load',
614
- 'ewww_lazy_vars',
615
- array(
616
- 'exactdn_domain' => ( $this->parsing_exactdn ? $this->exactdn_domain : '' ),
617
- )
618
- );
619
- }
620
- }
621
-
622
- global $ewwwio_lazy_load;
623
- $ewwwio_lazy_load = new EWWWIO_Lazy_Load();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/class-ewwwio-page-parser.php DELETED
@@ -1,318 +0,0 @@
1
- <?php
2
- /**
3
- * Implements basic page parsing functions.
4
- *
5
- * @link https://ewww.io
6
- * @package EWWW_Image_Optimizer
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
- /**
13
- * HTML element and attribute parsing, replacing, etc.
14
- */
15
- class EWWWIO_Page_Parser {
16
-
17
- /**
18
- * Allowed image extensions.
19
- *
20
- * @access private
21
- * @var array $extensions
22
- */
23
- protected $extensions = array(
24
- 'gif',
25
- 'jpg',
26
- 'jpeg',
27
- 'jpe',
28
- 'png',
29
- );
30
-
31
- /**
32
- * Match all images and any relevant <a> tags in a block of HTML.
33
- *
34
- * The hyperlinks param implies that the src attribute is required, but not the other way around.
35
- *
36
- * @param string $content Some HTML.
37
- * @param bool $hyperlinks Default true. Should we include encasing hyperlinks in our search.
38
- * @param bool $src_required Default true. Should we look only for images with src attributes.
39
- * @return array An array of $images matches, where $images[0] is
40
- * an array of full matches, and the link_url, img_tag,
41
- * and img_url keys are arrays of those matches.
42
- */
43
- function get_images_from_html( $content, $hyperlinks = true, $src_required = true ) {
44
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
45
- $images = array();
46
- $unquoted_images = array();
47
-
48
- $unquoted_pattern = '';
49
- $search_pattern = '#(?P<img_tag><img\s[^>]*?>)#is';
50
- if ( $hyperlinks ) {
51
- ewwwio_debug_message( 'using figure+hyperlink(a) patterns with src required' );
52
- $search_pattern = '#(?:<figure[^>]*?\s+?class\s*=\s*["\'](?P<figure_class>[\w\s-]+?)["\'][^>]*?>\s*)?(?:<a[^>]*?\s+?href\s*=\s*["\'](?P<link_url>[^\s]+?)["\'][^>]*?>\s*)?(?P<img_tag><img[^>]*?\s+?src\s*=\s*("|\')(?P<img_url>(?!\4).+?)\4[^>]*?>){1}(?:\s*</a>)?#is';
53
- $unquoted_pattern = '#(?:<figure[^>]*?\s+?class\s*=\s*(?P<figure_class>[\w-]+)[^>]*?>\s*)?(?:<a[^>]*?\s+?href\s*=\s*(?P<link_url>[^"\'][^\s>]+)[^>]*?>\s*)?(?P<img_tag><img[^>]*?\s+?src\s*=\s*(?P<img_url>[^"\'][^\s>]+)[^>]*?>){1}(?:\s*</a>)?#is';
54
- } elseif ( $src_required ) {
55
- ewwwio_debug_message( 'using plain img pattern, src still required' );
56
- $search_pattern = '#(?P<img_tag><img[^>]*?\s+?src\s*=\s*("|\')(?P<img_url>(?!\2).+?)\2[^>]*?>)#is';
57
- $unquoted_pattern = '#(?P<img_tag><img[^>]*?\s+?src\s*=\s*(?P<img_url>[^"\'][^\s>]+)[^>]*?>)#is';
58
- }
59
- if ( preg_match_all( $search_pattern, $content, $images ) ) {
60
- ewwwio_debug_message( 'found ' . count( $images[0] ) . ' image elements with quoted pattern' );
61
- foreach ( $images as $key => $unused ) {
62
- // Simplify the output as much as possible.
63
- if ( is_numeric( $key ) && $key > 0 ) {
64
- unset( $images[ $key ] );
65
- }
66
- }
67
- /* ewwwio_debug_message( print_r( $images, true ) ); */
68
- }
69
- $images = array_filter( $images );
70
- if ( $unquoted_pattern && preg_match_all( $unquoted_pattern, $content, $unquoted_images ) ) {
71
- ewwwio_debug_message( 'found ' . count( $unquoted_images[0] ) . ' image elements with unquoted pattern' );
72
- foreach ( $unquoted_images as $key => $unused ) {
73
- // Simplify the output as much as possible.
74
- if ( is_numeric( $key ) && $key > 0 ) {
75
- unset( $unquoted_images[ $key ] );
76
- }
77
- }
78
- /* ewwwio_debug_message( print_r( $unquoted_images, true ) ); */
79
- }
80
- $unquoted_images = array_filter( $unquoted_images );
81
- if ( ! empty( $images ) && ! empty( $unquoted_images ) ) {
82
- ewwwio_debug_message( 'both patterns found results, merging' );
83
- /* ewwwio_debug_message( print_r( $images, true ) ); */
84
- $images = array_merge_recursive( $images, $unquoted_images );
85
- /* ewwwio_debug_message( print_r( $images, true ) ); */
86
- if ( ! empty( $images[0] ) && ! empty( $images[1] ) ) {
87
- $images[0] = array_merge( $images[0], $images[1] );
88
- unset( $images[1] );
89
- }
90
- } elseif ( empty( $images ) && ! empty( $unquoted_images ) ) {
91
- ewwwio_debug_message( 'unquoted results only, subbing in' );
92
- $images = $unquoted_images;
93
- }
94
- /* ewwwio_debug_message( print_r( $images, true ) ); */
95
- return $images;
96
- }
97
-
98
- /**
99
- * Match all images wrapped in <noscript> tags in a block of HTML.
100
- *
101
- * @param string $content Some HTML.
102
- * @return array An array of $images matches, where $images[0] is
103
- * an array of full matches, and the noscript_tag, img_tag,
104
- * and img_url keys are arrays of those matches.
105
- */
106
- function get_noscript_images_from_html( $content ) {
107
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
108
- $images = array();
109
-
110
- if ( preg_match_all( '#(?P<noscript_tag><noscript[^>]*?>\s*)(?P<img_tag><img[^>]*?\s+?src\s*=\s*["\'](?P<img_url>[^\s]+?)["\'][^>]*?>){1}(?:\s*</noscript>)?#is', $content, $images ) ) {
111
- foreach ( $images as $key => $unused ) {
112
- // Simplify the output as much as possible, mostly for confirming test results.
113
- if ( is_numeric( $key ) && $key > 0 ) {
114
- unset( $images[ $key ] );
115
- }
116
- }
117
- return $images;
118
- }
119
- return array();
120
- }
121
-
122
- /**
123
- * Match all sources wrapped in <picture> tags in a block of HTML.
124
- *
125
- * @param string $content Some HTML.
126
- * @return array An array of $pictures matches, containing full elements with ending tags.
127
- */
128
- function get_picture_tags_from_html( $content ) {
129
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
130
- $pictures = array();
131
- if ( preg_match_all( '#(?:<picture[^>]*?>\s*)(?:<source[^>]*?>)+(?:.*?</picture>)?#is', $content, $pictures ) ) {
132
- return $pictures[0];
133
- }
134
- return array();
135
- }
136
-
137
- /**
138
- * Match all elements by tag name in a block of HTML. Does not retrieve contents or closing tags.
139
- *
140
- * @param string $content Some HTML.
141
- * @param string $tag_name The name of the elements to retrieve.
142
- * @return array An array of $elements.
143
- */
144
- function get_elements_from_html( $content, $tag_name ) {
145
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
146
- if ( ! ctype_alpha( $tag_name ) ) {
147
- return array();
148
- }
149
- if ( preg_match_all( '#<' . $tag_name . '\s[^>]+?>#is', $content, $elements ) ) {
150
- return $elements[0];
151
- }
152
- return array();
153
- }
154
-
155
- /**
156
- * Try to determine height and width from strings WP appends to resized image filenames.
157
- *
158
- * @param string $src The image URL.
159
- * @return array An array consisting of width and height.
160
- */
161
- function get_dimensions_from_filename( $src ) {
162
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
163
- $width_height_string = array();
164
- ewwwio_debug_message( "looking for dimensions in $src" );
165
- if ( preg_match( '#-(\d+)x(\d+)(@2x)?\.(?:' . implode( '|', $this->extensions ) . '){1}(?:\?.+)?$#i', $src, $width_height_string ) ) {
166
- $width = (int) $width_height_string[1];
167
- $height = (int) $width_height_string[2];
168
-
169
- if ( strpos( $src, '@2x' ) ) {
170
- $width = 2 * $width;
171
- $height = 2 * $height;
172
- }
173
- if ( $width && $height ) {
174
- ewwwio_debug_message( "found w$width h$height" );
175
- return array( $width, $height );
176
- }
177
- }
178
- return array( false, false );
179
- }
180
-
181
- /**
182
- * Get the width from an image element.
183
- *
184
- * @param string $img The full image element.
185
- * @return string The width found or an empty string.
186
- */
187
- public function get_img_width( $img ) {
188
- $width = $this->get_attribute( $img, 'width' );
189
- // Then check for an inline max-width directive.
190
- $style = $this->get_attribute( $img, 'style' );
191
- if ( $style && preg_match( '#max-width:\s?(\d+)px#', $style, $max_width_string ) ) {
192
- if ( $max_width_string[1] && ( ! $width || $max_width_string[1] < $width ) ) {
193
- $width = $max_width_string[1];
194
- }
195
- }
196
- return $width;
197
- }
198
-
199
- /**
200
- * Get an attribute from an HTML element.
201
- *
202
- * @param string $element The HTML element to parse.
203
- * @param string $name The name of the attribute to search for.
204
- * @return string The value of the attribute, or an empty string if not found.
205
- */
206
- function get_attribute( $element, $name ) {
207
- // Don't forget, back references cannot be used in character classes.
208
- if ( preg_match( '#\s' . $name . '\s*=\s*("|\')((?!\1).+?)\1#is', $element, $attr_matches ) ) {
209
- if ( ! empty( $attr_matches[2] ) ) {
210
- return $attr_matches[2];
211
- }
212
- }
213
- // If there were not any matches with quotes, look for unquoted attributes, no spaces or quotes allowed.
214
- if ( preg_match( '#\s' . $name . '\s*=\s*([^"\'][^\s>]+)#is', $element, $attr_matches ) ) {
215
- if ( ! empty( $attr_matches[1] ) ) {
216
- return $attr_matches[1];
217
- }
218
- }
219
- return '';
220
- }
221
-
222
- /**
223
- * Get a CSS background-image URL.
224
- *
225
- * @param string $attribute An element's style attribute. Do not pass a full HTML element.
226
- * @return string The URL from the background/background-image property.
227
- */
228
- function get_background_image_url( $attribute ) {
229
- if ( ( false !== strpos( $attribute, 'background:' ) || false !== strpos( $attribute, 'background-image:' ) ) && false !== strpos( $attribute, 'url(' ) ) {
230
- if ( preg_match( '#url\(([^)]+)\)#', $attribute, $prop_match ) ) {
231
- return trim( $prop_match[1], "'\"\t\n\r " );
232
- }
233
- }
234
- return '';
235
- }
236
-
237
- /**
238
- * Set an attribute on an HTML element.
239
- *
240
- * @param string $element The HTML element to modify. Passed by reference.
241
- * @param string $name The name of the attribute to set.
242
- * @param string $value The value of the attribute to set.
243
- * @param bool $replace Default false. True to replace, false to append.
244
- */
245
- function set_attribute( &$element, $name, $value, $replace = false ) {
246
- if ( 'class' === $name ) {
247
- $element = preg_replace( "#\s$name\s+[^=]#", ' ', $element );
248
- }
249
- $element = preg_replace( "#\s$name=\"\"#", ' ', $element );
250
- $value = trim( $value );
251
- if ( $replace ) {
252
- // Don't forget, back references cannot be used in character classes.
253
- $new_element = preg_replace( '#\s' . $name . '\s*=\s*("|\')(?!\1).*?\1#is', " $name=$1$value$1", $element );
254
- if ( strpos( $new_element, "$name=" ) ) {
255
- $element = $new_element;
256
- return;
257
- }
258
- $element = preg_replace( '#\s' . $name . '\s*=\s*[^"\'][^\s>]+#is', ' ', $element );
259
- }
260
- $closing = ' />';
261
- if ( false === strpos( $element, '/>' ) ) {
262
- $closing = '>';
263
- }
264
- if ( false === strpos( $value, '"' ) ) {
265
- $element = rtrim( $element, $closing ) . " $name=\"$value\"$closing";
266
- return;
267
- }
268
- $element = rtrim( $element, $closing ) . " $name='$value'$closing";
269
- }
270
-
271
- /**
272
- * Remove an attribute from an HTML element.
273
- *
274
- * @param string $element The HTML element to modify. Passed by reference.
275
- * @param string $name The name of the attribute to remove.
276
- */
277
- function remove_attribute( &$element, $name ) {
278
- // Don't forget, back references cannot be used in character classes.
279
- $element = preg_replace( '#\s' . $name . '\s*=\s*("|\')(?!\1).+?\1#is', ' ', $element );
280
- $element = preg_replace( '#\s' . $name . '\s*=\s*[^"\'][^\s>]+#is', ' ', $element );
281
- }
282
-
283
- /**
284
- * Remove the background image URL from a style attribute.
285
- *
286
- * @param string $attribute The element's style attribute to modify.
287
- * @return string The style attribute with any image url removed.
288
- */
289
- function remove_background_image( $attribute ) {
290
- if ( false !== strpos( $attribute, 'background:' ) && false !== strpos( $attribute, 'url(' ) ) {
291
- $attribute = preg_replace( '#\s?url\([^)]+\)#', '', $attribute );
292
- }
293
- if ( false !== strpos( $attribute, 'background-image:' ) && false !== strpos( $attribute, 'url(' ) ) {
294
- $attribute = preg_replace( '#background-image:\s*url\([^)]+\);?#', '', $attribute );
295
- }
296
- return $attribute;
297
- }
298
-
299
- /**
300
- * A wrapper for PHP's parse_url, prepending assumed scheme for network path
301
- * URLs. PHP versions 5.4.6 and earlier do not correctly parse without scheme.
302
- *
303
- * @param string $url The URL to parse.
304
- * @param integer $component Retrieve specific URL component.
305
- * @return mixed Result of parse_url.
306
- */
307
- function parse_url( $url, $component = -1 ) {
308
- if ( 0 === strpos( $url, '//' ) ) {
309
- $url = ( is_ssl() ? 'https:' : 'http:' ) . $url;
310
- }
311
- if ( false === strpos( $url, 'http' ) && '/' !== substr( $url, 0, 1 ) ) {
312
- $url = ( is_ssl() ? 'https://' : 'http://' ) . $url;
313
- }
314
- // Because encoded ampersands in the filename break things.
315
- $url = str_replace( '&#038;', '&', $url );
316
- return parse_url( $url, $component );
317
- }
318
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/class-exactdn.php CHANGED
@@ -3,2713 +3,2699 @@
3
  * Class and methods to implement ExactDN (based on Photon implementation).
4
  *
5
  * @link https://ewww.io
6
- * @package EWWW_Image_Optimizer
7
  */
8
 
9
  if ( ! defined( 'ABSPATH' ) ) {
10
  exit;
11
  }
12
 
13
- /**
14
- * Enables EWWW IO to filter the page content and replace image urls with ExactDN urls.
15
- */
16
- class ExactDN extends EWWWIO_Page_Parser {
17
-
18
  /**
19
- * A list of user-defined exclusions, populated by validate_user_exclusions().
20
- *
21
- * @access protected
22
- * @var array $user_exclusions
23
  */
24
- protected $user_exclusions = array();
25
 
26
- /**
27
- * A list of image sizes registered for attachments.
28
- *
29
- * @access protected
30
- * @var array $image_sizes
31
- */
32
- protected static $image_sizes = null;
33
 
34
- /**
35
- * Indicates if we are in full-page filtering mode.
36
- *
37
- * @access public
38
- * @var bool $filtering_the_page
39
- */
40
- public $filtering_the_page = false;
41
 
42
- /**
43
- * Indicates if we are in content filtering mode.
44
- *
45
- * @access public
46
- * @var bool $filtering_the_content
47
- */
48
- public $filtering_the_content = false;
49
 
50
- /**
51
- * List of permitted domains for ExactDN rewriting.
52
- *
53
- * @access public
54
- * @var array $allowed_domains
55
- */
56
- public $allowed_domains = array();
57
 
58
- /**
59
- * Path portion to remove at beginning of URL, usually for path-style S3 domains.
60
- *
61
- * @access public
62
- * @var string $remove_path
63
- */
64
- public $remove_path = '';
65
 
66
- /**
67
- * The ExactDN domain/zone.
68
- *
69
- * @access private
70
- * @var float $elapsed_time
71
- */
72
- private $exactdn_domain = false;
73
 
74
- /**
75
- * The detected site scheme (http/https).
76
- *
77
- * @access private
78
- * @var string $scheme
79
- */
80
- private $scheme = false;
81
 
82
- /**
83
- * Allow us to track how much overhead ExactDN introduces.
84
- *
85
- * @access private
86
- * @var float $elapsed_time
87
- */
88
- private $elapsed_time = 0;
89
 
90
- /**
91
- * Keep track of the attribute we use for srcset, in case a lazy load plugin is active.
92
- *
93
- * @access private
94
- * @var string $srcset_attr
95
- */
96
- private $srcset_attr = 'srcset';
97
 
98
- /**
99
- * Register (once) actions and filters for ExactDN. If you want to use this class, use the global.
100
- */
101
- function __construct() {
102
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
103
- global $exactdn;
104
- if ( is_object( $exactdn ) ) {
105
- return 'you are doing it wrong';
106
- }
107
 
108
- // Make sure we have an ExactDN domain to use.
109
- if ( ! $this->setup() ) {
110
- return;
111
- }
 
 
 
 
 
 
112
 
113
- if ( ! $this->scheme ) {
114
- $site_url = get_home_url();
115
- $scheme = 'http';
116
- if ( strpos( $site_url, 'https://' ) !== false ) {
117
- $scheme = 'https';
118
  }
119
- $this->scheme = $scheme;
120
- }
121
 
122
- // Images in post content and galleries.
123
- add_filter( 'the_content', array( $this, 'filter_the_content' ), 999999 );
124
- // Start an output buffer before any output starts.
125
- add_filter( 'ewww_image_optimizer_filter_page_output', array( $this, 'filter_page_output' ), 5 );
 
 
 
 
126
 
127
- // Core image retrieval.
128
- if ( ! function_exists( 'aq_resize' ) ) {
129
- add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
130
- } else {
131
- ewwwio_debug_message( 'aq_resize detected, image_downsize filter disabled' );
132
- }
133
- // Disable image_downsize filter during themify_get_image().
134
- add_action( 'themify_before_post_image', array( $this, 'disable_image_downsize' ) );
135
- if ( defined( 'EXACTDN_IMAGE_DOWNSIZE_SCALE' ) && EXACTDN_IMAGE_DOWNSIZE_SCALE ) {
136
- add_action( 'exactdn_image_downsize_array', array( $this, 'image_downsize_scale' ) );
137
- }
138
 
139
- // Check REST API requests to see if ExactDN should be running.
140
- add_filter( 'rest_request_before_callbacks', array( $this, 'parse_restapi_maybe' ), 10, 3 );
 
 
 
 
 
 
 
 
 
141
 
142
- // Overrides for admin-ajax images.
143
- add_filter( 'exactdn_admin_allow_image_downsize', array( $this, 'allow_admin_image_downsize' ), 10, 2 );
144
- // Overrides for "pass through" images.
145
- add_filter( 'exactdn_pre_args', array( $this, 'exactdn_remove_args' ), 10, 3 );
146
- // Overrides for user exclusions.
147
- add_filter( 'exactdn_skip_image', array( $this, 'exactdn_skip_user_exclusions' ), 9, 2 );
148
- add_filter( 'exactdn_skip_for_url', array( $this, 'exactdn_skip_user_exclusions' ), 9, 2 );
149
 
150
- // Responsive image srcset substitution.
151
- add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 1001, 5 );
152
- add_filter( 'wp_calculate_image_sizes', array( $this, 'filter_sizes' ), 1, 2 ); // Early so themes can still filter.
 
 
 
 
153
 
154
- // Filter for NextGEN image urls within JS.
155
- add_filter( 'ngg_pro_lightbox_images_queue', array( $this, 'ngg_pro_lightbox_images_queue' ) );
156
- add_filter( 'ngg_get_image_url', array( $this, 'ngg_get_image_url' ) );
157
 
158
- // Filter for legacy WooCommerce API endpoints.
159
- add_filter( 'woocommerce_api_product_response', array( $this, 'woocommerce_api_product_response' ) );
 
160
 
161
- // DNS prefetching.
162
- add_filter( 'wp_resource_hints', array( $this, 'dns_prefetch' ), 10, 2 );
163
 
164
- // Get all the script/css urls and rewrite them (if enabled).
165
- if ( ewww_image_optimizer_get_option( 'exactdn_all_the_things' ) ) {
166
- add_filter( 'style_loader_src', array( $this, 'parse_enqueue' ), 20 );
167
- add_filter( 'script_loader_src', array( $this, 'parse_enqueue' ), 20 );
168
- }
169
 
170
- // Improve the default content_width for Twenty Nineteen.
171
- global $content_width;
172
- if ( function_exists( 'twentynineteen_setup' ) && 640 === (int) $content_width ) {
173
- $content_width = 932;
174
- }
175
 
176
- // Configure Autoptimize with our CDN domain.
177
- add_filter( 'autoptimize_filter_cssjs_multidomain', array( $this, 'autoptimize_cdn_url' ) );
178
- if ( false && defined( 'AUTOPTIMIZE_PLUGIN_DIR' ) && ewww_image_optimizer_get_option( 'exactdn_all_the_things' ) ) {
179
- $ao_cdn_url = ewww_image_optimizer_get_option( 'autoptimize_cdn_url' );
180
- if ( empty( $ao_cdn_url ) ) {
181
- ewww_image_optimizer_set_option( 'autoptimize_cdn_url', '//' . $this->exactdn_domain );
182
- } elseif ( strpos( $ao_cdn_url, 'exactdn' ) && '//' . $this->exactdn_domain !== $ao_cdn_url ) {
183
- ewww_image_optimizer_set_option( 'autoptimize_cdn_url', '//' . $this->exactdn_domain );
184
  }
185
- }
186
 
187
- // Find the "local" domain.
188
- $s3_active = false;
189
- if ( class_exists( 'Amazon_S3_And_CloudFront' ) ) {
190
- global $as3cf;
191
- $s3_region = $as3cf->get_setting( 'region' );
192
- $s3_bucket = $as3cf->get_setting( 'bucket' );
193
- $s3_domain = $as3cf->get_provider()->get_url_domain( $s3_bucket, $s3_region, null, array(), true );
194
- ewwwio_debug_message( "found S3 domain of $s3_domain with bucket $s3_bucket and region $s3_region" );
195
- if ( ! empty( $s3_domain ) ) {
196
- $s3_active = true;
 
 
 
 
197
  }
198
- }
199
 
200
- if ( $s3_active ) {
201
- $upload_dir = array(
202
- 'baseurl' => 'https://' . $s3_domain,
203
- );
204
- } else {
205
- $upload_dir = wp_upload_dir( null, false );
206
- }
207
- $upload_url_parts = defined( 'EXACTDN_LOCAL_DOMAIN' ) && EXACTDN_LOCAL_DOMAIN ? $this->parse_url( EXACTDN_LOCAL_DOMAIN ) : $this->parse_url( $upload_dir['baseurl'] );
208
- if ( empty( $upload_url_parts ) ) {
209
- return;
210
- }
211
- $this->upload_domain = $upload_url_parts['host'];
212
- ewwwio_debug_message( "allowing images from here: $this->upload_domain" );
213
- if ( strpos( $this->upload_domain, 'amazonaws.com' ) && ! empty( $upload_url_parts['path'] ) ) {
214
- $this->remove_path = rtrim( $upload_url_parts['path'], '/' );
215
- ewwwio_debug_message( "removing this from urls: $this->remove_path" );
216
- }
217
- $this->allowed_domains[] = $this->upload_domain;
218
- if ( ! $s3_active && strpos( $this->upload_domain, 'www' ) === false ) {
219
- $this->allowed_domains[] = 'www.' . $this->upload_domain;
220
- } else {
221
- $nonwww = ltrim( 'www.', $this->upload_domain );
222
- if ( $nonwww !== $this->upload_domain ) {
223
- $this->allowed_domains[] = $nonwww;
224
  }
225
- }
226
- $wpml_domains = apply_filters( 'wpml_setting', array(), 'language_domains' );
227
- if ( ewww_image_optimizer_iterable( $wpml_domains ) ) {
228
- ewwwio_debug_message( 'wpml domains: ' . implode( ',', $wpml_domains ) );
229
- $this->allowed_domains[] = $this->parse_url( get_option( 'home' ), PHP_URL_HOST );
230
- foreach ( $wpml_domains as $wpml_domain ) {
231
- $this->allowed_domains[] = $wpml_domain;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  }
 
 
 
233
  }
234
- $this->allowed_domains = apply_filters( 'exactdn_allowed_domains', $this->allowed_domains );
235
- ewwwio_debug_message( 'allowed domains: ' . implode( ',', $this->allowed_domains ) );
236
- $this->validate_user_exclusions();
237
- }
238
 
239
- /**
240
- * If ExactDN is enabled, validates and configures the ExactDN domain name.
241
- */
242
- function setup() {
243
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
244
- // If we don't have a domain yet, go grab one.
245
- if ( ! $this->get_exactdn_domain() ) {
246
- ewwwio_debug_message( 'attempting to activate exactDN' );
247
- $exactdn_domain = $this->activate_site();
248
- } else {
249
- ewwwio_debug_message( 'grabbing existing exactDN domain' );
250
- $exactdn_domain = $this->get_exactdn_domain();
251
- }
252
- if ( ! $exactdn_domain ) {
253
- if ( get_option( 'ewww_image_optimizer_exactdn_failures' ) < 5 ) {
254
- $failures = (int) get_option( 'ewww_image_optimizer_exactdn_failures' );
255
- $failures++;
256
- ewwwio_debug_message( "could not activate ExactDN, failures: $failures" );
257
- update_option( 'ewww_image_optimizer_exactdn_failures', $failures );
258
  return false;
259
  }
260
- delete_option( 'ewww_image_optimizer_exactdn' );
261
- delete_site_option( 'ewww_image_optimizer_exactdn' );
262
- return false;
263
- }
264
- // If we have a domain, verify it.
265
- if ( $this->verify_domain( $exactdn_domain ) ) {
266
- ewwwio_debug_message( 'verified existing exactDN domain' );
267
- delete_option( 'ewww_image_optimizer_exactdn_failures' );
268
- $this->exactdn_domain = $exactdn_domain;
269
- ewwwio_debug_message( 'exactdn_domain: ' . $exactdn_domain );
270
- return true;
271
- } elseif ( $this->get_exactdn_option( 'checkin' ) < time() - 5 && get_option( 'ewww_image_optimizer_exactdn_failures' ) < 10 ) {
272
- $failures = (int) get_option( 'ewww_image_optimizer_exactdn_failures' );
273
- $failures++;
274
- ewwwio_debug_message( "could not verify existing exactDN domain, failures: $failures" );
275
- update_option( 'ewww_image_optimizer_exactdn_failures', $failures );
276
- $this->set_exactdn_option( 'checkin', time() + 300 );
277
- return false;
278
- } elseif ( get_option( 'ewww_image_optimizer_exactdn_failures' ) < 10 ) {
279
- $failures = (int) get_option( 'ewww_image_optimizer_exactdn_failures' );
280
- ewwwio_debug_message( 'could not verify existing exactDN domain, waiting for ' . $this->human_time_diff( $this->get_exactdn_option( 'checkin' ) ) );
281
- ewwwio_debug_message( 10 - $failures . ' attempts remaining' );
282
  return false;
283
  }
284
- delete_option( 'ewww_image_optimizer_exactdn_domain' );
285
- delete_option( 'ewww_image_optimizer_exactdn_verified' );
286
- delete_site_option( 'ewww_image_optimizer_exactdn_domain' );
287
- delete_site_option( 'ewww_image_optimizer_exactdn_verified' );
288
- return false;
289
- }
290
 
291
- /**
292
- * Use the Site URL to get the zone domain.
293
- */
294
- function activate_site() {
295
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
296
- $s3_active = false;
297
- if ( class_exists( 'Amazon_S3_And_CloudFront' ) ) {
298
- global $as3cf;
299
- $s3_scheme = $as3cf->get_url_scheme();
300
- $s3_region = $as3cf->get_setting( 'region' );
301
- $s3_bucket = $as3cf->get_setting( 'bucket' );
302
- $s3_domain = $as3cf->get_provider()->get_url_domain( $s3_bucket, $s3_region, null, array(), true );
303
- ewwwio_debug_message( "found S3 domain of $s3_domain with bucket $s3_bucket and region $s3_region" );
304
- if ( ! empty( $s3_domain ) ) {
305
- $s3_active = true;
 
306
  }
307
- }
308
 
309
- if ( $s3_active ) {
310
- $site_url = defined( 'EXACTDN_LOCAL_DOMAIN' ) && EXACTDN_LOCAL_DOMAIN ? EXACTDN_LOCAL_DOMAIN : $s3_scheme . '://' . $s3_domain;
311
- } else {
312
- $site_url = defined( 'EXACTDN_LOCAL_DOMAIN' ) && EXACTDN_LOCAL_DOMAIN ? EXACTDN_LOCAL_DOMAIN : get_home_url();
313
- }
314
- $url = 'http://optimize.exactlywww.com/exactdn/activate.php';
315
- $ssl = wp_http_supports( array( 'ssl' ) );
316
- if ( $ssl ) {
317
- $url = set_url_scheme( $url, 'https' );
318
- }
319
- add_filter( 'http_headers_useragent', 'ewww_image_optimizer_cloud_useragent', PHP_INT_MAX );
320
- $result = wp_remote_post(
321
- $url,
322
- array(
323
- 'timeout' => 10,
324
- 'body' => array(
325
- 'site_url' => $site_url,
326
- ),
327
- )
328
- );
329
- if ( is_wp_error( $result ) ) {
330
- $error_message = $result->get_error_message();
331
- ewwwio_debug_message( "exactdn activation request failed: $error_message" );
332
- return false;
333
- } elseif ( ! empty( $result['body'] ) && strpos( $result['body'], 'domain' ) !== false ) {
334
- $response = json_decode( $result['body'], true );
335
- if ( ! empty( $response['domain'] ) ) {
336
- if ( strpos( $site_url, 'amazonaws.com' ) || strpos( $site_url, 'digitaloceanspaces.com' ) ) {
337
- $this->set_exactdn_option( 'verify_method', -1, false );
338
- }
339
- if ( get_option( 'exactdn_never_been_active' ) ) {
340
- ewww_image_optimizer_set_option( 'ewww_image_optimizer_lazy_load', true );
341
- delete_option( 'exactdn_never_been_active' );
 
 
 
 
 
 
 
 
 
 
 
 
342
  }
343
- return $this->set_exactdn_domain( $response['domain'] );
 
 
 
 
 
 
 
344
  }
345
- } elseif ( ! empty( $result['body'] ) && strpos( $result['body'], 'error' ) !== false ) {
346
- $response = json_decode( $result['body'], true );
347
- $error_message = $response['error'];
348
- ewwwio_debug_message( "exactdn activation request failed: $error_message" );
349
  return false;
350
  }
351
- return false;
352
- }
353
 
354
- /**
355
- * Verify the ExactDN domain.
356
- *
357
- * @param string $domain The ExactDN domain to verify.
358
- * @return bool Whether the domain is still valid.
359
- */
360
- function verify_domain( $domain ) {
361
- if ( empty( $domain ) ) {
362
- return false;
363
- }
364
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
365
- // Check the time, to see how long it has been since we verified the domain.
366
- $last_checkin = $this->get_exactdn_option( 'checkin' );
367
- if ( ! empty( $last_checkin ) && $last_checkin > time() ) {
368
- ewwwio_debug_message( 'not time yet: ' . $this->human_time_diff( $this->get_exactdn_option( 'checkin' ) ) );
369
- if ( $this->get_exactdn_option( 'suspended' ) ) {
370
- ewwwio_debug_message( 'suspended marker, returning false until next checkin' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  return false;
372
  }
373
- return true;
374
- }
375
 
376
- $this->check_verify_method();
377
-
378
- if ( ! defined( 'EXACTDN_LOCAL_DOMAIN' ) && $this->get_exactdn_option( 'verify_method' ) > 0 ) {
379
- // Test with an image file that should be available on the ExactDN zone.
380
- $test_url = plugins_url( '/images/test.png', EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE );
381
- $local_domain = $this->parse_url( $test_url, PHP_URL_HOST );
382
- $test_url = str_replace( $local_domain, $domain, $test_url );
383
- ewwwio_debug_message( "test url is $test_url" );
384
- add_filter( 'http_headers_useragent', 'ewww_image_optimizer_cloud_useragent', PHP_INT_MAX );
385
- $test_result = wp_remote_get( $test_url );
386
- if ( is_wp_error( $test_result ) ) {
387
- $error_message = $test_result->get_error_message();
388
- ewwwio_debug_message( "exactdn verification request failed: $error_message" );
389
- $this->set_exactdn_option( 'suspended', 1 );
 
 
 
 
 
 
 
390
  return false;
391
- } elseif ( ! empty( $test_result['body'] ) && strlen( $test_result['body'] ) > 300 ) {
392
- if ( 200 === $test_result['response']['code'] &&
393
- ( '89504e470d0a1a0a' === bin2hex( substr( $test_result['body'], 0, 8 ) ) || '52494646' === bin2hex( substr( $test_result['body'], 0, 4 ) ) ) ) {
394
- ewwwio_debug_message( 'exactdn (real-world) verification succeeded' );
395
- $this->set_exactdn_option( 'checkin', time() + 3600 );
396
  $this->set_exactdn_option( 'verified', 1, false );
397
- $this->set_exactdn_option( 'suspended', 0 );
398
  return true;
399
  }
400
- ewwwio_debug_message( 'mime check failed: ' . bin2hex( substr( $test_result['body'], 0, 3 ) ) );
401
- }
402
- $this->set_exactdn_option( 'suspended', 1 );
403
- return false;
404
- }
405
-
406
- // Secondary test against the API db.
407
- $url = 'http://optimize.exactlywww.com/exactdn/verify.php';
408
- $ssl = wp_http_supports( array( 'ssl' ) );
409
- if ( $ssl ) {
410
- $url = set_url_scheme( $url, 'https' );
411
- }
412
- add_filter( 'http_headers_useragent', 'ewww_image_optimizer_cloud_useragent', PHP_INT_MAX );
413
- $result = wp_remote_post(
414
- $url,
415
- array(
416
- 'timeout' => 10,
417
- 'body' => array(
418
- 'alias' => $domain,
419
- ),
420
- )
421
- );
422
- if ( is_wp_error( $result ) ) {
423
- $error_message = $result->get_error_message();
424
- ewwwio_debug_message( "exactdn verification request failed: $error_message" );
425
- $this->set_exactdn_option( 'suspended', 1 );
426
- return false;
427
- } elseif ( ! empty( $result['body'] ) && strpos( $result['body'], 'error' ) === false ) {
428
- $response = json_decode( $result['body'], true );
429
- if ( ! empty( $response['success'] ) ) {
430
- ewwwio_debug_message( 'exactdn (secondary) verification succeeded' );
431
- $this->set_exactdn_option( 'checkin', time() + 3600 );
432
- $this->set_exactdn_option( 'verified', 1, false );
433
- $this->set_exactdn_option( 'suspended', 0 );
434
- return true;
435
  }
436
- } elseif ( ! empty( $result['body'] ) ) {
437
- $response = json_decode( $result['body'], true );
438
- $error_message = $response['error'];
439
- ewwwio_debug_message( "exactdn verification request failed: $error_message" );
440
- $this->set_exactdn_option( 'suspended', 1 );
441
  return false;
442
  }
443
- $this->set_exactdn_option( 'suspended', 1 );
444
- return false;
445
- }
446
 
447
- /**
448
- * Run a simulation to decide which verification method to use.
449
- */
450
- function check_verify_method() {
451
- if ( ! $this->get_exactdn_option( 'verify_method' ) ) {
452
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
453
- // Prelim test with a known valid image to ensure http(s) connectivity.
454
- $sim_url = 'https://optimize.exactdn.com/exactdn/testorig.jpg';
455
- add_filter( 'http_headers_useragent', 'ewww_image_optimizer_cloud_useragent', PHP_INT_MAX );
456
- $sim_result = wp_remote_get( $sim_url );
457
- if ( is_wp_error( $sim_result ) ) {
458
- $error_message = $sim_result->get_error_message();
459
- ewwwio_debug_message( "exactdn (simulated) verification request failed: $error_message" );
460
- } elseif ( ! empty( $sim_result['body'] ) && strlen( $sim_result['body'] ) > 300 ) {
461
- if ( 'ffd8ff' === bin2hex( substr( $sim_result['body'], 0, 3 ) ) ) {
462
- ewwwio_debug_message( 'exactdn (simulated) verification succeeded' );
463
- $this->set_exactdn_option( 'verify_method', 1, false );
464
- return;
 
465
  }
 
 
466
  }
467
- ewwwio_debug_message( 'exactdn (simulated) verification request failed, error unknown' );
468
- $this->set_exactdn_option( 'verify_method', -1, false );
469
  }
470
- }
471
 
472
- /**
473
- * Validate the ExactDN domain.
474
- *
475
- * @param string $domain The unverified ExactDN domain.
476
- * @return string The validated ExactDN domain.
477
- */
478
- function sanitize_domain( $domain ) {
479
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
480
- if ( ! $domain ) {
481
- return;
482
- }
483
- if ( strlen( $domain ) > 80 ) {
484
- ewwwio_debug_message( "$domain too long" );
485
- return false;
486
- }
487
- if ( ! preg_match( '#^[A-Za-z0-9\.\-]+$#', $domain ) ) {
488
- ewwwio_debug_message( "$domain has bad characters" );
489
- return false;
 
 
490
  }
491
- return $domain;
492
- }
493
 
494
- /**
495
- * Get the ExactDN domain name to use.
496
- *
497
- * @return string The ExactDN domain name for this site or network.
498
- */
499
- function get_exactdn_domain() {
500
- if ( defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) {
501
- return $this->sanitize_domain( EXACTDN_DOMAIN );
502
- }
503
- if ( is_multisite() ) {
504
- if ( ! SUBDOMAIN_INSTALL ) {
505
- return $this->sanitize_domain( get_site_option( 'ewww_image_optimizer_exactdn_domain' ) );
 
506
  }
 
507
  }
508
- return $this->sanitize_domain( get_option( 'ewww_image_optimizer_exactdn_domain' ) );
509
- }
510
 
511
- /**
512
- * Method to override the ExactDN domain at runtime, use with caution.
513
- *
514
- * @param string $domain The ExactDN domain to use instead.
515
- */
516
- function set_domain( $domain ) {
517
- if ( is_string( $domain ) ) {
518
- $this->exactdn_domain = $domain;
519
- }
520
- }
521
- /**
522
- * Get the ExactDN option.
523
- *
524
- * @param string $option_name The name of the ExactDN option.
525
- * @return int The numerical value of the option.
526
- */
527
- function get_exactdn_option( $option_name ) {
528
- if ( defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) {
529
- return (int) get_option( 'ewww_image_optimizer_exactdn_' . $option_name );
530
  }
531
- if ( is_multisite() ) {
532
- if ( ! SUBDOMAIN_INSTALL ) {
533
- return (int) get_site_option( 'ewww_image_optimizer_exactdn_' . $option_name );
 
 
 
 
 
 
 
 
 
 
 
534
  }
 
535
  }
536
- return (int) get_option( 'ewww_image_optimizer_exactdn_' . $option_name );
537
- }
538
 
539
- /**
540
- * Set the ExactDN domain name to use.
541
- *
542
- * @param string $domain The ExactDN domain name for this site or network.
543
- */
544
- function set_exactdn_domain( $domain ) {
545
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
546
- if ( defined( 'EXACTDN_DOMAIN' ) && $this->sanitize_domain( EXACTDN_DOMAIN ) ) {
547
- return true;
548
- }
549
- $domain = $this->sanitize_domain( $domain );
550
- if ( ! $domain ) {
551
- return false;
552
- }
553
- if ( is_multisite() ) {
554
- if ( ! SUBDOMAIN_INSTALL ) {
555
- update_site_option( 'ewww_image_optimizer_exactdn_domain', $domain );
556
- return $domain;
 
557
  }
 
 
558
  }
559
- update_option( 'ewww_image_optimizer_exactdn_domain', $domain );
560
- return $domain;
561
- }
562
 
563
- /**
564
- * Set an option for ExactDN.
565
- *
566
- * @param string $option_name The name of the ExactDN option.
567
- * @param int $option_value The value to set for the ExactDN option.
568
- * @param bool $autoload Optional. Whether to load the option when WordPress starts up.
569
- */
570
- function set_exactdn_option( $option_name, $option_value, $autoload = null ) {
571
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
572
- if ( defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) {
573
- return update_option( 'ewww_image_optimizer_exactdn_' . $option_name, $option_value, $autoload );
574
- }
575
- if ( is_multisite() ) {
576
- if ( ! SUBDOMAIN_INSTALL ) {
577
- return update_site_option( 'ewww_image_optimizer_exactdn_' . $option_name, $option_value );
 
578
  }
 
579
  }
580
- return update_option( 'ewww_image_optimizer_exactdn_' . $option_name, $option_value, $autoload );
581
- }
582
 
583
- /**
584
- * Validate the user-defined exclusions for "all the things" rewriting.
585
- */
586
- function validate_user_exclusions() {
587
- if ( defined( 'EXACTDN_EXCLUDE' ) && EXACTDN_EXCLUDE ) {
588
- $user_exclusions = EXACTDN_EXCLUDE;
589
- }
590
- if ( ! empty( $user_exclusions ) ) {
591
- if ( is_string( $user_exclusions ) ) {
592
- $user_exclusions = array( $user_exclusions );
593
- }
594
- if ( is_array( $user_exclusions ) ) {
595
- foreach ( $user_exclusions as $exclusion ) {
596
- if ( false !== strpos( $exclusion, 'wp-content' ) ) {
597
- $exclusion = preg_replace( '#([^"\'?>]+?)?wp-content/#i', '', $exclusion );
 
 
598
  }
599
- $this->user_exclusions[] = ltrim( $exclusion, '/' );
600
  }
601
  }
602
  }
603
- }
604
 
605
- /**
606
- * Get $content_width, with a filter.
607
- *
608
- * @return bool|string The content width, if set. Default false.
609
- */
610
- function get_content_width() {
611
- $content_width = isset( $GLOBALS['content_width'] ) && is_numeric( $GLOBALS['content_width'] ) && $GLOBALS['content_width'] > 100 ? $GLOBALS['content_width'] : 1920;
612
- if ( function_exists( 'twentynineteen_setup' ) && 640 === (int) $content_width ) {
613
- $content_width = 932;
614
- }
615
  /**
616
- * Filter the Content Width value.
617
  *
618
- * @param string $content_width Content Width value.
619
  */
620
- return (int) apply_filters( 'exactdn_content_width', $content_width );
621
- }
622
-
623
- /**
624
- * Get width within an ExactDN url.
625
- *
626
- * @param string $url The ExactDN url to parse.
627
- * @return string The width, if found.
628
- */
629
- public function get_exactdn_width_from_url( $url ) {
630
- $url_args = $this->parse_url( $url, PHP_URL_QUERY );
631
- if ( ! $url_args ) {
632
- return '';
633
- }
634
- $args = explode( '&', $url_args );
635
- foreach ( $args as $arg ) {
636
- if ( preg_match( '#w=(\d+)#', $arg, $width_match ) ) {
637
- return $width_match[1];
638
  }
639
- if ( preg_match( '#resize=(\d+)#', $arg, $width_match ) ) {
640
- return $width_match[1];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
  }
642
- if ( preg_match( '#fit=(\d+)#', $arg, $width_match ) ) {
643
- return $width_match[1];
 
 
 
 
 
 
 
 
 
644
  }
 
645
  }
646
- return '';
647
- }
648
-
649
- /**
650
- * Identify images in page content, and if images are local (uploaded to the current site), pass through ExactDN.
651
- *
652
- * @param string $content The page/post content.
653
- * @return string The content with ExactDN image urls.
654
- */
655
- function filter_page_output( $content ) {
656
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
657
- $this->filtering_the_page = true;
658
-
659
- $content = $this->filter_the_content( $content );
660
 
661
  /**
662
- * Allow parsing the full page content after ExactDN is finished with it.
663
  *
664
- * @param string $content The fully-parsed HTML code of the page.
 
665
  */
666
- $content = apply_filters( 'exactdn_the_page', $content );
 
 
667
 
668
- $this->filtering_the_page = false;
669
- ewwwio_debug_message( "parsing page took $this->elapsed_time seconds" );
670
- return $content;
671
- }
672
-
673
- /**
674
- * Identify images in the content, and if images are local (uploaded to the current site), pass through ExactDN.
675
- *
676
- * @param string $content The page/post content.
677
- * @return string The content with ExactDN image urls.
678
- */
679
- function filter_the_content( $content ) {
680
- $started = microtime( true );
681
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
682
- $images = $this->get_images_from_html( $content, true );
683
 
684
- if ( ! empty( $images ) ) {
685
- ewwwio_debug_message( 'we have images to parse' );
686
- $content_width = false;
687
- if ( ! $this->filtering_the_page ) {
688
- $this->filtering_the_content = true;
689
- ewwwio_debug_message( 'filtering the content' );
690
- $content_width = $this->get_content_width();
691
- }
692
- $resize_existing = defined( 'EXACTDN_RESIZE_EXISTING' ) && EXACTDN_RESIZE_EXISTING;
693
-
694
- $image_sizes = $this->image_sizes();
695
-
696
- foreach ( $images[0] as $index => $tag ) {
697
- // Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained.
698
- $transform = 'resize';
699
-
700
- // Start with a clean slate each time.
701
- $attachment_id = false;
702
- $exactdn_url = false;
703
- $width = false;
704
- $lazy = false;
705
- $srcset_fill = false;
706
-
707
- // Flag if we need to munge a fullsize URL.
708
- $fullsize_url = false;
709
 
710
- // Identify image source.
711
- $src = $images['img_url'][ $index ];
712
- $src_orig = $images['img_url'][ $index ];
713
- ewwwio_debug_message( $src );
714
 
715
- /**
716
- * Allow specific images to be skipped by ExactDN.
717
- *
718
- * @param bool false Should ExactDN ignore this image. Default false.
719
- * @param string $src Image URL.
720
- * @param string $tag Image HTML Tag.
721
- */
722
- if ( apply_filters( 'exactdn_skip_image', false, $src, $tag ) ) {
723
- continue;
 
 
 
 
 
 
 
 
 
724
  }
 
725
 
726
- ewwwio_debug_message( 'made it passed the filters' );
727
 
728
- // Log 0-size Pinterest schema images.
729
- if ( strpos( $tag, 'data-pin-description=' ) && strpos( $tag, 'width="0" height="0"' ) ) {
730
- ewwwio_debug_message( 'data-pin/Pinterest image' );
731
- }
732
 
733
- // Pre-empt srcset fill if the surrounding link has a background image or if there is a data-desktop attribute indicating a potential slider.
734
- if ( strpos( $tag, 'background-image:' ) || strpos( $tag, 'data-desktop=' ) ) {
735
- $srcset_fill = false;
736
- }
737
- /**
738
- * Documented in generate_url, in this case used to detect images that should bypass srcset fill.
739
- *
740
- * @param array|string $args Array of ExactDN arguments.
741
- * @param string $image_url Image URL.
742
- * @param string|null $scheme Image scheme. Default to null.
743
- */
744
- $args = apply_filters( 'exactdn_pre_args', array( 'test' => 'lazy-test' ), $src, null );
745
- if ( empty( $args ) ) {
746
- $srcset_fill = false;
747
- }
748
- // Support Lazy Load plugins.
749
- // Don't modify $tag yet as we need unmodified version later.
750
- $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-lazy-src' );
751
- if ( $lazy_load_src ) {
752
- $placeholder_src = $src;
753
- $placeholder_src_orig = $src;
754
- $src = $lazy_load_src;
755
- $src_orig = $lazy_load_src;
756
- $this->srcset_attr = 'data-lazy-srcset';
757
- $lazy = true;
758
- $srcset_fill = true;
759
- }
760
- // Must be a legacy Jetpack thing as far as I can tell, no matches found in any currently installed plugins.
761
- $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-lazy-original' );
762
- if ( ! $lazy && $lazy_load_src ) {
763
- $placeholder_src = $src;
764
- $placeholder_src_orig = $src;
765
- $src = $lazy_load_src;
766
- $src_orig = $lazy_load_src;
767
- $lazy = true;
768
- }
769
- if ( ! $lazy && strpos( $images['img_tag'][ $index ], 'a3-lazy-load/assets/images/lazy_placeholder' ) ) {
770
- $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-src' );
771
- }
772
- if (
773
- ! $lazy &&
774
- strpos( $images['img_tag'][ $index ], ' data-src=' ) &&
775
- strpos( $images['img_tag'][ $index ], 'lazyload' ) &&
776
- (
777
- strpos( $images['img_tag'][ $index ], 'data:image/gif' ) ||
778
- strpos( $images['img_tag'][ $index ], 'data:image/svg' )
779
- )
780
- ) {
781
- $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-src' );
782
- ewwwio_debug_message( "found ewwwio ll src: $lazy_load_src" );
783
- }
784
- if ( ! $lazy && $lazy_load_src ) {
785
- $placeholder_src = $src;
786
- $placeholder_src_orig = $src;
787
- $src = $lazy_load_src;
788
- $src_orig = $lazy_load_src;
789
- $this->srcset_attr = 'data-srcset';
790
- $lazy = true;
791
- $srcset_fill = true;
792
- }
793
- if ( ! $lazy && strpos( $images['img_tag'][ $index ], 'revslider/admin/assets/images/dummy' ) ) {
794
- $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-lazyload' );
795
- }
796
- if ( ! $lazy && $lazy_load_src ) {
797
- $placeholder_src = $src;
798
- $placeholder_src_orig = $src;
799
- $src = $lazy_load_src;
800
- $src_orig = $lazy_load_src;
801
- $lazy = true;
802
- }
803
 
804
- // Check for relative urls that start with a slash. Unlikely that we'll attempt relative urls beyond that.
805
- if (
806
- '/' === substr( $src, 0, 1 ) &&
807
- '/' !== substr( $src, 1, 1 ) &&
808
- false === strpos( $this->upload_domain, 'amazonaws.com' ) &&
809
- false === strpos( $this->upload_domain, 'digitaloceanspaces.com' )
810
- ) {
811
- $src = '//' . $this->upload_domain . $src;
812
- }
813
 
814
- // Check if image URL should be used with ExactDN.
815
- if ( $this->validate_image_url( $src ) ) {
816
- ewwwio_debug_message( 'url validated' );
817
-
818
- // Find the width and height attributes.
819
- $width = $this->get_img_width( $images['img_tag'][ $index ] );
820
- $height = $this->get_attribute( $images['img_tag'][ $index ], 'height' );
821
- // Falsify them if empty.
822
- $width = $width ? $width : false;
823
- $height = $height ? $height : false;
824
-
825
- // Can't pass both a relative width and height, so unset the dimensions in favor of not breaking the horizontal layout.
826
- if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) ) {
827
- $width = false;
828
- $height = false;
829
  }
830
 
831
- // Detect WP registered image size from HTML class.
832
- if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) {
833
- $size = array_pop( $size );
834
 
835
- ewwwio_debug_message( "detected $size" );
836
- if ( false === $width && false === $height && 'full' !== $size && array_key_exists( $size, $image_sizes ) ) {
837
- $width = (int) $image_sizes[ $size ]['width'];
838
- $height = (int) $image_sizes[ $size ]['height'];
839
- $transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
840
- }
841
- } else {
842
- unset( $size );
843
  }
844
 
845
- list( $filename_width, $filename_height ) = $this->get_dimensions_from_filename( $src );
846
- if ( false === $width && false === $height ) {
847
- $width = $filename_width;
848
- $height = $filename_height;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
849
  }
850
- // WP Attachment ID, if uploaded to this site.
851
- $attachment_id = $this->get_attribute( $images['img_tag'][ $index ], 'data-id' );
852
- if ( empty( $attachment_id ) ) {
853
- ewwwio_debug_message( 'data-id not found, looking for wp-image-x in class' );
854
- preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id );
 
 
 
855
  }
856
- if ( ! ewww_image_optimizer_get_option( 'exactdn_prevent_db_queries' ) && empty( $attachment_id ) ) {
857
- ewwwio_debug_message( 'looking for attachment id' );
858
- $attachment_id = attachment_url_to_postid( $src );
859
  }
860
- if ( ! ewww_image_optimizer_get_option( 'exactdn_prevent_db_queries' ) && ! empty( $attachment_id ) ) {
861
- if ( is_array( $attachment_id ) ) {
862
- $attachment_id = intval( array_pop( $attachment_id ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
863
  }
864
- ewwwio_debug_message( "using attachment id ($attachment_id) to get source image" );
865
-
866
- if ( $attachment_id ) {
867
- ewwwio_debug_message( "detected attachment $attachment_id" );
868
- $attachment = get_post( $attachment_id );
869
-
870
- // Basic check on returned post object.
871
- if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' === $attachment->post_type ) {
872
- $src_per_wp = wp_get_attachment_image_src( $attachment_id, 'full' );
873
-
874
- if ( $src_per_wp && is_array( $src_per_wp ) ) {
875
- ewwwio_debug_message( "src retrieved from db: {$src_per_wp[0]}, checking for match" );
876
- $fullsize_url_path = $this->parse_url( $src_per_wp[0], PHP_URL_PATH );
877
- if ( is_null( $fullsize_url_path ) ) {
878
- $src_per_wp = false;
879
- } elseif ( $fullsize_url_path ) {
880
- $fullsize_url_basename = pathinfo( $fullsize_url_path, PATHINFO_FILENAME );
881
- ewwwio_debug_message( "looking for $fullsize_url_basename in $src" );
882
- if ( strpos( wp_basename( $src ), $fullsize_url_basename ) === false ) {
883
- ewwwio_debug_message( 'fullsize url does not match' );
 
 
 
 
 
 
 
 
 
 
 
884
  $src_per_wp = false;
885
  }
886
- } else {
887
- $src_per_wp = false;
888
  }
889
- }
890
 
891
- if ( $src_per_wp && $this->validate_image_url( $src_per_wp[0] ) ) {
892
- ewwwio_debug_message( "detected $width filenamew $filename_width" );
893
- if ( $resize_existing || ( $width && (int) $filename_width !== (int) $width ) ) {
894
- ewwwio_debug_message( 'resizing existing or width does not match' );
895
- $src = $src_per_wp[0];
896
- }
897
- $fullsize_url = true;
898
 
899
- // Prevent image distortion if a detected dimension exceeds the image's natural dimensions.
900
- if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) {
901
- $width = false === $width ? false : min( $width, $src_per_wp[1] );
902
- $height = false === $height ? false : min( $height, $src_per_wp[2] );
903
- ewwwio_debug_message( "constrained to attachment dims, w=$width and h=$height" );
904
- }
905
 
906
- // If no width and height are found, max out at source image's natural dimensions.
907
- // Otherwise, respect registered image sizes' cropping setting.
908
- if ( false === $width && false === $height ) {
909
- $width = $src_per_wp[1];
910
- $height = $src_per_wp[2];
911
- $transform = 'fit';
912
- ewwwio_debug_message( "no dims, using attachment dims, w=$width and h=$height" );
913
- } elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) {
914
- $transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
915
- ewwwio_debug_message( 'attachment size set to crop' );
 
916
  }
 
 
 
917
  }
918
- } else {
919
- unset( $attachment_id );
920
- unset( $attachment );
921
  }
922
  }
923
- }
924
- $constrain_width = (int) $content_width;
925
- if ( ! empty( $images['figure_class'][ $index ] ) && false !== strpos( $images['figure_class'][ $index ], 'alignfull' ) && current_theme_supports( 'align-wide' ) ) {
926
- $constrain_width = (int) apply_filters( 'exactdn_full_align_image_width', max( 1920, $content_width ) );
927
- } elseif ( ! empty( $images['figure_class'][ $index ] ) && false !== strpos( $images['figure_class'][ $index ], 'alignwide' ) && current_theme_supports( 'align-wide' ) ) {
928
- $constrain_width = (int) apply_filters( 'exactdn_wide_align_image_width', max( 1500, $content_width ) );
929
- }
930
- // If width is available, constrain to $content_width.
931
- if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $constrain_width ) ) {
932
- if ( $width > $constrain_width && false !== $height && false === strpos( $height, '%' ) ) {
933
- ewwwio_debug_message( 'constraining to content width' );
934
- $height = round( ( $constrain_width * $height ) / $width );
935
- $width = $constrain_width;
936
- } elseif ( $width > $constrain_width ) {
937
- ewwwio_debug_message( 'constraining to content width' );
938
- $width = $constrain_width;
939
  }
940
- }
941
-
942
- // Set a width if none is found and $content_width is available.
943
- // If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing.
944
- if ( false === $width && is_numeric( $constrain_width ) ) {
945
- $width = (int) $constrain_width;
946
-
947
- if ( false !== $height ) {
948
- $transform = 'fit';
 
949
  }
950
- }
951
 
952
- // Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation.
953
- if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode( '|', $this->extensions ) . '){1}$#i', wp_basename( $src ), $filename ) ) {
954
- $fullsize_url = true;
955
- }
956
 
957
- // Build array of ExactDN args and expose to filter before passing to ExactDN URL function.
958
- $args = array();
 
 
959
 
960
- if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) ) {
961
- $args[ $transform ] = $width . ',' . $height;
962
- } elseif ( false !== $width ) {
963
- $args['w'] = $width;
964
- } elseif ( false !== $height ) {
965
- $args['h'] = $height;
966
- }
967
 
968
- if ( ! $resize_existing && ( ! $width || (int) $filename_width === (int) $width ) ) {
969
- ewwwio_debug_message( 'preventing resize' );
970
  $args = array();
971
- } elseif ( ! $fullsize_url ) {
972
- // Build URL, first maybe removing WP's resized string so we pass the original image to ExactDN (for higher quality).
973
- $src = $this->strip_image_dimensions_maybe( $src );
974
- }
975
-
976
- if ( ! ewww_image_optimizer_get_option( 'exactdn_prevent_db_queries' ) && ! empty( $attachment_id ) ) {
977
- ewwwio_debug_message( 'using attachment id to check smart crop' );
978
- $args = $this->maybe_smart_crop( $args, $attachment_id );
979
- }
980
-
981
- /**
982
- * Filter the array of ExactDN arguments added to an image.
983
- * By default, only includes width and height values.
984
- *
985
- * @param array $args Array of ExactDN Arguments.
986
- * @param array $args {
987
- * Array of image details.
988
- *
989
- * @type $tag Image tag (Image HTML output).
990
- * @type $src Image URL.
991
- * @type $src_orig Original Image URL.
992
- * @type $width Image width.
993
- * @type $height Image height.
994
- * }
995
- */
996
- $args = apply_filters( 'exactdn_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height' ) );
997
- ewwwio_debug_message( "width $width" );
998
- ewwwio_debug_message( "height $height" );
999
- ewwwio_debug_message( "transform $transform" );
1000
 
1001
- $exactdn_url = $this->generate_url( $src, $args );
1002
- ewwwio_debug_message( "new url $exactdn_url" );
 
 
 
 
 
1003
 
1004
- // Modify image tag if ExactDN function provides a URL
1005
- // Ensure changes are only applied to the current image by copying and modifying the matched tag, then replacing the entire tag with our modified version.
1006
- if ( $src !== $exactdn_url ) {
1007
- $new_tag = $tag;
 
 
 
1008
 
1009
- // If present, replace the link href with an ExactDN URL for the full-size image.
1010
- if ( ! empty( $images['link_url'][ $index ] ) && $this->validate_image_url( $images['link_url'][ $index ] ) ) {
1011
- $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . $this->generate_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 );
1012
  }
1013
 
1014
- // Insert new image src into the srcset as well, if we have a width.
1015
- if ( false !== $width && false === strpos( $width, '%' ) && $width ) {
1016
- ewwwio_debug_message( 'checking to see if srcset width already exists' );
1017
- $srcset_url = $exactdn_url . ' ' . (int) $width . 'w, ';
1018
- $new_srcset_attr = $this->get_attribute( $new_tag, $this->srcset_attr );
1019
- if ( $new_srcset_attr && false === strpos( $new_srcset_attr, ' ' . (int) $width . 'w' ) && ! preg_match( '/\s(1|2|3)x/', $new_srcset_attr ) ) {
1020
- ewwwio_debug_message( 'src not in srcset, adding' );
1021
- $this->set_attribute( $new_tag, $this->srcset_attr, $srcset_url . $new_srcset_attr, true );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1022
  }
1023
- }
1024
 
1025
- // Check if content width pushed the respimg sizes attribute too far down.
1026
- if ( ! empty( $constrain_width ) && (int) $constrain_width !== (int) $content_width ) {
1027
- $sizes_attr = $this->get_attribute( $new_tag, 'sizes' );
1028
- $new_sizes_attr = str_replace( ' ' . $content_width . 'px', ' ' . $constrain_width . 'px', $sizes_attr );
1029
- if ( $sizes_attr !== $new_sizes_attr ) {
1030
- $new_tag = str_replace( $sizes_attr, $new_sizes_attr, $new_tag );
 
 
 
1031
  }
1032
- }
1033
 
1034
- // Cleanup ExactDN URL.
1035
- $exactdn_url = str_replace( '&#038;', '&', esc_url( $exactdn_url ) );
1036
- // Supplant the original source value with our ExactDN URL.
1037
- ewwwio_debug_message( "replacing $src_orig with $exactdn_url" );
1038
- $new_tag = str_replace( $src_orig, $exactdn_url, $new_tag );
 
 
 
1039
 
1040
- // If Lazy Load is in use, pass placeholder image through ExactDN.
1041
- if ( isset( $placeholder_src ) && $this->validate_image_url( $placeholder_src ) ) {
1042
- $placeholder_src = $this->generate_url( $placeholder_src );
 
 
1043
 
1044
- if ( $placeholder_src !== $placeholder_src_orig ) {
1045
- $new_tag = str_replace( $placeholder_src_orig, str_replace( '&#038;', '&', esc_url( $placeholder_src ) ), $new_tag );
1046
- }
1047
 
1048
- unset( $placeholder_src );
1049
- }
 
1050
 
1051
- // Replace original tag with modified version.
1052
- $content = str_replace( $tag, $new_tag, $content );
1053
- if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) && strpos( $content, $exactdn_url ) ) {
1054
- ewwwio_debug_message( 'new image properly inserted' );
1055
- } elseif ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
1056
- ewwwio_debug_message( 'new url NOT found' );
1057
- }
1058
- }
1059
- } elseif ( ! $lazy && $this->validate_image_url( $src, true ) ) {
1060
- ewwwio_debug_message( "found a potential exactdn src url to insert into srcset: $src" );
1061
- // Find the width attribute.
1062
- $width = $this->get_img_width( $images['img_tag'][ $index ] );
1063
- if ( $width ) {
1064
- ewwwio_debug_message( 'found the width' );
1065
- // Insert new image src into the srcset as well, if we have a width.
1066
- if (
1067
- false !== $width &&
1068
- false === strpos( $width, '%' ) &&
1069
- false !== strpos( $src, $width ) &&
1070
- false !== strpos( $src, $this->exactdn_domain )
1071
- ) {
1072
- $new_tag = $tag;
1073
- $exactdn_url = $src;
1074
- ewwwio_debug_message( 'checking to see if srcset width already exists' );
1075
- $srcset_url = $exactdn_url . ' ' . (int) $width . 'w, ';
1076
- $new_srcset_attr = $this->get_attribute( $new_tag, $this->srcset_attr );
1077
- if ( $new_srcset_attr && false === strpos( $new_srcset_attr, ' ' . (int) $width . 'w' ) && ! preg_match( '/\s(1|2|3)x/', $new_srcset_attr ) ) {
1078
- ewwwio_debug_message( 'src not in srcset, adding' );
1079
- $this->set_attribute( $new_tag, $this->srcset_attr, $srcset_url . $new_srcset_attr, true );
1080
- // Replace original tag with modified version.
1081
- $content = str_replace( $tag, $new_tag, $content );
1082
  }
1083
- }
1084
- }
1085
- } elseif ( $lazy && ! empty( $placeholder_src ) && $this->validate_image_url( $placeholder_src ) ) {
1086
- $new_tag = $tag;
1087
- // If Lazy Load is in use, pass placeholder image through ExactDN.
1088
- $placeholder_src = $this->generate_url( $placeholder_src );
1089
- if ( $placeholder_src !== $placeholder_src_orig ) {
1090
- $new_tag = str_replace( $placeholder_src_orig, str_replace( '&#038;', '&', esc_url( $placeholder_src ) ), $new_tag );
1091
- // Replace original tag with modified version.
1092
- $content = str_replace( $tag, $new_tag, $content );
1093
- }
1094
- unset( $placeholder_src );
1095
- } // End if().
1096
 
1097
- // At this point, we discard the original src in favor of the ExactDN url.
1098
- if ( ! empty( $exactdn_url ) ) {
1099
- $src = $exactdn_url;
1100
- }
1101
- if ( $srcset_fill && ( ! defined( 'EXACTDN_PREVENT_SRCSET_FILL' ) || ! EXACTDN_PREVENT_SRCSET_FILL ) && false !== strpos( $src, $this->exactdn_domain ) ) {
1102
- if ( ! $this->get_attribute( $images['img_tag'][ $index ], $this->srcset_attr ) && ! $this->get_attribute( $images['img_tag'][ $index ], 'sizes' ) ) {
1103
- ewwwio_debug_message( "srcset filling with $src" );
1104
- $zoom = false;
1105
- // If $width is empty, we'll search the url for a width param, then we try searching the img element, with fall back to the filename.
1106
- if ( empty( $width ) || ! is_numeric( $width ) ) {
1107
- // This only searches for w, resize, or fit flags, others are ignored.
1108
- $width = $this->get_exactdn_width_from_url( $src );
1109
- if ( $width ) {
1110
- $zoom = true;
1111
- }
1112
- }
1113
- if ( empty( $width ) || ! is_numeric( $width ) ) {
1114
- $width = $this->get_img_width( $images['img_tag'][ $index ] );
1115
  }
1116
- list( $filename_width, $discard_height ) = $this->get_dimensions_from_filename( $src );
1117
- if ( empty( $width ) || ! is_numeric( $width ) ) {
1118
- $width = $filename_width;
1119
- }
1120
- if ( empty( $width ) || ! is_numeric( $width ) ) {
1121
- $width = $this->get_attribute( $images['img_tag'][ $index ], 'data-actual-width' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1122
  }
1123
- if ( false !== strpos( $src, 'crop=' ) || false !== strpos( $src, '&h=' ) || false !== strpos( $src, '?h=' ) ) {
1124
- $width = false;
 
 
 
 
 
 
1125
  }
1126
- // Then add a srcset and sizes.
1127
- if ( $width ) {
1128
- $srcset = $this->generate_image_srcset( $src, $width, $zoom, $filename_width );
1129
- if ( $srcset ) {
1130
- $new_tag = $images['img_tag'][ $index ];
1131
- $this->set_attribute( $new_tag, $this->srcset_attr, $srcset );
1132
- $this->set_attribute( $new_tag, 'sizes', sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width ) );
1133
- // Replace original tag with modified version.
1134
- $content = str_replace( $images['img_tag'][ $index ], $new_tag, $content );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1135
  }
1136
  }
1137
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1138
  }
1139
- } // End foreach().
1140
- } // End if();
1141
- $content = $this->filter_bg_images( $content );
1142
- if ( $this->filtering_the_page ) {
1143
- $content = $this->filter_prz_thumb( $content );
1144
- }
1145
- if ( $this->filtering_the_page && ewww_image_optimizer_get_option( 'exactdn_all_the_things' ) ) {
1146
- ewwwio_debug_message( 'rewriting all other wp_content urls' );
1147
- if ( $this->exactdn_domain && $this->upload_domain ) {
1148
- $escaped_upload_domain = str_replace( '.', '\.', ltrim( $this->upload_domain, 'w.' ) );
1149
- ewwwio_debug_message( $escaped_upload_domain );
1150
- if ( ! empty( $this->user_exclusions ) ) {
1151
- $content = preg_replace( '#(https?:)?//(?:www\.)?' . $escaped_upload_domain . '([^"\'?>]+?)?/wp-content/([^"\'?>]+?)?(' . implode( '|', $this->user_exclusions ) . ')#i', '$1//' . $this->upload_domain . '$2/?wpcontent-bypass?/$3$4', $content );
1152
- }
1153
- if ( strpos( $content, '<use ' ) ) {
1154
- // Pre-empt rewriting of files within <use> tags, particularly to prevent security errors for SVGs.
1155
- $content = preg_replace( '#(<use.+?href=["\'])(https?:)?//(?:www\.)?' . $escaped_upload_domain . '([^"\'?>]+?)/wp-content/#is', '$1$2//' . $this->upload_domain . '$3/?wpcontent-bypass?/', $content );
1156
- }
1157
- // Pre-empt rewriting of wp-includes and wp-content if the extension is not allowed by using a temporary placeholder.
1158
- $content = preg_replace( '#(https?:)?//(?:www\.)?' . $escaped_upload_domain . '([^"\'?>]+?)?/wp-content/([^"\'?>]+?)\.(htm|html|php|ashx|m4v|mov|wvm|qt|webm|ogv|mp4|m4p|mpg|mpeg|mpv)#i', '$1//' . $this->upload_domain . '$2/?wpcontent-bypass?/$3.$4', $content );
1159
- $content = str_replace( 'wp-content/themes/jupiter"', '?wpcontent-bypass?/themes/jupiter"', $content );
1160
- $content = str_replace( 'wp-content/plugins/anti-captcha/', '?wpcontent-bypass?/plugins/anti-captcha', $content );
1161
- if ( strpos( $this->upload_domain, 'amazonaws.com' ) || strpos( $this->upload_domain, 'digitaloceanspaces.com' ) ) {
1162
- $content = preg_replace( '#(https?:)?//(?:www\.)?' . $escaped_upload_domain . $this->remove_path . '/#i', '$1//' . $this->exactdn_domain . '/', $content );
1163
- } else {
1164
- $content = preg_replace( '#(https?:)?//(?:www\.)?' . $escaped_upload_domain . '/([^"\'?>]+?)?(nextgen-image|wp-includes|wp-content)/#i', '$1//' . $this->exactdn_domain . '/$2$3/', $content );
1165
- }
1166
- $content = str_replace( '?wpcontent-bypass?', 'wp-content', $content );
1167
  }
 
 
 
 
 
 
 
 
 
 
 
1168
  }
1169
- ewwwio_debug_message( 'done parsing page' );
1170
- $this->filtering_the_content = false;
1171
-
1172
- $elapsed_time = microtime( true ) - $started;
1173
- ewwwio_debug_message( "parsing the_content took $elapsed_time seconds" );
1174
- $this->elapsed_time += microtime( true ) - $started;
1175
- ewwwio_debug_message( "parsing the page took $this->elapsed_time seconds so far" );
1176
- if ( ! ewww_image_optimizer_get_option( 'exactdn_prevent_db_queries' ) && $this->elapsed_time > .5 ) {
1177
- ewww_image_optimizer_set_option( 'exactdn_prevent_db_queries', true );
1178
- }
1179
- return $content;
1180
- }
1181
 
1182
- /**
1183
- * Parse page content looking for elements with CSS background-image properties.
1184
- *
1185
- * @param string $content The HTML content to parse.
1186
- * @return string The filtered HTML content.
1187
- */
1188
- function filter_bg_images( $content ) {
1189
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
1190
- $content_width = false;
1191
- if ( ! $this->filtering_the_page ) {
1192
- $content_width = $this->get_content_width();
1193
- }
1194
- ewwwio_debug_message( "content width is $content_width" );
1195
- // Process background images on 'div' elements.
1196
- $divs = $this->get_elements_from_html( $content, 'div' );
1197
- if ( ewww_image_optimizer_iterable( $divs ) ) {
1198
- foreach ( $divs as $index => $div ) {
1199
- ewwwio_debug_message( 'parsing a div' );
1200
- if ( false === strpos( $div, 'background:' ) && false === strpos( $div, 'background-image:' ) ) {
1201
- continue;
1202
- }
1203
- $style = $this->get_attribute( $div, 'style' );
1204
- if ( empty( $style ) ) {
1205
- continue;
1206
- }
1207
- ewwwio_debug_message( "checking style attr for background-image: $style" );
1208
- $bg_image_url = $this->get_background_image_url( $style );
1209
- if ( $this->validate_image_url( $bg_image_url ) ) {
1210
- /** This filter is already documented in class-exactdn.php */
1211
- if ( apply_filters( 'exactdn_skip_image', false, $bg_image_url, $div ) ) {
1212
  continue;
1213
  }
1214
- $args = array();
1215
- $div_class = $this->get_attribute( $div, 'class' );
1216
- if ( false !== strpos( $div_class, 'alignfull' ) && current_theme_supports( 'align-wide' ) ) {
1217
- $args['w'] = apply_filters( 'exactdn_full_align_bgimage_width', 1920, $bg_image_url );
1218
- } elseif ( false !== strpos( $div_class, 'alignwide' ) && current_theme_supports( 'align-wide' ) ) {
1219
- $args['w'] = apply_filters( 'exactdn_wide_align_bgimage_width', 1500, $bg_image_url );
1220
- } elseif ( $content_width ) {
1221
- $args['w'] = apply_filters( 'exactdn_content_bgimage_width', $content_width, $bg_image_url );
1222
  }
1223
- if ( isset( $args['w'] ) && empty( $args['w'] ) ) {
1224
- unset( $args['w'] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1225
  }
1226
- $exactdn_bg_image_url = $this->generate_url( $bg_image_url, $args );
1227
- if ( $bg_image_url !== $exactdn_bg_image_url ) {
1228
- $new_style = str_replace( $bg_image_url, $exactdn_bg_image_url, $style );
1229
- $div = str_replace( $style, $new_style, $div );
1230
  }
1231
  }
1232
- if ( $div !== $divs[ $index ] ) {
1233
- $content = str_replace( $divs[ $index ], $div, $content );
1234
- }
1235
  }
1236
- }
1237
- return $content;
1238
- }
1239
-
1240
- /**
1241
- * Parse page content looking for thumburl from personalization.com.
1242
- *
1243
- * @param string $content The HTML content to parse.
1244
- * @return string The filtered HTML content.
1245
- */
1246
- function filter_prz_thumb( $content ) {
1247
- if ( ! class_exists( 'WooCommerce' ) || false === strpos( $content, 'productDetailsForPrz' ) ) {
1248
  return $content;
1249
  }
1250
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
1251
- $prz_match = preg_match( '#productDetailsForPrz=[^<]+?thumbnailUrl:\'([^\']+?)\'[^<]+?</script>#', $content, $prz_detail_matches );
1252
- if ( $prz_match && ! empty( $prz_detail_matches[1] ) && $this->validate_image_url( $prz_detail_matches[1] ) ) {
1253
- $prz_thumb = $this->generate_url( $prz_detail_matches[1], apply_filters( 'exactdn_personalizationdotcom_thumb_args', '', $prz_detail_matches[1] ) );
1254
- if ( $prz_thumb !== $prz_detail_matches ) {
1255
- $content = str_replace( "thumbnailUrl:'{$prz_detail_matches[1]}'", "thumbnailUrl:'$prz_thumb'", $content );
 
 
 
 
 
 
 
 
 
 
 
 
1256
  }
 
1257
  }
1258
- return $content;
1259
- }
1260
 
1261
- /**
1262
- * Allow resizing of images for some admin-ajax requests.
1263
- *
1264
- * @param bool $allow Will normally be false, unless already modified by another function.
1265
- * @param array $image Bunch of information about the image, but we don't care about that here.
1266
- * @return bool True if it's an allowable admin-ajax request, false for all other admin requests.
1267
- */
1268
- function allow_admin_image_downsize( $allow, $image ) {
1269
- if ( ! wp_doing_ajax() ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1270
  return $allow;
1271
  }
1272
- if ( ! empty( $_POST['action'] ) && 'eddvbugm_viewport_downloads' === $_POST['action'] ) {
1273
- return true;
1274
- }
1275
- if ( ! empty( $_POST['action'] ) && 'vc_get_vc_grid_data' === $_POST['action'] ) {
1276
- return true;
1277
- }
1278
- if ( ! empty( $_POST['action'] ) && 'Essential_Grid_Front_request_ajax' === $_POST['action'] ) {
1279
- return true;
1280
- }
1281
- if ( ! empty( $_POST['action'] ) && 'mabel-rpn-getnew-purchased-products' === $_POST['action'] ) {
1282
- return true;
1283
- }
1284
- return $allow;
1285
- }
1286
 
1287
- /**
1288
- * Disable resizing of images during image_downsize().
1289
- *
1290
- * @param mixed $param Could be anything (or nothing), we just pass it along untouched.
1291
- * @return mixed Just the same value, going back out the door.
1292
- */
1293
- function disable_image_downsize( $param = false ) {
1294
- remove_filter( 'image_downsize', array( $this, 'filter_image_downsize' ) );
1295
- add_action( 'themify_after_post_image', array( $this, 'enable_image_downsize' ) );
1296
- return $param;
1297
- }
1298
 
1299
- /**
1300
- * Re-enable resizing of images during image_downsize().
1301
- */
1302
- function enable_image_downsize() {
1303
- add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
1304
- }
1305
 
1306
- /**
1307
- * Change the default for processing an array of dimensions to scaling instead of cropping.
1308
- *
1309
- * @param array $exactdn_args The ExactDN args generated by filter_image_downsize() when $size is an array.
1310
- * @return array $exactdn_args The ExactDN args, with resize (crop) changed to fit (scale).
1311
- */
1312
- function image_downsize_scale( $exactdn_args ) {
1313
- if ( ! is_array( $exactdn_args ) ) {
 
 
 
 
 
 
1314
  return $exactdn_args;
1315
  }
1316
- if ( ! empty( $exactdn_args['resize'] ) ) {
1317
- $exactdn_args['fit'] = $exactdn_args['resize'];
1318
- unset( $exactdn_args['resize'] );
1319
- }
1320
- return $exactdn_args;
1321
- }
1322
 
1323
- /**
1324
- * Filter post thumbnail image retrieval, passing images through ExactDN.
1325
- *
1326
- * @param array|bool $image Defaults to false, but may be a url if another plugin/theme has already filtered the value.
1327
- * @param int $attachment_id The ID number for the image attachment.
1328
- * @param string|array $size The name of the image size or an array of width and height. Default 'medium'.
1329
- * @uses is_admin, apply_filters, wp_get_attachment_url, this::validate_image_url, this::image_sizes, this::generate_url
1330
- * @filter image_downsize
1331
- * @return string|bool
1332
- */
1333
- function filter_image_downsize( $image, $attachment_id, $size ) {
1334
- $started = microtime( true );
1335
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1336
 
1337
- // Don't foul up the admin side of things, unless a plugin wants to.
1338
- if ( is_admin() &&
1339
  /**
1340
- * Provide plugins a way of running ExactDN for images in the WordPress Dashboard (wp-admin).
1341
- *
1342
- * Note: enabling this will result in ExactDN URLs added to your post content, which could make migrations across domains (and off ExactDN) a bit more challenging.
1343
  *
1344
- * @param bool false Allow ExactDN to run on the Dashboard. Default to false.
1345
  * @param array $args {
1346
  * Array of image details.
1347
  *
1348
- * @type array|bool $image Image URL or false.
1349
  * @type int $attachment_id Attachment ID of the image.
1350
  * @type array|string $size Image size. Can be a string (name of the image size, e.g. full) or an array of height and width.
1351
  * }
1352
  */
1353
- false === apply_filters( 'exactdn_admin_allow_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) )
1354
- ) {
1355
- return $image;
1356
- }
1357
 
1358
- /**
1359
- * Provide plugins a way of preventing ExactDN from being applied to images retrieved from WordPress Core.
1360
- *
1361
- * @param bool false Stop ExactDN from being applied to the image. Default to false.
1362
- * @param array $args {
1363
- * Array of image details.
1364
- *
1365
- * @type string|bool $image Image URL or false.
1366
- * @type int $attachment_id Attachment ID of the image.
1367
- * @type array|string $size Image size. Can be a string (name of the image size, e.g. full) or an array of height and width.
1368
- * }
1369
- */
1370
- if ( apply_filters( 'exactdn_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) ) ) {
1371
- return $image;
1372
- }
1373
 
1374
- if ( function_exists( 'aq_resize' ) ) {
1375
- ewwwio_debug_message( 'aq_resize detected, image_downsize filter disabled' );
1376
- return $image;
1377
- }
1378
-
1379
- if ( $this->filtering_the_content || $this->filtering_the_page ) {
1380
- ewwwio_debug_message( 'end image_downsize early' );
1381
- return $image;
1382
- }
1383
-
1384
- // Get the image URL and proceed with ExactDN replacement if successful.
1385
- $image_url = wp_get_attachment_url( $attachment_id );
1386
- /** This filter is already documented in class-exactdn.php */
1387
- if ( apply_filters( 'exactdn_skip_image', false, $image_url, null ) ) {
1388
- return $image;
1389
- }
1390
- ewwwio_debug_message( $image_url );
1391
- ewwwio_debug_message( $attachment_id );
1392
- if ( is_string( $size ) || is_int( $size ) ) {
1393
- ewwwio_debug_message( $size );
1394
- } elseif ( is_array( $size ) ) {
1395
- foreach ( $size as $dimension ) {
1396
- ewwwio_debug_message( 'dimension: ' . $dimension );
1397
  }
1398
- }
1399
- // Set this to true later when we know we have size meta.
1400
- $has_size_meta = false;
1401
 
1402
- if ( $image_url ) {
1403
- // Check if image URL should be used with ExactDN.
1404
- if ( ! $this->validate_image_url( $image_url ) ) {
 
1405
  return $image;
1406
  }
 
 
 
 
 
 
 
 
 
 
 
1407
 
1408
- $intermediate = true; // For the fourth array item returned by the image_downsize filter.
1409
- $resize_existing = defined( 'EXACTDN_RESIZE_EXISTING' ) && EXACTDN_RESIZE_EXISTING;
 
 
 
1410
 
1411
- // If an image is requested with a size known to WordPress, use that size's settings with ExactDN.
1412
- if ( is_string( $size ) && array_key_exists( $size, $this->image_sizes() ) ) {
1413
- $image_args = $this->image_sizes();
1414
- $image_args = $image_args[ $size ];
1415
- ewwwio_debug_message( "image args for $size: " . ewwwio_implode( ',', $image_args ) );
1416
 
1417
- $exactdn_args = array();
 
 
 
 
1418
 
1419
- $image_meta = image_get_intermediate_size( $attachment_id, $size );
1420
 
1421
- // 'full' is a special case: We need consistent data regardless of the requested size.
1422
- if ( 'full' === $size ) {
1423
- $image_meta = wp_get_attachment_metadata( $attachment_id );
1424
- $intermediate = false;
1425
- } elseif ( ! $image_meta ) {
1426
- ewwwio_debug_message( 'still do not have meta, getting it now' );
1427
- // If we still don't have any image meta at this point, it's probably from a custom thumbnail size
1428
- // for an image that was uploaded before the custom image was added to the theme. Try to determine the size manually.
1429
- $image_meta = wp_get_attachment_metadata( $attachment_id );
1430
 
1431
- if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
1432
- $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $image_args['width'], $image_args['height'], $image_args['crop'] );
1433
- if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
1434
- $image_meta['width'] = $image_resized[6];
1435
- $image_meta['height'] = $image_resized[7];
 
 
 
 
 
 
 
 
 
 
 
1436
  }
1437
  }
1438
- }
1439
- if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
1440
- $image_args['width'] = $image_meta['width'];
1441
- $image_args['height'] = $image_meta['height'];
1442
-
1443
- // NOTE: it will constrain an image to $content_width which is expected behavior in core, so far as I can see.
1444
- list( $image_args['width'], $image_args['height'] ) = image_constrain_size_for_editor( $image_args['width'], $image_args['height'], $size, 'display' );
1445
-
1446
- $has_size_meta = true;
1447
- ewwwio_debug_message( 'image args constrained: ' . ewwwio_implode( ',', $image_args ) );
1448
- }
1449
 
1450
- $transform = $image_args['crop'] ? 'resize' : 'fit';
 
1451
 
1452
- // Check specified image dimensions and account for possible zero values; ExactDN fails to resize if a dimension is zero.
1453
- if ( ! $image_args['width'] || ! $image_args['height'] ) {
1454
- if ( ! $image_args['width'] && 0 < $image_args['height'] ) {
1455
- $exactdn_args['h'] = $image_args['height'];
1456
- } elseif ( ! $image_args['height'] && 0 < $image_args['width'] ) {
1457
- $exactdn_args['w'] = $image_args['width'];
1458
  }
1459
- } else {
1460
- if ( ! isset( $image_meta['sizes'] ) ) {
1461
- $size_meta = $image_meta;
1462
- // Because we don't have the "real" meta, just the height/width for the specific size.
1463
- ewwwio_debug_message( 'getting attachment meta now' );
1464
- $image_meta = wp_get_attachment_metadata( $attachment_id );
1465
- }
1466
- if ( 'resize' === $transform && $image_meta && isset( $image_meta['width'], $image_meta['height'] ) ) {
1467
- // Lets make sure that we don't upscale images since wp never upscales them as well.
1468
- $smaller_width = ( ( $image_meta['width'] < $image_args['width'] ) ? $image_meta['width'] : $image_args['width'] );
1469
- $smaller_height = ( ( $image_meta['height'] < $image_args['height'] ) ? $image_meta['height'] : $image_args['height'] );
1470
 
1471
- $exactdn_args[ $transform ] = $smaller_width . ',' . $smaller_height;
 
 
 
 
 
 
 
 
1472
  } else {
1473
- $exactdn_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1474
  }
1475
- }
1476
 
1477
- if (
1478
- ! empty( $image_meta['sizes'] ) && ! empty( $image_meta['width'] ) && ! empty( $image_meta['height'] ) &&
1479
- (int) $image_args['width'] === (int) $image_meta['width'] &&
1480
- (int) $image_args['height'] === (int) $image_meta['height']
1481
- ) {
1482
- ewwwio_debug_message( 'image args match size of original, just use that' );
1483
- $size = 'full';
1484
- }
1485
- if ( empty( $image_meta['sizes'] ) && ! empty( $size_meta ) ) {
1486
- $image_meta['sizes'][ $size ] = $size_meta;
1487
- }
1488
- if ( ! empty( $image_meta['sizes'] ) && 'full' !== $size && ! empty( $image_meta['sizes'][ $size ]['file'] ) ) {
1489
- $image_url_basename = wp_basename( $image_url );
1490
- $intermediate_url = str_replace( $image_url_basename, $image_meta['sizes'][ $size ]['file'], $image_url );
1491
-
1492
- if ( empty( $image_meta['width'] ) || empty( $image_meta['height'] ) ) {
1493
- list( $filename_width, $filename_height ) = $this->get_dimensions_from_filename( $intermediate_url );
1494
  }
1495
- $filename_width = $image_meta['width'] ? $image_meta['width'] : $filename_width;
1496
- $filename_height = $image_meta['height'] ? $image_meta['height'] : $filename_height;
1497
- if ( $filename_width && $filename_height && $image_args['width'] === $filename_width && $image_args['height'] === $filename_height ) {
1498
- $image_url = $intermediate_url;
 
 
 
 
 
 
 
 
 
 
1499
  } else {
1500
  $resize_existing = true;
1501
  }
1502
- } else {
1503
- $resize_existing = true;
1504
- }
1505
-
1506
- $exactdn_args = $resize_existing && 'full' !== $size ? $this->maybe_smart_crop( $exactdn_args, $attachment_id, $image_meta ) : array();
1507
 
1508
- /**
1509
- * Filter the ExactDN arguments added to an image, when that image size is a string.
1510
- * Image size will be a string (e.g. "full", "medium") when it is known to WordPress.
1511
- *
1512
- * @param array $exactdn_args ExactDN arguments.
1513
- * @param array $args {
1514
- * Array of image details.
1515
- *
1516
- * @type array $image_args Image arguments (width, height, crop).
1517
- * @type string $image_url Image URL.
1518
- * @type int $attachment_id Attachment ID of the image.
1519
- * @type string $size Image size name.
1520
- * @type string $transform Value can be resize or fit.
1521
- * }
1522
- */
1523
- $exactdn_args = apply_filters( 'exactdn_image_downsize_string', $exactdn_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
1524
-
1525
- // Generate ExactDN URL.
1526
- $image = array(
1527
- $this->generate_url( $image_url, $exactdn_args ),
1528
- $has_size_meta ? $image_args['width'] : false,
1529
- $has_size_meta ? $image_args['height'] : false,
1530
- $intermediate,
1531
- );
1532
- } elseif ( is_array( $size ) ) {
1533
- // Pull width and height values from the provided array, if possible.
1534
- $width = isset( $size[0] ) ? (int) $size[0] : false;
1535
- $height = isset( $size[1] ) ? (int) $size[1] : false;
1536
 
1537
- // Don't bother if necessary parameters aren't passed.
1538
- if ( ! $width || ! $height ) {
1539
- return $image;
1540
- }
1541
- ewwwio_debug_message( "requested w$width by h$height" );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1542
 
1543
- $image_meta = wp_get_attachment_metadata( $attachment_id );
1544
- if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
1545
- $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $width, $height, true );
1546
 
1547
- if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
1548
- $width = $image_resized[6];
1549
- $height = $image_resized[7];
1550
- ewwwio_debug_message( "using resize dims w$width by h$height" );
1551
- } else {
1552
- $width = $image_meta['width'];
1553
- $height = $image_meta['height'];
1554
- ewwwio_debug_message( "using meta dims w$width by h$height" );
 
 
1555
  }
1556
- $has_size_meta = true;
1557
- }
1558
 
1559
- list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
1560
- ewwwio_debug_message( "constrained to w$width by h$height" );
1561
 
1562
- // Expose arguments to a filter before passing to ExactDN.
1563
- $exactdn_args = array(
1564
- 'resize' => $width . ',' . $height,
1565
- );
1566
 
1567
- $exactdn_args = $this->maybe_smart_crop( $exactdn_args, $attachment_id, $image_meta );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1569
  /**
1570
- * Filter the ExactDN arguments added to an image, when the image size is an array of height and width values.
1571
  *
1572
- * @param array $exactdn_args ExactDN arguments/parameters.
1573
  * @param array $args {
1574
  * Array of image details.
1575
  *
1576
- * @type int $width Image width.
1577
- * @type int $height Image height.
1578
- * @type string $image_url Image URL.
1579
- * @type int $attachment_id Attachment ID of the image.
1580
  * }
1581
  */
1582
- $exactdn_args = apply_filters( 'exactdn_image_downsize_array', $exactdn_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
1583
-
1584
- // Generate ExactDN URL.
1585
- $image = array(
1586
- $this->generate_url( $image_url, $exactdn_args ),
1587
- $has_size_meta ? $width : false,
1588
- $has_size_meta ? $height : false,
1589
- $intermediate,
1590
- );
1591
  }
1592
- }
1593
- if ( ! empty( $image[0] ) && is_string( $image[0] ) ) {
1594
- ewwwio_debug_message( $image[0] );
1595
- }
1596
- ewwwio_debug_message( 'end image_downsize' );
1597
- $elapsed_time = microtime( true ) - $started;
1598
- ewwwio_debug_message( "parsing image_downsize took $elapsed_time seconds" );
1599
- $this->elapsed_time += microtime( true ) - $started;
1600
- return $image;
1601
- }
1602
-
1603
- /**
1604
- * Filters an array of image `srcset` values, replacing each URL with its ExactDN equivalent.
1605
- *
1606
- * @param array $sources An array of image urls and widths.
1607
- * @param array $size_array Array of width and height values in pixels.
1608
- * @param string $image_src The 'src' of the image.
1609
- * @param array $image_meta The image metadata as returned by 'wp_get_attachment_metadata()'.
1610
- * @param int $attachment_id Image attachment ID or 0.
1611
- * @uses this::validate_image_url, this::generate_url, this::parse_from_filename
1612
- * @uses this::strip_image_dimensions_maybe, this::get_content_width
1613
- * @return array An array of ExactDN image urls and widths.
1614
- */
1615
- public function filter_srcset_array( $sources = array(), $size_array = array(), $image_src = '', $image_meta = array(), $attachment_id = 0 ) {
1616
- $started = microtime( true );
1617
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
1618
- // Don't foul up the admin side of things, unless a plugin wants to.
1619
- if ( is_admin() &&
1620
- /**
1621
- * Provide plugins a way of running ExactDN for images in the WordPress Dashboard (wp-admin).
1622
- *
1623
- * @param bool false Stop ExactDN from being run on the Dashboard. Default to false, use true to run in wp-admin.
1624
- * @param array $args {
1625
- * Array of image details.
1626
- *
1627
- * @type string|bool $image Image URL or false.
1628
- * @type int $attachment_id Attachment ID of the image.
1629
- * }
1630
- */
1631
- false === apply_filters( 'exactdn_admin_allow_image_srcset', false, compact( 'image_src', 'attachment_id' ) )
1632
- ) {
1633
- return $sources;
1634
- }
1635
-
1636
- if ( ! is_array( $sources ) ) {
1637
- return $sources;
1638
- }
1639
 
1640
- $upload_dir = wp_get_upload_dir();
1641
- $resize_existing = defined( 'EXACTDN_RESIZE_EXISTING' ) && EXACTDN_RESIZE_EXISTING;
1642
- $w_descriptor = true;
1643
-
1644
- foreach ( $sources as $i => $source ) {
1645
- if ( 'x' === $source['descriptor'] ) {
1646
- $w_descriptor = false;
1647
- }
1648
- if ( ! $this->validate_image_url( $source['url'] ) ) {
1649
- continue;
1650
  }
1651
 
1652
- /** This filter is already documented in class-exactdn.php */
1653
- if ( apply_filters( 'exactdn_skip_image', false, $source['url'], $source ) ) {
1654
- continue;
1655
- }
1656
 
1657
- $url = $source['url'];
 
 
 
 
 
 
1658
 
1659
- list( $width, $height ) = $this->get_dimensions_from_filename( $url );
1660
- if ( ! $resize_existing && 'w' === $source['descriptor'] && (int) $source['value'] === (int) $width ) {
1661
- ewwwio_debug_message( "preventing further processing for $url" );
1662
- $sources[ $i ]['url'] = $this->generate_url( $source['url'] );
1663
- continue;
1664
- }
1665
 
1666
- if ( $image_meta && ! empty( $image_meta['width'] ) ) {
1667
- if ( ( $height && (int) $image_meta['height'] === (int) $height && $width && (int) $image_meta['width'] === (int) $width ) ||
1668
- ( ! $height && ! $width && (int) $image_meta['width'] === (int) $source['value'] )
1669
- ) {
1670
- ewwwio_debug_message( "preventing further processing for (detected) full-size $url" );
1671
  $sources[ $i ]['url'] = $this->generate_url( $source['url'] );
1672
  continue;
1673
  }
1674
- }
1675
 
1676
- ewwwio_debug_message( 'continuing: ' . $width . ' vs. ' . $source['value'] );
 
 
 
 
 
 
 
 
1677
 
1678
- // It's quicker to get the full size with the data we have already, if available.
1679
- if ( ! empty( $attachment_id ) ) {
1680
- $url = wp_get_attachment_url( $attachment_id );
1681
- } else {
1682
- $url = $this->strip_image_dimensions_maybe( $url );
1683
- }
1684
- ewwwio_debug_message( "building srcs from $url" );
1685
 
1686
- $args = array();
1687
- if ( 'w' === $source['descriptor'] ) {
1688
- if ( $height && ( (int) $source['value'] === (int) $width ) ) {
1689
- $args['resize'] = $width . ',' . $height;
1690
  } else {
1691
- $args['w'] = $source['value'];
1692
  }
1693
- }
1694
 
1695
- $args = $this->maybe_smart_crop( $args, $attachment_id, $image_meta );
 
 
 
 
 
 
 
1696
 
1697
- $sources[ $i ]['url'] = $this->generate_url( $url, $args );
1698
- }
1699
 
1700
- /**
1701
- * At this point, $sources is the original srcset with ExactDN URLs.
1702
- * Now, we're going to construct additional sizes based on multiples of the content_width.
1703
- */
1704
 
1705
- /**
1706
- * Filter the multiplier ExactDN uses to create new srcset items.
1707
- * Return false to short-circuit and bypass auto-generation.
1708
- *
1709
- * @param array|bool $multipliers Array of multipliers to use or false to bypass.
1710
- */
1711
- $multipliers = apply_filters( 'exactdn_srcset_multipliers', array( .2, .4, .6, .8, 1, 2, 3, 1920 ) );
1712
- $url = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
1713
- if ( ! $w_descriptor ) {
1714
- ewwwio_debug_message( 'using x descriptors instead of w' );
1715
- $multipliers = array_filter( $multipliers, 'is_int' );
1716
- }
1717
 
1718
- if (
1719
- /** Short-circuit via exactdn_srcset_multipliers filter. */
1720
- is_array( $multipliers )
1721
- /** This filter is already documented in class-exactdn.php */
1722
- && ! apply_filters( 'exactdn_skip_image', false, $url, null )
1723
- /** The original url is valid/allowed. */
1724
- && $this->validate_image_url( $url )
1725
- /** Verify basic meta is intact. */
1726
- && isset( $image_meta['width'] ) && isset( $image_meta['height'] ) && isset( $image_meta['file'] )
1727
- /** Verify we have the requested width/height. */
1728
- && isset( $size_array[0] ) && isset( $size_array[1] )
1729
- ) {
1730
-
1731
- $fullwidth = $image_meta['width'];
1732
- $fullheight = $image_meta['height'];
1733
- $reqwidth = $size_array[0];
1734
- $reqheight = $size_array[1];
1735
- ewwwio_debug_message( "filling additional sizes with requested w $reqwidth h $reqheight full w $fullwidth full h $fullheight" );
1736
-
1737
- $constrained_size = wp_constrain_dimensions( $fullwidth, $fullheight, $reqwidth );
1738
- $expected_size = array( $reqwidth, $reqheight );
1739
-
1740
- ewwwio_debug_message( $constrained_size[0] );
1741
- ewwwio_debug_message( $constrained_size[1] );
1742
- if ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ) {
1743
- ewwwio_debug_message( 'soft cropping' );
1744
- $crop = 'soft';
1745
- $base = $this->get_content_width(); // Provide a default width if none set by the theme.
1746
- } else {
1747
- ewwwio_debug_message( 'hard cropping' );
1748
- $crop = 'hard';
1749
- $base = $reqwidth;
1750
  }
1751
- ewwwio_debug_message( "base width: $base" );
1752
 
1753
- $currentwidths = array_keys( $sources );
1754
- $newsources = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1755
 
1756
- foreach ( $multipliers as $multiplier ) {
 
1757
 
1758
- $newwidth = intval( $base * $multiplier );
1759
- if ( 1920 === (int) $multiplier ) {
1760
- $newwidth = 1920;
1761
- if ( ! $w_descriptor ) {
1762
- continue;
 
 
 
1763
  }
1764
- }
1765
- if ( $newwidth < 50 ) {
1766
- continue;
1767
- }
1768
- foreach ( $currentwidths as $currentwidth ) {
1769
- // If a new width would be within 50 pixels of an existing one or larger than the full size image, skip.
1770
- if ( abs( $currentwidth - $newwidth ) < 50 || ( $newwidth > $fullwidth ) ) {
1771
- continue 2; // Back to the foreach ( $multipliers as $multiplier ).
1772
  }
1773
- } // foreach ( $currentwidths as $currentwidth ){
1774
-
1775
- if ( 1 === $multiplier && abs( $newwidth - $fullwidth ) < 5 ) {
1776
- $args = array();
1777
- } elseif ( 'soft' === $crop ) {
1778
- $args = array(
1779
- 'w' => $newwidth,
1780
- );
1781
- } else { // hard crop, e.g. add_image_size( 'example', 200, 200, true ).
1782
- $args = array(
1783
- 'zoom' => $multiplier,
1784
- 'resize' => $reqwidth . ',' . $reqheight,
1785
- );
1786
- }
1787
-
1788
- $args = $this->maybe_smart_crop( $args, $attachment_id, $image_meta );
1789
 
1790
- $newsources[ $newwidth ] = array(
1791
- 'url' => $this->generate_url( $url, $args ),
1792
- 'descriptor' => ( $w_descriptor ? 'w' : 'x' ),
1793
- 'value' => ( $w_descriptor ? $newwidth : $multiplier ),
1794
- );
 
 
 
 
 
 
 
1795
 
1796
- $currentwidths[] = $newwidth;
1797
- } // foreach ( $multipliers as $multiplier )
1798
 
1799
- if ( is_array( $newsources ) ) {
1800
- $sources = array_replace( $sources, $newsources );
1801
- }
1802
- } // if ( isset( $image_meta['width'] ) && isset( $image_meta['file'] ) )
1803
- $elapsed_time = microtime( true ) - $started;
1804
- ewwwio_debug_message( "parsing srcset took $elapsed_time seconds" );
1805
- /* ewwwio_debug_message( print_r( $sources, true ) ); */
1806
- $this->elapsed_time += microtime( true ) - $started;
1807
- return $sources;
1808
- }
1809
 
1810
- /**
1811
- * Filters an array of image `sizes` values, using $content_width instead of image's full size.
1812
- *
1813
- * @param array $sizes An array of media query breakpoints.
1814
- * @param array $size Width and height of the image.
1815
- * @uses this::get_content_width
1816
- * @return array An array of media query breakpoints.
1817
- */
1818
- public function filter_sizes( $sizes, $size ) {
1819
- $started = microtime( true );
1820
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
1821
- if ( ! doing_filter( 'the_content' ) ) {
1822
- return $sizes;
1823
- }
1824
- $content_width = $this->get_content_width();
1825
 
1826
- if ( ( is_array( $size ) && $size[0] < $content_width ) ) {
1827
- return $sizes;
 
 
 
 
 
 
 
1828
  }
1829
 
1830
- $elapsed_time = microtime( true ) - $started;
1831
- ewwwio_debug_message( "parsing sizes took $elapsed_time seconds" );
1832
- $this->elapsed_time += microtime( true ) - $started;
1833
- return sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $content_width );
1834
- }
 
 
 
 
 
 
 
 
 
 
1835
 
1836
- /**
1837
- * Creates an image `srcset` attribute based on the detected width.
1838
- *
1839
- * @param string $url The url of the image.
1840
- * @param int $width Image width to use for calculations.
1841
- * @param bool $zoom Whether to use zoom or w param.
1842
- * @param int $filename_width The width derived from the filename, or false.
1843
- * @uses this::generate_url
1844
- * @return string A srcset attribute with ExactDN image urls and widths.
1845
- */
1846
- public function generate_image_srcset( $url, $width, $zoom = false, $filename_width = false ) {
1847
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
1848
- // Don't foul up the admin side of things.
1849
- if ( is_admin() ) {
1850
- return '';
1851
- }
1852
 
1853
- if ( ! is_numeric( $width ) ) {
1854
- return '';
 
 
1855
  }
1856
 
1857
  /**
1858
- * Filter the multiplier ExactDN uses to create new srcset items.
1859
- * Return false to short-circuit and bypass auto-generation.
1860
- *
1861
- * @param array|bool $multipliers Array of multipliers to use or false to bypass.
1862
- */
1863
- $multipliers = apply_filters( 'exactdn_srcset_multipliers', array( .2, .4, .6, .8, 1, 2, 3, 1920 ) );
1864
- /**
1865
- * Filter the width ExactDN will use to create srcset attribute.
1866
- * Return a falsy value to short-circuit and bypass srcset fill.
1867
  *
1868
- * @param int|bool $width The max width for this $url, or false to bypass.
 
 
 
 
 
1869
  */
1870
- $width = (int) apply_filters( 'exactdn_srcset_fill_width', $width, $url );
1871
- if ( ! $width ) {
1872
- return '';
1873
- }
1874
- $srcset = '';
1875
- $currentwidths = array();
1876
 
1877
- if (
1878
- /** Short-circuit via exactdn_srcset_multipliers filter. */
1879
- is_array( $multipliers )
1880
- && $width
1881
- /** This filter is already documented in class-exactdn.php */
1882
- && ! apply_filters( 'exactdn_skip_image', false, $url, null )
1883
- ) {
1884
- $sources = null;
1885
-
1886
- foreach ( $multipliers as $multiplier ) {
1887
- $newwidth = intval( $width * $multiplier );
1888
- if ( 1920 === (int) $multiplier ) {
1889
- $newwidth = 1920;
1890
- }
1891
- if ( $newwidth < 50 ) {
1892
- continue;
1893
- }
1894
- foreach ( $currentwidths as $currentwidth ) {
1895
- // If a new width would be within 50 pixels of an existing one or larger than the full size image, skip.
1896
- if ( 1 !== $multiplier && abs( $currentwidth - $newwidth ) < 50 ) {
1897
- continue 2; // Back to the foreach ( $multipliers as $multiplier ).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1898
  }
1899
- } // foreach ( $currentwidths as $currentwidth ){
1900
- if ( $filename_width && $newwidth > $filename_width ) {
1901
- continue;
1902
- }
1903
 
1904
- if ( 1 === $multiplier ) {
1905
- $args = array();
1906
- } elseif ( $zoom ) {
1907
- $args = array(
1908
- 'zoom' => $multiplier,
1909
- );
1910
- } else {
1911
- $args = array(
1912
- 'w' => $newwidth,
1913
- );
1914
- }
1915
 
1916
- $sources[ $newwidth ] = array(
1917
- 'url' => $this->generate_url( $url, $args ),
1918
- 'descriptor' => 'w',
1919
- 'value' => $newwidth,
1920
- );
1921
 
1922
- $currentwidths[] = $newwidth;
 
1923
  }
1924
- }
1925
- if ( ! empty( $sources ) ) {
1926
- foreach ( $sources as $source ) {
1927
- $srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', ';
1928
  }
 
 
1929
  }
1930
- /* ewwwio_debug_message( print_r( $sources, true ) ); */
1931
- return rtrim( $srcset, ', ' );
1932
- }
1933
 
1934
- /**
1935
- * Check for smart-cropping plugin to adjust cropping parameters.
1936
- * Currently supports Theia Smart Thumbnails using the theiaSmartThumbnails_position meta.
1937
- *
1938
- * @param array $args The arguments that have been generated so far.
1939
- * @param int $attachment_id The ID number for the current image.
1940
- * @param array $meta Optional. The attachment (image) metadata. Default false.
1941
- * @return array The arguments, possibly altered for smart cropping.
1942
- */
1943
- function maybe_smart_crop( $args, $attachment_id, $meta = false ) {
1944
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
1945
- if ( ! empty( $args['crop'] ) ) {
1946
- ewwwio_debug_message( 'already cropped' );
1947
- return $args;
1948
- }
1949
- // Doing something other than a hard crop, or we don't know what the ID is.
1950
- if ( empty( $args['resize'] ) || empty( $attachment_id ) ) {
1951
- ewwwio_debug_message( 'not resizing, so no custom crop' );
1952
- return $args;
1953
- }
1954
- // TST is not active.
1955
- if ( ! defined( 'TST_VERSION' ) ) {
1956
- ewwwio_debug_message( 'no TST plugin' );
1957
- return $args;
1958
- }
1959
- if ( ! class_exists( 'TstPostOptions' ) || ! defined( 'TstPostOptions::META_POSITION' ) ) {
1960
- ewwwio_debug_message( 'no TstPostOptions class' );
1961
- return $args;
1962
- }
1963
- if ( ! $meta || ! is_array( $meta ) || empty( $meta['sizes'] ) ) {
1964
- // $focus_point = get_post_meta( $attachment_id, TstPostOptions::META_POSITION, true );
1965
- $meta = wp_get_attachment_metadata( $attachment_id );
1966
- if ( ! is_array( $meta ) || empty( $meta['width'] ) || empty( $meta['height'] ) ) {
1967
- ewwwio_debug_message( 'unusable meta retrieved' );
1968
  return $args;
1969
  }
1970
- $focus_point = TstPostOptions::get_meta( $attachment_id, $meta['width'], $meta['height'] );
1971
- } elseif ( ! empty( $meta['tst_thumbnail_version'] ) ) {
1972
- if ( empty( $meta['width'] ) || empty( $meta['height'] ) ) {
1973
- ewwwio_debug_message( 'unusable meta passed' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1974
  return $args;
1975
  }
1976
- $focus_point = TstPostOptions::get_meta( $attachment_id, $meta['width'], $meta['height'] );
1977
- } else {
1978
- ewwwio_debug_message( 'unusable meta' );
1979
- return $args;
1980
- }
1981
- if ( empty( $focus_point ) || ! is_array( $focus_point ) ) {
1982
- ewwwio_debug_message( 'unusable focus point' );
1983
- return $args;
1984
- }
1985
 
1986
- $dimensions = explode( ',', $args['resize'] );
1987
 
1988
- $new_w = $dimensions[0];
1989
- $new_h = $dimensions[1];
1990
- ewwwio_debug_message( "full size dims: w{$meta['width']} h{$meta['height']}" );
1991
- ewwwio_debug_message( "smart crop dims: w$new_w h$new_h" );
1992
- if ( ! empty( $args['zoom'] ) ) {
1993
- $new_w = round( $args['zoom'] * $new_w );
1994
- $new_h = round( $args['zoom'] * $new_h );
1995
- ewwwio_debug_message( "zooming: {$args['zoom']} w$new_w h$new_h" );
1996
- }
1997
- if ( ! $new_w || ! $new_h ) {
1998
- ewwwio_debug_message( 'empty dimension, not cropping' );
1999
- return $args;
2000
- }
2001
- $size_ratio = max( $new_w / $meta['width'], $new_h / $meta['height'] );
2002
- $crop_w = round( $new_w / $size_ratio );
2003
- $crop_h = round( $new_h / $size_ratio );
2004
- $s_x = floor( ( $meta['width'] - $crop_w ) * $focus_point[0] );
2005
- $s_y = floor( ( $meta['height'] - $crop_h ) * $focus_point[1] );
2006
- ewwwio_debug_message( "doing the math with size_ratio of $size_ratio" );
2007
-
2008
- $args['crop'] = $s_x . ',' . $s_y . ',' . $crop_w . ',' . $crop_h;
2009
- ewwwio_debug_message( $args['crop'] );
2010
- return $args;
2011
- }
2012
-
2013
- /**
2014
- * Check if this is a REST API request that we should handle (or not).
2015
- *
2016
- * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response.
2017
- * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
2018
- * @param WP_REST_Request $request Request used to generate the response.
2019
- * @return WP_HTTP_Response The result, unaltered.
2020
- */
2021
- function parse_restapi_maybe( $response, $handler, $request ) {
2022
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2023
- if ( ! is_a( $request, 'WP_REST_Request' ) ) {
2024
- ewwwio_debug_message( 'oddball REST request or handler' );
2025
- return $response; // Something isn't right, bail.
2026
- }
2027
- $route = $request->get_route();
2028
- if ( is_string( $route ) ) {
2029
- ewwwio_debug_message( "current REST route is $route" );
2030
- }
2031
- if ( is_string( $route ) && false !== strpos( $route, 'wp/v2/media/' ) && ! empty( $request['context'] ) && 'edit' === $request['context'] ) {
2032
- ewwwio_debug_message( 'REST API media endpoint from post editor' );
2033
- // We don't want ExactDN urls anywhere near the editor, so disable everything we can.
2034
- add_filter( 'exactdn_override_image_downsize', '__return_true', PHP_INT_MAX );
2035
- add_filter( 'exactdn_skip_image', '__return_true', PHP_INT_MAX ); // This skips existing srcset indices.
2036
- add_filter( 'exactdn_srcset_multipliers', '__return_false', PHP_INT_MAX ); // This one skips the additional multipliers.
2037
  }
2038
- return $response;
2039
- }
2040
 
2041
- /**
2042
- * Make sure the image domain is on the list of approved domains.
2043
- *
2044
- * @param string $domain The hostname to validate.
2045
- * @return bool True if the hostname is allowed, false otherwise.
2046
- */
2047
- public function allow_image_domain( $domain ) {
2048
- $domain = trim( $domain );
2049
- foreach ( $this->allowed_domains as $allowed ) {
2050
- $allowed = trim( $allowed );
2051
- if ( $domain === $allowed ) {
2052
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
2053
  }
 
2054
  }
2055
- return false;
2056
- }
2057
 
2058
- /**
2059
- * Ensure image URL is valid for ExactDN.
2060
- * Though ExactDN functions address some of the URL issues, we should avoid unnecessary processing if we know early on that the image isn't supported.
2061
- *
2062
- * @param string $url The image url to be validated.
2063
- * @param bool $exactdn_is_valid Optional. Whether an ExactDN URL should be considered valid. Default false.
2064
- * @uses wp_parse_args
2065
- * @return bool True if the url is considerd valid, false otherwise.
2066
- */
2067
- protected function validate_image_url( $url, $exactdn_is_valid = false ) {
2068
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2069
- if ( false !== strpos( $url, 'data:image/' ) ) {
2070
- ewwwio_debug_message( "could not parse data uri: $url" );
2071
- return false;
2072
- }
2073
- $parsed_url = $this->parse_url( $url );
2074
- if ( ! $parsed_url ) {
2075
- ewwwio_debug_message( "could not parse: $url" );
2076
  return false;
2077
  }
2078
 
2079
- // Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
2080
- $url_info = wp_parse_args(
2081
- $parsed_url,
2082
- array(
2083
- 'scheme' => null,
2084
- 'host' => null,
2085
- 'port' => null,
2086
- 'path' => null,
2087
- )
2088
- );
2089
-
2090
- // Bail if scheme isn't http or port is set that isn't port 80.
2091
- if (
2092
- ( 'http' !== $url_info['scheme'] || ( 80 !== (int) $url_info['port'] && ! is_null( $url_info['port'] ) ) ) &&
2093
- /**
2094
- * Tells ExactDN to ignore images that are served via HTTPS.
2095
- *
2096
- * @param bool $reject_https Should ExactDN ignore images using the HTTPS scheme. Default to false.
2097
- */
2098
- apply_filters( 'exactdn_reject_https', false )
2099
- ) {
2100
- ewwwio_debug_message( 'rejected https via filter' );
2101
- return false;
2102
- }
2103
 
2104
- // Bail if no host is found.
2105
- if ( is_null( $url_info['host'] ) ) {
2106
- ewwwio_debug_message( 'null host' );
2107
- return false;
2108
- }
 
 
 
 
 
2109
 
2110
- // Bail if the image already went through ExactDN.
2111
- if ( ! $exactdn_is_valid && $this->exactdn_domain === $url_info['host'] ) {
2112
- ewwwio_debug_message( 'exactdn image' );
2113
- return false;
2114
- }
 
 
 
 
 
 
 
 
2115
 
2116
- // Bail if the image already went through Photon to avoid conflicts.
2117
- if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) ) {
2118
- ewwwio_debug_message( 'photon/wp.com image' );
2119
- return false;
2120
- }
2121
 
2122
- // Bail if no path is found.
2123
- if ( is_null( $url_info['path'] ) ) {
2124
- ewwwio_debug_message( 'null path' );
2125
- return false;
2126
- }
2127
 
2128
- // Ensure image extension is acceptable, unless it's a dynamic NextGEN image.
2129
- if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), $this->extensions, true ) && false === strpos( $url_info['path'], 'nextgen-image/' ) ) {
2130
- ewwwio_debug_message( 'invalid extension' );
2131
- return false;
2132
- }
2133
 
2134
- // Make sure this is an allowed image domain/hostname for ExactDN on this site.
2135
- if ( ! $this->allow_image_domain( $url_info['host'] ) ) {
2136
- ewwwio_debug_message( 'invalid host for ExactDN' );
2137
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2138
  }
2139
 
2140
- // If we got this far, we should have an acceptable image URL,
2141
- // but let folks filter to decline if they prefer.
2142
  /**
2143
- * Overwrite the results of the previous validation steps an image goes through to be considered valid for ExactDN.
2144
  *
2145
- * @param bool true Is the image URL valid and can it be used by ExactDN. Default to true.
2146
- * @param string $url Image URL.
2147
- * @param array $parsed_url Array of information about the image url.
2148
- */
2149
- return apply_filters( 'exactdn_validate_image_url', true, $url, $parsed_url );
2150
- }
2151
-
2152
- /**
2153
- * Checks if the file exists before it passes the file to ExactDN.
2154
- *
2155
- * @param string $src The image URL.
2156
- * @return string The possibly altered URL without dimensions.
2157
- **/
2158
- protected function strip_image_dimensions_maybe( $src ) {
2159
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2160
- $stripped_src = $src;
2161
-
2162
- // Build URL, first removing WP's resized string so we pass the original image to ExactDN.
2163
- if ( preg_match( '#(-\d+x\d+)\.(' . implode( '|', $this->extensions ) . '){1}(?:\?.+)?$#i', $src, $src_parts ) ) {
2164
- $stripped_src = str_replace( $src_parts[1], '', $src );
2165
- $upload_dir = wp_get_upload_dir();
2166
-
2167
- // Extracts the file path to the image minus the base url.
2168
- $file_path = substr( $stripped_src, strlen( $upload_dir['baseurl'] ) );
2169
-
2170
- if ( is_file( $upload_dir['basedir'] . $file_path ) ) {
2171
- $src = $stripped_src;
2172
- }
2173
- ewwwio_debug_message( 'stripped dims' );
2174
  }
2175
- return $src;
2176
- }
2177
 
2178
- /**
2179
- * Provide an array of available image sizes and corresponding dimensions.
2180
- * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
2181
- *
2182
- * @global $wp_additional_image_sizes
2183
- * @uses get_option
2184
- * @return array
2185
- */
2186
- protected function image_sizes() {
2187
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2188
- if ( is_null( self::$image_sizes ) ) {
2189
- global $_wp_additional_image_sizes;
2190
-
2191
- // Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes.
2192
- $images = array(
2193
- 'thumb' => array(
2194
- 'width' => intval( get_option( 'thumbnail_size_w' ) ),
2195
- 'height' => intval( get_option( 'thumbnail_size_h' ) ),
2196
- 'crop' => (bool) get_option( 'thumbnail_crop' ),
2197
- ),
2198
- 'medium' => array(
2199
- 'width' => intval( get_option( 'medium_size_w' ) ),
2200
- 'height' => intval( get_option( 'medium_size_h' ) ),
2201
- 'crop' => false,
2202
- ),
2203
- 'large' => array(
2204
- 'width' => intval( get_option( 'large_size_w' ) ),
2205
- 'height' => intval( get_option( 'large_size_h' ) ),
2206
- 'crop' => false,
2207
- ),
2208
- 'full' => array(
2209
- 'width' => null,
2210
- 'height' => null,
2211
- 'crop' => false,
2212
- ),
2213
- );
2214
 
2215
- // Compatibility mapping as found in wp-includes/media.php.
2216
- $images['thumbnail'] = $images['thumb'];
2217
 
2218
- // Update class variable, merging in $_wp_additional_image_sizes if any are set.
2219
- if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) ) {
2220
- self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
2221
- } else {
2222
- self::$image_sizes = $images;
 
2223
  }
2224
- }
2225
 
2226
- return is_array( self::$image_sizes ) ? self::$image_sizes : array();
2227
- }
2228
 
2229
- /**
2230
- * Handle image urls within the NextGEN pro lightbox displays.
2231
- *
2232
- * @param array $images An array of NextGEN images and associate attributes.
2233
- * @return array The ExactDNified array of images.
2234
- */
2235
- function ngg_pro_lightbox_images_queue( $images ) {
2236
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2237
- if ( ewww_image_optimizer_iterable( $images ) ) {
2238
- foreach ( $images as $index => $image ) {
2239
- if ( ! empty( $image['image'] ) && $this->validate_image_url( $image['image'] ) ) {
2240
- $images[ $index ]['image'] = $this->generate_url( $image['image'] );
2241
- }
2242
- if ( ! empty( $image['thumb'] ) && $this->validate_image_url( $image['thumb'] ) ) {
2243
- $images[ $index ]['thumb'] = $this->generate_url( $image['thumb'] );
2244
- }
2245
- if ( ! empty( $image['full_image'] ) && $this->validate_image_url( $image['full_image'] ) ) {
2246
- $images[ $index ]['full_image'] = $this->generate_url( $image['full_image'] );
2247
- }
2248
- if ( ewww_image_optimizer_iterable( $image['srcsets'] ) ) {
2249
- foreach ( $image['srcsets'] as $size => $srcset ) {
2250
- if ( $this->validate_image_url( $srcset ) ) {
2251
- $images[ $index ]['srcsets'][ $size ] = $this->generate_url( $srcset );
 
2252
  }
2253
  }
2254
- }
2255
- if ( ewww_image_optimizer_iterable( $image['full_srcsets'] ) ) {
2256
- foreach ( $image['full_srcsets'] as $size => $srcset ) {
2257
- if ( $this->validate_image_url( $srcset ) ) {
2258
- $images[ $index ]['full_srcsets'][ $size ] = $this->generate_url( $srcset );
2259
  }
2260
  }
2261
  }
2262
  }
 
2263
  }
2264
- return $images;
2265
- }
2266
 
2267
- /**
2268
- * Handle image urls within NextGEN.
2269
- *
2270
- * @param string $image A url for a NextGEN image.
2271
- * @return string The ExactDNified image url.
2272
- */
2273
- function ngg_get_image_url( $image ) {
2274
- // Don't foul up the admin side of things, unless a plugin wants to.
2275
- if ( is_admin() &&
2276
- /**
2277
- * Provide plugins a way of running ExactDN for images in the WordPress Dashboard (wp-admin).
2278
- *
2279
- * @param bool false Stop ExactDN from being run on the Dashboard. Default to false, use true to run in wp-admin.
2280
- * @param array $args {
2281
- * Array of image details.
2282
- *
2283
- * @type string|bool $image Image URL or false.
2284
- * @type int $attachment_id Attachment ID of the image.
2285
- * }
2286
- */
2287
- false === apply_filters( 'exactdn_admin_allow_ngg_url', false, $image )
2288
- ) {
 
 
 
 
 
 
2289
  return $image;
2290
  }
2291
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2292
- if ( $this->validate_image_url( $image ) ) {
2293
- return $this->generate_url( $image );
2294
- }
2295
- return $image;
2296
- }
2297
 
2298
- /**
2299
- * Handle images in legacy WooCommerce API endpoints.
2300
- *
2301
- * @param array $product_data The product information that will be returned via the API.
2302
- * @return array The product information with ExactDNified image urls.
2303
- */
2304
- function woocommerce_api_product_response( $product_data ) {
2305
- if ( is_array( $product_data ) && ! empty( $product_data['featured_src'] ) ) {
2306
- if ( $this->validate_image_url( $product_data['featured_src'] ) ) {
2307
- $product_data['featured_src'] = $this->generate_url( $product_data['featured_src'] );
 
2308
  }
 
2309
  }
2310
- return $product_data;
2311
- }
2312
 
2313
- /**
2314
- * Enqueue ExactDN helper script
2315
- */
2316
- public function action_wp_enqueue_scripts() {
2317
- wp_enqueue_script( 'exactdn', plugins_url( 'includes/exactdn.js', EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE ), array( 'jquery' ), EWWW_IMAGE_OPTIMIZER_VERSION, true );
2318
- }
2319
-
2320
- /**
2321
- * Suppress query args for certain files, typically for placholder images.
2322
- *
2323
- * @param array|string $args Array of ExactDN arguments.
2324
- * @param string $image_url Image URL.
2325
- * @param string|null $scheme Image scheme. Default to null.
2326
- * @return array Empty if it matches our search, otherwise just $args untouched.
2327
- */
2328
- function exactdn_remove_args( $args, $image_url, $scheme ) {
2329
- if ( strpos( $image_url, 'revslider/admin/assets/images/dummy.png' ) ) {
2330
- return array();
2331
- }
2332
- if ( strpos( $image_url, '/lazy.png' ) ) {
2333
- return array();
2334
- }
2335
- if ( strpos( $image_url, 'lazy_placeholder.gif' ) ) {
2336
- return array();
2337
- }
2338
- if ( strpos( $image_url, '/assets/images/' ) ) {
2339
- return array();
2340
- }
2341
- if ( strpos( $image_url, 'LayerSlider/static/img' ) ) {
2342
- return array();
2343
- }
2344
- if ( strpos( $image_url, 'lazy-load/images/' ) ) {
2345
- return array();
2346
- }
2347
- if ( strpos( $image_url, 'public/images/spacer.' ) ) {
2348
- return array();
 
2349
  }
2350
- return $args;
2351
- }
2352
 
2353
- /**
2354
- * Exclude images and other resources from being processed based on user specified list.
2355
- *
2356
- * @since 4.6.0
2357
- *
2358
- * @param boolean $skip Whether ExactDN should skip processing.
2359
- * @param string $url Resource URL.
2360
- * @return boolean True to skip the resource, unchanged otherwise.
2361
- */
2362
- function exactdn_skip_user_exclusions( $skip, $url ) {
2363
- if ( $this->user_exclusions ) {
2364
- foreach ( $this->user_exclusions as $exclusion ) {
2365
- if ( false !== strpos( $url, $exclusion ) ) {
2366
- ewwwio_debug_message( "user excluded $url via $exclusion" );
2367
- return true;
 
2368
  }
2369
  }
 
2370
  }
2371
- return $skip;
2372
- }
2373
-
2374
- /**
2375
- * Converts a local script/css url to use ExactDN.
2376
- *
2377
- * @param string $url URL to the resource being parsed.
2378
- * @return string The ExactDN version of the resource, if it was local.
2379
- */
2380
- function parse_enqueue( $url ) {
2381
- if ( is_admin() ) {
2382
- return $url;
2383
- }
2384
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2385
- $parsed_url = $this->parse_url( $url );
2386
 
2387
- if ( false !== strpos( $url, 'wp-admin/' ) ) {
2388
- return $url;
2389
- }
2390
- if ( false !== strpos( $url, 'xmlrpc.php' ) ) {
2391
- return $url;
2392
- }
2393
- if ( strpos( $url, 'wp-content/plugins/anti-captcha/' ) ) {
2394
- return $url;
2395
- }
2396
  /**
2397
- * Allow specific URLs to avoid going through ExactDN.
2398
  *
2399
- * @param bool false Should the URL be returned as is, without going through ExactDN. Default to false.
2400
- * @param string $url Resource URL.
2401
- * @param array|string $args Array of ExactDN arguments.
2402
- * @param string|null $scheme URL scheme. Default to null.
2403
  */
2404
- if ( true === apply_filters( 'exactdn_skip_for_url', false, $url, array(), null ) ) {
2405
- return $url;
2406
- }
2407
-
2408
- // Unable to parse.
2409
- if ( ! $parsed_url || ! is_array( $parsed_url ) || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
2410
- ewwwio_debug_message( 'src url no good' );
2411
- return $url;
2412
- }
2413
 
2414
- // No PHP files shall pass.
2415
- if ( preg_match( '/\.php$/', $parsed_url['path'] ) ) {
2416
- return $url;
2417
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2418
 
2419
- // Make sure this is an allowed image domain/hostname for ExactDN on this site.
2420
- if ( ! $this->allow_image_domain( $parsed_url['host'] ) ) {
2421
- ewwwio_debug_message( "invalid host for ExactDN: {$parsed_url['host']}" );
2422
- return $url;
2423
- }
2424
 
2425
- // Figure out which CDN (sub)domain to use.
2426
- if ( empty( $this->exactdn_domain ) ) {
2427
- ewwwio_debug_message( 'no exactdn domain configured' );
2428
- return $url;
2429
- }
2430
 
2431
- // You can't run an ExactDN URL through again because query strings are stripped.
2432
- // So if the image is already an ExactDN URL, append the new arguments to the existing URL.
2433
- if ( $this->exactdn_domain === $parsed_url['host'] ) {
2434
- ewwwio_debug_message( 'url already has exactdn domain' );
2435
- return $url;
2436
- }
2437
 
2438
- global $wp_version;
2439
- // If a resource doesn't have a version string, we add one to help with cache-busting.
2440
- if ( ( empty( $parsed_url['query'] ) || 'ver=' . $wp_version === $parsed_url['query'] ) && false !== strpos( $url, 'wp-content/' ) ) {
2441
- $modified = ewww_image_optimizer_function_exists( 'filemtime' ) ? filemtime( get_template_directory() ) : '';
2442
- if ( empty( $modified ) ) {
2443
- $modified = (int) EWWW_IMAGE_OPTIMIZER_VERSION;
2444
  }
2445
- /**
2446
- * Allows a custom version string for resources that are missing one.
2447
- *
2448
- * @param string EWWW IO version.
2449
- */
2450
- $parsed_url['query'] = apply_filters( 'exactdn_version_string', "m=$modified" );
2451
- } elseif ( empty( $parsed_url['query'] ) ) {
2452
- $parsed_url['query'] = '';
2453
- }
2454
 
2455
- $exactdn_url = '//' . $this->exactdn_domain . '/' . ltrim( $parsed_url['path'], '/' ) . '?' . $parsed_url['query'];
2456
- ewwwio_debug_message( "exactdn css/script url: $exactdn_url" );
2457
- return $exactdn_url;
2458
- }
 
 
2459
 
2460
- /**
2461
- * Generates an ExactDN URL.
2462
- *
2463
- * @param string $image_url URL to the publicly accessible image you want to manipulate.
2464
- * @param array|string $args An array of arguments, i.e. array( 'w' => '300', 'resize' => '123,456' ), or in string form (w=123&h=456).
2465
- * @param string $scheme Indicates http or https, other schemes are invalid.
2466
- * @return string The raw final URL. You should run this through esc_url() before displaying it.
2467
- */
2468
- function generate_url( $image_url, $args = array(), $scheme = null ) {
2469
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2470
- $image_url = trim( $image_url );
 
 
 
 
 
2471
 
2472
- if ( is_null( $scheme ) ) {
2473
- $scheme = $this->scheme;
 
2474
  }
2475
 
2476
  /**
2477
- * Disables ExactDN URL processing for local development.
2478
  *
2479
- * @param bool false default
 
 
 
2480
  */
2481
- if ( true === apply_filters( 'exactdn_development_mode', false ) ) {
2482
- return $image_url;
2483
- }
2484
 
2485
- /**
2486
- * Allow specific URLs to avoid going through ExactDN.
2487
- *
2488
- * @param bool false Should the URL be returned as is, without going through ExactDN. Default to false.
2489
- * @param string $image_url Resource URL.
2490
- * @param array|string $args Array of ExactDN arguments.
2491
- * @param string|null $scheme URL scheme. Default to null.
2492
- */
2493
- if ( true === apply_filters( 'exactdn_skip_for_url', false, $image_url, $args, $scheme ) ) {
2494
- return $image_url;
2495
- }
2496
 
2497
- // TODO: Not differentiated yet, but it will be, so stay tuned!
2498
- $jpg_quality = apply_filters( 'jpeg_quality', null, 'image_resize' );
2499
- $webp_quality = apply_filters( 'jpeg_quality', $jpg_quality, 'image/webp' );
 
 
 
 
 
2500
 
2501
- $more_args = array();
2502
- if ( false === strpos( $image_url, 'strip=all' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_metadata_remove' ) ) {
2503
- $more_args['strip'] = 'all';
2504
- }
2505
- if ( false === strpos( $image_url, 'lossy=' ) && ewww_image_optimizer_get_option( 'exactdn_lossy' ) ) {
2506
- $more_args['lossy'] = is_numeric( ewww_image_optimizer_get_option( 'exactdn_lossy' ) ) ? (int) ewww_image_optimizer_get_option( 'exactdn_lossy' ) : 80;
2507
- }
2508
- if ( false === strpos( $image_url, 'quality=' ) && ! is_null( $jpg_quality ) && 82 !== (int) $jpg_quality ) {
2509
- $more_args['quality'] = $jpg_quality;
2510
- }
2511
- // Merge given args with the automatic (option-based) args, and also makes sure args is an array if it was previously a string.
2512
- $args = wp_parse_args( $args, $more_args );
2513
 
2514
- /**
2515
- * Filter the original image URL before it goes through ExactDN.
2516
- *
2517
- * @param string $image_url Image URL.
2518
- * @param array|string $args Array of ExactDN arguments.
2519
- * @param string|null $scheme Image scheme. Default to null.
2520
- */
2521
- $image_url = apply_filters( 'exactdn_pre_image_url', $image_url, $args, $scheme );
2522
 
2523
- if ( empty( $image_url ) ) {
2524
- return $image_url;
2525
- }
 
 
 
 
 
 
 
 
 
2526
 
2527
- $image_url_parts = $this->parse_url( $image_url );
 
 
 
 
 
 
 
2528
 
2529
- // Unable to parse.
2530
- if ( ! is_array( $image_url_parts ) || empty( $image_url_parts['host'] ) || empty( $image_url_parts['path'] ) ) {
2531
- ewwwio_debug_message( 'src url no good' );
2532
- return $image_url;
2533
- }
2534
 
2535
- if ( isset( $image_url_parts['scheme'] ) && 'https' === $image_url_parts['scheme'] ) {
2536
- if ( is_array( $args ) ) {
2537
- $args['ssl'] = 1;
 
 
 
2538
  }
2539
- $scheme = 'https';
2540
- }
2541
 
2542
- /**
2543
- * Filter the ExactDN image parameters before they are applied to an image.
2544
- *
2545
- * @param array|string $args Array of ExactDN arguments.
2546
- * @param string $image_url Image URL.
2547
- * @param string|null $scheme Image scheme. Default to null.
2548
- */
2549
- $args = apply_filters( 'exactdn_pre_args', $args, $image_url, $scheme );
2550
 
2551
- if ( is_array( $args ) ) {
2552
- // Convert values that are arrays into strings.
2553
- foreach ( $args as $arg => $value ) {
2554
- if ( is_array( $value ) ) {
2555
- $args[ $arg ] = implode( ',', $value );
 
 
 
 
 
 
 
 
 
 
2556
  }
 
 
 
2557
  }
2558
 
2559
- // Encode argument values.
2560
- $args = rawurlencode_deep( $args );
2561
- }
2562
 
2563
- ewwwio_debug_message( $image_url_parts['host'] );
 
 
 
 
2564
 
2565
- // Figure out which CDN (sub)domain to use.
2566
- if ( empty( $this->exactdn_domain ) ) {
2567
- ewwwio_debug_message( 'no exactdn domain configured' );
2568
- return $image_url;
2569
- }
 
 
2570
 
2571
- // You can't run an ExactDN URL through again because query strings are stripped.
2572
- // So if the image is already an ExactDN URL, append the new arguments to the existing URL.
2573
- if ( $this->exactdn_domain === $image_url_parts['host'] ) {
2574
- ewwwio_debug_message( 'url already has exactdn domain' );
2575
- $exactdn_url = add_query_arg( $args, $image_url );
2576
- return $this->url_scheme( $exactdn_url, $scheme );
2577
- }
 
2578
 
2579
- // ExactDN doesn't support query strings so we ignore them and look only at the path.
2580
- // However some source images are served via PHP so check the no-query-string extension.
2581
- // For future proofing, this is a blacklist of common issues rather than a whitelist.
2582
- $extension = pathinfo( $image_url_parts['path'], PATHINFO_EXTENSION );
2583
- if ( ( empty( $extension ) && false === strpos( $image_url_parts['path'], 'nextgen-image/' ) ) || in_array( $extension, array( 'php', 'ashx' ), true ) ) {
2584
- ewwwio_debug_message( 'bad extension' );
2585
- return $image_url;
2586
- }
2587
 
2588
- if ( $this->remove_path && 0 === strpos( $image_url_parts['path'], $this->remove_path ) ) {
2589
- $image_url_parts['path'] = substr( $image_url_parts['path'], strlen( $this->remove_path ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2590
  }
2591
- $domain = 'http://' . $this->exactdn_domain . '/';
2592
- $exactdn_url = $domain . ltrim( $image_url_parts['path'], '/' );
2593
- ewwwio_debug_message( "bare exactdn url: $exactdn_url" );
2594
 
2595
  /**
2596
- * Add query strings to ExactDN URL.
2597
- * By default, ExactDN doesn't support query strings so we ignore them.
2598
- * This setting is ExactDN Server dependent.
2599
  *
2600
- * @param bool false Should query strings be added to the image URL. Default is false.
2601
- * @param string $image_url_parts['host'] Image URL's host.
 
2602
  */
2603
- if ( isset( $image_url_parts['query'] ) && apply_filters( 'exactdn_add_query_string_to_domain', false, $image_url_parts['host'] ) ) {
2604
- $exactdn_url .= '?q=' . rawurlencode( $image_url_parts['query'] );
2605
- }
2606
- // This is disabled, as I don't think we really need it.
2607
- if ( false && ! empty( $image_url_parts['query'] ) && false !== strpos( $image_url_parts['query'], 'theia_smart' ) ) {
2608
- $args = wp_parse_args( $image_url_parts['query'], $args );
2609
- }
2610
-
2611
- if ( $args ) {
2612
- if ( is_array( $args ) ) {
2613
- $exactdn_url = add_query_arg( $args, $exactdn_url );
2614
- } else {
2615
- // You can pass a query string for complicated requests, although this should have been converted to an array already.
2616
- $exactdn_url .= '?' . $args;
2617
  }
 
 
2618
  }
2619
- ewwwio_debug_message( "exactdn url with args: $exactdn_url" );
2620
-
2621
- return $this->url_scheme( $exactdn_url, $scheme );
2622
- }
2623
 
2624
- /**
2625
- * Prepends schemeless urls or replaces non-http scheme with a valid scheme, defaults to 'http'.
2626
- *
2627
- * @param string $url The URL to parse.
2628
- * @param string|null $scheme Retrieve specific URL component.
2629
- * @return string Result of parse_url.
2630
- */
2631
- function url_scheme( $url, $scheme ) {
2632
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2633
- if ( ! in_array( $scheme, array( 'http', 'https' ), true ) ) {
2634
- ewwwio_debug_message( 'not a valid scheme' );
2635
- if ( preg_match( '#^(https?:)?//#', $url ) ) {
2636
- ewwwio_debug_message( 'url has a valid scheme already' );
2637
- return $url;
2638
  }
2639
- ewwwio_debug_message( 'invalid scheme provided, and url sucks, defaulting to http' );
2640
- $scheme = 'http';
 
2641
  }
2642
- ewwwio_debug_message( "valid $scheme - $url" );
2643
- return preg_replace( '#^([a-z:]+)?//#i', "$scheme://", $url );
2644
- }
2645
 
2646
- /**
2647
- * A wrapper for PHP's parse_url, prepending assumed scheme for network path
2648
- * URLs. PHP versions 5.4.6 and earlier do not correctly parse without scheme.
2649
- *
2650
- * @param string $url The URL to parse.
2651
- * @param integer $component Retrieve specific URL component.
2652
- * @return mixed Result of parse_url.
2653
- */
2654
- function parse_url( $url, $component = -1 ) {
2655
- if ( 0 === strpos( $url, '//' ) ) {
2656
- $url = ( is_ssl() ? 'https:' : 'http:' ) . $url;
2657
- }
2658
- if ( false === strpos( $url, 'http' ) && '/' !== substr( $url, 0, 1 ) ) {
2659
- $url = ( is_ssl() ? 'https://' : 'http://' ) . $url;
 
 
2660
  }
2661
- // Because encoded ampersands in the filename break things.
2662
- $url = str_replace( '&#038;', '&', $url );
2663
- return parse_url( $url, $component );
2664
- }
2665
 
2666
- /**
2667
- * A wrapper for human_time_diff() that gives sub-minute times in seconds.
2668
- *
2669
- * @param int $from Unix timestamp from which the difference begins.
2670
- * @param int $to Optional. Unix timestamp to end the time difference. Default is time().
2671
- * @return string Human readable time difference.
2672
- */
2673
- function human_time_diff( $from, $to = '' ) {
2674
- if ( empty( $to ) ) {
2675
- $to = time();
2676
- }
2677
- $diff = (int) abs( $to - $from );
2678
- if ( $diff < 60 ) {
2679
- return "$diff sec";
2680
  }
2681
- return human_time_diff( $from, $to );
2682
- }
2683
 
2684
- /**
2685
- * Adds link to header which enables DNS prefetching for faster speed.
2686
- *
2687
- * @param array $hints A list of hints for a particular relationship type.
2688
- * @param string $relationship_type The type of hint being filtered: dns-prefetch, preconnect, etc.
2689
- * @return array The list of hints, potentially with the ExactDN domain added in.
2690
- */
2691
- function dns_prefetch( $hints, $relationship_type ) {
2692
- if ( 'dns-prefetch' === $relationship_type && $this->exactdn_domain ) {
2693
- $hints[] = '//' . $this->exactdn_domain;
 
 
 
2694
  }
2695
- return $hints;
2696
  }
2697
 
2698
- /**
2699
- * Adds the ExactDN domain to the list of 'local' domains for Autoptimize.
2700
- *
2701
- * @param array $domains A list of domains considered 'local' by Autoptimize.
2702
- * @return array The same list, with the ExactDN domain appended.
2703
- */
2704
- function autoptimize_cdn_url( $domains ) {
2705
- ewwwio_debug_message( '<b>' . __METHOD__ . '()</b>' );
2706
- if ( is_array( $domains ) && ! in_array( $this->exactdn_domain, $domains, true ) ) {
2707
- ewwwio_debug_message( 'adding to AO list: ' . $this->exactdn_domain );
2708
- $domains[] = $this->exactdn_domain;
2709
- }
2710
- return $domains;
2711
- }
2712
  }
2713
-
2714
- global $exactdn;
2715
- $exactdn = new ExactDN();
3
  * Class and methods to implement ExactDN (based on Photon implementation).
4
  *
5
  * @link https://ewww.io
6
+ * @package EIO
7
  */
8
 
9
  if ( ! defined( 'ABSPATH' ) ) {
10
  exit;
11
  }
12
 
13
+ if ( ! class_exists( 'ExactDN' ) ) {
 
 
 
 
14
  /**
15
+ * Enables plugin to filter the page content and replace image urls with ExactDN urls.
 
 
 
16
  */
17
+ class ExactDN extends EIO_Page_Parser {
18
 
19
+ /**
20
+ * A list of user-defined exclusions, populated by validate_user_exclusions().
21
+ *
22
+ * @access protected
23
+ * @var array $user_exclusions
24
+ */
25
+ protected $user_exclusions = array();
26
 
27
+ /**
28
+ * A list of image sizes registered for attachments.
29
+ *
30
+ * @access protected
31
+ * @var array $image_sizes
32
+ */
33
+ protected static $image_sizes = null;
34
 
35
+ /**
36
+ * Indicates if we are in full-page filtering mode.
37
+ *
38
+ * @access public
39
+ * @var bool $filtering_the_page
40
+ */
41
+ public $filtering_the_page = false;
42
 
43
+ /**
44
+ * Indicates if we are in content filtering mode.
45
+ *
46
+ * @access public
47
+ * @var bool $filtering_the_content
48
+ */
49
+ public $filtering_the_content = false;
50
 
51
+ /**
52
+ * List of permitted domains for ExactDN rewriting.
53
+ *
54
+ * @access public
55
+ * @var array $allowed_domains
56
+ */
57
+ public $allowed_domains = array();
58
 
59
+ /**
60
+ * Path portion to remove at beginning of URL, usually for path-style S3 domains.
61
+ *
62
+ * @access public
63
+ * @var string $remove_path
64
+ */
65
+ public $remove_path = '';
66
 
67
+ /**
68
+ * The ExactDN domain/zone.
69
+ *
70
+ * @access private
71
+ * @var float $elapsed_time
72
+ */
73
+ private $exactdn_domain = false;
74
 
75
+ /**
76
+ * The detected site scheme (http/https).
77
+ *
78
+ * @access private
79
+ * @var string $scheme
80
+ */
81
+ private $scheme = false;
82
 
83
+ /**
84
+ * Allow us to track how much overhead ExactDN introduces.
85
+ *
86
+ * @access private
87
+ * @var float $elapsed_time
88
+ */
89
+ private $elapsed_time = 0;
90
 
91
+ /**
92
+ * Keep track of the attribute we use for srcset, in case a lazy load plugin is active.
93
+ *
94
+ * @access private
95
+ * @var string $srcset_attr
96
+ */
97
+ private $srcset_attr = 'srcset';
 
 
98
 
99
+ /**
100
+ * Register (once) actions and filters for ExactDN. If you want to use this class, use the global.
101
+ */
102
+ function __construct() {
103
+ parent::__construct( __FILE__ );
104
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
105
+ global $exactdn;
106
+ if ( is_object( $exactdn ) ) {
107
+ return 'you are doing it wrong';
108
+ }
109
 
110
+ // Make sure we have an ExactDN domain to use.
111
+ if ( ! $this->setup() ) {
112
+ return;
 
 
113
  }
 
 
114
 
115
+ if ( ! $this->scheme ) {
116
+ $site_url = get_home_url();
117
+ $scheme = 'http';
118
+ if ( strpos( $site_url, 'https://' ) !== false ) {
119
+ $scheme = 'https';
120
+ }
121
+ $this->scheme = $scheme;
122
+ }
123
 
124
+ // Images in post content and galleries.
125
+ add_filter( 'the_content', array( $this, 'filter_the_content' ), 999999 );
126
+ // Start an output buffer before any output starts.
127
+ add_filter( $this->prefix . 'filter_page_output', array( $this, 'filter_page_output' ), 5 );
 
 
 
 
 
 
 
128
 
129
+ // Core image retrieval.
130
+ if ( ! function_exists( 'aq_resize' ) ) {
131
+ add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
132
+ } else {
133
+ $this->debug_message( 'aq_resize detected, image_downsize filter disabled' );
134
+ }
135
+ // Disable image_downsize filter during themify_get_image().
136
+ add_action( 'themify_before_post_image', array( $this, 'disable_image_downsize' ) );
137
+ if ( defined( 'EXACTDN_IMAGE_DOWNSIZE_SCALE' ) && EXACTDN_IMAGE_DOWNSIZE_SCALE ) {
138
+ add_action( 'exactdn_image_downsize_array', array( $this, 'image_downsize_scale' ) );
139
+ }
140
 
141
+ // Check REST API requests to see if ExactDN should be running.
142
+ add_filter( 'rest_request_before_callbacks', array( $this, 'parse_restapi_maybe' ), 10, 3 );
 
 
 
 
 
143
 
144
+ // Overrides for admin-ajax images.
145
+ add_filter( 'exactdn_admin_allow_image_downsize', array( $this, 'allow_admin_image_downsize' ), 10, 2 );
146
+ // Overrides for "pass through" images.
147
+ add_filter( 'exactdn_pre_args', array( $this, 'exactdn_remove_args' ), 10, 3 );
148
+ // Overrides for user exclusions.
149
+ add_filter( 'exactdn_skip_image', array( $this, 'exactdn_skip_user_exclusions' ), 9, 2 );
150
+ add_filter( 'exactdn_skip_for_url', array( $this, 'exactdn_skip_user_exclusions' ), 9, 2 );
151
 
152
+ // Responsive image srcset substitution.
153
+ add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 1001, 5 );
154
+ add_filter( 'wp_calculate_image_sizes', array( $this, 'filter_sizes' ), 1, 2 ); // Early so themes can still filter.
155
 
156
+ // Filter for NextGEN image urls within JS.
157
+ add_filter( 'ngg_pro_lightbox_images_queue', array( $this, 'ngg_pro_lightbox_images_queue' ) );
158
+ add_filter( 'ngg_get_image_url', array( $this, 'ngg_get_image_url' ) );
159
 
160
+ // Filter for legacy WooCommerce API endpoints.
161
+ add_filter( 'woocommerce_api_product_response', array( $this, 'woocommerce_api_product_response' ) );
162
 
163
+ // DNS prefetching.
164
+ add_filter( 'wp_resource_hints', array( $this, 'dns_prefetch' ), 10, 2 );
 
 
 
165
 
166
+ // Get all the script/css urls and rewrite them (if enabled).
167
+ if ( $this->get_option( 'exactdn_all_the_things' ) ) {
168
+ add_filter( 'style_loader_src', array( $this, 'parse_enqueue' ), 20 );
169
+ add_filter( 'script_loader_src', array( $this, 'parse_enqueue' ), 20 );
170
+ }
171
 
172
+ // Improve the default content_width for Twenty Nineteen.
173
+ global $content_width;
174
+ if ( function_exists( 'twentynineteen_setup' ) && 640 === (int) $content_width ) {
175
+ $content_width = 932;
 
 
 
 
176
  }
 
177
 
178
+ // Configure Autoptimize with our CDN domain.
179
+ add_filter( 'autoptimize_filter_cssjs_multidomain', array( $this, 'autoptimize_cdn_url' ) );
180
+
181
+ // Find the "local" domain.
182
+ $s3_active = false;
183
+ if ( class_exists( 'Amazon_S3_And_CloudFront' ) ) {
184
+ global $as3cf;
185
+ $s3_region = $as3cf->get_setting( 'region' );
186
+ $s3_bucket = $as3cf->get_setting( 'bucket' );
187
+ $s3_domain = $as3cf->get_provider()->get_url_domain( $s3_bucket, $s3_region, null, array(), true );
188
+ $this->debug_message( "found S3 domain of $s3_domain with bucket $s3_bucket and region $s3_region" );
189
+ if ( ! empty( $s3_domain ) ) {
190
+ $s3_active = true;
191
+ }
192
  }
 
193
 
194
+ if ( $s3_active ) {
195
+ $upload_dir = array(
196
+ 'baseurl' => 'https://' . $s3_domain,
197
+ );
198
+ } else {
199
+ $upload_dir = wp_upload_dir( null, false );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  }
201
+ $upload_url_parts = defined( 'EXACTDN_LOCAL_DOMAIN' ) && EXACTDN_LOCAL_DOMAIN ? $this->parse_url( EXACTDN_LOCAL_DOMAIN ) : $this->parse_url( $upload_dir['baseurl'] );
202
+ if ( empty( $upload_url_parts ) ) {
203
+ return;
204
+ }
205
+ $this->upload_domain = $upload_url_parts['host'];
206
+ $this->debug_message( "allowing images from here: $this->upload_domain" );
207
+ if ( strpos( $this->upload_domain, 'amazonaws.com' ) && ! empty( $upload_url_parts['path'] ) ) {
208
+ $this->remove_path = rtrim( $upload_url_parts['path'], '/' );
209
+ $this->debug_message( "removing this from urls: $this->remove_path" );
210
+ }
211
+ $this->allowed_domains[] = $this->upload_domain;
212
+ if ( ! $s3_active && strpos( $this->upload_domain, 'www' ) === false ) {
213
+ $this->allowed_domains[] = 'www.' . $this->upload_domain;
214
+ } else {
215
+ $nonwww = ltrim( 'www.', $this->upload_domain );
216
+ if ( $nonwww !== $this->upload_domain ) {
217
+ $this->allowed_domains[] = $nonwww;
218
+ }
219
+ }
220
+ $wpml_domains = apply_filters( 'wpml_setting', array(), 'language_domains' );
221
+ if ( $this->is_iterable( $wpml_domains ) ) {
222
+ $this->debug_message( 'wpml domains: ' . implode( ',', $wpml_domains ) );
223
+ $this->allowed_domains[] = $this->parse_url( get_option( 'home' ), PHP_URL_HOST );
224
+ foreach ( $wpml_domains as $wpml_domain ) {
225
+ $this->allowed_domains[] = $wpml_domain;
226
+ }
227
  }
228
+ $this->allowed_domains = apply_filters( 'exactdn_allowed_domains', $this->allowed_domains );
229
+ $this->debug_message( 'allowed domains: ' . implode( ',', $this->allowed_domains ) );
230
+ $this->validate_user_exclusions();
231
  }
 
 
 
 
232
 
233
+ /**
234
+ * If ExactDN is enabled, validates and configures the ExactDN domain name.
235
+ */
236
+ function setup() {
237
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
238
+ // If we don't have a domain yet, go grab one.
239
+ if ( ! $this->get_exactdn_domain() ) {
240
+ $this->debug_message( 'attempting to activate exactDN' );
241
+ $exactdn_domain = $this->activate_site();
242
+ } else {
243
+ $this->debug_message( 'grabbing existing exactDN domain' );
244
+ $exactdn_domain = $this->get_exactdn_domain();
245
+ }
246
+ if ( ! $exactdn_domain ) {
247
+ delete_option( $this->prefix . 'exactdn' );
248
+ delete_site_option( $this->prefix . 'exactdn' );
 
 
 
249
  return false;
250
  }
251
+ // If we have a domain, verify it.
252
+ if ( $this->verify_domain( $exactdn_domain ) ) {
253
+ $this->debug_message( 'verified existing exactDN domain' );
254
+ $this->exactdn_domain = $exactdn_domain;
255
+ $this->debug_message( 'exactdn_domain: ' . $exactdn_domain );
256
+ return true;
257
+ }
258
+ delete_option( $this->prefix . 'exactdn_domain' );
259
+ delete_option( $this->prefix . 'exactdn_verified' );
260
+ delete_site_option( $this->prefix . 'exactdn_domain' );
261
+ delete_site_option( $this->prefix . 'exactdn_verified' );
 
 
 
 
 
 
 
 
 
 
 
262
  return false;
263
  }
 
 
 
 
 
 
264
 
265
+ /**
266
+ * Use the Site URL to get the zone domain.
267
+ */
268
+ function activate_site() {
269
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
270
+ $s3_active = false;
271
+ if ( class_exists( 'Amazon_S3_And_CloudFront' ) ) {
272
+ global $as3cf;
273
+ $s3_scheme = $as3cf->get_url_scheme();
274
+ $s3_region = $as3cf->get_setting( 'region' );
275
+ $s3_bucket = $as3cf->get_setting( 'bucket' );
276
+ $s3_domain = $as3cf->get_provider()->get_url_domain( $s3_bucket, $s3_region, null, array(), true );
277
+ $this->debug_message( "found S3 domain of $s3_domain with bucket $s3_bucket and region $s3_region" );
278
+ if ( ! empty( $s3_domain ) ) {
279
+ $s3_active = true;
280
+ }
281
  }
 
282
 
283
+ if ( $s3_active ) {
284
+ $site_url = defined( 'EXACTDN_LOCAL_DOMAIN' ) && EXACTDN_LOCAL_DOMAIN ? EXACTDN_LOCAL_DOMAIN : $s3_scheme . '://' . $s3_domain;
285
+ } else {
286
+ $site_url = defined( 'EXACTDN_LOCAL_DOMAIN' ) && EXACTDN_LOCAL_DOMAIN ? EXACTDN_LOCAL_DOMAIN : get_home_url();
287
+ }
288
+ $url = 'http://optimize.exactlywww.com/exactdn/activate.php';
289
+ $ssl = wp_http_supports( array( 'ssl' ) );
290
+ if ( $ssl ) {
291
+ $url = set_url_scheme( $url, 'https' );
292
+ }
293
+ add_filter( 'http_headers_useragent', $this->prefix . 'cloud_useragent', PHP_INT_MAX );
294
+ $result = wp_remote_post(
295
+ $url,
296
+ array(
297
+ 'timeout' => 10,
298
+ 'body' => array(
299
+ 'site_url' => $site_url,
300
+ ),
301
+ )
302
+ );
303
+ if ( is_wp_error( $result ) ) {
304
+ $error_message = $result->get_error_message();
305
+ $this->debug_message( "exactdn activation request failed: $error_message" );
306
+ global $exactdn_activate_error;
307
+ $exactdn_activate_error = $error_message;
308
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' );
309
+ return false;
310
+ } elseif ( ! empty( $result['body'] ) && strpos( $result['body'], 'domain' ) !== false ) {
311
+ $response = json_decode( $result['body'], true );
312
+ if ( ! empty( $response['domain'] ) ) {
313
+ if ( strpos( $site_url, 'amazonaws.com' ) || strpos( $site_url, 'digitaloceanspaces.com' ) ) {
314
+ $this->set_exactdn_option( 'verify_method', -1, false );
315
+ }
316
+ if ( get_option( 'exactdn_never_been_active' ) ) {
317
+ $this->set_option( $this->prefix . 'lazy_load', true );
318
+ delete_option( 'exactdn_never_been_active' );
319
+ }
320
+ if ( 'external' === get_option( 'elementor_css_print_method' ) ) {
321
+ update_option( 'elementor_css_print_method', 'internal' );
322
+ }
323
+ if ( function_exists( 'et_get_option' ) && function_exists( 'et_update_option' ) && 'on' === et_get_option( 'et_pb_static_css_file', 'on' ) ) {
324
+ et_update_option( 'et_pb_static_css_file', 'off' );
325
+ et_update_option( 'et_pb_css_in_footer', 'off' );
326
+ }
327
+ return $this->set_exactdn_domain( $response['domain'] );
328
  }
329
+ } elseif ( ! empty( $result['body'] ) && strpos( $result['body'], 'error' ) !== false ) {
330
+ $response = json_decode( $result['body'], true );
331
+ $error_message = $response['error'];
332
+ $this->debug_message( "exactdn activation request failed: $error_message" );
333
+ global $exactdn_activate_error;
334
+ $exactdn_activate_error = $error_message;
335
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' );
336
+ return false;
337
  }
 
 
 
 
338
  return false;
339
  }
 
 
340
 
341
+ /**
342
+ * Verify the ExactDN domain.
343
+ *
344
+ * @param string $domain The ExactDN domain to verify.
345
+ * @return bool Whether the domain is still valid.
346
+ */
347
+ function verify_domain( $domain ) {
348
+ if ( empty( $domain ) ) {
349
+ return false;
350
+ }
351
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
352
+ // Check the time, to see how long it has been since we verified the domain.
353
+ $last_checkin = $this->get_exactdn_option( 'checkin' );
354
+ if ( $this->get_exactdn_option( 'verified' ) ) {
355
+ return true;
356
+ }
357
+
358
+ $this->check_verify_method();
359
+
360
+ // Set a default error.
361
+ global $exactdn_activate_error;
362
+ $exactdn_activate_error = 'zone not verified';
363
+ if ( ! defined( 'EXACTDN_LOCAL_DOMAIN' ) && $this->get_exactdn_option( 'verify_method' ) > 0 ) {
364
+ // Test with an image file that should be available on the ExactDN zone.
365
+ $test_url = plugins_url( '/images/test.png', constant( strtoupper( $this->prefix ) . 'PLUGIN_FILE' ) );
366
+ $local_domain = $this->parse_url( $test_url, PHP_URL_HOST );
367
+ $test_url = str_replace( $local_domain, $domain, $test_url );
368
+ $this->debug_message( "test url is $test_url" );
369
+ add_filter( 'http_headers_useragent', $this->prefix . 'cloud_useragent', PHP_INT_MAX );
370
+ $test_result = wp_remote_get( $test_url );
371
+ if ( is_wp_error( $test_result ) ) {
372
+ $error_message = $test_result->get_error_message();
373
+ $this->debug_message( "exactdn verification request failed: $error_message" );
374
+ $exactdn_activate_error = $error_message;
375
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' );
376
+ return false;
377
+ } elseif ( ! empty( $test_result['body'] ) && strlen( $test_result['body'] ) > 300 ) {
378
+ if ( 200 === $test_result['response']['code'] &&
379
+ ( '89504e470d0a1a0a' === bin2hex( substr( $test_result['body'], 0, 8 ) ) || '52494646' === bin2hex( substr( $test_result['body'], 0, 4 ) ) ) ) {
380
+ $this->debug_message( 'exactdn (real-world) verification succeeded' );
381
+ $this->set_exactdn_option( 'verified', 1, false );
382
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_success' );
383
+ return true;
384
+ }
385
+ $this->debug_message( 'mime check failed: ' . bin2hex( substr( $test_result['body'], 0, 3 ) ) );
386
+ $exactdn_activate_error = 'zone setup pending';
387
+ }
388
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' );
389
  return false;
390
  }
 
 
391
 
392
+ // Secondary test against the API db.
393
+ $url = 'http://optimize.exactlywww.com/exactdn/verify.php';
394
+ $ssl = wp_http_supports( array( 'ssl' ) );
395
+ if ( $ssl ) {
396
+ $url = set_url_scheme( $url, 'https' );
397
+ }
398
+ add_filter( 'http_headers_useragent', $this->prefix . 'cloud_useragent', PHP_INT_MAX );
399
+ $result = wp_remote_post(
400
+ $url,
401
+ array(
402
+ 'timeout' => 10,
403
+ 'body' => array(
404
+ 'alias' => $domain,
405
+ ),
406
+ )
407
+ );
408
+ if ( is_wp_error( $result ) ) {
409
+ $error_message = $result->get_error_message();
410
+ $this->debug_message( "exactdn verification request failed: $error_message" );
411
+ $exactdn_activate_error = $error_message;
412
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' );
413
  return false;
414
+ } elseif ( ! empty( $result['body'] ) && strpos( $result['body'], 'error' ) === false ) {
415
+ $response = json_decode( $result['body'], true );
416
+ if ( ! empty( $response['success'] ) ) {
417
+ $this->debug_message( 'exactdn (secondary) verification succeeded' );
 
418
  $this->set_exactdn_option( 'verified', 1, false );
419
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_success' );
420
  return true;
421
  }
422
+ } elseif ( ! empty( $result['body'] ) ) {
423
+ $response = json_decode( $result['body'], true );
424
+ $error_message = $response['error'];
425
+ $this->debug_message( "exactdn verification request failed: $error_message" );
426
+ $exactdn_activate_error = $error_message;
427
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' );
428
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  }
430
+ add_action( 'admin_notices', $this->prefix . 'notice_exactdn_activation_error' );
 
 
 
 
431
  return false;
432
  }
 
 
 
433
 
434
+ /**
435
+ * Run a simulation to decide which verification method to use.
436
+ */
437
+ function check_verify_method() {
438
+ if ( ! $this->get_exactdn_option( 'verify_method' ) ) {
439
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
440
+ // Prelim test with a known valid image to ensure http(s) connectivity.
441
+ $sim_url = 'https://optimize.exactdn.com/exactdn/testorig.jpg';
442
+ add_filter( 'http_headers_useragent', $this->prefix . 'cloud_useragent', PHP_INT_MAX );
443
+ $sim_result = wp_remote_get( $sim_url );
444
+ if ( is_wp_error( $sim_result ) ) {
445
+ $error_message = $sim_result->get_error_message();
446
+ $this->debug_message( "exactdn (simulated) verification request failed: $error_message" );
447
+ } elseif ( ! empty( $sim_result['body'] ) && strlen( $sim_result['body'] ) > 300 ) {
448
+ if ( 'ffd8ff' === bin2hex( substr( $sim_result['body'], 0, 3 ) ) ) {
449
+ $this->debug_message( 'exactdn (simulated) verification succeeded' );
450
+ $this->set_exactdn_option( 'verify_method', 1, false );
451
+ return;
452
+ }
453
  }
454
+ $this->debug_message( 'exactdn (simulated) verification request failed, error unknown' );
455
+ $this->set_exactdn_option( 'verify_method', -1, false );
456
  }
 
 
457
  }
 
458
 
459
+ /**
460
+ * Validate the ExactDN domain.
461
+ *
462
+ * @param string $domain The unverified ExactDN domain.
463
+ * @return string The validated ExactDN domain.
464
+ */
465
+ function sanitize_domain( $domain ) {
466
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
467
+ if ( ! $domain ) {
468
+ return;
469
+ }
470
+ if ( strlen( $domain ) > 80 ) {
471
+ $this->debug_message( "$domain too long" );
472
+ return false;
473
+ }
474
+ if ( ! preg_match( '#^[A-Za-z0-9\.\-]+$#', $domain ) ) {
475
+ $this->debug_message( "$domain has bad characters" );
476
+ return false;
477
+ }
478
+ return $domain;
479
  }
 
 
480
 
481
+ /**
482
+ * Get the ExactDN domain name to use.
483
+ *
484
+ * @return string The ExactDN domain name for this site or network.
485
+ */
486
+ function get_exactdn_domain() {
487
+ if ( defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) {
488
+ return $this->sanitize_domain( EXACTDN_DOMAIN );
489
+ }
490
+ if ( is_multisite() ) {
491
+ if ( ! SUBDOMAIN_INSTALL ) {
492
+ return $this->sanitize_domain( get_site_option( $this->prefix . 'exactdn_domain' ) );
493
+ }
494
  }
495
+ return $this->sanitize_domain( get_option( $this->prefix . 'exactdn_domain' ) );
496
  }
 
 
497
 
498
+ /**
499
+ * Method to override the ExactDN domain at runtime, use with caution.
500
+ *
501
+ * @param string $domain The ExactDN domain to use instead.
502
+ */
503
+ function set_domain( $domain ) {
504
+ if ( is_string( $domain ) ) {
505
+ $this->exactdn_domain = $domain;
506
+ }
 
 
 
 
 
 
 
 
 
 
507
  }
508
+ /**
509
+ * Get the ExactDN option.
510
+ *
511
+ * @param string $option_name The name of the ExactDN option.
512
+ * @return int The numerical value of the option.
513
+ */
514
+ function get_exactdn_option( $option_name ) {
515
+ if ( defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) {
516
+ return (int) get_option( $this->prefix . 'exactdn_' . $option_name );
517
+ }
518
+ if ( is_multisite() ) {
519
+ if ( ! SUBDOMAIN_INSTALL ) {
520
+ return (int) get_site_option( $this->prefix . 'exactdn_' . $option_name );
521
+ }
522
  }
523
+ return (int) get_option( $this->prefix . 'exactdn_' . $option_name );
524
  }
 
 
525
 
526
+ /**
527
+ * Set the ExactDN domain name to use.
528
+ *
529
+ * @param string $domain The ExactDN domain name for this site or network.
530
+ */
531
+ function set_exactdn_domain( $domain ) {
532
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
533
+ if ( defined( 'EXACTDN_DOMAIN' ) && $this->sanitize_domain( EXACTDN_DOMAIN ) ) {
534
+ return true;
535
+ }
536
+ $domain = $this->sanitize_domain( $domain );
537
+ if ( ! $domain ) {
538
+ return false;
539
+ }
540
+ if ( is_multisite() ) {
541
+ if ( ! SUBDOMAIN_INSTALL ) {
542
+ update_site_option( $this->prefix . 'exactdn_domain', $domain );
543
+ return $domain;
544
+ }
545
  }
546
+ update_option( $this->prefix . 'exactdn_domain', $domain );
547
+ return $domain;
548
  }
 
 
 
549
 
550
+ /**
551
+ * Set an option for ExactDN.
552
+ *
553
+ * @param string $option_name The name of the ExactDN option.
554
+ * @param int $option_value The value to set for the ExactDN option.
555
+ * @param bool $autoload Optional. Whether to load the option when WordPress starts up.
556
+ */
557
+ function set_exactdn_option( $option_name, $option_value, $autoload = null ) {
558
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
559
+ if ( defined( 'EXACTDN_DOMAIN' ) && EXACTDN_DOMAIN ) {
560
+ return update_option( $this->prefix . 'exactdn_' . $option_name, $option_value, $autoload );
561
+ }
562
+ if ( is_multisite() ) {
563
+ if ( ! SUBDOMAIN_INSTALL ) {
564
+ return update_site_option( $this->prefix . 'exactdn_' . $option_name, $option_value );
565
+ }
566
  }
567
+ return update_option( $this->prefix . 'exactdn_' . $option_name, $option_value, $autoload );
568
  }
 
 
569
 
570
+ /**
571
+ * Validate the user-defined exclusions for "all the things" rewriting.
572
+ */
573
+ function validate_user_exclusions() {
574
+ if ( defined( 'EXACTDN_EXCLUDE' ) && EXACTDN_EXCLUDE ) {
575
+ $user_exclusions = EXACTDN_EXCLUDE;
576
+ }
577
+ if ( ! empty( $user_exclusions ) ) {
578
+ if ( is_string( $user_exclusions ) ) {
579
+ $user_exclusions = array( $user_exclusions );
580
+ }
581
+ if ( is_array( $user_exclusions ) ) {
582
+ foreach ( $user_exclusions as $exclusion ) {
583
+ if ( false !== strpos( $exclusion, 'wp-content' ) ) {
584
+ $exclusion = preg_replace( '#([^"\'?>]+?)?wp-content/#i', '', $exclusion );
585
+ }
586
+ $this->user_exclusions[] = ltrim( $exclusion, '/' );
587
  }
 
588
  }
589
  }
590
  }
 
591
 
 
 
 
 
 
 
 
 
 
 
592
  /**
593
+ * Get $content_width, with a filter.
594
  *
595
+ * @return bool|string The content width, if set. Default false.
596
  */
597
+ function get_content_width() {
598
+ $content_width = isset( $GLOBALS['content_width'] ) && is_numeric( $GLOBALS['content_width'] ) && $GLOBALS['content_width'] > 100 ? $GLOBALS['content_width'] : 1920;
599
+ if ( function_exists( 'twentynineteen_setup' ) && 640 === (int) $content_width ) {
600
+ $content_width = 932;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601
  }
602
+ /**
603
+ * Filter the Content Width value.
604
+ *
605
+ * @param string $content_width Content Width value.
606
+ */
607
+ return (int) apply_filters( 'exactdn_content_width', $content_width );
608
+ }
609
+
610
+ /**
611
+ * Get width within an ExactDN url.
612
+ *
613
+ * @param string $url The ExactDN url to parse.
614
+ * @return string The width, if found.
615
+ */
616
+ public function get_exactdn_width_from_url( $url ) {
617
+ $url_args = $this->parse_url( $url, PHP_URL_QUERY );
618
+ if ( ! $url_args ) {
619
+ return '';
620
  }
621
+ $args = explode( '&', $url_args );
622
+ foreach ( $args as $arg ) {
623
+ if ( preg_match( '#w=(\d+)#', $arg, $width_match ) ) {
624
+ return $width_match[1];
625
+ }
626
+ if ( preg_match( '#resize=(\d+)#', $arg, $width_match ) ) {
627
+ return $width_match[1];
628
+ }
629
+ if ( preg_match( '#fit=(\d+)#', $arg, $width_match ) ) {
630
+ return $width_match[1];
631
+ }
632
  }
633
+ return '';
634
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
 
636
  /**
637
+ * Identify images in page content, and if images are local (uploaded to the current site), pass through ExactDN.
638
  *
639
+ * @param string $content The page/post content.
640
+ * @return string The content with ExactDN image urls.
641
  */
642
+ function filter_page_output( $content ) {
643
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
644
+ $this->filtering_the_page = true;
645
 
646
+ $content = $this->filter_the_content( $content );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
647
 
648
+ /**
649
+ * Allow parsing the full page content after ExactDN is finished with it.
650
+ *
651
+ * @param string $content The fully-parsed HTML code of the page.
652
+ */
653
+ $content = apply_filters( 'exactdn_the_page', $content );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
 
655
+ $this->filtering_the_page = false;
656
+ $this->debug_message( "parsing page took $this->elapsed_time seconds" );
657
+ return $content;
658
+ }
659
 
660
+ /**
661
+ * Identify images in the content, and if images are local (uploaded to the current site), pass through ExactDN.
662
+ *
663
+ * @param string $content The page/post content.
664
+ * @return string The content with ExactDN image urls.
665
+ */
666
+ function filter_the_content( $content ) {
667
+ $started = microtime( true );
668
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
669
+ $images = $this->get_images_from_html( $content, true );
670
+
671
+ if ( ! empty( $images ) ) {
672
+ $this->debug_message( 'we have images to parse' );
673
+ $content_width = false;
674
+ if ( ! $this->filtering_the_page ) {
675
+ $this->filtering_the_content = true;
676
+ $this->debug_message( 'filtering the content' );
677
+ $content_width = $this->get_content_width();
678
  }
679
+ $resize_existing = defined( 'EXACTDN_RESIZE_EXISTING' ) && EXACTDN_RESIZE_EXISTING;
680
 
681
+ $image_sizes = $this->image_sizes();
682
 
683
+ foreach ( $images[0] as $index => $tag ) {
684
+ // Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained.
685
+ $transform = 'resize';
 
686
 
687
+ // Start with a clean slate each time.
688
+ $attachment_id = false;
689
+ $exactdn_url = false;
690
+ $width = false;
691
+ $lazy = false;
692
+ $srcset_fill = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
 
694
+ // Flag if we need to munge a fullsize URL.
695
+ $fullsize_url = false;
 
 
 
 
 
 
 
696
 
697
+ // Identify image source.
698
+ $src = $images['img_url'][ $index ];
699
+ $src_orig = $images['img_url'][ $index ];
700
+ $this->debug_message( $src );
701
+
702
+ /**
703
+ * Allow specific images to be skipped by ExactDN.
704
+ *
705
+ * @param bool false Should ExactDN ignore this image. Default false.
706
+ * @param string $src Image URL.
707
+ * @param string $tag Image HTML Tag.
708
+ */
709
+ if ( apply_filters( 'exactdn_skip_image', false, $src, $tag ) ) {
710
+ continue;
 
711
  }
712
 
713
+ $this->debug_message( 'made it passed the filters' );
 
 
714
 
715
+ // Log 0-size Pinterest schema images.
716
+ if ( strpos( $tag, 'data-pin-description=' ) && strpos( $tag, 'width="0" height="0"' ) ) {
717
+ $this->debug_message( 'data-pin/Pinterest image' );
 
 
 
 
 
718
  }
719
 
720
+ // Pre-empt srcset fill if the surrounding link has a background image or if there is a data-desktop attribute indicating a potential slider.
721
+ if ( strpos( $tag, 'background-image:' ) || strpos( $tag, 'data-desktop=' ) ) {
722
+ $srcset_fill = false;
723
+ }
724
+ /**
725
+ * Documented in generate_url, in this case used to detect images that should bypass srcset fill.
726
+ *
727
+ * @param array|string $args Array of ExactDN arguments.
728
+ * @param string $image_url Image URL.
729
+ * @param string|null $scheme Image scheme. Default to null.
730
+ */
731
+ $args = apply_filters( 'exactdn_pre_args', array( 'test' => 'lazy-test' ), $src, null );
732
+ if ( empty( $args ) ) {
733
+ $srcset_fill = false;
734
+ }
735
+ // Support Lazy Load plugins.
736
+ // Don't modify $tag yet as we need unmodified version later.
737
+ $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-lazy-src' );
738
+ if ( $lazy_load_src ) {
739
+ $placeholder_src = $src;
740
+ $placeholder_src_orig = $src;
741
+ $src = $lazy_load_src;
742
+ $src_orig = $lazy_load_src;
743
+ $this->srcset_attr = 'data-lazy-srcset';
744
+ $lazy = true;
745
+ $srcset_fill = true;
746
  }
747
+ // Must be a legacy Jetpack thing as far as I can tell, no matches found in any currently installed plugins.
748
+ $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-lazy-original' );
749
+ if ( ! $lazy && $lazy_load_src ) {
750
+ $placeholder_src = $src;
751
+ $placeholder_src_orig = $src;
752
+ $src = $lazy_load_src;
753
+ $src_orig = $lazy_load_src;
754
+ $lazy = true;
755
  }
756
+ if ( ! $lazy && strpos( $images['img_tag'][ $index ], 'a3-lazy-load/assets/images/lazy_placeholder' ) ) {
757
+ $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-src' );
 
758
  }
759
+ if (
760
+ ! $lazy &&
761
+ strpos( $images['img_tag'][ $index ], ' data-src=' ) &&
762
+ strpos( $images['img_tag'][ $index ], 'lazyload' ) &&
763
+ (
764
+ strpos( $images['img_tag'][ $index ], 'data:image/gif' ) ||
765
+ strpos( $images['img_tag'][ $index ], 'data:image/svg' )
766
+ )
767
+ ) {
768
+ $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-src' );
769
+ $this->debug_message( "found eio ll src: $lazy_load_src" );
770
+ }
771
+ if ( ! $lazy && $lazy_load_src ) {
772
+ $placeholder_src = $src;
773
+ $placeholder_src_orig = $src;
774
+ $src = $lazy_load_src;
775
+ $src_orig = $lazy_load_src;
776
+ $this->srcset_attr = 'data-srcset';
777
+ $lazy = true;
778
+ $srcset_fill = true;
779
+ }
780
+ if ( ! $lazy && strpos( $images['img_tag'][ $index ], 'revslider/admin/assets/images/dummy' ) ) {
781
+ $lazy_load_src = $this->get_attribute( $images['img_tag'][ $index ], 'data-lazyload' );
782
+ }
783
+ if ( ! $lazy && $lazy_load_src ) {
784
+ $placeholder_src = $src;
785
+ $placeholder_src_orig = $src;
786
+ $src = $lazy_load_src;
787
+ $src_orig = $lazy_load_src;
788
+ $lazy = true;
789
+ }
790
+
791
+ // Check for relative urls that start with a slash. Unlikely that we'll attempt relative urls beyond that.
792
+ if (
793
+ '/' === substr( $src, 0, 1 ) &&
794
+ '/' !== substr( $src, 1, 1 ) &&
795
+ false === strpos( $this->upload_domain, 'amazonaws.com' ) &&
796
+ false === strpos( $this->upload_domain, 'digitaloceanspaces.com' )
797
+ ) {
798
+ $src = '//' . $this->upload_domain . $src;
799
+ }
800
+
801
+ // Check if image URL should be used with ExactDN.
802
+ if ( $this->validate_image_url( $src ) ) {
803
+ $this->debug_message( 'url validated' );
804
+
805
+ // Find the width and height attributes.
806
+ $width = $this->get_img_width( $images['img_tag'][ $index ] );
807
+ $height = $this->get_attribute( $images['img_tag'][ $index ], 'height' );
808
+ // Falsify them if empty.
809
+ $width = $width ? $width : false;
810
+ $height = $height ? $height : false;
811
+
812
+ // Can't pass both a relative width and height, so unset the dimensions in favor of not breaking the horizontal layout.
813
+ if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) ) {
814
+ $width = false;
815
+ $height = false;
816
+ }
817
+
818
+ // Detect WP registered image size from HTML class.
819
+ if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) {
820
+ $size = array_pop( $size );
821
+
822
+ $this->debug_message( "detected $size" );
823
+ if ( false === $width && false === $height && 'full' !== $size && array_key_exists( $size, $image_sizes ) ) {
824
+ $width = (int) $image_sizes[ $size ]['width'];
825
+ $height = (int) $image_sizes[ $size ]['height'];
826
+ $transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
827
+ }
828
+ } else {
829
+ unset( $size );
830
+ }
831
+
832
+ list( $filename_width, $filename_height ) = $this->get_dimensions_from_filename( $src );
833
+ if ( false === $width && false === $height ) {
834
+ $width = $filename_width;
835
+ $height = $filename_height;
836
+ }
837
+ // WP Attachment ID, if uploaded to this site.
838
+ $attachment_id = $this->get_attribute( $images['img_tag'][ $index ], 'data-id' );
839
+ if ( empty( $attachment_id ) ) {
840
+ $this->debug_message( 'data-id not found, looking for wp-image-x in class' );
841
+ preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id );
842
  }
843
+ if ( ! $this->get_option( 'exactdn_prevent_db_queries' ) && empty( $attachment_id ) ) {
844
+ $this->debug_message( 'looking for attachment id' );
845
+ $attachment_id = attachment_url_to_postid( $src );
846
+ }
847
+ if ( ! $this->get_option( 'exactdn_prevent_db_queries' ) && ! empty( $attachment_id ) ) {
848
+ if ( is_array( $attachment_id ) ) {
849
+ $attachment_id = intval( array_pop( $attachment_id ) );
850
+ }
851
+ $this->debug_message( "using attachment id ($attachment_id) to get source image" );
852
+
853
+ if ( $attachment_id ) {
854
+ $this->debug_message( "detected attachment $attachment_id" );
855
+ $attachment = get_post( $attachment_id );
856
+
857
+ // Basic check on returned post object.
858
+ if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' === $attachment->post_type ) {
859
+ $src_per_wp = wp_get_attachment_image_src( $attachment_id, 'full' );
860
+
861
+ if ( $src_per_wp && is_array( $src_per_wp ) ) {
862
+ $this->debug_message( "src retrieved from db: {$src_per_wp[0]}, checking for match" );
863
+ $fullsize_url_path = $this->parse_url( $src_per_wp[0], PHP_URL_PATH );
864
+ if ( is_null( $fullsize_url_path ) ) {
865
+ $src_per_wp = false;
866
+ } elseif ( $fullsize_url_path ) {
867
+ $fullsize_url_basename = pathinfo( $fullsize_url_path, PATHINFO_FILENAME );
868
+ $this->debug_message( "looking for $fullsize_url_basename in $src" );
869
+ if ( strpos( wp_basename( $src ), $fullsize_url_basename ) === false ) {
870
+ $this->debug_message( 'fullsize url does not match' );
871
+ $src_per_wp = false;
872
+ }
873
+ } else {
874
  $src_per_wp = false;
875
  }
 
 
876
  }
 
877
 
878
+ if ( $src_per_wp && $this->validate_image_url( $src_per_wp[0] ) ) {
879
+ $this->debug_message( "detected $width filenamew $filename_width" );
880
+ if ( $resize_existing || ( $width && (int) $filename_width !== (int) $width ) ) {
881
+ $this->debug_message( 'resizing existing or width does not match' );
882
+ $src = $src_per_wp[0];
883
+ }
884
+ $fullsize_url = true;
885
 
886
+ // Prevent image distortion if a detected dimension exceeds the image's natural dimensions.
887
+ if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) {
888
+ $width = false === $width ? false : min( $width, $src_per_wp[1] );
889
+ $height = false === $height ? false : min( $height, $src_per_wp[2] );
890
+ $this->debug_message( "constrained to attachment dims, w=$width and h=$height" );
891
+ }
892
 
893
+ // If no width and height are found, max out at source image's natural dimensions.
894
+ // Otherwise, respect registered image sizes' cropping setting.
895
+ if ( false === $width && false === $height ) {
896
+ $width = $src_per_wp[1];
897
+ $height = $src_per_wp[2];
898
+ $transform = 'fit';
899
+ $this->debug_message( "no dims, using attachment dims, w=$width and h=$height" );
900
+ } elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) {
901
+ $transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
902
+ $this->debug_message( 'attachment size set to crop' );
903
+ }
904
  }
905
+ } else {
906
+ unset( $attachment_id );
907
+ unset( $attachment );
908
  }
 
 
 
909
  }
910
  }
911
+ $constrain_width = (int) $content_width;
912
+ if ( ! empty( $images['figure_class'][ $index ] ) && false !== strpos( $images['figure_class'][ $index ], 'alignfull' ) && current_theme_supports( 'align-wide' ) ) {
913
+ $constrain_width = (int) apply_filters( 'exactdn_full_align_image_width', max( 1920, $content_width ) );
914
+ } elseif ( ! empty( $images['figure_class'][ $index ] ) && false !== strpos( $images['figure_class'][ $index ], 'alignwide' ) && current_theme_supports( 'align-wide' ) ) {
915
+ $constrain_width = (int) apply_filters( 'exactdn_wide_align_image_width', max( 1500, $content_width ) );
 
 
 
 
 
 
 
 
 
 
 
916
  }
917
+ // If width is available, constrain to $content_width.
918
+ if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $constrain_width ) ) {
919
+ if ( $width > $constrain_width && false !== $height && false === strpos( $height, '%' ) ) {
920
+ $this->debug_message( 'constraining to content width' );
921
+ $height = round( ( $constrain_width * $height ) / $width );
922
+ $width = $constrain_width;
923
+ } elseif ( $width > $constrain_width ) {
924
+ $this->debug_message( 'constraining to content width' );
925
+ $width = $constrain_width;
926
+ }
927
  }
 
928
 
929
+ // Set a width if none is found and $content_width is available.
930
+ // If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing.
931
+ if ( false === $width && is_numeric( $constrain_width ) ) {
932
+ $width = (int) $constrain_width;
933
 
934
+ if ( false !== $height ) {
935
+ $transform = 'fit';
936
+ }
937
+ }
938
 
939
+ // Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation.
940
+ if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode( '|', $this->extensions ) . '){1}$#i', wp_basename( $src ), $filename ) ) {
941
+ $fullsize_url = true;
942
+ }
 
 
 
943
 
944
+ // Build array of ExactDN args and expose to filter before passing to ExactDN URL function.
 
945
  $args = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
946
 
947
+ if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) ) {
948
+ $args[ $transform ] = $width . ',' . $height;
949
+ } elseif ( false !== $width ) {
950
+ $args['w'] = $width;
951
+ } elseif ( false !== $height ) {
952
+ $args['h'] = $height;
953
+ }
954
 
955
+ if ( ! $resize_existing && ( ! $width || (int) $filename_width === (int) $width ) ) {
956
+ $this->debug_message( 'preventing resize' );
957
+ $args = array();
958
+ } elseif ( ! $fullsize_url ) {
959
+ // Build URL, first maybe removing WP's resized string so we pass the original image to ExactDN (for higher quality).
960
+ $src = $this->strip_image_dimensions_maybe( $src );
961
+ }
962
 
963
+ if ( ! $this->get_option( 'exactdn_prevent_db_queries' ) && ! empty( $attachment_id ) ) {
964
+ $this->debug_message( 'using attachment id to check smart crop' );
965
+ $args = $this->maybe_smart_crop( $args, $attachment_id );
966
  }
967
 
968
+ /**
969
+ * Filter the array of ExactDN arguments added to an image.
970
+ * By default, only includes width and height values.
971
+ *
972
+ * @param array $args Array of ExactDN Arguments.
973
+ * @param array $args {
974
+ * Array of image details.
975
+ *
976
+ * @type $tag Image tag (Image HTML output).
977
+ * @type $src Image URL.
978
+ * @type $src_orig Original Image URL.
979
+ * @type $width Image width.
980
+ * @type $height Image height.
981
+ * }
982
+ */
983
+ $args = apply_filters( 'exactdn_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height' ) );
984
+ $this->debug_message( "width $width" );
985
+ $this->debug_message( "height $height" );
986
+ $this->debug_message( "transform $transform" );
987
+
988
+ $exactdn_url = $this->generate_url( $src, $args );
989
+ $this->debug_message( "new url $exactdn_url" );
990
+
991
+ // Modify image tag if ExactDN function provides a URL
992
+ // Ensure changes are only applied to the current image by copying and modifying the matched tag, then replacing the entire tag with our modified version.
993
+ if ( $src !== $exactdn_url ) {
994
+ $new_tag = $tag;
995
+
996
+ // If present, replace the link href with an ExactDN URL for the full-size image.
997
+ if ( ! empty( $images['link_url'][ $index ] ) && $this->validate_image_url( $images['link_url'][ $index ] ) ) {
998
+ $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . $this->generate_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 );
999
  }
 
1000
 
1001
+ // Insert new image src into the srcset as well, if we have a width.
1002
+ if ( false !== $width && false === strpos( $width, '%' ) && $width ) {
1003
+ $this->debug_message( 'checking to see if srcset width already exists' );
1004
+ $srcset_url = $exactdn_url . ' ' . (int) $width . 'w, ';
1005
+ $new_srcset_attr = $this->get_attribute( $new_tag, $this->srcset_attr );
1006
+ if ( $new_srcset_attr && false === strpos( $new_srcset_attr, ' ' . (int) $width . 'w' ) && ! preg_match( '/\s(1|2|3)x/', $new_srcset_attr ) ) {
1007
+ $this->debug_message( 'src not in srcset, adding' );
1008
+ $this->set_attribute( $new_tag, $this->srcset_attr, $srcset_url . $new_srcset_attr, true );
1009
+ }
1010
  }
 
1011
 
1012
+ // Check if content width pushed the respimg sizes attribute too far down.
1013
+ if ( ! empty( $constrain_width ) && (int) $constrain_width !== (int) $content_width ) {
1014
+ $sizes_attr = $this->get_attribute( $new_tag, 'sizes' );
1015
+ $new_sizes_attr = str_replace( ' ' . $content_width . 'px', ' ' . $constrain_width . 'px', $sizes_attr );
1016
+ if ( $sizes_attr !== $new_sizes_attr ) {
1017
+ $new_tag = str_replace( $sizes_attr, $new_sizes_attr, $new_tag );
1018
+ }
1019
+ }
1020
 
1021
+ // Cleanup ExactDN URL.
1022
+ $exactdn_url = str_replace( '&#038;', '&', esc_url( $exactdn_url ) );
1023
+ // Supplant the original source value with our ExactDN URL.
1024
+ $this->debug_message( "replacing $src_orig with $exactdn_url" );
1025
+ $new_tag = str_replace( $src_orig, $exactdn_url, $new_tag );
1026
 
1027
+ // If Lazy Load is in use, pass placeholder image through ExactDN.
1028
+ if ( isset( $placeholder_src ) && $this->validate_image_url( $placeholder_src ) ) {
1029
+ $placeholder_src = $this->generate_url( $placeholder_src );
1030
 
1031
+ if ( $placeholder_src !== $placeholder_src_orig ) {
1032
+ $new_tag = str_replace( $placeholder_src_orig, str_replace( '&#038;', '&', esc_url( $placeholder_src ) ), $new_tag );
1033
+ }
1034
 
1035
+ unset( $placeholder_src );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1036
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
1037
 
1038
+ // Replace original tag with modified version.
1039
+ $content = str_replace( $tag, $new_tag, $content );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1040
  }
1041
+ } elseif ( ! $lazy && $this->validate_image_url( $src, true ) ) {
1042
+ $this->debug_message( "found a potential exactdn src url to insert into srcset: $src" );
1043
+ // Find the width attribute.
1044
+ $width = $this->get_img_width( $images['img_tag'][ $index ] );
1045
+ if ( $width ) {
1046
+ $this->debug_message( 'found the width' );
1047
+ // Insert new image src into the srcset as well, if we have a width.
1048
+ if (
1049
+ false !== $width &&
1050
+ false === strpos( $width, '%' ) &&
1051
+ false !== strpos( $src, $width ) &&
1052
+ false !== strpos( $src, $this->exactdn_domain )
1053
+ ) {
1054
+ $new_tag = $tag;
1055
+ $exactdn_url = $src;
1056
+ $this->debug_message( 'checking to see if srcset width already exists' );
1057
+ $srcset_url = $exactdn_url . ' ' . (int) $width . 'w, ';
1058
+ $new_srcset_attr = $this->get_attribute( $new_tag, $this->srcset_attr );
1059
+ if ( $new_srcset_attr && false === strpos( $new_srcset_attr, ' ' . (int) $width . 'w' ) && ! preg_match( '/\s(1|2|3)x/', $new_srcset_attr ) ) {
1060
+ $this->debug_message( 'src not in srcset, adding' );
1061
+ $this->set_attribute( $new_tag, $this->srcset_attr, $srcset_url . $new_srcset_attr, true );
1062
+ // Replace original tag with modified version.
1063
+ $content = str_replace( $tag, $new_tag, $content );
1064
+ }
1065
+ }
1066
  }
1067
+ } elseif ( $lazy && ! empty( $placeholder_src ) && $this->validate_image_url( $placeholder_src ) ) {
1068
+ $new_tag = $tag;
1069
+ // If Lazy Load is in use, pass placeholder image through ExactDN.
1070
+ $placeholder_src = $this->generate_url( $placeholder_src );
1071
+ if ( $placeholder_src !== $placeholder_src_orig ) {
1072
+ $new_tag = str_replace( $placeholder_src_orig, str_replace( '&#038;', '&', esc_url( $placeholder_src ) ), $new_tag );
1073
+ // Replace original tag with modified version.
1074
+ $content = str_replace( $tag, $new_tag, $content );
1075
  }
1076
+ unset( $placeholder_src );
1077
+ } // End if().
1078
+
1079
+ // At this point, we discard the original src in favor of the ExactDN url.
1080
+ if ( ! empty( $exactdn_url ) ) {
1081
+ $src = $exactdn_url;
1082
+ }
1083
+ if ( $srcset_fill && ( ! defined( 'EXACTDN_PREVENT_SRCSET_FILL' ) || ! EXACTDN_PREVENT_SRCSET_FILL ) && false !== strpos( $src, $this->exactdn_domain ) ) {
1084
+ if ( ! $this->get_attribute( $images['img_tag'][ $index ], $this->srcset_attr ) && ! $this->get_attribute( $images['img_tag'][ $index ], 'sizes' ) ) {
1085
+ $this->debug_message( "srcset filling with $src" );
1086
+ $zoom = false;
1087
+ // If $width is empty, we'll search the url for a width param, then we try searching the img element, with fall back to the filename.
1088
+ if ( empty( $width ) || ! is_numeric( $width ) ) {
1089
+ // This only searches for w, resize, or fit flags, others are ignored.
1090
+ $width = $this->get_exactdn_width_from_url( $src );
1091
+ if ( $width ) {
1092
+ $zoom = true;
1093
+ }
1094
+ }
1095
+ if ( empty( $width ) || ! is_numeric( $width ) ) {
1096
+ $width = $this->get_img_width( $images['img_tag'][ $index ] );
1097
+ }
1098
+ list( $filename_width, $discard_height ) = $this->get_dimensions_from_filename( $src );
1099
+ if ( empty( $width ) || ! is_numeric( $width ) ) {
1100
+ $width = $filename_width;
1101
+ }
1102
+ if ( empty( $width ) || ! is_numeric( $width ) ) {
1103
+ $width = $this->get_attribute( $images['img_tag'][ $index ], 'data-actual-width' );
1104
+ }
1105
+ if ( false !== strpos( $src, 'crop=' ) || false !== strpos( $src, '&h=' ) || false !== strpos( $src, '?h=' ) ) {
1106
+ $width = false;
1107
+ }
1108
+ // Then add a srcset and sizes.
1109
+ if ( $width ) {
1110
+ $srcset = $this->generate_image_srcset( $src, $width, $zoom, $filename_width );
1111
+ if ( $srcset ) {
1112
+ $new_tag = $images['img_tag'][ $index ];
1113
+ $this->set_attribute( $new_tag, $this->srcset_attr, $srcset );
1114
+ $this->set_attribute( $new_tag, 'sizes', sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width ) );
1115
+ // Replace original tag with modified version.
1116
+ $content = str_replace( $images['img_tag'][ $index ], $new_tag, $content );
1117
+ }
1118
  }
1119
  }
1120
  }
1121
+ } // End foreach().
1122
+ } // End if();
1123
+ $content = $this->filter_bg_images( $content, 'div' );
1124
+ $content = $this->filter_bg_images( $content, 'li' );
1125
+ $content = $this->filter_bg_images( $content, 'span' );
1126
+ $content = $this->filter_bg_images( $content, 'section' );
1127
+ if ( $this->filtering_the_page ) {
1128
+ $content = $this->filter_prz_thumb( $content );
1129
+ }
1130
+ if ( $this->filtering_the_page && $this->get_option( 'exactdn_all_the_things' ) ) {
1131
+ $this->debug_message( 'rewriting all other wp_content urls' );
1132
+ if ( $this->exactdn_domain && $this->upload_domain ) {
1133
+ $escaped_upload_domain = str_replace( '.', '\.', ltrim( $this->upload_domain, 'w.' ) );
1134
+ $this->debug_message( $escaped_upload_domain );
1135
+ if ( ! empty( $this->user_exclusions ) ) {
1136
+ $content = preg_replace( '#(https?:)?//(?:www\.)?' . $escaped_upload_domain . '([^"\'?>]+?)?/wp-content/([^"\'?>]+?)?(' . implode( '|', $this->user_exclusions ) . ')#i', '$1//' . $this->upload_domain . '$2/?wpcontent-bypass?/$3$4', $content );
1137
+ }
1138
+ if ( strpos( $content, '<use ' ) ) {
1139
+ // Pre-empt rewriting of files within <use> tags, particularly to prevent security errors for SVGs.
1140
+ $content = preg_replace( '#(<use.+?href=["\'])(https?:)?//(?:www\.)?' . $escaped_upload_domain . '([^"\'?>]+?)/wp-content/#is', '$1$2//' . $this->upload_domain . '$3/?wpcontent-bypass?/', $content );
1141
+ }
1142
+ // Pre-empt rewriting of wp-includes and wp-content if the extension is not allowed by using a temporary placeholder.
1143
+ $content = preg_replace( '#(https?:)?//(?:www\.)?' . $escaped_upload_domain . '([^"\'?>]+?)?/wp-content/([^"\'?>]+?)\.(htm|html|php|ashx|m4v|mov|wvm|qt|webm|ogv|mp4|m4p|mpg|mpeg|mpv)#i', '$1//' . $this->upload_domain . '$2/?wpcontent-bypass?/$3.$4', $content );
1144
+ $content = str_replace( 'wp-content/themes/jupiter"', '?wpcontent-bypass?/themes/jupiter"', $content );
1145
+ $content = str_replace( 'wp-content/plugins/anti-captcha/', '?wpcontent-bypass?/plugins/anti-captcha', $content );
1146
+ if ( strpos( $this->upload_domain, 'amazonaws.com' ) || strpos( $this->upload_domain, 'digitaloceanspaces.com' ) ) {
1147
+ $content = preg_replace( '#(https?:)?//(?:www\.)?' . $escaped_upload_domain . $this->remove_path . '/#i', '$1//' . $this->exactdn_domain . '/', $content );
1148
+ } else {
1149
+ $content = preg_replace( '#(https?:)?//(?:www\.)?' . $escaped_upload_domain . '/([^"\'?>]+?)?(nextgen-image|wp-includes|wp-content)/#i', '$1//' . $this->exactdn_domain . '/$2$3/', $content );
1150
+ }
1151
+ $content = str_replace( '?wpcontent-bypass?', 'wp-content', $content );
1152
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1153
  }
1154
+ $this->debug_message( 'done parsing page' );
1155
+ $this->filtering_the_content = false;
1156
+
1157
+ $elapsed_time = microtime( true ) - $started;
1158
+ $this->debug_message( "parsing the_content took $elapsed_time seconds" );
1159
+ $this->elapsed_time += microtime( true ) - $started;
1160
+ $this->debug_message( "parsing the page took $this->elapsed_time seconds so far" );
1161
+ if ( ! $this->get_option( 'exactdn_prevent_db_queries' ) && $this->elapsed_time > .5 ) {
1162
+ $this->set_option( 'exactdn_prevent_db_queries', true );
1163
+ }
1164
+ return $content;
1165
  }
 
 
 
 
 
 
 
 
 
 
 
 
1166
 
1167
+ /**
1168
+ * Parse page content looking for elements with CSS background-image properties.
1169
+ *
1170
+ * @param string $content The HTML content to parse.
1171
+ * @param string $tag_type The type of HTML tag to look for.
1172
+ * @return string The filtered HTML content.
1173
+ */
1174
+ function filter_bg_images( $content, $tag_type ) {
1175
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
1176
+ $content_width = false;
1177
+ if ( ! $this->filtering_the_page ) {
1178
+ $content_width = $this->get_content_width();
1179
+ }
1180
+ $this->debug_message( "content width is $content_width" );
1181
+ // Process background images on elements.
1182
+ $elements = $this->get_elements_from_html( $content, $tag_type );
1183
+ if ( $this->is_iterable( $elements ) ) {
1184
+ foreach ( $elements as $index => $element ) {
1185
+ $this->debug_message( "parsing a $tag_type" );
1186
+ if ( false === strpos( $element, 'background:' ) && false === strpos( $element, 'background-image:' ) ) {
 
 
 
 
 
 
 
 
 
 
1187
  continue;
1188
  }
1189
+ $style = $this->get_attribute( $element, 'style' );
1190
+ if ( empty( $style ) ) {
1191
+ continue;
 
 
 
 
 
1192
  }
1193
+ $this->debug_message( "checking style attr for background-image: $style" );
1194
+ $bg_image_url = $this->get_background_image_url( $style );
1195
+ if ( $this->validate_image_url( $bg_image_url ) ) {
1196
+ /** This filter is already documented in class-exactdn.php */
1197
+ if ( apply_filters( 'exactdn_skip_image', false, $bg_image_url, $element ) ) {
1198
+ continue;
1199
+ }
1200
+ $args = array();
1201
+ $element_class = $this->get_attribute( $element, 'class' );
1202
+ if ( false !== strpos( $element_class, 'alignfull' ) && current_theme_supports( 'align-wide' ) ) {
1203
+ $args['w'] = apply_filters( 'exactdn_full_align_bgimage_width', 1920, $bg_image_url );
1204
+ } elseif ( false !== strpos( $element_class, 'alignwide' ) && current_theme_supports( 'align-wide' ) ) {
1205
+ $args['w'] = apply_filters( 'exactdn_wide_align_bgimage_width', 1500, $bg_image_url );
1206
+ } elseif ( 'div' === $tag_type && $content_width ) {
1207
+ $args['w'] = apply_filters( 'exactdn_content_bgimage_width', $content_width, $bg_image_url );
1208
+ }
1209
+ if ( isset( $args['w'] ) && empty( $args['w'] ) ) {
1210
+ unset( $args['w'] );
1211
+ }
1212
+ $exactdn_bg_image_url = $this->generate_url( $bg_image_url, $args );
1213
+ if ( $bg_image_url !== $exactdn_bg_image_url ) {
1214
+ $new_style = str_replace( $bg_image_url, $exactdn_bg_image_url, $style );
1215
+ $element = str_replace( $style, $new_style, $element );
1216
+ }
1217
  }
1218
+ if ( $element !== $elements[ $index ] ) {
1219
+ $content = str_replace( $elements[ $index ], $element, $content );
 
 
1220
  }
1221
  }
 
 
 
1222
  }
 
 
 
 
 
 
 
 
 
 
 
 
1223
  return $content;
1224
  }
1225
+
1226
+ /**
1227
+ * Parse page content looking for thumburl from personalization.com.
1228
+ *
1229
+ * @param string $content The HTML content to parse.
1230
+ * @return string The filtered HTML content.
1231
+ */
1232
+ function filter_prz_thumb( $content ) {
1233
+ if ( ! class_exists( 'WooCommerce' ) || false === strpos( $content, 'productDetailsForPrz' ) ) {
1234
+ return $content;
1235
+ }
1236
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
1237
+ $prz_match = preg_match( '#productDetailsForPrz=[^<]+?thumbnailUrl:\'([^\']+?)\'[^<]+?</script>#', $content, $prz_detail_matches );
1238
+ if ( $prz_match && ! empty( $prz_detail_matches[1] ) && $this->validate_image_url( $prz_detail_matches[1] ) ) {
1239
+ $prz_thumb = $this->generate_url( $prz_detail_matches[1], apply_filters( 'exactdn_personalizationdotcom_thumb_args', '', $prz_detail_matches[1] ) );
1240
+ if ( $prz_thumb !== $prz_detail_matches ) {
1241
+ $content = str_replace( "thumbnailUrl:'{$prz_detail_matches[1]}'", "thumbnailUrl:'$prz_thumb'", $content );
1242
+ }
1243
  }
1244
+ return $content;
1245
  }
 
 
1246
 
1247
+ /**
1248
+ * Allow resizing of images for some admin-ajax requests.
1249
+ *
1250
+ * @param bool $allow Will normally be false, unless already modified by another function.
1251
+ * @param array $image Bunch of information about the image, but we don't care about that here.
1252
+ * @return bool True if it's an allowable admin-ajax request, false for all other admin requests.
1253
+ */
1254
+ function allow_admin_image_downsize( $allow, $image ) {
1255
+ if ( ! wp_doing_ajax() ) {
1256
+ return $allow;
1257
+ }
1258
+ if ( ! empty( $_POST['action'] ) && 'eddvbugm_viewport_downloads' === $_POST['action'] ) {
1259
+ return true;
1260
+ }
1261
+ if ( ! empty( $_POST['action'] ) && 'vc_get_vc_grid_data' === $_POST['action'] ) {
1262
+ return true;
1263
+ }
1264
+ if ( ! empty( $_POST['action'] ) && 'Essential_Grid_Front_request_ajax' === $_POST['action'] ) {
1265
+ return true;
1266
+ }
1267
+ if ( ! empty( $_POST['action'] ) && 'mabel-rpn-getnew-purchased-products' === $_POST['action'] ) {
1268
+ return true;
1269
+ }
1270
  return $allow;
1271
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1272
 
1273
+ /**
1274
+ * Disable resizing of images during image_downsize().
1275
+ *
1276
+ * @param mixed $param Could be anything (or nothing), we just pass it along untouched.
1277
+ * @return mixed Just the same value, going back out the door.
1278
+ */
1279
+ function disable_image_downsize( $param = false ) {
1280
+ remove_filter( 'image_downsize', array( $this, 'filter_image_downsize' ) );
1281
+ add_action( 'themify_after_post_image', array( $this, 'enable_image_downsize' ) );
1282
+ return $param;
1283
+ }
1284
 
1285
+ /**
1286
+ * Re-enable resizing of images during image_downsize().
1287
+ */
1288
+ function enable_image_downsize() {
1289
+ add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
1290
+ }
1291
 
1292
+ /**
1293
+ * Change the default for processing an array of dimensions to scaling instead of cropping.
1294
+ *
1295
+ * @param array $exactdn_args The ExactDN args generated by filter_image_downsize() when $size is an array.
1296
+ * @return array $exactdn_args The ExactDN args, with resize (crop) changed to fit (scale).
1297
+ */
1298
+ function image_downsize_scale( $exactdn_args ) {
1299
+ if ( ! is_array( $exactdn_args ) ) {
1300
+ return $exactdn_args;
1301
+ }
1302
+ if ( ! empty( $exactdn_args['resize'] ) ) {
1303
+ $exactdn_args['fit'] = $exactdn_args['resize'];
1304
+ unset( $exactdn_args['resize'] );
1305
+ }
1306
  return $exactdn_args;
1307
  }
 
 
 
 
 
 
1308
 
1309
+ /**
1310
+ * Filter post thumbnail image retrieval, passing images through ExactDN.
1311
+ *
1312
+ * @param array|bool $image Defaults to false, but may be a url if another plugin/theme has already filtered the value.
1313
+ * @param int $attachment_id The ID number for the image attachment.
1314
+ * @param string|array $size The name of the image size or an array of width and height. Default 'medium'.
1315
+ * @uses is_admin, apply_filters, wp_get_attachment_url, this::validate_image_url, this::image_sizes, this::generate_url
1316
+ * @filter image_downsize
1317
+ * @return string|bool
1318
+ */
1319
+ function filter_image_downsize( $image, $attachment_id, $size ) {
1320
+ $started = microtime( true );
1321
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
1322
+
1323
+ // Don't foul up the admin side of things, unless a plugin wants to.
1324
+ if ( is_admin() &&
1325
+ /**
1326
+ * Provide plugins a way of running ExactDN for images in the WordPress Dashboard (wp-admin).
1327
+ *
1328
+ * Note: enabling this will result in ExactDN URLs added to your post content, which could make migrations across domains (and off ExactDN) a bit more challenging.
1329
+ *
1330
+ * @param bool false Allow ExactDN to run on the Dashboard. Default to false.
1331
+ * @param array $args {
1332
+ * Array of image details.
1333
+ *
1334
+ * @type array|bool $image Image URL or false.
1335
+ * @type int $attachment_id Attachment ID of the image.
1336
+ * @type array|string $size Image size. Can be a string (name of the image size, e.g. full) or an array of height and width.
1337
+ * }
1338
+ */
1339
+ false === apply_filters( 'exactdn_admin_allow_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) )
1340
+ ) {
1341
+ return $image;
1342
+ }
1343
 
 
 
1344
  /**
1345
+ * Provide plugins a way of preventing ExactDN from being applied to images retrieved from WordPress Core.
 
 
1346
  *
1347
+ * @param bool false Stop ExactDN from being applied to the image. Default to false.
1348
  * @param array $args {
1349
  * Array of image details.
1350
  *
1351
+ * @type string|bool $image Image URL or false.
1352
  * @type int $attachment_id Attachment ID of the image.
1353
  * @type array|string $size Image size. Can be a string (name of the image size, e.g. full) or an array of height and width.
1354
  * }
1355
  */
1356
+ if ( apply_filters( 'exactdn_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) ) ) {
1357
+ return $image;
1358
+ }
 
1359
 
1360
+ if ( function_exists( 'aq_resize' ) ) {
1361
+ $this->debug_message( 'aq_resize detected, image_downsize filter disabled' );
1362
+ return $image;
1363
+ }
 
 
 
 
 
 
 
 
 
 
 
1364
 
1365
+ if ( $this->filtering_the_content || $this->filtering_the_page ) {
1366
+ $this->debug_message( 'end image_downsize early' );
1367
+ return $image;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1368
  }
 
 
 
1369
 
1370
+ // Get the image URL and proceed with ExactDN replacement if successful.
1371
+ $image_url = wp_get_attachment_url( $attachment_id );
1372
+ /** This filter is already documented in class-exactdn.php */
1373
+ if ( apply_filters( 'exactdn_skip_image', false, $image_url, null ) ) {
1374
  return $image;
1375
  }
1376
+ $this->debug_message( $image_url );
1377
+ $this->debug_message( $attachment_id );
1378
+ if ( is_string( $size ) || is_int( $size ) ) {
1379
+ $this->debug_message( $size );
1380
+ } elseif ( is_array( $size ) ) {
1381
+ foreach ( $size as $dimension ) {
1382
+ $this->debug_message( 'dimension: ' . $dimension );
1383
+ }
1384
+ }
1385
+ // Set this to true later when we know we have size meta.
1386
+ $has_size_meta = false;
1387
 
1388
+ if ( $image_url ) {
1389
+ // Check if image URL should be used with ExactDN.
1390
+ if ( ! $this->validate_image_url( $image_url ) ) {
1391
+ return $image;
1392
+ }
1393
 
1394
+ $intermediate = true; // For the fourth array item returned by the image_downsize filter.
1395
+ $resize_existing = defined( 'EXACTDN_RESIZE_EXISTING' ) && EXACTDN_RESIZE_EXISTING;
 
 
 
1396
 
1397
+ // If an image is requested with a size known to WordPress, use that size's settings with ExactDN.
1398
+ if ( is_string( $size ) && array_key_exists( $size, $this->image_sizes() ) ) {
1399
+ $image_args = $this->image_sizes();
1400
+ $image_args = $image_args[ $size ];
1401
+ $this->debug_message( "image args for $size: " . $this->implode( ',', $image_args ) );
1402
 
1403
+ $exactdn_args = array();
1404
 
1405
+ $image_meta = image_get_intermediate_size( $attachment_id, $size );
 
 
 
 
 
 
 
 
1406
 
1407
+ // 'full' is a special case: We need consistent data regardless of the requested size.
1408
+ if ( 'full' === $size ) {
1409
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
1410
+ $intermediate = false;
1411
+ } elseif ( ! $image_meta ) {
1412
+ $this->debug_message( 'still do not have meta, getting it now' );
1413
+ // If we still don't have any image meta at this point, it's probably from a custom thumbnail size
1414
+ // for an image that was uploaded before the custom image was added to the theme. Try to determine the size manually.
1415
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
1416
+
1417
+ if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
1418
+ $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $image_args['width'], $image_args['height'], $image_args['crop'] );
1419
+ if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
1420
+ $image_meta['width'] = $image_resized[6];
1421
+ $image_meta['height'] = $image_resized[7];
1422
+ }
1423
  }
1424
  }
1425
+ if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
1426
+ $image_args['width'] = $image_meta['width'];
1427
+ $image_args['height'] = $image_meta['height'];
 
 
 
 
 
 
 
 
1428
 
1429
+ // NOTE: it will constrain an image to $content_width which is expected behavior in core, so far as I can see.
1430
+ list( $image_args['width'], $image_args['height'] ) = image_constrain_size_for_editor( $image_args['width'], $image_args['height'], $size, 'display' );
1431
 
1432
+ $has_size_meta = true;
1433
+ $this->debug_message( 'image args constrained: ' . $this->implode( ',', $image_args ) );
 
 
 
 
1434
  }
 
 
 
 
 
 
 
 
 
 
 
1435
 
1436
+ $transform = $image_args['crop'] ? 'resize' : 'fit';
1437
+
1438
+ // Check specified image dimensions and account for possible zero values; ExactDN fails to resize if a dimension is zero.
1439
+ if ( ! $image_args['width'] || ! $image_args['height'] ) {
1440
+ if ( ! $image_args['width'] && 0 < $image_args['height'] ) {
1441
+ $exactdn_args['h'] = $image_args['height'];
1442
+ } elseif ( ! $image_args['height'] && 0 < $image_args['width'] ) {
1443
+ $exactdn_args['w'] = $image_args['width'];
1444
+ }
1445
  } else {
1446
+ if ( ! isset( $image_meta['sizes'] ) ) {
1447
+ $size_meta = $image_meta;
1448
+ // Because we don't have the "real" meta, just the height/width for the specific size.
1449
+ $this->debug_message( 'getting attachment meta now' );
1450
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
1451
+ }
1452
+ if ( 'resize' === $transform && $image_meta && isset( $image_meta['width'], $image_meta['height'] ) ) {
1453
+ // Lets make sure that we don't upscale images since wp never upscales them as well.
1454
+ $smaller_width = ( ( $image_meta['width'] < $image_args['width'] ) ? $image_meta['width'] : $image_args['width'] );
1455
+ $smaller_height = ( ( $image_meta['height'] < $image_args['height'] ) ? $image_meta['height'] : $image_args['height'] );
1456
+
1457
+ $exactdn_args[ $transform ] = $smaller_width . ',' . $smaller_height;
1458
+ } else {
1459
+ $exactdn_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
1460
+ }
1461
  }
 
1462
 
1463
+ if (
1464
+ ! empty( $image_meta['sizes'] ) && ! empty( $image_meta['width'] ) && ! empty( $image_meta['height'] ) &&
1465
+ (int) $image_args['width'] === (int) $image_meta['width'] &&
1466
+ (int) $image_args['height'] === (int) $image_meta['height']
1467
+ ) {
1468
+ $this->debug_message( 'image args match size of original, just use that' );
1469
+ $size = 'full';
1470
+ }
1471
+ if ( empty( $image_meta['sizes'] ) && ! empty( $size_meta ) ) {
1472
+ $image_meta['sizes'][ $size ] = $size_meta;
 
 
 
 
 
 
 
1473
  }
1474
+ if ( ! empty( $image_meta['sizes'] ) && 'full' !== $size && ! empty( $image_meta['sizes'][ $size ]['file'] ) ) {
1475
+ $image_url_basename = wp_basename( $image_url );
1476
+ $intermediate_url = str_replace( $image_url_basename, $image_meta['sizes'][ $size ]['file'], $image_url );
1477
+
1478
+ if ( empty( $image_meta['width'] ) || empty( $image_meta['height'] ) ) {
1479
+ list( $filename_width, $filename_height ) = $this->get_dimensions_from_filename( $intermediate_url );
1480
+ }
1481
+ $filename_width = $image_meta['width'] ? $image_meta['width'] : $filename_width;
1482
+ $filename_height = $image_meta['height'] ? $image_meta['height'] : $filename_height;
1483
+ if ( $filename_width && $filename_height && $image_args['width'] === $filename_width && $image_args['height'] === $filename_height ) {
1484
+ $image_url = $intermediate_url;
1485
+ } else {
1486
+ $resize_existing = true;
1487
+ }
1488
  } else {
1489
  $resize_existing = true;
1490
  }
 
 
 
 
 
1491
 
1492
+ $exactdn_args = $resize_existing && 'full' !== $size ? $this->maybe_smart_crop( $exactdn_args, $attachment_id, $image_meta ) : array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1493
 
1494
+ /**
1495
+ * Filter the ExactDN arguments added to an image, when that image size is a string.
1496
+ * Image size will be a string (e.g. "full", "medium") when it is known to WordPress.
1497
+ *
1498
+ * @param array $exactdn_args ExactDN arguments.
1499
+ * @param array $args {
1500
+ * Array of image details.
1501
+ *
1502
+ * @type array $image_args Image arguments (width, height, crop).
1503
+ * @type string $image_url Image URL.
1504
+ * @type int $attachment_id Attachment ID of the image.
1505
+ * @type string $size Image size name.
1506
+ * @type string $transform Value can be resize or fit.
1507
+ * }
1508
+ */
1509
+ $exactdn_args = apply_filters( 'exactdn_image_downsize_string', $exactdn_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
1510
+
1511
+ // Generate ExactDN URL.
1512
+ $image = array(
1513
+ $this->generate_url( $image_url, $exactdn_args ),
1514
+ $has_size_meta ? $image_args['width'] : false,
1515
+ $has_size_meta ? $image_args['height'] : false,
1516
+ $intermediate,
1517
+ );
1518
+ } elseif ( is_array( $size ) ) {
1519
+ // Pull width and height values from the provided array, if possible.
1520
+ $width = isset( $size[0] ) ? (int) $size[0] : false;
1521
+ $height = isset( $size[1] ) ? (int) $size[1] : false;
1522
+
1523
+ // Don't bother if necessary parameters aren't passed.
1524
+ if ( ! $width || ! $height ) {
1525
+ return $image;
1526
+ }
1527
+ $this->debug_message( "requested w$width by h$height" );
1528
 
1529
+ $image_meta = wp_get_attachment_metadata( $attachment_id );
1530
+ if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
1531
+ $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $width, $height, true );
1532
 
1533
+ if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
1534
+ $width = $image_resized[6];
1535
+ $height = $image_resized[7];
1536
+ $this->debug_message( "using resize dims w$width by h$height" );
1537
+ } else {
1538
+ $width = $image_meta['width'];
1539
+ $height = $image_meta['height'];
1540
+ $this->debug_message( "using meta dims w$width by h$height" );
1541
+ }
1542
+ $has_size_meta = true;
1543
  }
 
 
1544
 
1545
+ list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
1546
+ $this->debug_message( "constrained to w$width by h$height" );
1547
 
1548
+ // Expose arguments to a filter before passing to ExactDN.
1549
+ $exactdn_args = array(
1550
+ 'resize' => $width . ',' . $height,
1551
+ );
1552
 
1553
+ $exactdn_args = $this->maybe_smart_crop( $exactdn_args, $attachment_id, $image_meta );
1554
+
1555
+ /**
1556
+ * Filter the ExactDN arguments added to an image, when the image size is an array of height and width values.
1557
+ *
1558
+ * @param array $exactdn_args ExactDN arguments/parameters.
1559
+ * @param array $args {
1560
+ * Array of image details.
1561
+ *
1562
+ * @type int $width Image width.
1563
+ * @type int $height Image height.
1564
+ * @type string $image_url Image URL.
1565
+ * @type int $attachment_id Attachment ID of the image.
1566
+ * }
1567
+ */
1568
+ $exactdn_args = apply_filters( 'exactdn_image_downsize_array', $exactdn_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
1569
+
1570
+ // Generate ExactDN URL.
1571
+ $image = array(
1572
+ $this->generate_url( $image_url, $exactdn_args ),
1573
+ $has_size_meta ? $width : false,
1574
+ $has_size_meta ? $height : false,
1575
+ $intermediate,
1576
+ );
1577
+ }
1578
+ }
1579
+ if ( ! empty( $image[0] ) && is_string( $image[0] ) ) {
1580
+ $this->debug_message( $image[0] );
1581
+ }
1582
+ $this->debug_message( 'end image_downsize' );
1583
+ $elapsed_time = microtime( true ) - $started;
1584
+ $this->debug_message( "parsing image_downsize took $elapsed_time seconds" );
1585
+ $this->elapsed_time += microtime( true ) - $started;
1586
+ return $image;
1587
+ }
1588
 
1589
+ /**
1590
+ * Filters an array of image `srcset` values, replacing each URL with its ExactDN equivalent.
1591
+ *
1592
+ * @param array $sources An array of image urls and widths.
1593
+ * @param array $size_array Array of width and height values in pixels.
1594
+ * @param string $image_src The 'src' of the image.
1595
+ * @param array $image_meta The image metadata as returned by 'wp_get_attachment_metadata()'.
1596
+ * @param int $attachment_id Image attachment ID or 0.
1597
+ * @uses this::validate_image_url, this::generate_url, this::parse_from_filename
1598
+ * @uses this::strip_image_dimensions_maybe, this::get_content_width
1599
+ * @return array An array of ExactDN image urls and widths.
1600
+ */
1601
+ public function filter_srcset_array( $sources = array(), $size_array = array(), $image_src = '', $image_meta = array(), $attachment_id = 0 ) {
1602
+ $started = microtime( true );
1603
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
1604
+ // Don't foul up the admin side of things, unless a plugin wants to.
1605
+ if ( is_admin() &&
1606
  /**
1607
+ * Provide plugins a way of running ExactDN for images in the WordPress Dashboard (wp-admin).
1608
  *
1609
+ * @param bool false Stop ExactDN from being run on the Dashboard. Default to false, use true to run in wp-admin.
1610
  * @param array $args {
1611
  * Array of image details.
1612
  *
1613
+ * @type string|bool $image Image URL or false.
1614
+ * @type int $attachment_id Attachment ID of the image.
 
 
1615
  * }
1616
  */
1617
+ false === apply_filters( 'exactdn_admin_allow_image_srcset', false, compact( 'image_src', 'attachment_id' ) )
1618
+ ) {
1619
+ return $sources;
 
 
 
 
 
 
1620
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1621
 
1622
+ if ( ! is_array( $sources ) ) {
1623
+ return $sources;
 
 
 
 
 
 
 
 
1624
  }
1625
 
1626
+ $upload_dir = wp_get_upload_dir();
1627
+ $resize_existing = defined( 'EXACTDN_RESIZE_EXISTING' ) && EXACTDN_RESIZE_EXISTING;
1628
+ $w_descriptor = true;
 
1629
 
1630
+ foreach ( $sources as $i => $source ) {
1631
+ if ( 'x' === $source['descriptor'] ) {
1632
+ $w_descriptor = false;
1633
+ }
1634
+ if ( ! $this->validate_image_url( $source['url'] ) ) {
1635
+ continue;
1636
+ }
1637
 
1638
+ /** This filter is already documented in class-exactdn.php */
1639
+ if ( apply_filters( 'exactdn_skip_image', false, $source['url'], $source ) ) {
1640
+ continue;
1641
+ }
1642
+
1643
+ $url = $source['url'];
1644
 
1645
+ list( $width, $height ) = $this->get_dimensions_from_filename( $url );
1646
+ if ( ! $resize_existing && 'w' === $source['descriptor'] && (int) $source['value'] === (int) $width ) {
1647
+ $this->debug_message( "preventing further processing for $url" );
 
 
1648
  $sources[ $i ]['url'] = $this->generate_url( $source['url'] );
1649
  continue;
1650
  }
 
1651
 
1652
+ if ( $image_meta && ! empty( $image_meta['width'] ) ) {
1653
+ if ( ( $height && (int) $image_meta['height'] === (int) $height && $width && (int) $image_meta['width'] === (int) $width ) ||
1654
+ ( ! $height && ! $width && (int) $image_meta['width'] === (int) $source['value'] )
1655
+ ) {
1656
+ $this->debug_message( "preventing further processing for (detected) full-size $url" );
1657
+ $sources[ $i ]['url'] = $this->generate_url( $source['url'] );
1658
+ continue;
1659
+ }
1660
+ }
1661
 
1662
+ $this->debug_message( 'continuing: ' . $width . ' vs. ' . $source['value'] );
 
 
 
 
 
 
1663
 
1664
+ // It's quicker to get the full size with the data we have already, if available.
1665
+ if ( ! empty( $attachment_id ) ) {
1666
+ $url = wp_get_attachment_url( $attachment_id );
 
1667
  } else {
1668
+ $url = $this->strip_image_dimensions_maybe( $url );
1669
  }
1670
+ $this->debug_message( "building srcs from $url" );
1671
 
1672
+ $args = array();
1673
+ if ( 'w' === $source['descriptor'] ) {
1674
+ if ( $height && ( (int) $source['value'] === (int) $width ) ) {
1675
+ $args['resize'] = $width . ',' . $height;
1676
+ } else {
1677
+ $args['w'] = $source['value'];
1678
+ }
1679
+ }
1680
 
1681
+ $args = $this->maybe_smart_crop( $args, $attachment_id, $image_meta );
 
1682
 
1683
+ $sources[ $i ]['url'] = $this->generate_url( $url, $args );
1684
+ }
 
 
1685
 
1686
+ /**
1687
+ * At this point, $sources is the original srcset with ExactDN URLs.
1688
+ * Now, we're going to construct additional sizes based on multiples of the content_width.
1689
+ */
 
 
 
 
 
 
 
 
1690
 
1691
+ /**
1692
+ * Filter the multiplier ExactDN uses to create new srcset items.
1693
+ * Return false to short-circuit and bypass auto-generation.
1694
+ *
1695
+ * @param array|bool $multipliers Array of multipliers to use or false to bypass.
1696
+ */
1697
+ $multipliers = apply_filters( 'exactdn_srcset_multipliers', array( .2, .4, .6, .8, 1, 2, 3, 1920 ) );
1698
+ $url = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
1699
+ if ( ! $w_descriptor ) {
1700
+ $this->debug_message( 'using x descriptors instead of w' );
1701
+ $multipliers = array_filter( $multipliers, 'is_int' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1702
  }
 
1703
 
1704
+ if (
1705
+ /** Short-circuit via exactdn_srcset_multipliers filter. */
1706
+ is_array( $multipliers )
1707
+ /** This filter is already documented in class-exactdn.php */
1708
+ && ! apply_filters( 'exactdn_skip_image', false, $url, null )
1709
+ /** The original url is valid/allowed. */
1710
+ && $this->validate_image_url( $url )
1711
+ /** Verify basic meta is intact. */
1712
+ && isset( $image_meta['width'] ) && isset( $image_meta['height'] ) && isset( $image_meta['file'] )
1713
+ /** Verify we have the requested width/height. */
1714
+ && isset( $size_array[0] ) && isset( $size_array[1] )
1715
+ ) {
1716
+
1717
+ $fullwidth = $image_meta['width'];
1718
+ $fullheight = $image_meta['height'];
1719
+ $reqwidth = $size_array[0];
1720
+ $reqheight = $size_array[1];
1721
+ $this->debug_message( "filling additional sizes with requested w $reqwidth h $reqheight full w $fullwidth full h $fullheight" );
1722
+
1723
+ $constrained_size = wp_constrain_dimensions( $fullwidth, $fullheight, $reqwidth );
1724
+ $expected_size = array( $reqwidth, $reqheight );
1725
+
1726
+ $this->debug_message( $constrained_size[0] );
1727
+ $this->debug_message( $constrained_size[1] );
1728
+ if ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ) {
1729
+ $this->debug_message( 'soft cropping' );
1730
+ $crop = 'soft';
1731
+ $base = $this->get_content_width(); // Provide a default width if none set by the theme.
1732
+ } else {
1733
+ $this->debug_message( 'hard cropping' );
1734
+ $crop = 'hard';
1735
+ $base = $reqwidth;
1736
+ }
1737
+ $this->debug_message( "base width: $base" );
1738
 
1739
+ $currentwidths = array_keys( $sources );
1740
+ $newsources = null;
1741
 
1742
+ foreach ( $multipliers as $multiplier ) {
1743
+
1744
+ $newwidth = intval( $base * $multiplier );
1745
+ if ( 1920 === (int) $multiplier ) {
1746
+ $newwidth = 1920;
1747
+ if ( ! $w_descriptor ) {
1748
+ continue;
1749
+ }
1750
  }
1751
+ if ( $newwidth < 50 ) {
1752
+ continue;
 
 
 
 
 
 
1753
  }
1754
+ foreach ( $currentwidths as $currentwidth ) {
1755
+ // If a new width would be within 50 pixels of an existing one or larger than the full size image, skip.
1756
+ if ( abs( $currentwidth - $newwidth ) < 50 || ( $newwidth > $fullwidth ) ) {
1757
+ continue 2; // Back to the foreach ( $multipliers as $multiplier ).
1758
+ }
1759
+ } // foreach ( $currentwidths as $currentwidth ){
 
 
 
 
 
 
 
 
 
 
1760
 
1761
+ if ( 1 === $multiplier && abs( $newwidth - $fullwidth ) < 5 ) {
1762
+ $args = array();
1763
+ } elseif ( 'soft' === $crop ) {
1764
+ $args = array(
1765
+ 'w' => $newwidth,
1766
+ );
1767
+ } else { // hard crop, e.g. add_image_size( 'example', 200, 200, true ).
1768
+ $args = array(
1769
+ 'zoom' => $multiplier,
1770
+ 'resize' => $reqwidth . ',' . $reqheight,
1771
+ );
1772
+ }
1773
 
1774
+ $args = $this->maybe_smart_crop( $args, $attachment_id, $image_meta );
 
1775
 
1776
+ $newsources[ $newwidth ] = array(
1777
+ 'url' => $this->generate_url( $url, $args ),
1778
+ 'descriptor' => ( $w_descriptor ? 'w' : 'x' ),
1779
+ 'value' => ( $w_descriptor ? $newwidth : $multiplier ),
1780
+ );
 
 
 
 
 
1781
 
1782
+ $currentwidths[] = $newwidth;
1783
+ } // foreach ( $multipliers as $multiplier )
 
 
 
 
 
 
 
 
 
 
 
 
 
1784
 
1785
+ if ( is_array( $newsources ) ) {
1786
+ $sources = array_replace( $sources, $newsources );
1787
+ }
1788
+ } // if ( isset( $image_meta['width'] ) && isset( $image_meta['file'] ) )
1789
+ $elapsed_time = microtime( true ) - $started;
1790
+ $this->debug_message( "parsing srcset took $elapsed_time seconds" );
1791
+ /* $this->debug_message( print_r( $sources, true ) ); */
1792
+ $this->elapsed_time += microtime( true ) - $started;
1793
+ return $sources;
1794
  }
1795
 
1796
+ /**
1797
+ * Filters an array of image `sizes` values, using $content_width instead of image's full size.
1798
+ *
1799
+ * @param array $sizes An array of media query breakpoints.
1800
+ * @param array $size Width and height of the image.
1801
+ * @uses this::get_content_width
1802
+ * @return array An array of media query breakpoints.
1803
+ */
1804
+ public function filter_sizes( $sizes, $size ) {
1805
+ $started = microtime( true );
1806
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
1807
+ if ( ! doing_filter( 'the_content' ) ) {
1808
+ return $sizes;
1809
+ }
1810
+ $content_width = $this->get_content_width();
1811
 
1812
+ if ( ( is_array( $size ) && $size[0] < $content_width ) ) {
1813
+ return $sizes;
1814
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
1815
 
1816
+ $elapsed_time = microtime( true ) - $started;
1817
+ $this->debug_message( "parsing sizes took $elapsed_time seconds" );
1818
+ $this->elapsed_time += microtime( true ) - $started;
1819
+ return sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $content_width );
1820
  }
1821
 
1822
  /**
1823
+ * Creates an image `srcset` attribute based on the detected width.
 
 
 
 
 
 
 
 
1824
  *
1825
+ * @param string $url The url of the image.
1826
+ * @param int $width Image width to use for calculations.
1827
+ * @param bool $zoom Whether to use zoom or w param.
1828
+ * @param int $filename_width The width derived from the filename, or false.
1829
+ * @uses this::generate_url
1830
+ * @return string A srcset attribute with ExactDN image urls and widths.
1831
  */
1832
+ public function generate_image_srcset( $url, $width, $zoom = false, $filename_width = false ) {
1833
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
1834
+ // Don't foul up the admin side of things.
1835
+ if ( is_admin() ) {
1836
+ return '';
1837
+ }
1838
 
1839
+ if ( ! is_numeric( $width ) ) {
1840
+ return '';
1841
+ }
1842
+
1843
+ /**
1844
+ * Filter the multiplier ExactDN uses to create new srcset items.
1845
+ * Return false to short-circuit and bypass auto-generation.
1846
+ *
1847
+ * @param array|bool $multipliers Array of multipliers to use or false to bypass.
1848
+ */
1849
+ $multipliers = apply_filters( 'exactdn_srcset_multipliers', array( .2, .4, .6, .8, 1, 2, 3, 1920 ) );
1850
+ /**
1851
+ * Filter the width ExactDN will use to create srcset attribute.
1852
+ * Return a falsy value to short-circuit and bypass srcset fill.
1853
+ *
1854
+ * @param int|bool $width The max width for this $url, or false to bypass.
1855
+ */
1856
+ $width = (int) apply_filters( 'exactdn_srcset_fill_width', $width, $url );
1857
+ if ( ! $width ) {
1858
+ return '';
1859
+ }
1860
+ $srcset = '';
1861
+ $currentwidths = array();
1862
+
1863
+ if (
1864
+ /** Short-circuit via exactdn_srcset_multipliers filter. */
1865
+ is_array( $multipliers )
1866
+ && $width
1867
+ /** This filter is already documented in class-exactdn.php */
1868
+ && ! apply_filters( 'exactdn_skip_image', false, $url, null )
1869
+ ) {
1870
+ $sources = null;
1871
+
1872
+ foreach ( $multipliers as $multiplier ) {
1873
+ $newwidth = intval( $width * $multiplier );
1874
+ if ( 1920 === (int) $multiplier ) {
1875
+ $newwidth = 1920;
1876
+ }
1877
+ if ( $newwidth < 50 ) {
1878
+ continue;
1879
+ }
1880
+ foreach ( $currentwidths as $currentwidth ) {
1881
+ // If a new width would be within 50 pixels of an existing one or larger than the full size image, skip.
1882
+ if ( 1 !== $multiplier && abs( $currentwidth - $newwidth ) < 50 ) {
1883
+ continue 2; // Back to the foreach ( $multipliers as $multiplier ).
1884
+ }
1885
+ } // foreach ( $currentwidths as $currentwidth ){
1886
+ if ( $filename_width && $newwidth > $filename_width ) {
1887
+ continue;
1888
  }
 
 
 
 
1889
 
1890
+ if ( 1 === $multiplier ) {
1891
+ $args = array();
1892
+ } elseif ( $zoom ) {
1893
+ $args = array(
1894
+ 'zoom' => $multiplier,
1895
+ );
1896
+ } else {
1897
+ $args = array(
1898
+ 'w' => $newwidth,
1899
+ );
1900
+ }
1901
 
1902
+ $sources[ $newwidth ] = array(
1903
+ 'url' => $this->generate_url( $url, $args ),
1904
+ 'descriptor' => 'w',
1905
+ 'value' => $newwidth,
1906
+ );
1907
 
1908
+ $currentwidths[] = $newwidth;
1909
+ }
1910
  }
1911
+ if ( ! empty( $sources ) ) {
1912
+ foreach ( $sources as $source ) {
1913
+ $srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', ';
1914
+ }
1915
  }
1916
+ /* $this->debug_message( print_r( $sources, true ) ); */
1917
+ return rtrim( $srcset, ', ' );
1918
  }
 
 
 
1919
 
1920
+ /**
1921
+ * Check for smart-cropping plugin to adjust cropping parameters.
1922
+ * Currently supports Theia Smart Thumbnails using the theiaSmartThumbnails_position meta.
1923
+ *
1924
+ * @param array $args The arguments that have been generated so far.
1925
+ * @param int $attachment_id The ID number for the current image.
1926
+ * @param array $meta Optional. The attachment (image) metadata. Default false.
1927
+ * @return array The arguments, possibly altered for smart cropping.
1928
+ */
1929
+ function maybe_smart_crop( $args, $attachment_id, $meta = false ) {
1930
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
1931
+ if ( ! empty( $args['crop'] ) ) {
1932
+ $this->debug_message( 'already cropped' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1933
  return $args;
1934
  }
1935
+ // Doing something other than a hard crop, or we don't know what the ID is.
1936
+ if ( empty( $args['resize'] ) || empty( $attachment_id ) ) {
1937
+ $this->debug_message( 'not resizing, so no custom crop' );
1938
+ return $args;
1939
+ }
1940
+ // TST is not active.
1941
+ if ( ! defined( 'TST_VERSION' ) ) {
1942
+ $this->debug_message( 'no TST plugin' );
1943
+ return $args;
1944
+ }
1945
+ if ( ! class_exists( 'TstPostOptions' ) || ! defined( 'TstPostOptions::META_POSITION' ) ) {
1946
+ $this->debug_message( 'no TstPostOptions class' );
1947
+ return $args;
1948
+ }
1949
+ if ( ! $meta || ! is_array( $meta ) || empty( $meta['sizes'] ) ) {
1950
+ // $focus_point = get_post_meta( $attachment_id, TstPostOptions::META_POSITION, true );
1951
+ $meta = wp_get_attachment_metadata( $attachment_id );
1952
+ if ( ! is_array( $meta ) || empty( $meta['width'] ) || empty( $meta['height'] ) ) {
1953
+ $this->debug_message( 'unusable meta retrieved' );
1954
+ return $args;
1955
+ }
1956
+ $focus_point = TstPostOptions::get_meta( $attachment_id, $meta['width'], $meta['height'] );
1957
+ } elseif ( ! empty( $meta['tst_thumbnail_version'] ) ) {
1958
+ if ( empty( $meta['width'] ) || empty( $meta['height'] ) ) {
1959
+ $this->debug_message( 'unusable meta passed' );
1960
+ return $args;
1961
+ }
1962
+ $focus_point = TstPostOptions::get_meta( $attachment_id, $meta['width'], $meta['height'] );
1963
+ } else {
1964
+ $this->debug_message( 'unusable meta' );
1965
+ return $args;
1966
+ }
1967
+ if ( empty( $focus_point ) || ! is_array( $focus_point ) ) {
1968
+ $this->debug_message( 'unusable focus point' );
1969
  return $args;
1970
  }
 
 
 
 
 
 
 
 
 
1971
 
1972
+ $dimensions = explode( ',', $args['resize'] );
1973
 
1974
+ $new_w = $dimensions[0];
1975
+ $new_h = $dimensions[1];
1976
+ $this->debug_message( "full size dims: w{$meta['width']} h{$meta['height']}" );
1977
+ $this->debug_message( "smart crop dims: w$new_w h$new_h" );
1978
+ if ( ! empty( $args['zoom'] ) ) {
1979
+ $new_w = round( $args['zoom'] * $new_w );
1980
+ $new_h = round( $args['zoom'] * $new_h );
1981
+ $this->debug_message( "zooming: {$args['zoom']} w$new_w h$new_h" );
1982
+ }
1983
+ if ( ! $new_w || ! $new_h ) {
1984
+ $this->debug_message( 'empty dimension, not cropping' );
1985
+ return $args;
1986
+ }
1987
+ $size_ratio = max( $new_w / $meta['width'], $new_h / $meta['height'] );
1988
+ $crop_w = round( $new_w / $size_ratio );
1989
+ $crop_h = round( $new_h / $size_ratio );
1990
+ $s_x = floor( ( $meta['width'] - $crop_w ) * $focus_point[0] );
1991
+ $s_y = floor( ( $meta['height'] - $crop_h ) * $focus_point[1] );
1992
+ $this->debug_message( "doing the math with size_ratio of $size_ratio" );
1993
+
1994
+ $args['crop'] = $s_x . ',' . $s_y . ',' . $crop_w . ',' . $crop_h;
1995
+ $this->debug_message( $args['crop'] );
1996
+ return $args;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1997
  }
 
 
1998
 
1999
+ /**
2000
+ * Check if this is a REST API request that we should handle (or not).
2001
+ *
2002
+ * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response.
2003
+ * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
2004
+ * @param WP_REST_Request $request Request used to generate the response.
2005
+ * @return WP_HTTP_Response The result, unaltered.
2006
+ */
2007
+ function parse_restapi_maybe( $response, $handler, $request ) {
2008
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2009
+ if ( ! is_a( $request, 'WP_REST_Request' ) ) {
2010
+ $this->debug_message( 'oddball REST request or handler' );
2011
+ return $response; // Something isn't right, bail.
2012
+ }
2013
+ $route = $request->get_route();
2014
+ if ( is_string( $route ) ) {
2015
+ $this->debug_message( "current REST route is $route" );
2016
+ }
2017
+ if ( is_string( $route ) && false !== strpos( $route, 'wp/v2/media/' ) && ! empty( $request['context'] ) && 'edit' === $request['context'] ) {
2018
+ $this->debug_message( 'REST API media endpoint from post editor' );
2019
+ // We don't want ExactDN urls anywhere near the editor, so disable everything we can.
2020
+ add_filter( 'exactdn_override_image_downsize', '__return_true', PHP_INT_MAX );
2021
+ add_filter( 'exactdn_skip_image', '__return_true', PHP_INT_MAX ); // This skips existing srcset indices.
2022
+ add_filter( 'exactdn_srcset_multipliers', '__return_false', PHP_INT_MAX ); // This one skips the additional multipliers.
2023
  }
2024
+ return $response;
2025
  }
 
 
2026
 
2027
+ /**
2028
+ * Make sure the image domain is on the list of approved domains.
2029
+ *
2030
+ * @param string $domain The hostname to validate.
2031
+ * @return bool True if the hostname is allowed, false otherwise.
2032
+ */
2033
+ public function allow_image_domain( $domain ) {
2034
+ $domain = trim( $domain );
2035
+ foreach ( $this->allowed_domains as $allowed ) {
2036
+ $allowed = trim( $allowed );
2037
+ if ( $domain === $allowed ) {
2038
+ return true;
2039
+ }
2040
+ }
 
 
 
 
2041
  return false;
2042
  }
2043
 
2044
+ /**
2045
+ * Ensure image URL is valid for ExactDN.
2046
+ * Though ExactDN functions address some of the URL issues, we should avoid unnecessary processing if we know early on that the image isn't supported.
2047
+ *
2048
+ * @param string $url The image url to be validated.
2049
+ * @param bool $exactdn_is_valid Optional. Whether an ExactDN URL should be considered valid. Default false.
2050
+ * @uses wp_parse_args
2051
+ * @return bool True if the url is considerd valid, false otherwise.
2052
+ */
2053
+ protected function validate_image_url( $url, $exactdn_is_valid = false ) {
2054
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2055
+ if ( false !== strpos( $url, 'data:image/' ) ) {
2056
+ $this->debug_message( "could not parse data uri: $url" );
2057
+ return false;
2058
+ }
2059
+ $parsed_url = $this->parse_url( $url );
2060
+ if ( ! $parsed_url ) {
2061
+ $this->debug_message( "could not parse: $url" );
2062
+ return false;
2063
+ }
 
 
 
 
2064
 
2065
+ // Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
2066
+ $url_info = wp_parse_args(
2067
+ $parsed_url,
2068
+ array(
2069
+ 'scheme' => null,
2070
+ 'host' => null,
2071
+ 'port' => null,
2072
+ 'path' => null,
2073
+ )
2074
+ );
2075
 
2076
+ // Bail if scheme isn't http or port is set that isn't port 80.
2077
+ if (
2078
+ ( 'http' !== $url_info['scheme'] || ( 80 !== (int) $url_info['port'] && ! is_null( $url_info['port'] ) ) ) &&
2079
+ /**
2080
+ * Tells ExactDN to ignore images that are served via HTTPS.
2081
+ *
2082
+ * @param bool $reject_https Should ExactDN ignore images using the HTTPS scheme. Default to false.
2083
+ */
2084
+ apply_filters( 'exactdn_reject_https', false )
2085
+ ) {
2086
+ $this->debug_message( 'rejected https via filter' );
2087
+ return false;
2088
+ }
2089
 
2090
+ // Bail if no host is found.
2091
+ if ( is_null( $url_info['host'] ) ) {
2092
+ $this->debug_message( 'null host' );
2093
+ return false;
2094
+ }
2095
 
2096
+ // Bail if the image already went through ExactDN.
2097
+ if ( ! $exactdn_is_valid && $this->exactdn_domain === $url_info['host'] ) {
2098
+ $this->debug_message( 'exactdn image' );
2099
+ return false;
2100
+ }
2101
 
2102
+ // Bail if the image already went through Photon to avoid conflicts.
2103
+ if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) ) {
2104
+ $this->debug_message( 'photon/wp.com image' );
2105
+ return false;
2106
+ }
2107
 
2108
+ // Bail if no path is found.
2109
+ if ( is_null( $url_info['path'] ) ) {
2110
+ $this->debug_message( 'null path' );
2111
+ return false;
2112
+ }
2113
+
2114
+ // Ensure image extension is acceptable, unless it's a dynamic NextGEN image.
2115
+ if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), $this->extensions, true ) && false === strpos( $url_info['path'], 'nextgen-image/' ) ) {
2116
+ $this->debug_message( 'invalid extension' );
2117
+ return false;
2118
+ }
2119
+
2120
+ // Make sure this is an allowed image domain/hostname for ExactDN on this site.
2121
+ if ( ! $this->allow_image_domain( $url_info['host'] ) ) {
2122
+ $this->debug_message( 'invalid host for ExactDN' );
2123
+ return false;
2124
+ }
2125
+
2126
+ // If we got this far, we should have an acceptable image URL,
2127
+ // but let folks filter to decline if they prefer.
2128
+ /**
2129
+ * Overwrite the results of the previous validation steps an image goes through to be considered valid for ExactDN.
2130
+ *
2131
+ * @param bool true Is the image URL valid and can it be used by ExactDN. Default to true.
2132
+ * @param string $url Image URL.
2133
+ * @param array $parsed_url Array of information about the image url.
2134
+ */
2135
+ return apply_filters( 'exactdn_validate_image_url', true, $url, $parsed_url );
2136
  }
2137
 
 
 
2138
  /**
2139
+ * Checks if the file exists before it passes the file to ExactDN.
2140
  *
2141
+ * @param string $src The image URL.
2142
+ * @return string The possibly altered URL without dimensions.
2143
+ **/
2144
+ protected function strip_image_dimensions_maybe( $src ) {
2145
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2146
+ $stripped_src = $src;
2147
+
2148
+ // Build URL, first removing WP's resized string so we pass the original image to ExactDN.
2149
+ if ( preg_match( '#(-\d+x\d+)\.(' . implode( '|', $this->extensions ) . '){1}(?:\?.+)?$#i', $src, $src_parts ) ) {
2150
+ $stripped_src = str_replace( $src_parts[1], '', $src );
2151
+ $upload_dir = wp_get_upload_dir();
2152
+
2153
+ // Extracts the file path to the image minus the base url.
2154
+ $file_path = substr( $stripped_src, strlen( $upload_dir['baseurl'] ) );
2155
+
2156
+ if ( is_file( $upload_dir['basedir'] . $file_path ) ) {
2157
+ $src = $stripped_src;
2158
+ }
2159
+ $this->debug_message( 'stripped dims' );
2160
+ }
2161
+ return $src;
 
 
 
 
 
 
 
 
2162
  }
 
 
2163
 
2164
+ /**
2165
+ * Provide an array of available image sizes and corresponding dimensions.
2166
+ * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
2167
+ *
2168
+ * @global $wp_additional_image_sizes
2169
+ * @uses get_option
2170
+ * @return array
2171
+ */
2172
+ protected function image_sizes() {
2173
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2174
+ if ( is_null( self::$image_sizes ) ) {
2175
+ global $_wp_additional_image_sizes;
2176
+
2177
+ // Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes.
2178
+ $images = array(
2179
+ 'thumb' => array(
2180
+ 'width' => intval( get_option( 'thumbnail_size_w' ) ),
2181
+ 'height' => intval( get_option( 'thumbnail_size_h' ) ),
2182
+ 'crop' => (bool) get_option( 'thumbnail_crop' ),
2183
+ ),
2184
+ 'medium' => array(
2185
+ 'width' => intval( get_option( 'medium_size_w' ) ),
2186
+ 'height' => intval( get_option( 'medium_size_h' ) ),
2187
+ 'crop' => false,
2188
+ ),
2189
+ 'large' => array(
2190
+ 'width' => intval( get_option( 'large_size_w' ) ),
2191
+ 'height' => intval( get_option( 'large_size_h' ) ),
2192
+ 'crop' => false,
2193
+ ),
2194
+ 'full' => array(
2195
+ 'width' => null,
2196
+ 'height' => null,
2197
+ 'crop' => false,
2198
+ ),
2199
+ );
2200
 
2201
+ // Compatibility mapping as found in wp-includes/media.php.
2202
+ $images['thumbnail'] = $images['thumb'];
2203
 
2204
+ // Update class variable, merging in $_wp_additional_image_sizes if any are set.
2205
+ if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) ) {
2206
+ self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
2207
+ } else {
2208
+ self::$image_sizes = $images;
2209
+ }
2210
  }
 
2211
 
2212
+ return is_array( self::$image_sizes ) ? self::$image_sizes : array();
2213
+ }
2214
 
2215
+ /**
2216
+ * Handle image urls within the NextGEN pro lightbox displays.
2217
+ *
2218
+ * @param array $images An array of NextGEN images and associate attributes.
2219
+ * @return array The ExactDNified array of images.
2220
+ */
2221
+ function ngg_pro_lightbox_images_queue( $images ) {
2222
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2223
+ if ( $this->is_iterable( $images ) ) {
2224
+ foreach ( $images as $index => $image ) {
2225
+ if ( ! empty( $image['image'] ) && $this->validate_image_url( $image['image'] ) ) {
2226
+ $images[ $index ]['image'] = $this->generate_url( $image['image'] );
2227
+ }
2228
+ if ( ! empty( $image['thumb'] ) && $this->validate_image_url( $image['thumb'] ) ) {
2229
+ $images[ $index ]['thumb'] = $this->generate_url( $image['thumb'] );
2230
+ }
2231
+ if ( ! empty( $image['full_image'] ) && $this->validate_image_url( $image['full_image'] ) ) {
2232
+ $images[ $index ]['full_image'] = $this->generate_url( $image['full_image'] );
2233
+ }
2234
+ if ( $this->is_iterable( $image['srcsets'] ) ) {
2235
+ foreach ( $image['srcsets'] as $size => $srcset ) {
2236
+ if ( $this->validate_image_url( $srcset ) ) {
2237
+ $images[ $index ]['srcsets'][ $size ] = $this->generate_url( $srcset );
2238
+ }
2239
  }
2240
  }
2241
+ if ( $this->is_iterable( $image['full_srcsets'] ) ) {
2242
+ foreach ( $image['full_srcsets'] as $size => $srcset ) {
2243
+ if ( $this->validate_image_url( $srcset ) ) {
2244
+ $images[ $index ]['full_srcsets'][ $size ] = $this->generate_url( $srcset );
2245
+ }
2246
  }
2247
  }
2248
  }
2249
  }
2250
+ return $images;
2251
  }
 
 
2252
 
2253
+ /**
2254
+ * Handle image urls within NextGEN.
2255
+ *
2256
+ * @param string $image A url for a NextGEN image.
2257
+ * @return string The ExactDNified image url.
2258
+ */
2259
+ function ngg_get_image_url( $image ) {
2260
+ // Don't foul up the admin side of things, unless a plugin wants to.
2261
+ if ( is_admin() &&
2262
+ /**
2263
+ * Provide plugins a way of running ExactDN for images in the WordPress Dashboard (wp-admin).
2264
+ *
2265
+ * @param bool false Stop ExactDN from being run on the Dashboard. Default to false, use true to run in wp-admin.
2266
+ * @param array $args {
2267
+ * Array of image details.
2268
+ *
2269
+ * @type string|bool $image Image URL or false.
2270
+ * @type int $attachment_id Attachment ID of the image.
2271
+ * }
2272
+ */
2273
+ false === apply_filters( 'exactdn_admin_allow_ngg_url', false, $image )
2274
+ ) {
2275
+ return $image;
2276
+ }
2277
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2278
+ if ( $this->validate_image_url( $image ) ) {
2279
+ return $this->generate_url( $image );
2280
+ }
2281
  return $image;
2282
  }
 
 
 
 
 
 
2283
 
2284
+ /**
2285
+ * Handle images in legacy WooCommerce API endpoints.
2286
+ *
2287
+ * @param array $product_data The product information that will be returned via the API.
2288
+ * @return array The product information with ExactDNified image urls.
2289
+ */
2290
+ function woocommerce_api_product_response( $product_data ) {
2291
+ if ( is_array( $product_data ) && ! empty( $product_data['featured_src'] ) ) {
2292
+ if ( $this->validate_image_url( $product_data['featured_src'] ) ) {
2293
+ $product_data['featured_src'] = $this->generate_url( $product_data['featured_src'] );
2294
+ }
2295
  }
2296
+ return $product_data;
2297
  }
 
 
2298
 
2299
+ /**
2300
+ * Suppress query args for certain files, typically for placholder images.
2301
+ *
2302
+ * @param array|string $args Array of ExactDN arguments.
2303
+ * @param string $image_url Image URL.
2304
+ * @param string|null $scheme Image scheme. Default to null.
2305
+ * @return array Empty if it matches our search, otherwise just $args untouched.
2306
+ */
2307
+ function exactdn_remove_args( $args, $image_url, $scheme ) {
2308
+ if ( strpos( $image_url, 'ewww/lazy/placeholder' ) ) {
2309
+ return array();
2310
+ }
2311
+ if ( strpos( $image_url, 'easyio/lazy/placeholder' ) ) {
2312
+ return array();
2313
+ }
2314
+ if ( strpos( $image_url, 'revslider/admin/assets/images/dummy.png' ) ) {
2315
+ return array();
2316
+ }
2317
+ if ( strpos( $image_url, '/lazy.png' ) ) {
2318
+ return array();
2319
+ }
2320
+ if ( strpos( $image_url, 'lazy_placeholder.gif' ) ) {
2321
+ return array();
2322
+ }
2323
+ if ( strpos( $image_url, '/assets/images/' ) ) {
2324
+ return array();
2325
+ }
2326
+ if ( strpos( $image_url, 'LayerSlider/static/img' ) ) {
2327
+ return array();
2328
+ }
2329
+ if ( strpos( $image_url, 'lazy-load/images/' ) ) {
2330
+ return array();
2331
+ }
2332
+ if ( strpos( $image_url, 'public/images/spacer.' ) ) {
2333
+ return array();
2334
+ }
2335
+ return $args;
2336
  }
 
 
2337
 
2338
+ /**
2339
+ * Exclude images and other resources from being processed based on user specified list.
2340
+ *
2341
+ * @since 4.6.0
2342
+ *
2343
+ * @param boolean $skip Whether ExactDN should skip processing.
2344
+ * @param string $url Resource URL.
2345
+ * @return boolean True to skip the resource, unchanged otherwise.
2346
+ */
2347
+ function exactdn_skip_user_exclusions( $skip, $url ) {
2348
+ if ( $this->user_exclusions ) {
2349
+ foreach ( $this->user_exclusions as $exclusion ) {
2350
+ if ( false !== strpos( $url, $exclusion ) ) {
2351
+ $this->debug_message( "user excluded $url via $exclusion" );
2352
+ return true;
2353
+ }
2354
  }
2355
  }
2356
+ return $skip;
2357
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2358
 
 
 
 
 
 
 
 
 
 
2359
  /**
2360
+ * Converts a local script/css url to use ExactDN.
2361
  *
2362
+ * @param string $url URL to the resource being parsed.
2363
+ * @return string The ExactDN version of the resource, if it was local.
 
 
2364
  */
2365
+ function parse_enqueue( $url ) {
2366
+ if ( is_admin() ) {
2367
+ return $url;
2368
+ }
2369
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2370
+ $parsed_url = $this->parse_url( $url );
 
 
 
2371
 
2372
+ if ( false !== strpos( $url, 'wp-admin/' ) ) {
2373
+ return $url;
2374
+ }
2375
+ if ( false !== strpos( $url, 'xmlrpc.php' ) ) {
2376
+ return $url;
2377
+ }
2378
+ if ( strpos( $url, 'wp-content/plugins/anti-captcha/' ) ) {
2379
+ return $url;
2380
+ }
2381
+ /**
2382
+ * Allow specific URLs to avoid going through ExactDN.
2383
+ *
2384
+ * @param bool false Should the URL be returned as is, without going through ExactDN. Default to false.
2385
+ * @param string $url Resource URL.
2386
+ * @param array|string $args Array of ExactDN arguments.
2387
+ * @param string|null $scheme URL scheme. Default to null.
2388
+ */
2389
+ if ( true === apply_filters( 'exactdn_skip_for_url', false, $url, array(), null ) ) {
2390
+ return $url;
2391
+ }
2392
 
2393
+ // Unable to parse.
2394
+ if ( ! $parsed_url || ! is_array( $parsed_url ) || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
2395
+ $this->debug_message( 'src url no good' );
2396
+ return $url;
2397
+ }
2398
 
2399
+ // No PHP files shall pass.
2400
+ if ( preg_match( '/\.php$/', $parsed_url['path'] ) ) {
2401
+ return $url;
2402
+ }
 
2403
 
2404
+ // Make sure this is an allowed image domain/hostname for ExactDN on this site.
2405
+ if ( ! $this->allow_image_domain( $parsed_url['host'] ) ) {
2406
+ $this->debug_message( "invalid host for ExactDN: {$parsed_url['host']}" );
2407
+ return $url;
2408
+ }
 
2409
 
2410
+ // Figure out which CDN (sub)domain to use.
2411
+ if ( empty( $this->exactdn_domain ) ) {
2412
+ $this->debug_message( 'no exactdn domain configured' );
2413
+ return $url;
 
 
2414
  }
 
 
 
 
 
 
 
 
 
2415
 
2416
+ // You can't run an ExactDN URL through again because query strings are stripped.
2417
+ // So if the image is already an ExactDN URL, append the new arguments to the existing URL.
2418
+ if ( $this->exactdn_domain === $parsed_url['host'] ) {
2419
+ $this->debug_message( 'url already has exactdn domain' );
2420
+ return $url;
2421
+ }
2422
 
2423
+ global $wp_version;
2424
+ // If a resource doesn't have a version string, we add one to help with cache-busting.
2425
+ if ( ( empty( $parsed_url['query'] ) || 'ver=' . $wp_version === $parsed_url['query'] ) && false !== strpos( $url, 'wp-content/' ) ) {
2426
+ $modified = $this->function_exists( 'filemtime' ) ? filemtime( get_template_directory() ) : '';
2427
+ if ( empty( $modified ) ) {
2428
+ $modified = $this->version;
2429
+ }
2430
+ /**
2431
+ * Allows a custom version string for resources that are missing one.
2432
+ *
2433
+ * @param string Defaults to the modified time of the theme folder, and falls back to the plugin version.
2434
+ */
2435
+ $parsed_url['query'] = apply_filters( 'exactdn_version_string', "m=$modified" );
2436
+ } elseif ( empty( $parsed_url['query'] ) ) {
2437
+ $parsed_url['query'] = '';
2438
+ }
2439
 
2440
+ $exactdn_url = '//' . $this->exactdn_domain . '/' . ltrim( $parsed_url['path'], '/' ) . '?' . $parsed_url['query'];
2441
+ $this->debug_message( "exactdn css/script url: $exactdn_url" );
2442
+ return $exactdn_url;
2443
  }
2444
 
2445
  /**
2446
+ * Generates an ExactDN URL.
2447
  *
2448
+ * @param string $image_url URL to the publicly accessible image you want to manipulate.
2449
+ * @param array|string $args An array of arguments, i.e. array( 'w' => '300', 'resize' => '123,456' ), or in string form (w=123&h=456).
2450
+ * @param string $scheme Indicates http or https, other schemes are invalid.
2451
+ * @return string The raw final URL. You should run this through esc_url() before displaying it.
2452
  */
2453
+ function generate_url( $image_url, $args = array(), $scheme = null ) {
2454
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2455
+ $image_url = trim( $image_url );
2456
 
2457
+ if ( is_null( $scheme ) ) {
2458
+ $scheme = $this->scheme;
2459
+ }
 
 
 
 
 
 
 
 
2460
 
2461
+ /**
2462
+ * Disables ExactDN URL processing for local development.
2463
+ *
2464
+ * @param bool false default
2465
+ */
2466
+ if ( true === apply_filters( 'exactdn_development_mode', false ) ) {
2467
+ return $image_url;
2468
+ }
2469
 
2470
+ /**
2471
+ * Allow specific URLs to avoid going through ExactDN.
2472
+ *
2473
+ * @param bool false Should the URL be returned as is, without going through ExactDN. Default to false.
2474
+ * @param string $image_url Resource URL.
2475
+ * @param array|string $args Array of ExactDN arguments.
2476
+ * @param string|null $scheme URL scheme. Default to null.
2477
+ */
2478
+ if ( true === apply_filters( 'exactdn_skip_for_url', false, $image_url, $args, $scheme ) ) {
2479
+ return $image_url;
2480
+ }
 
2481
 
2482
+ // TODO: Not differentiated yet, but it will be, so stay tuned!
2483
+ $jpg_quality = apply_filters( 'jpeg_quality', null, 'image_resize' );
2484
+ $webp_quality = apply_filters( 'jpeg_quality', $jpg_quality, 'image/webp' );
 
 
 
 
 
2485
 
2486
+ $more_args = array();
2487
+ if ( false === strpos( $image_url, 'strip=all' ) && $this->get_option( $this->prefix . 'metadata_remove' ) ) {
2488
+ $more_args['strip'] = 'all';
2489
+ }
2490
+ if ( false === strpos( $image_url, 'lossy=' ) && $this->get_option( 'exactdn_lossy' ) ) {
2491
+ $more_args['lossy'] = is_numeric( $this->get_option( 'exactdn_lossy' ) ) ? (int) $this->get_option( 'exactdn_lossy' ) : 80;
2492
+ }
2493
+ if ( false === strpos( $image_url, 'quality=' ) && ! is_null( $jpg_quality ) && 82 !== (int) $jpg_quality ) {
2494
+ $more_args['quality'] = $jpg_quality;
2495
+ }
2496
+ // Merge given args with the automatic (option-based) args, and also makes sure args is an array if it was previously a string.
2497
+ $args = wp_parse_args( $args, $more_args );
2498
 
2499
+ /**
2500
+ * Filter the original image URL before it goes through ExactDN.
2501
+ *
2502
+ * @param string $image_url Image URL.
2503
+ * @param array|string $args Array of ExactDN arguments.
2504
+ * @param string|null $scheme Image scheme. Default to null.
2505
+ */
2506
+ $image_url = apply_filters( 'exactdn_pre_image_url', $image_url, $args, $scheme );
2507
 
2508
+ if ( empty( $image_url ) ) {
2509
+ return $image_url;
2510
+ }
 
 
2511
 
2512
+ $image_url_parts = $this->parse_url( $image_url );
2513
+
2514
+ // Unable to parse.
2515
+ if ( ! is_array( $image_url_parts ) || empty( $image_url_parts['host'] ) || empty( $image_url_parts['path'] ) ) {
2516
+ $this->debug_message( 'src url no good' );
2517
+ return $image_url;
2518
  }
 
 
2519
 
2520
+ if ( isset( $image_url_parts['scheme'] ) && 'https' === $image_url_parts['scheme'] ) {
2521
+ if ( is_array( $args ) ) {
2522
+ $args['ssl'] = 1;
2523
+ }
2524
+ $scheme = 'https';
2525
+ }
 
 
2526
 
2527
+ /**
2528
+ * Filter the ExactDN image parameters before they are applied to an image.
2529
+ *
2530
+ * @param array|string $args Array of ExactDN arguments.
2531
+ * @param string $image_url Image URL.
2532
+ * @param string|null $scheme Image scheme. Default to null.
2533
+ */
2534
+ $args = apply_filters( 'exactdn_pre_args', $args, $image_url, $scheme );
2535
+
2536
+ if ( is_array( $args ) ) {
2537
+ // Convert values that are arrays into strings.
2538
+ foreach ( $args as $arg => $value ) {
2539
+ if ( is_array( $value ) ) {
2540
+ $args[ $arg ] = implode( ',', $value );
2541
+ }
2542
  }
2543
+
2544
+ // Encode argument values.
2545
+ $args = rawurlencode_deep( $args );
2546
  }
2547
 
2548
+ $this->debug_message( $image_url_parts['host'] );
 
 
2549
 
2550
+ // Figure out which CDN (sub)domain to use.
2551
+ if ( empty( $this->exactdn_domain ) ) {
2552
+ $this->debug_message( 'no exactdn domain configured' );
2553
+ return $image_url;
2554
+ }
2555
 
2556
+ // You can't run an ExactDN URL through again because query strings are stripped.
2557
+ // So if the image is already an ExactDN URL, append the new arguments to the existing URL.
2558
+ if ( $this->exactdn_domain === $image_url_parts['host'] ) {
2559
+ $this->debug_message( 'url already has exactdn domain' );
2560
+ $exactdn_url = add_query_arg( $args, $image_url );
2561
+ return $this->url_scheme( $exactdn_url, $scheme );
2562
+ }
2563
 
2564
+ // ExactDN doesn't support query strings so we ignore them and look only at the path.
2565
+ // However some source images are served via PHP so check the no-query-string extension.
2566
+ // For future proofing, this is a blacklist of common issues rather than a whitelist.
2567
+ $extension = pathinfo( $image_url_parts['path'], PATHINFO_EXTENSION );
2568
+ if ( ( empty( $extension ) && false === strpos( $image_url_parts['path'], 'nextgen-image/' ) ) || in_array( $extension, array( 'php', 'ashx' ), true ) ) {
2569
+ $this->debug_message( 'bad extension' );
2570
+ return $image_url;
2571
+ }
2572
 
2573
+ if ( $this->remove_path && 0 === strpos( $image_url_parts['path'], $this->remove_path ) ) {
2574
+ $image_url_parts['path'] = substr( $image_url_parts['path'], strlen( $this->remove_path ) );
2575
+ }
2576
+ $domain = 'http://' . $this->exactdn_domain . '/';
2577
+ $exactdn_url = $domain . ltrim( $image_url_parts['path'], '/' );
2578
+ $this->debug_message( "bare exactdn url: $exactdn_url" );
 
 
2579
 
2580
+ /**
2581
+ * Add query strings to ExactDN URL.
2582
+ * By default, ExactDN doesn't support query strings so we ignore them.
2583
+ * This setting is ExactDN Server dependent.
2584
+ *
2585
+ * @param bool false Should query strings be added to the image URL. Default is false.
2586
+ * @param string $image_url_parts['host'] Image URL's host.
2587
+ */
2588
+ if ( isset( $image_url_parts['query'] ) && apply_filters( 'exactdn_add_query_string_to_domain', false, $image_url_parts['host'] ) ) {
2589
+ $exactdn_url .= '?q=' . rawurlencode( $image_url_parts['query'] );
2590
+ }
2591
+ // This is disabled, as I don't think we really need it.
2592
+ if ( false && ! empty( $image_url_parts['query'] ) && false !== strpos( $image_url_parts['query'], 'theia_smart' ) ) {
2593
+ $args = wp_parse_args( $image_url_parts['query'], $args );
2594
+ }
2595
+
2596
+ if ( $args ) {
2597
+ if ( is_array( $args ) ) {
2598
+ $exactdn_url = add_query_arg( $args, $exactdn_url );
2599
+ } else {
2600
+ // You can pass a query string for complicated requests, although this should have been converted to an array already.
2601
+ $exactdn_url .= '?' . $args;
2602
+ }
2603
+ }
2604
+ $this->debug_message( "exactdn url with args: $exactdn_url" );
2605
+
2606
+ return $this->url_scheme( $exactdn_url, $scheme );
2607
  }
 
 
 
2608
 
2609
  /**
2610
+ * Prepends schemeless urls or replaces non-http scheme with a valid scheme, defaults to 'http'.
 
 
2611
  *
2612
+ * @param string $url The URL to parse.
2613
+ * @param string|null $scheme Retrieve specific URL component.
2614
+ * @return string Result of parse_url.
2615
  */
2616
+ function url_scheme( $url, $scheme ) {
2617
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2618
+ if ( ! in_array( $scheme, array( 'http', 'https' ), true ) ) {
2619
+ $this->debug_message( 'not a valid scheme' );
2620
+ if ( preg_match( '#^(https?:)?//#', $url ) ) {
2621
+ $this->debug_message( 'url has a valid scheme already' );
2622
+ return $url;
2623
+ }
2624
+ $this->debug_message( 'invalid scheme provided, and url sucks, defaulting to http' );
2625
+ $scheme = 'http';
 
 
 
 
2626
  }
2627
+ $this->debug_message( "valid $scheme - $url" );
2628
+ return preg_replace( '#^([a-z:]+)?//#i', "$scheme://", $url );
2629
  }
 
 
 
 
2630
 
2631
+ /**
2632
+ * A wrapper for PHP's parse_url, prepending assumed scheme for network path
2633
+ * URLs. PHP versions 5.4.6 and earlier do not correctly parse without scheme.
2634
+ *
2635
+ * @param string $url The URL to parse.
2636
+ * @param integer $component Retrieve specific URL component.
2637
+ * @return mixed Result of parse_url.
2638
+ */
2639
+ function parse_url( $url, $component = -1 ) {
2640
+ if ( 0 === strpos( $url, '//' ) ) {
2641
+ $url = ( is_ssl() ? 'https:' : 'http:' ) . $url;
2642
+ }
2643
+ if ( false === strpos( $url, 'http' ) && '/' !== substr( $url, 0, 1 ) ) {
2644
+ $url = ( is_ssl() ? 'https://' : 'http://' ) . $url;
2645
  }
2646
+ // Because encoded ampersands in the filename break things.
2647
+ $url = str_replace( '&#038;', '&', $url );
2648
+ return parse_url( $url, $component );
2649
  }
 
 
 
2650
 
2651
+ /**
2652
+ * A wrapper for human_time_diff() that gives sub-minute times in seconds.
2653
+ *
2654
+ * @param int $from Unix timestamp from which the difference begins.
2655
+ * @param int $to Optional. Unix timestamp to end the time difference. Default is time().
2656
+ * @return string Human readable time difference.
2657
+ */
2658
+ function human_time_diff( $from, $to = '' ) {
2659
+ if ( empty( $to ) ) {
2660
+ $to = time();
2661
+ }
2662
+ $diff = (int) abs( $to - $from );
2663
+ if ( $diff < 60 ) {
2664
+ return "$diff sec";
2665
+ }
2666
+ return human_time_diff( $from, $to );
2667
  }
 
 
 
 
2668
 
2669
+ /**
2670
+ * Adds link to header which enables DNS prefetching for faster speed.
2671
+ *
2672
+ * @param array $hints A list of hints for a particular relationship type.
2673
+ * @param string $relationship_type The type of hint being filtered: dns-prefetch, preconnect, etc.
2674
+ * @return array The list of hints, potentially with the ExactDN domain added in.
2675
+ */
2676
+ function dns_prefetch( $hints, $relationship_type ) {
2677
+ if ( 'dns-prefetch' === $relationship_type && $this->exactdn_domain ) {
2678
+ $hints[] = '//' . $this->exactdn_domain;
2679
+ }
2680
+ return $hints;
 
 
2681
  }
 
 
2682
 
2683
+ /**
2684
+ * Adds the ExactDN domain to the list of 'local' domains for Autoptimize.
2685
+ *
2686
+ * @param array $domains A list of domains considered 'local' by Autoptimize.
2687
+ * @return array The same list, with the ExactDN domain appended.
2688
+ */
2689
+ function autoptimize_cdn_url( $domains ) {
2690
+ $this->debug_message( '<b>' . __METHOD__ . '()</b>' );
2691
+ if ( is_array( $domains ) && ! in_array( $this->exactdn_domain, $domains, true ) ) {
2692
+ $this->debug_message( 'adding to AO list: ' . $this->exactdn_domain );
2693
+ $domains[] = $this->exactdn_domain;
2694
+ }
2695
+ return $domains;
2696
  }
 
2697
  }
2698
 
2699
+ global $exactdn;
2700
+ $exactdn = new ExactDN();
 
 
 
 
 
 
 
 
 
 
 
 
2701
  }
 
 
 
common.php CHANGED
@@ -17,15 +17,15 @@
17
  // TODO: check this patch, to see if the use of 'full' causes any issues: https://core.trac.wordpress.org/ticket/37840 .
18
  // TODO: use this: https://codex.wordpress.org/AJAX_in_Plugins#The_post-load_JavaScript_Event .
19
  // TODO: can some of the bulk "fallbacks" be implemented for async processing?
20
- // TODO: check to see if we can use PHP and WP core is_iterable and is_countable functions.
21
  if ( ! defined( 'ABSPATH' ) ) {
22
  exit;
23
  }
24
 
25
- define( 'EWWW_IMAGE_OPTIMIZER_VERSION', '481.0' );
26
 
27
  // Initialize a couple globals.
28
- $ewww_debug = '';
29
  $ewww_defer = true;
30
 
31
  if ( WP_DEBUG && function_exists( 'memory_get_usage' ) ) {
@@ -122,10 +122,10 @@ add_filter( 'as3cf_attachment_file_paths', 'ewww_image_optimizer_as3cf_attachmen
122
  add_filter( 'as3cf_object_meta', 'ewww_image_optimizer_as3cf_object_meta' );
123
  // Loads the plugin translations.
124
  add_action( 'plugins_loaded', 'ewww_image_optimizer_preinit' );
 
 
125
  // Load our front-end parsers for ExactDN and/or Alt WebP.
126
  add_action( 'init', 'ewww_image_optimizer_parser_init', 99 );
127
- // Checks for nextgen/nextcellent/flagallery existence, and loads the appropriate classes.
128
- add_action( 'init', 'ewww_image_optimizer_gallery_support' );
129
  // Initializes the plugin for admin interactions, like saving network settings and scheduling cron jobs.
130
  add_action( 'admin_init', 'ewww_image_optimizer_admin_init' );
131
  // Check the current screen ID to see if temp debugging should still be enabled.
@@ -176,8 +176,12 @@ add_action( 'wp_ajax_ewww_manual_cloud_restore', 'ewww_image_optimizer_manual' )
176
  add_action( 'wp_ajax_ewww_manual_cloud_restore_single', 'ewww_image_optimizer_cloud_restore_single_image_handler' );
177
  // AJAX action hook to dismiss the WooCommerce regen notice.
178
  add_action( 'wp_ajax_ewww_dismiss_wc_regen', 'ewww_image_optimizer_dismiss_wc_regen' );
 
 
179
  // AJAX action hook to disable the media library notice.
180
  add_action( 'wp_ajax_ewww_dismiss_media_notice', 'ewww_image_optimizer_dismiss_media_notice' );
 
 
181
  // Adds script to highlight mis-sized images on the front-end (for logged in admins only).
182
  add_action( 'wp_head', 'ewww_image_optimizer_resize_detection_script' );
183
  // Adds a button on the admin bar to allow highlighting mis-sized images on-demand.
@@ -220,7 +224,7 @@ function ewww_image_optimizer_parser_init() {
220
  /**
221
  * Page Parsing class for working with HTML content.
222
  */
223
- require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-ewwwio-page-parser.php' );
224
  /**
225
  * ExactDN class for parsing image urls and rewriting them.
226
  */
@@ -237,11 +241,11 @@ function ewww_image_optimizer_parser_init() {
237
  /**
238
  * Page Parsing class for working with HTML content.
239
  */
240
- require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-ewwwio-page-parser.php' );
241
  /**
242
- * Alt WebP class for parsing image urls and rewriting them for WebP support.
243
  */
244
- require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-ewwwio-lazy-load.php' );
245
  }
246
  // If Alt WebP Rewriting is enabled.
247
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
@@ -249,11 +253,11 @@ function ewww_image_optimizer_parser_init() {
249
  /**
250
  * Page Parsing class for working with HTML content.
251
  */
252
- require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-ewwwio-page-parser.php' );
253
  /**
254
  * Alt WebP class for parsing image urls and rewriting them for WebP support.
255
  */
256
- require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-ewwwio-alt-webp.php' );
257
  }
258
  if ( $buffer_start ) {
259
  // Start an output buffer before any output starts.
@@ -486,16 +490,14 @@ function ewww_image_optimizer_preinit() {
486
  }
487
 
488
  /**
489
- * Checks to see if NextGEN, Nextcellent, or GRAND FlaGallery are enabled.
490
- *
491
- * If a supported gallery is found, loads the class(es) to support integration with that gallery.
492
  */
493
- function ewww_image_optimizer_gallery_support() {
494
  ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
495
 
496
  // Check to see if this is the settings page and enable debugging temporarily if it is.
497
  global $ewwwio_temp_debug;
498
- $ewwwio_temp_debug = false;
499
  if ( is_admin() && ! wp_doing_ajax() ) {
500
  if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
501
  $ewwwio_temp_debug = true;
@@ -536,6 +538,13 @@ function ewww_image_optimizer_gallery_support() {
536
  }
537
  }
538
  }
 
 
 
 
 
 
 
539
  }
540
 
541
  /**
@@ -591,6 +600,24 @@ function ewww_image_optimizer_upgrade() {
591
  update_option( 'ewww_image_optimizer_aux_resume', '' );
592
  ewww_image_optimizer_delete_pending();
593
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
  ewww_image_optimizer_remove_obsolete_settings();
595
  update_option( 'ewww_image_optimizer_version', EWWW_IMAGE_OPTIMIZER_VERSION );
596
  ewww_image_optimizer_debug_log();
@@ -836,6 +863,15 @@ function ewww_image_optimizer_admin_init() {
836
  add_action( 'network_admin_notices', 'ewww_image_optimizer_notice_reoptimization' );
837
  add_action( 'admin_notices', 'ewww_image_optimizer_notice_reoptimization' );
838
  add_action( 'admin_notices', 'ewww_image_optimizer_notice_media_listmode' );
 
 
 
 
 
 
 
 
 
839
  if ( ! empty( $_GET['page'] ) ) {
840
  if ( 'regenerate-thumbnails' === $_GET['page']
841
  || 'force-regenerate-thumbnails' === $_GET['page']
@@ -846,10 +882,6 @@ function ewww_image_optimizer_admin_init() {
846
  add_action( 'admin_notices', 'ewww_image_optimizer_thumbnail_regen_notice' );
847
  }
848
  }
849
- if ( class_exists( 'WooCommerce' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_wc_regen' ) ) {
850
- add_action( 'admin_notices', 'ewww_image_optimizer_notice_wc_regen' );
851
- add_action( 'admin_footer', 'ewww_image_optimizer_wc_regen_script' );
852
- }
853
  if ( ! empty( $_GET['ewww_pngout'] ) ) {
854
  add_action( 'admin_notices', 'ewww_image_optimizer_pngout_installed' );
855
  add_action( 'network_admin_notices', 'ewww_image_optimizer_pngout_installed' );
@@ -858,6 +890,14 @@ function ewww_image_optimizer_admin_init() {
858
  ewww_image_optimizer_privacy_policy_content();
859
  ewww_image_optimizer_ajax_compat_check();
860
  }
 
 
 
 
 
 
 
 
861
  // Increase the version when the next bump is coming.
862
  if ( defined( 'PHP_VERSION_ID' ) && PHP_VERSION_ID < 50600 ) {
863
  add_action( 'network_admin_notices', 'ewww_image_optimizer_php55_warning' );
@@ -989,10 +1029,10 @@ function ewww_image_optimizer_privacy_policy_content() {
989
  */
990
  function ewww_image_optimizer_current_screen( $screen ) {
991
  global $ewwwio_temp_debug;
992
- global $ewww_debug;
993
- if ( false === strpos( $screen->id, 'settings_page_ewww-image-optimizer' ) ) {
994
  $ewwwio_temp_debug = false;
995
- $ewww_debug = '';
996
  }
997
  }
998
 
@@ -1169,6 +1209,7 @@ function ewww_image_optimizer_save_admin_colors() {
1169
  $ewwwio_admin_color = '#0073aa';
1170
  }
1171
  }
 
1172
  /**
1173
  * Determines the background color to use based on the selected admin theme.
1174
  */
@@ -1452,6 +1493,9 @@ function ewww_image_optimizer_remove_obsolete_settings() {
1452
  delete_option( 'ewww_image_optimizer_bulk_image_count' );
1453
  delete_option( 'ewww_image_optimizer_maxwidth' );
1454
  delete_option( 'ewww_image_optimizer_maxheight' );
 
 
 
1455
  }
1456
 
1457
  /**
@@ -1475,6 +1519,29 @@ function ewww_image_optimizer_pngout_installed() {
1475
  }
1476
  }
1477
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1478
  /**
1479
  * Display a notice that PHP version 5.5 support is going away.
1480
  */
@@ -1530,6 +1597,32 @@ function ewww_image_optimizer_wc_regen_script() {
1530
  "</script>\n";
1531
  }
1532
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1533
  /**
1534
  * Let the user know they can view more options and stats in the Media Library's list mode.
1535
  */
@@ -1540,6 +1633,49 @@ function ewww_image_optimizer_notice_media_listmode() {
1540
  }
1541
  }
1542
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1543
  /**
1544
  * Alert the user when 5 images have been re-optimized more than 10 times.
1545
  *
@@ -1870,6 +2006,10 @@ function ewww_image_optimizer_handle_upload( $params ) {
1870
  } else {
1871
  $file_path = $params['file'];
1872
  }
 
 
 
 
1873
  ewww_image_optimizer_autorotate( $file_path );
1874
  $new_image = ewww_image_optimizer_autoconvert( $file_path );
1875
  if ( $new_image ) {
@@ -1888,7 +2028,6 @@ function ewww_image_optimizer_handle_upload( $params ) {
1888
  }
1889
  $file_path = $new_image;
1890
  }
1891
- // NOTE: if you use the ewww_image_optimizer_defer_resizing filter to defer the resize operation, only the "other" dimensions will apply
1892
  // Resize here unless the user chose to defer resizing or imsanity is enabled with a max size.
1893
  if ( ! apply_filters( 'ewww_image_optimizer_defer_resizing', false ) && ! function_exists( 'imsanity_get_max_width_height' ) ) {
1894
  if ( empty( $params['type'] ) ) {
@@ -1900,6 +2039,7 @@ function ewww_image_optimizer_handle_upload( $params ) {
1900
  ewww_image_optimizer_resize_upload( $file_path );
1901
  }
1902
  }
 
1903
  return $params;
1904
  }
1905
 
@@ -2692,7 +2832,7 @@ function ewww_image_optimizer_filesize( $file ) {
2692
  * @return bool True if the variable is iterable.
2693
  */
2694
  function ewww_image_optimizer_iterable( $var ) {
2695
- return ! empty( $var ) && ( is_array( $var ) || is_object( $var ) );
2696
  }
2697
 
2698
  /**
@@ -4299,7 +4439,7 @@ function ewww_image_optimizer_size_format( $size, $precision = 1 ) {
4299
  * @global object $wpdb
4300
  * @global object $ewwwdb A clone of $wpdb unless it is lacking utf8 connectivity.
4301
  * @global bool $ewww_defer Set to false to avoid deferring image optimization.
4302
- * @global string $ewww_debug Contains in-memory debug log.
4303
  * @global object $ewww_image Contains more information about the image currently being processed.
4304
  *
4305
  * @param array $attachment {
@@ -4404,8 +4544,8 @@ function ewww_image_optimizer_aux_images_loop( $attachment = null, $auto = false
4404
  number_format_i18n( $elapsed )
4405
  );
4406
  if ( get_site_option( 'ewww_image_optimizer_debug' ) ) {
4407
- global $ewww_debug;
4408
- $output['results'] .= '<div style="background-color:#f1f1f1;">' . $ewww_debug . '</div>';
4409
  }
4410
  $next_file = ewww_image_optimizer_absolutize_path( $wpdb->get_var( "SELECT path FROM $wpdb->ewwwio_images WHERE pending=1 LIMIT 1" ) );
4411
  if ( ! empty( $next_file ) ) {
@@ -6085,6 +6225,56 @@ function ewww_image_optimizer_resize_from_meta_data( $meta, $id = null, $log = t
6085
  return $meta;
6086
  }
6087
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6088
  /**
6089
  * Check to see if Shield's location lock option is enabled.
6090
  *
@@ -7293,6 +7483,20 @@ function ewww_image_optimizer_dismiss_wc_regen() {
7293
  wp_die();
7294
  }
7295
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7296
  /**
7297
  * Disables the Media Library notice about List Mode.
7298
  */
@@ -7308,6 +7512,21 @@ function ewww_image_optimizer_dismiss_media_notice() {
7308
  wp_die();
7309
  }
7310
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7311
  /**
7312
  * Load JS in media library footer for bulk actions.
7313
  */
@@ -7746,9 +7965,6 @@ function ewww_image_optimizer_get_image_sizes() {
7746
  if ( is_array( $image_sizes ) ) {
7747
  ewwwio_debug_message( 'sizes: ' . implode( '<br>', $image_sizes ) );
7748
  }
7749
- if ( false ) { // set to true to enable more debugging.
7750
- ewwwio_debug_message( print_r( $_wp_additional_image_sizes, true ) );
7751
- }
7752
  if ( ewww_image_optimizer_iterable( $image_sizes ) ) {
7753
  foreach ( $image_sizes as $_size ) {
7754
  if ( in_array( $_size, array( 'thumbnail', 'medium', 'medium_large', 'large' ), true ) ) {
@@ -7781,8 +7997,6 @@ function ewww_image_optimizer_get_image_sizes() {
7781
 
7782
  /**
7783
  * Wrapper that displays the EWWW IO options in the multisite network admin.
7784
- *
7785
- * @global string $ewww_debug In memory debug log.
7786
  */
7787
  function ewww_image_optimizer_network_options() {
7788
  ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
@@ -7795,8 +8009,6 @@ function ewww_image_optimizer_network_options() {
7795
  *
7796
  * By default, the only options displayed are the per-site resizes list, but a network admin can
7797
  * permit site admins to configure their own blog settings.
7798
- *
7799
- * @global string $ewww_debug In memory debug log.
7800
  */
7801
  function ewww_image_optimizer_network_singlesite_options() {
7802
  ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
@@ -7807,7 +8019,7 @@ function ewww_image_optimizer_network_singlesite_options() {
7807
  /**
7808
  * Displays the EWWW IO options along with status information, and debugging information.
7809
  *
7810
- * @global string $ewww_debug In memory debug log.
7811
  *
7812
  * @param string $network Indicates which options should be shown in multisite installations.
7813
  */
@@ -7842,8 +8054,8 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
7842
  require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
7843
  }
7844
  if ( 'debug-silent' !== $network ) {
7845
- global $ewwwio_hs_beacon;
7846
- $ewwwio_hs_beacon->admin_notice( $network_class );
7847
  }
7848
  if ( is_multisite() && is_plugin_active_for_network( EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE_REL ) && ! get_site_option( 'ewww_image_optimizer_allow_multisite_override' ) ) {
7849
  $network_class = 'network-multisite';
@@ -7930,6 +8142,13 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
7930
  }
7931
  if ( class_exists( 'Jetpack_Photon' ) && Jetpack::is_module_active( 'photon' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
7932
  $status_notices .= '<p><b>ExactDN:</b> <span style="color: red">' . esc_html__( 'Inactive, please disable the Image Performance option on the Jetpack Dashboard.', 'ewww-image-optimizer' ) . '</span></p>';
 
 
 
 
 
 
 
7933
  } elseif ( class_exists( 'ExactDN' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
7934
  $status_notices .= '<p><b>ExactDN:</b> ';
7935
  global $exactdn;
@@ -8198,12 +8417,11 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
8198
  ! ( 'network-singlesite' === $network && ! get_site_option( 'ewww_image_optimizer_allow_multisite_override' ) ) ) { // Also make sure that this isn't single site without override mode.
8199
  $output[] = "<ul class='ewww-tab-nav'>\n" .
8200
  "<li class='ewww-tab ewww-general-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Basic', 'ewww-image-optimizer' ) . "</span></li>\n" .
8201
- "<li class='ewww-tab ewww-exactdn-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'ExactDN', 'ewww-image-optimizer' ) . "</span></li>\n" .
8202
  "<li class='ewww-tab ewww-optimization-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Advanced', 'ewww-image-optimizer' ) . "</span></li>\n" .
8203
  "<li class='ewww-tab ewww-resize-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Resize', 'ewww-image-optimizer' ) . "</span></li>\n" .
8204
  "<li class='ewww-tab ewww-conversion-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Convert', 'ewww-image-optimizer' ) . "</span></li>\n" .
8205
  "<li class='ewww-tab ewww-webp-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'WebP', 'ewww-image-optimizer' ) . "</span></li>\n" .
8206
- // "<li class='ewww-tab ewww-tools-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Tools', 'ewww-image-optimizer' ) . "</span></li>\n" .
8207
  "<li class='ewww-tab ewww-overrides-nav'><span class='ewww-tab-hidden'><a href='https://docs.ewww.io/article/40-override-options' target='_blank'><span class='ewww-tab-hidden'>" . esc_html__( 'Overrides', 'ewww-image-optimizer' ) . "</a></span></li>\n" .
8208
  "<li class='ewww-tab ewww-support-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Support', 'ewww-image-optimizer' ) . "</span></li>\n" .
8209
  "<li class='ewww-tab ewww-contribute-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Contribute', 'ewww-image-optimizer' ) . "</span></li>\n" .
@@ -8578,7 +8796,7 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
8578
  $output[] = "<div id='ewww-webp-settings'>\n";
8579
  $output[] = '<noscript><h2>' . esc_html__( 'WebP', 'ewww-image-optimizer' ) . '</h2></noscript>';
8580
  $output[] = "<table class='form-table'>\n";
8581
- if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) || ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp' ) ) {
8582
  $output[] = "<tr class='$network_class'><th scope='row'><label for='ewww_image_optimizer_webp'>" . esc_html__( 'JPG/PNG to WebP', 'ewww-image-optimizer' ) . '</label>' .
8583
  ewwwio_help_link( 'https://docs.ewww.io/article/16-ewww-io-and-webp-images', '5854745ac697912ffd6c1c89' ) .
8584
  "</th><td><span><input type='checkbox' id='ewww_image_optimizer_webp' name='ewww_image_optimizer_webp' value='true' " .
@@ -8594,7 +8812,7 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
8594
  esc_html__( 'WebP images will be generated and saved for all JPG/PNG images regardless of their size. The JS WebP Rewriting will not check if a file exists, only that the domain matches the home url.', 'ewww-image-optimizer' ) . "</span></td></tr>\n";
8595
  ewwwio_debug_message( 'forced webp: ' . ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_force' ) ? 'on' : 'off' ) );
8596
  }
8597
- if ( ! ewww_image_optimizer_ce_webp_enabled() && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
8598
  $webp_paths = ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_paths' ) ? esc_html( implode( "\n", ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_paths' ) ) ) : '';
8599
  $output[] = "<tr class='$network_class'><th scope='row'><label for='ewww_image_optimizer_webp_paths'>" . esc_html__( 'WebP URLs', 'ewww-image-optimizer' ) . '</label>' .
8600
  ewwwio_help_link( 'https://docs.ewww.io/article/16-ewww-io-and-webp-images', '5854745ac697912ffd6c1c89' ) . '</th><td>' .
@@ -8612,6 +8830,8 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
8612
  ewwwio_debug_message( 'alt webp rewriting: ' . ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) ? 'on' : 'off' ) );
8613
  } elseif ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
8614
  $output[] = "<tr class='$network_class'><th>&nbsp;</th><td><p class='description'>" . esc_html__( 'WebP images are served automatically by ExactDN.', 'ewww-image-optimizer' ) . '</p></td></tr>';
 
 
8615
  }
8616
  $output[] = "</table>\n</div>\n";
8617
 
@@ -8659,10 +8879,10 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
8659
  $output[] = "<p class='submit'><input type='submit' class='button-primary' value='" . esc_attr__( 'Save Changes', 'ewww-image-optimizer' ) . "' /></p>\n";
8660
  $output[] = "</form>\n";
8661
  // Make sure .htaccess rules are terminated when ExactDN is enabled.
8662
- if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
8663
  ewww_image_optimizer_webp_rewrite_verify();
8664
  }
8665
- if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp' ) && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) && ! ewww_image_optimizer_ce_webp_enabled() && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
8666
  if ( ! apache_mod_loaded( 'mod_rewrite' ) ) {
8667
  ewwwio_debug_message( 'webp missing mod_rewrite' );
8668
  /* translators: %s: mod_rewrite or mod_headers */
@@ -8725,22 +8945,22 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
8725
  return;
8726
  }
8727
  $output = apply_filters( 'ewww_image_optimizer_settings', $output );
8728
- if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) && ! ewww_image_optimizer_ce_webp_enabled() && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
8729
- global $ewwwio_alt_webp;
8730
- $ewwwio_alt_webp->inline_script();
8731
  }
8732
 
8733
  $help_instructions = esc_html__( 'Enable the Debugging option and refresh this page to include debugging information with your question.', 'ewww-image-optimizer' ) . ' ' .
8734
  esc_html__( 'This will allow us to assist you more quickly.', 'ewww-image-optimizer' );
8735
 
8736
- global $ewww_debug;
8737
- if ( ! empty( $ewww_debug ) ) {
8738
  $debug_output = '<p style="clear:both"><b>' . esc_html__( 'Debugging Information', 'ewww-image-optimizer' ) . ':</b> <button id="ewww-copy-debug" class="button button-secondary" type="button">' . esc_html__( 'Copy', 'ewww-image-optimizer' ) . '</button>';
8739
  if ( is_file( WP_CONTENT_DIR . '/ewww/debug.log' ) ) {
8740
  $debug_output .= "&emsp;<a href='admin.php?action=ewww_image_optimizer_view_debug_log'>" . esc_html( 'View Debug Log', 'ewww-image-optimizer' ) . "</a> - <a href='admin.php?action=ewww_image_optimizer_delete_debug_log'>" . esc_html( 'Remove Debug Log', 'ewww-image-optimizer' ) . '</a>';
8741
  }
8742
  $debug_output .= '</p>';
8743
- $debug_output .= '<div id="ewww-debug-info" style="border:1px solid #e5e5e5;background:#fff;overflow:auto;height:300px;width:800px;" contenteditable="true">' . $ewww_debug . '</div>';
8744
 
8745
  $help_instructions = esc_html__( 'Debugging information will be included with your message automatically.', 'ewww-image-optimizer' ) . ' ' .
8746
  esc_html__( 'This will allow us to assist you more quickly.', 'ewww-image-optimizer' );
@@ -8767,12 +8987,12 @@ function ewww_image_optimizer_options( $network = 'singlesite' ) {
8767
  $hs_identify = array(
8768
  'email' => utf8_encode( $help_email ),
8769
  );
8770
- if ( ! empty( $ewww_debug ) ) {
8771
- $ewww_debug_array = explode( '<br>', $ewww_debug );
8772
- $ewww_debug_i = 0;
8773
- foreach ( $ewww_debug_array as $ewww_debug_line ) {
8774
- $hs_identify[ 'debug_info_' . $ewww_debug_i ] = $ewww_debug_line;
8775
- $ewww_debug_i++;
8776
  }
8777
  }
8778
  ?>
@@ -8871,6 +9091,16 @@ function ewwwio_help_link( $link, $hsid = '' ) {
8871
  return "<a class='$link_class' href='$link' target='_blank' style='margin: 3px'$beacon_attr><img title='" . esc_attr__( 'Help', 'ewww-image-optimizer' ) . "' src='$help_icon'></a>";
8872
  }
8873
 
 
 
 
 
 
 
 
 
 
 
8874
  /**
8875
  * Removes the API key currently installed.
8876
  *
@@ -8990,7 +9220,7 @@ function ewww_image_optimizer_admin_bar_menu() {
8990
  /**
8991
  * Adds information to the in-memory debug log.
8992
  *
8993
- * @global string $ewww_debug The in-memory debug log.
8994
  *
8995
  * @param string $message Debug information to add to the log.
8996
  */
@@ -9003,14 +9233,14 @@ function ewwwio_debug_message( $message ) {
9003
  if ( $ewwwio_temp_debug || ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
9004
  $memory_limit = ewwwio_memory_limit();
9005
  if ( strlen( $message ) + 4000000 + memory_get_usage( true ) <= $memory_limit ) {
9006
- global $ewww_debug;
9007
- $message = str_replace( "\n\n\n", '<br>', $message );
9008
- $message = str_replace( "\n\n", '<br>', $message );
9009
- $message = str_replace( "\n", '<br>', $message );
9010
- $ewww_debug .= "$message<br>";
9011
  } else {
9012
- global $ewww_debug;
9013
- $ewww_debug = "not logging message, memory limit is $memory_limit";
9014
  }
9015
  }
9016
  }
@@ -9018,13 +9248,13 @@ function ewwwio_debug_message( $message ) {
9018
  /**
9019
  * Saves the in-memory debug log to a logfile in the plugin folder.
9020
  *
9021
- * @global string $ewww_debug The in-memory debug log.
9022
  */
9023
  function ewww_image_optimizer_debug_log() {
9024
- global $ewww_debug;
9025
  global $ewwwio_temp_debug;
9026
  $debug_log = WP_CONTENT_DIR . '/ewww/debug.log';
9027
- if ( ! empty( $ewww_debug ) && empty( $ewwwio_temp_debug ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) && is_writable( WP_CONTENT_DIR . '/ewww/' ) ) {
9028
  $memory_limit = ewwwio_memory_limit();
9029
  clearstatcache();
9030
  $timestamp = date( 'Y-m-d H:i:s' ) . "\n";
@@ -9036,12 +9266,12 @@ function ewww_image_optimizer_debug_log() {
9036
  touch( $debug_log );
9037
  }
9038
  }
9039
- if ( filesize( $debug_log ) + strlen( $ewww_debug ) + 4000000 + memory_get_usage( true ) <= $memory_limit && is_writable( $debug_log ) ) {
9040
- $ewww_debug = str_replace( '<br>', "\n", $ewww_debug );
9041
- file_put_contents( $debug_log, $timestamp . $ewww_debug, FILE_APPEND );
9042
  }
9043
  }
9044
- $ewww_debug = '';
9045
  ewwwio_memory( __FUNCTION__ );
9046
  }
9047
 
@@ -9081,35 +9311,35 @@ function ewww_image_optimizer_delete_debug_log() {
9081
  /**
9082
  * Adds version information to the in-memory debug log.
9083
  *
9084
- * @global string $ewww_debug The in-memory debug log.
9085
  * @global int $wp_version
9086
  */
9087
  function ewwwio_debug_version_info() {
9088
- global $ewww_debug;
9089
  if ( ! extension_loaded( 'suhosin' ) && function_exists( 'get_current_user' ) ) {
9090
- $ewww_debug .= get_current_user() . '<br>';
9091
  }
9092
 
9093
- $ewww_debug .= 'EWWW IO version: ' . EWWW_IMAGE_OPTIMIZER_VERSION . '<br>';
9094
 
9095
  // Check the WP version.
9096
  global $wp_version;
9097
- $my_version = substr( $wp_version, 0, 3 );
9098
- $ewww_debug .= "WP version: $wp_version<br>";
9099
 
9100
  if ( defined( 'PHP_VERSION_ID' ) ) {
9101
- $ewww_debug .= 'PHP version: ' . PHP_VERSION_ID . '<br>';
9102
  }
9103
  if ( defined( 'LIBXML_VERSION' ) ) {
9104
- $ewww_debug .= 'libxml version: ' . LIBXML_VERSION . '<br>';
9105
  }
9106
  if ( ! empty( $_ENV['PANTHEON_ENVIRONMENT'] ) && in_array( $_ENV['PANTHEON_ENVIRONMENT'], array( 'test', 'live', 'dev' ), true ) ) {
9107
- $ewww_debug .= "detected pantheon env: {$_ENV['PANTHEON_ENVIRONMENT']}<br>";
9108
  }
9109
  if ( defined( 'EWWW_IO_CLOUD_PLUGIN' ) && EWWW_IO_CLOUD_PLUGIN ) {
9110
- $ewww_debug .= 'cloud plugin<br>';
9111
  } else {
9112
- $ewww_debug .= 'core plugin<br>';
9113
  }
9114
  }
9115
 
@@ -9274,9 +9504,9 @@ function ewww_image_optimizer_image_queue_debug() {
9274
  */
9275
  function ewww_image_optimizer_temp_debug_clear() {
9276
  global $ewwwio_temp_debug;
9277
- global $ewww_debug;
9278
  if ( $ewwwio_temp_debug ) {
9279
- $ewww_debug = '';
9280
  }
9281
  $ewwwio_temp_debug = false;
9282
  }
17
  // TODO: check this patch, to see if the use of 'full' causes any issues: https://core.trac.wordpress.org/ticket/37840 .
18
  // TODO: use this: https://codex.wordpress.org/AJAX_in_Plugins#The_post-load_JavaScript_Event .
19
  // TODO: can some of the bulk "fallbacks" be implemented for async processing?
20
+ // TODO: check to see if we can use PHP and WP core is_countable functions.
21
  if ( ! defined( 'ABSPATH' ) ) {
22
  exit;
23
  }
24
 
25
+ define( 'EWWW_IMAGE_OPTIMIZER_VERSION', '493.0' );
26
 
27
  // Initialize a couple globals.
28
+ $eio_debug = '';
29
  $ewww_defer = true;
30
 
31
  if ( WP_DEBUG && function_exists( 'memory_get_usage' ) ) {
122
  add_filter( 'as3cf_object_meta', 'ewww_image_optimizer_as3cf_object_meta' );
123
  // Loads the plugin translations.
124
  add_action( 'plugins_loaded', 'ewww_image_optimizer_preinit' );
125
+ // Runs any checks that need to run everywhere and early.
126
+ add_action( 'init', 'ewww_image_optimizer_init', 9 );
127
  // Load our front-end parsers for ExactDN and/or Alt WebP.
128
  add_action( 'init', 'ewww_image_optimizer_parser_init', 99 );
 
 
129
  // Initializes the plugin for admin interactions, like saving network settings and scheduling cron jobs.
130
  add_action( 'admin_init', 'ewww_image_optimizer_admin_init' );
131
  // Check the current screen ID to see if temp debugging should still be enabled.
176
  add_action( 'wp_ajax_ewww_manual_cloud_restore_single', 'ewww_image_optimizer_cloud_restore_single_image_handler' );
177
  // AJAX action hook to dismiss the WooCommerce regen notice.
178
  add_action( 'wp_ajax_ewww_dismiss_wc_regen', 'ewww_image_optimizer_dismiss_wc_regen' );
179
+ // AJAX action hook to dismiss the WP/LR Sync regen notice.
180
+ add_action( 'wp_ajax_ewww_dismiss_lr_sync', 'ewww_image_optimizer_dismiss_lr_sync' );
181
  // AJAX action hook to disable the media library notice.
182
  add_action( 'wp_ajax_ewww_dismiss_media_notice', 'ewww_image_optimizer_dismiss_media_notice' );
183
+ // AJAX action hook to disable the 'review request' notice.
184
+ add_action( 'wp_ajax_ewww_dismiss_review_notice', 'ewww_image_optimizer_dismiss_review_notice' );
185
  // Adds script to highlight mis-sized images on the front-end (for logged in admins only).
186
  add_action( 'wp_head', 'ewww_image_optimizer_resize_detection_script' );
187
  // Adds a button on the admin bar to allow highlighting mis-sized images on-demand.
224
  /**
225
  * Page Parsing class for working with HTML content.
226
  */
227
+ require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-eio-page-parser.php' );
228
  /**
229
  * ExactDN class for parsing image urls and rewriting them.
230
  */
241
  /**
242
  * Page Parsing class for working with HTML content.
243
  */
244
+ require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-eio-page-parser.php' );
245
  /**
246
+ * Lazy Load class for parsing image urls and rewriting them to defer off-screen images.
247
  */
248
+ require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-eio-lazy-load.php' );
249
  }
250
  // If Alt WebP Rewriting is enabled.
251
  if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
253
  /**
254
  * Page Parsing class for working with HTML content.
255
  */
256
+ require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-eio-page-parser.php' );
257
  /**
258
  * Alt WebP class for parsing image urls and rewriting them for WebP support.
259
  */
260
+ require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-eio-alt-webp.php' );
261
  }
262
  if ( $buffer_start ) {
263
  // Start an output buffer before any output starts.
490
  }
491
 
492
  /**
493
+ * Runs early for checks that need to happen on init before anything else.
 
 
494
  */
495
+ function ewww_image_optimizer_init() {
496
  ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
497
 
498
  // Check to see if this is the settings page and enable debugging temporarily if it is.
499
  global $ewwwio_temp_debug;
500
+ $ewwwio_temp_debug = ! empty( $ewwwio_temp_debug ) ? $ewwwio_temp_debug : false;
501
  if ( is_admin() && ! wp_doing_ajax() ) {
502
  if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
503
  $ewwwio_temp_debug = true;
538
  }
539
  }
540
  }
541
+ if ( defined( 'DOING_WPLR_REQUEST' ) && DOING_WPLR_REQUEST ) {
542
+ // Unhook all automatic processing, and save an option that (does not autoload) tells the user LR Sync regenerated their images and they should run the bulk optimizer.
543
+ remove_filter( 'wp_image_editors', 'ewww_image_optimizer_load_editor', 60 );
544
+ remove_filter( 'wp_generate_attachment_metadata', 'ewww_image_optimizer_resize_from_meta_data', 15 );
545
+ add_filter( 'wp_generate_attachment_metadata', 'ewww_image_optimizer_lr_sync_update' );
546
+ return;
547
+ }
548
  }
549
 
550
  /**
600
  update_option( 'ewww_image_optimizer_aux_resume', '' );
601
  ewww_image_optimizer_delete_pending();
602
  }
603
+ if ( get_option( 'ewww_image_optimizer_version' ) && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_review_time' ) ) {
604
+ $review_time = rand( time(), time() + 51 * DAY_IN_SECONDS );
605
+ add_option( 'ewww_image_optimizer_review_time', $review_time, '', false );
606
+ add_site_option( 'ewww_image_optimizer_review_time', $review_time );
607
+ } elseif ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_review_time' ) ) {
608
+ $review_time = time() + 7 * DAY_IN_SECONDS;
609
+ add_option( 'ewww_image_optimizer_review_time', $review_time, '', false );
610
+ add_site_option( 'ewww_image_optimizer_review_time', $review_time );
611
+ }
612
+ if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
613
+ if ( 'external' === get_option( 'elementor_css_print_method' ) ) {
614
+ update_option( 'elementor_css_print_method', 'internal' );
615
+ }
616
+ if ( function_exists( 'et_get_option' ) && function_exists( 'et_update_option' ) && 'on' === et_get_option( 'et_pb_static_css_file', 'on' ) ) {
617
+ et_update_option( 'et_pb_static_css_file', 'off' );
618
+ et_update_option( 'et_pb_css_in_footer', 'off' );
619
+ }
620
+ }
621
  ewww_image_optimizer_remove_obsolete_settings();
622
  update_option( 'ewww_image_optimizer_version', EWWW_IMAGE_OPTIMIZER_VERSION );
623
  ewww_image_optimizer_debug_log();
863
  add_action( 'network_admin_notices', 'ewww_image_optimizer_notice_reoptimization' );
864
  add_action( 'admin_notices', 'ewww_image_optimizer_notice_reoptimization' );
865
  add_action( 'admin_notices', 'ewww_image_optimizer_notice_media_listmode' );
866
+ if (
867
+ is_super_admin() &&
868
+ ewww_image_optimizer_get_option( 'ewww_image_optimizer_review_time' ) &&
869
+ ewww_image_optimizer_get_option( 'ewww_image_optimizer_review_time' ) < time() &&
870
+ ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_dismiss_review_notice' )
871
+ ) {
872
+ add_action( 'admin_notices', 'ewww_image_optimizer_notice_review' );
873
+ add_action( 'admin_footer', 'ewww_image_optimizer_notice_review_script' );
874
+ }
875
  if ( ! empty( $_GET['page'] ) ) {
876
  if ( 'regenerate-thumbnails' === $_GET['page']
877
  || 'force-regenerate-thumbnails' === $_GET['page']
882
  add_action( 'admin_notices', 'ewww_image_optimizer_thumbnail_regen_notice' );
883
  }
884
  }
 
 
 
 
885
  if ( ! empty( $_GET['ewww_pngout'] ) ) {
886
  add_action( 'admin_notices', 'ewww_image_optimizer_pngout_installed' );
887
  add_action( 'network_admin_notices', 'ewww_image_optimizer_pngout_installed' );
890
  ewww_image_optimizer_privacy_policy_content();
891
  ewww_image_optimizer_ajax_compat_check();
892
  }
893
+ if ( class_exists( 'WooCommerce' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_wc_regen' ) ) {
894
+ add_action( 'admin_notices', 'ewww_image_optimizer_notice_wc_regen' );
895
+ add_action( 'admin_footer', 'ewww_image_optimizer_wc_regen_script' );
896
+ }
897
+ if ( class_exists( 'Meow_WPLR_Sync_Core' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_lr_sync' ) ) {
898
+ add_action( 'admin_notices', 'ewww_image_optimizer_notice_lr_sync' );
899
+ add_action( 'admin_footer', 'ewww_image_optimizer_lr_sync_script' );
900
+ }
901
  // Increase the version when the next bump is coming.
902
  if ( defined( 'PHP_VERSION_ID' ) && PHP_VERSION_ID < 50600 ) {
903
  add_action( 'network_admin_notices', 'ewww_image_optimizer_php55_warning' );
1029
  */
1030
  function ewww_image_optimizer_current_screen( $screen ) {
1031
  global $ewwwio_temp_debug;
1032
+ global $eio_debug;
1033
+ if ( false === strpos( $screen->id, 'settings_page_ewww-image-optimizer' ) && false === strpos( $screen->id, 'settings_page_easy-image-optimizer' ) ) {
1034
  $ewwwio_temp_debug = false;
1035
+ $eio_debug = '';
1036
  }
1037
  }
1038
 
1209
  $ewwwio_admin_color = '#0073aa';
1210
  }
1211
  }
1212
+
1213
  /**
1214
  * Determines the background color to use based on the selected admin theme.
1215
  */
1493
  delete_option( 'ewww_image_optimizer_bulk_image_count' );
1494
  delete_option( 'ewww_image_optimizer_maxwidth' );
1495
  delete_option( 'ewww_image_optimizer_maxheight' );
1496
+ delete_option( 'ewww_image_optimizer_exactdn_failures' );
1497
+ delete_option( 'ewww_image_optimizer_exactdn_checkin' );
1498
+ delete_option( 'ewww_image_optimizer_exactdn_suspended' );
1499
  }
1500
 
1501
  /**
1519
  }
1520
  }
1521
 
1522
+ /**
1523
+ * Display a notice that we could not activate an ExactDN domain.
1524
+ */
1525
+ function ewww_image_optimizer_notice_exactdn_activation_error() {
1526
+ global $exactdn_activate_error;
1527
+ if ( empty( $exactdn_activate_error ) ) {
1528
+ $exactdn_activate_error = 'error unknown';
1529
+ }
1530
+ echo '<div id="ewww-image-optimizer-notice-exactdn-error" class="notice notice-error"><p>' .
1531
+ esc_html__( 'Could not activate ExactDN, please try again in a few minutes. If this error continues, please contact support and provide this complete error message.', 'ewww-image-optimizer' ) .
1532
+ '<br><code>' . $exactdn_activate_error . '</code>' .
1533
+ '</p></div>';
1534
+ }
1535
+
1536
+ /**
1537
+ * Let the user know ExactDN setup was successful.
1538
+ */
1539
+ function ewww_image_optimizer_notice_exactdn_activation_success() {
1540
+ echo '<div id="ewww-image-optimizer-notice-exactdn-success" class="notice notice-success"><p>' .
1541
+ esc_html__( 'ExactDN setup and verification is complete.', 'ewww-image-optimizer' ) .
1542
+ '</p></div>';
1543
+ }
1544
+
1545
  /**
1546
  * Display a notice that PHP version 5.5 support is going away.
1547
  */
1597
  "</script>\n";
1598
  }
1599
 
1600
+ /**
1601
+ * Lets the user know LR Sync has regenerated thumbnails and that they need to take action.
1602
+ */
1603
+ function ewww_image_optimizer_notice_lr_sync() {
1604
+ echo "<div id='ewww-image-optimizer-lr-sync' class='notice notice-info is-dismissible'><p>" . esc_html__( 'EWWW Image Optimizer has detected a WP/LR Sync process. To optimize new thumbnails, you may run the Bulk Optimizer from the Media menu. This notice may be dismissed after the Sync process is complete.', 'ewww-image-optimizer' ) . '</p></div>';
1605
+ }
1606
+
1607
+ /**
1608
+ * Loads the inline script to dismiss the LR sync notice.
1609
+ */
1610
+ function ewww_image_optimizer_lr_sync_script() {
1611
+ echo
1612
+ "<script>\n" .
1613
+ "jQuery(document).on('click', '#ewww-image-optimizer-lr-sync .notice-dismiss', function() {\n" .
1614
+ "\tvar ewww_dismiss_lr_sync_data = {\n" .
1615
+ "\t\taction: 'ewww_dismiss_lr_sync',\n" .
1616
+ "\t};\n" .
1617
+ "\tjQuery.post(ajaxurl, ewww_dismiss_lr_sync_data, function(response) {\n" .
1618
+ "\t\tif (response) {\n" .
1619
+ "\t\t\tconsole.log(response);\n" .
1620
+ "\t\t}\n" .
1621
+ "\t});\n" .
1622
+ "});\n" .
1623
+ "</script>\n";
1624
+ }
1625
+
1626
  /**
1627
  * Let the user know they can view more options and stats in the Media Library's list mode.
1628
  */
1633
  }
1634
  }
1635
 
1636
+ /**
1637
+ * Ask the user to leave a review for the plugin on wp.org.
1638
+ */
1639
+ function ewww_image_optimizer_notice_review() {
1640
+ echo "<div id='ewww-image-optimizer-review' class='notice notice-info is-dismissible'><p>" .
1641
+ esc_html__( "Hi, you've been using the EWWW Image Optimizer for a while, and we hope it has been a big help for you.", 'ewww-image-optimizer' ) . '<br>' .
1642
+ esc_html__( 'If you could take a few moments to rate it on WordPress.org, we would really appreciate your help making the plugin better. Thanks!', 'ewww-image-optimizer' ) .
1643
+ '<br><a target="_blank" href="https://wordpress.org/support/plugin/ewww-image-optimizer/reviews/#new-post" class="button-secondary">' . esc_html__( 'Post Review', 'ewww-image-optimizer' ) . '</a>' .
1644
+ '</p></div>';
1645
+ }
1646
+
1647
+ /**
1648
+ * Loads the inline script to dismiss the review notice.
1649
+ */
1650
+ function ewww_image_optimizer_notice_review_script() {
1651
+ echo
1652
+ "<script>\n" .
1653
+ "jQuery(document).on('click', '#ewww-image-optimizer-review .notice-dismiss', function() {\n" .
1654
+ "\tvar ewww_dismiss_review_data = {\n" .
1655
+ "\t\taction: 'ewww_dismiss_review_notice',\n" .
1656
+ "\t};\n" .
1657
+ "\tjQuery.post(ajaxurl, ewww_dismiss_review_data, function(response) {\n" .
1658
+ "\t\tif (response) {\n" .
1659
+ "\t\t\tconsole.log(response);\n" .
1660
+ "\t\t}\n" .
1661
+ "\t});\n" .
1662
+ "});\n" .
1663
+ "</script>\n";
1664
+ }
1665
+
1666
+ /**
1667
+ * Inform the user of our beacon function so that they can opt-in.
1668
+ */
1669
+ function ewww_image_optimizer_notice_beacon() {
1670
+ $optin_url = 'admin.php?action=eio_opt_into_hs_beacon';
1671
+ $optout_url = 'admin.php?action=eio_opt_out_of_hs_beacon';
1672
+ echo '<div id="ewww-image-optimizer-hs-beacon" class="notice notice-info"><p>' .
1673
+ esc_html__( 'Enable the EWWW I.O. support beacon, which gives you access to documentation and our support team right from your WordPress dashboard. To assist you more efficiently, we collect the current url, IP address, browser/device information, and debugging information.', 'ewww-image-optimizer' ) .
1674
+ '<br><a href="' . esc_url( $optin_url ) . '" class="button-secondary">' . esc_html__( 'Allow', 'ewww-image-optimizer' ) . '</a>' .
1675
+ '&nbsp;<a href="' . esc_url( $optout_url ) . '" class="button-secondary">' . esc_html__( 'Do not allow', 'ewww-image-optimizer' ) . '</a>' .
1676
+ '</p></div>';
1677
+ }
1678
+
1679
  /**
1680
  * Alert the user when 5 images have been re-optimized more than 10 times.
1681
  *
2006
  } else {
2007
  $file_path = $params['file'];
2008
  }
2009
+ if ( ! is_file( $file_path ) || ! filesize( $file_path ) ) {
2010
+ clearstatcache();
2011
+ return $params;
2012
+ }
2013
  ewww_image_optimizer_autorotate( $file_path );
2014
  $new_image = ewww_image_optimizer_autoconvert( $file_path );
2015
  if ( $new_image ) {
2028
  }
2029
  $file_path = $new_image;
2030
  }
 
2031
  // Resize here unless the user chose to defer resizing or imsanity is enabled with a max size.
2032
  if ( ! apply_filters( 'ewww_image_optimizer_defer_resizing', false ) && ! function_exists( 'imsanity_get_max_width_height' ) ) {
2033
  if ( empty( $params['type'] ) ) {
2039
  ewww_image_optimizer_resize_upload( $file_path );
2040
  }
2041
  }
2042
+ clearstatcache();
2043
  return $params;
2044
  }
2045
 
2832
  * @return bool True if the variable is iterable.
2833
  */
2834
  function ewww_image_optimizer_iterable( $var ) {
2835
+ return ! empty( $var ) && ( is_array( $var ) || $var instanceof Traversable );
2836
  }
2837
 
2838
  /**
4439
  * @global object $wpdb
4440
  * @global object $ewwwdb A clone of $wpdb unless it is lacking utf8 connectivity.
4441
  * @global bool $ewww_defer Set to false to avoid deferring image optimization.
4442
+ * @global string $eio_debug Contains in-memory debug log.
4443
  * @global object $ewww_image Contains more information about the image currently being processed.
4444
  *
4445
  * @param array $attachment {
4544
  number_format_i18n( $elapsed )
4545
  );
4546
  if ( get_site_option( 'ewww_image_optimizer_debug' ) ) {
4547
+ global $eio_debug;
4548
+ $output['results'] .= '<div style="background-color:#f1f1f1;">' . $eio_debug . '</div>';
4549
  }
4550
  $next_file = ewww_image_optimizer_absolutize_path( $wpdb->get_var( "SELECT path FROM $wpdb->ewwwio_images WHERE pending=1 LIMIT 1" ) );
4551
  if ( ! empty( $next_file ) ) {
6225
  return $meta;
6226
  }
6227
 
6228
+ /**
6229
+ * Only runs during WP/LR Sync to check if an attachment has been updated.
6230
+ *
6231
+ * @param array $meta The attachment metadata generated by WordPress.
6232
+ * @param int $id Optional. The attachment ID number. Default null. Accepts any non-negative integer.
6233
+ * @return array $meta Send the metadata back from whence it came.
6234
+ */
6235
+ function ewww_image_optimizer_lr_sync_update( $meta, $id = null ) {
6236
+ update_option( 'ewww_image_optimizer_lr_sync', true, false );
6237
+ list( $file_path, $upload_path ) = ewww_image_optimizer_attachment_path( $meta, $id );
6238
+ if ( ewww_image_optimizer_stream_wrapped( $file_path ) || ! is_file( $file_path ) ) {
6239
+ return $meta;
6240
+ }
6241
+ ewwwio_debug_message( "retrieved file path: $file_path" );
6242
+ $type = ewww_image_optimizer_mimetype( $file_path, 'i' );
6243
+ $supported_types = array(
6244
+ 'image/jpeg',
6245
+ 'image/png',
6246
+ 'image/gif',
6247
+ 'application/pdf',
6248
+ );
6249
+ if ( ! in_array( $type, $supported_types, true ) ) {
6250
+ ewwwio_debug_message( "mimetype not supported: $id" );
6251
+ return $meta;
6252
+ }
6253
+
6254
+ // Get a list of all the image files optimized for this attachment.
6255
+ global $wpdb;
6256
+ $optimized_images = $wpdb->get_results( $wpdb->prepare( "SELECT id,path,image_size FROM $wpdb->ewwwio_images WHERE attachment_id = %d AND gallery = 'media' AND image_size <> 0 ORDER BY orig_size DESC", $id ), ARRAY_A );
6257
+ if ( ! ewww_image_optimizer_iterable( $optimized_images ) ) {
6258
+ return $meta;
6259
+ }
6260
+ foreach ( $optimized_images as $optimized_image ) {
6261
+ $file_size = ewww_image_optimizer_filesize( ewww_image_optimizer_absolutize_path( $optimized_image['path'] ) );
6262
+ if ( $file_size === (int) $optimized_image['image_size'] ) {
6263
+ continue;
6264
+ }
6265
+ $wpdb->update(
6266
+ $wpdb->ewwwio_images,
6267
+ array(
6268
+ 'image_size' => 0,
6269
+ ),
6270
+ array(
6271
+ 'id' => $optimized_image['id'],
6272
+ )
6273
+ );
6274
+ }
6275
+ return $meta;
6276
+ }
6277
+
6278
  /**
6279
  * Check to see if Shield's location lock option is enabled.
6280
  *
7483
  wp_die();
7484
  }
7485
 
7486
+ /**
7487
+ * Dismisses the LR sync notice.
7488
+ */
7489
+ function ewww_image_optimizer_dismiss_lr_sync() {
7490
+ ewwwio_ob_clean();
7491
+ ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
7492
+ // Verify that the user is properly authorized.
7493
+ if ( ! current_user_can( apply_filters( 'ewww_image_optimizer_admin_permissions', 'manage_options' ) ) ) {
7494
+ wp_die( esc_html__( 'Access denied.', 'ewww-image-optimizer' ) );
7495
+ }
7496
+ delete_option( 'ewww_image_optimizer_lr_sync' );
7497
+ wp_die();
7498
+ }
7499
+
7500
  /**
7501
  * Disables the Media Library notice about List Mode.
7502
  */
7512
  wp_die();
7513
  }
7514
 
7515
+ /**
7516
+ * Disables the notice about leaving a review.
7517
+ */
7518
+ function ewww_image_optimizer_dismiss_review_notice() {
7519
+ ewwwio_ob_clean();
7520
+ ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
7521
+ // Verify that the user is properly authorized.
7522
+ if ( ! current_user_can( apply_filters( 'ewww_image_optimizer_admin_permissions', 'manage_options' ) ) ) {
7523
+ wp_die( esc_html__( 'Access denied.', 'ewww-image-optimizer' ) );
7524
+ }
7525
+ update_option( 'ewww_image_optimizer_dismiss_review_notice', true, false );
7526
+ update_site_option( 'ewww_image_optimizer_dismiss_review_notice', true );
7527
+ wp_die();
7528
+ }
7529
+
7530
  /**
7531
  * Load JS in media library footer for bulk actions.
7532
  */
7965
  if ( is_array( $image_sizes ) ) {
7966
  ewwwio_debug_message( 'sizes: ' . implode( '<br>', $image_sizes ) );
7967
  }
 
 
 
7968
  if ( ewww_image_optimizer_iterable( $image_sizes ) ) {
7969
  foreach ( $image_sizes as $_size ) {
7970
  if ( in_array( $_size, array( 'thumbnail', 'medium', 'medium_large', 'large' ), true ) ) {
7997
 
7998
  /**
7999
  * Wrapper that displays the EWWW IO options in the multisite network admin.
 
 
8000
  */
8001
  function ewww_image_optimizer_network_options() {
8002
  ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
8009
  *
8010
  * By default, the only options displayed are the per-site resizes list, but a network admin can
8011
  * permit site admins to configure their own blog settings.
 
 
8012
  */
8013
  function ewww_image_optimizer_network_singlesite_options() {
8014
  ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
8019
  /**
8020
  * Displays the EWWW IO options along with status information, and debugging information.
8021
  *
8022
+ * @global string $eio_debug In memory debug log.
8023
  *
8024
  * @param string $network Indicates which options should be shown in multisite installations.
8025
  */
8054
  require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
8055
  }
8056
  if ( 'debug-silent' !== $network ) {
8057
+ global $eio_hs_beacon;
8058
+ $eio_hs_beacon->admin_notice( $network_class );
8059
  }
8060
  if ( is_multisite() && is_plugin_active_for_network( EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE_REL ) && ! get_site_option( 'ewww_image_optimizer_allow_multisite_override' ) ) {
8061
  $network_class = 'network-multisite';
8142
  }
8143
  if ( class_exists( 'Jetpack_Photon' ) && Jetpack::is_module_active( 'photon' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
8144
  $status_notices .= '<p><b>ExactDN:</b> <span style="color: red">' . esc_html__( 'Inactive, please disable the Image Performance option on the Jetpack Dashboard.', 'ewww-image-optimizer' ) . '</span></p>';
8145
+ } elseif ( get_option( 'easyio_exactdn' ) ) {
8146
+ ewww_image_optimizer_webp_rewrite_verify();
8147
+ update_option( 'ewww_image_optimizer_exactdn', false );
8148
+ update_option( 'ewww_image_optimizer_lazy_load', false );
8149
+ update_option( 'ewww_image_optimizer_webp_for_cdn', false );
8150
+ $compress_score += 80;
8151
+ $resize_score += 50;
8152
  } elseif ( class_exists( 'ExactDN' ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
8153
  $status_notices .= '<p><b>ExactDN:</b> ';
8154
  global $exactdn;
8417
  ! ( 'network-singlesite' === $network && ! get_site_option( 'ewww_image_optimizer_allow_multisite_override' ) ) ) { // Also make sure that this isn't single site without override mode.
8418
  $output[] = "<ul class='ewww-tab-nav'>\n" .
8419
  "<li class='ewww-tab ewww-general-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Basic', 'ewww-image-optimizer' ) . "</span></li>\n" .
8420
+ ( get_option( 'easyio_exactdn' ) ? '' : "<li class='ewww-tab ewww-exactdn-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'ExactDN', 'ewww-image-optimizer' ) . "</span></li>\n" ) .
8421
  "<li class='ewww-tab ewww-optimization-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Advanced', 'ewww-image-optimizer' ) . "</span></li>\n" .
8422
  "<li class='ewww-tab ewww-resize-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Resize', 'ewww-image-optimizer' ) . "</span></li>\n" .
8423
  "<li class='ewww-tab ewww-conversion-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Convert', 'ewww-image-optimizer' ) . "</span></li>\n" .
8424
  "<li class='ewww-tab ewww-webp-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'WebP', 'ewww-image-optimizer' ) . "</span></li>\n" .
 
8425
  "<li class='ewww-tab ewww-overrides-nav'><span class='ewww-tab-hidden'><a href='https://docs.ewww.io/article/40-override-options' target='_blank'><span class='ewww-tab-hidden'>" . esc_html__( 'Overrides', 'ewww-image-optimizer' ) . "</a></span></li>\n" .
8426
  "<li class='ewww-tab ewww-support-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Support', 'ewww-image-optimizer' ) . "</span></li>\n" .
8427
  "<li class='ewww-tab ewww-contribute-nav'><span class='ewww-tab-hidden'>" . esc_html__( 'Contribute', 'ewww-image-optimizer' ) . "</span></li>\n" .
8796
  $output[] = "<div id='ewww-webp-settings'>\n";
8797
  $output[] = '<noscript><h2>' . esc_html__( 'WebP', 'ewww-image-optimizer' ) . '</h2></noscript>';
8798
  $output[] = "<table class='form-table'>\n";
8799
+ if ( ! ewww_image_optimizer_easy_active() || ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp' ) ) {
8800
  $output[] = "<tr class='$network_class'><th scope='row'><label for='ewww_image_optimizer_webp'>" . esc_html__( 'JPG/PNG to WebP', 'ewww-image-optimizer' ) . '</label>' .
8801
  ewwwio_help_link( 'https://docs.ewww.io/article/16-ewww-io-and-webp-images', '5854745ac697912ffd6c1c89' ) .
8802
  "</th><td><span><input type='checkbox' id='ewww_image_optimizer_webp' name='ewww_image_optimizer_webp' value='true' " .
8812
  esc_html__( 'WebP images will be generated and saved for all JPG/PNG images regardless of their size. The JS WebP Rewriting will not check if a file exists, only that the domain matches the home url.', 'ewww-image-optimizer' ) . "</span></td></tr>\n";
8813
  ewwwio_debug_message( 'forced webp: ' . ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_force' ) ? 'on' : 'off' ) );
8814
  }
8815
+ if ( ! ewww_image_optimizer_ce_webp_enabled() && ! ewww_image_optimizer_easy_active() ) {
8816
  $webp_paths = ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_paths' ) ? esc_html( implode( "\n", ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_paths' ) ) ) : '';
8817
  $output[] = "<tr class='$network_class'><th scope='row'><label for='ewww_image_optimizer_webp_paths'>" . esc_html__( 'WebP URLs', 'ewww-image-optimizer' ) . '</label>' .
8818
  ewwwio_help_link( 'https://docs.ewww.io/article/16-ewww-io-and-webp-images', '5854745ac697912ffd6c1c89' ) . '</th><td>' .
8830
  ewwwio_debug_message( 'alt webp rewriting: ' . ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) ? 'on' : 'off' ) );
8831
  } elseif ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
8832
  $output[] = "<tr class='$network_class'><th>&nbsp;</th><td><p class='description'>" . esc_html__( 'WebP images are served automatically by ExactDN.', 'ewww-image-optimizer' ) . '</p></td></tr>';
8833
+ } elseif ( get_option( 'easyio_exactdn' ) ) {
8834
+ $output[] = "<tr class='$network_class'><th>&nbsp;</th><td><p class='description'>" . esc_html__( 'WebP images are served automatically by Easy Image Optimizer.', 'ewww-image-optimizer' ) . '</p></td></tr>';
8835
  }
8836
  $output[] = "</table>\n</div>\n";
8837
 
8879
  $output[] = "<p class='submit'><input type='submit' class='button-primary' value='" . esc_attr__( 'Save Changes', 'ewww-image-optimizer' ) . "' /></p>\n";
8880
  $output[] = "</form>\n";
8881
  // Make sure .htaccess rules are terminated when ExactDN is enabled.
8882
+ if ( ewww_image_optimizer_easy_active() ) {
8883
  ewww_image_optimizer_webp_rewrite_verify();
8884
  }
8885
+ if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp' ) && ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) && ! ewww_image_optimizer_ce_webp_enabled() && ! ewww_image_optimizer_easy_active() ) {
8886
  if ( ! apache_mod_loaded( 'mod_rewrite' ) ) {
8887
  ewwwio_debug_message( 'webp missing mod_rewrite' );
8888
  /* translators: %s: mod_rewrite or mod_headers */
8945
  return;
8946
  }
8947
  $output = apply_filters( 'ewww_image_optimizer_settings', $output );
8948
+ if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) && ! ewww_image_optimizer_ce_webp_enabled() && ! ewww_image_optimizer_easy_active() ) {
8949
+ global $eio_alt_webp;
8950
+ $eio_alt_webp->inline_script();
8951
  }
8952
 
8953
  $help_instructions = esc_html__( 'Enable the Debugging option and refresh this page to include debugging information with your question.', 'ewww-image-optimizer' ) . ' ' .
8954
  esc_html__( 'This will allow us to assist you more quickly.', 'ewww-image-optimizer' );
8955
 
8956
+ global $eio_debug;
8957
+ if ( ! empty( $eio_debug ) ) {
8958
  $debug_output = '<p style="clear:both"><b>' . esc_html__( 'Debugging Information', 'ewww-image-optimizer' ) . ':</b> <button id="ewww-copy-debug" class="button button-secondary" type="button">' . esc_html__( 'Copy', 'ewww-image-optimizer' ) . '</button>';
8959
  if ( is_file( WP_CONTENT_DIR . '/ewww/debug.log' ) ) {
8960
  $debug_output .= "&emsp;<a href='admin.php?action=ewww_image_optimizer_view_debug_log'>" . esc_html( 'View Debug Log', 'ewww-image-optimizer' ) . "</a> - <a href='admin.php?action=ewww_image_optimizer_delete_debug_log'>" . esc_html( 'Remove Debug Log', 'ewww-image-optimizer' ) . '</a>';
8961
  }
8962
  $debug_output .= '</p>';
8963
+ $debug_output .= '<div id="ewww-debug-info" style="border:1px solid #e5e5e5;background:#fff;overflow:auto;height:300px;width:800px;" contenteditable="true">' . $eio_debug . '</div>';
8964
 
8965
  $help_instructions = esc_html__( 'Debugging information will be included with your message automatically.', 'ewww-image-optimizer' ) . ' ' .
8966
  esc_html__( 'This will allow us to assist you more quickly.', 'ewww-image-optimizer' );
8987
  $hs_identify = array(
8988
  'email' => utf8_encode( $help_email ),
8989
  );
8990
+ if ( ! empty( $eio_debug ) ) {
8991
+ $eio_debug_array = explode( '<br>', $eio_debug );
8992
+ $eio_debug_i = 0;
8993
+ foreach ( $eio_debug_array as $eio_debug_line ) {
8994
+ $hs_identify[ 'debug_info_' . $eio_debug_i ] = $eio_debug_line;
8995
+ $eio_debug_i++;
8996
  }
8997
  }
8998
  ?>
9091
  return "<a class='$link_class' href='$link' target='_blank' style='margin: 3px'$beacon_attr><img title='" . esc_attr__( 'Help', 'ewww-image-optimizer' ) . "' src='$help_icon'></a>";
9092
  }
9093
 
9094
+ /**
9095
+ * Checks to see if ExactDN or Easy I.O. is active.
9096
+ */
9097
+ function ewww_image_optimizer_easy_active() {
9098
+ if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) || get_option( 'easyio_exactdn' ) ) {
9099
+ return true;
9100
+ }
9101
+ return false;
9102
+ }
9103
+
9104
  /**
9105
  * Removes the API key currently installed.
9106
  *
9220
  /**
9221
  * Adds information to the in-memory debug log.
9222
  *
9223
+ * @global string $eio_debug The in-memory debug log.
9224
  *
9225
  * @param string $message Debug information to add to the log.
9226
  */
9233
  if ( $ewwwio_temp_debug || ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) ) {
9234
  $memory_limit = ewwwio_memory_limit();
9235
  if ( strlen( $message ) + 4000000 + memory_get_usage( true ) <= $memory_limit ) {
9236
+ global $eio_debug;
9237
+ $message = str_replace( "\n\n\n", '<br>', $message );
9238
+ $message = str_replace( "\n\n", '<br>', $message );
9239
+ $message = str_replace( "\n", '<br>', $message );
9240
+ $eio_debug .= "$message<br>";
9241
  } else {
9242
+ global $eio_debug;
9243
+ $eio_debug = "not logging message, memory limit is $memory_limit";
9244
  }
9245
  }
9246
  }
9248
  /**
9249
  * Saves the in-memory debug log to a logfile in the plugin folder.
9250
  *
9251
+ * @global string $eio_debug The in-memory debug log.
9252
  */
9253
  function ewww_image_optimizer_debug_log() {
9254
+ global $eio_debug;
9255
  global $ewwwio_temp_debug;
9256
  $debug_log = WP_CONTENT_DIR . '/ewww/debug.log';
9257
+ if ( ! empty( $eio_debug ) && empty( $ewwwio_temp_debug ) && ewww_image_optimizer_get_option( 'ewww_image_optimizer_debug' ) && is_writable( WP_CONTENT_DIR . '/ewww/' ) ) {
9258
  $memory_limit = ewwwio_memory_limit();
9259
  clearstatcache();
9260
  $timestamp = date( 'Y-m-d H:i:s' ) . "\n";
9266
  touch( $debug_log );
9267
  }
9268
  }
9269
+ if ( filesize( $debug_log ) + strlen( $eio_debug ) + 4000000 + memory_get_usage( true ) <= $memory_limit && is_writable( $debug_log ) ) {
9270
+ $eio_debug = str_replace( '<br>', "\n", $eio_debug );
9271
+ file_put_contents( $debug_log, $timestamp . $eio_debug, FILE_APPEND );
9272
  }
9273
  }
9274
+ $eio_debug = '';
9275
  ewwwio_memory( __FUNCTION__ );
9276
  }
9277
 
9311
  /**
9312
  * Adds version information to the in-memory debug log.
9313
  *
9314
+ * @global string $eio_debug The in-memory debug log.
9315
  * @global int $wp_version
9316
  */
9317
  function ewwwio_debug_version_info() {
9318
+ global $eio_debug;
9319
  if ( ! extension_loaded( 'suhosin' ) && function_exists( 'get_current_user' ) ) {
9320
+ $eio_debug .= get_current_user() . '<br>';
9321
  }
9322
 
9323
+ $eio_debug .= 'EWWW IO version: ' . EWWW_IMAGE_OPTIMIZER_VERSION . '<br>';
9324
 
9325
  // Check the WP version.
9326
  global $wp_version;
9327
+ $my_version = substr( $wp_version, 0, 3 );
9328
+ $eio_debug .= "WP version: $wp_version<br>";
9329
 
9330
  if ( defined( 'PHP_VERSION_ID' ) ) {
9331
+ $eio_debug .= 'PHP version: ' . PHP_VERSION_ID . '<br>';
9332
  }
9333
  if ( defined( 'LIBXML_VERSION' ) ) {
9334
+ $eio_debug .= 'libxml version: ' . LIBXML_VERSION . '<br>';
9335
  }
9336
  if ( ! empty( $_ENV['PANTHEON_ENVIRONMENT'] ) && in_array( $_ENV['PANTHEON_ENVIRONMENT'], array( 'test', 'live', 'dev' ), true ) ) {
9337
+ $eio_debug .= "detected pantheon env: {$_ENV['PANTHEON_ENVIRONMENT']}<br>";
9338
  }
9339
  if ( defined( 'EWWW_IO_CLOUD_PLUGIN' ) && EWWW_IO_CLOUD_PLUGIN ) {
9340
+ $eio_debug .= 'cloud plugin<br>';
9341
  } else {
9342
+ $eio_debug .= 'core plugin<br>';
9343
  }
9344
  }
9345
 
9504
  */
9505
  function ewww_image_optimizer_temp_debug_clear() {
9506
  global $ewwwio_temp_debug;
9507
+ global $eio_debug;
9508
  if ( $ewwwio_temp_debug ) {
9509
+ $eio_debug = '';
9510
  }
9511
  $ewwwio_temp_debug = false;
9512
  }
ewww-image-optimizer.php CHANGED
@@ -13,7 +13,7 @@ Plugin Name: EWWW Image Optimizer
13
  Plugin URI: https://wordpress.org/plugins/ewww-image-optimizer/
14
  Description: Reduce file sizes for images within WordPress including NextGEN Gallery and GRAND FlAGallery. Uses jpegtran, optipng/pngout, and gifsicle.
15
  Author: Exactly WWW
16
- Version: 4.8.1
17
  Author URI: https://ewww.io/
18
  License: GPLv3
19
  */
@@ -108,9 +108,13 @@ if ( ! defined( 'PHP_VERSION_ID' ) || PHP_VERSION_ID < 50600 ) {
108
  */
109
  require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'unique.php' );
110
  /**
111
- * All the 'common' functions for both EWWW I.O. functions.
112
  */
113
  require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'common.php' );
 
 
 
 
114
  /**
115
  * The various class extensions for parallel and background optimization.
116
  */
@@ -126,7 +130,7 @@ if ( ! defined( 'PHP_VERSION_ID' ) || PHP_VERSION_ID < 50600 ) {
126
  /**
127
  * EWWWIO_HS_Beacon class for embedding the HelpScout Beacon.
128
  */
129
- require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-ewwwio-hs-beacon.php' );
130
  } // End if().
131
 
132
  if ( ! function_exists( 'ewww_image_optimizer_unsupported_php' ) ) {
13
  Plugin URI: https://wordpress.org/plugins/ewww-image-optimizer/
14
  Description: Reduce file sizes for images within WordPress including NextGEN Gallery and GRAND FlAGallery. Uses jpegtran, optipng/pngout, and gifsicle.
15
  Author: Exactly WWW
16
+ Version: 4.9.3
17
  Author URI: https://ewww.io/
18
  License: GPLv3
19
  */
108
  */
109
  require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'unique.php' );
110
  /**
111
+ * All the 'common' functions for both EWWW I.O. plugins.
112
  */
113
  require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'common.php' );
114
+ /**
115
+ * All the base functions for our plugins.
116
+ */
117
+ require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-eio-base.php' );
118
  /**
119
  * The various class extensions for parallel and background optimization.
120
  */
130
  /**
131
  * EWWWIO_HS_Beacon class for embedding the HelpScout Beacon.
132
  */
133
+ require_once( EWWW_IMAGE_OPTIMIZER_PLUGIN_PATH . 'classes/class-eio-hs-beacon.php' );
134
  } // End if().
135
 
136
  if ( ! function_exists( 'ewww_image_optimizer_unsupported_php' ) ) {
includes/eio.js CHANGED
@@ -80,7 +80,7 @@ jQuery(document).ready(function($) {
80
  $.post(ajaxurl, ewww_webp_rewrite_data, function(response) {
81
  $('#ewww-webp-rewrite-status').html('<b>' + response + '</b>');
82
  ewww_webp_image = document.getElementById("webp-image").src;
83
- document.getElementById("webp-image").src = ewww_webp_image + '#' + new Date().getTime();
84
  });
85
  return false;
86
  });
@@ -93,7 +93,7 @@ jQuery(document).ready(function($) {
93
  $.post(ajaxurl, ewww_webp_rewrite_data, function(response) {
94
  $('#ewww-webp-rewrite-status').html('<b>' + response + '</b>');
95
  ewww_webp_image = document.getElementById("webp-image").src;
96
- document.getElementById("webp-image").src = ewww_webp_image + '#' + new Date().getTime();
97
  });
98
  return false;
99
  });
80
  $.post(ajaxurl, ewww_webp_rewrite_data, function(response) {
81
  $('#ewww-webp-rewrite-status').html('<b>' + response + '</b>');
82
  ewww_webp_image = document.getElementById("webp-image").src;
83
+ document.getElementById("webp-image").src = ewww_webp_image + '?m=' + new Date().getTime();
84
  });
85
  return false;
86
  });
93
  $.post(ajaxurl, ewww_webp_rewrite_data, function(response) {
94
  $('#ewww-webp-rewrite-status').html('<b>' + response + '</b>');
95
  ewww_webp_image = document.getElementById("webp-image").src;
96
+ document.getElementById("webp-image").src = ewww_webp_image + '?m' + new Date().getTime();
97
  });
98
  return false;
99
  });
includes/lazysizes-post.js CHANGED
@@ -7,7 +7,10 @@ function constrainSrc(url,objectWidth,objectHeight,objectType){
7
  var regFit = /fit=(\d+),(\d+)/;
8
  var regResize = /resize=(\d+),(\d+)/;
9
  var decUrl = decodeURIComponent(url);
10
- if (url.search('\\?') > 0 && url.search(ewww_lazy_vars.exactdn_domain) > 0){
 
 
 
11
  var resultResize = regResize.exec(decUrl);
12
  if(resultResize && objectWidth < resultResize[1]){
13
  return decUrl.replace(regResize, 'resize=' + objectWidth + ',' + objectHeight);
@@ -39,7 +42,7 @@ function constrainSrc(url,objectWidth,objectHeight,objectType){
39
  return url + '&w=' + objectWidth;
40
  }
41
  }
42
- if (url.search('\\?') == -1 && url.search(ewww_lazy_vars.exactdn_domain) > 0){
43
  if('img'===objectType){
44
  return url + '?fit=' + objectWidth + ',' + objectHeight;
45
  }
7
  var regFit = /fit=(\d+),(\d+)/;
8
  var regResize = /resize=(\d+),(\d+)/;
9
  var decUrl = decodeURIComponent(url);
10
+ if (typeof eio_lazy_vars === 'undefined'){
11
+ var eio_lazy_vars = {"exactdn_domain":".exactdn.com"};
12
+ }
13
+ if (url.search('\\?') > 0 && url.search(eio_lazy_vars.exactdn_domain) > 0){
14
  var resultResize = regResize.exec(decUrl);
15
  if(resultResize && objectWidth < resultResize[1]){
16
  return decUrl.replace(regResize, 'resize=' + objectWidth + ',' + objectHeight);
42
  return url + '&w=' + objectWidth;
43
  }
44
  }
45
+ if (url.search('\\?') == -1 && url.search(eio_lazy_vars.exactdn_domain) > 0){
46
  if('img'===objectType){
47
  return url + '?fit=' + objectWidth + ',' + objectHeight;
48
  }
includes/lazysizes.min.js CHANGED
@@ -1 +1 @@
1
- var ewww_webp_supported=!1;function lazysizesWebP(e,t){var a=new Image;a.onload=function(){ewww_webp_supported=0<a.width&&0<a.height,t()},a.onerror=function(){t()},a.src="data:image/webp;base64,"+{alpha:"UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",animation:"UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"}[e]}function constrainSrc(e,t,a,i){if(null===e)return e;var r=/w=(\d+)/,n=/fit=(\d+),(\d+)/,o=/resize=(\d+),(\d+)/,s=decodeURIComponent(e);if(0<e.search("\\?")&&0<e.search(ewww_lazy_vars.exactdn_domain)){var l=o.exec(s);if(l&&t<l[1])return s.replace(o,"resize="+t+","+a);var d=r.exec(e);if(d&&t<=d[1])return"bg-cover"===i?e.replace(r,"resize="+t+","+a):e.replace(r,"w="+t);var c=n.exec(s);if(c&&t<c[1])return"bg-cover"===i?e.replace(r,"resize="+t+","+a):s.replace(n,"fit="+t+","+a);if(!d&&!c&&!l)return"img"===i?e+"&fit="+t+","+a:"bg-cover"===i?e+"?resize="+t+","+a:t<a?e+"&h="+a:e+"&w="+t}return-1==e.search("\\?")&&0<e.search(ewww_lazy_vars.exactdn_domain)?"img"===i?e+"?fit="+t+","+a:"bg-cover"===i?e+"?resize="+t+","+a:t<a?e+"?h="+a:e+"?w="+t:e}window.lazySizesConfig=window.lazySizesConfig||{},window.lazySizesConfig.init=!1,function(e,t){var a=function(i,A){"use strict";if(!A.getElementsByClassName)return;var g,z,v=A.documentElement,n=i.Date,r=i.HTMLPictureElement,o="addEventListener",h="getAttribute",t=i[o],u=i.setTimeout,a=i.requestAnimationFrame||u,s=i.requestIdleCallback,f=/^picture$/i,l=["load","error","lazyincluded","_lazyloaded"],d={},p=Array.prototype.forEach,c=function(e,t){return d[t]||(d[t]=new RegExp("(\\s|^)"+t+"(\\s|$)")),d[t].test(e[h]("class")||"")&&d[t]},m=function(e,t){c(e,t)||e.setAttribute("class",(e[h]("class")||"").trim()+" "+t)},y=function(e,t){var a;(a=c(e,t))&&e.setAttribute("class",(e[h]("class")||"").replace(a," "))},b=function(t,a,e){var i=e?o:"removeEventListener";e&&b(t,a),l.forEach(function(e){t[i](e,a)})},w=function(e,t,a,i,r){var n=A.createEvent("Event");return a||(a={}),a.instance=g,n.initEvent(t,!i,!r),n.detail=a,e.dispatchEvent(n),n},C=function(e,t){var a;!r&&(a=i.picturefill||z.pf)?(t&&t.src&&!e[h]("srcset")&&e.setAttribute("srcset",t.src),a({reevaluate:!0,elements:[e]})):t&&t.src&&(e.src=t.src)},_=function(e,t){return(getComputedStyle(e,null)||{})[t]},E=function(e,t,a){for(a=a||e.offsetWidth;a<z.minSize&&t&&!e._lazysizesWidth;)a=t.offsetWidth,t=t.parentNode;return a},W=(B=[],R=[],L=B,N=function(){var e=L;for(L=B.length?R:B,M=!(S=!0);e.length;)e.shift()();S=!1},Q=function(e,t){S&&!t?e.apply(this,arguments):(L.push(e),M||(M=!0,(A.hidden?u:a)(N)))},Q._lsFlush=N,Q),e=function(a,e){return e?function(){W(a)}:function(){var e=this,t=arguments;W(function(){a.apply(e,t)})}},x=function(e){var t,a,i=function(){t=null,e()},r=function(){var e=n.now()-a;e<99?u(r,99-e):(s||i)(i)};return function(){a=n.now(),t||(t=u(r,99))}};var S,M,B,R,L,N,Q;!function(){var e,t={lazyClass:"lazyload",loadedClass:"lazyloaded",loadingClass:"lazyloading",preloadClass:"lazypreload",errorClass:"lazyerror",autosizesClass:"lazyautosizes",srcAttr:"data-src",srcsetAttr:"data-srcset",sizesAttr:"data-sizes",minSize:40,customMedia:{},init:!0,expFactor:1.5,hFac:.8,loadMode:2,loadHidden:!0,ricTimeout:0,throttleDelay:125};for(e in z=i.lazySizesConfig||i.lazysizesConfig||{},t)e in z||(z[e]=t[e]);i.lazySizesConfig=z,u(function(){z.init&&D()})}();var P=(se=/^img$/i,le=/^iframe$/i,de="onscroll"in i&&!/(gle|ing)bot/.test(navigator.userAgent),ce=0,ue=0,fe=-1,Ae=function(e){ue--,(!e||ue<0||!e.target)&&(ue=0)},ge=function(e){return null==Z&&(Z="hidden"==_(A.body,"visibility")),Z||"hidden"!=_(e.parentNode,"visibility")&&"hidden"!=_(e,"visibility")},ze=function(e,t){var a,i=e,r=ge(e);for(V-=t,K+=t,X-=t,Y+=t;r&&(i=i.offsetParent)&&i!=A.body&&i!=v;)(r=0<(_(i,"opacity")||1))&&"visible"!=_(i,"overflow")&&(a=i.getBoundingClientRect(),r=Y>a.left&&X<a.right&&K>a.top-1&&V<a.bottom+1);return r},ve=function(){var e,t,a,i,r,n,o,s,l,d,c,u,f=g.elements;if((G=z.loadMode)&&ue<8&&(e=f.length)){for(t=0,fe++,d=!z.expand||z.expand<1?500<v.clientHeight&&500<v.clientWidth?500:370:z.expand,g._defEx=d,c=d*z.expFactor,u=z.hFac,Z=null,ce<c&&ue<1&&2<fe&&2<G&&!A.hidden?(ce=c,fe=0):ce=1<G&&1<fe&&ue<6?d:0;t<e;t++)if(f[t]&&!f[t]._lazyRace)if(de)if((s=f[t][h]("data-expand"))&&(n=1*s)||(n=ce),l!==n&&(q=innerWidth+n*u,j=innerHeight+n,o=-1*n,l=n),a=f[t].getBoundingClientRect(),(K=a.bottom)>=o&&(V=a.top)<=j&&(Y=a.right)>=o*u&&(X=a.left)<=q&&(K||Y||X||V)&&(z.loadHidden||ge(f[t]))&&(J&&ue<3&&!s&&(G<3||fe<4)||ze(f[t],n))){if(Ce(f[t]),r=!0,9<ue)break}else!r&&J&&!i&&ue<4&&fe<4&&2<G&&(I[0]||z.preloadAfterLoad)&&(I[0]||!s&&(K||Y||X||V||"auto"!=f[t][h](z.sizesAttr)))&&(i=I[0]||f[t]);else Ce(f[t]);i&&!r&&Ce(i)}},ee=ve,ae=0,ie=z.throttleDelay,re=z.ricTimeout,ne=function(){te=!1,ae=n.now(),ee()},oe=s&&49<re?function(){s(ne,{timeout:re}),re!==z.ricTimeout&&(re=z.ricTimeout)}:e(function(){u(ne)},!0),he=function(e){var t;(e=!0===e)&&(re=33),te||(te=!0,(t=ie-(n.now()-ae))<0&&(t=0),e||t<9?oe():u(oe,t))},pe=function(e){var t=e.target;t._lazyCache?delete t._lazyCache:(Ae(e),m(t,z.loadedClass),y(t,z.loadingClass),b(t,ye),w(t,"lazyloaded"))},me=e(pe),ye=function(e){me({target:e.target})},be=function(e){var t,a=e[h](z.srcsetAttr);(t=z.customMedia[e[h]("data-media")||e[h]("media")])&&e.setAttribute("media",t),a&&e.setAttribute("srcset",a)},we=e(function(e,t,a,i,r){var n,o,s,l,d,c;(d=w(e,"lazybeforeunveil",t)).defaultPrevented||(i&&(a?m(e,z.autosizesClass):e.setAttribute("sizes",i)),o=e[h](z.srcsetAttr),n=e[h](z.srcAttr),r&&(s=e.parentNode,l=s&&f.test(s.nodeName||"")),c=t.firesLoad||"src"in e&&(o||n||l),d={target:e},m(e,z.loadingClass),c&&(clearTimeout(O),O=u(Ae,2500),b(e,ye,!0)),l&&p.call(s.getElementsByTagName("source"),be),o?e.setAttribute("srcset",o):n&&!l&&(le.test(e.nodeName)?function(t,a){try{t.contentWindow.location.replace(a)}catch(e){t.src=a}}(e,n):e.src=n),r&&(o||l)&&C(e,{src:n})),e._lazyRace&&delete e._lazyRace,y(e,z.lazyClass),W(function(){(!c||e.complete&&1<e.naturalWidth)&&(pe(d),e._lazyCache=!0,u(function(){"_lazyCache"in e&&delete e._lazyCache},9))},!0)}),Ce=function(e){var t,a=se.test(e.nodeName),i=a&&(e[h](z.sizesAttr)||e[h]("sizes")),r="auto"==i;(!r&&J||!a||!e[h]("src")&&!e.srcset||e.complete||c(e,z.errorClass)||!c(e,z.lazyClass))&&(t=w(e,"lazyunveilread").detail,r&&k.updateElem(e,!0,e.offsetWidth),e._lazyRace=!0,ue++,we(e,t,r,i,a))},_e=function(){if(!J)if(n.now()-$<999)u(_e,999);else{var e=x(function(){z.loadMode=3,he()});J=!0,z.loadMode=3,he(),t("scroll",function(){3==z.loadMode&&(z.loadMode=2),e()},!0)}},{_:function(){$=n.now(),g.elements=A.getElementsByClassName(z.lazyClass),I=A.getElementsByClassName(z.lazyClass+" "+z.preloadClass),t("scroll",he,!0),t("resize",he,!0),i.MutationObserver?new MutationObserver(he).observe(v,{childList:!0,subtree:!0,attributes:!0}):(v[o]("DOMNodeInserted",he,!0),v[o]("DOMAttrModified",he,!0),setInterval(he,999)),t("hashchange",he,!0),["focus","mouseover","click","load","transitionend","animationend","webkitAnimationEnd"].forEach(function(e){A[o](e,he,!0)}),/d$|^c/.test(A.readyState)?_e():(t("load",_e),A[o]("DOMContentLoaded",he),u(_e,2e4)),g.elements.length?(ve(),W._lsFlush()):he()},checkElems:he,unveil:Ce}),k=(H=e(function(e,t,a,i){var r,n,o;if(e._lazysizesWidth=i,i+="px",e.setAttribute("sizes",i),f.test(t.nodeName||""))for(r=t.getElementsByTagName("source"),n=0,o=r.length;n<o;n++)r[n].setAttribute("sizes",i);a.detail.dataAttr||C(e,a.detail)}),U=function(e,t,a){var i,r=e.parentNode;r&&(a=E(e,r,a),(i=w(e,"lazybeforesizes",{width:a,dataAttr:!!t})).defaultPrevented||(a=i.detail.width)&&a!==e._lazysizesWidth&&H(e,r,i,a))},F=x(function(){var e,t=T.length;if(t)for(e=0;e<t;e++)U(T[e])}),{_:function(){T=A.getElementsByClassName(z.autosizesClass),t("resize",F)},checkElems:F,updateElem:U}),D=function(){D.i||(D.i=!0,k._(),P._())};var T,H,U,F;var I,J,O,G,$,q,j,V,X,Y,K,Z,ee,te,ae,ie,re,ne,oe,se,le,de,ce,ue,fe,Ae,ge,ze,ve,he,pe,me,ye,be,we,Ce,_e;return g={cfg:z,autoSizer:k,loader:P,init:D,uP:C,aC:m,rC:y,hC:c,fire:w,gW:E,rAF:W}}(e,e.document);e.lazySizes=a,"object"==typeof module&&module.exports&&(module.exports=a)}(window),lazysizesWebP("alpha",lazySizes.init),document.addEventListener("lazybeforeunveil",function(e){var t=e.target,a=t.getAttribute("data-srcset");if(!a&&t.naturalWidth&&1<t.naturalWidth&&1<t.naturalHeight){var i=window.devicePixelRatio||1,r=1.25*t.clientWidth<t.naturalWidth,n=1.25*t.clientHeight<t.naturalHeight;if(r||n){var o=Math.round(t.offsetWidth*i),s=Math.round(t.offsetHeight*i),l=t.getAttribute("data-src"),d=t.getAttribute("data-src-webp");ewww_webp_supported&&d&&-1==l.search("webp=1")&&(l=d);var c=constrainSrc(l,o,s,"img");c&&l!=c&&t.setAttribute("data-src",c)}}if(ewww_webp_supported){if(a&&-1<a.search("webp=1"))return;if(a){var u=t.getAttribute("data-srcset-webp");u&&t.setAttribute("data-srcset",u)}if((l=t.getAttribute("data-src"))&&-1<l.search("webp=1"))return;if(!(d=t.getAttribute("data-src-webp")))return;t.setAttribute("data-src",d)}}),function(e,t){var a=function(){t(e.lazySizes),e.removeEventListener("lazyunveilread",a,!0)};t=t.bind(null,e,e.document),"object"==typeof module&&module.exports?t(require("lazysizes")):e.lazySizes?a():e.addEventListener("lazyunveilread",a,!0)}(window,function(s,i,l){"use strict";var d,c;i.addEventListener&&(d=function(e,t){var a=i.createElement("img");a.onload=function(){a.onload=null,a.onerror=null,a=null,t()},a.onerror=a.onload,a.src=e,a&&a.complete&&a.onload&&a.onload()},addEventListener("lazybeforeunveil",function(e){var t,a,i;if(e.detail.instance==l&&!e.defaultPrevented){if("none"==e.target.preload&&(e.target.preload="auto"),t=e.target.getAttribute("data-bg")){ewww_webp_supported&&(a=e.target.getAttribute("data-bg-webp"))&&(t=a);var r=s.devicePixelRatio||1,n=Math.round(e.target.offsetWidth*r),o=Math.round(e.target.offsetHeight*r);t=s.lazySizes.hC(e.target,"wp-block-cover")?constrainSrc(t,n,o,"bg-cover"):constrainSrc(t,n,o,"bg"),e.detail.firesLoad=!0,d(t,function(){e.target.style.backgroundImage="url("+(c.test(t)?JSON.stringify(t):t)+")",e.detail.firesLoad=!1,l.fire(e.target,"_lazyloaded",{},!0,!0)})}(i=e.target.getAttribute("data-poster"))&&(e.detail.firesLoad=!0,d(i,function(){e.target.poster=i,e.detail.firesLoad=!1,l.fire(e.target,"_lazyloaded",{},!0,!0)}))}},!(c=/\(|\)|\s|'/)))});
1
+ var ewww_webp_supported=!1;function lazysizesWebP(e,t){var a=new Image;a.onload=function(){ewww_webp_supported=0<a.width&&0<a.height,t()},a.onerror=function(){t()},a.src="data:image/webp;base64,"+{alpha:"UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",animation:"UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"}[e]}function constrainSrc(e,t,a,i){if(null===e)return e;var r=/w=(\d+)/,n=/fit=(\d+),(\d+)/,o=/resize=(\d+),(\d+)/,s=decodeURIComponent(e);if(void 0===eio_lazy_vars)var eio_lazy_vars={exactdn_domain:".exactdn.com"};if(0<e.search("\\?")&&0<e.search(eio_lazy_vars.exactdn_domain)){var l=o.exec(s);if(l&&t<l[1])return s.replace(o,"resize="+t+","+a);var d=r.exec(e);if(d&&t<=d[1])return"bg-cover"===i?e.replace(r,"resize="+t+","+a):e.replace(r,"w="+t);var c=n.exec(s);if(c&&t<c[1])return"bg-cover"===i?e.replace(r,"resize="+t+","+a):s.replace(n,"fit="+t+","+a);if(!d&&!c&&!l)return"img"===i?e+"&fit="+t+","+a:"bg-cover"===i?e+"?resize="+t+","+a:t<a?e+"&h="+a:e+"&w="+t}return-1==e.search("\\?")&&0<e.search(eio_lazy_vars.exactdn_domain)?"img"===i?e+"?fit="+t+","+a:"bg-cover"===i?e+"?resize="+t+","+a:t<a?e+"?h="+a:e+"?w="+t:e}window.lazySizesConfig=window.lazySizesConfig||{},window.lazySizesConfig.init=!1,function(e,t){var a=function(i,A){"use strict";if(!A.getElementsByClassName)return;var g,z,v=A.documentElement,n=i.Date,r=i.HTMLPictureElement,o="addEventListener",h="getAttribute",t=i[o],u=i.setTimeout,a=i.requestAnimationFrame||u,s=i.requestIdleCallback,f=/^picture$/i,l=["load","error","lazyincluded","_lazyloaded"],d={},m=Array.prototype.forEach,c=function(e,t){return d[t]||(d[t]=new RegExp("(\\s|^)"+t+"(\\s|$)")),d[t].test(e[h]("class")||"")&&d[t]},p=function(e,t){c(e,t)||e.setAttribute("class",(e[h]("class")||"").trim()+" "+t)},y=function(e,t){var a;(a=c(e,t))&&e.setAttribute("class",(e[h]("class")||"").replace(a," "))},b=function(t,a,e){var i=e?o:"removeEventListener";e&&b(t,a),l.forEach(function(e){t[i](e,a)})},w=function(e,t,a,i,r){var n=A.createEvent("Event");return a||(a={}),a.instance=g,n.initEvent(t,!i,!r),n.detail=a,e.dispatchEvent(n),n},C=function(e,t){var a;!r&&(a=i.picturefill||z.pf)?(t&&t.src&&!e[h]("srcset")&&e.setAttribute("srcset",t.src),a({reevaluate:!0,elements:[e]})):t&&t.src&&(e.src=t.src)},_=function(e,t){return(getComputedStyle(e,null)||{})[t]},E=function(e,t,a){for(a=a||e.offsetWidth;a<z.minSize&&t&&!e._lazysizesWidth;)a=t.offsetWidth,t=t.parentNode;return a},x=(B=[],R=[],L=B,N=function(){var e=L;for(L=B.length?R:B,M=!(W=!0);e.length;)e.shift()();W=!1},Q=function(e,t){W&&!t?e.apply(this,arguments):(L.push(e),M||(M=!0,(A.hidden?u:a)(N)))},Q._lsFlush=N,Q),e=function(a,e){return e?function(){x(a)}:function(){var e=this,t=arguments;x(function(){a.apply(e,t)})}},S=function(e){var t,a,i=function(){t=null,e()},r=function(){var e=n.now()-a;e<99?u(r,99-e):(s||i)(i)};return function(){a=n.now(),t||(t=u(r,99))}};var W,M,B,R,L,N,Q;!function(){var e,t={lazyClass:"lazyload",loadedClass:"lazyloaded",loadingClass:"lazyloading",preloadClass:"lazypreload",errorClass:"lazyerror",autosizesClass:"lazyautosizes",srcAttr:"data-src",srcsetAttr:"data-srcset",sizesAttr:"data-sizes",minSize:40,customMedia:{},init:!0,expFactor:1.5,hFac:.8,loadMode:2,loadHidden:!0,ricTimeout:0,throttleDelay:125};for(e in z=i.lazySizesConfig||i.lazysizesConfig||{},t)e in z||(z[e]=t[e]);i.lazySizesConfig=z,u(function(){z.init&&D()})}();var P=(se=/^img$/i,le=/^iframe$/i,de="onscroll"in i&&!/(gle|ing)bot/.test(navigator.userAgent),ce=0,ue=0,fe=-1,Ae=function(e){ue--,(!e||ue<0||!e.target)&&(ue=0)},ge=function(e){return null==Z&&(Z="hidden"==_(A.body,"visibility")),Z||"hidden"!=_(e.parentNode,"visibility")&&"hidden"!=_(e,"visibility")},ze=function(e,t){var a,i=e,r=ge(e);for(V-=t,K+=t,X-=t,Y+=t;r&&(i=i.offsetParent)&&i!=A.body&&i!=v;)(r=0<(_(i,"opacity")||1))&&"visible"!=_(i,"overflow")&&(a=i.getBoundingClientRect(),r=Y>a.left&&X<a.right&&K>a.top-1&&V<a.bottom+1);return r},ve=function(){var e,t,a,i,r,n,o,s,l,d,c,u,f=g.elements;if((G=z.loadMode)&&ue<8&&(e=f.length)){for(t=0,fe++,d=!z.expand||z.expand<1?500<v.clientHeight&&500<v.clientWidth?500:370:z.expand,g._defEx=d,c=d*z.expFactor,u=z.hFac,Z=null,ce<c&&ue<1&&2<fe&&2<G&&!A.hidden?(ce=c,fe=0):ce=1<G&&1<fe&&ue<6?d:0;t<e;t++)if(f[t]&&!f[t]._lazyRace)if(de)if((s=f[t][h]("data-expand"))&&(n=1*s)||(n=ce),l!==n&&(q=innerWidth+n*u,j=innerHeight+n,o=-1*n,l=n),a=f[t].getBoundingClientRect(),(K=a.bottom)>=o&&(V=a.top)<=j&&(Y=a.right)>=o*u&&(X=a.left)<=q&&(K||Y||X||V)&&(z.loadHidden||ge(f[t]))&&(J&&ue<3&&!s&&(G<3||fe<4)||ze(f[t],n))){if(Ce(f[t]),r=!0,9<ue)break}else!r&&J&&!i&&ue<4&&fe<4&&2<G&&(I[0]||z.preloadAfterLoad)&&(I[0]||!s&&(K||Y||X||V||"auto"!=f[t][h](z.sizesAttr)))&&(i=I[0]||f[t]);else Ce(f[t]);i&&!r&&Ce(i)}},ee=ve,ae=0,ie=z.throttleDelay,re=z.ricTimeout,ne=function(){te=!1,ae=n.now(),ee()},oe=s&&49<re?function(){s(ne,{timeout:re}),re!==z.ricTimeout&&(re=z.ricTimeout)}:e(function(){u(ne)},!0),he=function(e){var t;(e=!0===e)&&(re=33),te||(te=!0,(t=ie-(n.now()-ae))<0&&(t=0),e||t<9?oe():u(oe,t))},me=function(e){var t=e.target;t._lazyCache?delete t._lazyCache:(Ae(e),p(t,z.loadedClass),y(t,z.loadingClass),b(t,ye),w(t,"lazyloaded"))},pe=e(me),ye=function(e){pe({target:e.target})},be=function(e){var t,a=e[h](z.srcsetAttr);(t=z.customMedia[e[h]("data-media")||e[h]("media")])&&e.setAttribute("media",t),a&&e.setAttribute("srcset",a)},we=e(function(e,t,a,i,r){var n,o,s,l,d,c;(d=w(e,"lazybeforeunveil",t)).defaultPrevented||(i&&(a?p(e,z.autosizesClass):e.setAttribute("sizes",i)),o=e[h](z.srcsetAttr),n=e[h](z.srcAttr),r&&(s=e.parentNode,l=s&&f.test(s.nodeName||"")),c=t.firesLoad||"src"in e&&(o||n||l),d={target:e},p(e,z.loadingClass),c&&(clearTimeout(O),O=u(Ae,2500),b(e,ye,!0)),l&&m.call(s.getElementsByTagName("source"),be),o?e.setAttribute("srcset",o):n&&!l&&(le.test(e.nodeName)?function(t,a){try{t.contentWindow.location.replace(a)}catch(e){t.src=a}}(e,n):e.src=n),r&&(o||l)&&C(e,{src:n})),e._lazyRace&&delete e._lazyRace,y(e,z.lazyClass),x(function(){(!c||e.complete&&1<e.naturalWidth)&&(me(d),e._lazyCache=!0,u(function(){"_lazyCache"in e&&delete e._lazyCache},9))},!0)}),Ce=function(e){var t,a=se.test(e.nodeName),i=a&&(e[h](z.sizesAttr)||e[h]("sizes")),r="auto"==i;(!r&&J||!a||!e[h]("src")&&!e.srcset||e.complete||c(e,z.errorClass)||!c(e,z.lazyClass))&&(t=w(e,"lazyunveilread").detail,r&&k.updateElem(e,!0,e.offsetWidth),e._lazyRace=!0,ue++,we(e,t,r,i,a))},_e=function(){if(!J)if(n.now()-$<999)u(_e,999);else{var e=S(function(){z.loadMode=3,he()});J=!0,z.loadMode=3,he(),t("scroll",function(){3==z.loadMode&&(z.loadMode=2),e()},!0)}},{_:function(){$=n.now(),g.elements=A.getElementsByClassName(z.lazyClass),I=A.getElementsByClassName(z.lazyClass+" "+z.preloadClass),t("scroll",he,!0),t("resize",he,!0),i.MutationObserver?new MutationObserver(he).observe(v,{childList:!0,subtree:!0,attributes:!0}):(v[o]("DOMNodeInserted",he,!0),v[o]("DOMAttrModified",he,!0),setInterval(he,999)),t("hashchange",he,!0),["focus","mouseover","click","load","transitionend","animationend","webkitAnimationEnd"].forEach(function(e){A[o](e,he,!0)}),/d$|^c/.test(A.readyState)?_e():(t("load",_e),A[o]("DOMContentLoaded",he),u(_e,2e4)),g.elements.length?(ve(),x._lsFlush()):he()},checkElems:he,unveil:Ce}),k=(H=e(function(e,t,a,i){var r,n,o;if(e._lazysizesWidth=i,i+="px",e.setAttribute("sizes",i),f.test(t.nodeName||""))for(r=t.getElementsByTagName("source"),n=0,o=r.length;n<o;n++)r[n].setAttribute("sizes",i);a.detail.dataAttr||C(e,a.detail)}),U=function(e,t,a){var i,r=e.parentNode;r&&(a=E(e,r,a),(i=w(e,"lazybeforesizes",{width:a,dataAttr:!!t})).defaultPrevented||(a=i.detail.width)&&a!==e._lazysizesWidth&&H(e,r,i,a))},F=S(function(){var e,t=T.length;if(t)for(e=0;e<t;e++)U(T[e])}),{_:function(){T=A.getElementsByClassName(z.autosizesClass),t("resize",F)},checkElems:F,updateElem:U}),D=function(){D.i||(D.i=!0,k._(),P._())};var T,H,U,F;var I,J,O,G,$,q,j,V,X,Y,K,Z,ee,te,ae,ie,re,ne,oe,se,le,de,ce,ue,fe,Ae,ge,ze,ve,he,me,pe,ye,be,we,Ce,_e;return g={cfg:z,autoSizer:k,loader:P,init:D,uP:C,aC:p,rC:y,hC:c,fire:w,gW:E,rAF:x}}(e,e.document);e.lazySizes=a,"object"==typeof module&&module.exports&&(module.exports=a)}(window),lazysizesWebP("alpha",lazySizes.init),document.addEventListener("lazybeforeunveil",function(e){var t=e.target,a=t.getAttribute("data-srcset");if(!a&&t.naturalWidth&&1<t.naturalWidth&&1<t.naturalHeight){var i=window.devicePixelRatio||1,r=1.25*t.clientWidth<t.naturalWidth,n=1.25*t.clientHeight<t.naturalHeight;if(r||n){var o=Math.round(t.offsetWidth*i),s=Math.round(t.offsetHeight*i),l=t.getAttribute("data-src"),d=t.getAttribute("data-src-webp");ewww_webp_supported&&d&&-1==l.search("webp=1")&&(l=d);var c=constrainSrc(l,o,s,"img");c&&l!=c&&t.setAttribute("data-src",c)}}if(ewww_webp_supported){if(a&&-1<a.search("webp=1"))return;if(a){var u=t.getAttribute("data-srcset-webp");u&&t.setAttribute("data-srcset",u)}if((l=t.getAttribute("data-src"))&&-1<l.search("webp=1"))return;if(!(d=t.getAttribute("data-src-webp")))return;t.setAttribute("data-src",d)}}),function(e,t){var a=function(){t(e.lazySizes),e.removeEventListener("lazyunveilread",a,!0)};t=t.bind(null,e,e.document),"object"==typeof module&&module.exports?t(require("lazysizes")):e.lazySizes?a():e.addEventListener("lazyunveilread",a,!0)}(window,function(s,i,l){"use strict";var d,c;i.addEventListener&&(d=function(e,t){var a=i.createElement("img");a.onload=function(){a.onload=null,a.onerror=null,a=null,t()},a.onerror=a.onload,a.src=e,a&&a.complete&&a.onload&&a.onload()},addEventListener("lazybeforeunveil",function(e){var t,a,i;if(e.detail.instance==l&&!e.defaultPrevented){if("none"==e.target.preload&&(e.target.preload="auto"),t=e.target.getAttribute("data-bg")){ewww_webp_supported&&(a=e.target.getAttribute("data-bg-webp"))&&(t=a);var r=s.devicePixelRatio||1,n=Math.round(e.target.offsetWidth*r),o=Math.round(e.target.offsetHeight*r);t=s.lazySizes.hC(e.target,"wp-block-cover")?constrainSrc(t,n,o,"bg-cover"):s.lazySizes.hC(e.target,"elementor-bg")?constrainSrc(t,n,o,"bg-cover"):constrainSrc(t,n,o,"bg"),e.detail.firesLoad=!0,d(t,function(){e.target.style.backgroundImage="url("+(c.test(t)?JSON.stringify(t):t)+")",e.detail.firesLoad=!1,l.fire(e.target,"_lazyloaded",{},!0,!0)})}(i=e.target.getAttribute("data-poster"))&&(e.detail.firesLoad=!0,d(i,function(){e.target.poster=i,e.detail.firesLoad=!1,l.fire(e.target,"_lazyloaded",{},!0,!0)}))}},!(c=/\(|\)|\s|'/)))});
includes/load_webp.js CHANGED
@@ -527,7 +527,9 @@ function ewwwLoadImages(ewww_webp_supported) {
527
  }
528
  var lazies = document.querySelectorAll('img.ewww_webp_lazy_load');
529
  for (var i = 0, len = lazies.length; i < len; i++){
 
530
  if (ewww_webp_supported) {
 
531
  ewwwAttr(lazies[i], 'data-lazy-srcset', lazies[i].getAttribute('data-lazy-srcset-webp'));
532
  ewwwAttr(lazies[i], 'data-srcset', lazies[i].getAttribute('data-srcset-webp'));
533
  ewwwAttr(lazies[i], 'data-lazy-src', lazies[i].getAttribute('data-lazy-src-webp'));
@@ -589,6 +591,9 @@ function ewwwWebPInit(ewww_webp_supported) {
589
  document.arrive('.ewww_webp', function() {
590
  ewwwLoadImages(ewww_webp_supported);
591
  });
 
 
 
592
  var ewww_ngg_galleries_timer = 0;
593
  var ewww_ngg_galleries = setInterval(function() {
594
  if ( typeof galleries !== 'undefined' ) {
527
  }
528
  var lazies = document.querySelectorAll('img.ewww_webp_lazy_load');
529
  for (var i = 0, len = lazies.length; i < len; i++){
530
+ console.log('parsing an image: ' + lazies[i].getAttribute('src'));
531
  if (ewww_webp_supported) {
532
+ console.log('webp good');
533
  ewwwAttr(lazies[i], 'data-lazy-srcset', lazies[i].getAttribute('data-lazy-srcset-webp'));
534
  ewwwAttr(lazies[i], 'data-srcset', lazies[i].getAttribute('data-srcset-webp'));
535
  ewwwAttr(lazies[i], 'data-lazy-src', lazies[i].getAttribute('data-lazy-src-webp'));
591
  document.arrive('.ewww_webp', function() {
592
  ewwwLoadImages(ewww_webp_supported);
593
  });
594
+ document.arrive('.ewww_webp_lazy_load', function() {
595
+ ewwwLoadImages(ewww_webp_supported);
596
+ });
597
  var ewww_ngg_galleries_timer = 0;
598
  var ewww_ngg_galleries = setInterval(function() {
599
  if ( typeof galleries !== 'undefined' ) {
includes/load_webp.min.js CHANGED
@@ -1 +1 @@
1
- var Arrive=function(c,e,d){"use strict";if(c.MutationObserver&&"undefined"!=typeof HTMLElement){var r,t,a=0,u=(r=HTMLElement.prototype.matches||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector,{matchesSelector:function(e,t){return e instanceof HTMLElement&&r.call(e,t)},addMethod:function(e,t,r){var a=e[t];e[t]=function(){return r.length==arguments.length?r.apply(this,arguments):"function"==typeof a?a.apply(this,arguments):void 0}},callCallbacks:function(e,t){t&&t.options.onceOnly&&1==t.firedElems.length&&(e=[e[0]]);for(var r,a=0;r=e[a];a++)r&&r.callback&&r.callback.call(r.elem,r.elem);t&&t.options.onceOnly&&1==t.firedElems.length&&t.me.unbindEventWithSelectorAndCallback.call(t.target,t.selector,t.callback)},checkChildNodesRecursively:function(e,t,r,a){for(var i,n=0;i=e[n];n++)r(i,t,a)&&a.push({callback:t.callback,elem:i}),0<i.childNodes.length&&u.checkChildNodesRecursively(i.childNodes,t,r,a)},mergeArrays:function(e,t){var r,a={};for(r in e)e.hasOwnProperty(r)&&(a[r]=e[r]);for(r in t)t.hasOwnProperty(r)&&(a[r]=t[r]);return a},toElementsArray:function(e){return void 0===e||"number"==typeof e.length&&e!==c||(e=[e]),e}}),w=((t=function(){this._eventsBucket=[],this._beforeAdding=null,this._beforeRemoving=null}).prototype.addEvent=function(e,t,r,a){var i={target:e,selector:t,options:r,callback:a,firedElems:[]};return this._beforeAdding&&this._beforeAdding(i),this._eventsBucket.push(i),i},t.prototype.removeEvent=function(e){for(var t,r=this._eventsBucket.length-1;t=this._eventsBucket[r];r--)if(e(t)){this._beforeRemoving&&this._beforeRemoving(t);var a=this._eventsBucket.splice(r,1);a&&a.length&&(a[0].callback=null)}},t.prototype.beforeAdding=function(e){this._beforeAdding=e},t.prototype.beforeRemoving=function(e){this._beforeRemoving=e},t),l=function(i,n){var l=new w,o=this,s={fireOnAttributesModification:!1};return l.beforeAdding(function(t){var e,r=t.target;r!==c.document&&r!==c||(r=document.getElementsByTagName("html")[0]),e=new MutationObserver(function(e){n.call(this,e,t)});var a=i(t.options);e.observe(r,a),t.observer=e,t.me=o}),l.beforeRemoving(function(e){e.observer.disconnect()}),this.bindEvent=function(e,t,r){t=u.mergeArrays(s,t);for(var a=u.toElementsArray(this),i=0;i<a.length;i++)l.addEvent(a[i],e,t,r)},this.unbindEvent=function(){var r=u.toElementsArray(this);l.removeEvent(function(e){for(var t=0;t<r.length;t++)if(this===d||e.target===r[t])return!0;return!1})},this.unbindEventWithSelectorOrCallback=function(r){var e,a=u.toElementsArray(this),i=r;e="function"==typeof r?function(e){for(var t=0;t<a.length;t++)if((this===d||e.target===a[t])&&e.callback===i)return!0;return!1}:function(e){for(var t=0;t<a.length;t++)if((this===d||e.target===a[t])&&e.selector===r)return!0;return!1},l.removeEvent(e)},this.unbindEventWithSelectorAndCallback=function(r,a){var i=u.toElementsArray(this);l.removeEvent(function(e){for(var t=0;t<i.length;t++)if((this===d||e.target===i[t])&&e.selector===r&&e.callback===a)return!0;return!1})},this},i=new function(){var s={fireOnAttributesModification:!1,onceOnly:!1,existing:!1};function n(e,t,r){return!(!u.matchesSelector(e,t.selector)||(e._id===d&&(e._id=a++),-1!=t.firedElems.indexOf(e._id))||(t.firedElems.push(e._id),0))}var c=(i=new l(function(e){var t={attributes:!1,childList:!0,subtree:!0};return e.fireOnAttributesModification&&(t.attributes=!0),t},function(e,i){e.forEach(function(e){var t=e.addedNodes,r=e.target,a=[];null!==t&&0<t.length?u.checkChildNodesRecursively(t,i,n,a):"attributes"===e.type&&n(r,i)&&a.push({callback:i.callback,elem:r}),u.callCallbacks(a,i)})})).bindEvent;return i.bindEvent=function(e,t,r){t=void 0===r?(r=t,s):u.mergeArrays(s,t);var a=u.toElementsArray(this);if(t.existing){for(var i=[],n=0;n<a.length;n++)for(var l=a[n].querySelectorAll(e),o=0;o<l.length;o++)i.push({callback:r,elem:l[o]});if(t.onceOnly&&i.length)return r.call(i[0].elem,i[0].elem);setTimeout(u.callCallbacks,1,i)}c.call(this,e,t,r)},i},o=new function(){var a={};function i(e,t){return u.matchesSelector(e,t.selector)}var n=(o=new l(function(){return{childList:!0,subtree:!0}},function(e,a){e.forEach(function(e){var t=e.removedNodes,r=[];null!==t&&0<t.length&&u.checkChildNodesRecursively(t,a,i,r),u.callCallbacks(r,a)})})).bindEvent;return o.bindEvent=function(e,t,r){t=void 0===r?(r=t,a):u.mergeArrays(a,t),n.call(this,e,t,r)},o};e&&g(e.fn),g(HTMLElement.prototype),g(NodeList.prototype),g(HTMLCollection.prototype),g(HTMLDocument.prototype),g(Window.prototype);var n={};return s(i,n,"unbindAllArrive"),s(o,n,"unbindAllLeave"),n}function s(e,t,r){u.addMethod(t,r,e.unbindEvent),u.addMethod(t,r,e.unbindEventWithSelectorOrCallback),u.addMethod(t,r,e.unbindEventWithSelectorAndCallback)}function g(e){e.arrive=i.bindEvent,s(i,e,"unbindArrive"),e.leave=o.bindEvent,s(o,e,"unbindLeave")}}(window,"undefined"==typeof jQuery?null:jQuery,void 0);function check_webp_feature(e,t){var r=new Image;r.onload=function(){var e=0<r.width&&0<r.height;t(e)},r.onerror=function(){t(!1)},r.src="data:image/webp;base64,"+{alpha:"UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",animation:"UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"}[e]}function ewwwLoadImages(e){var n="data-";function t(e,t){for(var r=["align","alt","border","crossorigin","height","hspace","ismap","longdesc","usemap","vspace","width","accesskey","class","contenteditable","contextmenu","dir","draggable","dropzone","hidden","id","lang","spellcheck","style","tabindex","title","translate","sizes","data-caption","data-attachment-id","data-permalink","data-orig-size","data-comments-opened","data-image-meta","data-image-title","data-image-description","data-event-trigger","data-highlight-color","data-highlight-opacity","data-highlight-border-color","data-highlight-border-width","data-highlight-border-opacity","data-no-lazy","data-lazy","data-large_image_width","data-large_image_height"],a=0,i=r.length;a<i;a++)ewwwAttr(t,r[a],e.getAttribute(n+r[a]));return t}if(e){for(var r=document.querySelectorAll(".batch-image img, .image-wrapper a, .ngg-pro-masonry-item a, .ngg-galleria-offscreen-seo-wrapper a"),a=0,i=r.length;a<i;a++)ewwwAttr(r[a],"data-src",r[a].getAttribute("data-webp")),ewwwAttr(r[a],"data-thumbnail",r[a].getAttribute("data-webp-thumbnail"));for(a=0,i=(o=document.querySelectorAll(".rev_slider ul li")).length;a<i;a++){ewwwAttr(o[a],"data-thumb",o[a].getAttribute("data-webp-thumb"));for(var l=1;l<11;)ewwwAttr(o[a],"data-param"+l,o[a].getAttribute("data-webp-param"+l)),l++}var o;for(a=0,i=(o=document.querySelectorAll(".rev_slider img")).length;a<i;a++)ewwwAttr(o[a],"data-lazyload",o[a].getAttribute("data-webp-lazyload"));var s=document.querySelectorAll("div.woocommerce-product-gallery__image");for(a=0,i=s.length;a<i;a++)ewwwAttr(s[a],"data-thumb",s[a].getAttribute("data-webp-thumb"))}var c=document.querySelectorAll("videos");for(a=0,i=c.length;a<i;a++)ewwwAttr(c[a],"poster",e?c[a].getAttribute("data-poster-webp"):c[a].getAttribute("data-poster-image"));var d=document.querySelectorAll("img.ewww_webp_lazy_load");for(a=0,i=d.length;a<i;a++){if(e){ewwwAttr(d[a],"data-lazy-srcset",d[a].getAttribute("data-lazy-srcset-webp")),ewwwAttr(d[a],"data-srcset",d[a].getAttribute("data-srcset-webp")),ewwwAttr(d[a],"data-lazy-src",d[a].getAttribute("data-lazy-src-webp")),ewwwAttr(d[a],"data-src",d[a].getAttribute("data-src-webp")),ewwwAttr(d[a],"data-orig-file",d[a].getAttribute("data-webp-orig-file")),ewwwAttr(d[a],"data-medium-file",d[a].getAttribute("data-webp-medium-file")),ewwwAttr(d[a],"data-large-file",d[a].getAttribute("data-webp-large-file"));var u=d[a].getAttribute("srcset");null!=u&&!1!==u&&u.includes("R0lGOD")&&ewwwAttr(d[a],"src",d[a].getAttribute("data-lazy-src-webp"))}d[a].className=d[a].className.replace(/\bewww_webp_lazy_load\b/,"")}var w=document.querySelectorAll(".ewww_webp");for(a=0,i=w.length;a<i;a++){var g=document.createElement("img");e?(ewwwAttr(g,"src",w[a].getAttribute("data-webp")),ewwwAttr(g,"srcset",w[a].getAttribute("data-srcset-webp")),ewwwAttr(g,"data-orig-file",w[a].getAttribute("data-orig-file")),ewwwAttr(g,"data-orig-file",w[a].getAttribute("data-webp-orig-file")),ewwwAttr(g,"data-medium-file",w[a].getAttribute("data-medium-file")),ewwwAttr(g,"data-medium-file",w[a].getAttribute("data-webp-medium-file")),ewwwAttr(g,"data-large-file",w[a].getAttribute("data-large-file")),ewwwAttr(g,"data-large-file",w[a].getAttribute("data-webp-large-file")),ewwwAttr(g,"data-large_image",w[a].getAttribute("data-large_image")),ewwwAttr(g,"data-large_image",w[a].getAttribute("data-webp-large_image")),ewwwAttr(g,"data-src",w[a].getAttribute("data-src")),ewwwAttr(g,"data-src",w[a].getAttribute("data-webp-src"))):(ewwwAttr(g,"src",w[a].getAttribute("data-img")),ewwwAttr(g,"srcset",w[a].getAttribute("data-srcset-img")),ewwwAttr(g,"data-orig-file",w[a].getAttribute("data-orig-file")),ewwwAttr(g,"data-medium-file",w[a].getAttribute("data-medium-file")),ewwwAttr(g,"data-large-file",w[a].getAttribute("data-large-file")),ewwwAttr(g,"data-large_image",w[a].getAttribute("data-large_image")),ewwwAttr(g,"data-src",w[a].getAttribute("data-src"))),g=t(w[a],g),w[a].parentNode.insertBefore(g,w[a].nextSibling),w[a].className=w[a].className.replace(/\bewww_webp\b/,"")}window.jQuery&&jQuery.fn.isotope&&jQuery.fn.imagesLoaded&&(jQuery(".fusion-posts-container-infinite").imagesLoaded(function(){jQuery(".fusion-posts-container-infinite").hasClass("isotope")&&jQuery(".fusion-posts-container-infinite").isotope()}),jQuery(".fusion-portfolio:not(.fusion-recent-works) .fusion-portfolio-wrapper").imagesLoaded(function(){jQuery(".fusion-portfolio:not(.fusion-recent-works) .fusion-portfolio-wrapper").isotope()}))}function ewwwWebPInit(e){ewwwLoadImages(e),ewwwNggLoadGalleries(e),document.arrive(".ewww_webp",function(){ewwwLoadImages(e)});var t=0,r=setInterval(function(){"undefined"!=typeof galleries&&(ewwwNggParseGalleries(e),clearInterval(r)),1e3<(t+=25)&&clearInterval(r)},25)}function ewwwAttr(e,t,r){null!=r&&!1!==r&&e.setAttribute(t,r)}function ewwwNggParseGalleries(e){if(e)for(var t in galleries){var r=galleries[t];galleries[t].images_list=ewwwNggParseImageList(r.images_list)}}function ewwwNggLoadGalleries(e){e&&document.addEventListener("ngg.galleria.themeadded",function(e,t){window.ngg_galleria._create_backup=window.ngg_galleria.create,window.ngg_galleria.create=function(e,t){var r=$(e).data("id");return galleries["gallery_"+r].images_list=ewwwNggParseImageList(galleries["gallery_"+r].images_list),window.ngg_galleria._create_backup(e,t)}})}function ewwwNggParseImageList(e){for(var t in console.log("parsing gallery images"),e){var r=e[t];if(void 0!==r["image-webp"]&&(e[t].image=r["image-webp"],delete e[t]["image-webp"]),void 0!==r["thumb-webp"]&&(e[t].thumb=r["thumb-webp"],delete e[t]["thumb-webp"]),void 0!==r.full_image_webp&&(e[t].full_image=r.full_image_webp,delete e[t].full_image_webp),void 0!==r.srcsets)for(var a in r.srcsets)nggSrcset=r.srcsets[a],void 0!==r.srcsets[a+"-webp"]&&(e[t].srcsets[a]=r.srcsets[a+"-webp"],delete e[t].srcsets[a+"-webp"]);if(void 0!==r.full_srcsets)for(var i in r.full_srcsets)nggFSrcset=r.full_srcsets[i],void 0!==r.full_srcsets[i+"-webp"]&&(e[t].full_srcsets[i]=r.full_srcsets[i+"-webp"],delete e[t].full_srcsets[i+"-webp"])}return e}check_webp_feature("alpha",ewwwWebPInit);
1
+ var Arrive=function(c,e,d){"use strict";if(c.MutationObserver&&"undefined"!=typeof HTMLElement){var r,t,a=0,u=(r=HTMLElement.prototype.matches||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector,{matchesSelector:function(e,t){return e instanceof HTMLElement&&r.call(e,t)},addMethod:function(e,t,r){var a=e[t];e[t]=function(){return r.length==arguments.length?r.apply(this,arguments):"function"==typeof a?a.apply(this,arguments):void 0}},callCallbacks:function(e,t){t&&t.options.onceOnly&&1==t.firedElems.length&&(e=[e[0]]);for(var r,a=0;r=e[a];a++)r&&r.callback&&r.callback.call(r.elem,r.elem);t&&t.options.onceOnly&&1==t.firedElems.length&&t.me.unbindEventWithSelectorAndCallback.call(t.target,t.selector,t.callback)},checkChildNodesRecursively:function(e,t,r,a){for(var i,n=0;i=e[n];n++)r(i,t,a)&&a.push({callback:t.callback,elem:i}),0<i.childNodes.length&&u.checkChildNodesRecursively(i.childNodes,t,r,a)},mergeArrays:function(e,t){var r,a={};for(r in e)e.hasOwnProperty(r)&&(a[r]=e[r]);for(r in t)t.hasOwnProperty(r)&&(a[r]=t[r]);return a},toElementsArray:function(e){return void 0===e||"number"==typeof e.length&&e!==c||(e=[e]),e}}),w=((t=function(){this._eventsBucket=[],this._beforeAdding=null,this._beforeRemoving=null}).prototype.addEvent=function(e,t,r,a){var i={target:e,selector:t,options:r,callback:a,firedElems:[]};return this._beforeAdding&&this._beforeAdding(i),this._eventsBucket.push(i),i},t.prototype.removeEvent=function(e){for(var t,r=this._eventsBucket.length-1;t=this._eventsBucket[r];r--)if(e(t)){this._beforeRemoving&&this._beforeRemoving(t);var a=this._eventsBucket.splice(r,1);a&&a.length&&(a[0].callback=null)}},t.prototype.beforeAdding=function(e){this._beforeAdding=e},t.prototype.beforeRemoving=function(e){this._beforeRemoving=e},t),l=function(i,n){var l=new w,o=this,s={fireOnAttributesModification:!1};return l.beforeAdding(function(t){var e,r=t.target;r!==c.document&&r!==c||(r=document.getElementsByTagName("html")[0]),e=new MutationObserver(function(e){n.call(this,e,t)});var a=i(t.options);e.observe(r,a),t.observer=e,t.me=o}),l.beforeRemoving(function(e){e.observer.disconnect()}),this.bindEvent=function(e,t,r){t=u.mergeArrays(s,t);for(var a=u.toElementsArray(this),i=0;i<a.length;i++)l.addEvent(a[i],e,t,r)},this.unbindEvent=function(){var r=u.toElementsArray(this);l.removeEvent(function(e){for(var t=0;t<r.length;t++)if(this===d||e.target===r[t])return!0;return!1})},this.unbindEventWithSelectorOrCallback=function(r){var e,a=u.toElementsArray(this),i=r;e="function"==typeof r?function(e){for(var t=0;t<a.length;t++)if((this===d||e.target===a[t])&&e.callback===i)return!0;return!1}:function(e){for(var t=0;t<a.length;t++)if((this===d||e.target===a[t])&&e.selector===r)return!0;return!1},l.removeEvent(e)},this.unbindEventWithSelectorAndCallback=function(r,a){var i=u.toElementsArray(this);l.removeEvent(function(e){for(var t=0;t<i.length;t++)if((this===d||e.target===i[t])&&e.selector===r&&e.callback===a)return!0;return!1})},this},i=new function(){var s={fireOnAttributesModification:!1,onceOnly:!1,existing:!1};function n(e,t,r){return!(!u.matchesSelector(e,t.selector)||(e._id===d&&(e._id=a++),-1!=t.firedElems.indexOf(e._id))||(t.firedElems.push(e._id),0))}var c=(i=new l(function(e){var t={attributes:!1,childList:!0,subtree:!0};return e.fireOnAttributesModification&&(t.attributes=!0),t},function(e,i){e.forEach(function(e){var t=e.addedNodes,r=e.target,a=[];null!==t&&0<t.length?u.checkChildNodesRecursively(t,i,n,a):"attributes"===e.type&&n(r,i)&&a.push({callback:i.callback,elem:r}),u.callCallbacks(a,i)})})).bindEvent;return i.bindEvent=function(e,t,r){t=void 0===r?(r=t,s):u.mergeArrays(s,t);var a=u.toElementsArray(this);if(t.existing){for(var i=[],n=0;n<a.length;n++)for(var l=a[n].querySelectorAll(e),o=0;o<l.length;o++)i.push({callback:r,elem:l[o]});if(t.onceOnly&&i.length)return r.call(i[0].elem,i[0].elem);setTimeout(u.callCallbacks,1,i)}c.call(this,e,t,r)},i},o=new function(){var a={};function i(e,t){return u.matchesSelector(e,t.selector)}var n=(o=new l(function(){return{childList:!0,subtree:!0}},function(e,a){e.forEach(function(e){var t=e.removedNodes,r=[];null!==t&&0<t.length&&u.checkChildNodesRecursively(t,a,i,r),u.callCallbacks(r,a)})})).bindEvent;return o.bindEvent=function(e,t,r){t=void 0===r?(r=t,a):u.mergeArrays(a,t),n.call(this,e,t,r)},o};e&&g(e.fn),g(HTMLElement.prototype),g(NodeList.prototype),g(HTMLCollection.prototype),g(HTMLDocument.prototype),g(Window.prototype);var n={};return s(i,n,"unbindAllArrive"),s(o,n,"unbindAllLeave"),n}function s(e,t,r){u.addMethod(t,r,e.unbindEvent),u.addMethod(t,r,e.unbindEventWithSelectorOrCallback),u.addMethod(t,r,e.unbindEventWithSelectorAndCallback)}function g(e){e.arrive=i.bindEvent,s(i,e,"unbindArrive"),e.leave=o.bindEvent,s(o,e,"unbindLeave")}}(window,"undefined"==typeof jQuery?null:jQuery,void 0);function check_webp_feature(e,t){var r=new Image;r.onload=function(){var e=0<r.width&&0<r.height;t(e)},r.onerror=function(){t(!1)},r.src="data:image/webp;base64,"+{alpha:"UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",animation:"UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"}[e]}function ewwwLoadImages(e){var n="data-";function t(e,t){for(var r=["align","alt","border","crossorigin","height","hspace","ismap","longdesc","usemap","vspace","width","accesskey","class","contenteditable","contextmenu","dir","draggable","dropzone","hidden","id","lang","spellcheck","style","tabindex","title","translate","sizes","data-caption","data-attachment-id","data-permalink","data-orig-size","data-comments-opened","data-image-meta","data-image-title","data-image-description","data-event-trigger","data-highlight-color","data-highlight-opacity","data-highlight-border-color","data-highlight-border-width","data-highlight-border-opacity","data-no-lazy","data-lazy","data-large_image_width","data-large_image_height"],a=0,i=r.length;a<i;a++)ewwwAttr(t,r[a],e.getAttribute(n+r[a]));return t}if(e){for(var r=document.querySelectorAll(".batch-image img, .image-wrapper a, .ngg-pro-masonry-item a, .ngg-galleria-offscreen-seo-wrapper a"),a=0,i=r.length;a<i;a++)ewwwAttr(r[a],"data-src",r[a].getAttribute("data-webp")),ewwwAttr(r[a],"data-thumbnail",r[a].getAttribute("data-webp-thumbnail"));for(a=0,i=(o=document.querySelectorAll(".rev_slider ul li")).length;a<i;a++){ewwwAttr(o[a],"data-thumb",o[a].getAttribute("data-webp-thumb"));for(var l=1;l<11;)ewwwAttr(o[a],"data-param"+l,o[a].getAttribute("data-webp-param"+l)),l++}var o;for(a=0,i=(o=document.querySelectorAll(".rev_slider img")).length;a<i;a++)ewwwAttr(o[a],"data-lazyload",o[a].getAttribute("data-webp-lazyload"));var s=document.querySelectorAll("div.woocommerce-product-gallery__image");for(a=0,i=s.length;a<i;a++)ewwwAttr(s[a],"data-thumb",s[a].getAttribute("data-webp-thumb"))}var c=document.querySelectorAll("videos");for(a=0,i=c.length;a<i;a++)ewwwAttr(c[a],"poster",e?c[a].getAttribute("data-poster-webp"):c[a].getAttribute("data-poster-image"));var d=document.querySelectorAll("img.ewww_webp_lazy_load");for(a=0,i=d.length;a<i;a++){if(e){ewwwAttr(d[a],"data-lazy-srcset",d[a].getAttribute("data-lazy-srcset-webp")),ewwwAttr(d[a],"data-srcset",d[a].getAttribute("data-srcset-webp")),ewwwAttr(d[a],"data-lazy-src",d[a].getAttribute("data-lazy-src-webp")),ewwwAttr(d[a],"data-src",d[a].getAttribute("data-src-webp")),ewwwAttr(d[a],"data-orig-file",d[a].getAttribute("data-webp-orig-file")),ewwwAttr(d[a],"data-medium-file",d[a].getAttribute("data-webp-medium-file")),ewwwAttr(d[a],"data-large-file",d[a].getAttribute("data-webp-large-file"));var u=d[a].getAttribute("srcset");null!=u&&!1!==u&&u.includes("R0lGOD")&&ewwwAttr(d[a],"src",d[a].getAttribute("data-lazy-src-webp"))}d[a].className=d[a].className.replace(/\bewww_webp_lazy_load\b/,"")}var w=document.querySelectorAll(".ewww_webp");for(a=0,i=w.length;a<i;a++){var g=document.createElement("img");e?(ewwwAttr(g,"src",w[a].getAttribute("data-webp")),ewwwAttr(g,"srcset",w[a].getAttribute("data-srcset-webp")),ewwwAttr(g,"data-orig-file",w[a].getAttribute("data-orig-file")),ewwwAttr(g,"data-orig-file",w[a].getAttribute("data-webp-orig-file")),ewwwAttr(g,"data-medium-file",w[a].getAttribute("data-medium-file")),ewwwAttr(g,"data-medium-file",w[a].getAttribute("data-webp-medium-file")),ewwwAttr(g,"data-large-file",w[a].getAttribute("data-large-file")),ewwwAttr(g,"data-large-file",w[a].getAttribute("data-webp-large-file")),ewwwAttr(g,"data-large_image",w[a].getAttribute("data-large_image")),ewwwAttr(g,"data-large_image",w[a].getAttribute("data-webp-large_image")),ewwwAttr(g,"data-src",w[a].getAttribute("data-src")),ewwwAttr(g,"data-src",w[a].getAttribute("data-webp-src"))):(ewwwAttr(g,"src",w[a].getAttribute("data-img")),ewwwAttr(g,"srcset",w[a].getAttribute("data-srcset-img")),ewwwAttr(g,"data-orig-file",w[a].getAttribute("data-orig-file")),ewwwAttr(g,"data-medium-file",w[a].getAttribute("data-medium-file")),ewwwAttr(g,"data-large-file",w[a].getAttribute("data-large-file")),ewwwAttr(g,"data-large_image",w[a].getAttribute("data-large_image")),ewwwAttr(g,"data-src",w[a].getAttribute("data-src"))),g=t(w[a],g),w[a].parentNode.insertBefore(g,w[a].nextSibling),w[a].className=w[a].className.replace(/\bewww_webp\b/,"")}window.jQuery&&jQuery.fn.isotope&&jQuery.fn.imagesLoaded&&(jQuery(".fusion-posts-container-infinite").imagesLoaded(function(){jQuery(".fusion-posts-container-infinite").hasClass("isotope")&&jQuery(".fusion-posts-container-infinite").isotope()}),jQuery(".fusion-portfolio:not(.fusion-recent-works) .fusion-portfolio-wrapper").imagesLoaded(function(){jQuery(".fusion-portfolio:not(.fusion-recent-works) .fusion-portfolio-wrapper").isotope()}))}function ewwwWebPInit(e){ewwwLoadImages(e),ewwwNggLoadGalleries(e),document.arrive(".ewww_webp",function(){ewwwLoadImages(e)}),document.arrive(".ewww_webp_lazy_load",function(){ewwwLoadImages(e)});var t=0,r=setInterval(function(){"undefined"!=typeof galleries&&(ewwwNggParseGalleries(e),clearInterval(r)),1e3<(t+=25)&&clearInterval(r)},25)}function ewwwAttr(e,t,r){null!=r&&!1!==r&&e.setAttribute(t,r)}function ewwwNggParseGalleries(e){if(e)for(var t in galleries){var r=galleries[t];galleries[t].images_list=ewwwNggParseImageList(r.images_list)}}function ewwwNggLoadGalleries(e){e&&document.addEventListener("ngg.galleria.themeadded",function(e,t){window.ngg_galleria._create_backup=window.ngg_galleria.create,window.ngg_galleria.create=function(e,t){var r=$(e).data("id");return galleries["gallery_"+r].images_list=ewwwNggParseImageList(galleries["gallery_"+r].images_list),window.ngg_galleria._create_backup(e,t)}})}function ewwwNggParseImageList(e){for(var t in e){var r=e[t];if(void 0!==r["image-webp"]&&(e[t].image=r["image-webp"],delete e[t]["image-webp"]),void 0!==r["thumb-webp"]&&(e[t].thumb=r["thumb-webp"],delete e[t]["thumb-webp"]),void 0!==r.full_image_webp&&(e[t].full_image=r.full_image_webp,delete e[t].full_image_webp),void 0!==r.srcsets)for(var a in r.srcsets)nggSrcset=r.srcsets[a],void 0!==r.srcsets[a+"-webp"]&&(e[t].srcsets[a]=r.srcsets[a+"-webp"],delete e[t].srcsets[a+"-webp"]);if(void 0!==r.full_srcsets)for(var i in r.full_srcsets)nggFSrcset=r.full_srcsets[i],void 0!==r.full_srcsets[i+"-webp"]&&(e[t].full_srcsets[i]=r.full_srcsets[i+"-webp"],delete e[t].full_srcsets[i+"-webp"])}return e}check_webp_feature("alpha",ewwwWebPInit);
includes/ls.print.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ This lazySizes extension adds better support for print.
3
+ In case the user starts to print lazysizes will load all images.
4
+ */
5
+ (function(window, factory) {
6
+ var globalInstall = function(){
7
+ factory(window.lazySizes);
8
+ window.removeEventListener('lazyunveilread', globalInstall, true);
9
+ };
10
+
11
+ factory = factory.bind(null, window, window.document);
12
+
13
+ if(typeof module == 'object' && module.exports){
14
+ factory(require('lazysizes'));
15
+ } else if(window.lazySizes) {
16
+ globalInstall();
17
+ } else {
18
+ window.addEventListener('lazyunveilread', globalInstall, true);
19
+ }
20
+ }(window, function(window, document, lazySizes) {
21
+ /*jshint eqnull:true */
22
+ 'use strict';
23
+ var config, elements, onprint, printMedia;
24
+ // see also: http://tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/
25
+ if(window.addEventListener){
26
+ config = lazySizes && lazySizes.cfg;
27
+ elements = config.lazyClass || 'lazyload';
28
+ onprint = function(){
29
+ var i, len;
30
+ if(typeof elements == 'string'){
31
+ elements = document.getElementsByClassName(elements);
32
+ }
33
+
34
+ if(lazySizes){
35
+ for(i = 0, len = elements.length; i < len; i++){
36
+ lazySizes.loader.unveil(elements[i]);
37
+ }
38
+ }
39
+ };
40
+
41
+ addEventListener('beforeprint', onprint, false);
42
+
43
+ if(!('onbeforeprint' in window) && window.matchMedia && (printMedia = matchMedia('print')) && printMedia.addListener){
44
+ printMedia.addListener(function(){
45
+ if(printMedia.matches){
46
+ onprint();
47
+ }
48
+ });
49
+ }
50
+ }
51
+ }));
includes/ls.print.min.js ADDED
@@ -0,0 +1,2 @@
 
 
1
+ /*! lazysizes - v5.1.0 */
2
+ !function(a,b){var c=function(){b(a.lazySizes),a.removeEventListener("lazyunveilread",c,!0)};b=b.bind(null,a,a.document),"object"==typeof module&&module.exports?b(require("lazysizes")):a.lazySizes?c():a.addEventListener("lazyunveilread",c,!0)}(window,function(a,b,c){"use strict";var d,e,f,g;a.addEventListener&&(d=c&&c.cfg,e=d.lazyClass||"lazyload",f=function(){var a,d;if("string"==typeof e&&(e=b.getElementsByClassName(e)),c)for(a=0,d=e.length;a<d;a++)c.loader.unveil(e[a])},addEventListener("beforeprint",f,!1),!("onbeforeprint"in a)&&a.matchMedia&&(g=matchMedia("print"))&&g.addListener&&g.addListener(function(){g.matches&&f()}))});
includes/ls.unveilhooks.js CHANGED
@@ -85,7 +85,9 @@ For background images, use data-bg attribute:
85
  var dPR = (window.devicePixelRatio || 1);
86
  var targetWidth = Math.round(e.target.offsetWidth * dPR);
87
  var targetHeight = Math.round(e.target.offsetHeight * dPR);
88
- if(window.lazySizes.hC(e.target,'wp-block-cover')){
 
 
89
  bg = constrainSrc(bg,targetWidth,targetHeight,'bg-cover');
90
  } else {
91
  bg = constrainSrc(bg,targetWidth,targetHeight,'bg');
85
  var dPR = (window.devicePixelRatio || 1);
86
  var targetWidth = Math.round(e.target.offsetWidth * dPR);
87
  var targetHeight = Math.round(e.target.offsetHeight * dPR);
88
+ if (window.lazySizes.hC(e.target,'wp-block-cover')) {
89
+ bg = constrainSrc(bg,targetWidth,targetHeight,'bg-cover');
90
+ } else if (window.lazySizes.hC(e.target,'elementor-bg')){
91
  bg = constrainSrc(bg,targetWidth,targetHeight,'bg-cover');
92
  } else {
93
  bg = constrainSrc(bg,targetWidth,targetHeight,'bg');
readme.txt CHANGED
@@ -1,11 +1,11 @@
1
  === EWWW Image Optimizer ===
2
  Contributors: nosilver4u
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MKMQKCBFFG3WW
4
- Tags: image, compress, resize, optimize, optimization, lossless, lossy, seo, webp, wp-cli, scale, tinypng, tinyjpg
5
  Requires at least: 5.0
6
  Tested up to: 5.2
7
  Requires PHP: 5.6
8
- Stable tag: 4.8.1
9
  License: GPLv3
10
 
11
  Speed up your website and improve your visitors' experience by automatically compressing and resizing images and PDFs. Boost SEO and improve sales.
@@ -22,7 +22,7 @@ EWWW I.O. will optimize images uploaded and created by any plugin, and features
22
  1. **Smooth Handling** with pixel-perfect optimization using industry-leading tools and progressive rendering.
23
  1. **High Torque** as we bring you the best compression/quality ratio available with our lossy options for JPG, PNG, and PDF files.
24
  1. **Adaptive Steering** with intelligent conversion options to get the right image format for the job (JPG, PNG, or GIF).
25
- 1. **Free Parking** The core plugin is free and always will be. Additionally, if you choose the API, you never pay for an image we can’t compress, you are never billed for a month you do not use the API, and pre-paid credits never expire. Plus, get WebP image generation at no extra cost: any JPG or PNG can be converted to Google’s next-generation image format.
26
  1. **Comprehensive Coverage:** no image gets left behind, optimize everything on your site, beyond just the WordPress Media Library.
27
  1. **Safety First:** all communications are secured with top SSL encryption.
28
  1. **Roadside Assistance:** top-notch support is in our DNA. While API customers get top priority, we answer [every single support question with care](https://ewww.io/contact-us/).
@@ -159,9 +159,8 @@ Using the command *gifsicle -b -O3 --careful original file*. This is particularl
159
 
160
  = I want to know more about image optimization, and why you chose these options/tools. =
161
 
162
- That's not a question, but since I made it up, I'll answer it. See these resources:
163
- https://developers.google.com/speed/docs/insights/OptimizeImages
164
- http://developer.yahoo.com/performance/rules.html#opt_images
165
 
166
  == Screenshots ==
167
 
@@ -174,31 +173,34 @@ http://developer.yahoo.com/performance/rules.html#opt_images
174
  * Feature requests can be viewed and submitted at https://github.com/nosilver4u/ewww-image-optimizer/labels/enhancement
175
  * If you would like to help translate this plugin in your language, get started here: https://translate.wordpress.org/projects/wp-plugins/ewww-image-optimizer/
176
 
177
- = 4.8.1 =
178
- * added: Lazy Load background image support added for span elements
179
- * changed: constrain by height for background images that are taller than they are wide
180
- * changed: debug.log moved to more suitable location
181
- * fix: Lazy Load breaks when an image has an empty class attribute
182
- * fix: regression that caused jpegtran and pngout tests to fail on Windows
183
- * fix: writing to debug.log causes errors
184
-
185
- = 4.8.0 =
186
- * added: ability to resize images outside media library via scheduled or bulk optimization
187
- * added: compatibility with WP Stateless for GSC
188
- * added: use ewww_image_optimizer_autoconvert_threshold filter to modify conversion threshold (default of 300kb)
189
- * changed: Lazy Load without ExactDN uses blank PNG placeholders for better srcset auto-sizing
190
- * changed: API backups taken prior to resizing/scaling rather than just before compression
191
- * changed: ExactDN + Lazy Load uses scaling rather than cropping by default
192
- * changed: prevent NextGEN backup images from being optimized
193
- * fixed: bulk optimizer not resuming when non-media library images remain in queue
194
- * fixed: notices when a user-selected admin theme is unavailable
195
- * fixed: privacy policy function triggers notices in WP-CLI
196
- * fixed: background-image attributes with single-quotes now supported by ExactDN, Lazy Load, and JS WebP
197
- * fixed: background-image attributes getting extra arguments with lazy load
198
- * fixed: On multi-site installs, site admins could add folders to optimize outside of the uploads folder
199
- * fixed: LQIP with SVG files results in duplicate requests
200
- * fixed: image optimization results in media library report file missing when using WP Stateless
201
- * fixed: plugin checking for 'nice' on Windows servers
 
 
 
202
 
203
  = Earlier versions =
204
  Please refer to the separate changelog.txt file.
1
  === EWWW Image Optimizer ===
2
  Contributors: nosilver4u
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MKMQKCBFFG3WW
4
+ Tags: optimize, image, convert, webp, resize, compress, lazy load, optimization, lossless, lossy, seo, scale
5
  Requires at least: 5.0
6
  Tested up to: 5.2
7
  Requires PHP: 5.6
8
+ Stable tag: 4.9.3
9
  License: GPLv3
10
 
11
  Speed up your website and improve your visitors' experience by automatically compressing and resizing images and PDFs. Boost SEO and improve sales.
22
  1. **Smooth Handling** with pixel-perfect optimization using industry-leading tools and progressive rendering.
23
  1. **High Torque** as we bring you the best compression/quality ratio available with our lossy options for JPG, PNG, and PDF files.
24
  1. **Adaptive Steering** with intelligent conversion options to get the right image format for the job (JPG, PNG, or GIF).
25
+ 1. **Free Parking** The core plugin is free and always will be. However, our paid services offer up to 80% compression, and a [host of other features](https://ewww.io/plans/)!
26
  1. **Comprehensive Coverage:** no image gets left behind, optimize everything on your site, beyond just the WordPress Media Library.
27
  1. **Safety First:** all communications are secured with top SSL encryption.
28
  1. **Roadside Assistance:** top-notch support is in our DNA. While API customers get top priority, we answer [every single support question with care](https://ewww.io/contact-us/).
159
 
160
  = I want to know more about image optimization, and why you chose these options/tools. =
161
 
162
+ That's not a question, but since I made it up, I'll answer it. See this resource:
163
+ https://developers.google.com/web/tools/lighthouse/audits/optimize-images
 
164
 
165
  == Screenshots ==
166
 
173
  * Feature requests can be viewed and submitted at https://github.com/nosilver4u/ewww-image-optimizer/labels/enhancement
174
  * If you would like to help translate this plugin in your language, get started here: https://translate.wordpress.org/projects/wp-plugins/ewww-image-optimizer/
175
 
176
+ = 4.9.3 =
177
+ * fixed: ExactDN incorrectly scales Elementor background images rather than cropping
178
+ * fixed: ExactDN cannot work with Divi/Elementor background images due to use of external CSS files
179
+ * fixed: JS WebP rewriting picture tags that already have WebP markup in Force WebP mode
180
+ * fixed: JS WebP incorrectly parses GIF/SVG images in Force WebP mode
181
+ * fixed: JS WebP does not support lazy load + infinite scroll
182
+ * fixed: Lazy Load auto-scaling breaks if background image is enclosed in encoded quotes
183
+ * fixed: GRAND FlaGallery integration broken by hook suffix change
184
+
185
+ = 4.9.2 =
186
+ * fixed: generating lazy load PNG placeholders with large heights causes 500 errors
187
+ * fixed: error when importing media via WordPress Importer plugin
188
+ * fixed: error with WP/LR Sync
189
+
190
+ = 4.9.1 =
191
+ * fixed: error on settings screen when JS WebP is enabled
192
+
193
+ = 4.9.0 =
194
+ * added: Lazy Load background image support for section elements
195
+ * added: ExactDN background image support for li,span, and section elements
196
+ * added: lazysizes print plugin, enable via EWWW_IMAGE_OPTIMIZER_LAZY_PRINT constant
197
+ * added: compatibility with upcoming Easy Image Optimizer
198
+ * changed: automatic compression disabled during WP/LR Sync with admin notice
199
+ * changed: Lazy Load PNG placeholders capped at 1920px wide to prevent errors
200
+ * changed: use ExactDN, when active, for Lazy Load PNG placeholders
201
+ * changed: EWWW_MEMORY_LIMIT renamed to EIO_MEMORY_LIMIT for setting plugin memory limit
202
+ * fixed: WebP test image not refreshing after inserting .htaccess rules
203
+ * fixed: errors when manually adding lazysizes script
204
 
205
  = Earlier versions =
206
  Please refer to the separate changelog.txt file.
tests/test-utility.php CHANGED
@@ -39,15 +39,15 @@ class EWWWIO_Utility_Tests extends WP_UnitTestCase {
39
  */
40
  function test_mimetype() {
41
  $binaries = scandir( EWWW_IMAGE_OPTIMIZER_BINARY_PATH );
42
- global $ewww_debug;
43
- $ewww_debug .= '';
44
  ewww_image_optimizer_set_option( 'ewww_image_optimizer_debug', true );
45
  foreach ( $binaries as $binary ) {
46
  $binary = trailingslashit( EWWW_IMAGE_OPTIMIZER_BINARY_PATH ) . $binary;
47
  if ( ! is_file( $binary ) ) {
48
  continue;
49
  }
50
- $this->assertTrue( (bool) ewww_image_optimizer_mimetype( $binary, 'b' ), $binary . ":\n" . str_replace( '<br>', "\n", $ewww_debug ) );
51
  }
52
  }
53
 
39
  */
40
  function test_mimetype() {
41
  $binaries = scandir( EWWW_IMAGE_OPTIMIZER_BINARY_PATH );
42
+ global $eio_debug;
43
+ $eio_debug .= '';
44
  ewww_image_optimizer_set_option( 'ewww_image_optimizer_debug', true );
45
  foreach ( $binaries as $binary ) {
46
  $binary = trailingslashit( EWWW_IMAGE_OPTIMIZER_BINARY_PATH ) . $binary;
47
  if ( ! is_file( $binary ) ) {
48
  continue;
49
  }
50
+ $this->assertTrue( (bool) ewww_image_optimizer_mimetype( $binary, 'b' ), $binary . ":\n" . str_replace( '<br>', "\n", $eio_debug ) );
51
  }
52
  }
53
 
unique.php CHANGED
@@ -687,9 +687,32 @@ function ewww_image_optimizer_notice_utils( $quiet = null ) {
687
  } elseif ( ! is_writable( EWWW_IMAGE_OPTIMIZER_TOOL_PATH ) ) {
688
  ewww_image_optimizer_tool_folder_permissions_notice();
689
  }
690
- echo "<div id='ewww-image-optimizer-warning-opt-missing' class='notice notice-error'><p>" .
691
- /* translators: 1-6: jpegtran, optipng, pngout, pngquant, gifsicle, and cwebp (links) 7: Settings Page (link) 8: Installation Instructions (link) */
692
- sprintf( esc_html__( 'EWWW Image Optimizer uses %1$s, %2$s, %3$s, %4$s, %5$s, and %6$s. You are missing: %7$s. Please install via the %8$s or the %9$s.', 'ewww-image-optimizer' ), "<a href='http://jpegclub.org/jpegtran/'>jpegtran</a>", "<a href='http://optipng.sourceforge.net/'>optipng</a>", "<a href='http://advsys.net/ken/utils.htm'>pngout</a>", "<a href='http://pngquant.org/'>pngquant</a>", "<a href='http://www.lcdf.org/gifsicle/'>gifsicle</a>", "<a href='https://developers.google.com/speed/webp/'>cwebp</a>", $msg, "<a href='$settings_page'>" . esc_html__( 'Settings Page', 'ewww-image-optimizer' ) . '</a>', "<a href='https://docs.ewww.io/'>" . esc_html__( 'Installation Instructions', 'ewww-image-optimizer' ) . '</a>' ) . '</p></div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  ewwwio_memory( __FUNCTION__ );
694
  }
695
  }
@@ -2805,16 +2828,6 @@ function ewww_image_optimizer_install_pngout() {
2805
  return $sendback;
2806
  }
2807
 
2808
- /**
2809
- * Downloads a file to the EWWW IO folder.
2810
- *
2811
- * @param string $url The remote url to fetch.
2812
- * @return string|WP_Error The location of the downloaded file, or a WP_Error object on failure.
2813
- */
2814
- function ewwwio_download_url( $url ) {
2815
- return $location;
2816
- }
2817
-
2818
  /**
2819
  * Removes any binaries that have been installed in the wp-content/ewww/ folder.
2820
  */
687
  } elseif ( ! is_writable( EWWW_IMAGE_OPTIMIZER_TOOL_PATH ) ) {
688
  ewww_image_optimizer_tool_folder_permissions_notice();
689
  }
690
+ if ( 'pngout' === $msg ) {
691
+ echo "<div id='ewww-image-optimizer-warning-opt-missing' class='notice notice-error'><p>" .
692
+ sprintf(
693
+ /* translators: 1: automatically (link) 2: manually (link) */
694
+ esc_html__( 'You are missing pngout. Install %1$s or %2$s.', 'ewww-image-optimizer' ),
695
+ '<a href="admin.php?action=ewww_image_optimizer_install_pngout">' . esc_html__( 'automatically', 'ewww-image-optimizer' ) . '</a>',
696
+ '<a href="https://docs.ewww.io/article/13-installing-pngout" data-beacon-article="5854531bc697912ffd6c1afa">' . esc_html__( 'manually', 'ewww-image-optimizer' ) . '</a>'
697
+ ) .
698
+ '</p></div>';
699
+ } else {
700
+ echo "<div id='ewww-image-optimizer-warning-opt-missing' class='notice notice-error'><p>" .
701
+ sprintf(
702
+ /* translators: 1-6: jpegtran, optipng, pngout, pngquant, gifsicle, and cwebp (links) 7: Settings Page (link) 8: Installation Instructions (link) */
703
+ esc_html__( 'EWWW Image Optimizer uses %1$s, %2$s, %3$s, %4$s, %5$s, and %6$s. You are missing: %7$s. Please install via the %8$s or the %9$s.', 'ewww-image-optimizer' ),
704
+ "<a href='http://jpegclub.org/jpegtran/'>jpegtran</a>",
705
+ "<a href='http://optipng.sourceforge.net/'>optipng</a>",
706
+ "<a href='http://advsys.net/ken/utils.htm'>pngout</a>",
707
+ "<a href='http://pngquant.org/'>pngquant</a>",
708
+ "<a href='http://www.lcdf.org/gifsicle/'>gifsicle</a>",
709
+ "<a href='https://developers.google.com/speed/webp/'>cwebp</a>",
710
+ $msg,
711
+ "<a href='$settings_page'>" . esc_html__( 'Settings Page', 'ewww-image-optimizer' ) . '</a>',
712
+ "<a href='https://docs.ewww.io/article/6-the-plugin-says-i-m-missing-something' data-beacon-article='585371e3c697912ffd6c0ba1' target='_blank'>" . esc_html__( 'Installation Instructions', 'ewww-image-optimizer' ) . '</a>'
713
+ ) .
714
+ '</p></div>';
715
+ }
716
  ewwwio_memory( __FUNCTION__ );
717
  }
718
  }
2828
  return $sendback;
2829
  }
2830
 
 
 
 
 
 
 
 
 
 
 
2831
  /**
2832
  * Removes any binaries that have been installed in the wp-content/ewww/ folder.
2833
  */