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

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