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
|
|
|
// Change filter load order to ensure it loads after other CDN url transformations i.e. Amazon S3 which loads at position 99. |
33
|
|
|
add_filter( 'wp_get_attachment_url', [ $this, 'replace_image_url' ], 100 ); |
34
|
|
|
add_filter( 'imgix/add-image-url', [ $this, 'replace_image_url' ] ); |
35
|
|
|
|
36
|
|
|
add_filter( 'image_downsize', [ $this, 'image_downsize' ], 10, 3 ); |
37
|
|
|
|
38
|
|
|
add_filter( 'wp_calculate_image_srcset', [ $this, 'calculate_image_srcset' ], 10, 5 ); |
39
|
|
|
|
40
|
|
|
add_filter( 'the_content', [ $this, 'replace_images_in_content' ] ); |
41
|
|
|
add_action( 'wp_head', [ $this, 'prefetch_cdn' ], 1 ); |
42
|
|
|
|
43
|
|
|
add_action( 'after_setup_theme', [ $this, 'buffer_start_for_retina' ] ); |
44
|
|
|
add_action( 'shutdown', [ $this, 'buffer_end_for_retina' ], 0 ); |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Plugin loader instance. |
49
|
|
|
* |
50
|
|
|
* @return Images_Via_Imgix |
51
|
|
|
*/ |
52
|
|
|
public static function instance() { |
53
|
|
|
if ( ! isset( self::$instance ) ) { |
54
|
|
|
self::$instance = new self; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
return self::$instance; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Set a single option. |
62
|
|
|
* |
63
|
|
|
* @param string $key |
64
|
|
|
* @param mixed $value |
65
|
|
|
*/ |
66
|
|
|
public function set_option( $key, $value ) { |
67
|
|
|
$this->options[ $key ] = $value; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Get a single option. |
72
|
|
|
* |
73
|
|
|
* @param string $key |
74
|
|
|
* @param mixed $default |
75
|
|
|
* @return mixed |
76
|
|
|
*/ |
77
|
|
|
public function get_option( $key, $default = '' ) { |
78
|
|
|
return array_key_exists( $key, $this->options ) ? $this->options[ $key ] : $default; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Override options from settings. |
83
|
|
|
* Used in unit tests. |
84
|
|
|
* |
85
|
|
|
* @param array $options |
86
|
|
|
*/ |
87
|
|
|
public function set_options( $options ) { |
88
|
|
|
$this->options = $options; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Find all img tags with sources matching "imgix.net" without the parameter |
93
|
|
|
* "srcset" and add the "srcset" parameter to all those images, appending a new |
94
|
|
|
* source using the "dpr=2" modifier. |
95
|
|
|
* |
96
|
|
|
* @param $content |
97
|
|
|
* |
98
|
|
|
* @return string Content with retina-enriched image tags. |
99
|
|
|
*/ |
100
|
|
|
public function add_retina( $content ) { |
101
|
|
|
$pattern = '/<img((?![^>]+srcset )([^>]*)'; |
102
|
|
|
$pattern .= 'src=[\'"]([^\'"]*imgix.net[^\'"]*\?[^\'"]*w=[^\'"]*)[\'"]([^>]*)*?)>/i'; |
103
|
|
|
$repl = '<img$2src="$3" srcset="${3}, ${3}&dpr=2 2x, ${3}&dpr=3 3x,"$4>'; |
104
|
|
|
$content = preg_replace( $pattern, $repl, $content ); |
105
|
|
|
|
106
|
|
|
return preg_replace( $pattern, $repl, $content ); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Modify image urls for attachments to use imgix host. |
111
|
|
|
* |
112
|
|
|
* @param string $url |
113
|
|
|
* |
114
|
|
|
* @return string |
115
|
|
|
*/ |
116
|
|
|
public function replace_image_url( $url ) { |
117
|
|
|
if ( ! empty ( $this->options['cdn_link'] ) ) { |
118
|
|
|
$parsed_url = parse_url( $url ); |
119
|
|
|
|
120
|
|
|
//Check if image is hosted on current site url -OR- the CDN url specified. Using strpos because we're comparing the host to a full CDN url. |
121
|
|
|
if ( |
122
|
|
|
isset( $parsed_url['host'], $parsed_url['path'] ) |
123
|
|
|
&& ($parsed_url['host'] === parse_url( home_url( '/' ), PHP_URL_HOST ) || ( isset($this->options['external_cdn_link']) && ! empty($this->options['external_cdn_link']) && strpos( $this->options['external_cdn_link'], $parsed_url['host']) !== false ) ) |
124
|
|
|
&& preg_match( '/\.(jpg|jpeg|gif|png)$/i', $parsed_url['path'] ) |
125
|
|
|
) { |
126
|
|
|
$cdn = parse_url( $this->options['cdn_link'] ); |
127
|
|
|
|
128
|
|
|
foreach ( [ 'scheme', 'host', 'port' ] as $url_part ) { |
129
|
|
|
if ( isset( $cdn[ $url_part ] ) ) { |
130
|
|
|
$parsed_url[ $url_part ] = $cdn[ $url_part ]; |
131
|
|
|
} else { |
132
|
|
|
unset( $parsed_url[ $url_part ] ); |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
if ( ! empty( $this->options['external_cdn_link'] ) ) { |
137
|
|
|
$cdn_path = parse_url( $this->options['external_cdn_link'], PHP_URL_PATH ); |
138
|
|
|
|
139
|
|
|
if ( isset( $cdn_path, $parsed_url['path'] ) && $cdn_path !== '/' && ! empty( $parsed_url['path'] ) ) { |
140
|
|
|
$parsed_url['path'] = str_replace( $cdn_path, '', $parsed_url['path'] ); |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
$url = http_build_url( $parsed_url ); |
|
|
|
|
145
|
|
|
|
146
|
|
|
$url = add_query_arg( $this->get_global_params(), $url ); |
147
|
|
|
} |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
return $url; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Set params when running image_downsize |
155
|
|
|
* |
156
|
|
|
* @param false|array $return |
157
|
|
|
* @param int $attachment_id |
158
|
|
|
* @param string|array $size |
159
|
|
|
* |
160
|
|
|
* @return false|array |
161
|
|
|
*/ |
162
|
|
|
public function image_downsize( $return, $attachment_id, $size ) { |
163
|
|
|
if ( ! empty ( $this->options['cdn_link'] ) ) { |
164
|
|
|
$img_url = wp_get_attachment_url( $attachment_id ); |
165
|
|
|
|
166
|
|
|
$params = []; |
167
|
|
|
if ( is_array( $size ) ) { |
168
|
|
|
$params['w'] = $width = isset( $size[0] ) ? $size[0] : 0; |
169
|
|
|
$params['h'] = $height = isset( $size[1] ) ? $size[1] : 0; |
170
|
|
|
} else { |
171
|
|
|
$available_sizes = $this->get_all_defined_sizes(); |
172
|
|
|
if ( isset( $available_sizes[ $size ] ) ) { |
173
|
|
|
$size = $available_sizes[ $size ]; |
174
|
|
|
$params['w'] = $width = $size['width']; |
175
|
|
|
$params['h'] = $height = $size['height']; |
176
|
|
|
} |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
$params = array_filter( $params ); |
180
|
|
|
|
181
|
|
|
$img_url = add_query_arg( $params, $img_url ); |
182
|
|
|
|
183
|
|
|
if ( ! isset( $width ) || ! isset( $height ) ) { |
184
|
|
|
// any other type: use the real image |
185
|
|
|
$meta = wp_get_attachment_metadata( $attachment_id ); |
186
|
|
|
|
187
|
|
|
// Image sizes is missing for pdf thumbnails |
188
|
|
|
$meta['width'] = isset( $meta['width'] ) ? $meta['width'] : 0; |
189
|
|
|
$meta['height'] = isset( $meta['height'] ) ? $meta['height'] : 0; |
190
|
|
|
|
191
|
|
|
$width = isset( $width ) ? $width : $meta['width']; |
192
|
|
|
$height = isset( $height ) ? $height : $meta['height']; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
$return = [ $img_url, $width, $height, true ]; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
return $return; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Change url for images in srcset |
203
|
|
|
* |
204
|
|
|
* @param array $sources |
205
|
|
|
* @param array $size_array |
206
|
|
|
* @param string $image_src |
207
|
|
|
* @param array $image_meta |
208
|
|
|
* @param int $attachment_id |
209
|
|
|
* |
210
|
|
|
* @return array |
211
|
|
|
*/ |
212
|
|
|
public function calculate_image_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ) { |
213
|
|
|
if ( ! empty ( $this->options['cdn_link'] ) ) { |
214
|
|
|
foreach ( $sources as $i => $image_size ) { |
215
|
|
|
if ( $image_size['descriptor'] === 'w' ) { |
216
|
|
|
if ( $attachment_id ) { |
217
|
|
|
$image_src = wp_get_attachment_url( $attachment_id ); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
$image_src = remove_query_arg( 'h', $image_src ); |
221
|
|
|
$sources[ $i ]['url'] = add_query_arg( 'w', $image_size['value'], $image_src ); |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
return $sources; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Modify image urls in content to use imgix host. |
231
|
|
|
* |
232
|
|
|
* @param $content |
233
|
|
|
* |
234
|
|
|
* @return string |
235
|
|
|
*/ |
236
|
|
|
public function replace_images_in_content( $content ) { |
237
|
|
|
// Added null to apply filters wp_get_attachment_url to improve compatibility with https://en-gb.wordpress.org/plugins/amazon-s3-and-cloudfront/ - does not break wordpress if the plugin isn't present. |
238
|
|
|
if ( ! empty ( $this->options['cdn_link'] ) ) { |
239
|
|
View Code Duplication |
if ( preg_match_all( '/<img\s[^>]*src=([\"\']??)([^\" >]*?)\1[^>]*>/iU', $content, $matches ) ) { |
|
|
|
|
240
|
|
|
foreach ( $matches[2] as $image_src ) { |
241
|
|
|
$content = str_replace( $image_src, apply_filters( 'wp_get_attachment_url', $image_src, null ), $content ); |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
if ( preg_match_all( '/<img\s[^>]*srcset=([\"\']??)([^\">]*?)\1[^>]*\/?>/iU', $content, $matches ) ) { |
246
|
|
|
|
247
|
|
|
foreach ( $matches[2] as $image_srcset ) { |
248
|
|
|
$new_image_srcset = preg_replace_callback( '/(\S+)(\s\d+\w)/', function ( $srcset_matches ) { |
249
|
|
|
return apply_filters( 'wp_get_attachment_url', $srcset_matches[1], null ) . $srcset_matches[2]; |
250
|
|
|
}, $image_srcset ); |
251
|
|
|
|
252
|
|
|
$content = str_replace( $image_srcset, $new_image_srcset, $content ); |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
256
|
|
View Code Duplication |
if ( preg_match_all( '/<a\s[^>]*href=([\"\']??)([^\" >]*?)\1[^>]*>(.*)<\/a>/iU', $content, $matches ) ) { |
|
|
|
|
257
|
|
|
foreach ( $matches[0] as $link ) { |
258
|
|
|
$content = str_replace( $link[2], apply_filters( 'wp_get_attachment_url', $link[2], null ), $content ); |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
262
|
|
View Code Duplication |
if ( preg_match_all('/url\(([\s])?([\"|\'])?(.*?)([\"|\'])?([\s])?\)/i', $content, $matches ) ) { |
|
|
|
|
263
|
|
|
foreach ( $matches[3] as $image_src ) { |
264
|
|
|
$content = str_replace( $image_src, apply_filters( 'wp_get_attachment_url', $image_src, null ), $content ); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
return $content; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Add tag to dns prefetch cdn host |
273
|
|
|
*/ |
274
|
|
|
public function prefetch_cdn() { |
275
|
|
|
if ( ! empty ( $this->options['cdn_link'] ) ) { |
276
|
|
|
$host = parse_url( $this->options['cdn_link'], PHP_URL_HOST ); |
277
|
|
|
|
278
|
|
|
printf( |
279
|
|
|
'<link rel="dns-prefetch" href="%s"/>', |
280
|
|
|
esc_attr( '//' . $host ) |
281
|
|
|
); |
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Start output buffer if auto retina is enabled |
287
|
|
|
*/ |
288
|
|
|
public function buffer_start_for_retina() { |
289
|
|
|
if ( ! empty ( $this->options['add_dpi2_srcset'] ) ) { |
290
|
|
|
$this->buffer_started = ob_start( [ $this, 'add_retina' ] ); |
291
|
|
|
} |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Stop output buffer if it was enabled by the plugin |
296
|
|
|
*/ |
297
|
|
|
public function buffer_end_for_retina() { |
298
|
|
|
if ( $this->buffer_started && ob_get_level() ) { |
299
|
|
|
ob_end_flush(); |
300
|
|
|
} |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Returns a array of global parameters to be applied in all images, |
305
|
|
|
* according to plugin's settings. |
306
|
|
|
* |
307
|
|
|
* @return array Global parameters to be appened at the end of each img URL. |
308
|
|
|
*/ |
309
|
|
|
protected function get_global_params() { |
310
|
|
|
$params = []; |
311
|
|
|
|
312
|
|
|
// For now, only "auto" is supported. |
313
|
|
|
$auto = []; |
314
|
|
|
if ( ! empty ( $this->options['auto_format'] ) ) { |
315
|
|
|
array_push( $auto, 'format' ); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
if ( ! empty ( $this->options['auto_enhance'] ) ) { |
319
|
|
|
array_push( $auto, 'enhance' ); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
if ( ! empty ( $this->options['auto_compress'] ) ) { |
323
|
|
|
array_push( $auto, 'compress' ); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
if ( ! empty( $auto ) ) { |
327
|
|
|
$params['auto'] = implode( '%2C', $auto ); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
return $params; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Get all defined image sizes |
335
|
|
|
* |
336
|
|
|
* @return array |
337
|
|
|
*/ |
338
|
|
|
protected function get_all_defined_sizes() { |
339
|
|
|
// Make thumbnails and other intermediate sizes. |
340
|
|
|
$theme_image_sizes = wp_get_additional_image_sizes(); |
341
|
|
|
|
342
|
|
|
$sizes = []; |
343
|
|
|
foreach ( get_intermediate_image_sizes() as $s ) { |
344
|
|
|
$sizes[ $s ] = [ 'width' => '', 'height' => '', 'crop' => false ]; |
345
|
|
|
if ( isset( $theme_image_sizes[ $s ] ) ) { |
346
|
|
|
// For theme-added sizes |
347
|
|
|
$sizes[ $s ]['width'] = intval( $theme_image_sizes[ $s ]['width'] ); |
348
|
|
|
$sizes[ $s ]['height'] = intval( $theme_image_sizes[ $s ]['height'] ); |
349
|
|
|
$sizes[ $s ]['crop'] = $theme_image_sizes[ $s ]['crop']; |
350
|
|
|
} else { |
351
|
|
|
// For default sizes set in options |
352
|
|
|
$sizes[ $s ]['width'] = get_option( "{$s}_size_w" ); |
353
|
|
|
$sizes[ $s ]['height'] = get_option( "{$s}_size_h" ); |
354
|
|
|
$sizes[ $s ]['crop'] = get_option( "{$s}_crop" ); |
355
|
|
|
} |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
return $sizes; |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
Images_Via_Imgix::instance(); |
363
|
|
|
|
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
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 returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.