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 +244 -63 2.5.5 → 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,17 +11,47 @@
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 - 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';
17 + const IFRAME_PLACEHOLDER_CLASS = '
18 + iframe[data-opt-src]:not([data-opt-lazy-loaded]) {
19 + background-color: #ffffff;
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");
21 + background-repeat: no-repeat;
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%;
29 + }';
30 + const IFRAME_PLACEHOLDER_STYLE = '<style type="text/css">' . self::IFRAME_PLACEHOLDER_CLASS . '</style>';
31 +
32 + const IFRAME_TEMP_COMMENT = '/** optmliframelazyloadplaceholder */';
33 +
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';
14 35 /**
36 + * If frame lazyload is present on page.
37 + *
38 + * @var bool Whether or not at least one iframe has been lazyloaded.
39 + */
40 + private static $found_iframe = false;
41 + /**
15 42 * Cached object instance.
16 43 *
17 - * @var Optml_Tag_Replacer
44 + * @var Optml_Lazyload_Replacer
18 45 */
19 46 protected static $instance = null;
20 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 + /**
21 54 * Holds classes for listening to lazyload on background.
22 55 *
23 56 * @var array Lazyload background classes.
24 57 */
@@ -58,9 +91,9 @@
58 91 * Class instance method.
59 92 *
60 93 * @codeCoverageIgnore
61 94 * @static
62 - * @return Optml_Tag_Replacer
95 + * @return Optml_Lazyload_Replacer
63 96 * @since 1.0.0
64 97 * @access public
65 98 */
66 99 public static function instance() {
@@ -65,9 +98,9 @@
65 98 */
66 99 public static function instance() {
67 100 if ( null === self::$instance ) {
68 101 self::$instance = new self();
69 - add_action( 'optml_replacer_setup', array( self::$instance, 'init' ) );
102 + add_action( 'optml_replacer_setup', [ self::$instance, 'init' ] );
70 103 }
71 104
72 105 return self::$instance;
73 106 }
@@ -77,21 +110,26 @@
77 110 *
78 111 * @return array Lazyload selectors.
79 112 */
80 113 public static function get_background_lazyload_selectors() {
114 + if ( null !== self::$background_lazyload_selectors ) {
115 + return self::$background_lazyload_selectors;
116 + }
81 117
82 - if ( null != self::$background_lazyload_selectors && is_array( self::$background_lazyload_selectors ) ) {
118 + if ( self::instance()->settings === null ) {
119 + self::$background_lazyload_selectors = [];
120 +
83 121 return self::$background_lazyload_selectors;
84 122 }
85 123 if ( self::instance()->settings->get( 'bg_replacer' ) === 'disabled' ) {
86 124 self::$background_lazyload_selectors = [];
87 -
88 125 return self::$background_lazyload_selectors;
89 126 }
90 127 $default_watchers = [
91 - '.elementor-section[data-settings*="background_background"]',
92 - '.elementor-section > .elementor-background-overlay',
93 - '.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"]',
94 132 ];
95 133
96 134 $saved_watchers = self::instance()->settings->get_watchers();
97 135
@@ -116,10 +154,9 @@
116 154 *
117 155 * @return array
118 156 */
119 157 public static function get_lazyload_bg_classes() {
120 -
121 - if ( null != self::$lazyload_background_classes && is_array( self::$lazyload_background_classes ) ) {
158 + if ( null !== self::$lazyload_background_classes ) {
122 159 return self::$lazyload_background_classes;
123 160 }
124 161
125 162 self::$lazyload_background_classes = apply_filters( 'optml_lazyload_bg_classes', [] );
@@ -125,9 +162,22 @@
125 162 self::$lazyload_background_classes = apply_filters( 'optml_lazyload_bg_classes', [] );
126 163
127 164 return self::$lazyload_background_classes;
128 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() {
129 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 + }
130 180 /**
131 181 * Returns classes for lazyload additional watch.
132 182 *
133 183 * @return array
@@ -132,10 +182,9 @@
132 182 *
133 183 * @return array
134 184 */
135 185 public static function get_watcher_lz_classes() {
136 -
137 - if ( null != self::$lazyload_watcher_classes && is_array( self::$lazyload_watcher_classes ) ) {
186 + if ( null !== self::$lazyload_watcher_classes ) {
138 187 return self::$lazyload_watcher_classes;
139 188 }
140 189
141 190 self::$lazyload_watcher_classes = apply_filters( 'optml_watcher_lz_classes', [] );
@@ -151,26 +200,45 @@
151 200
152 201 if ( ! $this->settings->use_lazyload() ) {
153 202 return;
154 203 }
155 - 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 ] ) ) {
156 206 return;
157 207 }
158 208
159 - add_filter(
160 - 'max_srcset_image_width',
161 - function () {
162 - return 1;
163 - }
164 - );
165 209 self::$is_lazyload_placeholder = self::$instance->settings->get( 'lazyload_placeholder' ) === 'enabled';
166 210
167 - add_filter( 'optml_tag_replace', array( $this, 'lazyload_tag_replace' ), 2, 6 );
211 + add_filter( 'optml_tag_replace', [ $this, 'lazyload_tag_replace' ], 2, 6 );
168 212
169 - add_filter( 'optml_video_replace', array($this, 'lazyload_video_replace'), 2, 1 );
213 + add_filter( 'optml_video_replace', [ $this, 'lazyload_video_replace' ], 2, 1 );
214 + }
215 + /**
216 + * Check if there are lazyloaded iframes.
217 + *
218 + * @return bool Whether an iframe was lazyloaded on the page or not.
219 + */
220 + public static function found_iframe() {
221 + return self::$found_iframe;
222 + }
170 223
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;
171 240 }
172 -
173 241 /**
174 242 * Replaces the tags with lazyload tags.
175 243 *
176 244 * @param string $new_tag The new tag.
@@ -184,16 +252,49 @@
184 252 */
185 253 public function lazyload_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed = false, $full_tag = '' ) {
186 254
187 255 if ( ! $this->can_lazyload_for( $original_url, $full_tag ) ) {
256 +
188 257 return Optml_Tag_Replacer::instance()->regular_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed );
189 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 +
190 291 $should_ignore_rescale = ! $this->is_valid_mimetype_from_url( $original_url, [ 'gif' => true, 'svg' => true ] );
191 292
192 293 if ( ! self::$is_lazyload_placeholder && ! $should_ignore_rescale ) {
193 294 $optml_args['quality'] = 'eco';
194 295 $optml_args['resize'] = [];
195 - $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 );
196 297 $low_url = $is_slashed ? addcslashes( $low_url, '/' ) : $low_url;
197 298 } else {
198 299 $low_url = $this->get_svg_for(
199 300 isset( $optml_args['width'] ) ? $optml_args['width'] : '100%',
@@ -200,20 +301,28 @@
200 301 isset( $optml_args['height'] ) ? $optml_args['height'] : '100%',
201 302 ( $should_ignore_rescale ? null : $original_url )
202 303 );
203 304 }
204 -
205 305 $opt_format = '';
206 306
207 307 if ( $this->should_add_data_tag( $full_tag ) ) {
208 308 $opt_format = ' data-opt-src="%s" ';
309 + $custom_class = '';
310 +
209 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 ) ) {
210 319 if ( strpos( $new_tag, 'class=' ) === false ) {
211 - $opt_format .= ' class="optimole-lazy-only" ';
320 + $opt_format .= ' class="' . $custom_class . '" ';
212 321 } else {
213 322 $new_tag = str_replace(
214 323 ( $is_slashed ? 'class=\"' : 'class="' ),
215 - ( $is_slashed ? 'class=\"optimole-lazy-only ' : 'class="optimole-lazy-only ' ),
324 + ( $is_slashed ? 'class=\"' . $custom_class . ' ' : 'class="' . $custom_class . ' ' ),
216 325 $new_tag
217 326 );
218 327 }
219 328 }
@@ -239,12 +348,16 @@
239 348 ],
240 349 $new_tag,
241 350 1
242 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.
243 358 $new_tag = str_replace( 'srcset=', 'old-srcset=', $new_tag );
244 - if ( strpos( $new_tag, 'loading=' ) === false && self::instance()->settings->get( 'native_lazyload' ) === 'enabled' ) {
245 - $new_tag = preg_replace( '/<img/im', $is_slashed ? '<img loading=\"lazy\"' : '<img loading="lazy"', $new_tag );
246 - }
359 +
247 360 if ( ! $this->should_add_noscript( $new_tag ) ) {
248 361 return $new_tag;
249 362 }
250 363
@@ -257,30 +370,49 @@
257 370 *
258 371 * @return string
259 372 */
260 373 public function lazyload_video_replace( $content ) {
261 - $video_tags = array();
262 - preg_match_all( '#(?:<noscript\s*>(\s*|.*?))?<iframe(.*?)></iframe>#is', $content, $video_tags );
374 + $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] );
263 380
264 - $search = array();
265 - $replace = array();
266 - foreach ( $video_tags[0] as $video_tag ) {
381 + $search = [];
382 + $replace = [];
383 +
384 + foreach ( $video_tags as $video_tag ) {
385 +
267 386 if ( ! $this->should_lazyload_iframe( $video_tag ) ) {
268 387 continue;
269 388 }
270 - if ( preg_match( "/ data-opt-video-src=['\"]/is", $video_tag ) ) {
389 + if ( preg_match( "/ data-opt-src=['\"]/is", $video_tag ) ) {
271 390 continue;
272 391 }
273 - array_push( $search, $video_tag );
274 - $no_script = $video_tag;
392 + $should_add_noscript = true;
393 + $original_tag = $video_tag;
275 394 // replace the src and add the data-opt-src attribute
276 - $video_tag = preg_replace( '/iframe(.*?)src=/is', 'iframe$1 src="about:blank" data-opt-src=', $video_tag );
277 -
278 - if ( $this->should_add_noscript( $video_tag ) ) {
279 - $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 + }
280 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 );
281 413 array_push( $replace, $video_tag );
282 -
414 + self::$found_iframe = true;
283 415 }
284 416 $search = array_unique( $search );
285 417 $replace = array_unique( $replace );
286 418 $content = str_replace( $search, $replace, $content );
@@ -295,8 +427,11 @@
295 427 *
296 428 * @return bool We can lazyload?
297 429 */
298 430 public function can_lazyload_for( $url, $tag = '' ) {
431 + if ( OPTML_DEBUG ) {
432 + do_action( 'optml_log', 'can_lazyload_for: ' . $url . ' ' . $tag );
433 + }
299 434 foreach ( self::possible_lazyload_flags() as $banned_string ) {
300 435 if ( strpos( $tag, $banned_string ) !== false ) {
301 436 return false;
302 437 }
@@ -310,9 +445,9 @@
310 445 basename( $url ),
311 446 Optml_Config::$image_extensions
312 447 );
313 448
314 - if ( ! isset( $type['ext'] ) || empty( $type['ext'] ) ) {
449 + if ( empty( $type['ext'] ) ) {
315 450 return false;
316 451 }
317 452
318 453 if ( false === Optml_Filters::should_do_extension( self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_EXT ], $type['ext'] ) ) {
@@ -322,8 +457,36 @@
322 457 if ( defined( 'OPTML_DISABLE_PNG_LAZYLOAD' ) && OPTML_DISABLE_PNG_LAZYLOAD ) {
323 458 return $type['ext'] !== 'png';
324 459 }
325 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 + }
326 489 return true;
327 490 }
328 491
329 492 /**
@@ -345,10 +508,10 @@
345 508
346 509 /**
347 510 * Get SVG markup with specific width/height.
348 511 *
349 - * @param int $width Markup Width.
350 - * @param int $height Markup Height.
512 + * @param int|string $width Markup Width.
513 + * @param int|string $height Markup Height.
351 514 * @param string|null $url Original URL.
352 515 *
353 516 * @return string SVG code.
354 517 */
@@ -365,23 +528,39 @@
365 528 $sizes = getimagesize( $filepath );
366 529 wp_cache_add( $key, [ $sizes[0], $sizes[1] ], 'optml_sources', DAY_IN_SECONDS );
367 530 }
368 531 }
369 - 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 + }
370 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 + }
371 545
372 546 $width = ! is_numeric( $width ) ? '100%' : $width;
373 547 $height = ! is_numeric( $height ) ? '100%' : $height;
548 + $fill = $this->settings->get( 'placeholder_color' );
374 549
375 - return
376 - str_replace(
377 - [ '#width#', '#height#' ],
378 - [
379 - $width,
380 - $height,
381 - ],
382 - self::SVG_PLACEHOLDER
383 - );
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 + );
384 563 }
385 564
386 565 /**
387 566 * Check if we should add the noscript tag.
@@ -390,8 +569,11 @@
390 569 *
391 570 * @return bool Should add?
392 571 */
393 572 public function should_add_noscript( $tag ) {
573 + if ( $this->settings->get( 'no_script' ) === 'disabled' ) {
574 + return false;
575 + }
394 576 foreach ( self::get_ignore_noscript_flags() as $banned_string ) {
395 577 if ( strpos( $tag, $banned_string ) !== false ) {
396 578 return false;
397 579 }
@@ -406,8 +588,9 @@
406 588 *
407 589 * @return bool Should add?
408 590 */
409 591 public function should_lazyload_iframe( $tag ) {
592 +
410 593 foreach ( self::get_iframe_lazyload_flags() as $banned_string ) {
411 594 if ( strpos( $tag, $banned_string ) !== false ) {
412 595 return false;
413 596 }
@@ -421,14 +604,13 @@
421 604 *
422 605 * @return array
423 606 */
424 607 public static function get_ignore_noscript_flags() {
425 -
426 - if ( null != self::$ignore_no_script_flags && is_array( self::$ignore_no_script_flags ) ) {
608 + if ( null !== self::$ignore_no_script_flags ) {
427 609 return self::$ignore_no_script_flags;
428 610 }
429 611
430 - self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [] );
612 + self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [ '<noscript' ] );
431 613
432 614 return self::$ignore_no_script_flags;
433 615 }
434 616 /**
@@ -436,14 +618,14 @@
436 618 *
437 619 * @return array
438 620 */
439 621 public static function get_iframe_lazyload_flags() {
440 -
441 - if ( null != self::$iframe_lazyload_flags && is_array( self::$iframe_lazyload_flags ) ) {
622 + if ( null !== self::$iframe_lazyload_flags ) {
442 623 return self::$iframe_lazyload_flags;
443 624 }
444 625
445 - self::$iframe_lazyload_flags = apply_filters( 'optml_iframe_lazyload_flags', [ 'gform_ajax_frame', '<noscript' ] );
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' ] ) );
446 628
447 629 return self::$iframe_lazyload_flags;
448 630 }
449 631 /**
@@ -473,6 +655,5 @@
473 655 public function __wakeup() {
474 656 // Unserializing instances of the class is forbidden.
475 657 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
476 658 }
477 -
478 659 }