Completed
Pull Request — master (#16)
by Rasmus
04:25
created

Images_Via_Imgix::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
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( 'wp_calculate_image_srcset', [ $this, 'replace_host_in_srcset' ], 10 );
36
		add_filter( 'the_content', [ $this, 'replace_images_in_content' ] );
37
		add_action( 'wp_head', [ $this, 'prefetch_cdn' ], 1 );
38
39
		add_action( 'after_setup_theme', [ $this, 'buffer_start_for_retina' ] );
40
		add_action( 'shutdown', [ $this, 'buffer_end_for_retina' ] );
41
	}
42
43
	/**
44
	 * Plugin loader instance.
45
	 *
46
	 * @return Images_Via_Imgix
47
	 */
48
	public static function instance() {
49
		if ( ! isset( self::$instance ) ) {
50
			self::$instance = new self;
51
		}
52
53
		return self::$instance;
54
	}
55
56
	/**
57
	 * Override options from settings.
58
	 * Used in unit tests.
59
	 *
60
	 * @param array $options
61
	 */
62
	public function set_options( $options ) {
63
		$this->options = $options;
64
	}
65
66
	/**
67
	 * Find all img tags with sources matching "imgix.net" without the parameter
68
	 * "srcset" and add the "srcset" parameter to all those images, appending a new
69
	 * source using the "dpr=2" modifier.
70
	 *
71
	 * @param $content
72
	 *
73
	 * @return string Content with retina-enriched image tags.
74
	 */
75
	public function add_retina( $content ) {
76
		$pattern = '/<img((?![^>]+srcset )([^>]*)';
77
		$pattern .= 'src=[\'"]([^\'"]*imgix.net[^\'"]*\?[^\'"]*w=[^\'"]*)[\'"]([^>]*)*?)>/i';
78
		$repl    = '<img$2src="$3" srcset="${3}, ${3}&amp;dpr=2 2x, ${3}&amp;dpr=3 3x,"$4>';
79
		$content = preg_replace( $pattern, $repl, $content );
80
81
		return preg_replace( $pattern, $repl, $content );
82
	}
83
84
	/**
85
	 * Modify image urls for attachments to use imgix host.
86
	 *
87
	 * @param string $url
88
	 *
89
	 * @return string
90
	 */
91
	public function replace_image_url( $url ) {
92
		if ( ! empty ( $this->options['cdn_link'] ) ) {
93
			$pathinfo = pathinfo( $url );
94
95
			if ( in_array( $pathinfo['extension'], [ 'jpg', 'gif', 'png', 'jpeg' ] ) ) {
96
				$parsed_url = parse_url( $url );
97
				if ( isset( $parsed_url['host'] ) && $parsed_url['host'] === parse_url( home_url( '/' ), PHP_URL_HOST ) ) {
98
					$cdn = parse_url( $this->options['cdn_link'] );
99
					foreach ( [ 'scheme', 'host', 'port' ] as $url_part ) {
100
						if ( isset( $cdn[ $url_part ] ) ) {
101
							$parsed_url[ $url_part ] = $cdn[ $url_part ];
102
						} else {
103
							unset( $parsed_url[ $url_part ] );
104
						}
105
					}
106
107
					list( $filename, $arguments ) = $this->convert_filename_to_size_args( $pathinfo['basename'] );
108
109
					$arguments = array_merge( $arguments, $this->get_global_params() );
110
111
					$parsed_url['path'] = trailingslashit( dirname( $parsed_url['path'] ) ) . $filename;
112
113
					if ( ! empty( $arguments ) ) {
114
						$parsed_url['query'] = empty( $parsed_url['query'] ) ? build_query( $arguments ) : $parsed_url['query'] . '&' . build_query( $arguments );
115
					}
116
117
					$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 96 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...
118
				}
119
			}
120
		}
121
122
		return $url;
123
	}
124
125
	/**
126
	 * Modify image urls in srcset to use imgix host.
127
	 *
128
	 * @param array $sources
129
	 *
130
	 * @return array $sources
131
	 */
132
	public function replace_host_in_srcset( $sources ) {
133
		foreach ( $sources as $source ) {
134
			$sources[ $source['value'] ]['url'] = apply_filters( 'imgix/add-image-url', $sources[ $source['value'] ]['url'] );
135
		}
136
137
		return $sources;
138
	}
139
140
	/**
141
	 * Modify image urls in content to use imgix host.
142
	 *
143
	 * @param $content
144
	 *
145
	 * @return string
146
	 */
147
	public function replace_images_in_content( $content ) {
148
		if ( ! empty ( $this->options['cdn_link'] ) ) {
149
			$content = preg_replace_callback( '/(?<=\shref="|\ssrc="|\shref=\'|\ssrc=\').*(?=\'|")/', function ( $match ) {
150
				return esc_url( apply_filters( 'imgix/add-image-url', $match[0] ) );
151
			}, $content );
152
153
		}
154
155
		return $content;
156
	}
157
158
	/**
159
	 * Add tag to dns prefetch cdn host
160
	 */
161
	public function prefetch_cdn() {
162
		if ( ! empty ( $this->options['cdn_link'] ) ) {
163
			$host = parse_url( $this->options['cdn_link'], PHP_URL_HOST );
164
165
			printf(
166
				'<link rel="dns-prefetch" href="%s"/>',
167
				esc_attr( '//' . $host )
168
			);
169
		}
170
	}
171
172
	/**
173
	 * Start output buffer if auto retina is enabled
174
	 */
175
	public function buffer_start_for_retina() {
176
		if ( ! empty ( $this->options['add_dpi2_srcset'] ) ) {
177
			$this->buffer_started = true;
178
			ob_start( [ $this, 'add_retina' ] );
179
		}
180
	}
181
182
	/**
183
	 * Stop output buffer if it was enabled by the plugin
184
	 */
185
	public function buffer_end_for_retina() {
186
		if ( $this->buffer_started === true ) {
187
			ob_end_flush();
188
		}
189
	}
190
191
	/**
192
	 * Returns a array of global parameters to be applied in all images,
193
	 * according to plugin's settings.
194
	 *
195
	 * @return array Global parameters to be appened at the end of each img URL.
196
	 */
197
	protected function get_global_params() {
198
		$params = [];
199
200
		// For now, only "auto" is supported.
201
		$auto = [];
202
		if ( ! empty ( $this->options['auto_format'] ) ) {
203
			array_push( $auto, 'format' );
204
		}
205
206
		if ( ! empty ( $this->options['auto_enhance'] ) ) {
207
			array_push( $auto, 'enhance' );
208
		}
209
210
		if ( ! empty( $auto ) ) {
211
			$params['auto'] = implode( ',', $auto );
212
		}
213
214
		return $params;
215
	}
216
217
	/**
218
	 * Convert sizes in filename to parameters and returns origina filename without sizes.
219
	 * If no size is found the original filename is returned.
220
	 *
221
	 * @param string $filename
222
	 *
223
	 * @return array with filename and size arguments.
224
	 */
225
	protected function convert_filename_to_size_args( $filename ) {
226
		$arguments = [];
227
228
		$filename = preg_replace_callback( '/-(?<width>\d+)x(?<height>\d+)(?<extension>\.\w{3,4}$)/', function ( $match ) use ( &$arguments ) {
229
			$arguments = [
230
				'w' => $match['width'],
231
				'h' => $match['height']
232
			];
233
234
			return $match['extension'];
235
		}, $filename );
236
237
		return [ $filename, $arguments ];
238
	}
239
}
240
241
Images_Via_Imgix::instance();
242