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 / Page_Cache / Cache.php

Cache.php in Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution 1.5.0, at src/Performance/Page_Cache/Cache.php

289 lines 7.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles all functionality related to storing and returning files in the page cache directory.
4 *
5 * @since 0.1.0
6 *
7 * @package SolidWP\Performance
8 */
9
10 declare( strict_types=1 );
11
12 namespace SolidWP\Performance\Page_Cache;
13
14 use Exception;
15 use SolidWP\Performance\Container;
16 use SolidWP\Performance\Http\Header;
17 use SolidWP\Performance\Http\Request;
18 use SolidWP\Performance\Page_Cache\Meta\Meta_Manager;
19 use SolidWP\Performance\Pipelines\Page_Cache;
20 use SolidWP\Performance\StellarWP\Pipeline\Pipeline;
21 use SolidWP\Performance\StellarWP\SuperGlobals\SuperGlobals;
22
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit;
25 }
26
27 /**
28 * Handles all functionality related to storing and returning files in the page cache directory.
29 *
30 * @since 0.1.0
31 *
32 * @package SolidWP\Performance
33 */
34 class Cache {
35
36 /**
37 * The current request.
38 *
39 * @since 0.1.0
40 *
41 * @var Request
42 */
43 private Request $request;
44
45 /**
46 * @var Container
47 */
48 private Container $container;
49
50 /**
51 * @var Cache_Handler
52 */
53 private Cache_Handler $handler;
54
55 /**
56 * @var Meta_Manager
57 */
58 private Meta_Manager $meta_manager;
59
60 /**
61 * @var Header_Sender
62 */
63 private Header_Sender $header_sender;
64
65 /**
66 * The pipes to send all requests through before serving an existing cached page.
67 *
68 * @context pre-wordpress
69 *
70 * @since 0.1.0
71 *
72 * @var array<int,string>
73 */
74 private array $serve_pipes = [
75 Page_Cache\Response_Code::class,
76 Page_Cache\Constant::class,
77 Page_Cache\Admin::class,
78 Page_Cache\Authenticated::class,
79 Page_Cache\Method::class,
80 Page_Cache\Path::class,
81 Page_Cache\Query::class,
82 Page_Cache\Exclusion::class,
83
84 // Third Party Exclusions.
85 Page_Cache\Third_Party_Exclusions\GiveWP::class,
86 Page_Cache\Third_Party_Exclusions\WooCommerce::class,
87
88 Page_Cache\Preload_Response::class,
89 ];
90
91 /**
92 * The pipes to send new requests through before saving them to the cache directory.
93 *
94 * @context after-wordpress
95 *
96 * @since 0.1.0
97 *
98 * @var array<int,string>
99 */
100 private array $save_pipes = [
101 Page_Cache\Response_Code::class,
102 Page_Cache\Constant::class,
103 Page_Cache\Admin::class,
104 Page_Cache\Authenticated::class,
105 Page_Cache\Content_Type::class,
106 Page_Cache\Wp::class,
107 Page_Cache\Method::class,
108 Page_Cache\Path::class,
109 Page_Cache\Post::class,
110 Page_Cache\Exclusion::class,
111 Page_Cache\Query::class,
112
113 // Third Party Exclusions.
114 Page_Cache\Third_Party_Exclusions\GiveWP::class,
115 Page_Cache\Third_Party_Exclusions\WooCommerce::class,
116
117 Page_Cache\Preload_Request::class,
118 ];
119
120 /**
121 * The class constructor.
122 *
123 * @param Request $request The current request.
124 * @param Container $container The DI container.
125 * @param Cache_Handler $handler The Cache Handler.
126 * @param Meta_Manager $meta_manager The meta manager.
127 * @param Header_Sender $header_sender The header sender.
128 *
129 * @since 0.1.0
130 */
131 public function __construct(
132 Request $request,
133 Container $container,
134 Cache_Handler $handler,
135 Meta_Manager $meta_manager,
136 Header_Sender $header_sender
137 ) {
138 $this->request = $request;
139 $this->container = $container;
140 $this->handler = $handler;
141 $this->meta_manager = $meta_manager;
142 $this->header_sender = $header_sender;
143 }
144
145 /**
146 * Returns the cached file to the browser if it exists.
147 *
148 * @since 0.1.0
149 *
150 * @return void
151 */
152 public function serve_cached_file(): void {
153 if ( ! $this->should_serve_cached_file() ) {
154 return;
155 }
156
157 // Cache file doesn't exist or is expired.
158 if ( ! $this->handler->is_valid() ) {
159 return;
160 }
161
162 $cache_file = $this->handler->get_file_path();
163
164 // Get file modification time.
165 $mod_time = filemtime( $cache_file );
166
167 $header = $this->meta_manager->read( $this->request->uri )->headers ?? new Header();
168
169 // Add custom headers.
170 $header->set( 'Last-Modified', gmdate( 'D, d M Y H:i:s', $mod_time ) . ' GMT' );
171 $header->set( 'X-Cache-Age', time() - $mod_time );
172 $header->set( 'X-Cached-By', 'Solid Performance' );
173
174 if ( ! $header->has( 'Vary' ) ) {
175 $header->set( 'Vary', 'Accept-Encoding' );
176 }
177
178 $modified_since = SuperGlobals::get_server_var( 'HTTP_IF_MODIFIED_SINCE', '' );
179 $is_304 = $modified_since && strtotime( $modified_since ) === $mod_time;
180
181 // Cache is current, just respond with 304 headers.
182 if ( $is_304 ) {
183 // No Content-Encoding headers for 304 response.
184 $header->remove( 'Content-Encoding' );
185 $header->set( 'Expires', gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
186 $header->set( 'Cache-Control', 'no-cache, must-revalidate' );
187
188 // Send cached metadata and 304 headers.
189 $this->header_sender->send( $header );
190 header( SuperGlobals::get_server_var( 'SERVER_PROTOCOL', '' ) . ' 304 Not Modified', true, 304 );
191 exit;
192 }
193
194 $encoding = $this->handler->compressor()->encoding();
195
196 // Add Content-Encoding headers for compression (these should not be sent with a 304).
197 if ( $encoding ) {
198 $header->set( 'Content-Encoding', $encoding );
199 }
200
201 // Send cached metadata headers.
202 $this->header_sender->send( $header );
203
204 readfile( $cache_file );
205 exit;
206 }
207
208 /**
209 * Saves content to a new file in the cache.
210 *
211 * @since 0.1.0
212 *
213 * @throws Exception When cache cannot be saved or the cache directory can't be created.
214 *
215 * @param string $output Output to save in the cached file.
216 *
217 * @return void
218 */
219 public function save_output( string $output ): void {
220 // Don't save empty buffers.
221 if ( $output === '' ) {
222 return;
223 }
224
225 // Check if the current request should be cached.
226 if ( ! $this->should_save_cached_file() ) {
227 return;
228 }
229
230 $this->handler->save( $output );
231 }
232
233 /**
234 * Determines if the current request should be served an existing cached page.
235 *
236 * If any pipe returns false, the request will not be served from existing cache.
237 *
238 * @since 0.1.0
239 *
240 * @return bool
241 */
242 private function should_serve_cached_file(): bool {
243 $pipeline = new Pipeline( $this->container );
244 $context = new WP_Context( $this->request );
245
246 return $pipeline
247 ->send( $context )
248 ->through( $this->serve_pipes )
249 ->then(
250 function () {
251 // If we make it through the pipeline, we should cache the request.
252 return true;
253 }
254 );
255 }
256
257 /**
258 * Run through pipeline and determine if the current request should be saved to the cache directory.
259 *
260 * If any middleware return false, the current request will not be cached (or served from existing cache).
261 *
262 * @since 0.1.0
263 *
264 * @return bool
265 */
266 private function should_save_cached_file(): bool {
267 $pipeline = new Pipeline( $this->container );
268 $context = $this->container->get( WP_Context::class );
269
270 return $pipeline
271 ->send( $context )
272 ->through( $this->save_pipes )
273 ->then(
274 function () use ( $context ) {
275 /**
276 * If the request makes it through this pipeline, the request should be saved.
277 *
278 * However, since this is fired within a loaded WordPress context, developers can optionally hook in and determine if the response should be saved.
279 *
280 * @param WP_Context $context The context of the current request.
281 *
282 * @return bool
283 */
284 return apply_filters( 'solidwp/performance/should_save_cached_file', true, $context );
285 }
286 );
287 }
288 }
289