PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 3.10.0
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v3.10.0
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 3.10.0, at inc/app_replacer.php

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