PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 4.2.13
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v4.2.13
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 / app_replacer.php

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

699 lines 19.1 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\ValueObject\Position;
5
6 /**
7 * Class Optml_App_Replacer
8 *
9 * @package \Optml\Inc
10 * @author Optimole <friends@optimole.com>
11 */
12 abstract class Optml_App_Replacer {
13 use Optml_Dam_Offload_Utils;
14
15 /**
16 * Filters used for lazyload.
17 *
18 * @var array Lazyload filters.
19 */
20 protected static $filters = null;
21 /**
22 * Holds an array of image sizes.
23 *
24 * @var array
25 */
26 protected static $image_sizes = [];
27 /**
28 * Holds width/height to crop array based on possible image sizes.
29 *
30 * @var array
31 */
32 protected static $size_to_crop = [];
33 /**
34 * Holds possible src attributes.
35 *
36 * @var array
37 */
38 protected static $possible_src_attributes = null;
39 /**
40 * Holds possible tag replace flags where we should ignore our optimization.
41 *
42 * @var array
43 */
44 protected static $ignore_tag_strings = null;
45 /**
46 * Holds possible lazyload flags where we should ignore our lazyload.
47 *
48 * @var array
49 */
50 protected static $ignore_lazyload_strings = null;
51 /**
52 * Holds flags that should ignore the data-opt-tag format.
53 *
54 * @var array
55 */
56 protected static $ignore_data_opt_attribute = null;
57 /**
58 * Settings handler.
59 *
60 * @var Optml_Settings $settings
61 */
62 public $settings = null;
63 /**
64 * Cached offloading status.
65 *
66 * @var null|bool Offload status.
67 */
68 public static $offload_enabled = null;
69 /**
70 * Defines if the dimensions should be limited when images are served.
71 *
72 * @var bool
73 */
74 protected $limit_dimensions_enabled = false;
75 /**
76 * Defines which is the maximum width accepted when images are served.
77 *
78 * @var int
79 */
80 protected $limit_width = 1920;
81 /**
82 * Defines which is the maximum height accepted when images are served.
83 *
84 * @var int
85 */
86 protected $limit_height = 1080;
87 /**
88 * Defines if css minification should be used.
89 *
90 * @var int
91 */
92 protected $is_css_minify_on = 1;
93 /**
94 * Defines if js minification should be used.
95 *
96 * @var int
97 */
98 protected $is_js_minify_on = 0;
99 /**
100 * A cached version of `wp_upload_dir`
101 *
102 * @var null|array
103 */
104 protected $upload_resource = null;
105
106 /**
107 * Possible domain sources to optimize.
108 *
109 * @var array Domains.
110 */
111 protected $possible_sources = [];
112 /**
113 * Possible custom sizes definitions.
114 *
115 * @var array Custom sizes definitions.
116 */
117 private static $custom_size_buffer = [];
118 /**
119 * Whitelisted domains sources to optimize from, according to optimole service.
120 *
121 * @var array Domains.
122 */
123 protected $allowed_sources = [];
124
125 /**
126 * Holds site mapping array,
127 * if there is already a cdn and we want to fetch the images from there
128 * and not from he original site.
129 *
130 * @var array Site mappings.
131 */
132 protected $site_mappings = [];
133 /**
134 * Whether the site is whitelisted or not. Used when signing the urls.
135 *
136 * @var bool Domains.
137 */
138 protected $is_allowed_site = false;
139
140 /**
141 * Holds the most recent value for the cache buster, updated to hold only for images if active_cache_buster_assets is defined.
142 *
143 * @var string Cache Buster global value if active_cache_buster_assets is undefined else the value for images.
144 */
145 protected $active_cache_buster = '';
146
147 /**
148 * Holds possible ignored by class urls.
149 *
150 * @var array
151 */
152 protected static $ignored_url_map = [];
153
154 /**
155 * Holds the most recent value for the cache buster for assets.
156 *
157 * @var string Cache Buster value.
158 */
159 protected $active_cache_buster_assets = '';
160
161 /**
162 * Returns possible src attributes.
163 *
164 * @return array
165 */
166 public static function possible_src_attributes() {
167 if ( null !== self::$possible_src_attributes ) {
168 return self::$possible_src_attributes;
169 }
170
171 self::$possible_src_attributes = apply_filters( 'optml_possible_src_attributes', [] );
172
173 return self::$possible_src_attributes;
174 }
175
176 /**
177 * Returns possible src attributes.
178 *
179 * @return array
180 */
181 public static function possible_lazyload_flags() {
182 if ( null !== self::$ignore_lazyload_strings ) {
183 return self::$ignore_lazyload_strings;
184 }
185
186 self::$possible_src_attributes = apply_filters( 'optml_possible_lazyload_flags', [ 'skip-lazy', 'data-skip-lazy', 'data-opt-src' ] );
187
188 return array_merge( self::$possible_src_attributes, [ '<noscript' ] );
189 }
190 /**
191 * Returns possible tag replacement flags.
192 *
193 * @return array
194 */
195 public static function possible_tag_flags() {
196 if ( null !== self::$ignore_tag_strings ) {
197 return self::$ignore_tag_strings;
198 }
199
200 self::$ignore_tag_strings = apply_filters( 'optml_skip_optimizations_css_classes', [ 'skip-optimization' ] );
201
202 return self::$ignore_tag_strings;
203 }
204 /**
205 * Returns possible data-opt-src ignore flags attributes.
206 *
207 * @return array
208 */
209 public static function possible_data_ignore_flags() {
210 if ( null !== self::$ignore_data_opt_attribute ) {
211 return self::$ignore_data_opt_attribute;
212 }
213
214 self::$ignore_data_opt_attribute = apply_filters( 'optml_ignore_data_opt_flag', [] );
215
216 return self::$ignore_data_opt_attribute;
217 }
218
219 /**
220 * Size to crop maping.
221 *
222 * @return array Size mapping.
223 */
224 public static function size_to_crop() {
225 if ( ! empty( self::$size_to_crop ) ) {
226 return self::$size_to_crop;
227 }
228
229 foreach ( self::image_sizes() as $size_data ) {
230 if ( isset( self::$size_to_crop[ $size_data['width'] . $size_data['height'] ] ) && isset( $size_data['enlarge'] ) ) {
231 continue;
232 }
233 self::$size_to_crop[ $size_data['width'] . $size_data['height'] ] =
234 isset( $size_data['enlarge'] ) ? [
235 'crop' => $size_data['crop'],
236 'enlarge' => true,
237 ] : $size_data['crop'];
238 }
239
240 return self::$size_to_crop;
241 }
242
243 /**
244 * Set possible custom size.
245 *
246 * @param int $width Width value.
247 * @param int $height Height Value.
248 * @param bool|array $crop Croping.
249 */
250 public static function add_size( $width = null, $height = null, $crop = null ) {
251 if ( is_null( $width ) || is_null( $height ) ) {
252 return;
253 }
254
255 self::$custom_size_buffer[ 'cmole' . $width . $height ] = [
256 'width' => (int) $width,
257 'height' => (int) $height,
258 'enlarge' => true,
259 'crop' => $crop,
260 ];
261 }
262
263 /**
264 * Returns the array of image sizes since `get_intermediate_image_sizes` and image metadata doesn't include the
265 * custom image sizes in a reliable way.
266 *
267 * @return array
268 * @global $wp_additional_image_sizes
269 */
270 public static function image_sizes() {
271 if ( ! empty( self::$image_sizes ) ) {
272 return self::$image_sizes;
273 }
274
275 global $_wp_additional_image_sizes;
276
277 // Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes
278 $images = [
279 'thumb' => [
280 'width' => intval( get_option( 'thumbnail_size_w' ) ),
281 'height' => intval( get_option( 'thumbnail_size_h' ) ),
282 'crop' => (bool) get_option( 'thumbnail_crop', false ),
283 ],
284 'medium' => [
285 'width' => intval( get_option( 'medium_size_w' ) ),
286 'height' => intval( get_option( 'medium_size_h' ) ),
287 'crop' => false,
288 ],
289 'large' => [
290 'width' => intval( get_option( 'large_size_w' ) ),
291 'height' => intval( get_option( 'large_size_h' ) ),
292 'crop' => false,
293 ],
294 'full' => [
295 'width' => null,
296 'height' => null,
297 'crop' => false,
298 ],
299 ];
300
301 // Compatibility mapping as found in wp-includes/media.php
302 $images['thumbnail'] = $images['thumb'];
303
304 // Update class variable, merging in $_wp_additional_image_sizes if any are set
305 if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) ) {
306 self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
307 } else {
308 self::$image_sizes = $images;
309 }
310
311 self::$image_sizes = array_merge( self::$image_sizes, self::$custom_size_buffer );
312 self::$image_sizes = array_map(
313 function ( $value ) {
314 $value['crop'] = isset( $value['crop'] ) ?
315 ( is_array( $value['crop'] )
316 ? $value['crop'] :
317 (bool) $value['crop'] )
318 : false;
319
320 return $value;
321 },
322 self::$image_sizes
323 );
324
325 return self::$image_sizes;
326 }
327
328 /**
329 * The initialize method.
330 */
331 public function init() {
332 $this->settings = new Optml_Settings();
333 $this->set_properties();
334
335 self::$filters = $this->settings->get_filters();
336 add_filter(
337 'optml_should_avif_ext',
338 function ( $should_avif, $ext ) {
339 return $ext !== 'svg';
340 },
341 10,
342 2
343 );
344 add_filter(
345 'optml_possible_lazyload_flags',
346 function ( $strings = [] ) {
347 foreach ( self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_CLASS ] as $rule_flag => $status ) {
348 $strings[] = $rule_flag;
349 }
350
351 return $strings;
352 },
353 10
354 );
355 add_filter(
356 'optml_skip_optimizations_css_classes',
357 function ( $strings = [] ) {
358 foreach ( self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_CLASS ] as $rule_flag => $status ) {
359 $strings[] = $rule_flag;
360 }
361 return $strings;
362 },
363 10
364 );
365 if ( self::$offload_enabled === null ) {
366 self::$offload_enabled = ( new Optml_Settings() )->is_offload_enabled();
367 }
368 }
369
370
371 /**
372 * Set the cdn url based on the current connected user.
373 */
374 public function set_properties() {
375
376 $upload_data = wp_upload_dir();
377 $this->upload_resource = [
378 'url' => str_replace( [ 'https://', 'http://' ], '', $upload_data['baseurl'] ),
379 'directory' => $upload_data['basedir'],
380 ];
381 $this->upload_resource['url_length'] = strlen( $this->upload_resource['url'] );
382
383 $content_url = content_url();
384
385 if ( strpos( $content_url, '/' ) === 0 ) {
386 $content_url = get_site_url() . $content_url;
387 }
388 $content_parts = parse_url( $content_url );
389 $this->upload_resource['content_path'] = '/';
390 $this->upload_resource['content_folder'] = '/';
391 if ( isset( $content_parts['path'] ) ) {
392 $this->upload_resource['content_path'] = $content_parts['path'];
393 $this->upload_resource['content_folder'] = ltrim( $content_parts['path'], '/' );
394 }
395
396 $this->upload_resource['content_folder_length'] = strlen( $this->upload_resource['content_folder'] );
397 $this->upload_resource['content_host'] = $content_parts['scheme'] . '://' . $content_parts['host'];
398
399 $service_data = $this->settings->get( 'service_data' );
400
401 $domain = '';
402 if ( isset( $service_data['is_cname_assigned'] ) && $service_data['is_cname_assigned'] === 'yes' && ! empty( $service_data['domain'] ) ) {
403 $domain = $service_data['domain'];
404 }
405 Optml_Config::init(
406 [
407 'key' => $service_data['cdn_key'],
408 'secret' => $service_data['cdn_secret'],
409 'domain' => $domain,
410 'api_key' => $this->settings->get( 'api_key' ),
411 ]
412 );
413
414 if ( defined( 'OPTML_SITE_MIRROR' ) && constant( 'OPTML_SITE_MIRROR' ) ) {
415 $this->site_mappings[ rtrim( get_home_url(), '/' ) ] = rtrim( constant( 'OPTML_SITE_MIRROR' ), '/' );
416 }
417
418 $this->possible_sources = $this->extract_domain_from_urls(
419 array_merge(
420 [
421 get_home_url(),
422 ],
423 array_values( $this->site_mappings ),
424 array_keys( $this->site_mappings )
425 )
426 );
427
428 $this->allowed_sources = $this->extract_domain_from_urls( $service_data['whitelist'] );
429 $this->active_cache_buster = $this->settings->get( 'cache_buster_images' );
430 $this->active_cache_buster_assets = $this->settings->get( 'cache_buster_assets' );
431
432 if ( empty( $this->active_cache_buster ) ) {
433 $this->active_cache_buster = $this->settings->get( 'cache_buster' );
434 }
435 if ( empty( $this->active_cache_buster_assets ) ) {
436 $this->active_cache_buster_assets = $this->settings->get( 'cache_buster' );
437 }
438
439 // Allways allow Photon urls.
440 $this->allowed_sources['i0.wp.com'] = true;
441 $this->allowed_sources['i1.wp.com'] = true;
442 $this->allowed_sources['i2.wp.com'] = true;
443 $this->is_allowed_site = count( array_diff_key( $this->possible_sources, $this->allowed_sources ) ) > 0;
444
445 $this->limit_dimensions_enabled = $this->settings->get( 'limit_dimensions' ) === 'enabled';
446 if ( $this->limit_dimensions_enabled ) {
447 $this->limit_height = $this->settings->get( 'limit_height' );
448 $this->limit_width = $this->settings->get( 'limit_width' );
449 }
450
451 $this->is_css_minify_on = ( $this->settings->get( 'css_minify' ) === 'enabled' ) ? 1 : 0;
452 $this->is_js_minify_on = ( $this->settings->get( 'js_minify' ) === 'enabled' ) ? 1 : 0;
453
454 add_filter( 'optml_strip_image_size_from_url', [ $this, 'strip_image_size_from_url' ], 10 );
455 add_filter(
456 'image_resize_dimensions',
457 [ __CLASS__, 'listen_to_sizes' ],
458 999999,
459 6
460 );
461 }
462
463 /**
464 * Method to expose upload resource property.
465 *
466 * @return null|array
467 */
468 public function get_upload_resource() {
469 return $this->upload_resource;
470 }
471
472 /**
473 * List to resizes and save the crop for later re-use.
474 *
475 * @param null $value Original resize.
476 * @param int $orig_w Original W.
477 * @param int $orig_h Original H.
478 * @param int $dest_w Output W.
479 * @param int $dest_h Output H.
480 * @param mixed $crop Cropping behaviour.
481 *
482 * @return mixed Original value.
483 */
484 public static function listen_to_sizes( $value, $orig_w, $orig_h, $dest_w, $dest_h, $crop ) {
485 self::add_size( $dest_w, $dest_h, $crop );
486
487 return $value;
488 }
489
490 /**
491 * Extract domains and use them as keys for fast processing.
492 *
493 * @param array|null $urls Input urls.
494 *
495 * @return array Array of domains as keys.
496 */
497 protected function extract_domain_from_urls( $urls = [] ) {
498 if ( ! is_array( $urls ) ) {
499 return [];
500 }
501
502 $urls = array_map(
503 function ( $value ) {
504 $parts = parse_url( $value );
505 $domain = '';
506 if ( isset( $parts['host'] ) ) {
507 $domain = $parts['host'];
508 } elseif ( isset( $parts['path'] ) ) {
509 $domain = $parts['path'];
510 }
511 return $domain;
512 },
513 $urls
514 );
515 $urls = array_filter( $urls );
516 $urls = array_unique( $urls );
517
518 $urls = array_fill_keys( $urls, true );
519 // build www versions of urls, just in case we need them for validation.
520 foreach ( $urls as $domain => $status ) {
521 if ( ! ( substr( $domain, 0, 4 ) === 'www.' ) ) {
522 $urls[ 'www.' . $domain ] = true;
523 }
524 }
525
526 return $urls;
527 }
528
529 /**
530 * Check if we can replace the url.
531 *
532 * @param string|mixed $url Url to change.
533 *
534 * @return bool Either we can replace this url or not.
535 */
536 public function can_replace_url( $url ) {
537
538 if ( ! is_string( $url ) ) {
539 return false; // @codeCoverageIgnore
540 }
541
542 if ( $this->url_has_dam_flag( $url ) ) {
543 return true;
544 }
545
546 $url_parts = parse_url( $url );
547
548 if ( ! isset( $url_parts['host'] ) ) {
549 return false;
550 }
551 if ( false === ( isset( $this->possible_sources[ $url_parts['host'] ] ) || isset( $this->allowed_sources[ $url_parts['host'] ] ) ) ) {
552 return false;
553 }
554
555 if ( false === Optml_Filters::should_do_image( $url, self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_FILENAME ] ) ) {
556 return false;
557 }
558
559 if ( ! empty( self::$ignored_url_map ) && isset( self::$ignored_url_map[ crc32( $url ) ] ) ) {
560 return false;
561 }
562
563 return true;
564 }
565 /**
566 * Checks if the file is a image size and return the full url.
567 *
568 * @param string $url The image URL.
569 *
570 * @return string
571 **/
572 public function strip_image_size_from_url( $url ) {
573 if ( apply_filters( 'optml_should_skip_strip_image_size', false, $url ) === true ) {
574 return $url;
575 }
576 if ( preg_match( '#(-\d+x\d+(?:_c)?|(@2x))\.(' . implode( '|', array_keys( Optml_Config::$image_extensions ) ) . '){1}$#i', $url, $src_parts ) ) {
577 $stripped_url = str_replace( $src_parts[1], '', $url );
578
579 if ( $this->settings->is_offload_enabled() ) {
580 // Treat offloaded attachments differently.
581 $id = $this->attachment_url_to_post_id( $stripped_url );
582
583 if ( $id !== 0 && $this->is_new_offloaded_attachment( $id ) ) {
584 return $stripped_url;
585 }
586 }
587
588 // Extracts the file path to the image minus the base url
589 $file_path = substr( $stripped_url, strpos( $stripped_url, $this->upload_resource['url'] ) + $this->upload_resource['url_length'] );
590 if ( file_exists( $this->upload_resource['directory'] . $file_path ) ) {
591 return $stripped_url;
592 }
593 }
594
595 return $url;
596 }
597
598 /**
599 * Try to determine height and width from strings WP appends to resized image filenames.
600 *
601 * @param string $src The image URL.
602 *
603 * @return array An array consisting of width and height.
604 */
605 protected function parse_dimensions_from_filename( $src ) {
606 $width_height_string = [];
607 $extensions = array_keys( Optml_Config::$image_extensions );
608 if ( preg_match( '#-(\d+)x(\d+)(:?_c)?\.(?:' . implode( '|', $extensions ) . '){1}$#i', $src, $width_height_string ) ) {
609 $width = (int) $width_height_string[1];
610 $height = (int) $width_height_string[2];
611 $crop = ( isset( $width_height_string[3] ) && $width_height_string[3] === '_c' );
612 if ( $width && $height ) {
613 return [ $width, $height, $crop ];
614 }
615 } else {
616 $optimized_args = $this->parse_dimension_from_optimized_url( $src );
617 if ( $optimized_args[0] !== false || $optimized_args[1] !== false ) {
618 return [
619 $optimized_args[0] !== 'auto' ? (int) $optimized_args[0] : false,
620 $optimized_args[1] !== 'auto' ? (int) $optimized_args[1] : false,
621 false,
622 ];
623 }
624 }
625
626 return [ false, false, false ];
627 }
628
629 /**
630 * Get the optimized urls for the wp media modal.
631 *
632 * @param string $url Original url.
633 * @param string $table_id The cloud id of the image.
634 * @param int|string $width Image width.
635 * @param int|string $height Image height.
636 * @param array $resize Optml crop array.
637 * @return string The optimized url.
638 */
639 public function get_media_optimized_url( $url, $table_id, $width = 'auto', $height = 'auto', $resize = [] ) {
640 return str_replace( $url, Optml_Media_Offload::KEYS['not_processed_flag'] . 'media_cloud' . '/' . Optml_Media_Offload::KEYS['uploaded_flag'] . $table_id . '/' . $url, $this->get_optimized_image_url( $url, $width, $height, $resize ) );
641 }
642
643 /**
644 * Test that the url has the dam flag.
645 *
646 * @param string $url The image URL to check.
647 *
648 * @return bool
649 */
650 public function url_has_dam_flag( $url ) {
651 return strpos( $url, Optml_Dam::URL_DAM_FLAG ) !== false;
652 }
653
654 /**
655 * Get the optimized image url for the image url.
656 *
657 * @param string $url The image URL.
658 * @param mixed $width The image width.
659 * @param mixed $height The image height.
660 * @param array<string, mixed>|mixed $resize The resize properties.
661 *
662 * @return string
663 */
664 protected function get_optimized_image_url( $url, $width, $height, $resize = [] ) {
665 $width = is_int( $width ) ? $width : 'auto';
666 $height = is_int( $height ) ? $height : 'auto';
667 // If the image is already using Optimole URL, we extract the source to rebuild it.
668 $url = $this->get_unoptimized_url( $url );
669
670 $optimized_image = Optimole::image( $url, $this->settings->get( 'cache_buster' ) )
671 ->width( $width )
672 ->height( $height );
673
674 if ( is_array( $resize ) && ! empty( $resize['type'] ) ) {
675 $optimized_image->resize( $resize['type'], $resize['gravity'] ?? Position::CENTER, $resize['enlarge'] ?? false );
676
677 }
678
679 $optimized_image->quality( $this->settings->to_accepted_quality( $this->settings->get_quality() ) );
680
681 return $optimized_image->getUrl();
682 }
683
684 /**
685 * Returns an image id from the url.
686 *
687 * @param string $url The image URL.
688 *
689 * @return int
690 */
691 public function get_id_by_url( $url ) {
692 $url = $this->get_unoptimized_url( $url );
693 srand( crc32( $url ) );
694 $random_id = rand();
695 srand();
696 return $random_id;
697 }
698 }
699