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

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