PluginProbe
SpinupWP / 1.5
SpinupWP v1.5
trunk 1.0 1.0.1 1.0.2 1.0.3 1.1 1.1.1 1.1.2 1.2 1.3 1.4 1.4.1 1.4.2 1.5 1.5.1 1.6 1.7 1.7.1 1.8.0 1.9.0 1.9.1
spinupwp / src / Cache.php

Cache.php in SpinupWP 1.5, at src/Cache.php

431 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SpinupWp;
4
5 use SpinupWp\Cli\CacheCommands;
6 use WP_Post;
7
8 class Cache {
9
10 /**
11 * @var AdminBar
12 */
13 private $admin_bar;
14
15 /**
16 * @var Cli
17 */
18 private $cli;
19
20 /**
21 * @var string
22 */
23 private $cache_path;
24
25 /**
26 * Cache constructor.
27 *
28 * @param AdminBar $admin_bar
29 */
30 public function __construct( AdminBar $admin_bar, Cli $cli ) {
31 $this->admin_bar = $admin_bar;
32 $this->cli = $cli;
33 }
34
35 /**
36 * Init
37 */
38 public function init() {
39 $this->set_cache_path();
40
41 if ( $this->is_object_cache_enabled() && $this->is_page_cache_enabled() ) {
42 $this->admin_bar->add_item( __( 'Purge All Caches', 'spinupwp' ), 'purge-all' );
43 }
44
45 if ( $this->is_object_cache_enabled() ) {
46 $this->admin_bar->add_item( __( 'Purge Object Cache', 'spinupwp' ), 'purge-object' );
47 }
48
49 if ( $this->is_page_cache_enabled() ) {
50 $this->admin_bar->add_item( __( 'Purge Page Cache', 'spinupwp' ), 'purge-page' );
51 $this->cli->register_command( 'spinupwp cache', CacheCommands::class );
52 }
53
54 add_action( 'spinupwp_purge_object_cache', array( $this, 'purge_object_cache' ) );
55 add_action( 'spinupwp_purge_page_cache', array( $this, 'purge_page_cache' ) );
56 add_action( 'admin_init', array( $this, 'handle_manual_purge_action' ) );
57 add_action( 'transition_post_status', array( $this, 'purge_post_on_update' ), 10, 3 );
58 add_action( 'delete_post', array( $this, 'purge_post_on_delete' ), 10, 1 );
59 add_action( 'switch_theme', array( $this, 'purge_page_cache' ) );
60 add_action( 'comment_post', array( $this, 'purge_post_on_comment' ), 10, 2 );
61 add_action( 'wp_set_comment_status', array( $this, 'purge_post_by_comment' ) );
62 add_action( 'upgrader_process_complete', array( $this, 'purge_page_cache_on_shutdown' ) );
63 }
64
65
66 /**
67 * Handle manual purge actions.
68 */
69 public function handle_manual_purge_action() {
70 if ( ! current_user_can( apply_filters( 'spinupwp_purge_cache_capability', 'manage_options' ) ) ) {
71 return;
72 }
73
74 $action = filter_input( INPUT_GET, 'spinupwp_action' );
75
76 if ( ! $action || ! in_array( $action, array( 'purge-all', 'purge-object', 'purge-page' ) ) ) {
77 return;
78 }
79
80 if ( ! wp_verify_nonce( filter_input( INPUT_GET, '_wpnonce' ), $action ) ) {
81 return;
82 }
83
84 if ( 'purge-all' === $action ) {
85 $purge = $this->purge_object_cache() && $this->purge_page_cache();
86 $type = 'all';
87 }
88
89 if ( 'purge-object' === $action ) {
90 $purge = $this->purge_object_cache();
91 $type = 'object';
92 }
93
94 if ( 'purge-page' === $action ) {
95 $purge = $this->purge_page_cache();
96 $type = 'page';
97 }
98
99 $redirect_url = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : admin_url();
100
101 wp_safe_redirect( add_query_arg( array(
102 'purge_success' => (int) $purge,
103 'cache_type' => $type,
104 ), $redirect_url ) );
105 }
106
107 /**
108 * When a post is transitioned to 'publish' for the first time purge the
109 * entire site cache. This ensures blog pages, category archives, author archives,
110 * search results and the 'Latest Posts' footer section is accurate. Otherwise,
111 * only update the current post URL.
112 *
113 * @param string $new_status
114 * @param string $old_status
115 * @param WP_Post $post
116 *
117 * @return bool
118 */
119 public function purge_post_on_update( $new_status, $old_status, $post ) {
120 $post_type = get_post_type( $post );
121
122 if ( ! in_array( $post_type, $this->get_public_post_types() ) ) {
123 return false;
124 }
125
126 if ( in_array( $post_type, $this->get_post_types_excluded_from_purge() ) ) {
127 return false;
128 }
129
130 if ( ! $this->should_purge_post_status( $new_status, $old_status ) ) {
131 return false;
132 }
133
134 if ( $post_type === 'customize_changeset' && $new_status === 'trash' ) {
135 return false;
136 }
137
138 if ( in_array( $post_type, $this->get_post_types_needing_single_purge() ) ) {
139 return $this->purge_post( $post );
140 }
141
142 return $this->purge_page_cache();
143 }
144
145 /**
146 * Purge the entire cache when a post type is deleted.
147 *
148 * @param int $post_id
149 *
150 * @return bool
151 */
152 public function purge_post_on_delete( $post_id ) {
153 $post_type = get_post_type( $post_id );
154
155 if ( ! in_array( $post_type, $this->get_public_post_types() ) ) {
156 return false;
157 }
158
159 if ( in_array( $post_type, $this->get_post_types_excluded_from_purge() ) ) {
160 return false;
161 }
162
163 $post_status = get_post_status( $post_id );
164
165 if ( in_array( $post_status, array( 'auto-draft', 'draft', 'trash' ) ) ) {
166 return false;
167 }
168
169 return $this->purge_page_cache();
170 }
171
172 /**
173 * Purge a post on new comment (if approved).
174 *
175 * @param int $comment_id
176 * @param bool $comment_approved
177 *
178 * @return bool
179 */
180 public function purge_post_on_comment( $comment_id, $comment_approved ) {
181 if ( ! $comment_approved ) {
182 return false;
183 }
184
185 return $this->purge_post_by_comment( $comment_id );
186 }
187
188 /**
189 * Purge a post by comment ID.
190 *
191 * @param int $comment_id
192 *
193 * @return bool
194 */
195 public function purge_post_by_comment( $comment_id ) {
196 $comment = get_comment( $comment_id );
197
198 if ( $comment && $comment->comment_post_ID ) {
199 $post = get_post( $comment->comment_post_ID );
200
201 return $this->purge_post( $post );
202 }
203
204 return false;
205 }
206
207 /**
208 * Purge the page cache on shutdown.
209 */
210 public function purge_page_cache_on_shutdown() {
211 add_action( 'shutdown', array( $this, 'purge_page_cache' ) );
212 }
213
214 /**
215 * Set the base cache path.
216 */
217 private function set_cache_path() {
218 if ( getenv( 'SPINUPWP_CACHE_PATH' ) ) {
219 $this->cache_path = getenv( 'SPINUPWP_CACHE_PATH' );
220 }
221
222 if ( defined( 'SPINUPWP_CACHE_PATH' ) ) {
223 $this->cache_path = SPINUPWP_CACHE_PATH;
224 }
225 }
226
227 /**
228 * Is the object cache enabled?
229 *
230 * @return bool
231 */
232 public function is_object_cache_enabled() {
233 return wp_using_ext_object_cache();
234 }
235
236 /**
237 * Is the page cache enabled?
238 *
239 * @return bool
240 */
241 public function is_page_cache_enabled() {
242 return defined( 'SPINUPWP_CACHE_PATH' ) || getenv( 'SPINUPWP_CACHE_PATH' );
243 }
244
245 /**
246 * Purge entire object cache.
247 *
248 * @return bool
249 */
250 public function purge_object_cache() {
251 return wp_cache_flush();
252 }
253
254 /**
255 * Purge entire cache.
256 *
257 * @return bool
258 */
259 public function purge_page_cache() {
260 $result = $this->delete( $this->cache_path, true );
261
262 do_action( 'spinupwp_site_purged', $result );
263
264 return $result;
265 }
266
267 /**
268 * Purge the current post URL.
269 *
270 * @param WP_Post $post
271 *
272 * @return bool
273 */
274 public function purge_post( $post ) {
275 $result = $this->purge_url( get_permalink( $post ) );
276
277 do_action( 'spinupwp_post_purged', $post, $result );
278
279 return $result;
280 }
281
282 /**
283 * Purge a single URL from the cache.
284 *
285 * @param string $url
286 *
287 * @return bool
288 */
289 public function purge_url( $url ) {
290 $path = $this->get_cache_path_for_url( $url );
291 $result = $this->delete( $path );
292
293 do_action( 'spinupwp_url_purged', $url, $result );
294
295 return $result;
296 }
297
298 /**
299 * Get's the cache file path for a given URL.
300 *
301 * Must be using the default Nginx cache options (levels=1:2)
302 * and (fastcgi_cache_key "$scheme$request_method$host$request_uri").
303 * https://www.digitalocean.com/community/tutorials/how-to-setup-fastcgi-caching-with-nginx-on-your-vps#purging-the-cache
304 *
305 * @param string $url
306 *
307 * @return string
308 */
309 private function get_cache_path_for_url( $url ) {
310 $parsed_url = parse_url( trailingslashit( $url ) );
311 $cache_key = md5( $parsed_url['scheme'] . 'GET' . $parsed_url['host'] . $parsed_url['path'] );
312 $cache_path = substr( $cache_key, - 1 ) . '/' . substr( $cache_key, - 3, 2 ) . '/' . $cache_key;
313
314 return trailingslashit( $this->cache_path ) . $cache_path;
315 }
316
317 /**
318 * Delete a file/dir from the local filesystem.
319 *
320 * @param string $path Absolute path to file
321 * @param bool $recursive
322 *
323 * @return bool
324 */
325 private function delete( $path, $recursive = false ) {
326 if ( file_exists( $path ) && is_writable( $path ) ) {
327 require_once( ABSPATH . '/wp-admin/includes/file.php' );
328
329 $context = $path;
330 if ( is_file( $path ) ) {
331 $context = dirname( $path );
332 }
333
334 if ( ! WP_Filesystem( false, $context, true ) ) {
335 return false;
336 }
337
338 global $wp_filesystem;
339 $wp_filesystem->delete( $path, $recursive );
340
341 return true;
342 }
343
344 return $this->delete_via_cache_daemon( $path );
345 }
346
347 /**
348 * Use SpinupWP daemon to purge cache.
349 *
350 * @param string $path
351 *
352 * @return bool
353 */
354 private function delete_via_cache_daemon( $path ) {
355 $fp = @fsockopen( '127.0.0.1', '7836' );
356
357 if ( $fp ) {
358 fwrite( $fp, $path . "\n" );
359
360 $result = fread( $fp, 512 );
361 fclose( $fp );
362
363 return (bool) preg_match( '/^Purge:\sSuccess/', $result );
364 }
365
366 return false;
367 }
368
369 /**
370 * Should a post be purged based on the new/old status.
371 *
372 * @param string $new_status
373 * @param string $old_status
374 *
375 * @return bool
376 */
377 private function should_purge_post_status( $new_status, $old_status ) {
378 // A newly created post with no content
379 if ( $new_status === 'auto-draft' ) {
380 return false;
381 }
382
383 // A post in draft status
384 if ( $new_status === 'draft' && in_array( $old_status, array( 'auto-draft', 'draft', 'trash' ) ) ) {
385 return false;
386 }
387
388 // A post in trash status
389 if ( $new_status === 'trash' && in_array( $old_status, array( 'auto-draft', 'draft', 'trash' ) ) ) {
390 return false;
391 }
392
393 return apply_filters( 'spinupwp_should_purge_post_status', true );
394 }
395
396 /**
397 * Get public post types that should trigger a cache purge.
398 *
399 * @return array
400 */
401 private function get_public_post_types() {
402 $post_types = get_post_types( [
403 'public' => true,
404 ] );
405
406 return apply_filters( 'spinupwp_public_post_types', $post_types );
407 }
408
409 /**
410 * Get post types that should only purge their own public facing URL.
411 *
412 * @return array
413 */
414 private function get_post_types_needing_single_purge() {
415 return apply_filters( 'spinupwp_post_types_needing_single_purge', array() );
416 }
417
418 /**
419 * Get post types that should never trigger a cache purge.
420 *
421 * @return array
422 */
423 private function get_post_types_excluded_from_purge() {
424 return apply_filters( 'spinupwp_post_types_excluded_from_purge', array(
425 'attachment',
426 'custom_css',
427 'revision',
428 'user_request'
429 ) );
430 }
431 }