PluginProbe
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution / 1.8.0
Solid Performance – Your No-Code Caching, Performance, & Page Speed Solution v1.8.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.8.0, at src/Performance/Page_Cache/Cache.php

297 lines 7.2 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-Cache', 'HIT' );
173 $header->set( 'X-Cached-By', 'Solid Performance' );
174
175 if ( ! $header->has( 'Vary' ) ) {
176 $header->set( 'Vary', 'Accept-Encoding' );
177 }
178
179 $modified_since = SuperGlobals::get_server_var( 'HTTP_IF_MODIFIED_SINCE', '' );
180 $is_304 = $modified_since && strtotime( $modified_since ) === $mod_time;
181
182 // Cache is current, just respond with 304 headers.
183 if ( $is_304 ) {
184 // No Content-Encoding headers for 304 response.
185 $header->remove( 'Content-Encoding' );
186 $header->set( 'Expires', gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
187 $header->set( 'Cache-Control', 'no-cache, must-revalidate' );
188
189 // Send cached metadata and 304 headers.
190 $this->header_sender->send( $header );
191 header( SuperGlobals::get_server_var( 'SERVER_PROTOCOL', '' ) . ' 304 Not Modified', true, 304 );
192 exit;
193 }
194
195 $encoding = $this->handler->compressor()->encoding();
196
197 // Add Content-Encoding headers for compression (these should not be sent with a 304).
198 if ( $encoding ) {
199 $header->set( 'Content-Encoding', $encoding );
200 }
201
202 // Send cached metadata headers.
203 $this->header_sender->send( $header );
204
205 readfile( $cache_file );
206 exit;
207 }
208
209 /**
210 * Saves content to a new file in the cache.
211 *
212 * @since 0.1.0
213 *
214 * @throws Exception When cache cannot be saved or the cache directory can't be created.
215 *
216 * @param string $output Output to save in the cached file.
217 *
218 * @return void
219 */
220 public function save_output( string $output ): void {
221 // Don't save empty buffers.
222 if ( $output === '' ) {
223 return;
224 }
225
226 // Check if the current request should be cached.
227 if ( ! $this->should_save_cached_file() ) {
228 return;
229 }
230
231 $this->handler->save( $output );
232
233 /*
234 * If we're saving a cache file, this is going to be a cache miss.
235 */
236 if ( ! headers_sent() ) {
237 header( 'X-Cache: MISS' );
238 }
239 }
240
241 /**
242 * Determines if the current request should be served an existing cached page.
243 *
244 * If any pipe returns false, the request will not be served from existing cache.
245 *
246 * @since 0.1.0
247 *
248 * @return bool
249 */
250 private function should_serve_cached_file(): bool {
251 $pipeline = new Pipeline( $this->container );
252 $context = new WP_Context( $this->request );
253
254 return $pipeline
255 ->send( $context )
256 ->through( $this->serve_pipes )
257 ->then(
258 function () {
259 // If we make it through the pipeline, we should cache the request.
260 return true;
261 }
262 );
263 }
264
265 /**
266 * Run through pipeline and determine if the current request should be saved to the cache directory.
267 *
268 * If any middleware return false, the current request will not be cached (or served from existing cache).
269 *
270 * @since 0.1.0
271 *
272 * @return bool
273 */
274 private function should_save_cached_file(): bool {
275 $pipeline = new Pipeline( $this->container );
276 $context = $this->container->get( WP_Context::class );
277
278 return $pipeline
279 ->send( $context )
280 ->through( $this->save_pipes )
281 ->then(
282 function () use ( $context ) {
283 /**
284 * If the request makes it through this pipeline, the request should be saved.
285 *
286 * However, since this is fired within a loaded WordPress context, developers can optionally hook in and determine if the response should be saved.
287 *
288 * @param WP_Context $context The context of the current request.
289 *
290 * @return bool
291 */
292 return apply_filters( 'solidwp/performance/should_save_cached_file', true, $context );
293 }
294 );
295 }
296 }
297