405 lines
12 KiB
PHP
405 lines
12 KiB
PHP
<?php
|
||
namespace WP_Rocket\Engine\Cache\PurgeExpired;
|
||
|
||
use WP_Rocket\Buffer\Cache;
|
||
|
||
/**
|
||
* Purge expired cache files based on the defined lifespan
|
||
*
|
||
* @since 3.4
|
||
*/
|
||
class PurgeExpiredCache {
|
||
/**
|
||
* Path to the global cache folder.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @var string
|
||
*/
|
||
private $cache_path;
|
||
|
||
/**
|
||
* Filesystem object.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @var \WP_Filesystem_Direct
|
||
*/
|
||
private $filesystem;
|
||
|
||
/**
|
||
* Constructor
|
||
*
|
||
* @param string $cache_path Path to the global cache folder.
|
||
*/
|
||
public function __construct( $cache_path ) {
|
||
$this->cache_path = $cache_path;
|
||
}
|
||
|
||
/**
|
||
* Perform the event action.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @param int $lifespan The cache lifespan in seconds.
|
||
*/
|
||
public function purge_expired_files( $lifespan ) {
|
||
if ( ! $lifespan ) {
|
||
// Uh?
|
||
return;
|
||
}
|
||
|
||
$urls = get_rocket_i18n_uri();
|
||
$file_age_limit = time() - $lifespan;
|
||
|
||
/**
|
||
* Filter home URLs that will be searched for old cache files.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @param array $urls URLs that will be searched for old cache files.
|
||
* @param int $file_age_limit Timestamp of the maximum age files must have.
|
||
*/
|
||
$urls = apply_filters( 'rocket_automatic_cache_purge_urls', $urls, $file_age_limit );
|
||
|
||
if ( ! is_array( $urls ) ) {
|
||
// I saw what you did ಠ_ಠ.
|
||
$urls = get_rocket_i18n_uri();
|
||
}
|
||
|
||
$urls = array_filter( $urls, 'is_string' );
|
||
$urls = array_filter( $urls );
|
||
|
||
if ( ! $urls ) {
|
||
return;
|
||
}
|
||
|
||
$urls = array_unique( $urls );
|
||
|
||
if ( empty( $this->filesystem ) ) {
|
||
$this->filesystem = rocket_direct_filesystem();
|
||
}
|
||
|
||
$deleted = [];
|
||
$cache_enabled = Cache::can_generate_caching_files();
|
||
|
||
foreach ( $urls as $url ) {
|
||
/**
|
||
* Fires before purging a cache directory.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @param string $url The home url.
|
||
* @param int $file_age_limit Timestamp of the maximum age files must have.
|
||
*/
|
||
do_action( 'rocket_before_automatic_cache_purge_dir', $url, $file_age_limit );
|
||
|
||
$url_deleted = [];
|
||
|
||
if ( $cache_enabled ) {
|
||
// Get the directory names.
|
||
$file = get_rocket_parse_url( $url );
|
||
|
||
/** This filter is documented in inc/front/htaccess.php */
|
||
if ( apply_filters( 'rocket_url_no_dots', false ) ) {
|
||
$file['host'] = str_replace( '.', '_', $file['host'] );
|
||
}
|
||
|
||
$sub_dir = rtrim( $file['path'], '/' );
|
||
$files = $this->get_cache_files_in_dir( $file );
|
||
|
||
foreach ( $files as $item ) {
|
||
$dir_path = $item->getPathname();
|
||
$sub_dir_path = $dir_path . $sub_dir;
|
||
|
||
// Time to cut old leaves.
|
||
$item_paths = $this->purge_dir( $sub_dir_path, $file_age_limit );
|
||
|
||
if ( $item_paths ) {
|
||
$url_deleted[] = [
|
||
'home_url' => $url,
|
||
'home_path' => $sub_dir_path,
|
||
'logged_in' => $dir_path !== $this->cache_path . $file['host'],
|
||
'files' => $item_paths,
|
||
];
|
||
}
|
||
|
||
if ( $this->is_dir_empty( $dir_path ) ) {
|
||
// If the folder is empty, remove it.
|
||
$this->filesystem->delete( $dir_path );
|
||
}
|
||
}
|
||
|
||
if ( $url_deleted ) {
|
||
$deleted = array_merge( $deleted, $url_deleted );
|
||
}
|
||
}
|
||
|
||
$args = [
|
||
'url' => $url,
|
||
'lifespan' => $lifespan,
|
||
'file_age_limit' => $file_age_limit,
|
||
];
|
||
|
||
/**
|
||
* Fires after a cache directory is purged.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @param array $deleted {
|
||
* An array of arrays sharing the same home URL, described like: {
|
||
* @type string $home_url The home URL. This is the same as $args['url'].
|
||
* @type string $home_path Path to home.
|
||
* @type bool $logged_in True if the home path corresponds to a logged in user’s folder.
|
||
* @type array $files A list of paths of files that have been deleted.
|
||
* }
|
||
* Ex:
|
||
* [
|
||
* [
|
||
* 'home_url' => 'http://example.com/home1',
|
||
* 'home_path' => '/path-to/home1/wp-content/cache/wp-rocket/example.com/home1',
|
||
* 'logged_in' => false,
|
||
* 'files' => [
|
||
* '/path-to/home1/wp-content/cache/wp-rocket/example.com/home1/deleted-page',
|
||
* '/path-to/home1/wp-content/cache/wp-rocket/example.com/home1/very-dead-page',
|
||
* ],
|
||
* ],
|
||
* [
|
||
* 'home_url' => 'http://example.com/home1',
|
||
* 'home_path' => '/path-to/home1/wp-content/cache/wp-rocket/example.com-Greg-594d03f6ae698691165999/home1',
|
||
* 'logged_in' => true,
|
||
* 'files' => [
|
||
* '/path-to/home1/wp-content/cache/wp-rocket/example.com-Greg-594d03f6ae698691165999/home1/how-to-prank-your-coworkers',
|
||
* '/path-to/home1/wp-content/cache/wp-rocket/example.com-Greg-594d03f6ae698691165999/home1/best-source-of-gifs',
|
||
* ],
|
||
* ],
|
||
* ]
|
||
* @param array $args {
|
||
* @type string $url The home url.
|
||
* @type int $lifespan Files lifespan in seconds.
|
||
* @type int $file_age_limit Timestamp of the maximum age files must have. This is basically `time() - $lifespan`.
|
||
* }
|
||
*/
|
||
do_action( 'rocket_after_automatic_cache_purge_dir', $url_deleted, $args );
|
||
}
|
||
|
||
$args = [
|
||
'lifespan' => $lifespan,
|
||
'file_age_limit' => $file_age_limit,
|
||
];
|
||
|
||
/**
|
||
* Fires after cache directories are purged.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @param array $deleted {
|
||
* An array of arrays, described like: {
|
||
* @type string $home_url The home URL.
|
||
* @type string $home_path Path to home.
|
||
* @type bool $logged_in True if the home path corresponds to a logged in user’s folder.
|
||
* @type array $files A list of paths of files that have been deleted.
|
||
* }
|
||
* Ex:
|
||
* [
|
||
* [
|
||
* 'home_url' => 'http://example.com/home1',
|
||
* 'home_path' => '/path-to/home1/wp-content/cache/wp-rocket/example.com/home1',
|
||
* 'logged_in' => false,
|
||
* 'files' => [
|
||
* '/path-to/home1/wp-content/cache/wp-rocket/example.com/home1/deleted-page',
|
||
* '/path-to/home1/wp-content/cache/wp-rocket/example.com/home1/very-dead-page',
|
||
* ],
|
||
* ],
|
||
* [
|
||
* 'home_url' => 'http://example.com/home1',
|
||
* 'home_path' => '/path-to/home1/wp-content/cache/wp-rocket/example.com-Greg-594d03f6ae698691165999/home1',
|
||
* 'logged_in' => true,
|
||
* 'files' => [
|
||
* '/path-to/home1/wp-content/cache/wp-rocket/example.com-Greg-594d03f6ae698691165999/home1/how-to-prank-your-coworkers',
|
||
* '/path-to/home1/wp-content/cache/wp-rocket/example.com-Greg-594d03f6ae698691165999/home1/best-source-of-gifs',
|
||
* ],
|
||
* ],
|
||
* [
|
||
* 'home_url' => 'http://example.com/home4',
|
||
* 'home_path' => '/path-to/home4/wp-content/cache/wp-rocket/example.com-Greg-71edg8d6af865569979569/home4',
|
||
* 'logged_in' => true,
|
||
* 'files' => [
|
||
* '/path-to/home4/wp-content/cache/wp-rocket/example.com-Greg-71edg8d6af865569979569/home4/easter-eggs-in-code-your-best-opportunities',
|
||
* ],
|
||
* ],
|
||
* ]
|
||
* }
|
||
* @param array $args {
|
||
* @type int $lifespan Files lifespan in seconds.
|
||
* @type int $file_age_limit Timestamp of the maximum age files must have. This is basically `time() - $lifespan`.
|
||
* }
|
||
*/
|
||
do_action( 'rocket_after_automatic_cache_purge', $deleted, $args );
|
||
}
|
||
|
||
|
||
/** ----------------------------------------------------------------------------------------- */
|
||
/** TOOLS =================================================================================== */
|
||
/** ----------------------------------------------------------------------------------------- */
|
||
/**
|
||
* Get all cache files for the provided URL
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @param array $file An array of the parsed URL parts.
|
||
* @return array|\CallbackFilterIterator
|
||
*/
|
||
private function get_cache_files_in_dir( $file ) {
|
||
// Grab cache folders.
|
||
$host_pattern = '@^' . preg_quote( $file['host'], '@' ) . '@';
|
||
$sub_dir = rtrim( $file['path'], '/' );
|
||
|
||
try {
|
||
$iterator = new \DirectoryIterator( $this->cache_path );
|
||
}
|
||
catch ( \Exception $e ) {
|
||
return [];
|
||
}
|
||
|
||
return new \CallbackFilterIterator(
|
||
$iterator,
|
||
function ( $current ) use ( $host_pattern, $sub_dir ) {
|
||
|
||
if ( ! $current->isDir() || $current->isDot() ) {
|
||
// We look for folders only, and don't want '.' nor '..'.
|
||
return false;
|
||
}
|
||
|
||
if ( ! preg_match( $host_pattern, $current->getFilename() ) ) {
|
||
// Not the right host.
|
||
return false;
|
||
}
|
||
|
||
if ( '' !== $sub_dir && ! $this->filesystem->exists( $current->getPathname() . $sub_dir ) ) {
|
||
// Not the right path.
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Purge a folder from old files.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @param string $dir_path Path to the folder to purge.
|
||
* @param int $file_age_limit Timestamp of the maximum age files must have.
|
||
* @return array A list of files that have been deleted.
|
||
*/
|
||
private function purge_dir( $dir_path, $file_age_limit ) {
|
||
$deleted = [];
|
||
|
||
try {
|
||
$iterator = new \DirectoryIterator( $dir_path );
|
||
}
|
||
catch ( \Exception $e ) {
|
||
return [];
|
||
}
|
||
|
||
foreach ( $iterator as $item ) {
|
||
if ( $item->isDot() ) {
|
||
continue;
|
||
}
|
||
|
||
if ( $item->isDir() ) {
|
||
/**
|
||
* A folder, let’s see what’s in there.
|
||
* Maybe there’s a dinosaur fossil or a hidden treasure.
|
||
*/
|
||
$dir_deleted = $this->purge_dir( $item->getPathname(), $file_age_limit );
|
||
$deleted = array_merge( $deleted, $dir_deleted );
|
||
|
||
} elseif ( $item->isFile() && $item->getMTime() < $file_age_limit ) {
|
||
$file_path = $item->getPathname();
|
||
|
||
/**
|
||
* The file is older than our limit.
|
||
* This will also delete the file if `$item->getMTime()` fails.
|
||
*/
|
||
if ( ! $this->filesystem->delete( $file_path ) ) {
|
||
continue;
|
||
}
|
||
|
||
/**
|
||
* A page can have mutiple cache files:
|
||
* index(-mobile)(-https)(-dynamic-cookie-key){0,*}.html(_gzip).
|
||
*/
|
||
$dir_path = dirname( $file_path );
|
||
|
||
if ( ! in_array( $dir_path, $deleted, true ) ) {
|
||
$deleted[] = $dir_path;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( $this->is_dir_empty( $dir_path ) ) {
|
||
// If the folder is empty, remove it.
|
||
$this->filesystem->delete( $dir_path );
|
||
}
|
||
|
||
return $deleted;
|
||
}
|
||
|
||
/**
|
||
* Tell if a folder is empty.
|
||
*
|
||
* @since 3.4
|
||
*
|
||
* @param string $dir_path Path to the folder to purge.
|
||
* @return bool True if empty. False if it contains files.
|
||
*/
|
||
private function is_dir_empty( $dir_path ) {
|
||
try {
|
||
$iterator = new \DirectoryIterator( $dir_path );
|
||
}
|
||
catch ( \Exception $e ) {
|
||
return [];
|
||
}
|
||
|
||
foreach ( $iterator as $item ) {
|
||
if ( $item->isDot() ) {
|
||
continue;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Update lifespan option to convert old minutes to hours.
|
||
*
|
||
* @since 3.8
|
||
*
|
||
* @param int $old_lifespan Old value in minutes.
|
||
* @param int $old_lifespan_unit Old value of unit.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function update_lifespan_value( $old_lifespan, $old_lifespan_unit ) {
|
||
if ( 'MINUTE_IN_SECONDS' !== $old_lifespan_unit ) {
|
||
return;
|
||
}
|
||
|
||
$options = get_option( 'wp_rocket_settings', [] );
|
||
|
||
if ( $old_lifespan > 0 && $old_lifespan < 60 ) {
|
||
$old_lifespan = 60;
|
||
}
|
||
|
||
$options['purge_cron_unit'] = 'HOUR_IN_SECONDS';
|
||
$options['purge_cron_interval'] = round( $old_lifespan / 60 );
|
||
|
||
update_option( 'wp_rocket_settings', $options );
|
||
}
|
||
|
||
}
|