Completed
Pull Request — master (#18)
by Rasmus
37:28
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 ( in_array( $pathinfo['extension'], [ 'jpg', 'gif', 'png', 'jpeg' ] ) ) {
98
				$parsed_url = parse_url( $url );
99
				if ( isset( $parsed_url['host'] ) && $parsed_url['host'] === parse_url( home_url( '/' ), PHP_URL_HOST ) ) {
100
					$cdn = parse_url( $this->options['cdn_link'] );
101
					foreach ( [ 'scheme', 'host', 'port' ] as $url_part ) {
102
						if ( isset( $cdn[ $url_part ] ) ) {
103
							$parsed_url[ $url_part ] = $cdn[ $url_part ];
104
						} else {
105
							unset( $parsed_url[ $url_part ] );
106
						}
107
					}
108
109
					list( $filename, $arguments ) = $this->convert_filename_to_size_args( $pathinfo['basename'] );
110
111
					$arguments = array_merge( $arguments, $this->get_global_params() );
112
113
					$parsed_url['path'] = trailingslashit( dirname( $parsed_url['path'] ) ) . $filename;
114
115
					if ( ! empty( $arguments ) ) {
116
						$parsed_url['query'] = empty( $parsed_url['query'] ) ? build_query( $arguments ) : $parsed_url['query'] . '&' . build_query( $arguments );
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 98 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
			}
122
		}
123
124
		return $url;
125
	}
126
127
	/**
128
	 * Set params when running image_downsize
129
	 *
130
	 * @param false|array  $return
131
	 * @param int          $attachment_id
132
	 * @param string|array $size
133
	 *
134
	 * @return false|array
135
	 */
136
	public function image_downsize( $return, $attachment_id, $size ) {
137
		if ( ! empty ( $this->options['cdn_link'] ) ) {
138
			$img_url    = wp_get_attachment_url( $attachment_id );
139
			$parsed_url = parse_url( $img_url );
140
141
			if ( is_array( $parsed_url ) ) {
142
				parse_str( $parsed_url['query'], $params );
143
144
				if ( is_array( $size ) ) {
145
					$params['w'] = $width = isset( $size[0] ) ? $size[0] : 0;
146
					$params['h'] = $height = isset( $size[1] ) ? $size[1] : 0;
147
				} else {
148
					$available_sizes = $this->get_all_defined_sizes();
149
					if ( isset( $available_sizes[ $size ] ) ) {
150
						$size        = $available_sizes[ $size ];
151
						$params['w'] = $width = $size['width'];
152
						$params['h'] = $height = $size['height'];
153
					}
154
				}
155
156
				$params = array_filter( $params );
157
158
				$parsed_url['query'] = build_query( urlencode_deep( $params ) );
159
160
				$img_url = http_build_url( $parsed_url );
161
162
				if ( ! isset( $width ) || ! isset( $height ) ) {
163
					// any other type: use the real image
164
					$meta   = wp_get_attachment_metadata( $attachment_id );
165
					$width  = isset( $width ) ? $width : $meta['width'];
166
					$height = isset( $height ) ? $height : $meta['height'];
167
				}
168
169
				$return = [ $img_url, $width, $height, true ];
170
			}
171
		}
172
173
		return $return;
174
	}
175
176
	/**
177
	 * Modify image urls in srcset to use imgix host.
178
	 *
179
	 * @param array $sources
180
	 *
181
	 * @return array $sources
182
	 */
183
	public function replace_host_in_srcset( $sources ) {
184
		foreach ( $sources as $source ) {
185
			$sources[ $source['value'] ]['url'] = apply_filters( 'imgix/add-image-url', $sources[ $source['value'] ]['url'] );
186
		}
187
188
		return $sources;
189
	}
190
191
	/**
192
	 * Modify image urls in content to use imgix host.
193
	 *
194
	 * @param $content
195
	 *
196
	 * @return string
197
	 */
198
	public function replace_images_in_content( $content ) {
199
		if ( ! empty ( $this->options['cdn_link'] ) ) {
200
			$content = preg_replace_callback( '/(?<=\shref="|\ssrc="|\shref=\'|\ssrc=\').*(?=\'|")/', function ( $match ) {
201
				return esc_url( apply_filters( 'imgix/add-image-url', $match[0] ) );
202
			}, $content );
203
204
		}
205
206
		return $content;
207
	}
208
209
	/**
210
	 * Add tag to dns prefetch cdn host
211
	 */
212
	public function prefetch_cdn() {
213
		if ( ! empty ( $this->options['cdn_link'] ) ) {
214
			$host = parse_url( $this->options['cdn_link'], PHP_URL_HOST );
215
216
			printf(
217
				'<link rel="dns-prefetch" href="%s"/>',
218
				esc_attr( '//' . $host )
219
			);
220
		}
221
	}
222
223
	/**
224
	 * Start output buffer if auto retina is enabled
225
	 */
226
	public function buffer_start_for_retina() {
227
		if ( ! empty ( $this->options['add_dpi2_srcset'] ) ) {
228
			$this->buffer_started = true;
229
			ob_start( [ $this, 'add_retina' ] );
230
		}
231
	}
232
233
	/**
234
	 * Stop output buffer if it was enabled by the plugin
235
	 */
236
	public function buffer_end_for_retina() {
237
		if ( $this->buffer_started === true ) {
238
			ob_end_flush();
239
		}
240
	}
241
242
	/**
243
	 * Returns a array of global parameters to be applied in all images,
244
	 * according to plugin's settings.
245
	 *
246
	 * @return array Global parameters to be appened at the end of each img URL.
247
	 */
248
	protected function get_global_params() {
249
		$params = [];
250
251
		// For now, only "auto" is supported.
252
		$auto = [];
253
		if ( ! empty ( $this->options['auto_format'] ) ) {
254
			array_push( $auto, 'format' );
255
		}
256
257
		if ( ! empty ( $this->options['auto_enhance'] ) ) {
258
			array_push( $auto, 'enhance' );
259
		}
260
261
		if ( ! empty( $auto ) ) {
262
			$params['auto'] = implode( '%2C', $auto );
263
		}
264
265
		return $params;
266
	}
267
268
	/**
269
	 * Convert sizes in filename to parameters and returns origina filename without sizes.
270
	 * If no size is found the original filename is returned.
271
	 *
272
	 * @param string $filename
273
	 *
274
	 * @return array with filename and size arguments.
275
	 */
276
	protected function convert_filename_to_size_args( $filename ) {
277
		$arguments = [];
278
279
		$filename = preg_replace_callback( '/-(?<width>\d+)x(?<height>\d+)(?<extension>\.\w{3,4}$)/', function ( $match ) use ( &$arguments ) {
280
			$arguments = [
281
				'w' => $match['width'],
282
				'h' => $match['height']
283
			];
284
285
			return $match['extension'];
286
		}, $filename );
287
288
		return [ $filename, $arguments ];
289
	}
290
291
	/**
292
	 * Get all defined image sizes
293
	 *
294
	 * @return array
295
	 */
296
	protected function get_all_defined_sizes() {
297
		// Make thumbnails and other intermediate sizes.
298
		$theme_image_sizes = wp_get_additional_image_sizes();
299
300
		$sizes = [];
301
		foreach ( get_intermediate_image_sizes() as $s ) {
302
			$sizes[ $s ] = [ 'width' => '', 'height' => '', 'crop' => false ];
303
			if ( isset( $theme_image_sizes[ $s ] ) ) {
304
				// For theme-added sizes
305
				$sizes[ $s ]['width']  = intval( $theme_image_sizes[ $s ]['width'] );
306
				$sizes[ $s ]['height'] = intval( $theme_image_sizes[ $s ]['height'] );
307
				$sizes[ $s ]['crop']   = $theme_image_sizes[ $s ]['crop'];
308
			} else {
309
				// For default sizes set in options
310
				$sizes[ $s ]['width']  = get_option( "{$s}_size_w" );
311
				$sizes[ $s ]['height'] = get_option( "{$s}_size_h" );
312
				$sizes[ $s ]['crop']   = get_option( "{$s}_crop" );
313
			}
314
		}
315
316
		return $sizes;
317
	}
318
}
319
320
Images_Via_Imgix::instance();
321