Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
22.81% |
13 / 57 |
|
16.67% |
1 / 6 |
CRAP | |
0.00% |
0 / 1 |
| ImageLazyLoader | |
22.81% |
13 / 57 |
|
16.67% |
1 / 6 |
203.99 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| apply_lazy_loading_to_blocks | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| enqueue_lazy_loader | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
| get_inline_script | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| clean_content | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
| apply_lazy_loading | |
39.13% |
9 / 23 |
|
0.00% |
0 / 1 |
22.43 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace NewfoldLabs\WP\Module\Performance\Images; |
| 4 | |
| 5 | use DOMDocument; |
| 6 | use Exception; |
| 7 | |
| 8 | /** |
| 9 | * Manages the initialization and application of lazy loading for images. |
| 10 | */ |
| 11 | class ImageLazyLoader { |
| 12 | |
| 13 | /** |
| 14 | * Exclusion rules for lazy loading. |
| 15 | * Defines classes and attributes that should prevent lazy loading from being applied to specific images. |
| 16 | * |
| 17 | * @var array |
| 18 | */ |
| 19 | private static $exclusions = array( |
| 20 | 'classes' => array( |
| 21 | 'nfd-performance-not-lazy', |
| 22 | 'a3-notlazy', |
| 23 | 'disable-lazyload', |
| 24 | 'no-lazy', |
| 25 | 'no-lazyload', |
| 26 | 'skip-lazy', |
| 27 | ), |
| 28 | 'attributes' => array( |
| 29 | 'data-lazy-src', |
| 30 | 'data-crazy-lazy="exclude"', |
| 31 | 'data-no-lazy', |
| 32 | 'data-no-lazy="1"', |
| 33 | ), |
| 34 | ); |
| 35 | |
| 36 | /** |
| 37 | * List of content filters where lazy loading will be applied. |
| 38 | * These filters modify various types of WordPress content. |
| 39 | * |
| 40 | * @var array |
| 41 | */ |
| 42 | private static $content_filters = array( |
| 43 | 'the_content', |
| 44 | 'post_thumbnail_html', |
| 45 | 'widget_text', |
| 46 | 'get_avatar', |
| 47 | ); |
| 48 | |
| 49 | /** |
| 50 | * Constructor to initialize the lazy loading feature. |
| 51 | */ |
| 52 | public function __construct() { |
| 53 | // Enqueue the lazy loader script with inline settings. |
| 54 | add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_lazy_loader' ) ); |
| 55 | |
| 56 | // Add filters to apply lazy loading to various content types. |
| 57 | foreach ( self::$content_filters as $filter ) { |
| 58 | add_filter( $filter, array( $this, 'apply_lazy_loading' ) ); |
| 59 | } |
| 60 | |
| 61 | // Hook into Gutenberg block rendering to apply lazy loading. |
| 62 | add_filter( 'render_block', array( $this, 'apply_lazy_loading_to_blocks' ), 10, 2 ); |
| 63 | } |
| 64 | |
| 65 | /** |
| 66 | * Applies lazy loading to images within Gutenberg blocks. |
| 67 | * |
| 68 | * @param string $block_content The HTML content of the block. |
| 69 | * @param array $block The block data array. |
| 70 | * @return string Modified block content with lazy loading applied. |
| 71 | */ |
| 72 | public function apply_lazy_loading_to_blocks( $block_content, $block ) { |
| 73 | // Only target core/image blocks or other blocks with images. |
| 74 | if ( 'core/image' === $block['blockName'] || strpos( $block_content, '<img' ) !== false ) { |
| 75 | return $this->apply_lazy_loading( $block_content ); |
| 76 | } |
| 77 | |
| 78 | return $block_content; |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Enqueues the lazy loader script file and adds inline exclusion settings. |
| 83 | */ |
| 84 | public function enqueue_lazy_loader() { |
| 85 | $script_path = NFD_PERFORMANCE_BUILD_DIR . '/assets/image-lazy-loader.min.js'; |
| 86 | $script_url = NFD_PERFORMANCE_BUILD_URL . '/assets/image-lazy-loader.min.js'; |
| 87 | |
| 88 | // Register the script with version based on file modification time. |
| 89 | wp_register_script( |
| 90 | 'nfd-performance-lazy-loader', |
| 91 | $script_url, |
| 92 | array(), |
| 93 | file_exists( $script_path ) ? filemtime( $script_path ) : false, |
| 94 | true |
| 95 | ); |
| 96 | |
| 97 | // Inject the exclusion settings into the script. |
| 98 | wp_add_inline_script( |
| 99 | 'nfd-performance-lazy-loader', |
| 100 | $this->get_inline_script(), |
| 101 | 'before' |
| 102 | ); |
| 103 | |
| 104 | wp_enqueue_script( 'nfd-performance-lazy-loader' ); |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Generates the inline script to define lazy loading exclusions. |
| 109 | * This script populates the `window.nfdPerformance` object with exclusion rules. |
| 110 | * |
| 111 | * @return string JavaScript code to inline. |
| 112 | */ |
| 113 | private function get_inline_script() { |
| 114 | return 'window.nfdPerformance = window.nfdPerformance || {}; |
| 115 | window.nfdPerformance.imageOptimization = window.nfdPerformance.imageOptimization || {}; |
| 116 | window.nfdPerformance.imageOptimization.lazyLoading = ' . wp_json_encode( self::$exclusions ) . ';'; |
| 117 | } |
| 118 | |
| 119 | /** |
| 120 | * Cleans up content by replacing specific patterns with replacements. |
| 121 | * This method is used to sanitize or modify content before lazy loading is applied. |
| 122 | * |
| 123 | * @param string $pattern Regular expression pattern to match. |
| 124 | * @param string $search String to search for in the content. |
| 125 | * @param string $replace String to replace the search string with. |
| 126 | * @param string $content The content to be cleaned. |
| 127 | * @return string The cleaned content. |
| 128 | */ |
| 129 | public function clean_content( $pattern, $search, $replace, $content ) { |
| 130 | |
| 131 | if ( empty( $content ) || empty( $pattern ) || empty( $search ) ) { |
| 132 | return $content; |
| 133 | } |
| 134 | |
| 135 | $content = preg_replace_callback( |
| 136 | $pattern, |
| 137 | function ( $matches ) use ( $search, $replace ) { |
| 138 | $cleanedIframe = str_replace( $search, $replace, $matches[0] ); |
| 139 | return $cleanedIframe; |
| 140 | }, |
| 141 | $content |
| 142 | ); |
| 143 | return $content; |
| 144 | } |
| 145 | |
| 146 | /** |
| 147 | * Applies lazy loading to images in HTML content. |
| 148 | * Skips images with specified exclusion classes or attributes. |
| 149 | * |
| 150 | * @param string $content The HTML content to process. |
| 151 | * @return string Modified HTML content with lazy loading applied, or original content on error. |
| 152 | */ |
| 153 | public function apply_lazy_loading( $content ) { |
| 154 | // Return unmodified content if it is empty. |
| 155 | if ( empty( $content ) ) { |
| 156 | return $content; |
| 157 | } |
| 158 | |
| 159 | $exclusion_classes = self::$exclusions['classes']; |
| 160 | $exclusion_attributes = self::$exclusions['attributes']; |
| 161 | |
| 162 | $content = preg_replace_callback( |
| 163 | '/<img\b([^>]*)>/i', |
| 164 | function ( $matches ) use ( $exclusion_classes, $exclusion_attributes ) { |
| 165 | $img_tag = $matches[0]; |
| 166 | |
| 167 | // check for exclusion classes |
| 168 | if ( preg_match( '/class=["\']([^"\']+)["\']/', $img_tag, $class_match ) ) { |
| 169 | $classes = explode( ' ', $class_match[1] ); |
| 170 | foreach ( $exclusion_classes as $excluded ) { |
| 171 | if ( in_array( $excluded, $classes, true ) ) { |
| 172 | return $img_tag; |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | // Check for exclusion attributes |
| 178 | foreach ( $exclusion_attributes as $excluded_attr ) { |
| 179 | if ( preg_match( '/' . preg_quote( $excluded_attr, '/' ) . '(\s*=\s*["\'][^"\']*["\'])?/', $img_tag ) ) { |
| 180 | return $img_tag; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | // Not add lazy if already present |
| 185 | if ( preg_match( '/\bloading\s*=\s*["\']?lazy["\']?/i', $img_tag ) ) { |
| 186 | return $img_tag; |
| 187 | } |
| 188 | |
| 189 | // add loading="lazy" attribute |
| 190 | return preg_replace( '/<img\b/', '<img loading="lazy"', $img_tag, 1 ); |
| 191 | }, |
| 192 | $content |
| 193 | ); |
| 194 | |
| 195 | return $content; |
| 196 | } |
| 197 | } |