Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 51 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
ImageLazyLoader | |
0.00% |
0 / 51 |
|
0.00% |
0 / 5 |
462 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
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 | |||
apply_lazy_loading | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
182 |
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 | * Applies lazy loading to images in HTML content. |
121 | * Skips images with specified exclusion classes or attributes. |
122 | * |
123 | * @param string $content The HTML content to process. |
124 | * @return string Modified HTML content with lazy loading applied, or original content on error. |
125 | */ |
126 | public function apply_lazy_loading( $content ) { |
127 | // Return unmodified content if it is empty. |
128 | if ( empty( $content ) ) { |
129 | return $content; |
130 | } |
131 | |
132 | $doc = new DOMDocument(); |
133 | // Suppress warnings from invalid or malformed HTML. |
134 | libxml_use_internal_errors( true ); |
135 | |
136 | try { |
137 | // Attempt to parse the HTML content using htmlentities for encoding. |
138 | $content = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>' . $content . '</body></html>'; |
139 | if ( ! $doc->loadHTML( $content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ) ) { |
140 | return $content; |
141 | } |
142 | } catch ( Exception $e ) { |
143 | return $content; |
144 | } finally { |
145 | // Clear any errors collected during parsing to free memory. |
146 | libxml_clear_errors(); |
147 | } |
148 | |
149 | $images = $doc->getElementsByTagName( 'img' ); |
150 | |
151 | foreach ( $images as $image ) { |
152 | $skip = false; |
153 | |
154 | // Check if the image has an excluded class. |
155 | foreach ( self::$exclusions['classes'] as $class ) { |
156 | if ( $image->hasAttribute( 'class' ) && strpos( $image->getAttribute( 'class' ), $class ) !== false ) { |
157 | $skip = true; |
158 | break; |
159 | } |
160 | } |
161 | |
162 | // Check if the image has an excluded attribute. |
163 | foreach ( self::$exclusions['attributes'] as $attr ) { |
164 | if ( $image->hasAttribute( $attr ) ) { |
165 | $skip = true; |
166 | break; |
167 | } |
168 | } |
169 | |
170 | if ( $skip ) { |
171 | continue; |
172 | } |
173 | |
174 | // Add the loading="lazy" attribute if not already present. |
175 | if ( ! $image->hasAttribute( 'loading' ) ) { |
176 | $image->setAttribute( 'loading', 'lazy' ); |
177 | } |
178 | } |
179 | |
180 | // Extract the body content and return it. |
181 | $body = $doc->getElementsByTagName( 'body' )->item( 0 ); |
182 | return $body ? $doc->saveHTML( $body ) : $content; |
183 | } |
184 | } |