PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 3.0.0
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v3.0.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 / lazyload_replacer.php

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

504 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * The class handles the img tag replacements for lazyload.
5 *
6 * @package \Optml\Inc
7 * @author Optimole <friends@optimole.com>
8 */
9 final class Optml_Lazyload_Replacer extends Optml_App_Replacer {
10 use Optml_Normalizer;
11 use Optml_Validator;
12
13 const IFRAME_PLACEHOLDER_CLASS = '
14 iframe[data-opt-src]:not([data-opt-lazy-loaded]) {
15 background-color: #ffffff;
16 background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2240%22%20height%3D%2240%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22-webkit-transform-origin%3A50%25%2050%25%3B-webkit-animation%3Aspin%201.5s%20linear%20infinite%3B-webkit-backface-visibility%3Ahidden%3Banimation%3Aspin%201.5s%20linear%20infinite%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cdefs%3E%3Cstyle%3E%3C%21%5BCDATA%5B%40-webkit-keyframes%20spin%7Bfrom%7B-webkit-transform%3Arotate%280deg%29%7Dto%7B-webkit-transform%3Arotate%28-359deg%29%7D%7D%40keyframes%20spin%7Bfrom%7Btransform%3Arotate%280deg%29%7Dto%7Btransform%3Arotate%28-359deg%29%7D%7D%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22outer%22%3E%3Cpath%20d%3D%22M20%200a3.994%203.994%200%20110%207.988A3.994%203.994%200%200120%200z%22%2F%3E%3Cpath%20d%3D%22M5.858%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23d2d2d2%22%2F%3E%3Cpath%20d%3D%22M20%2032.012A3.994%203.994%200%201120%2040a3.994%203.994%200%20010-7.988z%22%20fill%3D%22%23828282%22%2F%3E%3Cpath%20d%3D%22M28.494%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23656565%22%2F%3E%3Cpath%20d%3D%22M3.994%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%23bbb%22%2F%3E%3Cpath%20d%3D%22M5.858%2028.494a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23a4a4a4%22%2F%3E%3Cpath%20d%3D%22M36.006%2016.006a3.994%203.994%200%20110%207.988%203.994%203.994%200%20010-7.988z%22%20fill%3D%22%234a4a4a%22%2F%3E%3Cpath%20d%3D%22M28.494%205.858a3.994%203.994%200%20115.648%205.648%203.994%203.994%200%2001-5.648-5.648z%22%20fill%3D%22%23323232%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
17 background-repeat: no-repeat;
18 background-position: 50% 50%;
19 }';
20 const IFRAME_PLACEHOLDER_STYLE = '<style type="text/css">' . self::IFRAME_PLACEHOLDER_CLASS . '</style>';
21
22 const IFRAME_TEMP_COMMENT = '/** optmliframelazyloadplaceholder */';
23
24 const SVG_PLACEHOLDER = 'data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%20#width#%20#height#%22%20width%3D%22#width#%22%20height%3D%22#height#%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3C%2Fsvg%3E';
25 /**
26 * If frame lazyload is present on page.
27 *
28 * @var bool Whether or not at least one iframe has been lazyloaded.
29 */
30 private static $found_iframe = false;
31 /**
32 * Cached object instance.
33 *
34 * @var Optml_Tag_Replacer
35 */
36 protected static $instance = null;
37 /**
38 * Holds classes for listening to lazyload on background.
39 *
40 * @var array Lazyload background classes.
41 */
42 private static $lazyload_background_classes = null;
43 /**
44 * Selectors used for background lazyload.
45 *
46 * @var array Lazyload background CSS selectors.
47 */
48 private static $background_lazyload_selectors = null;
49 /**
50 * Holds flags which remove noscript tag bundle causing issues on render, i.e slider plugins.
51 *
52 * @var array Noscript flags.
53 */
54 private static $ignore_no_script_flags = null;
55 /**
56 * Holds possible iframe lazyload flags where we should ignore our lazyload.
57 *
58 * @var array
59 */
60 protected static $iframe_lazyload_flags = null;
61 /**
62 * Holds classes responsabile for watching lazyload behaviour.
63 *
64 * @var array Lazyload classes.
65 */
66 private static $lazyload_watcher_classes = null;
67 /**
68 * Should we use the generic placeholder?
69 *
70 * @var bool Lazyload placeholder flag.
71 */
72 private static $is_lazyload_placeholder = false;
73
74 /**
75 * Class instance method.
76 *
77 * @codeCoverageIgnore
78 * @static
79 * @return Optml_Tag_Replacer
80 * @since 1.0.0
81 * @access public
82 */
83 public static function instance() {
84 if ( null === self::$instance ) {
85 self::$instance = new self();
86 add_action( 'optml_replacer_setup', [ self::$instance, 'init' ] );
87 }
88
89 return self::$instance;
90 }
91
92 /**
93 * Return lazyload selectors for background images.
94 *
95 * @return array Lazyload selectors.
96 */
97 public static function get_background_lazyload_selectors() {
98
99 if ( ! empty( self::$background_lazyload_selectors ) && is_array( self::$background_lazyload_selectors ) ) {
100 return self::$background_lazyload_selectors;
101 }
102 if ( self::instance()->settings->get( 'bg_replacer' ) === 'disabled' ) {
103 self::$background_lazyload_selectors = [];
104
105 return self::$background_lazyload_selectors;
106 }
107 $default_watchers = [
108 '.elementor-section[data-settings*="background_background"]',
109 '.elementor-section > .elementor-background-overlay',
110 '.wp-block-cover[style*="background-image"]',
111 ];
112
113 $saved_watchers = self::instance()->settings->get_watchers();
114
115 $saved_watchers = str_replace( [ "\n", "\r" ], ',', $saved_watchers );
116 $saved_watchers = explode( ',', $saved_watchers );
117 $all_watchers = array_merge( $default_watchers, $saved_watchers );
118 $all_watchers = apply_filters( 'optml_lazyload_bg_selectors', $all_watchers );
119 $all_watchers = array_filter(
120 $all_watchers,
121 function ( $value ) {
122 return ! empty( $value ) && strlen( $value ) >= 2;
123 }
124 );
125
126 self::$background_lazyload_selectors = $all_watchers;
127
128 return self::$background_lazyload_selectors;
129 }
130
131 /**
132 * Returns background classes for lazyload.
133 *
134 * @return array
135 */
136 public static function get_lazyload_bg_classes() {
137
138 if ( ! empty( self::$lazyload_background_classes ) && is_array( self::$lazyload_background_classes ) ) {
139 return self::$lazyload_background_classes;
140 }
141
142 self::$lazyload_background_classes = apply_filters( 'optml_lazyload_bg_classes', [] );
143
144 return self::$lazyload_background_classes;
145 }
146
147 /**
148 * Returns classes for lazyload additional watch.
149 *
150 * @return array
151 */
152 public static function get_watcher_lz_classes() {
153
154 if ( ! empty( self::$lazyload_watcher_classes ) && is_array( self::$lazyload_watcher_classes ) ) {
155 return self::$lazyload_watcher_classes;
156 }
157
158 self::$lazyload_watcher_classes = apply_filters( 'optml_watcher_lz_classes', [] );
159
160 return self::$lazyload_watcher_classes;
161 }
162
163 /**
164 * The initialize method.
165 */
166 public function init() {
167 parent::init();
168
169 if ( ! $this->settings->use_lazyload() ) {
170 return;
171 }
172 if ( ! Optml_Filters::should_do_page( self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_URL ] ) ) {
173 return;
174 }
175
176 add_filter(
177 'max_srcset_image_width',
178 function () {
179 return 1;
180 }
181 );
182 self::$is_lazyload_placeholder = self::$instance->settings->get( 'lazyload_placeholder' ) === 'enabled';
183
184 add_filter( 'optml_tag_replace', [ $this, 'lazyload_tag_replace' ], 2, 6 );
185
186 add_filter( 'optml_video_replace', [$this, 'lazyload_video_replace'], 2, 1 );
187
188 }
189 /**
190 * Check if there are lazyloaded iframes.
191 *
192 * @return bool Whether an iframe was lazyloaded on the page or not.
193 */
194 public static function found_iframe() {
195 return self::$found_iframe;
196 }
197
198 /**
199 * Replaces the tags with lazyload tags.
200 *
201 * @param string $new_tag The new tag.
202 * @param string $original_url The original URL.
203 * @param string $new_url The optimized URL.
204 * @param array $optml_args Options passed for URL optimization.
205 * @param bool $is_slashed If the url needs slashes.
206 * @param string $full_tag Full tag, wrapper included.
207 *
208 * @return string
209 */
210 public function lazyload_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed = false, $full_tag = '' ) {
211
212 if ( ! $this->can_lazyload_for( $original_url, $full_tag ) ) {
213 return Optml_Tag_Replacer::instance()->regular_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed );
214 }
215 $should_ignore_rescale = ! $this->is_valid_mimetype_from_url( $original_url, [ 'gif' => true, 'svg' => true ] );
216
217 if ( ! self::$is_lazyload_placeholder && ! $should_ignore_rescale ) {
218 $optml_args['quality'] = 'eco';
219 $optml_args['resize'] = [];
220 $low_url = apply_filters( 'optml_content_url', $original_url, $optml_args );
221 $low_url = $is_slashed ? addcslashes( $low_url, '/' ) : $low_url;
222 } else {
223 $low_url = $this->get_svg_for(
224 isset( $optml_args['width'] ) ? $optml_args['width'] : '100%',
225 isset( $optml_args['height'] ) ? $optml_args['height'] : '100%',
226 ( $should_ignore_rescale ? null : $original_url )
227 );
228 }
229
230 $opt_format = '';
231
232 if ( $this->should_add_data_tag( $full_tag ) ) {
233 $opt_format = ' data-opt-src="%s" ';
234 if ( $should_ignore_rescale ) {
235 if ( strpos( $new_tag, 'class=' ) === false ) {
236 $opt_format .= ' class="optimole-lazy-only" ';
237 } else {
238 $new_tag = str_replace(
239 ( $is_slashed ? 'class=\"' : 'class="' ),
240 ( $is_slashed ? 'class=\"optimole-lazy-only ' : 'class="optimole-lazy-only ' ),
241 $new_tag
242 );
243 }
244 }
245 $opt_format = $is_slashed ? addslashes( $opt_format ) : $opt_format;
246 }
247 $new_url = $is_slashed ? addcslashes( $new_url, '/' ) : $new_url;
248
249 $opt_src = sprintf( $opt_format, $new_url );
250
251 $no_script_tag = str_replace(
252 $original_url,
253 $new_url,
254 $new_tag
255 );
256 $new_tag = preg_replace(
257 [
258 '/((?:\s|\'|"){1,}src(?>=|"|\'|\s|\\\\)*)' . preg_quote( $original_url, '/' ) . '/m',
259 '/<img/im',
260 ],
261 [
262 "$1$low_url",
263 '<img' . $opt_src,
264 ],
265 $new_tag,
266 1
267 );
268 $new_tag = str_replace( 'srcset=', 'old-srcset=', $new_tag );
269 if ( strpos( $new_tag, 'loading=' ) === false && self::instance()->settings->get( 'native_lazyload' ) === 'enabled' ) {
270 $new_tag = preg_replace( '/<img/im', $is_slashed ? '<img loading=\"lazy\"' : '<img loading="lazy"', $new_tag );
271 }
272 if ( ! $this->should_add_noscript( $new_tag ) ) {
273 return $new_tag;
274 }
275
276 return $new_tag . '<noscript>' . $no_script_tag . '</noscript>';
277 }
278 /**
279 * Replaces video embeds with lazyload embeds.
280 *
281 * @param string $content Html page content.
282 *
283 * @return string
284 */
285 public function lazyload_video_replace( $content ) {
286 $video_tags = [];
287 preg_match_all( '#(?:<noscript\s*>\s*)?<iframe(.*?)></iframe>(?:\s*</noscript\s*>)?#is', $content, $video_tags );
288
289 $search = [];
290 $replace = [];
291 foreach ( $video_tags[0] as $video_tag ) {
292 if ( ! $this->should_lazyload_iframe( $video_tag ) ) {
293 continue;
294 }
295 if ( preg_match( "/ data-opt-video-src=['\"]/is", $video_tag ) ) {
296 continue;
297 }
298 array_push( $search, $video_tag );
299 $no_script = $video_tag;
300 // replace the src and add the data-opt-src attribute
301 $video_tag = preg_replace( '/iframe(.*?)src=/is', 'iframe$1 src="about:blank" data-opt-src=', $video_tag );
302
303 if ( $this->should_add_noscript( $video_tag ) ) {
304 $video_tag .= '<noscript>' . $no_script . '</noscript>';
305 }
306 array_push( $replace, $video_tag );
307 self::$found_iframe = true;
308 }
309 $search = array_unique( $search );
310 $replace = array_unique( $replace );
311 $content = str_replace( $search, $replace, $content );
312 return $content;
313 }
314
315 /**
316 * Check if the lazyload is allowed for this url.
317 *
318 * @param string $url Url.
319 * @param string $tag Html tag.
320 *
321 * @return bool We can lazyload?
322 */
323 public function can_lazyload_for( $url, $tag = '' ) {
324 foreach ( self::possible_lazyload_flags() as $banned_string ) {
325 if ( strpos( $tag, $banned_string ) !== false ) {
326 return false;
327 }
328 }
329 if ( false === Optml_Filters::should_do_image( $url, self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_FILENAME ] ) ) {
330 return false;
331 }
332 $url = strtok( $url, '?' );
333
334 $type = wp_check_filetype(
335 basename( $url ),
336 Optml_Config::$image_extensions
337 );
338
339 if ( ! isset( $type['ext'] ) || empty( $type['ext'] ) ) {
340 return false;
341 }
342
343 if ( false === Optml_Filters::should_do_extension( self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_EXT ], $type['ext'] ) ) {
344 return false;
345 }
346
347 if ( defined( 'OPTML_DISABLE_PNG_LAZYLOAD' ) && OPTML_DISABLE_PNG_LAZYLOAD ) {
348 return $type['ext'] !== 'png';
349 }
350
351 return true;
352 }
353
354 /**
355 * Check if we should add the data-opt-tag.
356 *
357 * @param string $tag Html tag.
358 *
359 * @return bool Should add?
360 */
361 public function should_add_data_tag( $tag ) {
362 foreach ( self::possible_data_ignore_flags() as $banned_string ) {
363 if ( strpos( $tag, $banned_string ) !== false ) {
364 return false;
365 }
366 }
367
368 return true;
369 }
370
371 /**
372 * Get SVG markup with specific width/height.
373 *
374 * @param int $width Markup Width.
375 * @param int $height Markup Height.
376 * @param string|null $url Original URL.
377 *
378 * @return string SVG code.
379 */
380 public function get_svg_for( $width, $height, $url = null ) {
381
382 if ( $url !== null && ! is_numeric( $width ) ) {
383 $url = strtok( $url, '?' );
384 $key = crc32( $url );
385 $sizes = wp_cache_get( $key, 'optml_sources' );
386 if ( $sizes === false ) {
387 $filepath = substr( $url, strpos( $url, $this->upload_resource['content_folder'] ) + $this->upload_resource['content_folder_length'] );
388 $filepath = WP_CONTENT_DIR . $filepath;
389 if ( is_file( $filepath ) ) {
390 $sizes = getimagesize( $filepath );
391 wp_cache_add( $key, [ $sizes[0], $sizes[1] ], 'optml_sources', DAY_IN_SECONDS );
392 }
393 }
394 list( $width, $height ) = $sizes;
395 }
396
397 $width = ! is_numeric( $width ) ? '100%' : $width;
398 $height = ! is_numeric( $height ) ? '100%' : $height;
399
400 return
401 str_replace(
402 [ '#width#', '#height#' ],
403 [
404 $width,
405 $height,
406 ],
407 self::SVG_PLACEHOLDER
408 );
409 }
410
411 /**
412 * Check if we should add the noscript tag.
413 *
414 * @param string $tag Html tag.
415 *
416 * @return bool Should add?
417 */
418 public function should_add_noscript( $tag ) {
419 foreach ( self::get_ignore_noscript_flags() as $banned_string ) {
420 if ( strpos( $tag, $banned_string ) !== false ) {
421 return false;
422 }
423 }
424
425 return true;
426 }
427 /**
428 * Check if we should lazyload iframe.
429 *
430 * @param string $tag Html tag.
431 *
432 * @return bool Should add?
433 */
434 public function should_lazyload_iframe( $tag ) {
435 foreach ( self::get_iframe_lazyload_flags() as $banned_string ) {
436 if ( strpos( $tag, $banned_string ) !== false ) {
437 return false;
438 }
439 }
440
441 return true;
442 }
443
444 /**
445 * Returns flags for ignoring noscript tag additional watch.
446 *
447 * @return array
448 */
449 public static function get_ignore_noscript_flags() {
450
451 if ( ! empty( self::$ignore_no_script_flags ) && is_array( self::$ignore_no_script_flags ) ) {
452 return self::$ignore_no_script_flags;
453 }
454
455 self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [] );
456
457 return self::$ignore_no_script_flags;
458 }
459 /**
460 * Returns possible lazyload flags for iframes.
461 *
462 * @return array
463 */
464 public static function get_iframe_lazyload_flags() {
465
466 if ( ! empty( self::$iframe_lazyload_flags ) && is_array( self::$iframe_lazyload_flags ) ) {
467 return self::$iframe_lazyload_flags;
468 }
469
470 self::$iframe_lazyload_flags = apply_filters( 'optml_iframe_lazyload_flags', [ 'gform_ajax_frame', '<noscript', 'recaptcha' ] );
471
472 return self::$iframe_lazyload_flags;
473 }
474 /**
475 * Throw error on object clone
476 *
477 * The whole idea of the singleton design pattern is that there is a single
478 * object therefore, we don't want the object to be cloned.
479 *
480 * @codeCoverageIgnore
481 * @access public
482 * @return void
483 * @since 1.0.0
484 */
485 public function __clone() {
486 // Cloning instances of the class is forbidden.
487 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
488 }
489
490 /**
491 * Disable unserializing of the class
492 *
493 * @codeCoverageIgnore
494 * @access public
495 * @return void
496 * @since 1.0.0
497 */
498 public function __wakeup() {
499 // Unserializing instances of the class is forbidden.
500 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
501 }
502
503 }
504