PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.1.2
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.1.2
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
powered-cache / includes / classes / LazyLoad.php

LazyLoad.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 2.1.2, at includes/classes/LazyLoad.php

494 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * LazyLoad
4 *
5 * Most of the code borrowed from https://github.com/Angrycreative/bj-lazy-load
6 *
7 * @package PoweredCache
8 */
9
10 namespace PoweredCache;
11
12 use const PoweredCache\Constants\POST_META_DISABLE_LAZYLOAD_KEY;
13
14 /**
15 * Class LazyLoad
16 */
17 class LazyLoad {
18
19 /**
20 * Hold plugin settings
21 *
22 * @var array $settings
23 */
24 private $settings;
25
26 /**
27 * Return an instance of the current class
28 *
29 * @return LazyLoad
30 */
31 public static function factory() {
32 static $instance = false;
33
34 if ( ! $instance ) {
35 $instance = new self();
36 $instance->setup();
37 }
38
39 return $instance;
40 }
41
42 /**
43 * Setup routine
44 */
45 public function setup() {
46 $this->settings = \PoweredCache\Utils\get_settings();
47
48 if ( $this->settings['enable_lazy_load'] ) {
49 add_action( 'wp', [ $this, 'init' ], 9999 ); // run this as late as possible
50 add_action( 'powered_cache_lazy_load_compat', [ $this, 'compat' ] );
51 add_action( 'powered_cache_lazy_load_run_filter', [ $this, 'maybe_disable_through_meta' ] );
52 }
53
54 /**
55 * Filter to disable native lazyload support.
56 *
57 * @hook powered_cache_disable_native_lazyload
58 *
59 * @param {boolean} $status true to disable native lazy load.
60 *
61 * @return {boolean} New value.
62 * @since 2.0
63 */
64 if ( apply_filters( 'powered_cache_disable_native_lazyload', $this->settings['disable_wp_lazy_load'] ) ) {
65 add_filter( 'wp_lazy_loading_enabled', '__return_false' );
66 }
67
68 }
69
70 /**
71 * Initialize the setup
72 */
73 public function init() {
74 /* We do not touch the feeds */
75 if ( is_admin() || is_feed() || is_preview() ) {
76 return;
77 }
78
79 /**
80 * Fires before filtering lazy-load hooks.
81 *
82 * @hook powered_cache_lazy_load_compat
83 *
84 * @since 1.0
85 */
86 do_action( 'powered_cache_lazy_load_compat' );
87
88 /**
89 * Filters lazy-load status.
90 *
91 * @hook powered_cache_lazy_load_enabled
92 *
93 * @param {boolean} $enable true to enable
94 *
95 * @return {boolean} New value
96 * @since 1.0
97 */
98 $enabled = apply_filters( 'powered_cache_lazy_load_enabled', true );
99
100 if ( $enabled ) {
101 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
102
103 $this->setup_filtering();
104 }
105 }
106
107 /**
108 * Enqueue scripts
109 */
110 public function enqueue_scripts() {
111 wp_enqueue_script( 'PCLL', POWERED_CACHE_URL . 'dist/js/lazyload.js', null, POWERED_CACHE_VERSION, true );
112
113 /**
114 * Filters screen threshold value.
115 *
116 * @hook powered_cache_lazy_load_threshold
117 *
118 * @param {int} $threshold Screen display threshold.
119 *
120 * @return {int} New value
121 * @since 1.0
122 */
123 $threshold = apply_filters( 'powered_cache_lazy_load_threshold', 200 );
124
125 if ( 200 !== (int) $threshold ) {
126 wp_localize_script( 'PCLL', 'PCLL_options', array( 'threshold' => $threshold ) );
127 }
128 }
129
130 /**
131 * Set up filtering for certain content
132 */
133 protected function setup_filtering() {
134
135 /**
136 * Filters whether apply or not apply lazyload for images.
137 *
138 * @hook powered_cache_lazy_load_images
139 *
140 * @param {boolean} true to lazyload for images
141 *
142 * @return {boolean} New value.
143 * @since 1.0
144 */
145 if ( true === apply_filters( 'powered_cache_lazy_load_images', $this->settings['lazy_load_images'] ) ) {
146 add_filter( 'powered_cache_lazy_load_filter', array( __CLASS__, 'filter_images' ) );
147 }
148
149 /**
150 * Filters whether apply or not apply lazyload for iframes.
151 *
152 * @hook powered_cache_lazy_load_iframes
153 *
154 * @param {boolean} true to apply lazyload for iframes
155 *
156 * @return {boolean} New value.
157 * @since 1.0
158 */
159 if ( true === apply_filters( 'powered_cache_lazy_load_iframes', $this->settings['lazy_load_iframes'] ) ) {
160 add_filter( 'powered_cache_lazy_load_filter', array( __CLASS__, 'filter_iframes' ) );
161 }
162
163 /**
164 * Filters whether apply or not apply lazyload for post_content.
165 *
166 * @hook powered_cache_lazy_load_post_content
167 *
168 * @param {boolean} true to apply lazyload for post_content
169 *
170 * @return {boolean} New value.
171 * @since 1.0
172 */
173 if ( true === apply_filters( 'powered_cache_lazy_load_post_content', $this->settings['lazy_load_post_content'] ) ) {
174 add_filter( 'the_content', array( __CLASS__, 'filter' ), 200 );
175 }
176
177 /**
178 * Filters whether apply or not apply lazyload for widgets.
179 *
180 * @hook powered_cache_lazy_load_widget_text
181 *
182 * @param {boolean} true to apply lazyload for widgets
183 *
184 * @return {boolean} New value.
185 * @since 1.0
186 */
187 if ( true === apply_filters( 'powered_cache_lazy_load_widget_text', $this->settings['lazy_load_widgets'] ) ) {
188 add_filter( 'widget_text', array( __CLASS__, 'filter' ), 200 );
189 }
190
191 /**
192 * Filters whether apply or not apply lazyload for thumbnails.
193 *
194 * @hook powered_cache_lazy_load_post_thumbnail
195 *
196 * @param {boolean} true to apply lazyload for thumbnails
197 *
198 * @return {boolean} New value.
199 * @since 1.0
200 */
201 if ( true === apply_filters( 'powered_cache_lazy_load_post_thumbnail', $this->settings['lazy_load_post_thumbnail'] ) ) {
202 add_filter( 'post_thumbnail_html', array( __CLASS__, 'filter' ), 200 );
203 }
204
205 /**
206 * Filters whether apply or not apply lazyload for avatars.
207 *
208 * @hook powered_cache_lazy_load_avatar
209 *
210 * @param {boolean} true to apply lazyload for avatars
211 *
212 * @return {boolean} New value.
213 * @since 1.0
214 */
215 if ( true === apply_filters( 'powered_cache_lazy_load_avatar', $this->settings['lazy_load_avatars'] ) ) {
216 add_filter( 'get_avatar', array( __CLASS__, 'filter' ), 200 );
217 }
218
219 add_filter( 'powered_cache_lazy_load_html', array( __CLASS__, 'filter' ) );
220 }
221
222 /**
223 * Filter HTML content. Replace supported content with placeholders.
224 *
225 * @param string $content The HTML string to filter
226 *
227 * @return string The filtered HTML string
228 */
229 public static function filter( $content ) {
230
231 // Last chance to bail out before running the filter
232 /**
233 * Filters lazy-load run filter.
234 *
235 * @hook powered_cache_lazy_load_run_filter
236 *
237 * @param {boolean} $run_filter true to enable
238 *
239 * @return {boolean} New value
240 * @since 1.0
241 */
242 $run_filter = apply_filters( 'powered_cache_lazy_load_run_filter', true );
243 if ( ! $run_filter ) {
244 return $content;
245 }
246
247 /**
248 * Filters the content
249 *
250 * @hook powered_cache_lazy_load_filter
251 *
252 * @param string $content The HTML string to filter
253 *
254 * @since 1.0
255 */
256 $content = apply_filters( 'powered_cache_lazy_load_filter', $content );
257
258 return $content;
259 }
260
261
262 /**
263 * Replace images with placeholders in the content
264 *
265 * @param string $content The HTML to do the filtering on
266 *
267 * @return string The HTML with the images replaced
268 */
269 public static function filter_images( $content ) {
270
271 /**
272 * Filters the content
273 *
274 * @hook powered_cache_lazy_load_filter
275 *
276 * @param string $content The HTML string to filter
277 *
278 * @since 1.0
279 */
280 $placeholder_url = apply_filters( 'powered_cache_lazy_load_placeholder_url', 'data:image/gif;base64,R0lGODdhAQABAPAAAP///wAAACwAAAAAAQABAEACAkQBADs=' );
281
282 $match_content = self::get_content_haystack( $content );
283
284 $matches = array();
285 preg_match_all( '/<img[\s\r\n]+.*?>/is', $match_content, $matches );
286
287 $search = array();
288 $replace = array();
289
290 foreach ( $matches[0] as $img_html ) {
291
292 // don't to the replacement if the image is a data-uri
293 if ( ! preg_match( "/src=['\"]data:image/is", $img_html ) ) {
294
295 $placeholder_url_used = $placeholder_url;
296
297 // replace the src and add the data-src attribute
298 $replace_html = preg_replace( '/<img(.*?)src=/is', '<img$1src="' . esc_attr( $placeholder_url_used ) . '" data-lazy-type="image" data-lazy-src=', $img_html );
299
300 // also replace the srcset (responsive images)
301 $replace_html = str_replace( 'srcset', 'data-lazy-srcset', $replace_html );
302
303 // add the lazy class to the img element
304 if ( preg_match( '/class=["\']/i', $replace_html ) ) {
305 $replace_html = preg_replace( '/class=(["\'])(.*?)["\']/is', 'class=$1lazy lazy-hidden $2$1', $replace_html );
306 } else {
307 $replace_html = preg_replace( '/<img/is', '<img class="lazy lazy-hidden"', $replace_html );
308 }
309
310 $replace_html .= '<noscript>' . $img_html . '</noscript>';
311
312 array_push( $search, $img_html );
313 array_push( $replace, $replace_html );
314 }
315 }
316
317 $content = str_replace( $search, $replace, $content );
318
319 return $content;
320
321 }
322
323 /**
324 * Replace iframes with placeholders in the content
325 *
326 * @param string $content The HTML to do the filtering on
327 *
328 * @return string The HTML with the iframes replaced
329 */
330 public static function filter_iframes( $content ) {
331 /**
332 * Filters the lazyload placeholder URL.
333 *
334 * @hook powered_cache_lazy_load_placeholder_url
335 *
336 * @param string $image default placeholder url. Base64 string as default.
337 *
338 * @since 1.0
339 */
340 $placeholder_url = apply_filters( 'powered_cache_lazy_load_placeholder_url', 'data:image/gif;base64,R0lGODdhAQABAPAAAP///wAAACwAAAAAAQABAEACAkQBADs=' );
341
342 $match_content = self::get_content_haystack( $content );
343
344 $matches = array();
345 preg_match_all( '|<iframe\s+.*?</iframe>|si', $match_content, $matches );
346
347 $search = array();
348 $replace = array();
349
350 foreach ( $matches[0] as $iframe_html ) {
351
352 // Don't mess with the Gravity Forms ajax iframe
353 if ( strpos( $iframe_html, 'gform_ajax_frame' ) ) {
354 continue;
355 }
356
357 $replace_html = '<img src="' . esc_attr( $placeholder_url ) . '" class="lazy lazy-hidden" data-lazy-type="iframe" data-lazy-src="' . esc_attr( $iframe_html ) . '" alt="">';
358
359 $replace_html .= '<noscript>' . $iframe_html . '</noscript>';
360
361 array_push( $search, $iframe_html );
362 array_push( $replace, $replace_html );
363 }
364
365 $content = str_replace( $search, $replace, $content );
366
367 return $content;
368
369 }
370
371 /**
372 * Remove elements we don’t want to filter from the HTML string
373 * We’re reducing the haystack by removing the hay we know we don’t want to look for needles in
374 *
375 * @param string $content The HTML string
376 *
377 * @return string The HTML string without the unwanted elements
378 */
379 protected static function get_content_haystack( $content ) {
380 $content = self::remove_noscript( $content );
381 $content = self::remove_skip_classes_elements( $content );
382
383 return $content;
384 }
385
386 /**
387 * Remove <noscript> elements from HTML string
388 *
389 * @param string $content The HTML string
390 *
391 * @return string The HTML string without <noscript> elements
392 * @author sigginet
393 */
394 public static function remove_noscript( $content ) {
395 return preg_replace( '/<noscript.*?(\/noscript>)/i', '', $content );
396 }
397
398 /**
399 * Remove HTML elements with certain classnames (or IDs) from HTML string
400 *
401 * @param string $content The HTML string
402 *
403 * @return string The HTML string without the unwanted elements
404 */
405 public static function remove_skip_classes_elements( $content ) {
406
407 $skip_classes = self::get_skip_classes( 'html' );
408
409 /**
410 * http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454
411 * We can’t do this, but we still do it.
412 */
413 $skip_classes_quoted = array_map( 'preg_quote', $skip_classes );
414 $skip_classes_ored = implode( '|', $skip_classes_quoted );
415
416 $regex = '/<\s*\w*\s*class\s*=\s*[\'"]?(|.*\s)?' . $skip_classes_ored . '(|\s.*)?[\'"]?.*?>/isU';
417
418 return preg_replace( $regex, '', $content );
419 }
420
421
422 /**
423 * Get the skip classes
424 *
425 * @param string $content_type The content type (image/iframe etc)
426 *
427 * @return array An array of strings with the class names
428 */
429 protected static function get_skip_classes( $content_type ) {
430 /**
431 * Filters the class names to skip
432 *
433 * @hook powered_cache_lazy_load_skip_classes
434 *
435 * @param {array} $skip_classes The current classes to skip
436 * @param {string} $content_type The current content type
437 *
438 * @return {array} New value.
439 * @since 1.0
440 */
441 $skip_classes = apply_filters( 'powered_cache_lazy_load_skip_classes', array( 'lazy' ), $content_type );
442
443 return $skip_classes;
444 }
445
446 /**
447 * Manage 3rd party compat cases
448 */
449 public function compat() {
450 if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
451 add_filter( 'powered_cache_lazy_load_enabled', '__return_false' );
452 }
453
454 if ( function_exists( 'bp_is_my_profile' ) && bp_is_my_profile() ) {
455 add_filter( 'powered_cache_lazy_load_enabled', '__return_false' );
456 }
457
458 if ( function_exists( 'mopr_get_option' ) && WP_CONTENT_DIR . mopr_get_option( 'mobile_theme_root', 1 ) === get_theme_root() ) {
459 add_filter( 'powered_cache_lazy_load_enabled', '__return_false' );
460 }
461
462 if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && false !== strpos( $_SERVER['HTTP_USER_AGENT'], 'Opera Mini' ) ) {
463 add_filter( 'powered_cache_lazy_load_enabled', '__return_false' );
464 }
465
466 if ( 1 === intval( get_query_var( 'print' ) ) || 1 === intval( get_query_var( 'printpage' ) ) ) {
467 add_filter( 'powered_cache_lazy_load_enabled', '__return_false' );
468 }
469
470 if ( function_exists( 'bnc_wptouch_is_mobile' ) || defined( 'WPTOUCH_VERSION' ) ) {
471 add_filter( 'powered_cache_lazy_load_enabled', '__return_false' );
472 }
473 }
474
475 /**
476 * Maybe disable lazyloading for a particular post
477 *
478 * @param bool $status Lazyload filter status
479 *
480 * @return bool
481 * @since 2.0
482 */
483 public function maybe_disable_through_meta( $status ) {
484
485 if ( in_the_loop() && get_post_meta( get_the_ID(), POST_META_DISABLE_LAZYLOAD_KEY, true ) ) {
486 $status = false;
487 }
488
489 return $status;
490 }
491
492 }
493
494