Skip to content
Open
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,47 @@
# Longcache
# Smartcache

Longcache will set Cache-Control headers for CDNs / reverse proxies to cache WordPress pages for a long period of times (days+). As such this plugin also handles invalidations on content updates, to FLUSH the CDN / reverse proxy on page changes.
Smartcache integrates with Batcache to smartly tune the cache lifetime for your content.

This is a work-in-progress.

## Todo
## Behaviour

- Delete post / transition should trigger invalidate.
By default, caching plugins like Batcache will cache for a static 5 minutes for all content.

Smartcache dynamically tunes this cache lifetime to match the type of content. It breaks content into three buckets: frequently updated, regular content, and infrequently updated content.

Frequently updated content is cached for 5 minutes. This content is:

* The "home" page - this is the page showing the list of posts, not necessarily the "front page" for sites using a static home page.
* Content (posts, pages, etc) published in the past 24 hours

Regular content is cached for 6 hours. This content is:

* Content published in the past 30 days (except last 24 hours as above)
* Most archive pages (including categories, tags, author pages)
* Date archive pages, except the current one (today/current month/current year)
* Search pages

Infrequently updated content is cached for 14 days. This content is:

* Content published more than 30 days ago
* Pages (except those published recently)
* Date archive pages which aren't the current one
* 404 pages

Smartcache forces cache invalidation on the CDN in the following cases:

* Publishing a new piece of content (to invalidate the 404)
* Updating an infrequently updated piece of content


## Overriding behaviour

Smartcache has a variety of filters available. These include:

* `smartcache.old_threshold` - Filter how long a post must be published before it's considered old (infrequently updated). Default is 7 days.
* `smartcache.is_old_post` - Filter whether a specific post is considered old (infrequently updated). (Default true for posts older than the old threshold.)
* `smartcache.is_new_post` - Filter whether a specific post is considered new (frequently updated). (Default true if published in previous 24 hours.)
* `smartcache.max-age` - Filter the maximum lifetime for the current page directly.
* `smartcache.should_cache` - Filter whether a page should be cached at all. (Only affects whether Smartcache generates a header, but may be overridden by other behaviour or by the cache itself.)
* `smartcache.urls_to_invalidate_for_post` - Filter which URLs to invalidate for a given post.
* `smartcache.should_invalidate` - Should we invalidate URLs for this post? (Default true, false for new content as it will expire naturally quickly.)
166 changes: 141 additions & 25 deletions inc/admin/namespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
*/
function bootstrap() : void {
add_action( 'admin_menu', __NAMESPACE__ . '\\register_admin_page' );
add_action( 'admin_init', __NAMESPACE__ . '\\register_settings' );
add_action( 'admin_init', __NAMESPACE__ . '\\check_on_invalidate_urls_submit' );

require_once ABSPATH . '/wp-admin/includes/class-wp-list-table.php';
require_once __DIR__ . '/class-log-list-table.php';
}


function register_admin_page() : void {
add_submenu_page(
'options-general.php',
'tools.php',
_x( 'Smartcache', 'settings page title', 'smartcache' ),
_x( 'Smartcache', 'settings menu title', 'smartcache' ),
'manage_options',
Expand All @@ -32,32 +32,142 @@ function register_admin_page() : void {
);
}

function register_settings() {
add_settings_section(
'smartcache-quota',
__( 'Quota', 'smartcache' ),
__NAMESPACE__ . '\\render_quota_section',
'smartcache'
);
}

function render_quota_section() {
$usage = Smartcache\get_invalidation_quota_usage();
$quota = Smartcache\get_invalidation_quota();

$is_warning = $usage >= ( 0.8 * $quota );
$is_full = $usage >= $quota;
$class = $is_warning ? 'warning' : ( $is_full ? 'error' : '' );
?>
<table class="form-table">
<tr>
<th scope="row">
Monthly quota usage
</th>
<td>
<meter
class="usage-meter <?php echo sanitize_html_class( $class ); ?>"
high="<?php echo esc_attr( 0.8 * $quota ); ?>"
max="<?php echo esc_attr( $quota ); ?>"
value="<?php echo esc_attr( $usage ); ?>"
style="width: 20em;"
>
<?php printf( '%d / %d', $usage, $quota ); ?>
</meter>

<p>
<?php
printf(
__( 'You have used %d of %d invalidation requests this month.', 'smartcache' ),
$usage,
$quota
);
?>
</p>
</td>
</tr>
</table>
<?php
}

function render_settings_page() : void {
settings_errors( 'smartcache' );
$usage = Smartcache\get_invalidation_quota_usage();
$quota = Smartcache\get_invalidation_quota();
$exceeded_quota = $usage >= $quota;
if ( $exceeded_quota ) {
add_settings_error( 'smartcache', 'quota_exceeded', __( 'You have exceeded your invalidation quota for this month. Contact support.'), 'warning' );
}
$submit_attr = $exceeded_quota ? 'disabled' : '';

?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<?php settings_errors( 'smartcache' ); ?>
<form action="options.php" method="post">
<?php
settings_fields( 'smartcache' );
do_settings_sections( 'smartcache' );
?>
</form>

<h1><?php echo __( 'Invalidate URLs', 'smartcache' ) ?></h1>
<form method="post">
<label>
URLs to invalidate (one per line.)
<textarea class="large-text code" rows=10 name="smartcache_urls"></textarea>
</label>
<p class="description">
Use <code>*</code> as a wildcard, wildcards can only be at the end of a URL. A maximum of <?php echo esc_html( Cloud\PATHS_INVALIDATION_LIMIT ) ?> absolute URLs or <?php echo esc_html( Cloud\WILDCARD_INVALIDATION_LIMIT ) ?> wildcard URLs can be issued per request.
</p>
<?php
wp_nonce_field( 'smartcache.invalidate-urls' );
submit_button( __( 'Invalidate', 'smartcache' ) );
?>
</form>
<table class="form-table">
<tr>
<th scope="row">
Invalidate all URLs
</th>
<td>
<form method="post">
<input
name="smartcache_urls"
type="hidden"
value="*"
/>
<?php
wp_nonce_field( 'smartcache.invalidate-urls' );
submit_button(
__( 'Invalidate entire cache', 'smartcache' ),
'',
'submit',
true,
$submit_attr
);
?>
</form>

<p class="description">
<?php
_e( 'Invalidate the entire cache. Use this when performing major updates to the site, such as navigation changes or changing the theme.', 'smartcache' );
?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="smartcache_urls">
Invalidate URLs
</label>
</th>
<td>
<form method="post">
<textarea
class="large-text code"
id="smartcache_urls"
rows="10"
name="smartcache_urls"
></textarea>
<p class="description">
Specify URLs to invalidate, one per line.
</p>
<p class="description">
Use <code>*</code> as a wildcard at the end of a URL. A maximum of <?php echo esc_html( Cloud\PATHS_INVALIDATION_LIMIT ) ?> absolute URLs or <?php echo esc_html( Cloud\WILDCARD_INVALIDATION_LIMIT ) ?> wildcard URLs can be issued per request.
</p>
<p class="description">
If you need to invalidate a lot of URLs, use a single broad wildcard instead of listing each URL.
</p>
<?php
wp_nonce_field( 'smartcache.invalidate-urls' );
submit_button(
__( 'Invalidate URLs', 'smartcache' ),
'primary',
'submit',
true,
$submit_attr
);
?>
</form>
</td>
</tr>
</table>

<h1>Log</h1>
<?php
Expand All @@ -75,15 +185,21 @@ function render_settings_page() : void {
* @return void
*/
function check_on_invalidate_urls_submit() {
if ( isset( $_POST['smartcache_urls'] ) && check_admin_referer( 'smartcache.invalidate-urls' ) ) {
$urls = array_filter( array_map( 'sanitize_text_field', array_map( 'trim', explode( "\n", $_POST['smartcache_urls'] ) ) ) );
if ( ! isset( $_POST['smartcache_urls'] ) ) {
return;
}

if ( ! check_admin_referer( 'smartcache.invalidate-urls' ) ) {
add_settings_error( 'smartcache', 'invalidated', __( 'Could not validate your request (invalid nonce). Try again.'), 'success' );
return;
}

$result = Smartcache\invalidate_urls( $urls );
$urls = array_filter( array_map( 'sanitize_text_field', array_map( 'trim', explode( "\n", $_POST['smartcache_urls'] ) ) ) );
$result = Smartcache\invalidate_urls( $urls );

if ( $result === true ) {
add_settings_error( 'logcache', 'invalidated', __( 'Invalidate request successful.'), 'success' );
} else {
add_settings_error( 'logcache', 'invalidated', __( 'There was a problem issueing the invalidation request.'), 'error' );
}
if ( $result === true ) {
add_settings_error( 'smartcache', 'invalidated', __( 'Invalidate request successful.'), 'success' );
} else {
add_settings_error( 'smartcache', 'invalidated', __( 'There was a problem issuing the invalidation request.'), 'error' );
}
}
Loading