PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 4.2.15
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v4.2.15
4.2.15 4.2.14 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 All 136 releases
optimole-wp / inc / v2 / BgOptimizer / Lazyload.php

Lazyload.php in Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization 4.2.15, at inc/v2/BgOptimizer/Lazyload.php

160 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace OptimoleWP\BgOptimizer;
4
5 use OptimoleWP\PageProfiler\Profile;
6 use OptimoleWP\Preload\Links;
7 use Optml_Lazyload_Replacer;
8
9 /**
10 * Class Lazyload
11 *
12 * @package OptimoleWP\BgOptimizer
13 */
14 class Lazyload {
15 const MARKER = '/* OPTML_VIEWPORT_BG_SELECTORS */';
16 /**
17 * Shape of a plain selector the frontend getUniqueSelector() reports: ids, tags, classes, ' > ' and :nth-of-type(N).
18 */
19 const SAFE_SELECTOR_PATTERN = '/^(?:[\w\-#. >\x{00A0}-\x{10FFFF}]|:nth-of-type\(\d+\))+$/u';
20
21 /**
22 * Check whether a client-reported selector is a plain selector safe to embed in the stylesheet as-is.
23 *
24 * A selector carrying CSS metacharacters (`(`, `)`, `{`, `;`, `:` beyond nth-of-type, `/`, …) is not
25 * embedded: the browser already discards the whole rule when it sees one, so treating it as unsafe
26 * keeps the current behaviour while making CSS injection impossible.
27 *
28 * @param mixed $selector The selector to check.
29 *
30 * @return bool Whether the selector matches the plain-selector shape.
31 */
32 public static function is_safe_selector( $selector ): bool {
33 return is_string( $selector ) && $selector !== '' && preg_match( self::SAFE_SELECTOR_PATTERN, $selector ) === 1;
34 }
35 /**
36 * Get the current personalized CSS for lazy loading.
37 *
38 * @return string The personalized CSS.
39 */
40 public static function get_current_personalized_css() {
41 return self::get_personalized_css( Profile::get_current_profile_data() );
42 }
43 /**
44 * Get personalized CSS based on profile data.
45 *
46 * @param array $data Profile data.
47 *
48 * @return string The personalized CSS.
49 */
50 public static function get_personalized_css( $data ) {
51 $lazyload_selectors = array_values( Optml_Lazyload_Replacer::get_background_lazyload_selectors() );
52 $lazyload_selectors = array_fill_keys( $lazyload_selectors, true );
53 $css_selectors = [];
54 $preload_urls = [];
55 $tainted = [];
56 foreach ( Profile::get_active_devices() as $device ) {
57 $personalized_selectors = $data[ $device ]['bg'] ?? [];
58 $lcp_data = $data[ $device ]['lcp'] ?? [];
59 // Guard against malformed profile shapes (e.g. a non-normalizing custom storage backend).
60 $personalized_selectors = is_array( $personalized_selectors ) ? $personalized_selectors : [];
61 $lcp_data = is_array( $lcp_data ) ? $lcp_data : [];
62 if ( OPTML_DEBUG ) {
63 do_action( 'optml_log', 'personalized_selectors: ' . $device . ' ' . print_r( $personalized_selectors, true ) );
64 do_action( 'optml_log', 'LCP data: ' . $device . ' ' . print_r( $lcp_data, true ) );
65 }
66
67 $selectors = self::collect_device_selectors( $personalized_selectors, $lcp_data, $lazyload_selectors );
68 // null means a client selector was unsafe, so the whole device rule is voided.
69 $tainted[ $device ] = null === $selectors;
70 $css_selectors[ $device ] = $tainted[ $device ] ? [] : $selectors;
71
72 $preload_urls[ $device ] = [];
73 if ( isset( $lcp_data['type'] ) && $lcp_data['type'] === 'bg' && ! empty( $lcp_data['bgUrls'] ) ) {
74 $preload_urls[ $device ] = array_merge( $preload_urls[ $device ], $lcp_data['bgUrls'] );
75 }
76 }
77 if ( OPTML_DEBUG ) {
78 do_action( 'optml_log', 'BGCSS selectors: ' . print_r( $css_selectors, true ) );
79 do_action( 'optml_log', 'BGPreload URLs: ' . print_r( $preload_urls, true ) );
80 }
81
82 foreach ( array_intersect( $preload_urls[ Profile::DEVICE_TYPE_MOBILE ], $preload_urls[ Profile::DEVICE_TYPE_DESKTOP ] ) as $url ) {
83 Links::add_link( [ 'url' => $url, 'priority' => 'high' ] );
84 }
85
86 $hide_rule = ' { background-image: none !important; }';
87 $mobile = $css_selectors[ Profile::DEVICE_TYPE_MOBILE ];
88 $desktop = $css_selectors[ Profile::DEVICE_TYPE_DESKTOP ];
89
90 // Keep the surviving rule scoped when the other device was voided.
91 if ( $tainted[ Profile::DEVICE_TYPE_MOBILE ] !== $tainted[ Profile::DEVICE_TYPE_DESKTOP ] ) {
92 if ( $tainted[ Profile::DEVICE_TYPE_MOBILE ] ) {
93 return empty( $desktop ) ? '' : '@media (min-width: 600px) { ' . implode( ',', $desktop ) . $hide_rule . ' }';
94 }
95 return empty( $mobile ) ? '' : '@media (max-width: 600px) { ' . implode( ',', $mobile ) . $hide_rule . ' }';
96 }
97
98 $mobile_selectors = implode( ',', $mobile );
99 $desktop_selectors = implode( ',', $desktop );
100
101 if ( $mobile_selectors === $desktop_selectors ) {
102 return empty( $mobile_selectors ) ? '' : $mobile_selectors . $hide_rule;
103 }
104 // if any of those are empty, return the other one
105 if ( empty( $mobile_selectors ) ) {
106 return $desktop_selectors . $hide_rule;
107 }
108 if ( empty( $desktop_selectors ) ) {
109 return $mobile_selectors . $hide_rule;
110 }
111
112 // generate media query for desktop and mobile
113 $media_query = '@media (max-width: 600px) { ' . $mobile_selectors . $hide_rule . ' } @media (min-width: 600px) { ' . $desktop_selectors . $hide_rule . ' }';
114 return $media_query;
115 }
116 /**
117 * Build the background-hide selectors for one device.
118 *
119 * @param array<string, array<string, mixed>> $personalized_selectors Stored bg data: watcher => [ above-fold selector => urls ].
120 * @param array<string, mixed> $lcp_data Stored LCP data for the device.
121 * @param array<string, bool> $lazyload_selectors Allowed watcher selectors as keys.
122 *
123 * @return array<int, string>|null Selector strings, or null when a client selector is unsafe (the device rule is voided).
124 */
125 private static function collect_device_selectors( array $personalized_selectors, array $lcp_data, array $lazyload_selectors ) {
126 $css_selectors = [];
127 foreach ( $personalized_selectors as $selector => $above_fold_selectors ) {
128 if ( ! isset( $lazyload_selectors[ $selector ] ) ) {
129 continue;
130 }
131 if ( empty( $above_fold_selectors ) ) {
132 $css_selectors[] = 'html ' . strip_tags( $selector ) . ':not(.optml-bg-lazyloaded)';
133 continue;
134 }
135 foreach ( $above_fold_selectors as $above_fold_selector => $bg_urls ) {
136 if ( ! self::is_safe_selector( $above_fold_selector ) ) {
137 return null;
138 }
139 $css_selectors[] = 'html ' . strip_tags( $selector ) . ':not(' . $above_fold_selector . '):not(.optml-bg-lazyloaded)';
140 }
141 }
142
143 $css_selectors = array_unique( $css_selectors );
144
145 if ( isset( $lcp_data['type'] ) && $lcp_data['type'] === 'bg' && ! empty( $lcp_data['bgSelector'] ) ) {
146 if ( ! self::is_safe_selector( $lcp_data['bgSelector'] ) ) {
147 return null;
148 }
149 $css_selectors = array_map(
150 function ( $selector ) use ( $lcp_data ) {
151 return $selector . ':not(' . $lcp_data['bgSelector'] . ')';
152 },
153 $css_selectors
154 );
155 }
156
157 return array_values( $css_selectors );
158 }
159 }
160