279 lines
7.7 KiB
PHP
279 lines
7.7 KiB
PHP
<?php
|
|
/**
|
|
* This file is part of Avatar Privacy.
|
|
*
|
|
* Copyright 2018-2023 Peter Putzer.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* ***
|
|
*
|
|
* @package mundschenk-at/avatar-privacy
|
|
* @license http://www.gnu.org/licenses/gpl-2.0.html
|
|
*/
|
|
|
|
namespace Avatar_Privacy\Data_Storage;
|
|
|
|
use Avatar_Privacy\Exceptions\Filesystem_Exception;
|
|
|
|
use function Avatar_Privacy\Tools\delete_file;
|
|
|
|
/**
|
|
* A filesystem caching handler.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @author Peter Putzer <github@mundschenk.at>
|
|
*
|
|
* @phpstan-type UploadDir array{
|
|
* path: string,
|
|
* url: string,
|
|
* subdir: string,
|
|
* basedir: string,
|
|
* baseurl: string,
|
|
* error: string|false
|
|
* }
|
|
*/
|
|
class Filesystem_Cache {
|
|
|
|
const CACHE_DIR = 'avatar-privacy/cache/';
|
|
|
|
/**
|
|
* The base directory for the filesystem cache.
|
|
*
|
|
* @var string
|
|
*/
|
|
private string $base_dir;
|
|
|
|
/**
|
|
* The base URL for accessing cached files.
|
|
*
|
|
* @var string
|
|
*/
|
|
private string $base_url;
|
|
|
|
/**
|
|
* Information about the uploads directory.
|
|
*
|
|
* @var array
|
|
*
|
|
* @phpstan-var UploadDir
|
|
*/
|
|
private array $upload_dir;
|
|
|
|
/**
|
|
* Creates a new instance.
|
|
*/
|
|
public function __construct() {
|
|
$this->get_base_dir();
|
|
}
|
|
|
|
/**
|
|
* Retrieves the base directory for caching files.
|
|
*
|
|
* @since 2.4.0 A Filesystem_Exception is thrown instead of a generic \RuntimeException.
|
|
*
|
|
* @throws Filesystem_Exception An exception is thrown if the cache directory
|
|
* does not exist and can't be created.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_base_dir() {
|
|
if ( empty( $this->base_dir ) ) {
|
|
$this->base_dir = "{$this->get_upload_dir()['basedir']}/" . self::CACHE_DIR;
|
|
|
|
if ( ! \wp_mkdir_p( $this->base_dir ) ) {
|
|
throw new Filesystem_Exception( "The cache directory {$this->base_dir} could not be created." );
|
|
}
|
|
}
|
|
|
|
return $this->base_dir;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the base URL for accessing cached files.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_base_url() {
|
|
if ( empty( $this->base_url ) ) {
|
|
$this->base_url = "{$this->get_upload_dir()['baseurl']}/" . self::CACHE_DIR;
|
|
}
|
|
|
|
return $this->base_url;
|
|
}
|
|
|
|
/**
|
|
* Retrieves information about the upload directory.
|
|
*
|
|
* @since 2.1.0 Visibility changed to protected.
|
|
*
|
|
* @return array
|
|
*
|
|
* @phpstan-return UploadDir
|
|
*/
|
|
protected function get_upload_dir() {
|
|
if ( ! isset( $this->upload_dir ) ) {
|
|
$multisite = \is_multisite();
|
|
|
|
if ( $multisite ) {
|
|
\switch_to_blog( \get_main_site_id() );
|
|
}
|
|
|
|
// We only need the basedir, so don't create the monthly sub-directory.
|
|
$this->upload_dir = \wp_upload_dir( null, false );
|
|
|
|
if ( $multisite ) {
|
|
\restore_current_blog();
|
|
}
|
|
}
|
|
|
|
return $this->upload_dir;
|
|
}
|
|
|
|
/**
|
|
* Stores data in the filesystem cache.
|
|
*
|
|
* @since 2.6.0 The type of the `$data` parameter has been corrected to `string`.
|
|
*
|
|
* @param string $filename The filename (including any sub directory).
|
|
* @param string $data The (possibly binary) data. Will not be cached if empty.
|
|
* @param bool $force Optional. The cached file will only be overwritten if set to true. Default false.
|
|
*
|
|
* @return bool True if the file was successfully stored in the cache, false otherwise.
|
|
*/
|
|
public function set( $filename, $data, $force = false ) {
|
|
$file = $this->get_base_dir() . $filename;
|
|
|
|
if ( \file_exists( $file ) && ! $force ) {
|
|
return true;
|
|
}
|
|
|
|
return ! (
|
|
// Don't create empty files.
|
|
0 === \strlen( $data ) ||
|
|
// Make sure that the file path is valid.
|
|
! \wp_mkdir_p( \dirname( $file ) ) ||
|
|
// Check if the file has been stored successfully.
|
|
false === \file_put_contents( $file, $data, \LOCK_EX ) // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the URL for the cached file.
|
|
*
|
|
* @param string $filename The filename (including any sub directory).
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_url( $filename ) {
|
|
return $this->get_base_url() . $filename;
|
|
}
|
|
|
|
/**
|
|
* Removes a file from the cache.
|
|
*
|
|
* @param string $filename The filename (including any sub directory).
|
|
*
|
|
* @return bool True if the file was successfully removed from the cache, false otherwise.
|
|
*/
|
|
public function delete( $filename ) {
|
|
$file = $this->get_base_dir() . $filename;
|
|
|
|
return \wp_is_writable( $file ) && delete_file( $file );
|
|
}
|
|
|
|
/**
|
|
* Invalidate all cached elements by recursively deleting all files and directories.
|
|
*
|
|
* @param string $subdir Optional. Limit invalidation to the given subdirectory. Default ''.
|
|
* @param string $regex Optional. Limit invalidation to files matching the given regular expression. Default ''.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function invalidate( $subdir = '', $regex = '' ) {
|
|
try {
|
|
$iterator = $this->get_recursive_file_iterator( $subdir, $regex );
|
|
} catch ( \UnexpectedValueException $e ) {
|
|
// Ignore non-existing subdirectories.
|
|
return;
|
|
}
|
|
|
|
foreach ( $iterator as $path => $file ) {
|
|
if ( $file->isWritable() ) {
|
|
|
|
if ( $file->isDir() ) {
|
|
\rmdir( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- Useing the WP_Filesystem API is not an option on the frontend.
|
|
} else {
|
|
delete_file( $path );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate all cached files older than the given age.
|
|
*
|
|
* @param int $age The maximum file age in seconds.
|
|
* @param string $subdir Optional. Limit invalidation to the given subdirectory. Default ''.
|
|
* @param string $regex Optional. Limit invalidation to files matching the given regular expression. Default ''.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function invalidate_files_older_than( $age, $subdir = '', $regex = '' ) {
|
|
try {
|
|
$now = \time();
|
|
$iterator = $this->get_recursive_file_iterator( $subdir, $regex );
|
|
} catch ( \UnexpectedValueException $e ) {
|
|
// Ignore non-existing subdirectories.
|
|
return;
|
|
}
|
|
|
|
foreach ( $iterator as $path => $file ) {
|
|
if ( $file->isWritable() && ! $file->isDir() && $now - $file->getMTime() > $age ) {
|
|
delete_file( $path );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves a recursive iterator for all files in the cache.
|
|
*
|
|
* @since 2.1.0 Visibility changed to protected.
|
|
*
|
|
* @param string $subdir Optional. Limit invalidation to the given subdirectory. Default ''.
|
|
* @param string $regex Optional. Limit invalidation to files matching the given regular expression. Default ''.
|
|
*
|
|
* @return \OuterIterator<string,\SplFileInfo>
|
|
*/
|
|
protected function get_recursive_file_iterator( $subdir = '', $regex = '' ) {
|
|
$files = new \RecursiveIteratorIterator(
|
|
new \RecursiveDirectoryIterator( "{$this->get_base_dir()}{$subdir}", \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS ),
|
|
\RecursiveIteratorIterator::CHILD_FIRST
|
|
);
|
|
|
|
if ( ! empty( $regex ) ) {
|
|
/**
|
|
* Further filter the collected files using the given regular expression.
|
|
*
|
|
* @phpstan-var \RegexIterator<string,\SplFileInfo,\RecursiveIteratorIterator<\RecursiveDirectoryIterator<string,\SplFileInfo>>> $files
|
|
*/
|
|
$files = new \RegexIterator( $files, $regex, \RecursiveRegexIterator::MATCH );
|
|
}
|
|
|
|
return $files;
|
|
}
|
|
}
|