Completed
Push — try/lazy-images-svg ( 5194c3 )
by
unknown
07:18
created

Jetpack_Lazy_Images   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 426
Duplicated Lines 3.76 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 16
loc 426
rs 4.08
c 0
b 0
f 0
wmc 59
lcom 2
cbo 2

15 Methods

Rating   Name   Duplication   Size   Complexity  
A should_skip_image_with_blacklisted_class() 0 29 5
A instance() 0 7 2
A __construct() 0 34 4
A setup_filters() 8 8 1
A remove_filters() 8 8 1
A allow_lazy_attributes() 0 16 2
A add_image_placeholders() 0 16 4
A process_image() 0 20 4
C process_image_attributes() 0 81 10
A add_nojs_fallback() 0 15 1
A get_placeholder_image() 0 29 2
D get_image_dimensions() 0 53 17
A flatten_kses_hair_data() 0 7 2
A build_attributes_string() 0 11 3
A enqueue_assets() 0 12 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
2
3
use Automattic\Jetpack\Assets;
4
5
class Jetpack_Lazy_Images {
6
	private static $__instance = null;
7
8
	/**
9
	 * Singleton implementation
10
	 *
11
	 * @return object
12
	 */
13
	public static function instance() {
14
		if ( is_null( self::$__instance ) ) {
15
			self::$__instance = new Jetpack_Lazy_Images();
16
		}
17
18
		return self::$__instance;
19
	}
20
21
	/**
22
	 * Registers actions
23
	 */
24
	private function __construct() {
25
		if ( is_admin() ) {
26
			return;
27
		}
28
29
		/**
30
		 * Whether the lazy-images module should load.
31
		 *
32
		 * This filter is not prefixed with jetpack_ to provide a smoother migration
33
		 * process from the WordPress Lazy Load plugin.
34
		 *
35
		 * @module lazy-images
36
		 *
37
		 * @since 5.6.0
38
		 *
39
		 * @param bool true Whether lazy image loading should occur.
40
		 */
41
		if ( ! apply_filters( 'lazyload_is_enabled', true ) ) {
42
			return;
43
		}
44
45
		if ( Jetpack_AMP_Support::is_amp_request() ) {
46
			return;
47
		}
48
49
		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
50
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
51
52
		// Do not lazy load avatar in admin bar
53
		add_action( 'admin_bar_menu', array( $this, 'remove_filters' ), 0 );
54
55
		add_filter( 'wp_kses_allowed_html', array( $this, 'allow_lazy_attributes' ) );
56
		add_action( 'wp_head', array( $this, 'add_nojs_fallback' ) );
57
	}
58
59 View Code Duplication
	public function setup_filters() {
60
		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
61
		add_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
62
		add_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
63
		add_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
64
		add_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX);
65
		add_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX );
66
	}
67
68 View Code Duplication
	public function remove_filters() {
69
		remove_filter( 'the_content', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
70
		remove_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
71
		remove_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
72
		remove_filter( 'widget_text', array( $this, 'add_image_placeholders' ), PHP_INT_MAX );
73
		remove_filter( 'get_image_tag', array( $this, 'add_image_placeholders' ), PHP_INT_MAX);
74
		remove_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'process_image_attributes' ), PHP_INT_MAX );
75
	}
76
77
	/**
78
	 * Ensure that our lazy image attributes are not filtered out of image tags.
79
	 *
80
	 * @param array $allowed_tags The allowed tags and their attributes.
81
	 * @return array
82
	 */
83
	public function allow_lazy_attributes( $allowed_tags ) {
84
		if ( ! isset( $allowed_tags['img'] ) ) {
85
			return $allowed_tags;
86
		}
87
88
		// But, if images are allowed, ensure that our attributes are allowed!
89
		$img_attributes = array_merge( $allowed_tags['img'], array(
90
			'data-lazy-src' => 1,
91
			'data-lazy-srcset' => 1,
92
			'data-lazy-sizes' => 1,
93
		) );
94
95
		$allowed_tags['img'] = $img_attributes;
96
97
		return $allowed_tags;
98
	}
99
100
	public function add_image_placeholders( $content ) {
101
		// Don't lazyload for feeds, previews
102
		if ( is_feed() || is_preview() ) {
103
			return $content;
104
		}
105
106
		// Don't lazy-load if the content has already been run through previously
107
		if ( false !== strpos( $content, 'data-lazy-src' ) ) {
108
			return $content;
109
		}
110
111
		// This is a pretty simple regex, but it works
112
		$content = preg_replace_callback( '#<(img)([^>]+?)(>(.*?)</\\1>|[\/]?>)#si', array( __CLASS__, 'process_image' ), $content );
113
114
		return $content;
115
	}
116
117
	/**
118
	 * Returns true when a given string of classes contains a class signifying lazy images
119
	 * should not process the image.
120
	 *
121
	 * @since 5.9.0
122
	 *
123
	 * @param string $classes A string of space-separated classes.
124
	 * @return bool
125
	 */
126
	public static function should_skip_image_with_blacklisted_class( $classes ) {
127
		$blacklisted_classes = array(
128
			'skip-lazy',
129
			'gazette-featured-content-thumbnail',
130
		);
131
132
		/**
133
		 * Allow plugins and themes to tell lazy images to skip an image with a given class.
134
		 *
135
		 * @module lazy-images
136
		 *
137
		 * @since 5.9.0
138
		 *
139
		 * @param array An array of strings where each string is a class.
140
		 */
141
		$blacklisted_classes = apply_filters( 'jetpack_lazy_images_blacklisted_classes', $blacklisted_classes );
142
143
		if ( ! is_array( $blacklisted_classes ) || empty( $blacklisted_classes ) ) {
144
			return false;
145
		}
146
147
		foreach ( $blacklisted_classes as $class ) {
148
			if ( false !== strpos( $classes, $class ) ) {
149
				return true;
150
			}
151
		}
152
153
		return false;
154
	}
155
156
	/**
157
	 * Processes images in content by acting as the preg_replace_callback
158
	 *
159
	 * @since 5.6.0
160
	 *
161
	 * @param array $matches
162
	 *
163
	 * @return string The image with updated lazy attributes
164
	 */
165
	static function process_image( $matches ) {
166
		$old_attributes_str       = $matches[2];
167
		$old_attributes_kses_hair = wp_kses_hair( $old_attributes_str, wp_allowed_protocols() );
168
169
		if ( empty( $old_attributes_kses_hair['src'] ) ) {
170
			return $matches[0];
171
		}
172
173
		$old_attributes = self::flatten_kses_hair_data( $old_attributes_kses_hair );
174
175
		// If we didn't add lazy attributes, just return the original image source.
176
		if ( ! empty( $old_attributes['class'] ) && false !== strpos( $old_attributes['class'], 'jetpack-lazy-image' ) ) {
177
			return $matches[0];
178
		}
179
180
		$new_attributes     = self::process_image_attributes( $old_attributes );
181
		$new_attributes_str = self::build_attributes_string( $new_attributes );
182
183
		return sprintf( '<img %1$s><noscript>%2$s</noscript>', $new_attributes_str, $matches[0] );
184
	}
185
186
	/**
187
	 * Given an array of image attributes, updates the `src`, `srcset`, and `sizes` attributes so
188
	 * that they load lazily.
189
	 *
190
	 * @since 5.7.0
191
	 *
192
	 * @param array $attributes
193
	 *
194
	 * @return array The updated image attributes array with lazy load attributes
195
	 */
196
	static function process_image_attributes( $attributes ) {
197
		if ( empty( $attributes['src'] ) ) {
198
			return $attributes;
199
		}
200
201
		if ( ! empty( $attributes['class'] ) && self::should_skip_image_with_blacklisted_class( $attributes['class'] ) ) {
202
			return $attributes;
203
		}
204
205
		if ( isset( $attributes['data-skip-lazy'] ) ) {
206
			return $attributes;
207
		}
208
209
		/**
210
		 * Allow plugins and themes to conditionally skip processing an image via its attributes.
211
		 *
212
		 * @module-lazy-images
213
		 *
214
		 * @deprecated 6.5.0 Use jetpack_lazy_images_skip_image_with_attributes instead.
215
		 *
216
		 * @since 5.9.0
217
		 *
218
		 * @param bool  Default to not skip processing the current image.
219
		 * @param array An array of attributes via wp_kses_hair() for the current image.
220
		 */
221
		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...
222
			return $attributes;
223
		}
224
225
		/**
226
		 * Allow plugins and themes to conditionally skip processing an image via its attributes.
227
		 *
228
		 * @module-lazy-images
229
		 *
230
		 * @since 6.5.0 Filter name was updated from jetpack_lazy_images_skip_image_with_atttributes to correct typo.
231
		 * @since 5.9.0
232
		 *
233
		 * @param bool  Default to not skip processing the current image.
234
		 * @param array An array of attributes via wp_kses_hair() for the current image.
235
		 */
236
		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...
237
			return $attributes;
238
		}
239
240
		$old_attributes = $attributes;
241
242
		// Stash srcset and sizes in data attributes.
243
		foreach ( array( 'srcset', 'sizes' ) as $attribute ) {
244
			if ( isset( $old_attributes[ $attribute ] ) ) {
245
				$attributes[ "data-lazy-$attribute" ] = $old_attributes[ $attribute ];
246
				unset( $attributes[ $attribute ] );
247
			}
248
		}
249
250
		$attributes['data-lazy-src'] = $attributes['src'];
251
		$attributes['src']           = self::get_placeholder_image( $attributes );
252
253
		$attributes['class'] = sprintf(
254
			'%s jetpack-lazy-image',
255
			empty( $old_attributes['class'] )
256
				? ''
257
				: $old_attributes['class']
258
		);
259
260
		/**
261
		 * Allow plugins and themes to override the attributes on the image before the content is updated.
262
		 *
263
		 * One potential use of this filter is for themes that set `height:auto` on the `img` tag.
264
		 * With this filter, the theme could get the width and height attributes from the
265
		 * $attributes array and then add a style tag that sets those values as well, which could
266
		 * minimize reflow as images load.
267
		 *
268
		 * @module lazy-images
269
		 *
270
		 * @since 5.6.0
271
		 *
272
		 * @param array An array containing the attributes for the image, where the key is the attribute name
273
		 *              and the value is the attribute value.
274
		 */
275
		return apply_filters( 'jetpack_lazy_images_new_attributes', $attributes );
276
	}
277
278
	/**
279
	 * Adds JavaScript to check if the current browser supports JavaScript as well as some styles to hide lazy
280
	 * images when the browser does not support JavaScript.
281
	 *
282
	 * @return void
283
	 */
284
	public function add_nojs_fallback() {
285
		?>
286
			<style type="text/css">
287
				/* If html does not have either class, do not show lazy loaded images. */
288
				html:not( .jetpack-lazy-images-js-enabled ):not( .js ) .jetpack-lazy-image {
289
					display: none;
290
				}
291
			</style>
292
			<script>
293
				document.documentElement.classList.add(
294
					'jetpack-lazy-images-js-enabled'
295
				);
296
			</script>
297
		<?php
298
	}
299
300
	/**
301
	 * Retrieves the placeholder image after running it through the lazyload_images_placeholder_image filter.
302
	 *
303
	 * @param array $attributes An array of attributes to be added to the image.
304
	 *
305
	 * @return string The placeholder image source.
306
	 */
307
	public static function get_placeholder_image( $attributes = array() ) {
308
		$image_dimensions = self::get_image_dimensions( $attributes );
309
310
		/**
311
		 * Allows plugins and themes to modify the placeholder image.
312
		 *
313
		 * This filter is not prefixed with jetpack_ to provide a smoother migration
314
		 * process from the WordPress Lazy Load plugin.
315
		 *
316
		 * @module lazy-images
317
		 *
318
		 * @since 5.6.0
319
		 * @since 6.5.0 Default image is now a base64 encoded transparent gif.
320
		 *
321
		 * @param string The URL to the placeholder image
322
		 */
323
		return apply_filters(
324
			'lazyload_images_placeholder_image',
325
			(
326
				! empty( $image_dimensions )
327
					? sprintf(
328
						"data:image/svg+xml,%%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 %d %d'%%3E%%3C/svg%%3E",
329
						$image_dimensions['width'],
330
						$image_dimensions['height']
331
					)
332
					: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
333
			)
334
		);
335
	}
336
337
	/**
338
	 * Given the attributes about an image, will attempt to determine the height, width, and aspect ratio of an image.
339
	 *
340
	 * @param array $attributes An array of image attributes.
341
	 *
342
	 * @return bool|array
343
	 */
344
	public static function get_image_dimensions( $attributes ) {
345
		$width  = 0;
346
		$height = 0;
347
348
		do {
349
			if ( ! empty( $attributes['width'] ) && ! empty( $attributes['height'] ) ) {
350
				$width  = intval( $attributes['width'] );
351
				$height = intval( $attributes['height'] );
352
				continue;
353
			}
354
355
			if ( ! empty( $attributes['data-orig-size'] ) ) {
356
				$split = explode( ',', $attributes['data-orig-size'] );
357
				if ( 2 === count( $split ) ) {
358
					$width  = trim( $split[0] );
359
					$height = trim( $split[1] );
360
361
					continue;
362
				}
363
			}
364
365
			$attachment_id = ! empty( $attributes['data-id'] ) ? $attributes['data-id'] : null;
366
			if ( is_null( $attachment_id ) ) {
367
				$attachment_id = ! empty( $attributes['data-attachment-id'] )
368
					? $attributes['data-attachment-id']
369
					: null;
370
			}
371
372
			if ( is_null( $attachment_id ) && ! empty( $attributes['class'] ) && false !== strpos( $attributes['class'], 'wp-image-' ) ) {
373
				$attachment_id = str_replace( 'wp-image-', '', preg_replace( '/(wp-image-(\d+)\s)/', '$2', $attributes['class'] ) );
374
			}
375
376
			if ( empty( $attachment_id ) ) {
377
				continue;
378
			}
379
380
			// Default to full since we're mostly interested in aspect ratio.
381
			$image_src = wp_get_attachment_image_src( $attachment_id, 'full' );
382
			if ( ! empty( $image_src ) && count( $image_src ) >= 3 ) {
383
				$width  = $image_src[1];
384
				$height = $image_src[2];
385
			}
386
		} while ( false );
387
388
		if ( empty( $width ) || empty( $height ) ) {
389
			return false;
390
		}
391
392
		return array(
393
			'width'  => $width,
394
			'height' => $height,
395
		);
396
	}
397
398
	private static function flatten_kses_hair_data( $attributes ) {
399
		$flattened_attributes = array();
400
		foreach ( $attributes as $name => $attribute ) {
401
			$flattened_attributes[ $name ] = $attribute['value'];
402
		}
403
		return $flattened_attributes;
404
	}
405
406
	private static function build_attributes_string( $attributes ) {
407
		$string = array();
408
		foreach ( $attributes as $name => $value ) {
409
			if ( '' === $value ) {
410
				$string[] = sprintf( '%s', $name );
411
			} else {
412
				$string[] = sprintf( '%s="%s"', $name, esc_attr( $value ) );
413
			}
414
		}
415
		return implode( ' ', $string );
416
	}
417
418
	public function enqueue_assets() {
419
		wp_enqueue_script(
420
			'jetpack-lazy-images',
421
			Assets::get_file_url_for_environment(
422
				'_inc/build/lazy-images/js/lazy-images.min.js',
423
				'modules/lazy-images/js/lazy-images.js'
424
			),
425
			array(),
426
			JETPACK__VERSION,
427
			true
428
		);
429
	}
430
}
431