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

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