PluginProbe
SpinupWP / 1.7.1
SpinupWP v1.7.1
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.7.1, at src/Cache.php

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