| @@ -10,8 +10,10 @@ | ||
| 10 | 10 | namespace PoweredCache; |
| 11 | 11 | |
| 12 | 12 | use const PoweredCache\Constants\POST_META_DISABLE_LAZYLOAD_KEY; |
| 13 | 13 | |
| 14 | +// phpcs:disable WordPressVIPMinimum.Security.ProperEscapingFunction.hrefSrcEscUrl | |
| 15 | + | |
| 14 | 16 | /** |
| 15 | 17 | * Class LazyLoad |
| 16 | 18 | */ |
| 17 | 19 | class LazyLoad { |
| @@ -20,9 +22,9 @@ | ||
| 20 | 22 | * Hold plugin settings |
| 21 | 23 | * |
| 22 | 24 | * @var array $settings |
| 23 | 25 | */ |
| 24 | - private $settings; | |
| 26 | + private static $settings = null; | |
| 25 | 27 | |
| 26 | 28 | /** |
| 27 | 29 | * Return an instance of the current class |
| 28 | 30 | * |
| @@ -42,14 +44,18 @@ | ||
| 42 | 44 | /** |
| 43 | 45 | * Setup routine |
| 44 | 46 | */ |
| 45 | 47 | public function setup() { |
| 46 | - $this->settings = \PoweredCache\Utils\get_settings(); | |
| 48 | + if ( ! self::$settings ) { | |
| 49 | + self::$settings = \PoweredCache\Utils\get_settings(); | |
| 50 | + } | |
| 47 | 51 | |
| 48 | - if ( $this->settings['enable_lazy_load'] ) { | |
| 52 | + if ( self::$settings['enable_lazy_load'] ) { | |
| 49 | 53 | add_action( 'wp', [ $this, 'init' ], 9999 ); // run this as late as possible |
| 50 | 54 | add_action( 'powered_cache_lazy_load_compat', [ $this, 'compat' ] ); |
| 51 | 55 | add_action( 'powered_cache_lazy_load_run_filter', [ $this, 'maybe_disable_through_meta' ] ); |
| 56 | + add_filter( 'powered_cache_delayed_js_skip', [ $this, 'delayed_js_skip' ], 10, 2 ); | |
| 57 | + add_filter( 'powered_cache_fo_excluded_js_files', [ $this, 'add_file_optimizer_exclusion' ] ); | |
| 52 | 58 | } |
| 53 | 59 | |
| 54 | 60 | /** |
| 55 | 61 | * Filter to disable native lazyload support. |
| @@ -60,10 +66,10 @@ | ||
| 60 | 66 | * |
| 61 | 67 | * @return {boolean} New value. |
| 62 | 68 | * @since 2.0 |
| 63 | 69 | */ |
| 64 | - if ( apply_filters( 'powered_cache_disable_native_lazyload', $this->settings['disable_wp_lazy_load'] ) ) { | |
| 65 | - add_filter( 'wp_lazy_loading_enabled', '__return_false' ); | |
| 70 | + if ( apply_filters( 'powered_cache_disable_native_lazyload', self::$settings['disable_wp_lazy_load'] ) ) { | |
| 71 | + add_filter( 'wp_lazy_loading_enabled', '__return_false', 9999 ); | |
| 66 | 72 | } |
| 67 | 73 | |
| 68 | 74 | } |
| 69 | 75 | |
| @@ -121,11 +127,37 @@ | ||
| 121 | 127 | * @since 1.0 |
| 122 | 128 | */ |
| 123 | 129 | $threshold = apply_filters( 'powered_cache_lazy_load_threshold', 200 ); |
| 124 | 130 | |
| 125 | - if ( 200 !== (int) $threshold ) { | |
| 126 | - wp_localize_script( 'PCLL', 'PCLL_options', array( 'threshold' => $threshold ) ); | |
| 131 | + /** | |
| 132 | + * Filters the count of images that skipped from lazyload. | |
| 133 | + * | |
| 134 | + * @hook powered_cache_lazy_load_skip_first_nth_img | |
| 135 | + * | |
| 136 | + * @param {int} $immediate_load_count Default image count | |
| 137 | + * | |
| 138 | + * @return {int} New value | |
| 139 | + * @since 3.1 | |
| 140 | + */ | |
| 141 | + $immediate_load_count = apply_filters( 'powered_cache_lazy_load_skip_first_nth_img', self::$settings['lazy_load_skip_first_nth_img'] ); | |
| 142 | + | |
| 143 | + if ( 200 !== (int) $threshold || 3 !== (int) $immediate_load_count ) { | |
| 144 | + wp_localize_script( | |
| 145 | + 'PCLL', | |
| 146 | + 'PCLL_options', | |
| 147 | + [ | |
| 148 | + 'threshold' => $threshold, | |
| 149 | + 'immediate_load_count' => $immediate_load_count, | |
| 150 | + ] | |
| 151 | + ); | |
| 127 | 152 | } |
| 153 | + | |
| 154 | + if ( self::$settings['lazy_load_youtube'] ) { | |
| 155 | + wp_register_style( 'pcll-youtube-lazyload', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion | |
| 156 | + wp_enqueue_style( 'pcll-youtube-lazyload' ); | |
| 157 | + wp_add_inline_style( 'pcll-youtube-lazyload', $this->add_inline_youtube_css() ); | |
| 158 | + } | |
| 159 | + | |
| 128 | 160 | } |
| 129 | 161 | |
| 130 | 162 | /** |
| 131 | 163 | * Set up filtering for certain content |
| @@ -141,9 +173,9 @@ | ||
| 141 | 173 | * |
| 142 | 174 | * @return {boolean} New value. |
| 143 | 175 | * @since 1.0 |
| 144 | 176 | */ |
| 145 | - if ( true === apply_filters( 'powered_cache_lazy_load_images', $this->settings['lazy_load_images'] ) ) { | |
| 177 | + if ( true === apply_filters( 'powered_cache_lazy_load_images', self::$settings['lazy_load_images'] ) ) { | |
| 146 | 178 | add_filter( 'powered_cache_lazy_load_filter', array( __CLASS__, 'filter_images' ) ); |
| 147 | 179 | } |
| 148 | 180 | |
| 149 | 181 | /** |
| @@ -155,9 +187,9 @@ | ||
| 155 | 187 | * |
| 156 | 188 | * @return {boolean} New value. |
| 157 | 189 | * @since 1.0 |
| 158 | 190 | */ |
| 159 | - if ( true === apply_filters( 'powered_cache_lazy_load_iframes', $this->settings['lazy_load_iframes'] ) ) { | |
| 191 | + if ( true === apply_filters( 'powered_cache_lazy_load_iframes', self::$settings['lazy_load_iframes'] ) ) { | |
| 160 | 192 | add_filter( 'powered_cache_lazy_load_filter', array( __CLASS__, 'filter_iframes' ) ); |
| 161 | 193 | } |
| 162 | 194 | |
| 163 | 195 | /** |
| @@ -169,9 +201,9 @@ | ||
| 169 | 201 | * |
| 170 | 202 | * @return {boolean} New value. |
| 171 | 203 | * @since 1.0 |
| 172 | 204 | */ |
| 173 | - if ( true === apply_filters( 'powered_cache_lazy_load_post_content', $this->settings['lazy_load_post_content'] ) ) { | |
| 205 | + if ( true === apply_filters( 'powered_cache_lazy_load_post_content', self::$settings['lazy_load_post_content'] ) ) { | |
| 174 | 206 | add_filter( 'the_content', array( __CLASS__, 'filter' ), 200 ); |
| 175 | 207 | } |
| 176 | 208 | |
| 177 | 209 | /** |
| @@ -183,9 +215,9 @@ | ||
| 183 | 215 | * |
| 184 | 216 | * @return {boolean} New value. |
| 185 | 217 | * @since 1.0 |
| 186 | 218 | */ |
| 187 | - if ( true === apply_filters( 'powered_cache_lazy_load_widget_text', $this->settings['lazy_load_widgets'] ) ) { | |
| 219 | + if ( true === apply_filters( 'powered_cache_lazy_load_widget_text', self::$settings['lazy_load_widgets'] ) ) { | |
| 188 | 220 | add_filter( 'widget_text', array( __CLASS__, 'filter' ), 200 ); |
| 189 | 221 | } |
| 190 | 222 | |
| 191 | 223 | /** |
| @@ -197,13 +229,27 @@ | ||
| 197 | 229 | * |
| 198 | 230 | * @return {boolean} New value. |
| 199 | 231 | * @since 1.0 |
| 200 | 232 | */ |
| 201 | - if ( true === apply_filters( 'powered_cache_lazy_load_post_thumbnail', $this->settings['lazy_load_post_thumbnail'] ) ) { | |
| 233 | + if ( true === apply_filters( 'powered_cache_lazy_load_post_thumbnail', self::$settings['lazy_load_post_thumbnail'] ) ) { | |
| 202 | 234 | add_filter( 'post_thumbnail_html', array( __CLASS__, 'filter' ), 200 ); |
| 203 | 235 | } |
| 204 | 236 | |
| 205 | 237 | /** |
| 238 | + * Filters whether replace youtube iframe with thumbnail. | |
| 239 | + * | |
| 240 | + * @hook powered_cache_lazy_load_youtube | |
| 241 | + * | |
| 242 | + * @param {boolean} true to replace youtube iframe with thumbnail | |
| 243 | + * | |
| 244 | + * @return {boolean} New value. | |
| 245 | + * @since 3.4 | |
| 246 | + */ | |
| 247 | + if ( true === apply_filters( 'powered_cache_lazy_load_youtube', self::$settings['lazy_load_youtube'] ) ) { | |
| 248 | + add_filter( 'powered_cache_lazy_load_filter', array( __CLASS__, 'replace_youtube_iframe_with_thumbnail' ), 9 ); | |
| 249 | + } | |
| 250 | + | |
| 251 | + /** | |
| 206 | 252 | * Filters whether apply or not apply lazyload for avatars. |
| 207 | 253 | * |
| 208 | 254 | * @hook powered_cache_lazy_load_avatar |
| 209 | 255 | * |
| @@ -211,12 +257,22 @@ | ||
| 211 | 257 | * |
| 212 | 258 | * @return {boolean} New value. |
| 213 | 259 | * @since 1.0 |
| 214 | 260 | */ |
| 215 | - if ( true === apply_filters( 'powered_cache_lazy_load_avatar', $this->settings['lazy_load_avatars'] ) ) { | |
| 261 | + if ( true === apply_filters( 'powered_cache_lazy_load_avatar', self::$settings['lazy_load_avatars'] ) ) { | |
| 216 | 262 | add_filter( 'get_avatar', array( __CLASS__, 'filter' ), 200 ); |
| 217 | 263 | } |
| 218 | 264 | |
| 265 | + /** | |
| 266 | + * * Filters whether apply or not apply lazyload for html. | |
| 267 | + * | |
| 268 | + * @hook powered_cache_lazy_load_html | |
| 269 | + * | |
| 270 | + * @param {boolean} true to apply lazyload for html | |
| 271 | + * | |
| 272 | + * @return {boolean} New value. | |
| 273 | + * @since 1.0 | |
| 274 | + */ | |
| 219 | 275 | add_filter( 'powered_cache_lazy_load_html', array( __CLASS__, 'filter' ) ); |
| 220 | 276 | } |
| 221 | 277 | |
| 222 | 278 | /** |
| @@ -287,8 +343,11 @@ | ||
| 287 | 343 | $search = array(); |
| 288 | 344 | $replace = array(); |
| 289 | 345 | |
| 290 | 346 | foreach ( $matches[0] as $img_html ) { |
| 347 | + if ( self::is_excluded( $img_html ) ) { | |
| 348 | + continue; | |
| 349 | + } | |
| 291 | 350 | |
| 292 | 351 | // don't to the replacement if the image is a data-uri |
| 293 | 352 | if ( ! preg_match( "/src=['\"]data:image/is", $img_html ) ) { |
| 294 | 353 | |
| @@ -348,8 +407,12 @@ | ||
| 348 | 407 | $replace = array(); |
| 349 | 408 | |
| 350 | 409 | foreach ( $matches[0] as $iframe_html ) { |
| 351 | 410 | |
| 411 | + if ( self::is_excluded( $iframe_html ) ) { | |
| 412 | + continue; | |
| 413 | + } | |
| 414 | + | |
| 352 | 415 | // Don't mess with the Gravity Forms ajax iframe |
| 353 | 416 | if ( strpos( $iframe_html, 'gform_ajax_frame' ) ) { |
| 354 | 417 | continue; |
| 355 | 418 | } |
| @@ -458,9 +521,9 @@ | ||
| 458 | 521 | if ( function_exists( 'mopr_get_option' ) && WP_CONTENT_DIR . mopr_get_option( 'mobile_theme_root', 1 ) === get_theme_root() ) { |
| 459 | 522 | add_filter( 'powered_cache_lazy_load_enabled', '__return_false' ); |
| 460 | 523 | } |
| 461 | 524 | |
| 462 | - if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && false !== strpos( $_SERVER['HTTP_USER_AGENT'], 'Opera Mini' ) ) { | |
| 525 | + if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && false !== strpos( $_SERVER['HTTP_USER_AGENT'], 'Opera Mini' ) ) { // phpcs:ignore | |
| 463 | 526 | add_filter( 'powered_cache_lazy_load_enabled', '__return_false' ); |
| 464 | 527 | } |
| 465 | 528 | |
| 466 | 529 | if ( 1 === intval( get_query_var( 'print' ) ) || 1 === intval( get_query_var( 'printpage' ) ) ) { |
| @@ -486,8 +549,166 @@ | ||
| 486 | 549 | $status = false; |
| 487 | 550 | } |
| 488 | 551 | |
| 489 | 552 | return $status; |
| 553 | + } | |
| 554 | + | |
| 555 | + /** | |
| 556 | + * Skip lazyload for delayed script | |
| 557 | + * | |
| 558 | + * @param boolean $is_delay_skipped Whether skip or not skip delayed JS | |
| 559 | + * @param string $script script | |
| 560 | + * | |
| 561 | + * @return boolean | |
| 562 | + */ | |
| 563 | + public function delayed_js_skip( $is_delay_skipped, $script ) { | |
| 564 | + if ( false !== stripos( $script, 'powered-cache/dist/js/lazyload.js' ) || false !== stripos( $script, 'PCLL_' ) ) { | |
| 565 | + return true; | |
| 566 | + } | |
| 567 | + | |
| 568 | + return $is_delay_skipped; | |
| 569 | + } | |
| 570 | + | |
| 571 | + /** | |
| 572 | + * Exclude link preloader from file optimizer | |
| 573 | + * | |
| 574 | + * @param array $excluded_files the list of excluded files for optimization | |
| 575 | + * | |
| 576 | + * @return mixed | |
| 577 | + */ | |
| 578 | + public function add_file_optimizer_exclusion( $excluded_files ) { | |
| 579 | + $excluded_files[] = POWERED_CACHE_URL . 'dist/js/lazyload.js'; | |
| 580 | + | |
| 581 | + return $excluded_files; | |
| 582 | + } | |
| 583 | + | |
| 584 | + /** | |
| 585 | + * Replace youtube iframe with thumbnail | |
| 586 | + * | |
| 587 | + * @param string $content HTML content, or the content of the current post if called in the loop. | |
| 588 | + * | |
| 589 | + * @return array|string|string[]|null | |
| 590 | + * @since 3.4 | |
| 591 | + */ | |
| 592 | + public static function replace_youtube_iframe_with_thumbnail( $content ) { | |
| 593 | + $match_content = self::get_content_haystack( $content ); | |
| 594 | + | |
| 595 | + // Regular expression to match YouTube iframes | |
| 596 | + $pattern = '/<iframe[^>]+src="https?:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]+)([^"]*)"[^>]*><\/iframe>/i'; | |
| 597 | + | |
| 598 | + // Find all YouTube iframes | |
| 599 | + preg_match_all( $pattern, $match_content, $matches ); | |
| 600 | + | |
| 601 | + $search = array(); | |
| 602 | + $replace = array(); | |
| 603 | + | |
| 604 | + foreach ( $matches[0] as $iframe_html ) { | |
| 605 | + if ( self::is_excluded( $iframe_html ) ) { | |
| 606 | + continue; | |
| 607 | + } | |
| 608 | + | |
| 609 | + // Extract video ID and additional parameters | |
| 610 | + preg_match( $pattern, $iframe_html, $iframe_parts ); | |
| 611 | + $video_id = $iframe_parts[1]; | |
| 612 | + $params = $iframe_parts[2]; | |
| 613 | + | |
| 614 | + // Construct the replacement HTML using the video ID | |
| 615 | + $replacement = '<div class="pcll-youtube-player" data-src="https://www.youtube.com/embed/' . $video_id . $params . '"> | |
| 616 | + <img src="https://img.youtube.com/vi/' . $video_id . '/0.jpg" style="width:100%;height:auto;"> | |
| 617 | + <div style="width: 100%; height: 100%;cursor:pointer;"> | |
| 618 | + <svg class="pcll-youtube-play-button" viewBox="0 0 68 48" style="position: absolute; left: 50%; top: 50%; width: 68px; height: 48px; margin-left: -34px; margin-top: -24px; transition: opacity .25s cubic-bezier(0,0,.2,1); z-index: 63; cursor: pointer;"> | |
| 619 | + <path d="M66.52,7.74c-0.78-2.93-3.07-5.22-6-6C53.08,0.74,34,0.74,34,0.74s-19.08,0-26.52,1C4.57,2.52,2.28,4.81,1.5,7.74C0.68,11,0.68,24,0.68,24s0,13,0.82,16.26c0.78,2.93,3.07,5.22,6,6c7.44,1,26.52,1,26.52,1s19.08,0,26.52-1c2.93-0.78,5.22-3.07,6-6C67.32,37,67.32,24,67.32,24S67.32,11,66.52,7.74z" fill-opacity="0.8" fill="#FF0000"></path> | |
| 620 | + <path d="M 45,24 27,14 27,34" fill="#fff"></path> | |
| 621 | + </svg> | |
| 622 | + </div> | |
| 623 | + </div>'; | |
| 624 | + | |
| 625 | + // Add original iframe HTML and replacement HTML to their respective arrays | |
| 626 | + array_push( $search, $iframe_html ); | |
| 627 | + array_push( $replace, $replacement ); | |
| 628 | + } | |
| 629 | + | |
| 630 | + // Replace all YouTube iframes with their corresponding thumbnail placeholders | |
| 631 | + $content = str_replace( $search, $replace, $content ); | |
| 632 | + | |
| 633 | + return $content; | |
| 634 | + } | |
| 635 | + | |
| 636 | + | |
| 637 | + /** | |
| 638 | + * Add inline css for youtube lazyload | |
| 639 | + * | |
| 640 | + * @return string | |
| 641 | + * @since 3.4 | |
| 642 | + */ | |
| 643 | + public function add_inline_youtube_css() { | |
| 644 | + $css = ''; | |
| 645 | + | |
| 646 | + $yt_lazyload = wp_normalize_path( POWERED_CACHE_PATH . 'dist/css/lazyload-youtube.css' ); | |
| 647 | + if ( file_exists( $yt_lazyload ) ) { | |
| 648 | + $css = (string) file_get_contents( $yt_lazyload ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents | |
| 649 | + } | |
| 650 | + | |
| 651 | + /** | |
| 652 | + * Filters the inline css for youtube lazyload | |
| 653 | + * | |
| 654 | + * @hook powered_cache_lazy_load_youtube_css | |
| 655 | + * | |
| 656 | + * @param string $css | |
| 657 | + * | |
| 658 | + * @return string | |
| 659 | + * @since 3.4 | |
| 660 | + */ | |
| 661 | + $css = apply_filters( 'powered_cache_lazy_load_youtube_css', $css ); | |
| 662 | + | |
| 663 | + return $css; | |
| 664 | + } | |
| 665 | + | |
| 666 | + | |
| 667 | + /** | |
| 668 | + * Get lazyload exclusion list | |
| 669 | + * | |
| 670 | + * @return array | |
| 671 | + * @since 3.4 | |
| 672 | + */ | |
| 673 | + public static function get_exclusions() { | |
| 674 | + if ( ! self::$settings ) { | |
| 675 | + self::$settings = \PoweredCache\Utils\get_settings(); | |
| 676 | + } | |
| 677 | + | |
| 678 | + $load_exclusions = preg_split( '#(\r\n|\n|\r)#', self::$settings['lazy_load_exclusions'], - 1, PREG_SPLIT_NO_EMPTY ); | |
| 679 | + $load_exclusions[] = 'selectors.core.image.lightboxObjectFit'; // exclude core lightboxed images | |
| 680 | + | |
| 681 | + /** | |
| 682 | + * Filter the lazyload exclusions | |
| 683 | + * | |
| 684 | + * @hook powered_cache_lazy_load_exclusions | |
| 685 | + * | |
| 686 | + * @param {array} $load_exclusions The current exclusions | |
| 687 | + * | |
| 688 | + * @return {array} New value | |
| 689 | + * @since 3.4 | |
| 690 | + */ | |
| 691 | + return (array) apply_filters( 'powered_cache_lazy_load_exclusions', $load_exclusions ); | |
| 692 | + } | |
| 693 | + | |
| 694 | + /** | |
| 695 | + * Check if excluded or not from defer | |
| 696 | + * | |
| 697 | + * @param string $tag Resource | |
| 698 | + * | |
| 699 | + * @return bool | |
| 700 | + * @since 3.4 | |
| 701 | + */ | |
| 702 | + public static function is_excluded( $tag ) { | |
| 703 | + $excluded_files = self::get_exclusions(); | |
| 704 | + $excluded_files = implode( '|', $excluded_files ); | |
| 705 | + | |
| 706 | + if ( ! empty( $excluded_files ) && preg_match( '#(' . $excluded_files . ')#', $tag ) ) { | |
| 707 | + return true; | |
| 708 | + } | |
| 709 | + | |
| 710 | + return false; | |
| 490 | 711 | } |
| 491 | 712 | |
| 492 | 713 | } |
| 493 | 714 | |