PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 4.2.8
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v4.2.8
4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 3.12.1 All 134 releases
optimole-wp / inc / url_replacer.php

url_replacer.php in Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization 4.2.8, at inc/url_replacer.php

468 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Optimole\Sdk\Optimole;
4 use Optimole\Sdk\Resource\Image;
5 use Optimole\Sdk\Resource\ImageProperty\GravityProperty;
6 use Optimole\Sdk\ValueObject\Position;
7
8 /**
9 * The class handles the url replacements.
10 *
11 * @package \Optml\Inc
12 * @author Optimole <friends@optimole.com>
13 */
14 final class Optml_Url_Replacer extends Optml_App_Replacer {
15
16 use Optml_Dam_Offload_Utils;
17 use Optml_Validator;
18 use Optml_Normalizer;
19
20 /**
21 * Cached object instance.
22 *
23 * @var Optml_Url_Replacer
24 */
25 protected static $instance = null;
26
27 /**
28 * Class instance method.
29 *
30 * @codeCoverageIgnore
31 * @static
32 * @return Optml_Url_Replacer
33 * @since 1.0.0
34 * @access public
35 */
36 public static function instance() {
37 if ( null === self::$instance ) {
38 self::$instance = new self();
39 add_action( 'optml_replacer_setup', [ self::$instance, 'init' ] );
40 }
41
42 return self::$instance;
43 }
44
45
46 /**
47 * The initialize method.
48 */
49 public function init() {
50
51 add_filter( 'optml_replace_image', [ $this, 'build_url' ], 10, 2 );
52 parent::init();
53
54 add_filter( 'optml_content_url', [ $this, 'build_url' ], 1, 2 );
55 }
56
57 /**
58 * Returns a signed image url authorized to be used in our CDN.
59 *
60 * @param string $url The url which should be signed.
61 * @param array $args Dimension params; Supports `width` and `height`.
62 * @param bool $retried Whether the url has been retried.
63 * @return string
64 */
65 public function build_url(
66 $url,
67 $args = [
68 'width' => 'auto',
69 'height' => 'auto',
70 ],
71 $retried = false
72 ) {
73 if ( apply_filters( 'optml_dont_replace_url', false, $url ) ) {
74 return $url;
75 }
76
77 if ( OPTML_DEBUG ) {
78 do_action( 'optml_log', 'building url: ' . $url . ' args: ' . print_r( $args, true ) );
79 }
80 $original_url = $url;
81
82 $is_slashed = strpos( $url, '\/' ) !== false;
83
84 // We do a little hack here, for json unicode chars we first replace them with html special chars,
85 // we then strip slashes to normalize the URL and last we convert html special chars back to get a clean URL
86 $url = $is_slashed ? html_entity_decode( stripslashes( preg_replace( '/\\\u([\da-fA-F]{4})/', '&#x\1;', $url ) ) ) : ( $url );
87 $is_uploaded = Optml_Media_Offload::is_not_processed_image( $url );
88 if ( $is_uploaded === true ) {
89 $attachment_id = [];
90 preg_match( '/\/' . Optml_Media_Offload::KEYS['not_processed_flag'] . '([^\/]*)\//', $url, $attachment_id );
91 if ( ! isset( $attachment_id[1] ) ) {
92 return $url;
93 }
94 $id_and_filename = [];
95 preg_match( '/\/(' . Optml_Media_Offload::KEYS['uploaded_flag'] . '.*)/', $url, $id_and_filename );
96 if ( isset( $id_and_filename[0] ) ) {
97 $id_and_filename = $id_and_filename[0];
98 }
99 $sizes = $this->parse_dimension_from_optimized_url( $url );
100 if ( isset( $sizes[0] ) && $sizes[0] !== false ) {
101 $args['width'] = $sizes[0];
102 }
103 if ( isset( $sizes[1] ) && $sizes[1] !== false ) {
104 $args['height'] = $sizes[1];
105 }
106 $unoptimized_url = false;
107 if ( $attachment_id[1] === 'media_cloud' ) {
108 $tmp_url = explode( '/', $id_and_filename, 3 );
109 if ( isset( $tmp_url[2] ) ) {
110 $unoptimized_url = $tmp_url[2];
111 }
112 } else {
113 $unoptimized_url = Optml_Media_Offload::get_original_url( (int) $attachment_id[1] );
114 }
115 if ( $unoptimized_url !== false ) {
116 $url = $unoptimized_url;
117 }
118 }
119 if ( isset( $args['force'] ) && $args['force'] === true && strpos( $url, Optml_Config::$service_url ) !== false && ! $this->url_has_dam_flag( $url ) ) {
120 if ( OPTML_DEBUG ) {
121 do_action( 'optml_log', 'url is already using optimole: ' . $url );
122 }
123 if ( $retried === true ) {
124 return $original_url;
125 }
126 // We retry because the url is already using optimole, but we need to rebuild it because the args might be different.
127 $pos = strpos( $url, 'http', 8 ); // skip 'https://' or 'http://'
128 if ( $pos !== false ) {
129 return $this->build_url( substr( $url, $pos ), $args, true );
130 }
131 return $original_url;
132 }
133
134 if ( substr( $url, 0, 2 ) === '//' ) {
135 $url = ltrim( $url, '/' );
136 $url = sprintf( '%s://%s', is_ssl() ? 'https' : 'http', $url );
137 }
138
139 if ( ! $this->can_replace_url( $url ) ) {
140 return $original_url;
141 }
142 // Remove any query strings that might affect conversion.
143 $url = strtok( $url, '?' );
144 $ext = $this->is_valid_mimetype_from_url( $url, self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_EXT ] );
145 if ( false === $ext ) {
146 return $original_url;
147 }
148
149 if ( isset( $args['quality'] ) && ! empty( $args['quality'] ) ) {
150 $args['quality'] = $this->to_accepted_quality( $args['quality'] );
151 }
152
153 if ( isset( $args['minify'] ) && ! empty( $args['minify'] ) ) {
154 $args['minify'] = $this->to_accepted_minify( $args['minify'] );
155 }
156
157 // this will authorize the image
158 if ( ! empty( $this->site_mappings ) ) {
159 $url = str_replace( array_keys( $this->site_mappings ), array_values( $this->site_mappings ), $url );
160 }
161 if ( substr( $url, 0, 2 ) === '//' ) {
162 $url = ltrim( $url, '/' );
163 $url = sprintf( '%s://%s', is_ssl() ? 'https' : 'http', $url );
164 }
165 $normalized_ext = strtolower( $ext );
166 $url = esc_url( $url );
167 if ( empty( $url ) ) {
168 return $original_url;
169 }
170 if ( isset( Optml_Config::$image_extensions[ $normalized_ext ] ) ) {
171 $new_url = $this->normalize_image( $url, $original_url, $args, $is_uploaded, $normalized_ext );
172 if ( $is_uploaded ) {
173 $new_url = str_replace( '/' . $url, $id_and_filename, $new_url );
174 }
175 } else {
176 $asset = Optimole::asset( $url, $this->active_cache_buster_assets );
177
178 if ( stripos( $url, '.css' ) || stripos( $url, '.js' ) ) {
179 $asset->quality();
180 }
181
182 $asset->minify( ( $this->is_css_minify_on && str_ends_with( strtolower( $url ), '.css' ) ) || ( $this->is_js_minify_on && str_ends_with( strtolower( $url ), '.js' ) ) );
183
184 return $asset->getUrl();
185 }
186 return $is_slashed ? addcslashes( $new_url, '/' ) : $new_url;
187 }
188
189 /**
190 * Apply extra normalization to image.
191 *
192 * @param string $url Image url.
193 * @param string $original_url Original image url.
194 * @param array $args Args.
195 *
196 * @return string
197 */
198 private function normalize_image( $url, $original_url, $args, $is_uploaded = false, $ext = '' ) {
199
200 $new_url = $this->strip_image_size_from_url( $url );
201
202 if ( $new_url !== $url || $is_uploaded === true ) {
203 if ( ! isset( $args['quality'] ) || $args['quality'] !== 'eco' ) {
204 if ( $is_uploaded === true ) {
205 $crop = false;
206 }
207 if ( $is_uploaded !== true ) {
208 list($args['width'], $args['height'], $crop) = $this->parse_dimensions_from_filename( $url );
209 }
210 if ( ! $crop ) {
211 $sizes2crop = self::size_to_crop();
212 $crop = isset( $sizes2crop[ $args['width'] . $args['height'] ] ) ? $sizes2crop[ $args['width'] . $args['height'] ] : false;
213 }
214 if ( $crop ) {
215 $args['resize'] = $this->to_optml_crop( $crop );
216 }
217 }
218 $url = $new_url;
219 }
220
221 if ( ! isset( $args['width'] ) ) {
222 $args['width'] = $this->limit_dimensions_enabled ? $this->limit_width : 'auto';
223 }
224 if ( ! isset( $args['height'] ) ) {
225 $args['height'] = $this->limit_dimensions_enabled ? $this->limit_height : 'auto';
226 }
227
228 $args['width'] = (int) $args['width'];
229 $args['height'] = (int) $args['height'];
230
231 if ( $this->limit_dimensions_enabled && $args['height'] !== 0 && $args['width'] !== 0 ) {
232 if ( $args['width'] > $this->limit_width || $args['height'] > $this->limit_height ) {
233 $scale = min( $this->limit_width / $args['width'], $this->limit_height / $args['height'] );
234
235 if ( $scale < 1 ) {
236 $args['width'] = (int) floor( $scale * $args['width'] );
237 $args['height'] = (int) floor( $scale * $args['height'] );
238 }
239 }
240
241 if ( isset( $args['resize'], $args['resize']['enlarge'] ) ) {
242 $args['resize']['enlarge'] = false;
243 }
244 }
245
246 if ( empty( $args['quality'] ) ) {
247 $args['quality'] = $this->to_accepted_quality( $this->settings->get_quality() );
248 }
249
250 if ( isset( $args['resize'], $args['resize']['gravity'] ) && $this->settings->is_smart_cropping() ) {
251 $args['resize']['gravity'] = GravityProperty::SMART;
252 }
253
254 $is_retina_enabled = $this->settings->get( 'retina_images' ) !== 'disabled';
255 if ( ! $is_retina_enabled ) {
256 $max_dimension = max( $args['width'], $args['height'] );
257 $should_apply_dpr = false;
258
259 if ( $max_dimension > 0 && $max_dimension < 150 ) {
260 $should_apply_dpr = true;
261 }
262
263 $should_apply_dpr = apply_filters( 'optml_should_apply_dpr', $should_apply_dpr, $args, $url );
264
265 if ( $should_apply_dpr && ! isset( $args['dpr'] ) ) {
266 $args['dpr'] = 2;
267 }
268 }
269
270 $args = apply_filters( 'optml_image_args', $args, $original_url );
271 $image = Optimole::image( apply_filters( 'optml_processed_url', $url ), self::get_active_cache_booster( $url, $this->active_cache_buster ) );
272
273 $image->width( ! empty( $args['width'] ) && is_int( $args['width'] ) ? $args['width'] : 'auto' );
274 $image->height( ! empty( $args['height'] ) && is_int( $args['height'] ) ? $args['height'] : 'auto' );
275
276 $image->quality( $args['quality'] );
277
278 if ( ! empty( $args['resize'] ) ) {
279 $this->apply_resize( $image, $args['resize'] );
280 } elseif ( $this->settings->is_smart_cropping() ) {
281 // If smart cropping is enabled and no resize is set, we apply smart focus since the resize can be triggered from the JS library.
282 $image->smartFocus();
283 }
284
285 if ( apply_filters( 'optml_apply_watermark_for', true, $url ) ) {
286 $this->apply_watermark( $image );
287 }
288
289 if ( ! empty( $args['format'] ) ) {
290 $image->format( (string) $args['format'] );
291 } elseif ( $this->settings->is_best_format() ) {
292 // If format is not already set, we use best format if it's enabled.
293 $image->format( 'best' );
294 }
295
296 if ( $this->settings->get( 'strip_metadata' ) === 'disabled' ) {
297 $image->stripMetadata( false );
298 }
299
300 if ( ! apply_filters( 'optml_should_avif_ext', true, $ext, $original_url ) || $this->settings->get( 'avif' ) === 'disabled' ) {
301 $image->ignoreAvif();
302 }
303
304 if ( apply_filters( 'optml_keep_copyright', false ) === true ) {
305 $image->keepCopyright();
306 }
307
308 if ( $this->is_dam_url( $image->getSource() ) ) {
309 return $this->get_dam_url( $image );
310 }
311
312 $offloaded_id = $this->is_offloaded_url( $image->getSource() );
313
314 if ( isset( $args['dpr'] ) && $args['dpr'] > 1 ) {
315 $image->dpr( $args['dpr'] );
316 }
317 if ( $offloaded_id !== 0 ) {
318 return $this->get_offloaded_url( $offloaded_id, $image->getUrl(), $image->getSource() );
319 }
320
321 return $image->getUrl();
322 }
323
324 /**
325 * Get the active cache booster.
326 *
327 * @param string $url The URL.
328 * @param string $main_cache_buster The default value.
329 *
330 * @return string
331 */
332 public static function get_active_cache_booster( $url, $main_cache_buster ) {
333 return ( get_transient( Optml_Settings::INDIVIDUAL_CACHE_TOKENS_KEY ) ?: [] )[ crc32( wp_basename( $url ) ) ] ?? $main_cache_buster;
334 }
335 /**
336 * Throw error on object clone
337 *
338 * The whole idea of the singleton design pattern is that there is a single
339 * object therefore, we don't want the object to be cloned.
340 *
341 * @codeCoverageIgnore
342 * @access public
343 * @return void
344 * @since 1.0.0
345 */
346 public function __clone() {
347 // Cloning instances of the class is forbidden.
348 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
349 }
350
351 /**
352 * Disable unserializing of the class
353 *
354 * @codeCoverageIgnore
355 * @access public
356 * @return void
357 * @since 1.0.0
358 */
359 public function __wakeup() {
360 // Unserializing instances of the class is forbidden.
361 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
362 }
363
364 /**
365 * Apply resize to image.
366 *
367 * @param Image $image Image object.
368 * @param mixed $resize Resize arguments.
369 */
370 private function apply_resize( Image $image, $resize ) {
371 if ( ! is_array( $resize ) || empty( $resize['type'] ) ) {
372 return;
373 }
374
375 $image->resize( $resize['type'], $resize['gravity'] ?? Position::CENTER, $resize['enlarge'] ?? false );
376 }
377
378 /**
379 * Apply watermark to image.
380 *
381 * @param Image $image Image object.
382 */
383 private function apply_watermark( Image $image ) {
384 $settings = $this->settings->get_site_settings();
385
386 if ( empty( $settings['watermark'] ) ) {
387 return;
388 }
389
390 $watermark = $settings['watermark'];
391
392 if ( ! isset( $watermark['id'], $watermark['opacity'], $watermark['position'] ) || $watermark['id'] <= 0 ) {
393 return;
394 }
395
396 $image->watermark( $watermark['id'], $watermark['opacity'], $watermark['position'], $watermark['x_offset'] ?? 0, $watermark['y_offset'] ?? 0, $watermark['scale'] ?? 0 );
397 }
398
399 /**
400 * Get the DAM image URL.
401 *
402 * @param Image $image Image object.
403 *
404 * @return string
405 */
406 private function get_dam_url( Image $image ) {
407 // Remove DAM flag.
408 $url = str_replace( Optml_Dam::URL_DAM_FLAG, '', $image->getSource() );
409
410 foreach ( $image->getProperties() as $property ) {
411 [ $name, $value ] = explode( ':', $property );
412
413 // Check if the property exists in the URL, if so, replace it with the current property value. Otherwise, add it before the /q: param.
414 $url = strpos( $url, '/' . $name . ':' ) !== false
415 ? preg_replace( '/\/' . $name . ':.*?\//', '/' . $name . ':' . $value . '/', $url )
416 : str_replace( '/q:', '/' . $name . ':' . $value . '/q:', $url );
417 }
418
419 return $url;
420 }
421
422
423 /**
424 * Check if the URL is offloaded.
425 *
426 * @param string $source_url The source image URL.
427 *
428 * @return int
429 */
430 private function is_offloaded_url( $source_url ) {
431 $attachment_id = 0;
432
433 if ( ! self::$offload_enabled ) {
434 return 0;
435 }
436 if ( strpos( $source_url, Optml_Media_Offload::KEYS['not_processed_flag'] ) !== false ) {
437 $attachment_id = (int) Optml_Media_Offload::get_attachment_id_from_url( $source_url );
438 } else {
439 $attachment_id = $this->attachment_url_to_post_id( $source_url );
440 }
441
442 if ( $attachment_id === 0 ) {
443 return 0;
444 }
445
446 if ( ! $this->is_completed_offload( $attachment_id ) ) {
447 return 0;
448 }
449
450 return (int) $attachment_id;
451 }
452
453 /**
454 * Get the offloaded URL for an image.
455 *
456 * @param int $id The attachment ID.
457 * @param string $optimized_url The optimized image URL.
458 * @param string $source_url The source image URL.
459 *
460 * @return string
461 */
462 private function get_offloaded_url( $id, $optimized_url, $source_url ) {
463 $suffix = wp_get_attachment_metadata( $id )['file'];
464
465 return str_replace( $source_url, ltrim( $suffix, '/' ), $optimized_url );
466 }
467 }
468