PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 4.2.12
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v4.2.12
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 4.2.12, at inc/lazyload_replacer.php

660 lines 23.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use OptimoleWP\PageProfiler\Profile;
4 use OptimoleWP\Preload\Links;
5
6 /**
7 * The class handles the img tag replacements for lazyload.
8 *
9 * @package \Optml\Inc
10 * @author Optimole <friends@optimole.com>
11 */
12 final class Optml_Lazyload_Replacer extends Optml_App_Replacer {
13 use Optml_Normalizer;
14 use Optml_Validator;
15 use Optml_Dam_Offload_Utils;
16
17 const IFRAME_PLACEHOLDER_CLASS = '
18 iframe[data-opt-src]:not([data-opt-lazy-loaded]) {
19 background-color: #ffffff;
20 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");
21 background-repeat: no-repeat;
22 background-position: 50% 50%;
23 }
24 video[data-opt-src]:not([data-opt-lazy-loaded]) {
25 background-color: #ffffff;
26 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");
27 background-repeat: no-repeat;
28 background-position: 50% 50%;
29 }';
30 const IFRAME_PLACEHOLDER_STYLE = '<style type="text/css">' . self::IFRAME_PLACEHOLDER_CLASS . '</style>';
31
32 const IFRAME_TEMP_COMMENT = '/** optmliframelazyloadplaceholder */';
33
34 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%3Crect%20width%3D%22#width#%22%20height%3D%22#height#%22%20fill%3D%22#fill#%22%2F%3E%3C%2Fsvg%3E';
35 /**
36 * If frame lazyload is present on page.
37 *
38 * @var bool Whether or not at least one iframe has been lazyloaded.
39 */
40 private static $found_iframe = false;
41 /**
42 * Cached object instance.
43 *
44 * @var Optml_Lazyload_Replacer
45 */
46 protected static $instance = null;
47 /**
48 * Holds the number of images to skip from lazyload.
49 *
50 * @var integer The number image tags to skip.
51 */
52 private static $skip_lazyload_images = null;
53 /**
54 * Holds classes for listening to lazyload on background.
55 *
56 * @var array Lazyload background classes.
57 */
58 private static $lazyload_background_classes = null;
59 /**
60 * Selectors used for background lazyload.
61 *
62 * @var array Lazyload background CSS selectors.
63 */
64 private static $background_lazyload_selectors = null;
65 /**
66 * Holds flags which remove noscript tag bundle causing issues on render, i.e slider plugins.
67 *
68 * @var array Noscript flags.
69 */
70 private static $ignore_no_script_flags = null;
71 /**
72 * Holds possible iframe lazyload flags where we should ignore our lazyload.
73 *
74 * @var array
75 */
76 protected static $iframe_lazyload_flags = null;
77 /**
78 * Holds classes responsabile for watching lazyload behaviour.
79 *
80 * @var array Lazyload classes.
81 */
82 private static $lazyload_watcher_classes = null;
83 /**
84 * Should we use the generic placeholder?
85 *
86 * @var bool Lazyload placeholder flag.
87 */
88 private static $is_lazyload_placeholder = false;
89
90 /**
91 * Class instance method.
92 *
93 * @codeCoverageIgnore
94 * @static
95 * @return Optml_Lazyload_Replacer
96 * @since 1.0.0
97 * @access public
98 */
99 public static function instance() {
100 if ( null === self::$instance ) {
101 self::$instance = new self();
102 add_action( 'optml_replacer_setup', [ self::$instance, 'init' ] );
103 }
104
105 return self::$instance;
106 }
107
108 /**
109 * Return lazyload selectors for background images.
110 *
111 * @return array Lazyload selectors.
112 */
113 public static function get_background_lazyload_selectors() {
114 if ( null !== self::$background_lazyload_selectors ) {
115 return self::$background_lazyload_selectors;
116 }
117
118 if ( self::instance()->settings === null ) {
119 self::$background_lazyload_selectors = [];
120
121 return self::$background_lazyload_selectors;
122 }
123 if ( self::instance()->settings->get( 'bg_replacer' ) === 'disabled' ) {
124 self::$background_lazyload_selectors = [];
125 return self::$background_lazyload_selectors;
126 }
127 $default_watchers = [
128 '[class*="wp-block-cover"][style*="background-image"]',
129 '[style*="background-image:url("]', '[style*="background-image: url("]',
130 '[style*="background:url("]', '[style*="background: url("]',
131 '[class*="wp-block-group"][style*="background-image"]',
132 ];
133
134 $saved_watchers = self::instance()->settings->get_watchers();
135
136 $saved_watchers = str_replace( [ "\n", "\r" ], ',', $saved_watchers );
137 $saved_watchers = explode( ',', $saved_watchers );
138 $all_watchers = array_merge( $default_watchers, $saved_watchers );
139 $all_watchers = apply_filters( 'optml_lazyload_bg_selectors', $all_watchers );
140 $all_watchers = array_filter(
141 $all_watchers,
142 function ( $value ) {
143 return ! empty( $value ) && strlen( $value ) >= 2;
144 }
145 );
146
147 self::$background_lazyload_selectors = $all_watchers;
148
149 return self::$background_lazyload_selectors;
150 }
151
152 /**
153 * Returns background classes for lazyload.
154 *
155 * @return array
156 */
157 public static function get_lazyload_bg_classes() {
158 if ( null !== self::$lazyload_background_classes ) {
159 return self::$lazyload_background_classes;
160 }
161
162 self::$lazyload_background_classes = apply_filters( 'optml_lazyload_bg_classes', [] );
163
164 return self::$lazyload_background_classes;
165 }
166 /**
167 * Returns the number of images to skip from lazyload.
168 *
169 * @return integer
170 */
171 public static function get_skip_lazyload_limit() {
172
173 if ( self::$skip_lazyload_images !== null ) {
174 return self::$skip_lazyload_images;
175 }
176
177 self::$skip_lazyload_images = apply_filters( 'optml_lazyload_images_skip', self::instance()->settings->get( 'skip_lazyload_images' ) );
178 return self::$skip_lazyload_images;
179 }
180 /**
181 * Returns classes for lazyload additional watch.
182 *
183 * @return array
184 */
185 public static function get_watcher_lz_classes() {
186 if ( null !== self::$lazyload_watcher_classes ) {
187 return self::$lazyload_watcher_classes;
188 }
189
190 self::$lazyload_watcher_classes = apply_filters( 'optml_watcher_lz_classes', [] );
191
192 return self::$lazyload_watcher_classes;
193 }
194
195 /**
196 * The initialize method.
197 */
198 public function init() {
199 parent::init();
200
201 if ( ! $this->settings->use_lazyload() ) {
202 return;
203 }
204 $filters = $this->settings->get_filters();
205 if ( ! Optml_Filters::should_do_page( $filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_URL ], $filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_URL_MATCH ] ) ) {
206 return;
207 }
208
209 self::$is_lazyload_placeholder = self::$instance->settings->get( 'lazyload_placeholder' ) === 'enabled';
210
211 add_filter( 'optml_tag_replace', [ $this, 'lazyload_tag_replace' ], 2, 6 );
212
213 add_filter( 'optml_video_replace', [ $this, 'lazyload_video_replace' ], 2, 1 );
214 }
215 /**
216 * Check if there are lazyloaded iframes.
217 *
218 * @return bool Whether an iframe was lazyloaded on the page or not.
219 */
220 public static function found_iframe() {
221 return self::$found_iframe;
222 }
223
224 /**
225 * Check if the img tag should be lazyloaded early.
226 *
227 * @param string $image_tag The image tag.
228 * @return bool Whether the image tag should be lazyloaded early or not.
229 */
230 public function should_lazyload_early( $image_tag ) {
231 $flags = apply_filters( 'optml_lazyload_early_flags', [] );
232
233 foreach ( $flags as $flag ) {
234 if ( strpos( $image_tag, $flag ) !== false ) {
235 return true;
236 }
237 }
238
239 return false;
240 }
241 /**
242 * Replaces the tags with lazyload tags.
243 *
244 * @param string $new_tag The new tag.
245 * @param string $original_url The original URL.
246 * @param string $new_url The optimized URL.
247 * @param array $optml_args Options passed for URL optimization.
248 * @param bool $is_slashed If the url needs slashes.
249 * @param string $full_tag Full tag, wrapper included.
250 *
251 * @return string
252 */
253 public function lazyload_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed = false, $full_tag = '' ) {
254
255 if ( ! $this->can_lazyload_for( $original_url, $full_tag ) ) {
256
257 return Optml_Tag_Replacer::instance()->regular_tag_replace( $new_tag, $original_url, $new_url, $optml_args, $is_slashed );
258 }
259 // Remove fetchpriority high attribute when the image is lazyloaded
260 if ( strpos( $new_tag, 'fetchpriority' ) !== false ) {
261 $new_tag = str_ireplace(
262 [
263 'fetchpriority=high',
264 'fetchpriority="high"',
265 "fetchpriority='high'",
266 'fetchpriority=\"high\"',
267 "fetchpriority=\'high\'",
268 ],
269 '',
270 $new_tag
271 );
272 }
273
274 if ( self::instance()->settings->get( 'native_lazyload' ) === 'enabled' ) {
275 // First check if loading attribute already exists
276 if ( strpos( $new_tag, 'loading=' ) !== false ) {
277 // Replace existing loading attribute with lazy
278 $loading_attr = $is_slashed ? 'loading=\"lazy\"' : 'loading="lazy"';
279 $new_tag = preg_replace( '/loading=(["\'])[^"\']*\1/i', $loading_attr, $new_tag );
280 } else {
281 // No loading attribute exists, add it after <img
282 $loading_attr = $is_slashed ? ' loading=\"lazy\"' : ' loading="lazy"';
283 $new_tag = preg_replace( '/<img/i', '<img' . $loading_attr, $new_tag, 1 );
284 }
285 return $new_tag;
286 }
287
288 // we remove the loading attribute if it exists with strpos
289 $new_tag = preg_replace( '/loading=(["\'])[^"\']*\1/i', '', $new_tag );
290
291 $should_ignore_rescale = ! $this->is_valid_mimetype_from_url( $original_url, [ 'gif' => true, 'svg' => true ] );
292
293 if ( ! self::$is_lazyload_placeholder && ! $should_ignore_rescale ) {
294 $optml_args['quality'] = 'eco';
295 $optml_args['resize'] = [];
296 $low_url = apply_filters( 'optml_content_url', $this->get_unoptimized_url( $original_url ), $optml_args );
297 $low_url = $is_slashed ? addcslashes( $low_url, '/' ) : $low_url;
298 } else {
299 $low_url = $this->get_svg_for(
300 isset( $optml_args['width'] ) ? $optml_args['width'] : '100%',
301 isset( $optml_args['height'] ) ? $optml_args['height'] : '100%',
302 ( $should_ignore_rescale ? null : $original_url )
303 );
304 }
305 $opt_format = '';
306
307 if ( $this->should_add_data_tag( $full_tag ) ) {
308 $opt_format = ' data-opt-src="%s" ';
309 $custom_class = '';
310
311 if ( $should_ignore_rescale ) {
312 $custom_class .= 'optimole-lazy-only ';
313 }
314 if ( $this->should_lazyload_early( $full_tag ) ) {
315 $custom_class .= 'optimole-load-early';
316 }
317
318 if ( ! empty( $custom_class ) ) {
319 if ( strpos( $new_tag, 'class=' ) === false ) {
320 $opt_format .= ' class="' . $custom_class . '" ';
321 } else {
322 $new_tag = str_replace(
323 ( $is_slashed ? 'class=\"' : 'class="' ),
324 ( $is_slashed ? 'class=\"' . $custom_class . ' ' : 'class="' . $custom_class . ' ' ),
325 $new_tag
326 );
327 }
328 }
329 $opt_format = $is_slashed ? addslashes( $opt_format ) : $opt_format;
330 }
331 $new_url = $is_slashed ? addcslashes( $new_url, '/' ) : $new_url;
332
333 $opt_src = sprintf( $opt_format, $new_url );
334
335 $no_script_tag = str_replace(
336 $original_url,
337 $new_url,
338 $new_tag
339 );
340 $new_tag = preg_replace(
341 [
342 '/((?:\s|\'|"){1,}src(?>=|"|\'|\s|\\\\)*)' . preg_quote( $original_url, '/' ) . '/m',
343 '/<img/im',
344 ],
345 [
346 "$1$low_url",
347 '<img' . $opt_src,
348 ],
349 $new_tag,
350 1
351 );
352
353 // We remove srcset and sizes attributes since our lazyload does not need them.
354 $pattern = '/\s+(?:srcset|sizes)=("[^"]*"|\'[^\']*\'|\\\\"[^\\\\"]*\\\\")/i';
355 $new_tag = preg_replace( $pattern, '', $new_tag );
356
357 // We keep this for srcset lazyload compatibility that might break our mechanism.
358 $new_tag = str_replace( 'srcset=', 'old-srcset=', $new_tag );
359
360 if ( ! $this->should_add_noscript( $new_tag ) ) {
361 return $new_tag;
362 }
363
364 return $new_tag . '<noscript>' . $no_script_tag . '</noscript>';
365 }
366 /**
367 * Replaces video embeds with lazyload embeds.
368 *
369 * @param string $content Html page content.
370 *
371 * @return string
372 */
373 public function lazyload_video_replace( $content ) {
374 $video_tags = [];
375 $iframes = [];
376 $videos = [];
377 preg_match_all( '#(?:<noscript\s*>\s*)?<iframe(.*?)></iframe>(?:\s*</noscript\s*>)?#is', $content, $iframes );
378 preg_match_all( '#(?:<noscript\s*>\s*)?<video(.*?)>.*?</video>(?:\s*</noscript\s*>)?#is', $content, $videos );
379 $video_tags = array_merge( $iframes[0], $videos[0] );
380
381 $search = [];
382 $replace = [];
383
384 foreach ( $video_tags as $video_tag ) {
385
386 if ( ! $this->should_lazyload_iframe( $video_tag ) ) {
387 continue;
388 }
389 if ( preg_match( "/ data-opt-src=['\"]/is", $video_tag ) ) {
390 continue;
391 }
392 $should_add_noscript = true;
393 $original_tag = $video_tag;
394 // replace the src and add the data-opt-src attribute
395 if ( strpos( $video_tag, 'iframe' ) !== false ) {
396 $video_tag = preg_replace( '/iframe(.*?)src=/is', 'iframe$1 src="about:blank" data-opt-src=', $video_tag );
397 } elseif ( strpos( $video_tag, 'video' ) !== false ) {
398 if ( strpos( $video_tag, 'source' ) !== false ) {
399 if ( strpos( $video_tag, 'preload' ) === false ) {
400 $video_tag = preg_replace( '/video(.*?)>/is', 'video$1 preload="metadata">', $video_tag, 1 );
401 $should_add_noscript = false;
402 } else {
403 continue;
404 }
405 } else {
406 $video_tag = preg_replace( '/video(.*?)src=/is', 'video$1 data-opt-src=', $video_tag );
407 }
408 }
409 if ( $this->should_add_noscript( $video_tag ) && $should_add_noscript ) {
410 $video_tag .= '<noscript>' . $original_tag . '</noscript>';
411 }
412 array_push( $search, $original_tag );
413 array_push( $replace, $video_tag );
414 self::$found_iframe = true;
415 }
416 $search = array_unique( $search );
417 $replace = array_unique( $replace );
418 $content = str_replace( $search, $replace, $content );
419 return $content;
420 }
421
422 /**
423 * Check if the lazyload is allowed for this url.
424 *
425 * @param string $url Url.
426 * @param string $tag Html tag.
427 *
428 * @return bool We can lazyload?
429 */
430 public function can_lazyload_for( $url, $tag = '' ) {
431 if ( OPTML_DEBUG ) {
432 do_action( 'optml_log', 'can_lazyload_for: ' . $url . ' ' . $tag );
433 }
434 foreach ( self::possible_lazyload_flags() as $banned_string ) {
435 if ( strpos( $tag, $banned_string ) !== false ) {
436 return false;
437 }
438 }
439 if ( false === Optml_Filters::should_do_image( $url, self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_FILENAME ] ) ) {
440 return false;
441 }
442 $url = strtok( $url, '?' );
443
444 $type = wp_check_filetype(
445 basename( $url ),
446 Optml_Config::$image_extensions
447 );
448
449 if ( empty( $type['ext'] ) ) {
450 return false;
451 }
452
453 if ( false === Optml_Filters::should_do_extension( self::$filters[ Optml_Settings::FILTER_TYPE_LAZYLOAD ][ Optml_Settings::FILTER_EXT ], $type['ext'] ) ) {
454 return false;
455 }
456
457 if ( defined( 'OPTML_DISABLE_PNG_LAZYLOAD' ) && OPTML_DISABLE_PNG_LAZYLOAD ) {
458 return $type['ext'] !== 'png';
459 }
460
461 $no_viewport_data_available = true;
462
463 if ( $this->settings->is_lazyload_type_viewport() ) {
464 $image_id = $this->get_id_by_url( $url );
465 $is_in_all_viewports = Optml_Manager::instance()->page_profiler->is_in_all_viewports( $image_id );
466 $is_lcp_image = Optml_Manager::instance()->page_profiler->is_lcp_image_in_all_viewports( $image_id );
467 $no_viewport_data_available = ! Optml_Manager::instance()->page_profiler->is_data_available();
468
469 if ( OPTML_DEBUG ) {
470 if ( $is_in_all_viewports ) {
471 do_action( 'optml_log', 'Lazyload skipped image is in all viewports ' . $url . '|' . $image_id );
472 } elseif ( $is_lcp_image ) {
473 do_action( 'optml_log', 'Lazyload skipped image is LCP ' . $url . '|' . $image_id );
474 }
475 }
476
477 if ( $is_in_all_viewports || $is_lcp_image ) {
478 return false;
479 }
480 }
481
482 if (
483 $no_viewport_data_available &&
484 $this->settings->is_lazyload_type_fixed() &&
485 Optml_Tag_Replacer::$lazyload_skipped_images < self::get_skip_lazyload_limit()
486 ) {
487 return false;
488 }
489 return true;
490 }
491
492 /**
493 * Check if we should add the data-opt-tag.
494 *
495 * @param string $tag Html tag.
496 *
497 * @return bool Should add?
498 */
499 public function should_add_data_tag( $tag ) {
500 foreach ( self::possible_data_ignore_flags() as $banned_string ) {
501 if ( strpos( $tag, $banned_string ) !== false ) {
502 return false;
503 }
504 }
505
506 return true;
507 }
508
509 /**
510 * Get SVG markup with specific width/height.
511 *
512 * @param int|string $width Markup Width.
513 * @param int|string $height Markup Height.
514 * @param string|null $url Original URL.
515 *
516 * @return string SVG code.
517 */
518 public function get_svg_for( $width, $height, $url = null ) {
519
520 if ( $url !== null && ! is_numeric( $width ) ) {
521 $url = strtok( $url, '?' );
522 $key = crc32( $url );
523 $sizes = wp_cache_get( $key, 'optml_sources' );
524 if ( $sizes === false ) {
525 $filepath = substr( $url, strpos( $url, $this->upload_resource['content_folder'] ) + $this->upload_resource['content_folder_length'] );
526 $filepath = WP_CONTENT_DIR . $filepath;
527 if ( is_file( $filepath ) ) {
528 $sizes = getimagesize( $filepath );
529 wp_cache_add( $key, [ $sizes[0], $sizes[1] ], 'optml_sources', DAY_IN_SECONDS );
530 }
531 }
532 // Check if $sizes is an array before list() assignment to prevent fatal error on PHP 8.2+
533 // when the image file is missing (e.g., offloaded to CDN or deleted).
534 if ( is_array( $sizes ) ) {
535 list( $width, $height ) = $sizes;
536 }
537 }
538 // If the width is not found the url might be an offloaded attachment so we can get the width and height from the metadata.
539 if ( ! is_numeric( $width ) && ! empty( $url ) ) {
540 $attachment_id = $this->attachment_url_to_post_id( $url );
541 $meta = wp_get_attachment_metadata( $attachment_id );
542 $width = $meta['width'] ?? false;
543 $height = $meta['height'] ?? false;
544 }
545
546 $width = ! is_numeric( $width ) ? '100%' : $width;
547 $height = ! is_numeric( $height ) ? '100%' : $height;
548 $fill = $this->settings->get( 'placeholder_color' );
549
550 if ( empty( $fill ) ) {
551 $fill = 'transparent';
552 }
553
554 return str_replace(
555 [ '#width#', '#height#', '#fill#' ],
556 [
557 $width,
558 $height,
559 urlencode( $fill ),
560 ],
561 self::SVG_PLACEHOLDER
562 );
563 }
564
565 /**
566 * Check if we should add the noscript tag.
567 *
568 * @param string $tag Html tag.
569 *
570 * @return bool Should add?
571 */
572 public function should_add_noscript( $tag ) {
573 if ( $this->settings->get( 'no_script' ) === 'disabled' ) {
574 return false;
575 }
576 foreach ( self::get_ignore_noscript_flags() as $banned_string ) {
577 if ( strpos( $tag, $banned_string ) !== false ) {
578 return false;
579 }
580 }
581
582 return true;
583 }
584 /**
585 * Check if we should lazyload iframe.
586 *
587 * @param string $tag Html tag.
588 *
589 * @return bool Should add?
590 */
591 public function should_lazyload_iframe( $tag ) {
592
593 foreach ( self::get_iframe_lazyload_flags() as $banned_string ) {
594 if ( strpos( $tag, $banned_string ) !== false ) {
595 return false;
596 }
597 }
598
599 return true;
600 }
601
602 /**
603 * Returns flags for ignoring noscript tag additional watch.
604 *
605 * @return array
606 */
607 public static function get_ignore_noscript_flags() {
608 if ( null !== self::$ignore_no_script_flags ) {
609 return self::$ignore_no_script_flags;
610 }
611
612 self::$ignore_no_script_flags = apply_filters( 'optml_ignore_noscript_on', [ '<noscript' ] );
613
614 return self::$ignore_no_script_flags;
615 }
616 /**
617 * Returns possible lazyload flags for iframes.
618 *
619 * @return array
620 */
621 public static function get_iframe_lazyload_flags() {
622 if ( null !== self::$iframe_lazyload_flags ) {
623 return self::$iframe_lazyload_flags;
624 }
625
626 self::$iframe_lazyload_flags = self::possible_lazyload_flags();
627 self::$iframe_lazyload_flags = array_merge( self::$iframe_lazyload_flags, apply_filters( 'optml_iframe_lazyload_flags', [ 'gform_ajax_frame', '<noscript', 'recaptcha', '-src' ] ) );
628
629 return self::$iframe_lazyload_flags;
630 }
631 /**
632 * Throw error on object clone
633 *
634 * The whole idea of the singleton design pattern is that there is a single
635 * object therefore, we don't want the object to be cloned.
636 *
637 * @codeCoverageIgnore
638 * @access public
639 * @return void
640 * @since 1.0.0
641 */
642 public function __clone() {
643 // Cloning instances of the class is forbidden.
644 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
645 }
646
647 /**
648 * Disable unserializing of the class
649 *
650 * @codeCoverageIgnore
651 * @access public
652 * @return void
653 * @since 1.0.0
654 */
655 public function __wakeup() {
656 // Unserializing instances of the class is forbidden.
657 _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; huh?', 'optimole-wp' ), '1.0.0' );
658 }
659 }
660