oont-contents/plugins/wp-rocket/inc/deprecated/Engine/Addon/Busting/FileBustingTrait.php
2025-02-08 15:10:23 +01:00

478 lines
11 KiB
PHP

<?php
namespace WP_Rocket\Addon\Busting;
use WP_Rocket\Logger\Logger;
trait FileBustingTrait {
/**
* Saves the content of the URL to bust to the busting file if it doesn't exist yet.
*
* @since 3.2.4
* @access public
*
* @param string $url URL to get the content from.
* @return bool
*/
public function save( $url ) {
if ( $this->get_busting_version() ) {
// We have a local copy.
Logger::debug(
'Found local file.',
[
self::LOGGER_CONTEXT,
'path' => $this->get_busting_path(),
]
);
return true;
}
if ( $this->refresh_save( $url ) ) {
// We downloaded a fresh copy.
Logger::debug(
'New copy downloaded.',
[
self::LOGGER_CONTEXT,
'path' => $this->get_busting_path(),
]
);
return true;
}
return false;
}
/**
* Deletes the busting file.
*
* @since 3.1
* @since 3.2.4 Handle versioning.
* @access public
* @author Remy Perona
* @author Grégory Viguier
*
* @return bool True on success. False on failure.
*/
public function delete() {
$files = $this->get_all_files();
if ( false === $files ) {
// Error.
return false;
}
$this->file_version = null;
if ( ! $files ) {
// No local files yet.
return true;
}
return $this->delete_files( array_keys( $files ) );
}
/** ----------------------------------------------------------------------------------------- */
/** LOCAL FILE ============================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the version of the current busting file.
*
* @since 3.2.4
* @access protected
* @author Grégory Viguier
*
* @return string|bool Version of the file. False if the file does not exist.
*/
protected function get_busting_version() {
if ( ! empty( $this->file_version ) ) {
return $this->file_version;
}
$files = $this->get_all_files();
if ( ! $files ) {
// Error or no local files yet.
return false;
}
// Since we're not supposed to have several files, return the first one.
$this->file_version = reset( $files );
return $this->file_version;
}
/**
* Get all cached files in the directory.
* In a perfect world, there should be only one.
*
* @since 3.2.4
* @access private
*
* @return bool|array A list of file names (as array keys) and versions (as array values). False on failure.
*/
private function get_all_files() {
$dir_path = rtrim( $this->busting_path, '\\/' );
if ( ! $this->filesystem->exists( $dir_path ) ) {
return [];
}
if ( ! $this->filesystem->is_readable( $dir_path ) ) {
Logger::error(
'Directory is not readable.',
[
self::LOGGER_CONTEXT,
'path' => $dir_path,
]
);
return false;
}
$pattern = '/' . sprintf(
$this->escape_file_name( $this->filename_pattern ),
'([a-f0-9]{32}|local)'
) . '/';
$entries = _rocket_get_dir_files_by_regex( $dir_path, $pattern );
$list = [];
foreach ( $entries as $entry ) {
$filename = $entry->getFilename();
preg_match( $pattern, $filename, $file_details_match );
if ( ! empty( $file_details_match[1] ) ) {
$list[ $filename ] = $file_details_match[1];
}
}
return $list;
}
/**
* Get the final URL for the current cache busting file.
*
* @since 3.2.4
* @access protected
*
* @return string|bool URL of the file. False if the file does not exist.
*/
public function get_busting_url() {
return $this->get_busting_file_url( $this->get_busting_version() );
}
/**
* Get the path to the current cache busting file.
*
* @since 3.2.4
* @access protected
* @author Grégory Viguier
*
* @return string|bool URL of the file. False if the file does not exist.
*/
protected function get_busting_path() {
return $this->get_busting_file_path( $this->get_busting_version() );
}
/**
* Get the final URL for a cache busting file.
*
* @since 3.2.4
* @access private
* @author Grégory Viguier
*
* @param string $version The file version.
* @return string|bool URL of the file with this version. False if no versions are provided.
*/
private function get_busting_file_url( $version ) {
if ( ! $version ) {
return false;
}
$filename = $this->get_busting_file_name( $version );
// This filter is documented in inc/functions/minify.php.
return apply_filters( 'rocket_js_url', $this->busting_url . $filename );
}
/**
* Get the local file name.
*
* @since 3.2.4
* @access private
* @author Grégory Viguier
*
* @param string $version The file version.
* @return string|bool The name of the file with this version. False if no versions are provided.
*/
private function get_busting_file_name( $version ) {
if ( ! $version ) {
return false;
}
return sprintf( $this->filename_pattern, $version );
}
/**
* Get the local file path.
*
* @since 3.2.4
* @access private
* @author Grégory Viguier
*
* @param string $version The file version.
* @return string|bool Path to the file with this version. False if no versions are provided.
*/
private function get_busting_file_path( $version ) {
if ( ! $version ) {
return false;
}
return $this->busting_path . $this->get_busting_file_name( $version );
}
/**
* Escape a file name, to be used in a regex pattern (delimiter is `/`).
* `%s` conversion specifications are protected.
*
* @since 3.2.4
* @access private
*
* @param string $filename_pattern The file name.
* @return string
*/
private function escape_file_name( $filename_pattern ) {
return preg_quote( $filename_pattern, '/' );
}
/**
* Delete busting files.
*
* @since 3.2.4
* @access private
* @author Grégory Viguier
*
* @param array $files A list of file names.
* @return bool True if files have been deleted (or no files have been provided). False on failure.
*/
private function delete_files( $files ) {
if ( ! $files ) {
// ¯\_(ツ)_/¯
return true;
}
$has_deleted = false;
$error_paths = [];
foreach ( $files as $file_name ) {
if ( ! $this->filesystem->delete( $this->busting_path . $file_name, false, 'f' ) ) {
$error_paths[] = $this->busting_path . $file_name;
} else {
$has_deleted = true;
}
}
if ( $error_paths ) {
// Group all deletion errors into one log.
Logger::error(
'Local file(s) could not be deleted.',
[
self::LOGGER_CONTEXT,
'paths' => $error_paths,
]
);
}
return $has_deleted;
}
/** ----------------------------------------------------------------------------------------- */
/** UPDATE THE LOCAL FILE =================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Add new contents to a file. If the file doesn't exist, it is created.
*
* @since 3.2.4
* @access private
* @author Grégory Viguier
*
* @param string $file_path Path to the file to update.
* @param string $file_contents New contents.
* @return string|bool The file contents on success. False on failure.
*/
private function update_file_contents( $file_path, $file_contents ) {
if ( ! $this->is_busting_dir_writable() ) {
return false;
}
if ( ! rocket_put_content( $file_path, $file_contents ) ) {
Logger::error(
'Contents could not be written into file.',
[
self::LOGGER_CONTEXT,
'path' => $file_path,
]
);
return false;
}
return $file_contents;
}
/**
* Tell if the directory containing the busting file is writable.
*
* @since 3.2
* @access private
* @author Grégory Viguier
*
* @return bool
*/
private function is_busting_dir_writable() {
if ( ! $this->filesystem->exists( $this->busting_path ) ) {
rocket_mkdir_p( $this->busting_path );
}
if ( ! $this->filesystem->is_writable( $this->busting_path ) ) {
Logger::error(
'Directory is not writable.',
[
self::LOGGER_CONTEXT,
'paths' => $this->busting_path,
]
);
return false;
}
return true;
}
/** ----------------------------------------------------------------------------------------- */
/** GET LOCAL/REMOTE CONTENTS =============================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get a file contents. If the file doesn't exist, new contents are fetched remotely.
*
* @since 3.2.4
* @access private
* @author Grégory Viguier
*
* @param string $file_path Path to the file.
* @param string $file_url URL to the remote file.
* @return string|bool The contents on success, false on failure.
*/
private function get_file_or_remote_contents( $file_path, $file_url ) {
$content = $this->get_file_contents( $file_path );
if ( $content ) {
// We have a local file.
return $content;
}
return $this->get_remote_contents( $file_url );
}
/**
* Get a file contents.
*
* @since 3.2.4
* @access private
* @author Grégory Viguier
*
* @param string $file_path Path to the file.
* @return string|bool The contents on success, false on failure.
*/
private function get_file_contents( $file_path ) {
if ( ! $this->filesystem->exists( $file_path ) ) {
Logger::error(
'Local file does not exist.',
[
self::LOGGER_CONTEXT,
'path' => $file_path,
]
);
return false;
}
if ( ! $this->filesystem->is_readable( $file_path ) ) {
Logger::error(
'Local file is not readable.',
[
self::LOGGER_CONTEXT,
'path' => $file_path,
]
);
return false;
}
$content = $this->filesystem->get_contents( $file_path );
if ( ! $content ) {
Logger::error(
'Local file is empty.',
[
self::LOGGER_CONTEXT,
'path' => $file_path,
]
);
return false;
}
return $content;
}
/**
* Get the contents of a URL.
*
* @since 3.2.4
* @access private
* @author Grégory Viguier
*
* @param string $url The URL to request.
* @return string|bool The contents on success. False on failure.
*/
private function get_remote_contents( $url ) {
try {
$response = wp_remote_get( $url );
} catch ( Exception $e ) {
Logger::error(
'Remote file could not be fetched.',
[
self::LOGGER_CONTEXT,
'url' => $url,
'response' => $e->getMessage(),
]
);
return false;
}
if ( is_wp_error( $response ) ) {
Logger::error(
'Remote file could not be fetched.',
[
self::LOGGER_CONTEXT,
'url' => $url,
'response' => $response->get_error_message(),
]
);
return false;
}
$contents = wp_remote_retrieve_body( $response );
if ( ! $contents ) {
Logger::error(
'Remote file could not be fetched.',
[
self::LOGGER_CONTEXT,
'url' => $url,
'response' => $response,
]
);
return false;
}
return $contents;
}
}