Automattic / Automattic/wp-super-cache
Delete Cache button does not clear the legacy wp-cache files for the URL
- Dominant language
- PHP
- Stars
- 436
- Forks
- 130
- Avg merge
- 15h 11m
- Merged PRs (30d)
- 10
Description
> *This issue was generated by AI.*
The "Delete Cache" button in the admin bar only deletes supercache files. It never touches the legacy wp-cache files, so on any URL those are what serve the page, clicking it appears to do nothing and the visitor keeps getting the stale copy.
Easiest way to see it: load a cached post with a tracking parameter on the end, say `/some-post/?utm_source=test`, and click Delete Cache. Nothing is deleted, and the debug log shows the page served from cache again on the next request. It looks like the button is broken, and from the user's point of view it is.
### Why
Two things combine.
`wp_cache_serve_cache_file()` refuses to serve a supercache file for any URL carrying a query string, at `wp-cache-phase2.php:198`:
```php
} elseif ( wpsc_is_get_query() ) {
wp_cache_debug( 'GET array not empty. Cannot serve a supercache file. ' . wpsc_dump_get_request() );
return false;
}
```
So `/some-post/?utm_source=test` was never being served out of `supercache//some-post/`. What serves it is the legacy wp-cache file, keyed on the full request URI including the query.
And `wpsc_delete_cache_directory()` in `inc/delete-cache-button.php:146` only ever resolves a supercache directory and calls `wpsc_delete_files()` on it. There is no code path in it that deletes a legacy wp-cache file. So the one thing actually serving that request is the one thing the button cannot clear.
This is not specific to query strings. A plain permalink normally has both a supercache file and a legacy wp-cache file, and the button clears the first and leaves the second. It only becomes visible when the legacy file is the one being served, which is what a query string guarantees.
### What it should do
Delete every cache file for that one URL, and nothing below it.
- Deleting the cache on the front page deletes the front page only, not the whole site.
- Deleting the cache on a category page does not delete child categories.
The supercache half already behaves correctly. `wpsc_delete_files()` at `wp-cache-phase2.php:1132` iterates with `is_file()` and never recurses, and its trailing `@rmdir()` fails harmlessly when subdirectories exist, so children survive. That behaviour should be preserved, not replaced with `prune_super_cache()`, which does recurse.
The missing half is the legacy files.
### Suggested fix
The plugin already knows how to do this. `wp_cache_post_change()` at `wp-cache-phase2.php:3594` opens `$blog_cache_dir`, decodes each meta file, and matches on the metadata before unlinking the pair:
```php
$meta = json_decode( wp_cache_get_legacy_cache( $blog_cache_dir . 'meta/' . $file ), true );
...
@unlink( $blog_cache_dir . 'meta/' . $file );
@unlink( $blog_cache_dir . $file );
```
`wpsc_delete_cache_directory()` wants the same scan, matched on the URI rather than on a post ID. `$wp_cache_meta['uri']` is written at `wp-cache-phase2.php:3059` as the host followed by the full request URI:
```php
$wp_cache_meta['uri'] = $WPSC_HTTP_HOST . preg_replace( '/[ <>\'\"\r\n\t\(\)]/', '', $wp_cache_request_uri );
```
Because that includes the query string, one path can have many legacy entries: the bare URL, `?utm_source=a`, `?utm_source=b`, and so on. All of them are the same page and all should go.
So the match is on the **path component only**, compared for equality after the query is removed:
- `/some-post/` matches `/some-post/`, `/some-post/?utm_source=a` and `/some-post/?fbclid=b`
- `/some-post/` does **not** match `/some-post/child/`, which is what keeps children out of it
Worth checking while implementing:
- `$blog_cache_dir` is not `$cache_path` on multisite, so use the global rather than rebuilding the path.
- The gzipped variants and any `.html`-suffixed legacy files in that directory belong to the same URL and should go with it.
- The existing `$_POST['admin'] == 1` branch is the separate "delete the whole site cache" button and should be left alone.
- The nonce is verified against the raw posted path, so it already covers the URL including its query string.
- `wp_cache_confirm_delete()` and the check that the resolved path sits inside the supercache directory guard the directory half. The legacy half needs its own guard, since it is matching on metadata rather than resolving a path, and the value being matched comes from the request.
### Where this came from
Found while testing #1084, which fixed cache clearing for non-ASCII slugs. Not a regression from that PR: the behaviour is identical in 3.1.1 and on trunk, because the old `sanitize_text_field()` call left `?` and `=` alone and `realpath()` failed on the same directory that does not exist. It is a long-standing gap rather than anything new.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in inc/delete-cache-button.php at wpsc_delete_cache_directory(), then compare its directory handling with wp_cache_post_change() in wp-cache-phase2.php. Exercise the described front-page, query-string, category, and child URLs, and verify that matching legacy files and the corresponding supercache file are removed without affecting child paths or whole-site deletion.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100