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

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