PluginProbe
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution / 1.5.0
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution v1.5.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 1.5.0, at src/Performance/Dom/Modifiers/Lazy_Load_Modifier.php

165 lines 4.4 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 * Return true immediately if we're enabled, otherwise pass the current result to the next stage.
64 *
65 * @param bool $enabled Whether the previous stage is enabled.
66 * @param Closure $next The closer passed to the next stage.
67 *
68 * @return bool
69 */
70 public function enabled( bool $enabled, Closure $next ): bool {
71 $on = $this->config->get( 'page_cache.lazy_loading.enabled' );
72
73 return $on ?: $next( $enabled );
74 }
75
76 /**
77 * Find elements that have a style tag which contains a `url()` data tag.
78 * Add our custom data attributes and copy the entire style attribute content to another
79 * data attribute in order to be swapped back when it enters the viewport to enable CSS background
80 * lazy loading.
81 *
82 * @param Crawler $crawler The Symfony Crawler instance.
83 * @param Closure $next The next closure to pass in the pipeline.
84 *
85 * @throws InvalidArgumentException When current node is empty.
86 * @throws LogicException If the CssSelector Component is not available.
87 *
88 * @return Crawler
89 */
90 public function modify( Crawler $crawler, Closure $next ): Crawler {
91 // Iterate through all elements with a style tag that contains a url() data type.
92 $crawler->filter( '[style*="url("]' )->each(
93 function ( Crawler $node ) {
94 $current = $node->getNode( 0 );
95
96 $style = $node->attr( 'style' );
97
98 // Match CSS property:value pairs.
99 preg_match_all( '/([\w-]+)\s*:\s*([^;]+)\s*;?/', $style, $matches, PREG_SET_ORDER );
100
101 $style_without_urls = '';
102 $style_with_urls = '';
103
104 foreach ( $matches as $match ) {
105 $css_property = trim( $match[1] );
106 $css_value = trim( $match[2] );
107
108 $formatted_style = "$css_property:$css_value;";
109
110 if ( str_contains( $css_value, 'url(' ) ) {
111 $style_with_urls .= $formatted_style;
112 } else {
113 $style_without_urls .= $formatted_style;
114 }
115 }
116
117 // These must match our attributes from our lazy-load.js script.
118 $current->setAttribute( 'style', $style_without_urls );
119 $current->setAttribute( 'data-solid-perf-style', $style_with_urls );
120 $current->setAttribute( 'data-solid-perf-trigger', 'viewport' );
121 $current->setAttribute( 'data-solid-perf-attrs', 'style' );
122
123 $this->inject_script = true;
124 }
125 );
126
127 if ( $this->inject_script ) {
128 $this->inject_lazy_loading_script( $crawler );
129 }
130
131 return $next( $crawler );
132 }
133
134 /**
135 * Injects our lazy loading script tag just before the closing </body> tag.
136 *
137 * @param Crawler $crawler The crawler instance.
138 *
139 * @throws LogicException If the CssSelector Component is not available.
140 *
141 * @return void
142 */
143 private function inject_lazy_loading_script( Crawler $crawler ): void {
144 $this->logger->debug( 'DOM Modifier: injecting lazyLoader.js before closing body tag' );
145
146 $body = $crawler->filter( 'body' );
147
148 if ( $body->count() < 0 ) {
149 return;
150 }
151
152 // Build the script "src" attribute.
153 $script_src = sprintf(
154 '%s?ver=%s',
155 $this->asset->get_url( 'build/lazyLoader.js' ),
156 $this->asset->get_meta( 'build/lazyLoader' )['version'] ?? '1.0.0'
157 );
158
159 $script_node = $crawler->getNode( 0 )->ownerDocument->createElement( 'script' );
160 $script_node->setAttribute( 'src', esc_url( $script_src ) );
161
162 $body->getNode( 0 )->appendChild( $script_node );
163 }
164 }
165