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 +221 -77 3.10.0 → 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,52 +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|array $url The url which should be replaced.
43 - *
44 - * @return string|array Replaced url.
45 - */
46 - public function replace_option_url( $url ) {
47 - if ( empty( $url ) ) {
48 - return $url;
49 - }
50 45
51 - // $url might be an array or an json encoded array with urls.
52 - if ( is_array( $url ) || filter_var( $url, FILTER_VALIDATE_URL ) === false ) {
53 - $array = $url;
54 - $encoded = false;
55 -
56 - // it might a json encoded array
57 - if ( is_string( $url ) ) {
58 - $array = json_decode( $url, true );
59 - $encoded = true;
60 - }
61 -
62 - // in case there is an array, apply it recursively.
63 - if ( is_array( $array ) ) {
64 - foreach ( $array as $index => $value ) {
65 - $array[ $index ] = $this->replace_option_url( $value );
66 - }
67 -
68 - if ( $encoded ) {
69 - return json_encode( $array );
70 - }
71 -
72 - return $array;
73 - }
74 -
75 - if ( filter_var( $url, FILTER_VALIDATE_URL ) === false ) {
76 - return $url;
77 - }
78 - }
79 -
80 - return apply_filters( 'optml_content_url', $url );
81 - }
82 -
83 46 /**
84 47 * The initialize method.
85 48 */
86 49 public function init() {
@@ -87,13 +50,9 @@
87 50
88 51 add_filter( 'optml_replace_image', [ $this, 'build_url' ], 10, 2 );
89 52 parent::init();
90 53
91 - Optml_Quality::$default_quality = $this->to_accepted_quality( $this->settings->get_quality() );
92 - Optml_Image::$watermark = new Optml_Watermark( $this->settings->get_site_settings()['watermark'] );
93 - Optml_Resize::$default_enlarge = apply_filters( 'optml_always_enlarge', false );
94 54 add_filter( 'optml_content_url', [ $this, 'build_url' ], 1, 2 );
95 -
96 55 }
97 56
98 57 /**
99 58 * Returns a signed image url authorized to be used in our CDN.
@@ -99,21 +58,26 @@
99 58 * Returns a signed image url authorized to be used in our CDN.
100 59 *
101 60 * @param string $url The url which should be signed.
102 61 * @param array $args Dimension params; Supports `width` and `height`.
103 - *
62 + * @param bool $retried Whether the url has been retried.
104 63 * @return string
105 64 */
106 65 public function build_url(
107 - $url, $args = [
66 + $url,
67 + $args = [
108 68 'width' => 'auto',
109 69 'height' => 'auto',
110 - ]
70 + ],
71 + $retried = false
111 72 ) {
112 73 if ( apply_filters( 'optml_dont_replace_url', false, $url ) ) {
113 74 return $url;
114 75 }
115 76
77 + if ( OPTML_DEBUG ) {
78 + do_action( 'optml_log', 'building url: ' . $url . ' args: ' . print_r( $args, true ) );
79 + }
116 80 $original_url = $url;
117 81
118 82 $is_slashed = strpos( $url, '\/' ) !== false;
119 83
@@ -131,9 +95,9 @@
131 95 preg_match( '/\/(' . Optml_Media_Offload::KEYS['uploaded_flag'] . '.*)/', $url, $id_and_filename );
132 96 if ( isset( $id_and_filename[0] ) ) {
133 97 $id_and_filename = $id_and_filename[0];
134 98 }
135 - $sizes = Optml_Media_Offload::parse_dimension_from_optimized_url( $url );
99 + $sizes = $this->parse_dimension_from_optimized_url( $url );
136 100 if ( isset( $sizes[0] ) && $sizes[0] !== false ) {
137 101 $args['width'] = $sizes[0];
138 102 }
139 103 if ( isset( $sizes[1] ) && $sizes[1] !== false ) {
@@ -151,16 +115,31 @@
151 115 if ( $unoptimized_url !== false ) {
152 116 $url = $unoptimized_url;
153 117 }
154 118 }
155 - if ( strpos( $url, Optml_Config::$service_url ) !== false && ! $this->url_has_dam_flag( $url ) ) {
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 + }
156 131 return $original_url;
157 132 }
158 133
134 + if ( substr( $url, 0, 2 ) === '//' ) {
135 + $url = ltrim( $url, '/' );
136 + $url = sprintf( '%s://%s', is_ssl() ? 'https' : 'http', $url );
137 + }
138 +
159 139 if ( ! $this->can_replace_url( $url ) ) {
160 140 return $original_url;
161 141 }
162 -
163 142 // Remove any query strings that might affect conversion.
164 143 $url = strtok( $url, '?' );
165 144 $ext = $this->is_valid_mimetype_from_url( $url, self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_EXT ] );
166 145 if ( false === $ext ) {
@@ -179,11 +158,16 @@
179 158 if ( ! empty( $this->site_mappings ) ) {
180 159 $url = str_replace( array_keys( $this->site_mappings ), array_values( $this->site_mappings ), $url );
181 160 }
182 161 if ( substr( $url, 0, 2 ) === '//' ) {
183 - $url = sprintf( '%s:%s', is_ssl() ? 'https' : 'http', $url );
162 + $url = ltrim( $url, '/' );
163 + $url = sprintf( '%s://%s', is_ssl() ? 'https' : 'http', $url );
184 164 }
185 165 $normalized_ext = strtolower( $ext );
166 + $url = esc_url( $url );
167 + if ( empty( $url ) ) {
168 + return $original_url;
169 + }
186 170 if ( isset( Optml_Config::$image_extensions[ $normalized_ext ] ) ) {
187 171 $new_url = $this->normalize_image( $url, $original_url, $args, $is_uploaded, $normalized_ext );
188 172 if ( $is_uploaded ) {
189 173 $new_url = str_replace( '/' . $url, $id_and_filename, $new_url );
@@ -188,9 +172,17 @@
188 172 if ( $is_uploaded ) {
189 173 $new_url = str_replace( '/' . $url, $id_and_filename, $new_url );
190 174 }
191 175 } else {
192 - $new_url = ( new Optml_Asset( $url, $args, $this->active_cache_buster_assets, $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();
193 185 }
194 186 return $is_slashed ? addcslashes( $new_url, '/' ) : $new_url;
195 187 }
196 188
@@ -235,9 +227,9 @@
235 227
236 228 $args['width'] = (int) $args['width'];
237 229 $args['height'] = (int) $args['height'];
238 230
239 - if ( $this->limit_dimensions_enabled ) {
231 + if ( $this->limit_dimensions_enabled && $args['height'] !== 0 && $args['width'] !== 0 ) {
240 232 if ( $args['width'] > $this->limit_width || $args['height'] > $this->limit_height ) {
241 233 $scale = min( $this->limit_width / $args['width'], $this->limit_height / $args['height'] );
242 234
243 235 if ( $scale < 1 ) {
@@ -250,50 +242,98 @@
250 242 $args['resize']['enlarge'] = false;
251 243 }
252 244 }
253 245
254 - if ( $args['width'] > 0 && $args['height'] > 0 ) {
255 - list( $args['width'], $args['height'] ) = wp_constrain_dimensions( $args['width'], $args['height'], $this->max_width, $this->max_height );
256 - } elseif ( $args['width'] > 0 ) {
257 - $args['width'] = $args['width'] > $this->max_width ? $this->max_width : $args['width'];
258 - } elseif ( $args['height'] > 0 ) {
259 - $args['height'] = $args['height'] > $this->max_height ? $this->max_height : $args['height'];
246 + if ( empty( $args['quality'] ) ) {
247 + $args['quality'] = $this->to_accepted_quality( $this->settings->get_quality() );
260 248 }
249 +
261 250 if ( isset( $args['resize'], $args['resize']['gravity'] ) && $this->settings->is_smart_cropping() ) {
262 - $args['resize']['gravity'] = Optml_Resize::GRAVITY_SMART;
251 + $args['resize']['gravity'] = GravityProperty::SMART;
263 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 +
264 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 ) );
265 272
266 - $arguments = [
267 - 'apply_watermark' => apply_filters( 'optml_apply_watermark_for', true, $url ),
268 - ];
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' );
269 275
270 - if ( isset( $args['format'] ) && ! empty( $args['format'] ) ) {
271 - $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();
272 283 }
273 284
274 - // If format is not already set, we use best format if it's enabled.
275 - if ( ! isset( $arguments['format'] ) && $this->settings->is_best_format() ) {
276 - $arguments['format'] = 'best';
285 + if ( apply_filters( 'optml_apply_watermark_for', true, $url ) ) {
286 + $this->apply_watermark( $image );
277 287 }
278 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 +
279 296 if ( $this->settings->get( 'strip_metadata' ) === 'disabled' ) {
280 - $arguments['strip_metadata'] = '0';
297 + $image->stripMetadata( false );
281 298 }
282 299
283 - if ( ! apply_filters( 'optml_should_avif_ext', true, $ext, $original_url ) ) {
284 - $arguments['ignore_avif'] = true;
300 + if ( ! apply_filters( 'optml_should_avif_ext', true, $ext, $original_url ) || $this->settings->get( 'avif' ) === 'disabled' ) {
301 + $image->ignoreAvif();
285 302 }
286 303
287 - if ( ! isset( $arguments['ignore_avif'] ) && $this->settings->get( 'avif' ) === 'disabled' ) {
288 - $arguments['ignore_avif'] = true;
304 + if ( apply_filters( 'optml_keep_copyright', false ) === true ) {
305 + $image->keepCopyright();
289 306 }
290 307
291 - return ( new Optml_Image( $url, $args, $this->active_cache_buster ) )->get_url( $arguments );
308 + if ( $this->is_dam_url( $image->getSource() ) ) {
309 + return $this->get_dam_url( $image );
310 + }
292 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();
293 322 }
294 323
295 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 + /**
296 336 * Throw error on object clone
297 337 *
298 338 * The whole idea of the singleton design pattern is that there is a single
299 339 * object therefore, we don't want the object to be cloned.
@@ -318,6 +358,110 @@
318 358 */
319 359 public function __wakeup() {
320 360 // Unserializing instances of the class is forbidden.
321 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 );
322 466 }
323 467 }