Skip to content

Commit

Permalink
Merge branch 'release-1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
chesio committed Sep 20, 2018
2 parents ba32298 + bab1856 commit 9455f92
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 54 deletions.
111 changes: 77 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,54 @@ Simple disk cache for WordPress inspired by Cachify.
## Limitations

* BC Cache has not been tested on WordPress multisite installation.
* BC Cache has not been tested on Windows servers.

## Installation

You have to configure your Apache webserver to serve cached files. One way to do it is to add the lines below to the root `.htaccess` file (ie. the same file to which WordPress automatically writes pretty permalinks configuration). Note that 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 rules:

* `RewriteCond %{REQUEST_URI} !^/wordpress/(wp-admin|wp-content/cache)/.*`
* `RewriteCond %{DOCUMENT_ROOT}/wordpress/wp-content/cache/bc-cache/%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} -f`
* `RewriteRule .* /wordpress/wp-content/cache/bc-cache/%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} [L]`
* `RewriteRule .* /wordpress/wp-content/cache/bc-cache/%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} [L,NS]`

```
```.apacheconf
# BEGIN BC Cache
AddDefaultCharset utf-8
<IfModule mod_rewrite.c>
RewriteEngine on
# Set scheme and hostname directories
RewriteCond %{ENV:HTTPS} =on
RewriteRule .* - [E=BC_CACHE_HOST:https/%{HTTP_HOST}]
RewriteCond %{ENV:HTTPS} !=on
RewriteRule .* - [E=BC_CACHE_HOST:http/%{HTTP_HOST}]
# Set path subdirectory
RewriteCond %{REQUEST_URI} /$
RewriteRule .* - [E=BC_CACHE_PATH:%{REQUEST_URI}]
RewriteCond %{REQUEST_URI} ^$
RewriteRule .* - [E=BC_CACHE_PATH:/]
# gzip
RewriteRule .* - [E=BC_CACHE_FILE:index.html]
<IfModule mod_mime.c>
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteRule .* - [E=BC_CACHE_FILE:index.html.gz]
AddType text/html .gz
AddEncoding gzip .gz
</IfModule>
# Main rules
RewriteCond %{REQUEST_METHOD} !=POST
RewriteCond %{QUERY_STRING} =""
RewriteCond %{ENV:BC_CACHE_PATH} !=""
RewriteCond %{REQUEST_URI} !^/wordpress/(wp-admin|wp-content/cache)/.*
RewriteCond %{HTTP_COOKIE} !(wp-postpass|wordpress_logged_in|comment_author)_
RewriteCond %{DOCUMENT_ROOT}/wordpress/wp-content/cache/bc-cache/%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} -f
RewriteRule .* /wordpress/wp-content/cache/bc-cache/%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} [L,NS]
RewriteEngine on
# Set scheme and hostname directories
RewriteCond %{ENV:HTTPS} =on
RewriteRule .* - [E=BC_CACHE_HOST:https/%{HTTP_HOST}]
RewriteCond %{ENV:HTTPS} !=on
RewriteRule .* - [E=BC_CACHE_HOST:http/%{HTTP_HOST}]
# Set path subdirectory
RewriteCond %{REQUEST_URI} /$
RewriteRule .* - [E=BC_CACHE_PATH:%{REQUEST_URI}]
RewriteCond %{REQUEST_URI} ^$
RewriteRule .* - [E=BC_CACHE_PATH:/]
# gzip
RewriteRule .* - [E=BC_CACHE_FILE:index.html]
<IfModule mod_mime.c>
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteRule .* - [E=BC_CACHE_FILE:index.html.gz]
AddType text/html .gz
AddEncoding gzip .gz
</IfModule>
# Main rules
RewriteCond %{REQUEST_METHOD} !=POST
RewriteCond %{QUERY_STRING} =""
RewriteCond %{ENV:BC_CACHE_PATH} !=""
RewriteCond %{REQUEST_URI} !^/wordpress/(wp-admin|wp-content/cache)/.*
RewriteCond %{HTTP_COOKIE} !(wp-postpass|wordpress_logged_in|comment_author)_
RewriteCond %{DOCUMENT_ROOT}/wordpress/wp-content/cache/bc-cache/%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} -f
RewriteRule .* /wordpress/wp-content/cache/bc-cache/%{ENV:BC_CACHE_HOST}%{ENV:BC_CACHE_PATH}%{ENV:BC_CACHE_FILE} [L,NS]
</IfModule>
# END BC Cache
```

## Configuration
Expand All @@ -67,6 +67,7 @@ BC Cache has no settings. You can modify plugin behavior with following filters:
* `bc-cache/filter:flush-hooks` - filters list of actions that trigger cache flushing. Filter is executed in a hook registered to `init` action with priority 10, so make sure to register your hook earlier (for example within `plugins_loaded` or `after_setup_theme` actions).
* `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:skip-cache` - filters whether response to current HTTP(S) request should be cached. Filter is only executed, when none from [built-in skip rules](#cache-exclusions) is matched - this means that you cannot override built-in skip rules with this filter, only add your own rules.
* `bc-cache/filter:request-variant` - filters name of [request variant](#request-variants) of current HTTP request.

## Cache exclusions

Expand All @@ -80,6 +81,48 @@ A response to HTTP(S) request is cached by BC Cache if **none** of the condition
1. `DONOTCACHEPAGE` constant is set and evaluates to true.
1. Return value of `bc-cache/filter:skip-cache` filter evaluates to true.

**Important!** Cache exclusion rules are essentialy defined in two places:
1. 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.
1. 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.

## Request variants

Sometimes a different HTML is served as response 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 HTML responses based on configured conditions. A typical example in EU countries is the situation in which cookie policy notice is displayed to user until the user 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 both users that have and have not accepted cookie policy.

### Example

A website has two variants: one with cookie notice (no `cookie_notice_accepted` cookie is present) and one without.

Request variant name should be set whenever cookie notice is accepted (example uses API of [Cookie Notice plugin](https://wordpress.org/plugins/cookie-notice/)):
```php
add_filter('bc-cache/filter:request-variant', function (string $default_variant): string {
return cn_cookies_accepted() ? '_cna' : $default_variant;
}, 10, 1);
```

The [default configuration](#installation) needs to be extended in the following way:

```.apacheconf
# gzip
RewriteRule .* - [E=BC_CACHE_FILE:index.html]
RewriteCond %{HTTP_COOKIE} cookie_notice_accepted=true
RewriteRule .* - [E=BC_CACHE_FILE:index_cna.html]
<IfModule mod_mime.c>
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteRule .* - [E=BC_CACHE_FILE:index.html.gz]
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{HTTP_COOKIE} cookie_notice_accepted=true
RewriteRule .* - [E=BC_CACHE_FILE:index_cna.html.gz]
AddType text/html .gz
AddEncoding gzip .gz
</IfModule>
```

Notice, how viariant name `_cna` is appended to basename part of cache file names, so `index.html` becomes `index_cna.html` and `index.html.gz` becomes `index_cna.html.gz`. To make sure your setup will work, use only letters from `[a-z0-9_-]` set in variant name.

## Credits

* Sergej Müller & Plugin Kollektiv for inspiration in form of [Cachify plugin](https://wordpress.org/plugins/cachify/).
Expand Down
2 changes: 1 addition & 1 deletion autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/**
* Register autoloader for classes shipped with the plugin.
*
* @package BC_Apacache
* @package BC_Cache
*/

// Register autoload function
Expand Down
4 changes: 2 additions & 2 deletions bc-cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
* Plugin Name: BC Cache
* Plugin URI: https://github.com/chesio/bc-cache
* Description: Simple disk cache plugin inspired by Cachify.
* Version: 1.0.3
* Version: 1.1.0
* Author: Česlav Przywara <ceslav@przywara.cz>
* Author URI: https://www.chesio.com
* Requires PHP: 7.0
* Requires WP: 4.7
* Tested up to: 4.9
* Text Domain: bc-cache
* GitHub Plugin URI: https://github.com/chesio/bc-security
* GitHub Plugin URI: https://github.com/chesio/bc-cache
*/

if (version_compare(PHP_VERSION, '7.0', '<')) {
Expand Down
38 changes: 23 additions & 15 deletions classes/BlueChip/Cache/Core.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,27 @@ public function flush()
* Delete data for given URL from cache.
*
* @param string $url
* @param array $request_variants [optional] List of all request variants to delete for given $url.
* @throws Exception
*/
public function delete(string $url)
public function delete(string $url, array $request_variants = [''])
{
$path = self::getPath($url);

$html_filename = self::getHtmlFilename($path);
$gzip_filename = self::getGzipFilename($path);
foreach ($request_variants as $request_variant) {
$html_filename = self::getHtmlFilename($path, $request_variant);
$gzip_filename = self::getGzipFilename($path, $request_variant);

if (file_exists($html_filename) && !unlink($html_filename)) {
throw new Exception("Could not remove file {$html_filename}.");
}
if (file_exists($gzip_filename) && !unlink($gzip_filename)) {
throw new Exception("Could not remove file {$gzip_filename}.");
if (file_exists($html_filename) && !unlink($html_filename)) {
throw new Exception("Could not remove file {$html_filename}.");
}
if (file_exists($gzip_filename) && !unlink($gzip_filename)) {
throw new Exception("Could not remove file {$gzip_filename}.");
}
}

// TODO: Possibly return size of deleted files to update cache size stored in transient.

clearstatcache();
}

Expand All @@ -72,10 +77,11 @@ public function delete(string $url)
*
* @param string $url
* @param string $data
* @param string $request_variant [optional] Request variant.
* @return int
* @throws Exception
*/
public function push(string $url, string $data): int
public function push(string $url, string $data, string $request_variant = ''): int
{
$path = self::getPath($url);

Expand All @@ -84,9 +90,9 @@ public function push(string $url, string $data): int
throw new Exception("Unable to create directory {$path}.");
}

$bytes_written = self::writeFile(self::getHtmlFilename($path), $data);
$bytes_written = self::writeFile(self::getHtmlFilename($path, $request_variant), $data);
if (($gzip = gzencode($data, 9)) !== false) {
$bytes_written += self::writeFile(self::getGzipFilename($path), $gzip);
$bytes_written += self::writeFile(self::getGzipFilename($path, $request_variant), $gzip);
}

clearstatcache();
Expand Down Expand Up @@ -139,21 +145,23 @@ private static function getDirectorySize(string $dirname): int

/**
* @param string $path Path to cache directory without trailing directory separator.
* @param string $request_variant [optional] Request variant (default empty).
* @return string Path to gzipped cache file for $path.
*/
private static function getGzipFilename(string $path): string
private static function getGzipFilename(string $path, string $request_variant = ''): string
{
return $path . DIRECTORY_SEPARATOR . 'index.html.gz';
return $path . DIRECTORY_SEPARATOR . "index{$request_variant}.html.gz";
}


/**
* @param string $path Path to cache directory without trailing directory separator.
* @param string $request_variant [optional] Request variant (default empty).
* @return string Path to HTML cache file for $path.
*/
private static function getHtmlFilename(string $path): string
private static function getHtmlFilename(string $path, string $request_variant = ''): string
{
return $path . DIRECTORY_SEPARATOR . 'index.html';
return $path . DIRECTORY_SEPARATOR . "index{$request_variant}.html";
}


Expand Down
5 changes: 5 additions & 0 deletions classes/BlueChip/Cache/Hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ abstract class Hooks
* Name of hook to filter HTML signature appended to cached data.
*/
const FILTER_HTML_SIGNATURE = 'bc-cache/filter:html-signature';

/**
* Name of hook to filter current HTTP request variant.
*/
const FILTER_REQUEST_VARIANT = 'bc-cache/filter:request-variant';
}
5 changes: 3 additions & 2 deletions classes/BlueChip/Cache/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public function addDashboardInfo(array $items): array
$size = $this->getCacheSize();

$icon = sprintf(
'<svg style="width: 20px; height: 20px; fill: #82878c; vertical-align: middle;" aria-hidden="true" role="img"><use xlink:href="%s#bc-cache-icon-%s"></svg>',
'<svg style="width: 20px; height: 20px; fill: #82878c; float: left; margin-right: 5px;" aria-hidden="true" role="img"><use xlink:href="%s#bc-cache-icon-%s"></svg>',
plugins_url('assets/icons.svg', $this->plugin_filename),
strtolower($this->cache->getName())
);
Expand Down Expand Up @@ -312,7 +312,8 @@ public function handleOutputBuffer(string $buffer): string
try {
$bytes_written = $this->cache->push(
Utils::getRequestUrl(),
$buffer . $this->getSignature()
$buffer . $this->getSignature(),
apply_filters(Hooks::FILTER_REQUEST_VARIANT, '')
);

// If cache size transient exists, update it.
Expand Down

0 comments on commit 9455f92

Please # to comment.