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 +100 -28 3.11.2 → 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;
@@ -110,16 +114,22 @@
110 114 if ( null !== self::$background_lazyload_selectors ) {
111 115 return self::$background_lazyload_selectors;
112 116 }
113 117
118 + if ( self::instance()->settings === null ) {
119 + self::$background_lazyload_selectors = [];
120 +
121 + return self::$background_lazyload_selectors;
122 + }
114 123 if ( self::instance()->settings->get( 'bg_replacer' ) === 'disabled' ) {
115 124 self::$background_lazyload_selectors = [];
116 125 return self::$background_lazyload_selectors;
117 126 }
118 127 $default_watchers = [
119 - '.elementor-section[data-settings*="background_background"]',
120 - '.elementor-section > .elementor-background-overlay',
121 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"]',
122 132 ];
123 133
124 134 $saved_watchers = self::instance()->settings->get_watchers();
125 135
@@ -162,8 +172,9 @@
162 172
163 173 if ( self::$skip_lazyload_images !== null ) {
164 174 return self::$skip_lazyload_images;
165 175 }
176 +
166 177 self::$skip_lazyload_images = apply_filters( 'optml_lazyload_images_skip', self::instance()->settings->get( 'skip_lazyload_images' ) );
167 178 return self::$skip_lazyload_images;
168 179 }
169 180 /**
@@ -194,20 +205,13 @@
194 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 ] ) ) {
195 206 return;
196 207 }
197 208
198 - add_filter(
199 - 'max_srcset_image_width',
200 - function () {
201 - return 1;
202 - }
203 - );
204 209 self::$is_lazyload_placeholder = self::$instance->settings->get( 'lazyload_placeholder' ) === 'enabled';
205 210
206 211 add_filter( 'optml_tag_replace', [ $this, 'lazyload_tag_replace' ], 2, 6 );
207 212
208 - add_filter( 'optml_video_replace', [$this, 'lazyload_video_replace'], 2, 1 );
209 -
213 + add_filter( 'optml_video_replace', [ $this, 'lazyload_video_replace' ], 2, 1 );
210 214 }
211 215 /**
212 216 * Check if there are lazyloaded iframes.
213 217 *
@@ -248,24 +252,49 @@
248 252 */
249 253 public function lazyload_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed = false, $full_tag = '' ) {
250 254
251 255 if ( ! $this->can_lazyload_for( $original_url, $full_tag ) ) {
256 +
252 257 return Optml_Tag_Replacer::instance()->regular_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed );
253 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 + }
254 273
255 274 if ( self::instance()->settings->get( 'native_lazyload' ) === 'enabled' ) {
256 - if ( strpos( $new_tag, 'loading=' ) === false ) {
257 - $new_tag = preg_replace( '/<img/im', $is_slashed ? '<img loading=\"lazy\"' : '<img loading="lazy"', $new_tag );
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 );
258 284 }
259 285 return $new_tag;
260 286 }
261 287
288 + // we remove the loading attribute if it exists with strpos
289 + $new_tag = preg_replace( '/loading=(["\'])[^"\']*\1/i', '', $new_tag );
290 +
262 291 $should_ignore_rescale = ! $this->is_valid_mimetype_from_url( $original_url, [ 'gif' => true, 'svg' => true ] );
263 292
264 293 if ( ! self::$is_lazyload_placeholder && ! $should_ignore_rescale ) {
265 294 $optml_args['quality'] = 'eco';
266 295 $optml_args['resize'] = [];
267 - $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 );
268 297 $low_url = $is_slashed ? addcslashes( $low_url, '/' ) : $low_url;
269 298 } else {
270 299 $low_url = $this->get_svg_for(
271 300 isset( $optml_args['width'] ) ? $optml_args['width'] : '100%',
@@ -272,9 +301,8 @@
272 301 isset( $optml_args['height'] ) ? $optml_args['height'] : '100%',
273 302 ( $should_ignore_rescale ? null : $original_url )
274 303 );
275 304 }
276 -
277 305 $opt_format = '';
278 306
279 307 if ( $this->should_add_data_tag( $full_tag ) ) {
280 308 $opt_format = ' data-opt-src="%s" ';
@@ -320,8 +348,14 @@
320 348 ],
321 349 $new_tag,
322 350 1
323 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.
324 358 $new_tag = str_replace( 'srcset=', 'old-srcset=', $new_tag );
325 359
326 360 if ( ! $this->should_add_noscript( $new_tag ) ) {
327 361 return $new_tag;
@@ -393,8 +427,11 @@
393 427 *
394 428 * @return bool We can lazyload?
395 429 */
396 430 public function can_lazyload_for( $url, $tag = '' ) {
431 + if ( OPTML_DEBUG ) {
432 + do_action( 'optml_log', 'can_lazyload_for: ' . $url . ' ' . $tag );
433 + }
397 434 foreach ( self::possible_lazyload_flags() as $banned_string ) {
398 435 if ( strpos( $tag, $banned_string ) !== false ) {
399 436 return false;
400 437 }
@@ -419,9 +456,35 @@
419 456
420 457 if ( defined( 'OPTML_DISABLE_PNG_LAZYLOAD' ) && OPTML_DISABLE_PNG_LAZYLOAD ) {
421 458 return $type['ext'] !== 'png';
422 459 }
423 - if ( Optml_Tag_Replacer::$lazyload_skipped_images < self::get_skip_lazyload_limit() ) {
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 + ) {
424 487 return false;
425 488 }
426 489 return true;
427 490 }
@@ -465,10 +528,21 @@
465 528 $sizes = getimagesize( $filepath );
466 529 wp_cache_add( $key, [ $sizes[0], $sizes[1] ], 'optml_sources', DAY_IN_SECONDS );
467 530 }
468 531 }
469 - 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 + }
470 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 + }
471 545
472 546 $width = ! is_numeric( $width ) ? '100%' : $width;
473 547 $height = ! is_numeric( $height ) ? '100%' : $height;
474 548 $fill = $this->settings->get( 'placeholder_color' );
@@ -476,18 +550,17 @@
476 550 if ( empty( $fill ) ) {
477 551 $fill = 'transparent';
478 552 }
479 553
480 - return
481 - str_replace(
482 - [ '#width#', '#height#', '#fill#' ],
483 - [
484 - $width,
485 - $height,
486 - urlencode( $fill ),
487 - ],
488 - self::SVG_PLACEHOLDER
489 - );
554 + return str_replace(
555 + [ '#width#', '#height#', '#fill#' ],
556 + [
557 + $width,
558 + $height,
559 + urlencode( $fill ),
560 + ],
561 + self::SVG_PLACEHOLDER
562 + );
490 563 }
491 564
492 565 /**
493 566 * Check if we should add the noscript tag.
@@ -535,9 +608,9 @@
535 608 if ( null !== self::$ignore_no_script_flags ) {
536 609 return self::$ignore_no_script_flags;
537 610 }
538 611
539 - self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [] );
612 + self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [ '<noscript' ] );
540 613
541 614 return self::$ignore_no_script_flags;
542 615 }
543 616 /**
@@ -582,6 +655,5 @@
582 655 public function __wakeup() {
583 656 // Unserializing instances of the class is forbidden.
584 657 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
585 658 }
586 -
587 659 }