| @@ -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’ huh?', 'optimole-wp' ), '1.0.0' ); |
| 585 | 658 | } |
| 586 | - | |
| 587 | 659 | } |