Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 152 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
CachePurgingService | |
0.00% |
0 / 152 |
|
0.00% |
0 / 10 |
2070 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
can_purge | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
manual_purge_request | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
42 | |||
purge_all | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
purge_url | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
on_save_post | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
110 | |||
on_edit_term | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
on_update_comment | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
on_update_option | |
0.00% |
0 / 86 |
|
0.00% |
0 / 1 |
90 | |||
is_public_taxonomy | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace NewfoldLabs\WP\Module\Performance\Cache; |
4 | |
5 | use NewfoldLabs\WP\Module\Performance\Performance; |
6 | use NewfoldLabs\WP\Module\Performance\Cache\Types\CacheBase; |
7 | use wpscholar\Url; |
8 | |
9 | use function NewfoldLabs\WP\Module\Performance\to_studly_case; |
10 | use function NewfoldLabs\WP\Module\Performance\to_snake_case; |
11 | |
12 | /** |
13 | * Cache purging service. |
14 | */ |
15 | class CachePurgingService { |
16 | |
17 | /** |
18 | * Define cache types. |
19 | * |
20 | * @var array|CacheBase[] $cache_types Cache types. |
21 | */ |
22 | public $cache_types = array(); |
23 | |
24 | /** |
25 | * Constructor. |
26 | * |
27 | * @param CacheBase[] $cache_types Cache types. |
28 | */ |
29 | public function __construct( array $cache_types ) { |
30 | |
31 | $this->cache_types = $cache_types; |
32 | |
33 | if ( $this->can_purge() ) { |
34 | |
35 | // Handle manual purge requests |
36 | add_action( 'init', array( $this, 'manual_purge_request' ) ); |
37 | |
38 | // Handle automatic purging |
39 | add_action( 'transition_post_status', array( $this, 'on_save_post' ), 10, 3 ); |
40 | add_action( 'edit_terms', array( $this, 'on_edit_term' ) ); |
41 | add_action( 'comment_post', array( $this, 'on_update_comment' ) ); |
42 | add_action( 'updated_option', array( $this, 'on_update_option' ), 10, 3 ); |
43 | add_action( 'wp_update_nav_menu', array( $this, 'purge_all' ) ); |
44 | |
45 | } |
46 | } |
47 | |
48 | /** |
49 | * Check if the cache can be purged. |
50 | * |
51 | * @return bool |
52 | */ |
53 | public function can_purge() { |
54 | foreach ( $this->cache_types as $instance ) { |
55 | if ( array_key_exists( Purgeable::class, class_implements( $instance ) ) ) { |
56 | return true; |
57 | } |
58 | } |
59 | |
60 | return false; |
61 | } |
62 | |
63 | /** |
64 | * Listens for purge actions and handles based on type. |
65 | */ |
66 | public function manual_purge_request() { |
67 | |
68 | $purge_all = Performance::PURGE_ALL; |
69 | $purge_url = Performance::PURGE_URL; |
70 | |
71 | if ( ( isset( $_GET[ $purge_all ] ) || isset( $_GET[ $purge_url ] ) ) && is_user_logged_in() && current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification |
72 | |
73 | $url = new Url(); |
74 | $url->removeQueryVar( $purge_all ); |
75 | $url->removeQueryVar( $purge_url ); |
76 | |
77 | if ( isset( $_GET[ $purge_all ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification |
78 | $this->purge_all(); |
79 | } else { |
80 | $this->purge_url( Url::stripQueryString( $url ) ); |
81 | } |
82 | wp_safe_redirect( |
83 | $url, |
84 | 302, |
85 | 'Newfold File Caching' |
86 | ); |
87 | exit; |
88 | } |
89 | } |
90 | |
91 | /** |
92 | * Purge everything. |
93 | */ |
94 | public function purge_all() { |
95 | foreach ( $this->cache_types as $instance ) { |
96 | if ( array_key_exists( Purgeable::class, class_implements( $instance ) ) ) { |
97 | /** |
98 | * Purgeable instance. |
99 | * |
100 | * @var Purgeable $instance |
101 | */ |
102 | $instance->purge_all(); |
103 | } |
104 | } |
105 | } |
106 | |
107 | /** |
108 | * Purge a specific URL. |
109 | * |
110 | * @param string $url The URL to be purged. |
111 | */ |
112 | public function purge_url( $url ) { |
113 | foreach ( $this->cache_types as $instance ) { |
114 | if ( array_key_exists( Purgeable::class, class_implements( $instance ) ) ) { |
115 | /** |
116 | * Purgeable instance. |
117 | * |
118 | * @var Purgeable $instance |
119 | */ |
120 | $instance->purge_url( $url ); |
121 | } |
122 | } |
123 | } |
124 | |
125 | /** |
126 | * Purge appropriate caches when a post is updated. |
127 | * |
128 | * @param string $oldStatus The previous post status |
129 | * @param string $newStatus The new post status |
130 | * @param \WP_Post $post The post object of the edited or created post |
131 | */ |
132 | public function on_save_post( $oldStatus, $newStatus, \WP_Post $post ) { |
133 | |
134 | // Skip purging for non-public post types |
135 | if ( ! get_post_type_object( $post->post_type )->public ) { |
136 | return; |
137 | } |
138 | |
139 | // Skip purging if the post wasn't public before and isn't now |
140 | if ( 'publish' !== $oldStatus && 'publish' !== $newStatus ) { |
141 | return; |
142 | } |
143 | |
144 | // Purge post URL when post is updated. |
145 | $permalink = get_permalink( $post ); |
146 | if ( $permalink ) { |
147 | $this->purge_url( $permalink ); |
148 | } |
149 | |
150 | // Purge taxonomy term URLs for related terms. |
151 | $taxonomies = get_post_taxonomies( $post ); |
152 | foreach ( $taxonomies as $taxonomy ) { |
153 | if ( $this->is_public_taxonomy( $taxonomy ) ) { |
154 | $terms = get_the_terms( $post, $taxonomy ); |
155 | if ( is_array( $terms ) ) { |
156 | foreach ( $terms as $term ) { |
157 | $term_link = get_term_link( $term ); |
158 | $this->purge_url( $term_link ); |
159 | } |
160 | } |
161 | } |
162 | } |
163 | |
164 | // Purge post type archive URL when post is updated. |
165 | $post_type_archive = get_post_type_archive_link( $post->post_type ); |
166 | if ( $post_type_archive ) { |
167 | $this->purge_url( $post_type_archive ); |
168 | } |
169 | |
170 | // Purge date archive URL when post is updated. |
171 | $year_archive = get_year_link( (int) get_the_date( 'y', $post ) ); |
172 | $this->purge_url( $year_archive ); |
173 | } |
174 | |
175 | /** |
176 | * Purge taxonomy term URL when a term is updated. |
177 | * |
178 | * @param int $termId Term ID |
179 | */ |
180 | public function on_edit_term( $termId ) { |
181 | $url = get_term_link( $termId ); |
182 | if ( ! is_wp_error( $url ) ) { |
183 | $this->purge_url( $url ); |
184 | } |
185 | } |
186 | |
187 | /** |
188 | * Purge a single post when a comment is updated. |
189 | * |
190 | * @param int $commentId ID of the comment. |
191 | */ |
192 | public function on_update_comment( $commentId ) { |
193 | $comment = get_comment( $commentId ); |
194 | if ( $comment && property_exists( $comment, 'comment_post_ID' ) ) { |
195 | $postUrl = get_permalink( $comment->comment_post_ID ); |
196 | if ( $postUrl ) { |
197 | $this->purge_url( $postUrl ); |
198 | } |
199 | } |
200 | } |
201 | |
202 | /** |
203 | * Purge all caches when an option is updated. |
204 | * |
205 | * @param string $option Option name. |
206 | * @param mixed $oldValue Old option value. |
207 | * @param mixed $newValue New option value. |
208 | * |
209 | * @return bool |
210 | */ |
211 | public function on_update_option( $option, $oldValue, $newValue ) { |
212 | // No need to process if nothing was updated |
213 | if ( $oldValue === $newValue ) { |
214 | return false; |
215 | } |
216 | |
217 | $exemptIfEquals = array( |
218 | 'active_plugins' => true, |
219 | 'html_type' => true, |
220 | 'fs_accounts' => true, |
221 | 'rewrite_rules' => true, |
222 | 'uninstall_plugins' => true, |
223 | 'wp_user_roles' => true, |
224 | ); |
225 | |
226 | // If we have an exact match, we can just stop here. |
227 | if ( array_key_exists( $option, $exemptIfEquals ) ) { |
228 | return false; |
229 | } |
230 | |
231 | $forceIfContains = array( |
232 | 'html', |
233 | 'css', |
234 | 'style', |
235 | 'query', |
236 | 'queries', |
237 | ); |
238 | |
239 | $exemptIfContains = array( |
240 | '_active', |
241 | '_activated', |
242 | '_activation', |
243 | '_attempts', |
244 | '_available', |
245 | '_blacklist', |
246 | '_cache_validator', |
247 | '_check_', |
248 | '_checksum', |
249 | '_config', |
250 | '_count', |
251 | '_dectivated', |
252 | '_disable', |
253 | '_enable', |
254 | '_errors', |
255 | '_hash', |
256 | '_inactive', |
257 | '_installed', |
258 | '_key', |
259 | '_last_', |
260 | '_license', |
261 | '_log_', |
262 | '_mode', |
263 | '_options', |
264 | '_pageviews', |
265 | '_redirects', |
266 | '_rules', |
267 | '_schedule', |
268 | '_session', |
269 | '_settings', |
270 | '_shown', |
271 | '_stats', |
272 | '_status', |
273 | '_statistics', |
274 | '_supports', |
275 | '_sync', |
276 | '_task', |
277 | '_time', |
278 | '_token', |
279 | '_traffic', |
280 | '_transient', |
281 | '_url_', |
282 | '_version', |
283 | '_views', |
284 | '_visits', |
285 | '_whitelist', |
286 | '404s', |
287 | 'cron', |
288 | 'limit_login_', |
289 | 'nonce', |
290 | 'user_roles', |
291 | ); |
292 | |
293 | $force_purge = false; |
294 | |
295 | if ( ctype_upper( str_replace( array( '-', '_' ), '', $option ) ) ) { |
296 | $option = strtolower( $option ); |
297 | } |
298 | $option_name = '_' . to_snake_case( to_studly_case( $option ) ) . '_'; |
299 | |
300 | foreach ( $forceIfContains as $slug ) { |
301 | if ( false !== strpos( $option_name, $slug ) ) { |
302 | $force_purge = true; |
303 | break; |
304 | } |
305 | } |
306 | |
307 | if ( ! $force_purge ) { |
308 | foreach ( $exemptIfContains as $slug ) { |
309 | if ( false !== strpos( $option_name, $slug ) ) { |
310 | return false; |
311 | } |
312 | } |
313 | } |
314 | |
315 | $this->purge_all(); |
316 | |
317 | return true; |
318 | } |
319 | |
320 | /** |
321 | * Checks if a taxonomy is public. |
322 | * |
323 | * @param string $taxonomy Taxonomy name. |
324 | * |
325 | * @return boolean |
326 | */ |
327 | protected function is_public_taxonomy( $taxonomy ) { |
328 | $public = false; |
329 | $taxonomy_object = get_taxonomy( $taxonomy ); |
330 | if ( $taxonomy_object && isset( $taxonomy_object->public ) ) { |
331 | $public = $taxonomy_object->public; |
332 | } |
333 | |
334 | return $public; |
335 | } |
336 | } |