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

Images_Via_Imgix::image_downsize()   C

Complexity

Conditions 11
Paths 32

Size

Total Lines 39
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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