PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / trunk
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization vtrunk
4.2.14 4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 All 135 releases
← All changes | inc/lazyload_replacer.php +213 -57 2.5.7 → trunk View file →
@@ -1,6 +1,9 @@
1 1 <?php
2 2
3 +use OptimoleWP\PageProfiler\Profile;
4 +use OptimoleWP\Preload\Links;
5 +
3 6 /**
4 7 * The class handles the img tag replacements for lazyload.
5 8 *
6 9 * @package \Optml\Inc
@@ -8,8 +11,9 @@
8 11 */
9 12 final class Optml_Lazyload_Replacer extends Optml_App_Replacer {
10 13 use Optml_Normalizer;
11 14 use Optml_Validator;
15 + use Optml_Dam_Offload_Utils;
12 16
13 17 const IFRAME_PLACEHOLDER_CLASS = '
14 18 iframe[data-opt-src]:not([data-opt-lazy-loaded]) {
15 19 background-color: #ffffff;
@@ -15,14 +19,20 @@
15 19 background-color: #ffffff;
16 20 background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2240%22%20height%3D%2240%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22-webkit-transform-origin%3A50%25%2050%25%3B-webkit-animation%3Aspin%201.5s%20linear%20infinite%3B-webkit-backface-visibility%3Ahidden%3Banimation%3Aspin%201.5s%20linear%20infinite%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cdefs%3E%3Cstyle%3E%3C%21%5BCDATA%5B%40-webkit-keyframes%20spin%7Bfrom%7B-webkit-transform%3Arotate%280deg%29%7Dto%7B-webkit-transform%3Arotate%28-359deg%29%7D%7D%40keyframes%20spin%7Bfrom%7Btransform%3Arotate%280deg%29%7Dto%7Btransform%3Arotate%28-359deg%29%7D%7D%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22outer%22%3E%3Cpath%20d%3D%22M20%200a3.994%203.994%200%20110%207.988A3.994%203.994%200%200120%200z%22%2F%3E%3Cpath%20d%3D%22M5.858%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23d2d2d2%22%2F%3E%3Cpath%20d%3D%22M20%2032.012A3.994%203.994%200%201120%2040a3.994%203.994%200%20010-7.988z%22%20fill%3D%22%23828282%22%2F%3E%3Cpath%20d%3D%22M28.494%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23656565%22%2F%3E%3Cpath%20d%3D%22M3.994%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%23bbb%22%2F%3E%3Cpath%20d%3D%22M5.858%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23a4a4a4%22%2F%3E%3Cpath%20d%3D%22M36.006%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%234a4a4a%22%2F%3E%3Cpath%20d%3D%22M28.494%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23323232%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
17 21 background-repeat: no-repeat;
18 22 background-position: 50% 50%;
23 + }
24 + video[data-opt-src]:not([data-opt-lazy-loaded]) {
25 + background-color: #ffffff;
26 + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2240%22%20height%3D%2240%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22-webkit-transform-origin%3A50%25%2050%25%3B-webkit-animation%3Aspin%201.5s%20linear%20infinite%3B-webkit-backface-visibility%3Ahidden%3Banimation%3Aspin%201.5s%20linear%20infinite%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cdefs%3E%3Cstyle%3E%3C%21%5BCDATA%5B%40-webkit-keyframes%20spin%7Bfrom%7B-webkit-transform%3Arotate%280deg%29%7Dto%7B-webkit-transform%3Arotate%28-359deg%29%7D%7D%40keyframes%20spin%7Bfrom%7Btransform%3Arotate%280deg%29%7Dto%7Btransform%3Arotate%28-359deg%29%7D%7D%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22outer%22%3E%3Cpath%20d%3D%22M20%200a3.994%203.994%200%20110%207.988A3.994%203.994%200%200120%200z%22%2F%3E%3Cpath%20d%3D%22M5.858%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23d2d2d2%22%2F%3E%3Cpath%20d%3D%22M20%2032.012A3.994%203.994%200%201120%2040a3.994%203.994%200%20010-7.988z%22%20fill%3D%22%23828282%22%2F%3E%3Cpath%20d%3D%22M28.494%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23656565%22%2F%3E%3Cpath%20d%3D%22M3.994%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%23bbb%22%2F%3E%3Cpath%20d%3D%22M5.858%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23a4a4a4%22%2F%3E%3Cpath%20d%3D%22M36.006%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%234a4a4a%22%2F%3E%3Cpath%20d%3D%22M28.494%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23323232%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
27 + background-repeat: no-repeat;
28 + background-position: 50% 50%;
19 29 }';
20 30 const IFRAME_PLACEHOLDER_STYLE = '<style type="text/css">' . self::IFRAME_PLACEHOLDER_CLASS . '</style>';
21 31
22 32 const IFRAME_TEMP_COMMENT = '/** optmliframelazyloadplaceholder */';
23 33
24 - const SVG_PLACEHOLDER = 'data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20#width#%20#height#%22%20width%3D%22#width#%22%20height%3D%22#height#%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3C%2Fsvg%3E';
34 + const SVG_PLACEHOLDER = 'data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20#width#%20#height#%22%20width%3D%22#width#%22%20height%3D%22#height#%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%22#width#%22%20height%3D%22#height#%22%20fill%3D%22#fill#%22%2F%3E%3C%2Fsvg%3E';
25 35 /**
26 36 * If frame lazyload is present on page.
27 37 *
28 38 * @var bool Whether or not at least one iframe has been lazyloaded.
@@ -30,12 +40,18 @@
30 40 private static $found_iframe = false;
31 41 /**
32 42 * Cached object instance.
33 43 *
34 - * @var Optml_Tag_Replacer
44 + * @var Optml_Lazyload_Replacer
35 45 */
36 46 protected static $instance = null;
37 47 /**
48 + * Holds the number of images to skip from lazyload.
49 + *
50 + * @var integer The number image tags to skip.
51 + */
52 + private static $skip_lazyload_images = null;
53 + /**
38 54 * Holds classes for listening to lazyload on background.
39 55 *
40 56 * @var array Lazyload background classes.
41 57 */
@@ -75,9 +91,9 @@
75 91 * Class instance method.
76 92 *
77 93 * @codeCoverageIgnore
78 94 * @static
79 - * @return Optml_Tag_Replacer
95 + * @return Optml_Lazyload_Replacer
80 96 * @since 1.0.0
81 97 * @access public
82 98 */
83 99 public static function instance() {
@@ -94,21 +110,26 @@
94 110 *
95 111 * @return array Lazyload selectors.
96 112 */
97 113 public static function get_background_lazyload_selectors() {
114 + if ( null !== self::$background_lazyload_selectors ) {
115 + return self::$background_lazyload_selectors;
116 + }
98 117
99 - if ( ! empty( self::$background_lazyload_selectors ) && is_array( self::$background_lazyload_selectors ) ) {
118 + if ( self::instance()->settings === null ) {
119 + self::$background_lazyload_selectors = [];
120 +
100 121 return self::$background_lazyload_selectors;
101 122 }
102 123 if ( self::instance()->settings->get( 'bg_replacer' ) === 'disabled' ) {
103 124 self::$background_lazyload_selectors = [];
104 -
105 125 return self::$background_lazyload_selectors;
106 126 }
107 127 $default_watchers = [
108 - '.elementor-section[data-settings*="background_background"]',
109 - '.elementor-section > .elementor-background-overlay',
110 - '.wp-block-cover[style*="background-image"]',
128 + '[class*="wp-block-cover"][style*="background-image"]',
129 + '[style*="background-image:url("]', '[style*="background-image: url("]',
130 + '[style*="background:url("]', '[style*="background: url("]',
131 + '[class*="wp-block-group"][style*="background-image"]',
111 132 ];
112 133
113 134 $saved_watchers = self::instance()->settings->get_watchers();
114 135
@@ -133,10 +154,9 @@
133 154 *
134 155 * @return array
135 156 */
136 157 public static function get_lazyload_bg_classes() {
137 -
138 - if ( ! empty( self::$lazyload_background_classes ) && is_array( self::$lazyload_background_classes ) ) {
158 + if ( null !== self::$lazyload_background_classes ) {
139 159 return self::$lazyload_background_classes;
140 160 }
141 161
142 162 self::$lazyload_background_classes = apply_filters( 'optml_lazyload_bg_classes', [] );
@@ -142,9 +162,22 @@
142 162 self::$lazyload_background_classes = apply_filters( 'optml_lazyload_bg_classes', [] );
143 163
144 164 return self::$lazyload_background_classes;
145 165 }
166 + /**
167 + * Returns the number of images to skip from lazyload.
168 + *
169 + * @return integer
170 + */
171 + public static function get_skip_lazyload_limit() {
146 172
173 + if ( self::$skip_lazyload_images !== null ) {
174 + return self::$skip_lazyload_images;
175 + }
176 +
177 + self::$skip_lazyload_images = apply_filters( 'optml_lazyload_images_skip', self::instance()->settings->get( 'skip_lazyload_images' ) );
178 + return self::$skip_lazyload_images;
179 + }
147 180 /**
148 181 * Returns classes for lazyload additional watch.
149 182 *
150 183 * @return array
@@ -149,10 +182,9 @@
149 182 *
150 183 * @return array
151 184 */
152 185 public static function get_watcher_lz_classes() {
153 -
154 - if ( ! empty( self::$lazyload_watcher_classes ) && is_array( self::$lazyload_watcher_classes ) ) {
186 + if ( null !== self::$lazyload_watcher_classes ) {
155 187 return self::$lazyload_watcher_classes;
156 188 }
157 189
158 190 self::$lazyload_watcher_classes = apply_filters( 'optml_watcher_lz_classes', [] );
@@ -168,24 +200,18 @@
168 200
169 201 if ( ! $this->settings->use_lazyload() ) {
170 202 return;
171 203 }
172 - if ( ! Optml_Filters::should_do_page( self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_URL ] ) ) {
204 + $filters = $this->settings->get_filters();
205 + if ( ! Optml_Filters::should_do_page( $filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_URL ], $filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_URL_MATCH ] ) ) {
173 206 return;
174 207 }
175 208
176 - add_filter(
177 - 'max_srcset_image_width',
178 - function () {
179 - return 1;
180 - }
181 - );
182 209 self::$is_lazyload_placeholder = self::$instance->settings->get( 'lazyload_placeholder' ) === 'enabled';
183 210
184 211 add_filter( 'optml_tag_replace', [ $this, 'lazyload_tag_replace' ], 2, 6 );
185 212
186 - add_filter( 'optml_video_replace', [$this, 'lazyload_video_replace'], 2, 1 );
187 -
213 + add_filter( 'optml_video_replace', [ $this, 'lazyload_video_replace' ], 2, 1 );
188 214 }
189 215 /**
190 216 * Check if there are lazyloaded iframes.
191 217 *
@@ -195,8 +221,25 @@
195 221 return self::$found_iframe;
196 222 }
197 223
198 224 /**
225 + * Check if the img tag should be lazyloaded early.
226 + *
227 + * @param string $image_tag The image tag.
228 + * @return bool Whether the image tag should be lazyloaded early or not.
229 + */
230 + public function should_lazyload_early( $image_tag ) {
231 + $flags = apply_filters( 'optml_lazyload_early_flags', [] );
232 +
233 + foreach ( $flags as $flag ) {
234 + if ( strpos( $image_tag, $flag ) !== false ) {
235 + return true;
236 + }
237 + }
238 +
239 + return false;
240 + }
241 + /**
199 242 * Replaces the tags with lazyload tags.
200 243 *
201 244 * @param string $new_tag The new tag.
202 245 * @param string $original_url The original URL.
@@ -209,16 +252,49 @@
209 252 */
210 253 public function lazyload_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed = false, $full_tag = '' ) {
211 254
212 255 if ( ! $this->can_lazyload_for( $original_url, $full_tag ) ) {
256 +
213 257 return Optml_Tag_Replacer::instance()->regular_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed );
214 258 }
259 + // Remove fetchpriority high attribute when the image is lazyloaded
260 + if ( strpos( $new_tag, 'fetchpriority' ) !== false ) {
261 + $new_tag = str_ireplace(
262 + [
263 + 'fetchpriority=high',
264 + 'fetchpriority="high"',
265 + "fetchpriority='high'",
266 + 'fetchpriority=\"high\"',
267 + "fetchpriority=\'high\'",
268 + ],
269 + '',
270 + $new_tag
271 + );
272 + }
273 +
274 + if ( self::instance()->settings->get( 'native_lazyload' ) === 'enabled' ) {
275 + // First check if loading attribute already exists
276 + if ( strpos( $new_tag, 'loading=' ) !== false ) {
277 + // Replace existing loading attribute with lazy
278 + $loading_attr = $is_slashed ? 'loading=\"lazy\"' : 'loading="lazy"';
279 + $new_tag = preg_replace( '/loading=(["\'])[^"\']*\1/i', $loading_attr, $new_tag );
280 + } else {
281 + // No loading attribute exists, add it after <img
282 + $loading_attr = $is_slashed ? ' loading=\"lazy\"' : ' loading="lazy"';
283 + $new_tag = preg_replace( '/<img/i', '<img' . $loading_attr, $new_tag, 1 );
284 + }
285 + return $new_tag;
286 + }
287 +
288 + // we remove the loading attribute if it exists with strpos
289 + $new_tag = preg_replace( '/loading=(["\'])[^"\']*\1/i', '', $new_tag );
290 +
215 291 $should_ignore_rescale = ! $this->is_valid_mimetype_from_url( $original_url, [ 'gif' => true, 'svg' => true ] );
216 292
217 293 if ( ! self::$is_lazyload_placeholder && ! $should_ignore_rescale ) {
218 294 $optml_args['quality'] = 'eco';
219 295 $optml_args['resize'] = [];
220 - $low_url = apply_filters( 'optml_content_url', $original_url, $optml_args );
296 + $low_url = apply_filters( 'optml_content_url', $this->get_unoptimized_url( $original_url ), $optml_args );
221 297 $low_url = $is_slashed ? addcslashes( $low_url, '/' ) : $low_url;
222 298 } else {
223 299 $low_url = $this->get_svg_for(
224 300 isset( $optml_args['width'] ) ? $optml_args['width'] : '100%',
@@ -225,20 +301,28 @@
225 301 isset( $optml_args['height'] ) ? $optml_args['height'] : '100%',
226 302 ( $should_ignore_rescale ? null : $original_url )
227 303 );
228 304 }
229 -
230 305 $opt_format = '';
231 306
232 307 if ( $this->should_add_data_tag( $full_tag ) ) {
233 308 $opt_format = ' data-opt-src="%s" ';
309 + $custom_class = '';
310 +
234 311 if ( $should_ignore_rescale ) {
312 + $custom_class .= 'optimole-lazy-only ';
313 + }
314 + if ( $this->should_lazyload_early( $full_tag ) ) {
315 + $custom_class .= 'optimole-load-early';
316 + }
317 +
318 + if ( ! empty( $custom_class ) ) {
235 319 if ( strpos( $new_tag, 'class=' ) === false ) {
236 - $opt_format .= ' class="optimole-lazy-only" ';
320 + $opt_format .= ' class="' . $custom_class . '" ';
237 321 } else {
238 322 $new_tag = str_replace(
239 323 ( $is_slashed ? 'class=\"' : 'class="' ),
240 - ( $is_slashed ? 'class=\"optimole-lazy-only ' : 'class="optimole-lazy-only ' ),
324 + ( $is_slashed ? 'class=\"' . $custom_class . ' ' : 'class="' . $custom_class . ' ' ),
241 325 $new_tag
242 326 );
243 327 }
244 328 }
@@ -264,12 +348,16 @@
264 348 ],
265 349 $new_tag,
266 350 1
267 351 );
352 +
353 + // We remove srcset and sizes attributes since our lazyload does not need them.
354 + $pattern = '/\s+(?:srcset|sizes)=("[^"]*"|\'[^\']*\'|\\\\"[^\\\\"]*\\\\")/i';
355 + $new_tag = preg_replace( $pattern, '', $new_tag );
356 +
357 + // We keep this for srcset lazyload compatibility that might break our mechanism.
268 358 $new_tag = str_replace( 'srcset=', 'old-srcset=', $new_tag );
269 - if ( strpos( $new_tag, 'loading=' ) === false && self::instance()->settings->get( 'native_lazyload' ) === 'enabled' ) {
270 - $new_tag = preg_replace( '/<img/im', $is_slashed ? '<img loading=\"lazy\"' : '<img loading="lazy"', $new_tag );
271 - }
359 +
272 360 if ( ! $this->should_add_noscript( $new_tag ) ) {
273 361 return $new_tag;
274 362 }
275 363
@@ -283,27 +371,46 @@
283 371 * @return string
284 372 */
285 373 public function lazyload_video_replace( $content ) {
286 374 $video_tags = [];
287 - preg_match_all( '#(?:<noscript\s*>\s*)?<iframe(.*?)></iframe>(?:\s*</noscript\s*>)?#is', $content, $video_tags );
375 + $iframes = [];
376 + $videos = [];
377 + preg_match_all( '#(?:<noscript\s*>\s*)?<iframe(.*?)></iframe>(?:\s*</noscript\s*>)?#is', $content, $iframes );
378 + preg_match_all( '#(?:<noscript\s*>\s*)?<video(.*?)>.*?</video>(?:\s*</noscript\s*>)?#is', $content, $videos );
379 + $video_tags = array_merge( $iframes[0], $videos[0] );
288 380
289 381 $search = [];
290 382 $replace = [];
291 - foreach ( $video_tags[0] as $video_tag ) {
383 +
384 + foreach ( $video_tags as $video_tag ) {
385 +
292 386 if ( ! $this->should_lazyload_iframe( $video_tag ) ) {
293 387 continue;
294 388 }
295 - if ( preg_match( "/ data-opt-video-src=['\"]/is", $video_tag ) ) {
389 + if ( preg_match( "/ data-opt-src=['\"]/is", $video_tag ) ) {
296 390 continue;
297 391 }
298 - array_push( $search, $video_tag );
299 - $no_script = $video_tag;
392 + $should_add_noscript = true;
393 + $original_tag = $video_tag;
300 394 // replace the src and add the data-opt-src attribute
301 - $video_tag = preg_replace( '/iframe(.*?)src=/is', 'iframe$1 src="about:blank" data-opt-src=', $video_tag );
302 -
303 - if ( $this->should_add_noscript( $video_tag ) ) {
304 - $video_tag .= '<noscript>' . $no_script . '</noscript>';
395 + if ( strpos( $video_tag, 'iframe' ) !== false ) {
396 + $video_tag = preg_replace( '/iframe(.*?)src=/is', 'iframe$1 src="about:blank" data-opt-src=', $video_tag );
397 + } elseif ( strpos( $video_tag, 'video' ) !== false ) {
398 + if ( strpos( $video_tag, 'source' ) !== false ) {
399 + if ( strpos( $video_tag, 'preload' ) === false ) {
400 + $video_tag = preg_replace( '/video(.*?)>/is', 'video$1 preload="metadata">', $video_tag, 1 );
401 + $should_add_noscript = false;
402 + } else {
403 + continue;
404 + }
405 + } else {
406 + $video_tag = preg_replace( '/video(.*?)src=/is', 'video$1 data-opt-src=', $video_tag );
407 + }
305 408 }
409 + if ( $this->should_add_noscript( $video_tag ) && $should_add_noscript ) {
410 + $video_tag .= '<noscript>' . $original_tag . '</noscript>';
411 + }
412 + array_push( $search, $original_tag );
306 413 array_push( $replace, $video_tag );
307 414 self::$found_iframe = true;
308 415 }
309 416 $search = array_unique( $search );
@@ -320,8 +427,11 @@
320 427 *
321 428 * @return bool We can lazyload?
322 429 */
323 430 public function can_lazyload_for( $url, $tag = '' ) {
431 + if ( OPTML_DEBUG ) {
432 + do_action( 'optml_log', 'can_lazyload_for: ' . $url . ' ' . $tag );
433 + }
324 434 foreach ( self::possible_lazyload_flags() as $banned_string ) {
325 435 if ( strpos( $tag, $banned_string ) !== false ) {
326 436 return false;
327 437 }
@@ -335,9 +445,9 @@
335 445 basename( $url ),
336 446 Optml_Config::$image_extensions
337 447 );
338 448
339 - if ( ! isset( $type['ext'] ) || empty( $type['ext'] ) ) {
449 + if ( empty( $type['ext'] ) ) {
340 450 return false;
341 451 }
342 452
343 453 if ( false === Optml_Filters::should_do_extension( self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_EXT ], $type['ext'] ) ) {
@@ -347,8 +457,36 @@
347 457 if ( defined( 'OPTML_DISABLE_PNG_LAZYLOAD' ) && OPTML_DISABLE_PNG_LAZYLOAD ) {
348 458 return $type['ext'] !== 'png';
349 459 }
350 460
461 + $no_viewport_data_available = true;
462 +
463 + if ( $this->settings->is_lazyload_type_viewport() ) {
464 + $image_id = $this->get_id_by_url( $url );
465 + $is_in_all_viewports = Optml_Manager::instance()->page_profiler->is_in_all_viewports( $image_id );
466 + $is_lcp_image = Optml_Manager::instance()->page_profiler->is_lcp_image_in_all_viewports( $image_id );
467 + $no_viewport_data_available = ! Optml_Manager::instance()->page_profiler->is_data_available();
468 +
469 + if ( OPTML_DEBUG ) {
470 + if ( $is_in_all_viewports ) {
471 + do_action( 'optml_log', 'Lazyload skipped image is in all viewports ' . $url . '|' . $image_id );
472 + } elseif ( $is_lcp_image ) {
473 + do_action( 'optml_log', 'Lazyload skipped image is LCP ' . $url . '|' . $image_id );
474 + }
475 + }
476 +
477 + if ( $is_in_all_viewports || $is_lcp_image ) {
478 + return false;
479 + }
480 + }
481 +
482 + if (
483 + $no_viewport_data_available &&
484 + $this->settings->is_lazyload_type_fixed() &&
485 + Optml_Tag_Replacer::$lazyload_skipped_images < self::get_skip_lazyload_limit()
486 + ) {
487 + return false;
488 + }
351 489 return true;
352 490 }
353 491
354 492 /**
@@ -370,10 +508,10 @@
370 508
371 509 /**
372 510 * Get SVG markup with specific width/height.
373 511 *
374 - * @param int $width Markup Width.
375 - * @param int $height Markup Height.
512 + * @param int|string $width Markup Width.
513 + * @param int|string $height Markup Height.
376 514 * @param string|null $url Original URL.
377 515 *
378 516 * @return string SVG code.
379 517 */
@@ -390,23 +528,39 @@
390 528 $sizes = getimagesize( $filepath );
391 529 wp_cache_add( $key, [ $sizes[0], $sizes[1] ], 'optml_sources', DAY_IN_SECONDS );
392 530 }
393 531 }
394 - list( $width, $height ) = $sizes;
532 + // Check if $sizes is an array before list() assignment to prevent fatal error on PHP 8.2+
533 + // when the image file is missing (e.g., offloaded to CDN or deleted).
534 + if ( is_array( $sizes ) ) {
535 + list( $width, $height ) = $sizes;
536 + }
395 537 }
538 + // If the width is not found the url might be an offloaded attachment so we can get the width and height from the metadata.
539 + if ( ! is_numeric( $width ) && ! empty( $url ) ) {
540 + $attachment_id = $this->attachment_url_to_post_id( $url );
541 + $meta = wp_get_attachment_metadata( $attachment_id );
542 + $width = $meta['width'] ?? false;
543 + $height = $meta['height'] ?? false;
544 + }
396 545
397 546 $width = ! is_numeric( $width ) ? '100%' : $width;
398 547 $height = ! is_numeric( $height ) ? '100%' : $height;
548 + $fill = $this->settings->get( 'placeholder_color' );
399 549
400 - return
401 - str_replace(
402 - [ '#width#', '#height#' ],
403 - [
404 - $width,
405 - $height,
406 - ],
407 - self::SVG_PLACEHOLDER
408 - );
550 + if ( empty( $fill ) ) {
551 + $fill = 'transparent';
552 + }
553 +
554 + return str_replace(
555 + [ '#width#', '#height#', '#fill#' ],
556 + [
557 + $width,
558 + $height,
559 + urlencode( $fill ),
560 + ],
561 + self::SVG_PLACEHOLDER
562 + );
409 563 }
410 564
411 565 /**
412 566 * Check if we should add the noscript tag.
@@ -415,8 +569,11 @@
415 569 *
416 570 * @return bool Should add?
417 571 */
418 572 public function should_add_noscript( $tag ) {
573 + if ( $this->settings->get( 'no_script' ) === 'disabled' ) {
574 + return false;
575 + }
419 576 foreach ( self::get_ignore_noscript_flags() as $banned_string ) {
420 577 if ( strpos( $tag, $banned_string ) !== false ) {
421 578 return false;
422 579 }
@@ -431,8 +588,9 @@
431 588 *
432 589 * @return bool Should add?
433 590 */
434 591 public function should_lazyload_iframe( $tag ) {
592 +
435 593 foreach ( self::get_iframe_lazyload_flags() as $banned_string ) {
436 594 if ( strpos( $tag, $banned_string ) !== false ) {
437 595 return false;
438 596 }
@@ -446,14 +604,13 @@
446 604 *
447 605 * @return array
448 606 */
449 607 public static function get_ignore_noscript_flags() {
450 -
451 - if ( ! empty( self::$ignore_no_script_flags ) && is_array( self::$ignore_no_script_flags ) ) {
608 + if ( null !== self::$ignore_no_script_flags ) {
452 609 return self::$ignore_no_script_flags;
453 610 }
454 611
455 - self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [] );
612 + self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [ '<noscript' ] );
456 613
457 614 return self::$ignore_no_script_flags;
458 615 }
459 616 /**
@@ -461,14 +618,14 @@
461 618 *
462 619 * @return array
463 620 */
464 621 public static function get_iframe_lazyload_flags() {
465 -
466 - if ( ! empty( self::$iframe_lazyload_flags ) && is_array( self::$iframe_lazyload_flags ) ) {
622 + if ( null !== self::$iframe_lazyload_flags ) {
467 623 return self::$iframe_lazyload_flags;
468 624 }
469 625
470 - self::$iframe_lazyload_flags = apply_filters( 'optml_iframe_lazyload_flags', [ 'gform_ajax_frame', '<noscript', 'recaptcha' ] );
626 + self::$iframe_lazyload_flags = self::possible_lazyload_flags();
627 + self::$iframe_lazyload_flags = array_merge( self::$iframe_lazyload_flags, apply_filters( 'optml_iframe_lazyload_flags', [ 'gform_ajax_frame', '<noscript', 'recaptcha', '-src' ] ) );
471 628
472 629 return self::$iframe_lazyload_flags;
473 630 }
474 631 /**
@@ -498,6 +655,5 @@
498 655 public function __wakeup() {
499 656 // Unserializing instances of the class is forbidden.
500 657 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
501 658 }
502 -
503 659 }