Completed
Pull Request — master (#18)
by Rasmus
01:49
created

Images_Via_Imgix   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 288
rs 8.3157
c 0
b 0
f 0
wmc 43
lcom 2
cbo 0

12 Methods

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