PluginProbe
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution / 2.0.0
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution v2.0.0
2.0.1 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.5.0 1.6.0 1.6.1 1.7.0 1.7.1 1.8.0 1.9.0 2.0.0
solid-performance / src / Performance / Dom / Modifiers / Lazy_Load_Modifier.php

Lazy_Load_Modifier.php in Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution 2.0.0, at src/Performance/Dom/Modifiers/Lazy_Load_Modifier.php

176 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The Lazy Loading DOM modifier.
4 *
5 * @package SolidWP\Performance
6 */
7
8 declare( strict_types=1 );
9
10 namespace SolidWP\Performance\Dom\Modifiers;
11
12 use Closure;
13 use InvalidArgumentException;
14 use LogicException;
15 use SolidWP\Performance\Assets\Asset;
16 use SolidWP\Performance\Config\Config;
17 use SolidWP\Performance\Dom\Contracts\Modifier;
18 use SolidWP\Performance\Psr\Log\LoggerInterface;
19 use SolidWP\Performance\Symfony\Component\DomCrawler\Crawler;
20
21 /**
22 * The Lazy Loading DOM modifier.
23 *
24 * @package SolidWP\Performance
25 */
26 final class Lazy_Load_Modifier implements Modifier {
27
28 /**
29 * Whether we found CSS background images to process and need to inject
30 * our JS into the markup.
31 *
32 * @var bool
33 */
34 private bool $inject_script = false;
35
36 /**
37 * @var Config
38 */
39 private Config $config;
40
41 /**
42 * @var Asset
43 */
44 private Asset $asset;
45
46 /**
47 * @var LoggerInterface
48 */
49 private LoggerInterface $logger;
50
51 /**
52 * @param Config $config The config object.
53 * @param Asset $asset The asset handler.
54 * @param LoggerInterface $logger The logger.
55 */
56 public function __construct( Config $config, Asset $asset, LoggerInterface $logger ) {
57 $this->config = $config;
58 $this->asset = $asset;
59 $this->logger = $logger;
60 }
61
62 /**
63 * Whether this pipeline step is enabled.
64 *
65 * @return bool
66 */
67 public function is_enabled(): bool {
68 return (bool) $this->config->get( 'page_cache.lazy_loading.enabled' );
69 }
70
71 /**
72 * Return true immediately if we're enabled, otherwise pass the current result to the next stage.
73 *
74 * @param bool $enabled Whether the previous stage is enabled.
75 * @param Closure $next The closer passed to the next stage.
76 *
77 * @return bool
78 */
79 public function enabled( bool $enabled, Closure $next ): bool {
80 return $this->is_enabled() ?: $next( $enabled );
81 }
82
83 /**
84 * Find elements that have a style tag which contains a `url()` data tag.
85 * Add our custom data attributes and copy the entire style attribute content to another
86 * data attribute in order to be swapped back when it enters the viewport to enable CSS background
87 * lazy loading.
88 *
89 * @param Crawler $crawler The Symfony Crawler instance.
90 * @param Closure $next The next closure to pass in the pipeline.
91 *
92 * @throws InvalidArgumentException When current node is empty.
93 * @throws LogicException If the CssSelector Component is not available.
94 *
95 * @return Crawler
96 */
97 public function modify( Crawler $crawler, Closure $next ): Crawler {
98 if ( ! $this->is_enabled() ) {
99 return $next( $crawler );
100 }
101
102 // Iterate through all elements with a style tag that contains a url() data type.
103 $crawler->filter( '[style*="url("]' )->each(
104 function ( Crawler $node ) {
105 $current = $node->getNode( 0 );
106
107 $style = $node->attr( 'style' );
108
109 // Match CSS property:value pairs.
110 preg_match_all( '/([\w-]+)\s*:\s*([^;]+)\s*;?/', $style, $matches, PREG_SET_ORDER );
111
112 $style_without_urls = '';
113 $style_with_urls = '';
114
115 foreach ( $matches as $match ) {
116 $css_property = trim( $match[1] );
117 $css_value = trim( $match[2] );
118
119 $formatted_style = "$css_property:$css_value;";
120
121 if ( str_contains( $css_value, 'url(' ) ) {
122 $style_with_urls .= $formatted_style;
123 } else {
124 $style_without_urls .= $formatted_style;
125 }
126 }
127
128 // These must match our attributes from our lazy-load.js script.
129 $current->setAttribute( 'style', $style_without_urls );
130 $current->setAttribute( 'data-solid-perf-style', $style_with_urls );
131 $current->setAttribute( 'data-solid-perf-trigger', 'viewport' );
132 $current->setAttribute( 'data-solid-perf-attrs', 'style' );
133
134 $this->inject_script = true;
135 }
136 );
137
138 if ( $this->inject_script ) {
139 $this->inject_lazy_loading_script( $crawler );
140 }
141
142 return $next( $crawler );
143 }
144
145 /**
146 * Injects our lazy loading script tag just before the closing </body> tag.
147 *
148 * @param Crawler $crawler The crawler instance.
149 *
150 * @throws LogicException If the CssSelector Component is not available.
151 *
152 * @return void
153 */
154 private function inject_lazy_loading_script( Crawler $crawler ): void {
155 $this->logger->debug( 'DOM Modifier: injecting lazyLoader.js before closing body tag' );
156
157 $body = $crawler->filter( 'body' );
158
159 if ( $body->count() < 0 ) {
160 return;
161 }
162
163 // Build the script "src" attribute.
164 $script_src = sprintf(
165 '%s?ver=%s',
166 $this->asset->get_url( 'build/lazyLoader.js' ),
167 $this->asset->get_meta( 'build/lazyLoader' )['version'] ?? '1.0.0'
168 );
169
170 $script_node = $crawler->getNode( 0 )->ownerDocument->createElement( 'script' );
171 $script_node->setAttribute( 'src', esc_url( $script_src ) );
172
173 $body->getNode( 0 )->appendChild( $script_node );
174 }
175 }
176