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