Completed
Push — add/e2e-connection-purchase-fl... ( d02cce...68beb5 )
by Yaroslav
14:07 queued 04:35
created

Jetpack_Lazy_Images   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 462
Duplicated Lines 3.46 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 0
Metric Value
dl 16
loc 462
rs 8.96
c 0
b 0
f 0
wmc 43
lcom 2
cbo 1

15 Methods

Rating   Name   Duplication   Size   Complexity  
A instance() 0 7 2
A is_amp_request() 0 12 2
A __construct() 0 34 4
A setup_filters() 8 8 1
A remove_filters() 8 8 1
A allow_lazy_attributes() 0 19 2
A add_image_placeholders() 0 16 4
B should_skip_image_with_blocked_class() 0 41 5
A process_image() 0 20 4
C process_image_attributes() 0 82 10
A add_nojs_fallback() 0 15 1
A get_placeholder_image() 0 19 1
A flatten_kses_hair_data() 0 7 2
A build_attributes_string() 0 11 3
A enqueue_assets() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Lazy_Images often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Lazy_Images, and based on these observations, apply Extract Interface, too.

1
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3
/**
4
 * The Lazy Images package.
5
 *
6
 * @since 8.8
7
 *
8
 * This package has been lifted from the Jetpack modules folder and adapted to be
9
 * an standalone package reusable outside Jetpack.
10
 *
11
 * @package automattic/jetpack-lazy-images
12
 */
13
14
/**
15
 * This package relies heavily upon the Lazy Load plugin which was worked on by
16
 * Mohammad Jangda (batmoo), the WordPress.com VIP team, the TechCrunch 2011
17
 * redesign team, and Jake Goldman of 10up LLC.
18
 *
19
 * The JavaScript has been updated to rely on InterSection observer instead of
20
 * jQuery Sonar. Many thanks to Dean Hume (deanhume) and his example:
21
 * https://github.com/deanhume/lazy-observer-load
22
 */
23
24
namespace Automattic\Jetpack;
25
26
use Automattic\Jetpack\Assets;
27
use Automattic\Jetpack\Constants as Jetpack_Constants;
28
29
/**
30
 * Class Automattic\Jetpack\Jetpack_Lazy_Images
31
 *
32
 * @since 8.8
33
 */
34
class Jetpack_Lazy_Images {
35
36
	/**
37
	 * The assets version.
38
	 *
39
	 * @since 8.8
40
	 *
41
	 * @var string Assets version.
42
	 */
43
	const ASSETS_VERSION = '1.0.0';
44
45
	/**
46
	 * Class instance.
47
	 *
48
	 * @since 5.6.0
49
	 *
50
	 * @var null
51
	 */
52
	private static $instance = null;
53
54
	/**
55
	 * Singleton implementation.
56
	 *
57
	 * @since 5.6.0
58
	 *
59
	 * @return object The class instance.
60
	 */
61
	public static function instance() {
62
		if ( is_null( self::$instance ) ) {
63
			self::$instance = new Jetpack_Lazy_Images();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Automattic\Jetpack\Jetpack_Lazy_Images() of type object<Automattic\Jetpack\Jetpack_Lazy_Images> is incompatible with the declared type null of property $instance.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
64
		}
65
66
		return self::$instance;
67
	}
68
69
	/**
70
	 * Check if the request is a AMP request.
71
	 *
72
	 * @since 8.8
73
	 *
74
	 * @return bool
75
	 */
76
	public static function is_amp_request() {
77
		$is_amp_request = ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() );
78
79
		/**
80
		 * Returns true if the current request should return valid AMP content.
81
		 *
82
		 * @since 8.8
83
		 *
84
		 * @param boolean $is_amp_request Is this request supposed to return valid AMP content?
85
		 */
86
		return apply_filters( 'jetpack_lazy_images_is_amp_request', $is_amp_request );
87
	}
88
89
	/**
90
	 * Registers actions.
91
	 *
92
	 * @since 8.8
93
	 *
94
	 * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
95
	 */
96
	private function __construct() {
97
		if ( is_admin() ) {
98
			return;
99
		}
100
101
		/**
102
		 * Whether the lazy-images module should load.
103
		 *
104
		 * This filter is not prefixed with jetpack_ to provide a smoother migration
105
		 * process from the WordPress Lazy Load plugin.
106
		 *
107
		 * @package automattic/jetpack-lazy-images
108
		 *
109
		 * @since 5.6.0
110
		 *
111
		 * @param bool true Whether lazy image loading should occur.
112
		 */
113
		if ( ! apply_filters( 'lazyload_is_enabled', true ) ) {
114
			return;
115
		}
116
117
		if ( self::is_amp_request() ) {
118
			return;
119
		}
120
121
		add_action( 'wp_head', array( $this, 'setup_filters' ), 9999 ); // We don't really want to modify anything in <head> since it's mostly all metadata.
122
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
123
124
		// Do not lazy load avatar in admin bar.
125
		add_action( 'admin_bar_menu', array( $this, 'remove_filters' ), 0 );
126
127
		add_filter( 'wp_kses_allowed_html', array( $this, 'allow_lazy_attributes' ) );
128
		add_action( 'wp_head', array( $this, 'add_nojs_fallback' ) );
129
	}
130
131
	/**
132
	 * Setup filters.
133
	 *
134
	 * @since 5.6.0
135
	 *
136
	 * @return void
137
	 */
138 View Code Duplication
	public function setup_filters() {
139
		add_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX ); // Run this later, so other content filters have run, including image_add_wh on WP.com.
140
		add_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
141
		add_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
142
		add_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
143
		add_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
144
		add_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX );
145
	}
146
147
	/**
148
	 * Remove filters.
149
	 *
150
	 * @since 5.6.0
151
	 *
152
	 * @return void
153
	 */
154 View Code Duplication
	public function remove_filters() {
155
		remove_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
156
		remove_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
157
		remove_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
158
		remove_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
159
		remove_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
160
		remove_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX );
161
	}
162
163
	/**
164
	 * Ensure that our lazy image attributes are not filtered out of image tags.
165
	 *
166
	 * @since 5.6.0
167
	 *
168
	 * @param array $allowed_tags The allowed tags and their attributes.
169
	 * @return array
170
	 */
171
	public function allow_lazy_attributes( $allowed_tags ) {
172
		if ( ! isset( $allowed_tags['img'] ) ) {
173
			return $allowed_tags;
174
		}
175
176
		// But, if images are allowed, ensure that our attributes are allowed!
177
		$img_attributes = array_merge(
178
			$allowed_tags['img'],
179
			array(
180
				'data-lazy-src'    => 1,
181
				'data-lazy-srcset' => 1,
182
				'data-lazy-sizes'  => 1,
183
			)
184
		);
185
186
		$allowed_tags['img'] = $img_attributes;
187
188
		return $allowed_tags;
189
	}
190
191
	/**
192
	 * Add image placeholders.
193
	 *
194
	 * @since 5.6.0
195
	 *
196
	 * @param string $content Content.
197
	 * @return string
198
	 */
199
	public function add_image_placeholders( $content ) {
200
		// Don't lazy load for feeds, previews.
201
		if ( is_feed() || is_preview() ) {
202
			return $content;
203
		}
204
205
		// Don't lazy-load if the content has already been run through previously.
206
		if ( false !== strpos( $content, 'data-lazy-src' ) ) {
207
			return $content;
208
		}
209
210
		// This is a pretty simple regex, but it works.
211
		$content = preg_replace_callback( '#<(img)([^>]+?)(>(.*?)</\\1>|[\/]?>)#si', array( __CLASS__, 'process_image' ), $content );
212
213
		return $content;
214
	}
215
216
	/**
217
	 * Returns true when a given string of classes contains a class signifying lazy images.
218
	 * should not process the image.
219
	 *
220
	 * @since 5.9.0
221
	 *
222
	 * @param string $classes A string of space-separated classes.
223
	 * @return bool
224
	 */
225
	public static function should_skip_image_with_blocked_class( $classes ) {
226
		$blocked_classes = array(
227
			'skip-lazy',
228
			'gazette-featured-content-thumbnail',
229
		);
230
231
		/**
232
		 * Allow plugins and themes to tell lazy images to skip an image with a given class.
233
		 *
234
		 * @package automattic/jetpack-lazy-images
235
		 *
236
		 * @since 5.9.0
237
		 * @deprecated 8.7.0 Use jetpack_lazy_images_blocked_classes
238
		 *
239
		 * @param array An array of strings where each string is a class.
240
		 */
241
		$blocked_classes = apply_filters_deprecated( 'jetpack_lazy_images_blacklisted_classes', array( $blocked_classes ), 'Jetpack 8.7.0', 'jetpack_lazy_images_blocked_classes' );
242
243
		/**
244
		 * Allow plugins and themes to tell lazy images to skip an image with a given class.
245
		 *
246
		 * @package automattic/jetpack-lazy-images
247
		 *
248
		 * @since 8.7.0
249
		 *
250
		 * @param array An array of strings where each string is a class.
251
		 */
252
		$blocked_classes = apply_filters( 'jetpack_lazy_images_blocked_classes', $blocked_classes );
253
254
		if ( ! is_array( $blocked_classes ) || empty( $blocked_classes ) ) {
255
			return false;
256
		}
257
258
		foreach ( $blocked_classes as $class ) {
259
			if ( false !== strpos( $classes, $class ) ) {
260
				return true;
261
			}
262
		}
263
264
		return false;
265
	}
266
267
	/**
268
	 * Processes images in content by acting as the preg_replace_callback.
269
	 *
270
	 * @since 5.6.0
271
	 *
272
	 * @param array $matches Matches.
273
	 *
274
	 * @return string The image with updated lazy attributes.
275
	 */
276
	public static function process_image( $matches ) {
277
		$old_attributes_str       = $matches[2];
278
		$old_attributes_kses_hair = wp_kses_hair( $old_attributes_str, wp_allowed_protocols() );
279
280
		if ( empty( $old_attributes_kses_hair['src'] ) ) {
281
			return $matches[0];
282
		}
283
284
		$old_attributes = self::flatten_kses_hair_data( $old_attributes_kses_hair );
285
286
		// If we didn't add lazy attributes, just return the original image source.
287
		if ( ! empty( $old_attributes['class'] ) && false !== strpos( $old_attributes['class'], 'jetpack-lazy-image' ) ) {
288
			return $matches[0];
289
		}
290
291
		$new_attributes     = self::process_image_attributes( $old_attributes );
292
		$new_attributes_str = self::build_attributes_string( $new_attributes );
293
294
		return sprintf( '<img %1$s><noscript>%2$s</noscript>', $new_attributes_str, $matches[0] );
295
	}
296
297
	/**
298
	 * Given an array of image attributes, updates the `src`, `srcset`, and `sizes` attributes so
299
	 * that they load lazily.
300
	 *
301
	 * @since 5.7.0
302
	 *
303
	 * @param array $attributes Attributes.
304
	 *
305
	 * @return array The updated image attributes array with lazy load attributes.
306
	 */
307
	public static function process_image_attributes( $attributes ) {
308
		if ( empty( $attributes['src'] ) ) {
309
			return $attributes;
310
		}
311
312
		if ( ! empty( $attributes['class'] ) && self::should_skip_image_with_blocked_class( $attributes['class'] ) ) {
313
			return $attributes;
314
		}
315
316
		if ( isset( $attributes['data-skip-lazy'] ) ) {
317
			return $attributes;
318
		}
319
320
		/**
321
		 * Allow plugins and themes to conditionally skip processing an image via its attributes.
322
		 *
323
		 * @package automattic/jetpack-lazy-images
324
		 *
325
		 * @deprecated 6.5.0 Use jetpack_lazy_images_skip_image_with_attributes instead.
326
		 * @since 5.9.0
327
		 *
328
		 * @param bool  Default to not skip processing the current image.
329
		 * @param array An array of attributes via wp_kses_hair() for the current image.
330
		 */
331
		if ( apply_filters( 'jetpack_lazy_images_skip_image_with_atttributes', false, $attributes ) ) {
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $attributes.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
332
			return $attributes;
333
		}
334
335
		/**
336
		 * Allow plugins and themes to conditionally skip processing an image via its attributes.
337
		 *
338
		 * @package automattic/jetpack-lazy-images
339
		 *
340
		 * @since 6.5.0 Filter name was updated from jetpack_lazy_images_skip_image_with_atttributes to correct typo.
341
		 * @since 5.9.0
342
		 *
343
		 * @param bool  Default to not skip processing the current image.
344
		 * @param array An array of attributes via wp_kses_hair() for the current image.
345
		 */
346
		if ( apply_filters( 'jetpack_lazy_images_skip_image_with_attributes', false, $attributes ) ) {
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $attributes.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
347
			return $attributes;
348
		}
349
350
		$old_attributes = $attributes;
351
352
		// Stash srcset and sizes in data attributes.
353
		foreach ( array( 'srcset', 'sizes' ) as $attribute ) {
354
			if ( isset( $old_attributes[ $attribute ] ) ) {
355
				$attributes[ "data-lazy-$attribute" ] = $old_attributes[ $attribute ];
356
				unset( $attributes[ $attribute ] );
357
			}
358
		}
359
360
		// We set this, adding the query arg so that it doesn't exactly equal the src attribute, so that photon JavaScript
361
		// will hold off on processing this image.
362
		$attributes['data-lazy-src'] = esc_url_raw( add_query_arg( 'is-pending-load', true, $attributes['src'] ) );
363
364
		$attributes['srcset'] = self::get_placeholder_image();
365
		$attributes['class']  = sprintf(
366
			'%s jetpack-lazy-image',
367
			empty( $old_attributes['class'] )
368
				? ''
369
				: $old_attributes['class']
370
		);
371
372
		/**
373
		 * Allow plugins and themes to override the attributes on the image before the content is updated.
374
		 *
375
		 * One potential use of this filter is for themes that set `height:auto` on the `img` tag.
376
		 * With this filter, the theme could get the width and height attributes from the
377
		 * $attributes array and then add a style tag that sets those values as well, which could
378
		 * minimize reflow as images load.
379
		 *
380
		 * @package automattic/jetpack-lazy-images
381
		 *
382
		 * @since 5.6.0
383
		 *
384
		 * @param array An array containing the attributes for the image, where the key is the attribute name
385
		 *              and the value is the attribute value.
386
		 */
387
		return apply_filters( 'jetpack_lazy_images_new_attributes', $attributes );
388
	}
389
390
	/**
391
	 * Adds JavaScript to check if the current browser supports JavaScript as well as some styles to hide lazy
392
	 * images when the browser does not support JavaScript.
393
	 *
394
	 * @since 5.6.0
395
	 *
396
	 * @return void
397
	 */
398
	public function add_nojs_fallback() {
399
		?>
400
			<style type="text/css">
401
				/* If html does not have either class, do not show lazy loaded images. */
402
				html:not( .jetpack-lazy-images-js-enabled ):not( .js ) .jetpack-lazy-image {
403
					display: none;
404
				}
405
			</style>
406
			<script>
407
				document.documentElement.classList.add(
408
					'jetpack-lazy-images-js-enabled'
409
				);
410
			</script>
411
		<?php
412
	}
413
414
	/**
415
	 * Retrieves the placeholder image after running it through the lazyload_images_placeholder_image filter.
416
	 *
417
	 * @since 5.6.0
418
	 *
419
	 * @return string The placeholder image source.
420
	 */
421
	private static function get_placeholder_image() {
422
		/**
423
		 * Allows plugins and themes to modify the placeholder image.
424
		 *
425
		 * This filter is not prefixed with jetpack_ to provide a smoother migration
426
		 * process from the WordPress Lazy Load plugin.
427
		 *
428
		 * @module lazy-images
429
		 *
430
		 * @since 5.6.0
431
		 * @since 6.5.0 Default image is now a base64 encoded transparent gif.
432
		 *
433
		 * @param string The URL to the placeholder image.
434
		 */
435
		return apply_filters(
436
			'lazyload_images_placeholder_image',
437
			'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
438
		);
439
	}
440
441
	/**
442
	 * Flatter KSES hair data.
443
	 *
444
	 * @since 5.6.0
445
	 *
446
	 * @param array $attributes Attributes.
447
	 *
448
	 * @return array
449
	 */
450
	private static function flatten_kses_hair_data( $attributes ) {
451
		$flattened_attributes = array();
452
		foreach ( $attributes as $name => $attribute ) {
453
			$flattened_attributes[ $name ] = $attribute['value'];
454
		}
455
		return $flattened_attributes;
456
	}
457
458
	/**
459
	 * Build attributes string.
460
	 *
461
	 * @since 5.6.0
462
	 *
463
	 * @param array $attributes Attributes.
464
	 *
465
	 * @return string
466
	 */
467
	private static function build_attributes_string( $attributes ) {
468
		$string = array();
469
		foreach ( $attributes as $name => $value ) {
470
			if ( '' === $value ) {
471
				$string[] = sprintf( '%s', $name );
472
			} else {
473
				$string[] = sprintf( '%s="%s"', $name, esc_attr( $value ) );
474
			}
475
		}
476
		return implode( ' ', $string );
477
	}
478
479
	/**
480
	 * Enqueue assets.
481
	 *
482
	 * @since 5.6.0
483
	 *
484
	 * @return void
485
	 */
486
	public function enqueue_assets() {
487
		wp_enqueue_script(
488
			'jetpack-lazy-images',
489
			Assets::get_file_url_for_environment( 'js/lazy-images.min.js', 'js/lazy-images.js', __FILE__ ),
490
			array(),
491
			self::ASSETS_VERSION,
492
			true
493
		);
494
	}
495
}
496