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