chesio / bc-cache
Simple disk cache plugin inspired by Cachify.
Installs: 3
Dependents: 0
Suggesters: 0
Security: 0
Stars: 9
Watchers: 2
Forks: 0
Open Issues: 1
Type:wordpress-plugin
Requires
- php: ^8.1
- composer/installers: ^1.0 || ^2.0
Requires (Dev)
- brain/monkey: ^2.6
- mockery/mockery: ^1.6
- php-parallel-lint/php-parallel-lint: ^1.3
- php-stubs/wp-cli-stubs: ^2.8
- phpunit/phpunit: ^10.5
- squizlabs/php_codesniffer: ^3.3
- szepeviktor/phpstan-wordpress: ^1.3
This package is auto-updated.
Last update: 2024-10-28 16:35:24 UTC
README
Modern and simple full page cache plugin for WordPress inspired by Cachify.
BC Cache has no settings page - it is intended for webmasters who are familiar with .htaccess
files and WordPress actions and filters.
BC Cache can cache not only HTML pages, but core XML sitemaps as well. Technically, any content type generated by WordPress for front-end requests routed via root index.php
file can be handled provided that output generation triggers send_headers
hook.
Requirements
- Apache webserver with mod_rewrite enabled
- PHP 8.1 or newer
- WordPress 6.2 or newer with pretty permalinks on
Limitations
- BC Cache has not been tested on WordPress multisite installation.
- BC Cache has not been tested on Windows servers.
- BC Cache does not support content negotiation via
Accept
header.
Installation
BC Cache is not available at WordPress Plugins Directory, but there are several other ways you can get it.
Using WP-CLI
If you have WP-CLI installed, you can install (and optionally activate) BC Cache with a single command:
wp plugin install [--activate] https://github.com/chesio/bc-cache/archive/master.zip
Using Composer
Composer is a great tool for managing PHP project dependencies. Although WordPress itself does not make it easy to use Composer to manage WordPress installation as a whole, there are multiple ways how to do it.
BC Cache is available at Packagist, so just run composer require chesio/bc-cache
as usual.
Using Git
Master branch always contains latest stable version, so you can install BC Cache by cloning it from within your plugins directory:
cd [your-project]/wp-content/plugins
git clone --single-branch --branch master https://github.com/chesio/bc-cache.git
Updating is as easy as:
cd [your-project]/wp-content/plugins/bc-cache
git pull
Using Git Updater plugin
Once installed, BC Cache can be kept up to date via Git Updater plugin. To install it either use the direct download method described below or use Git Updater Pro.
Direct download
This method is the least recommended, but it works without any other tool. You can download BC Cache directly from GitHub. Make sure to unpack the plugin into correct directory and drop the version number from folder name.
Setup
You have to configure your Apache webserver to serve cached files. Most common way to do it is to add the lines below to the root .htaccess
file. This is the same file to which WordPress automatically writes pretty permalinks configuration - you must put the lines below before the pretty permalinks configuration.
Note: the configuration below assumes that you have WordPress installed in wordpress
subdirectory - if it is not your case, simply drop the /wordpress
part from the following rule: RewriteRule .* - [E=BC_CACHE_ROOT:%{DOCUMENT_ROOT}/wordpress]
. In general, you may need to make some tweaks to the example configuration below to fit your server environment.
# BEGIN BC Cache AddDefaultCharset utf-8 <IfModule mod_rewrite.c> RewriteEngine on # Configure document root. RewriteRule .* - [E=BC_CACHE_ROOT:%{DOCUMENT_ROOT}/wordpress] # Get request scheme (either http or https). RewriteRule .* - [E=BC_CACHE_SCHEME:http] RewriteCond %{ENV:HTTPS} =on [OR] RewriteCond %{HTTP:X-Forwarded-Proto} https RewriteRule .* - [E=BC_CACHE_SCHEME:https] # Clean up hostname (drop optional port number). RewriteCond %{HTTP_HOST} ^([^:]+)(:[0-9]+)?$ RewriteRule .* - [E=BC_CACHE_HOST:%1] # Set path subdirectory - its suffix depends on whether URI ends with slash or not. RewriteRule .* - [E=BC_CACHE_PATH:%{REQUEST_URI}/@file/] RewriteCond %{REQUEST_URI} /$ RewriteRule .* - [E=BC_CACHE_PATH:%{REQUEST_URI}@dir/] # Set request variant (by default there is only empty one). RewriteRule .* - [E=BC_CACHE_REQUEST_VARIANT:] # Optionally, serve gzipped version of the file. RewriteRule .* - [E=BC_CACHE_FILE:index%{ENV:BC_CACHE_REQUEST_VARIANT}] <IfModule mod_mime.c> RewriteCond %{HTTP:Accept-Encoding} gzip RewriteRule .* - [E=BC_CACHE_FILE:index%{ENV:BC_CACHE_REQUEST_VARIANT}.gz] </IfModule> # Main rules: serve only GET requests with whitelisted query string fields coming from anonymous users. RewriteCond %{REQUEST_METHOD} GET RewriteCond %{QUERY_STRING} ^(?:(?:_gl|gclid|gclsrc|fbclid|msclkid|utm_(?:source|medium|campaign|term|content))=[\w\-]*(?:&|$))*$ RewriteCond %{HTTP_COOKIE} !(wp-postpass|wordpress_logged_in|comment_author)_ RewriteCond %{ENV:BC_CACHE_ROOT}/wp-content/cache/bc-cache/%{ENV:BC_CACHE_SCHEME}_%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} -f RewriteRule .* %{ENV:BC_CACHE_ROOT}/wp-content/cache/bc-cache/%{ENV:BC_CACHE_SCHEME}_%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} [L,NS] # Do not allow direct access to cache entries. RewriteCond %{REQUEST_URI} /wp-content/cache/bc-cache/ RewriteCond %{ENV:REDIRECT_STATUS} ^$ RewriteRule .* - [F,L] </IfModule> # END BC Cache
Configuration
BC Cache has no settings. You can modify plugin behavior with PHP constants, WordPress filters and theme features.
Constants
Two advanced features of the plugin can be optionally disabled with a constant.
The cache warm up feature can be disabled by setting BC_CACHE_WARM_UP_ENABLED
constant with a false
value:
define('BC_CACHE_WARM_UP_ENABLED', false);
File locking is used to ensure atomicity of operations that manipulate the cache. If your webserver has issues with flock() you may want to disable use of file locking by setting BC_CACHE_FILE_LOCKING_ENABLED
constant with a false
value:
define('BC_CACHE_FILE_LOCKING_ENABLED', false);
Both constant must be defined at the time the plugin boots - typically the best place to define them is wp-config.php
file. It is recommended to set the constants before activating the plugin.
Filters
If there was a settings page, following filters would likely become plugin settings as they alter basic functionality:
bc-cache/filter:can-user-flush-cache
- filters whether current user can clear the cache. By default, any user withmanage_options
capability can clear the cache.bc-cache/filter:html-signature
- filters HTML signature appended to HTML files stored in cache. You can use this filter to get rid of the signature:add_filter('bc-cache/filter:html-signature', '__return_empty_string');
bc-cache/filter:cached-response-headers
- filters HTTP response headers generated by WordPress/PHP that are kept when cache entry is created. These headers are then sent as additional response headers with cached content. By default following headers are kept:Content-Type
,Link
,X-Pingback
,X-Robots-Tag
,X-BC-Cache-Generated
. Note thatContent-Type
header is crucial for proper delivery of cached content and thus is always preserved.bc-cache/filter:cache-generation-timestamp-format
- filters format of cache generation timestamp. This timestamp is included in HTML signature and inX-BC-Cache-Generated
HTTP response header. The value must be a valid$format
argument forwp_date()
.
Filters for advanced functions
Following filters can be used to tweak automatic cache flushing:
bc-cache/filter:flush-hooks
- filters list of actions that trigger cache flushing. Filter is executed in a hook registered toinit
action with priority 10, so make sure to register your hook earlier (for example withinplugins_loaded
orafter_setup_theme
actions).bc-cache/filter:is-public-post-type
- filters whether given post type should be deemed as public or not. Publishing or trashing of public post type items triggers automatic cache flushing, but related action hooks cannot be adjusted with thebc-cache/filter:flush-hooks
filter, you have to use this filter.bc-cache/filter:is-public-taxonomy
- filters whether given taxonomy should be deemed as public or not. Creating, deleting or editing terms from public taxonomy triggers automatic cache flushing, but related action hooks cannot be adjusted with thebc-cache/filter:flush-hooks
filter, you have to use this filter.
Following filters can be used to extend list of cache exclusions or whitelist some query string parameters:
bc-cache/filter:skip-cache
- filters whether response to current HTTP(S) request should be cached. Filter is only executed, when none from built-in skip rules is matched - this means that you cannot override built-in skip rules with this filter, only add your own rules.bc-cache/filter:query-string-fields-whitelist
- filters list of query string fields that do not prevent cache write.
Following filters are necessary to set up request variants:
bc-cache/filter:request-variant
- filters name of request variant of current HTTP request.bc-cache/filter:request-variants
- filters list of all available request variants. You should use this filter, if you use variants and want to have complete and proper information about cache entries listed in Cache Viewer.
Following filters can be used to tweak warming up of cache:
bc-cache/filter:cache-warm-up-initial-url-list
- filters list of initial URLs to be included in warm up. This filter is used to shortcut default processing: if it returns an array (even empty), no URLs are read from XML sitemap(s).bc-cache/filter:cache-warm-up-final-url-list
- filters the final list of URLs to be included in warm up.bc-cache/filter:cache-warm-up-invocation-delay
- filters the time (in seconds) between cache flush and warm up invocation.bc-cache/filter:cache-warm-up-run-timeout
- sets the time (in seconds) warm up crawler is allowed to run within single WP-Cron invocation. The value cannot be larger than value ofWP_CRON_LOCK_TIMEOUT
constant. Note that crawler stops only after this limit is reached. This means for example that even if the timeout is set to0
, there is one HTTP request sent.bc-cache/filter:cache-warm-up-request-arguments
- filters list of arguments of HTTP request run during warm up.
Following filters are only useful if your theme declares support for caching for front-end users:
bc-cache/filter:frontend-user-capabilities
- filters list of capabilities of front-end users.bc-cache/filter:is-frontend-user
- filters whether current user is a front-end user.bc-cache/filter:frontend-user-cookie-name
- filters name of front-end user cookie.bc-cache/filter:frontend-user-cookie-value
- filters contents of front-end user cookie.
Theme features
Some advanced features must be supported by your theme and are active only if the theme explicitly declares its support for particular feature:
add_theme_support('bc-cache', 'caching-for-frontend-users');
- activates caching for front-end users.
Automatic cache flushing
The cache is flushed automatically on core actions listed below. The list of actions can be filtered with bc-cache/filter:flush-hooks
filter.
-
WordPress gets updated:
-
Frontend changes:
-
Post state changes from publish to another one (except trash). Note: publish and trash related actions are handled separately and for public posts only - see below):
-
Comment changes:
-
Site widgets configuration changes:
update_option_sidebars_widgets
- the configuration is saved insidebars_widgets
option, so cache is flushed whenever this option is updated.
Special handling of posts and terms
In WordPress, posts can be used to hold various types of data - including data that is not presented on frontend in any way. To make cache flushing as sensible as possible, when a post is published or trashed the cache is flushed only when post type is public. You may use bc-cache/filter:is-public-post-type
filter to override whether a particular post type is deemed as public for cache flushing purposes or not.
Note: Changing post status to draft, future or pending always triggers cache flush (regardless of the post type).
Terms (taxonomies) are handled in a similar manner - cache is automatically flushed when a term is created, deleted or edited, but only in case of terms from a public taxonomy. You may use bc-cache/filter:is-public-taxonomy
filter to override whether a particular taxonomy should be deemed as public or not.
Flushing the cache programmatically
If you want to flush BC Cache cache from within your code, just call do_action('bc-cache/action:flush-cache')
. Note that the action is available after the init
hook with priority 10
is executed.
Scheduled cache flushing
Flushing of BC Cache cache on given schedule can be easily achieved with WP-Cron - you only have to hook the bc-cache/action:flush-cache
action to a scheduled event. Following WP-CLI command sets WP-Cron event that triggers cache flush every midnight:
wp cron event schedule 'bc-cache/action:flush-cache' midnight daily
Cache exclusions
A response to HTTP(S) request is not cached by BC Cache if any of the conditions below evaluates as true:
- Request is a POST request.
- Request is a GET request with one or more query string fields that are not whitelisted. By default, the whitelist consists of Google click IDs, Facebook Click Identifier, Microsoft Click ID and standard UTM parameters, but it can be filtered.
- Request is not for a front-end page (ie.
wp_using_themes
returnsfalse
). Output of AJAX, WP-CLI or WP-Cron calls is never cached. - Request comes from a non-anonymous user (ie. user that is logged in, left a comment or accessed password protected page/post). The rule can be tweaked to ignore front-end users if your theme supports it.
- Request/response type is one of the following: search, 404, feed, trackback, robots.txt, preview or password protected post.
- Fatal error recovery mode is active.
DONOTCACHEPAGE
constant is set and evaluates to true. This constant is for example automatically set by WooCommerce on certain pages.- Return value of
bc-cache/filter:skip-cache
filter evaluates to true.
Important! Cache exclusion rules are essentially defined in two places:
- In PHP code (including
bc-cache/filter:skip-cache
filter), the rules are used to determine whether current HTTP(S) request should be written to cache. - In
.htaccess
file, the rules are used to determine whether current HTTP(S) request should be served from cache.
When you add new rule for cache writing via bc-cache/filter:skip-cache
filter, you should always consider whether the rule should be also enforced for cache reading via .htaccess
file. In general, if your rule has no relation to request URI (for example you check cookies or User-Agent
string), you probably want to have the rule in both places.
The same applies to bc-cache/filter:query-string-fields-whitelist
filter - any extra whitelisted fields will not prevent cache writing anymore, but will still prevent cache reading unless they are integrated into respective rule in .htaccess
file.
Cache viewer
Contents of cache can be inspected (by any user with manage_options
capability) via Cache Viewer management page (under Tools). Users who can flush the cache are able to delete individual cache entries.
You may encounter a warning in Cache Viewer about total size of cache files being different from total size of files in cache folder - this usually means that you failed to correctly provide list of all available request variants via bc-cache/filter:request-variants
filter.
Front-end users and caching
Note: front-end user is any user that has no business accessing /wp-admin
area despite being able to log in via wp-login.php
. Although the implementation details do not presume any particular plugin, following text is written with WooCommerce (and registered customers as front-end users) in mind.
Depending on your theme, the HTML served to front-end users can be identical to the HTML served to anonymous users. Such themes most often fetch any personalized content (like items added to cart) via a JavaScript call. In such case there is no reason to exclude front-end users from full page caching.
There is a catch though...
Unlike some other content management systems, WordPress does not distinguish between back-end and front-end users. The same authentication mechanism is used to authenticate back-end users (like shop managers) and front-end users (like shop customers). As a fact, you cannot use the same email address to create a test customer account as you had used for shop manager account.
BC Cache by default does not read from or write to cache when HTTP request comes from any logged-in user:
- When call to
is_user_logged_in
function returns true, response to HTTP request is not written to cache. - When HTTP request has a cookie with
wordpress_logged_in
in its name, response to HTTP request is not read from cache - this check must be configured in.htaccess
file.
When your theme declares support for front-end user caching:
The first check is relaxed automatically with some reasonable defaults: any user that has read
and customer
capabilities only is considered to be front-end user and any pages he/she visits are written to cache normally. You may filter the capabilities list or the output of the check if you wish so.
To make it possible to relax the second check, BC Cache sets an additional session cookie whenever front-end user logs in. The rule in .htaccess
file that deals with login cookie has to be extended as follows:
# The legacy rule is replaced by 3 rules below: # RewriteCond %{HTTP_COOKIE} !(wp-postpass|wordpress_logged_in|comment_author)_ RewriteCond %{HTTP_COOKIE} !(wp-postpass|comment_author)_ RewriteCond %{HTTP_COOKIE} !wordpress_logged_in_ [OR] RewriteCond %{HTTP_COOKIE} bc_cache_is_fe_user=true
This way cached pages can be served to front-end users too. Cookie name and content can be adjusted by designated filters - make sure to adapt respective .htaccess
rule if you change them.
Request variants
Sometimes a different response body is served to request to the same URL, typically when particular cookie is set or request is made by particular browser/bot. In such cases, BC Cache allows to define request variants and cache/serve different response body based on configured conditions. A typical example is the situation in which privacy policy notice is displayed until site visitor accepts it. The state (cookie policy accepted or not) is often determined based on presence of particular cookie. Using request variants, BC Cache can serve visitors regardless if they have or have not accepted the cookie policy.
Request variant configuration example
A website has two variants: one with cookie notice (cookie_notice_accepted cookie is not set) and one without (cookie_notice_accepted cookie is already set).
Request variant name should be set whenever cookie notice is accepted (example uses API of Cookie Notice plugin):
add_filter('bc-cache/filter:request-variant', function (string $default_variant): string { return cn_cookies_accepted() ? '_cna' : $default_variant; }, 10, 1); add_filter('bc-cache/filter:request-variants', function (array $variants): array { $variants['_cna'] = 'Cookie notice accepted'; return $variants; }, 10, 1);
The default configuration needs to be extended as well and set the new variant accordingly:
# Set request variants (default and "cookie notice accepted"): RewriteRule .* - [E=BC_CACHE_REQUEST_VARIANT:] RewriteCond %{HTTP_COOKIE} cookie_notice_accepted=true RewriteRule .* - [E=BC_CACHE_REQUEST_VARIANT:_cna]
Important: Variant names are appended to basename part of cache file names, so index
becomes index_cna
and index.gz
becomes index_cna.gz
in the example above. To make sure your setup will work, use only letters from [a-z0-9_-]
range as variant names.
Cache warm up
Since version 2, the plugin performs cache warm up, ie. stores all pages in cache automatically without the need of front-end users to visit them. The obvious advantage is that even the first visitors of particular pages are served from cache (= fast).
Internally, the warm up process is hooked to WP-Cron and the website is crawling itself in the background. This automatic crawling is kicked up every time cache is flushed (with a 10 minutes delay by default, but this can be configured).
Since version 2.2, cache warm up can be triggered immediately from Cache Viewer. Also, the cache can be warmed up from command line via following WP-CLI command: wp bc-cache warm-up
In order for the warm up to function properly:
- Website has to have XML sitemap(s) available. URL of the XML sitemap has to be either advertised in
robots.txt
file or has to be (default)<home-url>/sitemap.xml
. XML sitemap index is supported, but not recursively. - In case request variants are used, the
bc-cache/filter:cache-warm-up-request-arguments
filter should be used to modify arguments of HTTP request to any non-default URL variant, so the website generates correct response to such request. - It is highly recommended to hook WP-Cron into system task scheduler for increased performance.
Cache warm up configuration examples
Invoke cache warm up just 5 minutes after last cache flush:
add_filter('bc-cache/filter:cache-warm-up-invocation-delay', function (): int { return 5 * MINUTE_IN_SECONDS; }, 10, 0);
Allow only single warm up HTTP request per WP-Cron invocation:
add_filter('bc-cache/filter:cache-warm-up-run-timeout', '__return_zero', 10, 0);
Modify arguments of HTTP request to get page variant with cookie notice accepted (see request variant configuration example for context):
add_filter('bc-cache/filter:cache-warm-up-request-arguments', function (array $args, string $url, string $request_variant): array { if ($request_variant === '_cna') { $args['cookies'] = [ 'cookie_notice_accepted' => 'true', ]; } return $args; }, 10, 3);
WP-CLI commands
You might use WP-CLI to delete specific posts/pages form cache, flush entire cache, run cache warm up, get size information or even list all cache entries. BC Cache registers bc-cache
command with following subcommands:
delete <post-id>
- deletes cache data (all request variants) of post/page with given IDremove <url>
- deletes cache data (all request variants) of given URLflush
- flushes entire cachewarm-up
- runs cache warm upsize [--human-readable]
- retrieves cache directory apparent size, optionally in human readable formatlist [<column>...] [--format=<format>] [--plain] [--sort-by=<column>]
- list cache entries, optionally in specified format or sorted
Integration with 3rd-party plugins and tools
Autoptimize
Autoptimize is a very popular plugin to optimize script and styles by aggregation, minification, caching etc. BC Cache automatically flushes its cache whenever Autoptimize cache is purged.
Cookie Notice
Cookie Notice is a popular plugin to display a simple, customizable website banner helpful achieving compliance with certain cookie consent requirements. BC Cache automatically flushes its cache whenever Cookie Notice banner configuration is changed.
7G firewall
If you happen to have 7G firewall by Jeff Starr installed on your website, you may have to alter the rule in 7G:[REQUEST URI]
section that prevents access to .gz
files (note that the code snippet below has been shortened with ...
for better readability):
RewriteCond %{REQUEST_URI} (\.)(7z|...|git|gz|hg|...|zlib)$ [NC,OR]
If you see 403 errors instead of cached pages, you have to either remove the |gz
part from the RewriteCond
line above or remove the line completely.
Credits
- Sergej Müller & Plugin Kollektiv for inspiration in form of Cachify plugin.
- Font Awesome for HDD icon
- Tim Lochmüller for inspirational tweaks to
.htaccess
configuration taken from his Static File Cache extension