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

Images_Via_Imgix::image_downsize()   D

Complexity

Conditions 10
Paths 31

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 21
nc 31
nop 3
dl 0
loc 33
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
116
					$arguments = [];
117
					if ( ! empty( $parsed_url['query'] ) ) {
118
						parse_str( $parsed_url['query'], $arguments );
119
					}
120
					$arguments = array_merge( $arguments, $this->get_global_params() );
121
122
					if ( ! empty( $arguments ) ) {
123
						$parsed_url['query'] = build_query( urlencode_deep( $arguments ) );
124
					}
125
126
					$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...
127
				}
128
			}
129
		}
130
131
		return $url;
132
	}
133
134
	/**
135
	 * Set params when running image_downsize
136
	 *
137
	 * @param false|array  $return
138
	 * @param int          $attachment_id
139
	 * @param string|array $size
140
	 *
141
	 * @return false|array
142
	 */
143
	public function image_downsize( $return, $attachment_id, $size ) {
144
		if ( ! empty ( $this->options['cdn_link'] ) ) {
145
			$img_url = wp_get_attachment_url( $attachment_id );
146
147
			$params = [];
148
			if ( is_array( $size ) ) {
149
				$params['w'] = $width = isset( $size[0] ) ? $size[0] : 0;
150
				$params['h'] = $height = isset( $size[1] ) ? $size[1] : 0;
151
			} else {
152
				$available_sizes = $this->get_all_defined_sizes();
153
				if ( isset( $available_sizes[ $size ] ) ) {
154
					$size        = $available_sizes[ $size ];
155
					$params['w'] = $width = $size['width'];
156
					$params['h'] = $height = $size['height'];
157
				}
158
			}
159
160
			$params = array_filter( $params );
161
162
			$img_url = add_query_arg( $params, $img_url );
163
164
			if ( ! isset( $width ) || ! isset( $height ) ) {
165
				// any other type: use the real image
166
				$meta   = wp_get_attachment_metadata( $attachment_id );
167
				$width  = isset( $width ) ? $width : $meta['width'];
168
				$height = isset( $height ) ? $height : $meta['height'];
169
			}
170
171
			$return = [ $img_url, $width, $height, true ];
172
		}
173
174
		return $return;
175
	}
176
177
	/**
178
	 * Change url for images in srcset
179
	 *
180
	 * @param array  $image_meta
181
	 * @param array  $size_array
182
	 * @param string $image_src
183
	 *
184
	 * @return array
185
	 */
186
	public function calculate_image_srcset( $image_meta, $size_array, $image_src ) {
187
		foreach ( $image_meta as $i => $image_size ) {
188
			if ( $image_size['descriptor'] === 'w' ) {
189
				$image_meta[ $i ]['url'] = add_query_arg( 'w', $image_size['value'], $image_src );
190
			}
191
		}
192
193
		return $image_meta;
194
	}
195
196
	/**
197
	 * Modify image urls in content to use imgix host.
198
	 *
199
	 * @param $content
200
	 *
201
	 * @return string
202
	 */
203
	public function replace_images_in_content( $content ) {
204
		if ( ! empty ( $this->options['cdn_link'] ) ) {
205
			$content = preg_replace_callback( '/(?<=\shref="|\ssrc="|\shref=\'|\ssrc=\').*(?=\'|")/', function ( $match ) {
206
				return esc_url( apply_filters( 'imgix/add-image-url', $match[0] ) );
207
			}, $content );
208
209
		}
210
211
		return $content;
212
	}
213
214
	/**
215
	 * Add tag to dns prefetch cdn host
216
	 */
217
	public function prefetch_cdn() {
218
		if ( ! empty ( $this->options['cdn_link'] ) ) {
219
			$host = parse_url( $this->options['cdn_link'], PHP_URL_HOST );
220
221
			printf(
222
				'<link rel="dns-prefetch" href="%s"/>',
223
				esc_attr( '//' . $host )
224
			);
225
		}
226
	}
227
228
	/**
229
	 * Start output buffer if auto retina is enabled
230
	 */
231
	public function buffer_start_for_retina() {
232
		if ( ! empty ( $this->options['add_dpi2_srcset'] ) ) {
233
			$this->buffer_started = true;
234
			ob_start( [ $this, 'add_retina' ] );
235
		}
236
	}
237
238
	/**
239
	 * Stop output buffer if it was enabled by the plugin
240
	 */
241
	public function buffer_end_for_retina() {
242
		if ( $this->buffer_started === true ) {
243
			ob_end_flush();
244
		}
245
	}
246
247
	/**
248
	 * Returns a array of global parameters to be applied in all images,
249
	 * according to plugin's settings.
250
	 *
251
	 * @return array Global parameters to be appened at the end of each img URL.
252
	 */
253
	protected function get_global_params() {
254
		$params = [];
255
256
		// For now, only "auto" is supported.
257
		$auto = [];
258
		if ( ! empty ( $this->options['auto_format'] ) ) {
259
			array_push( $auto, 'format' );
260
		}
261
262
		if ( ! empty ( $this->options['auto_enhance'] ) ) {
263
			array_push( $auto, 'enhance' );
264
		}
265
266
		if ( ! empty( $auto ) ) {
267
			$params['auto'] = implode( '%2C', $auto );
268
		}
269
270
		return $params;
271
	}
272
273
	/**
274
	 * Get all defined image sizes
275
	 *
276
	 * @return array
277
	 */
278
	protected function get_all_defined_sizes() {
279
		// Make thumbnails and other intermediate sizes.
280
		$theme_image_sizes = wp_get_additional_image_sizes();
281
282
		$sizes = [];
283
		foreach ( get_intermediate_image_sizes() as $s ) {
284
			$sizes[ $s ] = [ 'width' => '', 'height' => '', 'crop' => false ];
285
			if ( isset( $theme_image_sizes[ $s ] ) ) {
286
				// For theme-added sizes
287
				$sizes[ $s ]['width']  = intval( $theme_image_sizes[ $s ]['width'] );
288
				$sizes[ $s ]['height'] = intval( $theme_image_sizes[ $s ]['height'] );
289
				$sizes[ $s ]['crop']   = $theme_image_sizes[ $s ]['crop'];
290
			} else {
291
				// For default sizes set in options
292
				$sizes[ $s ]['width']  = get_option( "{$s}_size_w" );
293
				$sizes[ $s ]['height'] = get_option( "{$s}_size_h" );
294
				$sizes[ $s ]['crop']   = get_option( "{$s}_crop" );
295
			}
296
		}
297
298
		return $sizes;
299
	}
300
}
301
302
Images_Via_Imgix::instance();
303