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

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