PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / trunk
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization vtrunk
4.2.14 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 All 135 releases
← All changes | inc/url_replacer.php +254 -73 3.1.2 → trunk View file →
@@ -1,6 +1,11 @@
1 1 <?php
2 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 +
3 8 /**
4 9 * The class handles the url replacements.
5 10 *
6 11 * @package \Optml\Inc
@@ -7,8 +12,9 @@
7 12 * @author Optimole <[email protected]>
8 13 */
9 14 final class Optml_Url_Replacer extends Optml_App_Replacer {
10 15
16 + use Optml_Dam_Offload_Utils;
11 17 use Optml_Validator;
12 18 use Optml_Normalizer;
13 19
14 20 /**
@@ -35,51 +41,9 @@
35 41
36 42 return self::$instance;
37 43 }
38 44
39 - /**
40 - * A filter which turns a local url into an optimized CDN image url or an array of image urls.
41 - *
42 - * @param string $url The url which should be replaced.
43 - *
44 - * @return string Replaced url.
45 - */
46 - public function replace_option_url( $url ) {
47 - if ( empty( $url ) ) {
48 - return $url;
49 - }
50 - // $url might be an array or an json encoded array with urls.
51 - if ( is_array( $url ) || filter_var( $url, FILTER_VALIDATE_URL ) === false ) {
52 - $array = $url;
53 - $encoded = false;
54 45
55 - // it might a json encoded array
56 - if ( is_string( $url ) ) {
57 - $array = json_decode( $url, true );
58 - $encoded = true;
59 - }
60 -
61 - // in case there is an array, apply it recursively.
62 - if ( is_array( $array ) ) {
63 - foreach ( $array as $index => $value ) {
64 - $array[ $index ] = $this->replace_option_url( $value );
65 - }
66 -
67 - if ( $encoded ) {
68 - return json_encode( $array );
69 - }
70 -
71 - return $array;
72 - }
73 -
74 - if ( filter_var( $url, FILTER_VALIDATE_URL ) === false ) {
75 - return $url;
76 - }
77 - }
78 -
79 - return apply_filters( 'optml_content_url', $url );
80 - }
81 -
82 46 /**
83 47 * The initialize method.
84 48 */
85 49 public function init() {
@@ -86,13 +50,9 @@
86 50
87 51 add_filter( 'optml_replace_image', [ $this, 'build_url' ], 10, 2 );
88 52 parent::init();
89 53
90 - Optml_Quality::$default_quality = $this->to_accepted_quality( $this->settings->get_quality() );
91 - Optml_Image::$watermark = new Optml_Watermark( $this->settings->get_site_settings()['watermark'] );
92 - Optml_Resize::$default_enlarge = apply_filters( 'optml_always_enlarge', false );
93 54 add_filter( 'optml_content_url', [ $this, 'build_url' ], 1, 2 );
94 -
95 55 }
96 56
97 57 /**
98 58 * Returns a signed image url authorized to be used in our CDN.
@@ -98,21 +58,26 @@
98 58 * Returns a signed image url authorized to be used in our CDN.
99 59 *
100 60 * @param string $url The url which should be signed.
101 61 * @param array $args Dimension params; Supports `width` and `height`.
102 - *
62 + * @param bool $retried Whether the url has been retried.
103 63 * @return string
104 64 */
105 65 public function build_url(
106 - $url, $args = [
66 + $url,
67 + $args = [
107 68 'width' => 'auto',
108 69 'height' => 'auto',
109 - ]
70 + ],
71 + $retried = false
110 72 ) {
111 73 if ( apply_filters( 'optml_dont_replace_url', false, $url ) ) {
112 74 return $url;
113 75 }
114 76
77 + if ( OPTML_DEBUG ) {
78 + do_action( 'optml_log', 'building url: ' . $url . ' args: ' . print_r( $args, true ) );
79 + }
115 80 $original_url = $url;
116 81
117 82 $is_slashed = strpos( $url, '\/' ) !== false;
118 83
@@ -130,9 +95,9 @@
130 95 preg_match( '/\/(' . Optml_Media_Offload::KEYS['uploaded_flag'] . '.*)/', $url, $id_and_filename );
131 96 if ( isset( $id_and_filename[0] ) ) {
132 97 $id_and_filename = $id_and_filename[0];
133 98 }
134 - $sizes = Optml_Media_Offload::parse_dimension_from_optimized_url( $url );
99 + $sizes = $this->parse_dimension_from_optimized_url( $url );
135 100 if ( isset( $sizes[0] ) && $sizes[0] !== false ) {
136 101 $args['width'] = $sizes[0];
137 102 }
138 103 if ( isset( $sizes[1] ) && $sizes[1] !== false ) {
@@ -144,22 +109,37 @@
144 109 if ( isset( $tmp_url[2] ) ) {
145 110 $unoptimized_url = $tmp_url[2];
146 111 }
147 112 } else {
148 - $unoptimized_url = Optml_Media_Offload::get_original_url( $attachment_id[1] );
113 + $unoptimized_url = Optml_Media_Offload::get_original_url( (int) $attachment_id[1] );
149 114 }
150 115 if ( $unoptimized_url !== false ) {
151 116 $url = $unoptimized_url;
152 117 }
153 118 }
154 - if ( strpos( $url, Optml_Config::$service_url ) !== false ) {
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 + }
155 131 return $original_url;
156 132 }
157 133
134 + if ( substr( $url, 0, 2 ) === '//' ) {
135 + $url = ltrim( $url, '/' );
136 + $url = sprintf( '%s://%s', is_ssl() ? 'https' : 'http', $url );
137 + }
138 +
158 139 if ( ! $this->can_replace_url( $url ) ) {
159 140 return $original_url;
160 141 }
161 -
162 142 // Remove any query strings that might affect conversion.
163 143 $url = strtok( $url, '?' );
164 144 $ext = $this->is_valid_mimetype_from_url( $url, self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_EXT ] );
165 145 if ( false === $ext ) {
@@ -178,17 +158,31 @@
178 158 if ( ! empty( $this->site_mappings ) ) {
179 159 $url = str_replace( array_keys( $this->site_mappings ), array_values( $this->site_mappings ), $url );
180 160 }
181 161 if ( substr( $url, 0, 2 ) === '//' ) {
182 - $url = sprintf( '%s:%s', is_ssl() ? 'https' : 'http', $url );
162 + $url = ltrim( $url, '/' );
163 + $url = sprintf( '%s://%s', is_ssl() ? 'https' : 'http', $url );
183 164 }
184 - if ( isset( Optml_Config::$image_extensions[ strtolower( $ext ) ] ) ) {
185 - $new_url = $this->normalize_image( $url, $original_url, $args, $is_uploaded );
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 );
186 172 if ( $is_uploaded ) {
187 173 $new_url = str_replace( '/' . $url, $id_and_filename, $new_url );
188 174 }
189 175 } else {
190 - $new_url = ( new Optml_Asset( $url, $args, $this->active_cache_buster, $this->is_css_minify_on, $this->is_js_minify_on ) )->get_url();
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();
191 185 }
192 186 return $is_slashed ? addcslashes( $new_url, '/' ) : $new_url;
193 187 }
194 188
@@ -200,9 +194,9 @@
200 194 * @param array $args Args.
201 195 *
202 196 * @return string
203 197 */
204 - private function normalize_image( $url, $original_url, $args, $is_uploaded = false ) {
198 + private function normalize_image( $url, $original_url, $args, $is_uploaded = false, $ext = '' ) {
205 199
206 200 $new_url = $this->strip_image_size_from_url( $url );
207 201
208 202 if ( $new_url !== $url || $is_uploaded === true ) {
@@ -222,41 +216,124 @@
222 216 }
223 217 }
224 218 $url = $new_url;
225 219 }
220 +
226 221 if ( ! isset( $args['width'] ) ) {
227 - $args['width'] = 'auto';
222 + $args['width'] = $this->limit_dimensions_enabled ? $this->limit_width : 'auto';
228 223 }
229 224 if ( ! isset( $args['height'] ) ) {
230 - $args['height'] = 'auto';
225 + $args['height'] = $this->limit_dimensions_enabled ? $this->limit_height : 'auto';
231 226 }
227 +
232 228 $args['width'] = (int) $args['width'];
233 229 $args['height'] = (int) $args['height'];
234 - if ( $args['width'] > 0 && $args['height'] > 0 ) {
235 - list( $args['width'], $args['height'] ) = wp_constrain_dimensions( $args['width'], $args['height'], $this->max_width, $this->max_height );
236 - } elseif ( $args['width'] > 0 ) {
237 - $args['width'] = $args['width'] > $this->max_width ? $this->max_width : $args['width'];
238 - } elseif ( $args['height'] > 0 ) {
239 - $args['height'] = $args['height'] > $this->max_height ? $this->max_height : $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 + }
240 244 }
245 +
246 + if ( empty( $args['quality'] ) ) {
247 + $args['quality'] = $this->to_accepted_quality( $this->settings->get_quality() );
248 + }
249 +
241 250 if ( isset( $args['resize'], $args['resize']['gravity'] ) && $this->settings->is_smart_cropping() ) {
242 - $args['resize']['gravity'] = Optml_Resize::GRAVITY_SMART;
251 + $args['resize']['gravity'] = GravityProperty::SMART;
243 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 +
244 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 ) );
245 272
246 - $arguments = [
247 - 'apply_watermark' => apply_filters( 'optml_apply_watermark_for', true, $url ),
248 - ];
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' );
249 275
250 - if ( isset( $args['format'] ) && ! empty( $args['format'] ) ) {
251 - $arguments['format'] = $args['format'];
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();
252 283 }
253 284
254 - return ( new Optml_Image( $url, $args, $this->active_cache_buster ) )->get_url( $arguments );
285 + if ( apply_filters( 'optml_apply_watermark_for', true, $url ) ) {
286 + $this->apply_watermark( $image );
287 + }
255 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();
256 322 }
257 323
258 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 + /**
259 336 * Throw error on object clone
260 337 *
261 338 * The whole idea of the singleton design pattern is that there is a single
262 339 * object therefore, we don't want the object to be cloned.
@@ -281,6 +358,110 @@
281 358 */
282 359 public function __wakeup() {
283 360 // Unserializing instances of the class is forbidden.
284 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 );
285 466 }
286 467 }