Completed
Push — master ( e4b824...05b7b2 )
by
unknown
12s
created

Images_Via_Imgix   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 323
Duplicated Lines 3.1 %

Coupling/Cohesion

Components 2
Dependencies 0

Importance

Changes 0
Metric Value
dl 10
loc 323
rs 7.4757
c 0
b 0
f 0
wmc 53
lcom 2
cbo 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
A instance() 0 7 2
A set_options() 0 3 1
A add_retina() 0 8 1
C replace_image_url() 0 32 11
D image_downsize() 0 33 10
B calculate_image_srcset() 0 16 5
C replace_images_in_content() 10 28 8
A prefetch_cdn() 0 10 2
A buffer_start_for_retina() 0 6 2
A buffer_end_for_retina() 0 5 2
B get_global_params() 0 23 5
A get_all_defined_sizes() 0 22 3

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 Images_Via_Imgix 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 Images_Via_Imgix, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
class Images_Via_Imgix {
4
5
	/**
6
	 * The instance of the class.
7
	 *
8
	 * @var Images_Via_Imgix
9
	 */
10
	protected static $instance;
11
12
	/**
13
	 * Plugin options
14
	 *
15
	 * @var array
16
	 */
17
	protected $options = [];
18
19
	/**
20
	 * Buffer is started by plugin and should be ended on shutdown.
21
	 *
22
	 * @var bool
23
	 */
24
	protected $buffer_started = false;
25
26
	/**
27
	 * ImagesViaImgix constructor.
28
	 */
29
	public function __construct() {
30
		$this->options = get_option( 'imgix_settings', [] );
31
32
		// Change filter load order to ensure it loads after other CDN url transformations i.e. Amazon S3 which loads at position 99.
33
		add_filter( 'wp_get_attachment_url', [ $this, 'replace_image_url' ], 100 );
34
		add_filter( 'imgix/add-image-url', [ $this, 'replace_image_url' ] );
35
36
		add_filter( 'image_downsize', [ $this, 'image_downsize' ], 10, 3 );
37
38
		add_filter( 'wp_calculate_image_srcset', [ $this, 'calculate_image_srcset' ], 10, 5 );
39
40
		add_filter( 'the_content', [ $this, 'replace_images_in_content' ] );
41
		add_action( 'wp_head', [ $this, 'prefetch_cdn' ], 1 );
42
43
		add_action( 'after_setup_theme', [ $this, 'buffer_start_for_retina' ] );
44
		add_action( 'shutdown', [ $this, 'buffer_end_for_retina' ] );
45
	}
46
47
	/**
48
	 * Plugin loader instance.
49
	 *
50
	 * @return Images_Via_Imgix
51
	 */
52
	public static function instance() {
53
		if ( ! isset( self::$instance ) ) {
54
			self::$instance = new self;
55
		}
56
57
		return self::$instance;
58
	}
59
60
	/**
61
	 * Override options from settings.
62
	 * Used in unit tests.
63
	 *
64
	 * @param array $options
65
	 */
66
	public function set_options( $options ) {
67
		$this->options = $options;
68
	}
69
70
	/**
71
	 * Find all img tags with sources matching "imgix.net" without the parameter
72
	 * "srcset" and add the "srcset" parameter to all those images, appending a new
73
	 * source using the "dpr=2" modifier.
74
	 *
75
	 * @param $content
76
	 *
77
	 * @return string Content with retina-enriched image tags.
78
	 */
79
	public function add_retina( $content ) {
80
		$pattern = '/<img((?![^>]+srcset )([^>]*)';
81
		$pattern .= 'src=[\'"]([^\'"]*imgix.net[^\'"]*\?[^\'"]*w=[^\'"]*)[\'"]([^>]*)*?)>/i';
82
		$repl    = '<img$2src="$3" srcset="${3}, ${3}&amp;dpr=2 2x, ${3}&amp;dpr=3 3x,"$4>';
83
		$content = preg_replace( $pattern, $repl, $content );
84
85
		return preg_replace( $pattern, $repl, $content );
86
	}
87
88
	/**
89
	 * Modify image urls for attachments to use imgix host.
90
	 *
91
	 * @param string $url
92
	 *
93
	 * @return string
94
	 */
95
	public function replace_image_url( $url ) {
96
		if ( ! empty ( $this->options['cdn_link'] ) ) {
97
			$parsed_url = parse_url( $url );
98
99
			//Check if image is hosted on current site url -OR- the CDN url specified. Using strpos because we're comparing the host to a full CDN url.
100
            if (
101
                isset( $parsed_url['host'], $parsed_url['path'] )
102
                && ($parsed_url['host'] === parse_url( home_url( '/' ), PHP_URL_HOST ) || ( isset($this->options['external_cdn_link']) && ! empty($this->options['external_cdn_link']) && strpos( $this->options['external_cdn_link'], $parsed_url['host']) !== false ) )
103
                && preg_match( '/\.(jpg|jpeg|gif|png)$/i', $parsed_url['path'] )
104
            ) {
105
				$cdn = parse_url( $this->options['cdn_link'] );
106
				foreach ( [ 'scheme', 'host', 'port' ] as $url_part ) {
107
					if ( isset( $cdn[ $url_part ] ) ) {
108
						$parsed_url[ $url_part ] = $cdn[ $url_part ];
109
					} else {
110
						unset( $parsed_url[ $url_part ] );
111
					}
112
				}
113
				if ( ! empty( $this->options['external_cdn_link'] ) ) {
114
                    //Modify the CDN URL, we won't need any parts after the host.
115
                    $parsed_cdn_url = parse_url( $this->options['external_cdn_link'] );
116
                    $parsed_url['path'] = str_replace( $parsed_cdn_url['path'], "", $parsed_url['path'] );
117
                }
118
119
				$url = http_build_url( $parsed_url );
0 ignored issues
show
Security Bug introduced by
It seems like $parsed_url defined by parse_url($url) on line 97 can also be of type false; however, http_build_url() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
120
121
				$url = add_query_arg( $this->get_global_params(), $url );
122
			}
123
		}
124
125
		return $url;
126
	}
127
128
	/**
129
	 * Set params when running image_downsize
130
	 *
131
	 * @param false|array  $return
132
	 * @param int          $attachment_id
133
	 * @param string|array $size
134
	 *
135
	 * @return false|array
136
	 */
137
	public function image_downsize( $return, $attachment_id, $size ) {
138
		if ( ! empty ( $this->options['cdn_link'] ) ) {
139
			$img_url = wp_get_attachment_url( $attachment_id );
140
141
			$params = [];
142
			if ( is_array( $size ) ) {
143
				$params['w'] = $width = isset( $size[0] ) ? $size[0] : 0;
144
				$params['h'] = $height = isset( $size[1] ) ? $size[1] : 0;
145
			} else {
146
				$available_sizes = $this->get_all_defined_sizes();
147
				if ( isset( $available_sizes[ $size ] ) ) {
148
					$size        = $available_sizes[ $size ];
149
					$params['w'] = $width = $size['width'];
150
					$params['h'] = $height = $size['height'];
151
				}
152
			}
153
154
			$params = array_filter( $params );
155
156
			$img_url = add_query_arg( $params, $img_url );
157
158
			if ( ! isset( $width ) || ! isset( $height ) ) {
159
				// any other type: use the real image
160
				$meta   = wp_get_attachment_metadata( $attachment_id );
161
				$width  = isset( $width ) ? $width : $meta['width'];
162
				$height = isset( $height ) ? $height : $meta['height'];
163
			}
164
165
			$return = [ $img_url, $width, $height, true ];
166
		}
167
168
		return $return;
169
	}
170
171
	/**
172
	 * Change url for images in srcset
173
	 *
174
	 * @param array  $sources
175
	 * @param array  $size_array
176
	 * @param string $image_src
177
	 * @param array  $image_meta
178
	 * @param int    $attachment_id
179
	 *
180
	 * @return array
181
	 */
182
	public function calculate_image_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ) {
183
		if ( ! empty ( $this->options['cdn_link'] ) ) {
184
			foreach ( $sources as $i => $image_size ) {
185
				if ( $image_size['descriptor'] === 'w' ) {
186
					if ( $attachment_id ) {
187
						$image_src = wp_get_attachment_url( $attachment_id );
188
					}
189
190
					$image_src            = remove_query_arg( 'h', $image_src );
191
					$sources[ $i ]['url'] = add_query_arg( 'w', $image_size['value'], $image_src );
192
				}
193
			}
194
		}
195
196
		return $sources;
197
	}
198
199
	/**
200
	 * Modify image urls in content to use imgix host.
201
	 *
202
	 * @param $content
203
	 *
204
	 * @return string
205
	 */
206
	public function replace_images_in_content( $content ) {
207
	    // Added null to apply filters wp_get_attachment_url to improve compatibility with https://en-gb.wordpress.org/plugins/amazon-s3-and-cloudfront/ - does not break wordpress if the plugin isn't present.
208
		if ( ! empty ( $this->options['cdn_link'] ) ) {
209 View Code Duplication
			if ( preg_match_all( '/<img\s[^>]*src=([\"\']??)([^\" >]*?)\1[^>]*>/iU', $content, $matches ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
				foreach ( $matches[2] as $image_src ) {
211
					$content = str_replace( $image_src, apply_filters( 'wp_get_attachment_url', $image_src, null ), $content );
212
				}
213
			}
214
215
			if ( preg_match_all( '/<img\s[^>]*srcset=([\"\']??)([^\">]*?)\1[^>]*\/?>/iU', $content, $matches ) ) {
216
217
				foreach ( $matches[2] as $image_srcset ) {
218
					$new_image_srcset = preg_replace_callback( '/(\S+)(\s\d+\w)/', function ( $srcset_matches ) {
219
						return apply_filters( 'wp_get_attachment_url', $srcset_matches[1], null ) . $srcset_matches[2];
220
					}, $image_srcset );
221
222
					$content = str_replace( $image_srcset, $new_image_srcset, $content );
223
				}
224
			}
225
226 View Code Duplication
			if ( preg_match_all( '/<a\s[^>]*href=([\"\']??)([^\" >]*?)\1[^>]*>(.*)<\/a>/iU', $content, $matches ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
				foreach ( $matches[0] as $link ) {
228
					$content = str_replace( $link[2], apply_filters( 'wp_get_attachment_url', $link[2], null ), $content );
229
				}
230
			}
231
		}
232
		return $content;
233
	}
234
235
	/**
236
	 * Add tag to dns prefetch cdn host
237
	 */
238
	public function prefetch_cdn() {
239
		if ( ! empty ( $this->options['cdn_link'] ) ) {
240
			$host = parse_url( $this->options['cdn_link'], PHP_URL_HOST );
241
242
			printf(
243
				'<link rel="dns-prefetch" href="%s"/>',
244
				esc_attr( '//' . $host )
245
			);
246
		}
247
	}
248
249
	/**
250
	 * Start output buffer if auto retina is enabled
251
	 */
252
	public function buffer_start_for_retina() {
253
		if ( ! empty ( $this->options['add_dpi2_srcset'] ) ) {
254
			$this->buffer_started = true;
255
			ob_start( [ $this, 'add_retina' ] );
256
		}
257
	}
258
259
	/**
260
	 * Stop output buffer if it was enabled by the plugin
261
	 */
262
	public function buffer_end_for_retina() {
263
		if ( $this->buffer_started === true ) {
264
			ob_end_flush();
265
		}
266
	}
267
268
	/**
269
	 * Returns a array of global parameters to be applied in all images,
270
	 * according to plugin's settings.
271
	 *
272
	 * @return array Global parameters to be appened at the end of each img URL.
273
	 */
274
	protected function get_global_params() {
275
		$params = [];
276
277
		// For now, only "auto" is supported.
278
		$auto = [];
279
		if ( ! empty ( $this->options['auto_format'] ) ) {
280
			array_push( $auto, 'format' );
281
		}
282
283
		if ( ! empty ( $this->options['auto_enhance'] ) ) {
284
			array_push( $auto, 'enhance' );
285
		}
286
287
		if ( ! empty ( $this->options['auto_compress'] ) ) {
288
			array_push( $auto, 'compress' );
289
		}
290
291
		if ( ! empty( $auto ) ) {
292
			$params['auto'] = implode( '%2C', $auto );
293
		}
294
295
		return $params;
296
	}
297
298
	/**
299
	 * Get all defined image sizes
300
	 *
301
	 * @return array
302
	 */
303
	protected function get_all_defined_sizes() {
304
		// Make thumbnails and other intermediate sizes.
305
		$theme_image_sizes = wp_get_additional_image_sizes();
306
307
		$sizes = [];
308
		foreach ( get_intermediate_image_sizes() as $s ) {
309
			$sizes[ $s ] = [ 'width' => '', 'height' => '', 'crop' => false ];
310
			if ( isset( $theme_image_sizes[ $s ] ) ) {
311
				// For theme-added sizes
312
				$sizes[ $s ]['width']  = intval( $theme_image_sizes[ $s ]['width'] );
313
				$sizes[ $s ]['height'] = intval( $theme_image_sizes[ $s ]['height'] );
314
				$sizes[ $s ]['crop']   = $theme_image_sizes[ $s ]['crop'];
315
			} else {
316
				// For default sizes set in options
317
				$sizes[ $s ]['width']  = get_option( "{$s}_size_w" );
318
				$sizes[ $s ]['height'] = get_option( "{$s}_size_h" );
319
				$sizes[ $s ]['crop']   = get_option( "{$s}_crop" );
320
			}
321
		}
322
323
		return $sizes;
324
	}
325
}
326
327
Images_Via_Imgix::instance();
328