Completed
Pull Request — master (#18)
by Rasmus
02:13
created

Images_Via_Imgix   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 289
rs 8.2769
c 0
b 0
f 0
wmc 41
lcom 2
cbo 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 1
A instance() 0 7 2
A set_options() 0 3 1
A add_retina() 0 8 1
C replace_image_url() 0 30 8
D image_downsize() 0 33 10
A calculate_image_srcset() 0 9 3
A replace_images_in_content() 0 10 2
A prefetch_cdn() 0 10 2
A buffer_start_for_retina() 0 6 2
A buffer_end_for_retina() 0 5 2
A get_global_params() 0 19 4
A get_all_defined_sizes() 0 22 3

How to fix   Complexity   

Complex Class

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
		add_filter( 'wp_get_attachment_url', [ $this, 'replace_image_url' ] );
33
		add_filter( 'imgix/add-image-url', [ $this, 'replace_image_url' ] );
34
35
		add_filter( 'image_downsize', [ $this, 'image_downsize' ], 10, 3 );
36
37
		add_filter( 'wp_calculate_image_srcset', [ $this, 'calculate_image_srcset' ], 10, 3 );
38
39
		add_filter( 'the_content', [ $this, 'replace_images_in_content' ] );
40
		add_action( 'wp_head', [ $this, 'prefetch_cdn' ], 1 );
41
42
		add_action( 'after_setup_theme', [ $this, 'buffer_start_for_retina' ] );
43
		add_action( 'shutdown', [ $this, 'buffer_end_for_retina' ] );
44
	}
45
46
	/**
47
	 * Plugin loader instance.
48
	 *
49
	 * @return Images_Via_Imgix
50
	 */
51
	public static function instance() {
52
		if ( ! isset( self::$instance ) ) {
53
			self::$instance = new self;
54
		}
55
56
		return self::$instance;
57
	}
58
59
	/**
60
	 * Override options from settings.
61
	 * Used in unit tests.
62
	 *
63
	 * @param array $options
64
	 */
65
	public function set_options( $options ) {
66
		$this->options = $options;
67
	}
68
69
	/**
70
	 * Find all img tags with sources matching "imgix.net" without the parameter
71
	 * "srcset" and add the "srcset" parameter to all those images, appending a new
72
	 * source using the "dpr=2" modifier.
73
	 *
74
	 * @param $content
75
	 *
76
	 * @return string Content with retina-enriched image tags.
77
	 */
78
	public function add_retina( $content ) {
79
		$pattern = '/<img((?![^>]+srcset )([^>]*)';
80
		$pattern .= 'src=[\'"]([^\'"]*imgix.net[^\'"]*\?[^\'"]*w=[^\'"]*)[\'"]([^>]*)*?)>/i';
81
		$repl    = '<img$2src="$3" srcset="${3}, ${3}&amp;dpr=2 2x, ${3}&amp;dpr=3 3x,"$4>';
82
		$content = preg_replace( $pattern, $repl, $content );
83
84
		return preg_replace( $pattern, $repl, $content );
85
	}
86
87
	/**
88
	 * Modify image urls for attachments to use imgix host.
89
	 *
90
	 * @param string $url
91
	 *
92
	 * @return string
93
	 */
94
	public function replace_image_url( $url ) {
95
		if ( ! empty ( $this->options['cdn_link'] ) ) {
96
			$pathinfo = pathinfo( $url );
97
98
			if ( isset( $pathinfo['extension'] ) && in_array( $pathinfo['extension'], [
99
					'jpg',
100
					'gif',
101
					'png',
102
					'jpeg'
103
				] )
104
			) {
105
				$parsed_url = parse_url( $url );
106
				if ( isset( $parsed_url['host'] ) && $parsed_url['host'] === parse_url( home_url( '/' ), PHP_URL_HOST ) ) {
107
					$cdn = parse_url( $this->options['cdn_link'] );
108
					foreach ( [ 'scheme', 'host', 'port' ] as $url_part ) {
109
						if ( isset( $cdn[ $url_part ] ) ) {
110
							$parsed_url[ $url_part ] = $cdn[ $url_part ];
111
						} else {
112
							unset( $parsed_url[ $url_part ] );
113
						}
114
					}
115
					$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 105 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...
116
117
					$url = add_query_arg( $this->get_global_params(), $url );
118
				}
119
			}
120
		}
121
122
		return $url;
123
	}
124
125
	/**
126
	 * Set params when running image_downsize
127
	 *
128
	 * @param false|array  $return
129
	 * @param int          $attachment_id
130
	 * @param string|array $size
131
	 *
132
	 * @return false|array
133
	 */
134
	public function image_downsize( $return, $attachment_id, $size ) {
135
		if ( ! empty ( $this->options['cdn_link'] ) ) {
136
			$img_url = wp_get_attachment_url( $attachment_id );
137
138
			$params = [];
139
			if ( is_array( $size ) ) {
140
				$params['w'] = $width = isset( $size[0] ) ? $size[0] : 0;
141
				$params['h'] = $height = isset( $size[1] ) ? $size[1] : 0;
142
			} else {
143
				$available_sizes = $this->get_all_defined_sizes();
144
				if ( isset( $available_sizes[ $size ] ) ) {
145
					$size        = $available_sizes[ $size ];
146
					$params['w'] = $width = $size['width'];
147
					$params['h'] = $height = $size['height'];
148
				}
149
			}
150
151
			$params = array_filter( $params );
152
153
			$img_url = add_query_arg( $params, $img_url );
154
155
			if ( ! isset( $width ) || ! isset( $height ) ) {
156
				// any other type: use the real image
157
				$meta   = wp_get_attachment_metadata( $attachment_id );
158
				$width  = isset( $width ) ? $width : $meta['width'];
159
				$height = isset( $height ) ? $height : $meta['height'];
160
			}
161
162
			$return = [ $img_url, $width, $height, true ];
163
		}
164
165
		return $return;
166
	}
167
168
	/**
169
	 * Change url for images in srcset
170
	 *
171
	 * @param array  $image_meta
172
	 * @param array  $size_array
173
	 * @param string $image_src
174
	 *
175
	 * @return array
176
	 */
177
	public function calculate_image_srcset( $image_meta, $size_array, $image_src ) {
178
		foreach ( $image_meta as $i => $image_size ) {
179
			if ( $image_size['descriptor'] === 'w' ) {
180
				$image_meta[ $i ]['url'] = add_query_arg( 'w', $image_size['value'], $image_src );
181
			}
182
		}
183
184
		return $image_meta;
185
	}
186
187
	/**
188
	 * Modify image urls in content to use imgix host.
189
	 *
190
	 * @param $content
191
	 *
192
	 * @return string
193
	 */
194
	public function replace_images_in_content( $content ) {
195
		if ( ! empty ( $this->options['cdn_link'] ) ) {
196
			$content = preg_replace_callback( '/(?<=\shref="|\ssrc="|\shref=\'|\ssrc=\').*(?=\'|")/', function ( $match ) {
197
				return esc_url( apply_filters( 'imgix/add-image-url', $match[0] ) );
198
			}, $content );
199
200
		}
201
202
		return $content;
203
	}
204
205
	/**
206
	 * Add tag to dns prefetch cdn host
207
	 */
208
	public function prefetch_cdn() {
209
		if ( ! empty ( $this->options['cdn_link'] ) ) {
210
			$host = parse_url( $this->options['cdn_link'], PHP_URL_HOST );
211
212
			printf(
213
				'<link rel="dns-prefetch" href="%s"/>',
214
				esc_attr( '//' . $host )
215
			);
216
		}
217
	}
218
219
	/**
220
	 * Start output buffer if auto retina is enabled
221
	 */
222
	public function buffer_start_for_retina() {
223
		if ( ! empty ( $this->options['add_dpi2_srcset'] ) ) {
224
			$this->buffer_started = true;
225
			ob_start( [ $this, 'add_retina' ] );
226
		}
227
	}
228
229
	/**
230
	 * Stop output buffer if it was enabled by the plugin
231
	 */
232
	public function buffer_end_for_retina() {
233
		if ( $this->buffer_started === true ) {
234
			ob_end_flush();
235
		}
236
	}
237
238
	/**
239
	 * Returns a array of global parameters to be applied in all images,
240
	 * according to plugin's settings.
241
	 *
242
	 * @return array Global parameters to be appened at the end of each img URL.
243
	 */
244
	protected function get_global_params() {
245
		$params = [];
246
247
		// For now, only "auto" is supported.
248
		$auto = [];
249
		if ( ! empty ( $this->options['auto_format'] ) ) {
250
			array_push( $auto, 'format' );
251
		}
252
253
		if ( ! empty ( $this->options['auto_enhance'] ) ) {
254
			array_push( $auto, 'enhance' );
255
		}
256
257
		if ( ! empty( $auto ) ) {
258
			$params['auto'] = implode( '%2C', $auto );
259
		}
260
261
		return $params;
262
	}
263
264
	/**
265
	 * Get all defined image sizes
266
	 *
267
	 * @return array
268
	 */
269
	protected function get_all_defined_sizes() {
270
		// Make thumbnails and other intermediate sizes.
271
		$theme_image_sizes = wp_get_additional_image_sizes();
272
273
		$sizes = [];
274
		foreach ( get_intermediate_image_sizes() as $s ) {
275
			$sizes[ $s ] = [ 'width' => '', 'height' => '', 'crop' => false ];
276
			if ( isset( $theme_image_sizes[ $s ] ) ) {
277
				// For theme-added sizes
278
				$sizes[ $s ]['width']  = intval( $theme_image_sizes[ $s ]['width'] );
279
				$sizes[ $s ]['height'] = intval( $theme_image_sizes[ $s ]['height'] );
280
				$sizes[ $s ]['crop']   = $theme_image_sizes[ $s ]['crop'];
281
			} else {
282
				// For default sizes set in options
283
				$sizes[ $s ]['width']  = get_option( "{$s}_size_w" );
284
				$sizes[ $s ]['height'] = get_option( "{$s}_size_h" );
285
				$sizes[ $s ]['crop']   = get_option( "{$s}_crop" );
286
			}
287
		}
288
289
		return $sizes;
290
	}
291
}
292
293
Images_Via_Imgix::instance();
294