add wp-rocket

This commit is contained in:
nguyen dung
2022-02-18 19:09:35 +07:00
parent 39b8cb3612
commit 3110d00ee7
927 changed files with 271703 additions and 2 deletions

View File

@@ -0,0 +1,58 @@
<?php
namespace WP_Rocket\Addon\Busting;
use WP_Rocket\Addon\GoogleTracking\GoogleAnalytics;
use WP_Rocket\Addon\GoogleTracking\GoogleTagManager;
use WP_Rocket\Busting\Facebook_Pickles;
use WP_Rocket\Busting\Facebook_SDK;
/**
* Busting classes Factory
*
* @since 3.6.2
*/
class BustingFactory {
/**
* Base cache busting filepath.
*
* @var string
*/
private $busting_path;
/**
* Base cache busting URL.
*
* @var string
*/
private $busting_url;
/**
* Constructor
*
* @param string $busting_path Base cache busting filepath.
* @param string $busting_url Base cache busting URL.
*/
public function __construct( $busting_path, $busting_url ) {
$this->busting_path = $busting_path;
$this->busting_url = $busting_url;
}
/**
* Creator method
*
* @param string $type Type of busting class to create.
* @return Busting_Interface
*/
public function type( $type ) {
switch ( $type ) {
case 'fbpix':
return new Facebook_Pickles( $this->busting_path, $this->busting_url );
case 'fbsdk':
return new Facebook_SDK( $this->busting_path, $this->busting_url );
case 'ga':
return new GoogleAnalytics( $this->busting_path, $this->busting_url );
case 'gtm':
return new GoogleTagManager( $this->busting_path, $this->busting_url, new GoogleAnalytics( $this->busting_path, $this->busting_url ) );
}
}
}

View File

@@ -0,0 +1,478 @@
<?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;
}
}

View File

@@ -0,0 +1,394 @@
<?php
namespace WPMedia\Cloudflare;
use stdClass;
use Exception;
/**
* Cloudflare API Client.
*
* @since 1.0
*/
class APIClient {
const CLOUDFLARE_API = 'https://api.cloudflare.com/client/v4/';
/**
* Email address for API authentication.
*
* @var string
*/
protected $email;
/**
* API key for API authentication.
*
* @var string
*/
protected $api_key;
/**
* Zone ID.
*
* @var string
*/
protected $zone_id;
/**
* An array of arguments for wp_remote_get.
*
* @var array
*/
protected $args = [];
/**
* HTTP headers.
*
* @var array
*/
protected $headers = [];
/**
* APIClient constructor.
*
* @since 1.0
*
* @param string $useragent The user agent for this plugin or package. For example, "wp-rocket/3.5".
*/
public function __construct( $useragent ) {
$this->args = [
'timeout' => 30, // Increase from default of 5 to give extra time for the plugin to process story for exporting.
'sslverify' => true,
'body' => [],
];
$this->headers = [
'X-Auth-Email' => '',
'X-Auth-Key' => '',
'User-Agent' => $useragent,
'Content-type' => 'application/json',
];
}
/**
* Sets up the API credentials.
*
* @since 1.0
*
* @param string $email The email associated with the Cloudflare account.
* @param string $api_key The API key for the associated Cloudflare account.
* @param string $zone_id The zone ID.
*/
public function set_api_credentials( $email, $api_key, $zone_id ) {
$this->email = $email;
$this->api_key = $api_key;
$this->zone_id = $zone_id;
$this->headers['X-Auth-Email'] = $email;
$this->headers['X-Auth-Key'] = $api_key;
}
/**
* Get zone data.
*
* @since 1.0
*
* @return stdClass Cloudflare response packet.
*/
public function get_zones() {
return $this->get( "zones/{$this->zone_id}" );
}
/**
* Get the zone's page rules.
*
* @since 1.0
*
* @return stdClass Cloudflare response packet.
*/
public function list_pagerules() {
return $this->get( "zones/{$this->zone_id}/pagerules?status=active" );
}
/**
* Purges the cache.
*
* @since 1.0
*
* @return stdClass Cloudflare response packet.
*/
public function purge() {
return $this->delete( "zones/{$this->zone_id}/purge_cache", [ 'purge_everything' => true ] );
}
/**
* Purges the given URLs.
*
* @since 1.0
*
* @param array|null $urls An array of URLs that should be removed from cache.
*
* @return stdClass Cloudflare response packet.
*/
public function purge_files( array $urls ) {
return $this->delete( "zones/{$this->zone_id}/purge_cache", [ 'files' => $urls ] );
}
/**
* Changes the zone's browser cache TTL setting.
*
* @since 1.0
*
* @param string $value New setting's value.
*
* @return stdClass Cloudflare response packet.
*/
public function change_browser_cache_ttl( $value ) {
return $this->change_setting( 'browser_cache_ttl', $value );
}
/**
* Changes the zone's rocket loader setting.
*
* @since 1.0
*
* @param string $value New setting's value.
*
* @return stdClass Cloudflare response packet.
*/
public function change_rocket_loader( $value ) {
return $this->change_setting( 'rocket_loader', $value );
}
/**
* Changes the zone's minify setting.
*
* @since 1.0
*
* @param string $value New setting's value.
*
* @return stdClass Cloudflare response packet.
*/
public function change_minify( $value ) {
return $this->change_setting( 'minify', $value );
}
/**
* Changes the zone's cache level.
*
* @since 1.0
*
* @param string $value New setting's value.
*
* @return stdClass Cloudflare response packet.
*/
public function change_cache_level( $value ) {
return $this->change_setting( 'cache_level', $value );
}
/**
* Changes the zone's development mode.
*
* @since 1.0
*
* @param string $value New setting's value.
*
* @return stdClass Cloudflare response packet.
*/
public function change_development_mode( $value ) {
return $this->change_setting( 'development_mode', $value );
}
/**
* Changes the given setting.
*
* @since 1.0
*
* @param string $setting Name of the setting to change.
* @param string $value New setting's value.
*
* @return stdClass Cloudflare response packet.
*/
protected function change_setting( $setting, $value ) {
return $this->patch( "zones/{$this->zone_id}/settings/{$setting}", [ 'value' => $value ] );
}
/**
* Gets all of the Cloudflare settings.
*
* @since 1.0
*
* @return stdClass Cloudflare response packet.
*/
public function get_settings() {
return $this->get( "zones/{$this->zone_id}/settings" );
}
/**
* Gets Cloudflare's IPs.
*
* @since 1.0
*
* @return stdClass Cloudflare response packet.
*/
public function get_ips() {
return $this->get( '/ips' );
}
/**
* API call method for sending requests using GET.
*
* @since 1.0
*
* @param string $path Path of the endpoint.
* @param array $data Data to be sent along with the request.
*
* @return stdClass Cloudflare response packet.
*/
protected function get( $path, array $data = [] ) {
return $this->request( $path, $data, 'get' );
}
/**
* API call method for sending requests using DELETE.
*
* @since 1.0
*
* @param string $path Path of the endpoint.
* @param array $data Data to be sent along with the request.
*
* @return stdClass Cloudflare response packet.
*/
protected function delete( $path, array $data = [] ) {
return $this->request( $path, $data, 'delete' );
}
/**
* API call method for sending requests using PATCH.
*
* @since 1.0
*
* @param string $path Path of the endpoint.
* @param array $data Data to be sent along with the request.
*
* @return stdClass Cloudflare response packet.
*/
protected function patch( $path, array $data = [] ) {
return $this->request( $path, $data, 'patch' );
}
/**
* API call method for sending requests using GET, POST, PUT, DELETE OR PATCH.
*
* @since 1.0
*
* @author James Bell <james@james-bell.co.uk> - credit for original code adapted for version 1.0.
* @author WP Media
*
* @param string $path Path of the endpoint.
* @param array $data Data to be sent along with the request.
* @param string $method Type of method that should be used ('GET', 'DELETE', 'PATCH').
*
* @return stdClass response object.
* @throws AuthenticationException When email or api key are not set.
* @throws UnauthorizedException When Cloudflare's API returns a 401 or 403.
*/
protected function request( $path, array $data = [], $method = 'get' ) {
if ( '/ips' !== $path && ! $this->is_authorized() ) {
throw new AuthenticationException( 'Authentication information must be provided.' );
}
$response = $this->do_remote_request( $path, $data, $method );
if ( is_wp_error( $response ) ) {
throw new Exception( $response->get_error_message() );
}
$data = wp_remote_retrieve_body( $response );
if ( empty( $data ) ) {
throw new Exception( __( 'Cloudflare did not provide any reply. Please try again later.', 'rocket' ) );
}
$data = json_decode( $data );
if ( empty( $data->success ) ) {
$errors = [];
foreach ( $data->errors as $error ) {
if ( 6003 === $error->code || 9103 === $error->code ) {
$msg = __( 'Incorrect Cloudflare email address or API key.', 'rocket' );
$msg .= ' ' . sprintf(
/* translators: %1$s = opening link; %2$s = closing link */
__( 'Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
// translators: Documentation exists in EN, FR; use localized URL if applicable.
'<a href="' . esc_url( __( 'https://docs.wp-rocket.me/article/18-using-wp-rocket-with-cloudflare/?utm_source=wp_plugin&utm_medium=wp_rocket#add-on', 'rocket' ) ) . '" rel="noopener noreferrer" target="_blank">',
'</a>'
);
throw new Exception( $msg );
}
if ( 7003 === $error->code ) {
$msg = __( 'Incorrect Cloudflare Zone ID.', 'rocket' );
$msg .= ' ' . sprintf(
/* translators: %1$s = opening link; %2$s = closing link */
__( 'Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
// translators: Documentation exists in EN, FR; use localized URL if applicable.
'<a href="' . esc_url( __( 'https://docs.wp-rocket.me/article/18-using-wp-rocket-with-cloudflare/?utm_source=wp_plugin&utm_medium=wp_rocket#add-on', 'rocket' ) ) . '" rel="noopener noreferrer" target="_blank">',
'</a>'
);
throw new Exception( $msg );
}
$errors[] = $error->message;
}
throw new Exception( wp_sprintf_l( '%l ', $errors ) );
}
return $data;
}
/**
* Checks if the email and API key for the API credentials are set.
*
* @since 1.0
*
* @return bool true if authorized; else false.
*/
private function is_authorized() {
return (
isset( $this->email, $this->api_key )
&&
false !== filter_var( $this->email, FILTER_VALIDATE_EMAIL )
);
}
/**
* Does the request remote cURL request.
*
* @since 1.0
*
* @param string $path Path of the endpoint.
* @param array $data Data to be sent along with the request.
* @param string $method Type of method that should be used ('GET', 'DELETE', 'PATCH').
*
* @return array curl response packet.
*/
private function do_remote_request( $path, array $data, $method ) {
$this->args['method'] = isset( $method ) ? strtoupper( $method ) : 'GET';
if ( '/ips' !== $path ) {
$this->args['headers'] = $this->headers;
}
$this->args['body'] = [];
if ( ! empty( $data ) ) {
$this->args['body'] = wp_json_encode( $data );
}
$response = wp_remote_request( self::CLOUDFLARE_API . $path, $this->args );
return $response;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace WPMedia\Cloudflare;
use RuntimeException;
class AuthenticationException extends RuntimeException {}

View File

@@ -0,0 +1,481 @@
<?php
namespace WPMedia\Cloudflare;
use Exception;
use stdClass;
use WP_Error;
use WP_Rocket\Admin\Options_Data;
/**
* Cloudflare
*
* @since 1.0
*/
class Cloudflare {
/**
* Options Data instance.
*
* @var Options_Data
*/
private $options;
/**
* Cloudflare API instance.
*
* @var APIClient
*/
private $api;
/**
* WP_Error if Cloudflare Credentials are not valid.
*
* @var WP_Error
*/
private $cloudflare_api_error;
/**
* Creates an instance of Cloudflare Addon.
*
* @param Options_Data $options WP Rocket options instance.
* @param APIClient $api Cloudflare API instance.
*/
public function __construct( Options_Data $options, APIClient $api ) {
$this->options = $options;
$this->cloudflare_api_error = null;
$this->api = $api;
// Update api_error with WP_Error if credentials are not valid.
// Update API with Cloudflare instance with correct auth data.
$this->get_cloudflare_instance();
}
/**
* Get a Cloudflare\Api instance & the zone_id corresponding to the domain.
*
* @since 1.0
*
* @return Object Cloudflare instance & zone_id if credentials are correct, WP_Error otherwise.
*/
public function get_cloudflare_instance() {
$cf_email = $this->options->get( 'cloudflare_email', null );
$cf_api_key = defined( 'WP_ROCKET_CF_API_KEY' ) ? WP_ROCKET_CF_API_KEY : $this->options->get( 'cloudflare_api_key', null );
$cf_zone_id = $this->options->get( 'cloudflare_zone_id', null );
$is_api_keys_valid_cf = get_transient( 'rocket_cloudflare_is_api_keys_valid' );
if ( false === $is_api_keys_valid_cf ) {
$is_api_keys_valid_cf = $this->is_api_keys_valid( $cf_email, $cf_api_key, $cf_zone_id );
set_transient( 'rocket_cloudflare_is_api_keys_valid', $is_api_keys_valid_cf, 2 * WEEK_IN_SECONDS );
}
if ( is_wp_error( $is_api_keys_valid_cf ) ) {
// Sets Cloudflare API as WP_Error if credentials are not valid.
$this->cloudflare_api_error = $is_api_keys_valid_cf;
return;
}
// Sets Cloudflare Valid Credentials and User Agent.
$this->api->set_api_credentials( $cf_email, $cf_api_key, $cf_zone_id );
}
/**
* Validate Cloudflare input data.
*
* @since 1.0
*
* @param string $cf_email Cloudflare email.
* @param string $cf_api_key Cloudflare API key.
* @param string $cf_zone_id Cloudflare zone ID.
*
* @return stdClass true if credentials are ok, WP_Error otherwise.
*/
public function is_api_keys_valid( $cf_email, $cf_api_key, $cf_zone_id ) {
if ( empty( $cf_email ) || empty( $cf_api_key ) ) {
return new WP_Error(
'cloudflare_credentials_empty',
sprintf(
/* translators: %1$s = opening link; %2$s = closing link */
__( 'Cloudflare email and/or API key are not set. Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
// translators: Documentation exists in EN, FR; use localized URL if applicable.
'<a href="' . esc_url( __( 'https://docs.wp-rocket.me/article/18-using-wp-rocket-with-cloudflare/?utm_source=wp_plugin&utm_medium=wp_rocket#add-on', 'rocket' ) ) . '" rel="noopener noreferrer" target="_blank">',
'</a>'
)
);
}
if ( empty( $cf_zone_id ) ) {
$msg = __( 'Missing Cloudflare Zone ID.', 'rocket' );
$msg .= ' ' . sprintf(
/* translators: %1$s = opening link; %2$s = closing link */
__( 'Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
// translators: Documentation exists in EN, FR; use localized URL if applicable.
'<a href="' . esc_url( __( 'https://docs.wp-rocket.me/article/18-using-wp-rocket-with-cloudflare/?utm_source=wp_plugin&utm_medium=wp_rocket#add-on', 'rocket' ) ) . '" rel="noopener noreferrer" target="_blank">',
'</a>'
);
return new WP_Error( 'cloudflare_no_zone_id', $msg );
}
try {
$this->api->set_api_credentials( $cf_email, $cf_api_key, $cf_zone_id );
$cf_zone = $this->api->get_zones();
$zone_found = false;
$site_url = get_site_url();
if ( function_exists( 'domain_mapping_siteurl' ) ) {
$site_url = domain_mapping_siteurl( $site_url );
}
if ( ! empty( $cf_zone->result ) ) {
$parsed_url = wp_parse_url( $site_url );
if ( false !== strpos( strtolower( $parsed_url['host'] ), $cf_zone->result->name ) ) {
$zone_found = true;
}
}
if ( ! $zone_found ) {
$msg = __( 'It looks like your domain is not set up on Cloudflare.', 'rocket' );
$msg .= ' ' . sprintf(
/* translators: %1$s = opening link; %2$s = closing link */
__( 'Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
// translators: Documentation exists in EN, FR; use localized URL if applicable.
'<a href="' . esc_url( __( 'https://docs.wp-rocket.me/article/18-using-wp-rocket-with-cloudflare/?utm_source=wp_plugin&utm_medium=wp_rocket#add-on', 'rocket' ) ) . '" rel="noopener noreferrer" target="_blank">',
'</a>'
);
return new WP_Error( 'cloudflare_wrong_zone_id', $msg );
}
$this->cloudflare_api_error = null;
return true;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_invalid_auth', $e->getMessage() );
}
}
/**
* Checks if CF has the $action_value set as a Page Rule.
*
* @since 1.0
*
* @param string $action_value Cache_everything.
*
* @return mixed Object|bool true / false if $action_value was found or not, WP_Error otherwise.
*/
public function has_page_rule( $action_value ) {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
try {
$cf_page_rule = $this->api->list_pagerules();
$cf_page_rule_arr = wp_json_encode( $cf_page_rule );
return preg_match( '/' . $action_value . '/', $cf_page_rule_arr );
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_page_rule_failed', $e->getMessage() );
}
}
/**
* Purge Cloudflare cache.
*
* @since 1.0
*
* @return mixed Object|bool true if the purge is successful, WP_Error otherwise.
*/
public function purge_cloudflare() {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
try {
$cf_purge = $this->api->purge();
return true;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_purge_failed', $e->getMessage() );
}
}
/**
* Purge Cloudflare Cache by URL
*
* @since 1.0
*
* @param WP_Post $post The post object.
* @param array $purge_urls URLs cache files to remove.
* @param string $lang The post language.
*
* @return mixed Object|bool true if the purge is successful, WP_Error otherwise
*/
public function purge_by_url( $post, $purge_urls, $lang ) {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
try {
$cf_purge = $this->api->purge_files( $purge_urls );
return true;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_purge_failed', $e->getMessage() );
}
}
/**
* Set the Browser Cache TTL in Cloudflare.
*
* @since 1.0
*
* @param string $mode Value for Cloudflare browser cache TTL.
*
* @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
*/
public function set_browser_cache_ttl( $mode ) {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
try {
$cf_return = $this->api->change_browser_cache_ttl( (int) $mode );
return $mode;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_browser_cache', $e->getMessage() );
}
}
/**
* Set the Cloudflare Rocket Loader.
*
* @since 1.0
*
* @param string $mode Value for Cloudflare Rocket Loader.
*
* @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
*/
public function set_rocket_loader( $mode ) {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
try {
$cf_return = $this->api->change_rocket_loader( $mode );
return $mode;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_rocket_loader', $e->getMessage() );
}
}
/**
* Set the Cloudflare Minification.
*
* @since 1.0
*
* @param string $mode Value for Cloudflare minification.
*
* @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
*/
public function set_minify( $mode ) {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
$cf_minify_settings = [
'css' => $mode,
'html' => $mode,
'js' => $mode,
];
try {
$cf_return = $this->api->change_minify( $cf_minify_settings );
return $mode;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_minification', $e->getMessage() );
}
}
/**
* Set the Cloudflare Caching level.
*
* @since 1.0
*
* @param string $mode Value for Cloudflare caching level.
*
* @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
*/
public function set_cache_level( $mode ) {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
try {
$cf_return = $this->api->change_cache_level( $mode );
return $mode;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_cache_level', $e->getMessage() );
}
}
/**
* Set the Cloudflare Development mode.
*
* @since 1.0
*
* @param string $mode Value for Cloudflare development mode.
*
* @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
*/
public function set_devmode( $mode ) {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
if ( 0 === (int) $mode ) {
$value = 'off';
} else {
$value = 'on';
}
try {
$cf_return = $this->api->change_development_mode( $value );
if ( 'on' === $value ) {
wp_schedule_single_event( time() + 3 * HOUR_IN_SECONDS, 'rocket_cron_deactivate_cloudflare_devmode' );
}
return $value;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_dev_mode', $e->getMessage() );
}
}
/**
* Get all the current Cloudflare settings for a given domain.
*
* @since 1.0
*
* @return mixed bool|Array Array of Cloudflare settings, false if any error connection to Cloudflare.
*/
public function get_settings() {
if ( is_wp_error( $this->cloudflare_api_error ) ) {
return $this->cloudflare_api_error;
}
try {
$cf_settings = $this->api->get_settings();
foreach ( $cf_settings->result as $cloudflare_option ) {
switch ( $cloudflare_option->id ) {
case 'browser_cache_ttl':
$browser_cache_ttl = $cloudflare_option->value;
break;
case 'cache_level':
$cache_level = $cloudflare_option->value;
break;
case 'rocket_loader':
$rocket_loader = $cloudflare_option->value;
break;
case 'minify':
$cf_minify = $cloudflare_option->value;
break;
}
}
$cf_minify_value = 'on';
if ( 'off' === $cf_minify->js || 'off' === $cf_minify->css || 'off' === $cf_minify->html ) {
$cf_minify_value = 'off';
}
$cf_settings_array = [
'cache_level' => $cache_level,
'minify' => $cf_minify_value,
'rocket_loader' => $rocket_loader,
'browser_cache_ttl' => $browser_cache_ttl,
];
return $cf_settings_array;
} catch ( Exception $e ) {
return new WP_Error( 'cloudflare_current_settings', $e->getMessage() );
}
}
/**
* Get Cloudflare IPs. No API validation needed, all exceptions returns the default CF IPs array.
*
* @since 1.0
*
* @return Object Result of API request if successful, default CF IPs otherwise.
*/
public function get_cloudflare_ips() {
$cf_ips = get_transient( 'rocket_cloudflare_ips' );
if ( false !== $cf_ips ) {
return $cf_ips;
}
try {
$cf_ips = $this->api->get_ips();
if ( empty( $cf_ips->success ) ) {
// Set default IPs from Cloudflare if call to Cloudflare /ips API does not contain a success.
// Prevents from making API calls on each page load.
$cf_ips = $this->get_default_ips();
}
} catch ( Exception $e ) {
// Set default IPs from Cloudflare if call to Cloudflare /ips API fails.
// Prevents from making API calls on each page load.
$cf_ips = $this->get_default_ips();
}
set_transient( 'rocket_cloudflare_ips', $cf_ips, 2 * WEEK_IN_SECONDS );
return $cf_ips;
}
/**
* Get default Cloudflare IPs.
*
* @since 1.0
*
* @return stdClass Default Cloudflare connecting IPs.
*/
private function get_default_ips() {
$cf_ips = (object) [
'result' => (object) [],
'success' => true,
'errors' => [],
'messages' => [],
];
$cf_ips->result->ipv4_cidrs = [
'173.245.48.0/20',
'103.21.244.0/22',
'103.22.200.0/22',
'103.31.4.0/22',
'141.101.64.0/18',
'108.162.192.0/18',
'190.93.240.0/20',
'188.114.96.0/20',
'197.234.240.0/22',
'198.41.128.0/17',
'162.158.0.0/15',
'104.16.0.0/12',
'172.64.0.0/13',
'131.0.72.0/22',
];
$cf_ips->result->ipv6_cidrs = [
'2400:cb00::/32',
'2606:4700::/32',
'2803:f800::/32',
'2405:b500::/32',
'2405:8100::/32',
'2a06:98c0::/29',
'2c0f:f248::/32',
];
return $cf_ips;
}
}

View File

@@ -0,0 +1,608 @@
<?php
namespace WPMedia\Cloudflare;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Admin\Options;
/**
* Cloudflare Subscriber.
*
* @since 1.0
*/
class Subscriber implements Subscriber_Interface {
/**
* Cloudflare instance.
*
* @var Cloudflare
*/
private $cloudflare;
/**
* Options Data instance.
*
* @var Options_Data
*/
private $options;
/**
* Options instance.
*
* @var Options
*/
private $options_api;
/**
* Creates an instance of the Cloudflare Subscriber.
*
* @param Cloudflare $cloudflare Cloudflare instance.
* @param Options_Data $options WP Rocket options instance.
* @param Options $options_api Options instance.
*/
public function __construct( Cloudflare $cloudflare, Options_Data $options, Options $options_api ) {
$this->options = $options;
$this->options_api = $options_api;
$this->cloudflare = $cloudflare;
}
/**
* Gets the subscribed events.
*
* @since 1.0
*
* @return array subscribed events => callbacks.
*/
public static function get_subscribed_events() {
$slug = rocket_get_constant( 'WP_ROCKET_SLUG', 'wp_rocket_settings' );
return [
'rocket_varnish_ip' => 'set_varnish_localhost',
'rocket_varnish_purge_request_host' => 'set_varnish_purge_request_host',
'rocket_cron_deactivate_cloudflare_devmode' => 'deactivate_devmode',
'after_rocket_clean_domain' => 'auto_purge',
'after_rocket_clean_post' => [ 'auto_purge_by_url', 10, 3 ],
'admin_post_rocket_purge_cloudflare' => 'purge_cache',
'init' => [ 'set_real_ip', 1 ],
'update_option_' . $slug => [ 'save_cloudflare_options', 10, 2 ],
'pre_update_option_' . $slug => [ 'save_cloudflare_old_settings', 10, 2 ],
'admin_notices' => [
[ 'maybe_display_purge_notice' ],
[ 'maybe_print_update_settings_notice' ],
],
];
}
/**
* Sets the Varnish IP to localhost if Cloudflare is active.
*
* @since 1.0
*
* @param string|array $varnish_ip Varnish IP.
*
* @return array
*/
public function set_varnish_localhost( $varnish_ip ) {
if ( ! $this->should_filter_varnish() ) {
return $varnish_ip;
}
if ( is_string( $varnish_ip ) ) {
$varnish_ip = (array) $varnish_ip;
}
$varnish_ip[] = 'localhost';
return $varnish_ip;
}
/**
* Sets the Host header to the website domain if Cloudflare is active.
*
* @since 1.0
*
* @param string $host the host header value.
*
* @return string
*/
public function set_varnish_purge_request_host( $host ) {
if ( ! $this->should_filter_varnish() ) {
return $host;
}
return wp_parse_url( home_url(), PHP_URL_HOST );
}
/**
* Checks if we should filter the value for the Varnish purge.
*
* @since 1.0
*
* @return bool
*/
private function should_filter_varnish() {
// This filter is documented in inc/classes/subscriber/Addons/Varnish/VarnishSubscriber.php.
if ( ! apply_filters( 'do_rocket_varnish_http_purge', false ) && ! $this->options->get( 'varnish_auto_purge', 0 ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
return false;
}
return true;
}
/**
* Automatically set Cloudflare development mode value to off after 3 hours to reflect Cloudflare behaviour.
*
* @since 1.0
*/
public function deactivate_devmode() {
$this->options->set( 'cloudflare_devmode', 'off' );
$this->options_api->set( 'settings', $this->options->get_options() );
}
/**
* Purge Cloudflare cache automatically if Cache Everything is set as a Page Rule.
*
* @since 1.0
*/
public function auto_purge() {
if ( ! current_user_can( 'rocket_purge_cloudflare_cache' ) ) {
return;
}
$cf_cache_everything = $this->cloudflare->has_page_rule( 'cache_everything' );
if ( is_wp_error( $cf_cache_everything ) || ! $cf_cache_everything ) {
return;
}
// Purge CloudFlare.
$this->cloudflare->purge_cloudflare();
}
/**
* Purge Cloudflare cache URLs automatically if Cache Everything is set as a Page Rule.
*
* @since 1.0
*
* @param WP_Post $post The post object.
* @param array $purge_urls URLs cache files to remove.
* @param string $lang The post language.
*/
public function auto_purge_by_url( $post, $purge_urls, $lang ) {
if ( ! current_user_can( 'rocket_purge_cloudflare_cache' ) ) {
return;
}
$cf_cache_everything = $this->cloudflare->has_page_rule( 'cache_everything' );
if ( is_wp_error( $cf_cache_everything ) || ! $cf_cache_everything ) {
return;
}
// Add home URL and feeds URLs to Cloudflare clean cache URLs list.
$purge_urls[] = get_rocket_i18n_home_url( $lang );
$feed_urls = [];
$feed_urls[] = get_feed_link();
$feed_urls[] = get_feed_link( 'comments_' );
// this filter is documented in inc/functions/files.php.
$feed_urls = apply_filters( 'rocket_clean_home_feeds', $feed_urls );
$purge_urls = array_unique( array_merge( $purge_urls, $feed_urls ) );
// Purge CloudFlare.
$this->cloudflare->purge_by_url( $post, $purge_urls, $lang );
}
/**
* Purge CloudFlare cache.
*
* @since 1.0
*/
public function purge_cache_no_die() {
if ( ! current_user_can( 'rocket_purge_cloudflare_cache' ) ) {
return;
}
// Purge CloudFlare.
$cf_purge = $this->cloudflare->purge_cloudflare();
if ( is_wp_error( $cf_purge ) ) {
$cf_purge_result = [
'result' => 'error',
// translators: %s = CloudFare API return message.
'message' => sprintf( __( '<strong>WP Rocket:</strong> %s', 'rocket' ), $cf_purge->get_error_message() ),
];
} else {
$cf_purge_result = [
'result' => 'success',
'message' => __( '<strong>WP Rocket:</strong> Cloudflare cache successfully purged.', 'rocket' ),
];
}
set_transient( get_current_user_id() . '_cloudflare_purge_result', $cf_purge_result );
}
/**
* Purge CloudFlare cache.
*
* @since 1.0
*/
public function purge_cache() {
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'rocket_purge_cloudflare' ) ) {
wp_nonce_ays( '' );
}
$this->purge_cache_no_die();
wp_safe_redirect( esc_url_raw( wp_get_referer() ) );
defined( 'WPMEDIA_IS_TESTING' ) ? wp_die() : exit;
}
/**
* Set Real IP from CloudFlare.
*
* @since 1.0
* @source cloudflare.php - https://wordpress.org/plugins/cloudflare/
*/
public function set_real_ip() {
// only run this logic if the REMOTE_ADDR is populated, to avoid causing notices in CLI mode.
if ( ! isset( $_SERVER['HTTP_CF_CONNECTING_IP'], $_SERVER['REMOTE_ADDR'] ) ) {
return;
}
$cf_ips_values = $this->cloudflare->get_cloudflare_ips();
$cf_ip_ranges = $cf_ips_values->result->ipv6_cidrs;
$ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
$ipv6 = get_rocket_ipv6_full( $ip );
if ( false === strpos( $ip, ':' ) ) {
// IPV4: Update the REMOTE_ADDR value if the current REMOTE_ADDR value is in the specified range.
$cf_ip_ranges = $cf_ips_values->result->ipv4_cidrs;
}
foreach ( $cf_ip_ranges as $range ) {
if (
( strpos( $ip, ':' ) && rocket_ipv6_in_range( $ipv6, $range ) )
||
( false === strpos( $ip, ':' ) && rocket_ipv4_in_range( $ip, $range ) )
) {
$_SERVER['REMOTE_ADDR'] = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
break;
}
}
}
/**
* This notice is displayed after purging the CloudFlare cache.
*
* @since 1.0
*/
public function maybe_display_purge_notice() {
if ( ! current_user_can( 'rocket_purge_cloudflare_cache' ) ) {
return;
}
$user_id = get_current_user_id();
$notice = get_transient( $user_id . '_cloudflare_purge_result' );
if ( ! $notice ) {
return;
}
delete_transient( $user_id . '_cloudflare_purge_result' );
rocket_notice_html(
[
'status' => $notice['result'],
'message' => $notice['message'],
]
);
}
/**
* This notice is displayed after modifying the CloudFlare settings.
*
* @since 1.0
*/
public function maybe_print_update_settings_notice() {
$screen = get_current_screen();
if ( ! current_user_can( 'rocket_manage_options' ) || 'settings_page_wprocket' !== $screen->id ) {
return;
}
$user_id = get_current_user_id();
$notices = get_transient( $user_id . '_cloudflare_update_settings' );
if ( ! $notices ) {
return;
}
$errors = '';
$success = '';
delete_transient( $user_id . '_cloudflare_update_settings' );
foreach ( $notices as $notice ) {
if ( 'error' === $notice['result'] ) {
$errors .= $notice['message'] . '<br>';
} elseif ( 'success' === $notice['result'] ) {
$success .= $notice['message'] . '<br>';
}
}
if ( ! empty( $success ) ) {
rocket_notice_html(
[
'message' => $success,
]
);
}
if ( ! empty( $errors ) ) {
rocket_notice_html(
[
'status' => 'error',
'message' => $errors,
]
);
}
}
/**
* Save Cloudflare dev mode admin option.
*
* @since 3.5.2
* @author Soponar Cristina
*
* @param string $devmode New value for Cloudflare dev mode.
*/
private function save_cloudflare_devmode( $devmode ) {
$cloudflare_dev_mode_return = $this->cloudflare->set_devmode( $devmode );
if ( is_wp_error( $cloudflare_dev_mode_return ) ) {
return [
'result' => 'error',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare development mode error: %s', 'rocket' ), $cloudflare_dev_mode_return->get_error_message() ),
];
}
return [
'result' => 'success',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare development mode %s', 'rocket' ), $cloudflare_dev_mode_return ),
];
}
/**
* Save Cloudflare cache_level admin option.
*
* @since 3.5.2
* @author Soponar Cristina
*
* @param string $cache_level New value for Cloudflare cache_level.
*/
private function save_cache_level( $cache_level ) {
// Set Cache Level to Aggressive.
$cf_cache_level_return = $this->cloudflare->set_cache_level( $cache_level );
if ( is_wp_error( $cf_cache_level_return ) ) {
return [
'result' => 'error',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare cache level error: %s', 'rocket' ), $cf_cache_level_return->get_error_message() ),
];
}
if ( 'aggressive' === $cf_cache_level_return ) {
$cf_cache_level_return = _x( 'Standard', 'Cloudflare caching level', 'rocket' );
}
return [
'result' => 'success',
// translators: %s is the caching level returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare cache level set to %s', 'rocket' ), $cf_cache_level_return ),
];
}
/**
* Save Cloudflare minify admin option.
*
* @since 3.5.2
* @author Soponar Cristina
*
* @param string $minify New value for Cloudflare minify.
*/
private function save_minify( $minify ) {
$cf_minify_return = $this->cloudflare->set_minify( $minify );
if ( is_wp_error( $cf_minify_return ) ) {
return [
'result' => 'error',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare minification error: %s', 'rocket' ), $cf_minify_return->get_error_message() ),
];
}
return [
'result' => 'success',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare minification %s', 'rocket' ), $cf_minify_return ),
];
}
/**
* Save Cloudflare rocket loader admin option.
*
* @since 3.5.2
* @author Soponar Cristina
*
* @param string $rocket_loader New value for Cloudflare rocket loader.
*/
private function save_rocket_loader( $rocket_loader ) {
$cf_rocket_loader_return = $this->cloudflare->set_rocket_loader( $rocket_loader );
if ( is_wp_error( $cf_rocket_loader_return ) ) {
return [
'result' => 'error',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare rocket loader error: %s', 'rocket' ), $cf_rocket_loader_return->get_error_message() ),
];
}
return [
'result' => 'success',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare rocket loader %s', 'rocket' ), $cf_rocket_loader_return ),
];
}
/**
* Save Cloudflare browser cache ttl admin option.
*
* @since 3.5.2
* @author Soponar Cristina
*
* @param int $browser_cache_ttl New value for Cloudflare browser cache ttl.
*/
private function save_browser_cache_ttl( $browser_cache_ttl ) {
$cf_browser_cache_return = $this->cloudflare->set_browser_cache_ttl( $browser_cache_ttl );
if ( is_wp_error( $cf_browser_cache_return ) ) {
return [
'result' => 'error',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare browser cache error: %s', 'rocket' ), $cf_browser_cache_return->get_error_message() ),
];
}
return [
'result' => 'success',
// translators: %s is the message returned by the CloudFlare API.
'message' => '<strong>' . __( 'WP Rocket: ', 'rocket' ) . '</strong>' . sprintf( __( 'Cloudflare browser cache set to %s seconds', 'rocket' ), $cf_browser_cache_return ),
];
}
/**
* Save Cloudflare auto settings admin option.
*
* @since 3.5.2
* @author Soponar Cristina
*
* @param array $auto_settings New value for Cloudflare auto_settings.
* @param array $old_settings Cloudflare cloudflare_old_settings.
*/
private function save_cloudflare_auto_settings( $auto_settings, $old_settings ) {
$cf_old_settings = explode( ',', $old_settings );
$cloudflare_update_result = [];
// Set Cache Level to Aggressive.
$cf_cache_level = isset( $cf_old_settings[0] ) && 0 === $auto_settings ? 'basic' : 'aggressive';
$cloudflare_update_result[] = $this->save_cache_level( $cf_cache_level );
// Active Minification for HTML, CSS & JS.
$cf_minify = isset( $cf_old_settings[1] ) && 0 === $auto_settings ? $cf_old_settings[1] : 'on';
$cloudflare_update_result[] = $this->save_minify( $cf_minify );
// Deactivate Rocket Loader to prevent conflicts.
$cf_rocket_loader = isset( $cf_old_settings[2] ) && 0 === $auto_settings ? $cf_old_settings[2] : 'off';
$cloudflare_update_result[] = $this->save_rocket_loader( $cf_rocket_loader );
// Set Browser cache to 1 year.
$cf_browser_cache_ttl = isset( $cf_old_settings[3] ) && 0 === $auto_settings ? $cf_old_settings[3] : '31536000';
$cloudflare_update_result[] = $this->save_browser_cache_ttl( $cf_browser_cache_ttl );
return $cloudflare_update_result;
}
/**
* Save Cloudflare admin options.
*
* @since 1.0
*
* @param array $old_value An array of previous values for the settings.
* @param array $value An array of submitted values for the settings.
*/
public function save_cloudflare_options( $old_value, $value ) {
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
$is_api_keys_valid_cloudflare = get_transient( 'rocket_cloudflare_is_api_keys_valid' );
$submit_cloudflare_view = false;
if (
( isset( $old_value['cloudflare_email'], $value['cloudflare_email'] ) && $old_value['cloudflare_email'] !== $value['cloudflare_email'] )
||
( isset( $old_value['cloudflare_api_key'], $value['cloudflare_api_key'] ) && $old_value['cloudflare_api_key'] !== $value['cloudflare_api_key'] )
||
( isset( $old_value['cloudflare_zone_id'], $value['cloudflare_zone_id'] ) && $old_value['cloudflare_zone_id'] !== $value['cloudflare_zone_id'] )
) {
delete_transient( 'rocket_cloudflare_is_api_keys_valid' );
$is_api_keys_valid_cloudflare = $this->cloudflare->is_api_keys_valid( $value['cloudflare_email'], $value['cloudflare_api_key'], $value['cloudflare_zone_id'], true );
set_transient( 'rocket_cloudflare_is_api_keys_valid', $is_api_keys_valid_cloudflare, 2 * WEEK_IN_SECONDS );
$submit_cloudflare_view = true;
}
if ( ( isset( $old_value['cloudflare_devmode'], $value['cloudflare_devmode'] ) && (int) $old_value['cloudflare_devmode'] !== (int) $value['cloudflare_devmode'] ) ||
( isset( $old_value['cloudflare_auto_settings'], $value['cloudflare_auto_settings'] ) && (int) $old_value['cloudflare_auto_settings'] !== (int) $value['cloudflare_auto_settings'] ) ) {
$submit_cloudflare_view = true;
}
// Revalidate Cloudflare credentials if transient is false.
if ( false === $is_api_keys_valid_cloudflare ) {
if ( isset( $value['cloudflare_email'], $value['cloudflare_api_key'], $value['cloudflare_zone_id'] ) ) {
$is_api_keys_valid_cloudflare = $this->cloudflare->is_api_keys_valid( $value['cloudflare_email'], $value['cloudflare_api_key'], $value['cloudflare_zone_id'] );
} else {
$is_api_keys_valid_cloudflare = false;
}
set_transient( 'rocket_cloudflare_is_api_keys_valid', $is_api_keys_valid_cloudflare, 2 * WEEK_IN_SECONDS );
}
// If is submit CF view & CF Credentials are invalid, display error and bail out.
if ( is_wp_error( $is_api_keys_valid_cloudflare ) && $submit_cloudflare_view ) {
$cloudflare_error_message = $is_api_keys_valid_cloudflare->get_error_message();
add_settings_error( 'general', 'cloudflare_api_key_invalid', __( 'WP Rocket: ', 'rocket' ) . '</strong>' . $cloudflare_error_message . '<strong>', 'error' );
set_transient( get_current_user_id() . '_cloudflare_update_settings', [] );
return;
}
// Update CloudFlare Development Mode.
$cloudflare_update_result = [];
if ( isset( $old_value['cloudflare_devmode'], $value['cloudflare_devmode'] ) && (int) $old_value['cloudflare_devmode'] !== (int) $value['cloudflare_devmode'] ) {
$cloudflare_update_result[] = $this->save_cloudflare_devmode( $value['cloudflare_devmode'] );
}
// Update CloudFlare settings.
if ( isset( $old_value['cloudflare_auto_settings'], $value['cloudflare_auto_settings'] ) && (int) $old_value['cloudflare_auto_settings'] !== (int) $value['cloudflare_auto_settings'] ) {
$cloudflare_update_result = array_merge( $cloudflare_update_result, $this->save_cloudflare_auto_settings( $value['cloudflare_auto_settings'], $value['cloudflare_old_settings'] ) );
}
set_transient( get_current_user_id() . '_cloudflare_update_settings', $cloudflare_update_result );
}
/**
* Save Cloudflare old settings when the auto settings option is enabled.
*
* @since 1.0
*
* @param array $value An array of previous values for the settings.
* @param array $old_value An array of submitted values for the settings.
*
* @return array settings with old settings.
*/
public function save_cloudflare_old_settings( $value, $old_value ) {
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return $value;
}
// Save old CloudFlare settings.
if (
isset( $value['cloudflare_auto_settings'], $old_value ['cloudflare_auto_settings'] )
&&
$value['cloudflare_auto_settings'] !== $old_value ['cloudflare_auto_settings']
&&
1 === $value['cloudflare_auto_settings']
) {
$cf_settings = $this->cloudflare->get_settings();
$value['cloudflare_old_settings'] = ! is_wp_error( $cf_settings )
? implode( ',', array_filter( $cf_settings ) )
: '';
}
return $value;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace WPMedia\Cloudflare;
use RuntimeException;
class UnauthorizedException extends RuntimeException {}

View File

@@ -0,0 +1,51 @@
{
"name": "wp-media/cloudflare",
"description": "Cloudflare Addon",
"homepage": "https://github.com/wp-media/cloudflare",
"license": "GPL-2.0+",
"authors": [
{
"name": "WP Media",
"email": "contact@wp-media.me",
"homepage": "https://wp-media.me"
}
],
"type": "library",
"config": {
"sort-packages": true
},
"support": {
"issues": "https://github.com/wp-media/cloudflare/issues",
"source": "https://github.com/wp-media/cloudflare"
},
"require-dev": {
"php": "^5.6 || ^7",
"brain/monkey": "^2.0",
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
"phpcompatibility/phpcompatibility-wp": "^2.0",
"phpunit/phpunit": "^5.7 || ^7",
"wp-coding-standards/wpcs": "^2",
"wp-media/event-manager": "^3.1",
"wp-media/options": "^3.0",
"wp-media/phpunit": "^1.0"
},
"autoload": {
"psr-4": { "WPMedia\\Cloudflare\\": "." },
"exclude-from-classmap": [ "/Tests/" ]
},
"autoload-dev": {
"psr-4": { "WPMedia\\Cloudflare\\Tests\\": "Tests/" }
},
"scripts": {
"test-unit": "\"vendor/bin/wpmedia-phpunit\" unit path=Tests/Unit",
"test-integration": "\"vendor/bin/wpmedia-phpunit\" integration path=Tests/Integration/",
"run-tests": [
"@test-unit",
"@test-integration"
],
"install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run",
"phpcs": "phpcs --basepath=.",
"phpcs-changed": "./bin/phpcs-changed.sh",
"phpcs:fix": "phpcbf"
}
}

View File

@@ -0,0 +1,192 @@
<?php
namespace WP_Rocket\Addon\FacebookTracking;
use WP_Rocket\Addon\Busting\BustingFactory;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Admin\Options_Data as Options;
/**
* Event subscriber for Facebook tracking cache busting.
*
* @since 3.2
*/
class Subscriber implements Subscriber_Interface {
/**
* Name of the cron.
*
* @var string
* @since 3.2
*/
const CRON_NAME = 'rocket_facebook_tracking_cache_update';
/**
* Instance of the Busting Factory class.
*
* @var BustingFactory
* @since 3.2
*/
private $busting_factory;
/**
* Instance of the Option_Data class.
*
* @var Options
* @since 3.2
*/
private $options;
/**
* Constructor.
*
* @since 3.2
*
* @param BustingFactory $busting_factory Instance of the Busting Factory class.
* @param Options $options Instance of the Options_Data class.
*/
public function __construct( BustingFactory $busting_factory, Options $options ) {
$this->busting_factory = $busting_factory;
$this->options = $options;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.2
*
* @return array
*/
public static function get_subscribed_events() {
$events = [
'cron_schedules' => 'add_schedule',
'init' => 'schedule_cache_update',
self::CRON_NAME => 'update_cache',
'rocket_purge_cache' => 'delete_cache',
'rocket_buffer' => 'cache_busting_facebook_tracking',
];
return $events;
}
/**
* Add weekly interval to cron schedules.
*
* @since 3.2
*
* @param array $schedules An array of intervals used by cron jobs.
* @return array
*/
public function add_schedule( $schedules ) {
if ( ! $this->is_busting_active() ) {
return $schedules;
}
$schedules['weekly'] = [
'interval' => 604800,
'display' => __( 'weekly', 'rocket' ),
];
return $schedules;
}
/**
* (Un)Schedule the auto-update of the cache busting files.
*
* @since 3.2
*/
public function schedule_cache_update() {
$scheduled = wp_next_scheduled( self::CRON_NAME );
if ( ! $this->is_busting_active() ) {
if ( $scheduled ) {
wp_clear_scheduled_hook( self::CRON_NAME );
}
return;
}
if ( ! $scheduled ) {
wp_schedule_event( time(), 'weekly', self::CRON_NAME );
}
}
/**
* Update the Facebook Pixel cache busting files.
*
* @since 3.2
*
* @return bool
*/
public function update_cache() {
if ( ! $this->is_busting_active() ) {
return false;
}
$html = $this->busting_factory->type( 'fbsdk' )->refresh();
return $this->busting_factory->type( 'fbpix' )->refresh_all();
}
/**
* Delete Facebook Pixel cache busting files.
*
* @since 3.2
* @since 3.6 Argument replacement.
*
* @param string $type Type of cache clearance: 'all', 'post', 'term', 'user', 'url'.
* @return bool
*/
public function delete_cache( $type ) {
if ( 'all' !== $type || ! $this->is_busting_active() ) {
return false;
}
$html = $this->busting_factory->type( 'fbsdk' )->delete();
return $this->busting_factory->type( 'fbpix' )->delete_all();
}
/**
* Process the cache busting on the HTML contents.
*
* @since 3.2
*
* @param string $html HTML contents.
* @return string
*/
public function cache_busting_facebook_tracking( $html ) {
if ( ! $this->is_allowed() ) {
return $html;
}
$html = $this->busting_factory->type( 'fbsdk' )->replace_url( $html );
return $this->busting_factory->type( 'fbpix' )->replace_url( $html );
}
/**
* Tell if the cache busting should happen.
*
* @since 3.2
*
* @return bool
*/
private function is_allowed() {
if ( defined( 'DONOTROCKETOPTIMIZE' ) && DONOTROCKETOPTIMIZE ) {
return false;
}
return $this->is_busting_active();
}
/**
* Tell if the cache busting option is active.
*
* @since 3.2
*
* @return bool
*/
private function is_busting_active() {
return (bool) $this->options->get( 'facebook_pixel_cache', 0 );
}
}

View File

@@ -0,0 +1,245 @@
<?php
namespace WP_Rocket\Addon\GoogleTracking;
use WP_Rocket\Addon\Busting\FileBustingTrait;
use WP_Rocket\Busting\Abstract_Busting;
use WP_Rocket\Logger\Logger;
/**
* Manages the cache busting of the Google Analytics file.
*
* @since 3.1
*/
class GoogleAnalytics extends Abstract_Busting {
use FileBustingTrait;
/**
* Context used for the logger.
*
* @var string
* @since 3.2.4
* @author Grégory Viguier
*/
const LOGGER_CONTEXT = 'gg analytics';
/**
* Google Analytics URL.
*
* @var string
* @since 3.1
* @access protected
* @author Remy Perona
*/
protected $url = 'https://www.google-analytics.com/analytics.js';
/**
* File name (local).
* %s is a "version": a md5 hash of the file contents.
*
* @var string
* @since 3.2.4
* @access protected
* @author Grégory Viguier
*/
protected $filename_pattern = 'ga-%s.js';
/**
* Current file version (local): a md5 hash of the file contents.
*
* @var string
* @since 3.2.4
* @access protected
* @author Grégory Viguier
*/
protected $file_version;
/**
* Flag to track the replacement.
*
* @var bool
* @since 3.1
* @access protected
* @author Remy Perona
*/
protected $is_replaced = false;
/**
* Filesystem object.
*
* @var object
* @since 3.2.4
* @access protected
* @author Grégory Viguier
*/
protected $filesystem = false;
/**
* Constructor.
*
* @since 3.1
* @access public
* @author Remy Perona
*
* @param string $busting_path Path to the busting directory.
* @param string $busting_url URL of the busting directory.
*/
public function __construct( $busting_path, $busting_url ) {
$this->busting_path = $busting_path . 'google-tracking/';
$this->busting_url = $busting_url . 'google-tracking/';
$this->filesystem = rocket_direct_filesystem();
}
/** ----------------------------------------------------------------------------------------- */
/** PUBLIC METHODS ========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Performs the replacement process.
*
* @since 3.1
* @access public
* @author Remy Perona
*
* @param string $html HTML content.
* @return string
*/
public function replace_url( $html ) {
$this->is_replaced = false;
$tag = $this->find( '<script\s*(?<attr>[^>]*)?>(?<content>.*)?<\/script>', $html );
if ( ! $tag ) {
return $html;
}
Logger::info(
'GOOGLE ANALYTICS CACHING PROCESS STARTED.',
[
self::LOGGER_CONTEXT,
'tag' => $tag,
]
);
if ( ! $this->save( $this->url ) ) {
return $html;
}
$replace_tag = preg_replace( '/(?:https?:)?\/\/www\.google-analytics\.com\/analytics\.js/i', $this->get_busting_url(), $tag );
$html = str_replace( $tag, $replace_tag, $html );
$this->is_replaced = true;
Logger::info(
'Google Analytics caching process succeeded.',
[
self::LOGGER_CONTEXT,
'file' => $this->get_busting_path(),
]
);
return $html;
}
/**
* Tell if the replacement was sucessful or not.
*
* @since 3.1
* @access public
* @author Remy Perona
*
* @return bool
*/
public function is_replaced() {
return $this->is_replaced;
}
/**
* Saves the content of the URL to cache to the busting file.
*
* @since 3.2.4
* @access public
* @author Grégory Viguier
*
* @param string $url URL to get the content from.
* @return bool
*/
public function refresh_save( $url ) {
// Before doing anything, make sure the busting file can be created.
if ( ! $this->is_busting_dir_writable() ) {
return false;
}
// Get remote content.
$content = $this->get_remote_contents( $url );
if ( ! $content ) {
// Could not get the remote contents.
return false;
}
$version = md5( $content );
$path = $this->get_busting_file_path( $version );
return $this->update_file_contents( $path, $content );
}
/** ----------------------------------------------------------------------------------------- */
/** REMOTE FILE ============================================================================= */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the Google Analytics URL.
*
* @since 3.1
* @access public
* @author Remy Perona
*
* @return string
*/
public function get_url() {
return $this->url;
}
/** ----------------------------------------------------------------------------------------- */
/** VARIOUS INTERNAL TOOLS ================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Searches for element(s) in the DOM.
*
* @since 3.1
* @access public
* @author Remy Perona
*
* @param string $pattern Pattern to match.
* @param string $html HTML content.
* @return string
*/
protected function find( $pattern, $html ) {
preg_match_all( '/' . $pattern . '/si', $html, $all_matches, PREG_SET_ORDER );
$matches = array_map(
function( $match ) {
if (
! preg_match( '/src\s*=\s*[\'"]\s*(?:https?:)?\/\/www\.google-analytics\.com\/analytics\.js\s*[\'"]/i', $match['attr'] . $match['content'] )
&&
false === strpos( $match['content'], 'GoogleAnalyticsObject' )
) {
return;
}
return $match[0];
},
$all_matches
);
$matches = array_values( array_filter( $matches ) );
if ( ! $matches ) {
return false;
}
return $matches[0];
}
}

View File

@@ -0,0 +1,200 @@
<?php
namespace WP_Rocket\Addon\GoogleTracking;
use WP_Rocket\Addon\Busting\FileBustingTrait;
use WP_Rocket\Logger\Logger;
use WP_Rocket\Busting\Abstract_Busting;
use WP_Filesystem_Direct;
/**
* Manages the cache busting of the Google Tag Manager file
*
* @since 3.1
*/
class GoogleTagManager extends Abstract_Busting {
use FileBustingTrait;
/**
* Context used for the logger.
*
* @var string
* @since 3.2.4
*/
const LOGGER_CONTEXT = 'gg tag manager';
/**
* File name (local).
* %s is a "version": a md5 hash of the file contents.
*
* @var string
* @since 3.2.4
* @access protected
*/
protected $filename_pattern = 'gtm-%s.js';
/**
* Current file version (local): a md5 hash of the file contents.
*
* @var string
* @since 3.2.4
* @access protected
*/
protected $file_version;
/**
* Filesystem object.
*
* @var object
* @since 3.2.4
* @access protected
*/
protected $filesystem = false;
/**
* Google Analytics object.
*
* @var object
* @since 3.2.4
* @access protected
*/
protected $ga_busting = false;
/**
* Constructor.
*
* @since 3.1
* @access public
*
* @param string $busting_path Path to the busting directory.
* @param string $busting_url URL of the busting directory.
* @param GoogleAnalytics $ga_busting A GoogleAnalytics instance.
* @param WP_Filesystem_Direct $filesystem Instance of the filesystem handler.
*/
public function __construct( $busting_path, $busting_url, GoogleAnalytics $ga_busting, $filesystem = null ) {
$blog_id = get_current_blog_id();
$this->busting_path = $busting_path . $blog_id . '/';
$this->busting_url = $busting_url . $blog_id . '/';
$this->ga_busting = $ga_busting;
$this->filesystem = is_null( $filesystem ) ? rocket_direct_filesystem() : $filesystem;
}
/** ----------------------------------------------------------------------------------------- */
/** PUBLIC METHODS ========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Performs the replacement process.
*
* @since 3.1
* @access public
*
* @param string $html HTML content.
* @return string
*/
public function replace_url( $html ) {
$script = $this->find( '<script(\s+[^>]+)?\s+src\s*=\s*[\'"]\s*?((?:https?:)?\/\/www\.googletagmanager\.com(?:.+)?)\s*?[\'"]([^>]+)?\/?>', $html );
if ( ! $script ) {
return $html;
}
// replace relative protocol // with full https://.
$gtm_url = preg_replace( '/^\/\//', 'https://', $script[2] );
Logger::info(
'GOOGLE TAG MANAGER CACHING PROCESS STARTED.',
[
self::LOGGER_CONTEXT,
'tag' => $script,
]
);
if ( ! $this->save( $gtm_url ) ) {
return $html;
}
$replace_script = str_replace( $script[2], $this->get_busting_url(), $script[0] );
$replace_script = str_replace( '<script', '<script data-no-minify="1"', $replace_script );
$html = str_replace( $script[0], $replace_script, $html );
Logger::info(
'Google Tag Manager caching process succeeded.',
[
self::LOGGER_CONTEXT,
'file' => $this->get_busting_path(),
]
);
return $html;
}
/**
* Saves the content of the URL to cache to the busting file.
*
* @since 3.2
* @access public
*
* @param string $url URL to get the content from.
* @return bool
*/
public function refresh_save( $url ) {
// Before doing anything, make sure the busting file can be created.
if ( ! $this->is_busting_dir_writable() ) {
return false;
}
// Get remote content.
$content = $this->get_remote_contents( $url );
if ( ! $content ) {
// Could not get the remote contents.
return false;
}
$version = md5( $content );
$path = $this->get_busting_file_path( $version );
$content = $this->replace_ga_url( $content );
return $this->update_file_contents( $path, $content );
}
/** ----------------------------------------------------------------------------------------- */
/** VARIOUS INTERNAL TOOLS ================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Searches for element(s) in the DOM.
*
* @since 3.1
* @access public
*
* @param string $pattern Pattern to match.
* @param string $html HTML content.
* @return string
*/
protected function find( $pattern, $html ) {
preg_match_all( '/' . $pattern . '/Umsi', $html, $matches, PREG_SET_ORDER );
if ( empty( $matches ) ) {
return false;
}
return $matches[0];
}
/**
* Replaces the Google Analytics URL by the local copy inside the gtm-local.js file content
*
* @since 3.1
*
* @param string $content JavaScript content.
* @return string
*/
protected function replace_ga_url( $content ) {
if ( ! $this->ga_busting->save( $this->ga_busting->get_url() ) ) {
return $content;
}
return str_replace( $this->ga_busting->get_url(), $this->ga_busting->get_busting_url(), $content );
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace WP_Rocket\Addon\GoogleTracking;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Addon\Busting\BustingFactory;
use WP_Rocket\Admin\Options_Data as Options;
/**
* Event subscriber for Google tracking cache busting
*
* @since 3.1
*/
class Subscriber implements Subscriber_Interface {
/**
* Instance of the Busting Factory class
*
* @var BustingFactory
*/
private $busting_factory;
/**
* Instance of the Option_Data class
*
* @var Options
*/
private $options;
/**
* Constructor
*
* @param BustingFactory $busting_factory Instance of the Busting Factory class.
* @param Options $options Instance of the Option_Data class.
*/
public function __construct( BustingFactory $busting_factory, Options $options ) {
$this->busting_factory = $busting_factory;
$this->options = $options;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.1
*
* @return array
*/
public static function get_subscribed_events() {
$events = [
'cron_schedules' => 'add_schedule',
'init' => 'schedule_tracking_cache_update',
'rocket_google_tracking_cache_update' => 'update_tracking_cache',
'rocket_purge_cache' => 'delete_tracking_cache',
'rocket_buffer' => 'cache_busting_google_tracking',
];
return $events;
}
/**
* Processes the cache busting on the HTML content
*
* Google Analytics replacement is performed first, and if no replacement occured, Google Tag Manager replacement is performed.
*
* @since 3.1
*
* @param string $html HTML content.
* @return string
*/
public function cache_busting_google_tracking( $html ) {
if ( ! $this->is_allowed() ) {
return $html;
}
$processor = $this->busting_factory->type( 'ga' );
$html = $processor->replace_url( $html );
$processor = $this->busting_factory->type( 'gtm' );
$html = $processor->replace_url( $html );
return $html;
}
/**
* Schedules the auto-update of Google Analytics cache busting file
*
* @since 3.1
*
* @return void
*/
public function schedule_tracking_cache_update() {
if ( ! $this->is_busting_active() ) {
return;
}
if ( ! wp_next_scheduled( 'rocket_google_tracking_cache_update' ) ) {
wp_schedule_event( time(), 'weekly', 'rocket_google_tracking_cache_update' );
}
}
/**
* Updates Google Analytics cache busting file
*
* @since 3.1
*
* @return bool
*/
public function update_tracking_cache() {
if ( ! $this->is_busting_active() ) {
return false;
}
$processor = $this->busting_factory->type( 'ga' );
return $processor->refresh_save( $processor->get_url() );
}
/**
* Adds weekly interval to cron schedules
*
* @since 3.1
*
* @param Array $schedules An array of intervals used by cron jobs.
* @return Array
*/
public function add_schedule( $schedules ) {
if ( ! $this->is_busting_active() ) {
return $schedules;
}
$schedules['weekly'] = [
'interval' => 604800,
'display' => __( 'weekly', 'rocket' ),
];
return $schedules;
}
/**
* Deletes the GA busting file.
*
* @since 3.1
* @since 3.6 Argument replacement.
*
* @param string $type Type of cache clearance: 'all', 'post', 'term', 'user', 'url'.
* @return bool
*/
public function delete_tracking_cache( $type ) {
if ( 'all' !== $type || ! $this->is_busting_active() ) {
return false;
}
$this->busting_factory->type( 'gtm' )->delete();
return $this->busting_factory->type( 'ga' )->delete();
}
/**
* Checks if the cache busting should happen
*
* @since 3.1
*
* @return boolean
*/
private function is_allowed() {
if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE' ) ) {
return false;
}
return $this->is_busting_active();
}
/**
* Tell if the cache busting option is active.
*
* @since 3.6
*
* @return bool
*/
private function is_busting_active() {
return (bool) $this->options->get( 'google_analytics_cache', 0 );
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace WP_Rocket\Addon;
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
use WP_Rocket\Admin\Options_Data;
/**
* Service provider for WP Rocket addons.
*
* @since 3.3
* @since 3.5 - renamed and moved into this module.
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* The provides array is a way to let the container
* know that a service is provided by this service
* provider. Every service that is registered via
* this service provider must have an alias added
* to this array or it will be ignored.
*
* @var array
*/
protected $provides = [
'busting_factory',
'facebook_tracking',
'google_tracking',
'sucuri_subscriber',
];
/**
* Registers the subscribers in the container.
*
* @since 3.3
*/
public function register() {
$options = $this->getContainer()->get( 'options' );
// Busting Factory.
$this->getContainer()->add( 'busting_factory', 'WP_Rocket\Addon\Busting\BustingFactory' )
->withArgument( rocket_get_constant( 'WP_ROCKET_CACHE_BUSTING_PATH' ) )
->withArgument( rocket_get_constant( 'WP_ROCKET_CACHE_BUSTING_URL' ) );
// Facebook Tracking Subscriber.
$this->getContainer()->share( 'facebook_tracking', 'WP_Rocket\Addon\FacebookTracking\Subscriber' )
->withArgument( $this->getContainer()->get( 'busting_factory' ) )
->withArgument( $options );
// Google Tracking Subscriber.
$this->getContainer()->share( 'google_tracking', 'WP_Rocket\Addon\GoogleTracking\Subscriber' )
->withArgument( $this->getContainer()->get( 'busting_factory' ) )
->withArgument( $options );
// Sucuri Addon.
$this->getContainer()->share( 'sucuri_subscriber', 'WP_Rocket\Subscriber\Third_Party\Plugins\Security\Sucuri_Subscriber' )
->withArgument( $options );
// Cloudflare Addon.
$this->addon_cloudflare( $options );
}
/**
* Adds Cloudflare Addon into the Container when the addon is enabled.
*
* @since 3.5
*
* @param Options_Data $options Instance of options.
*/
protected function addon_cloudflare( Options_Data $options ) {
// If the addon is not enabled, delete the transient and bail out. Don't load the addon.
if ( ! (bool) $options->get( 'do_cloudflare', false ) ) {
delete_transient( 'rocket_cloudflare_is_api_keys_valid' );
return;
}
$this->provides[] = 'cloudflare_subscriber';
$this->getContainer()->add( 'cloudflare_api', 'WPMedia\Cloudflare\APIClient' )
->withArgument( rocket_get_constant( 'WP_ROCKET_VERSION' ) );
$this->getContainer()->add( 'cloudflare', 'WPMedia\Cloudflare\Cloudflare' )
->withArgument( $options )
->withArgument( $this->getContainer()->get( 'cloudflare_api' ) );
$this->getContainer()->share( 'cloudflare_subscriber', 'WPMedia\Cloudflare\Subscriber' )
->withArgument( $this->getContainer()->get( 'cloudflare' ) )
->withArgument( $options )
->withArgument( $this->getContainer()->get( 'options_api' ) );
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace WP_Rocket\Addon\Varnish;
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
/**
* Service provider for Varnish Addon.
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* The provides array is a way to let the container
* know that a service is provided by this service
* provider. Every service that is registered via
* this service provider must have an alias added
* to this array or it will be ignored.
*
* @var array
*/
protected $provides = [
'varnish',
'varnish_subscriber',
];
/**
* Registers the subscribers in the container.
*
* @since 3.3
*/
public function register() {
$this->getContainer()->add( 'varnish', 'WP_Rocket\Addon\Varnish\Varnish' );
$this->getContainer()->share( 'varnish_subscriber', 'WP_Rocket\Addon\Varnish\Subscriber' )
->withArgument( $this->getContainer()->get( 'varnish' ) )
->withArgument( $this->getContainer()->get( 'options' ) );
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace WP_Rocket\Addon\Varnish;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the Varnish Purge.
*
* @since 3.5
*/
class Subscriber implements Subscriber_Interface {
/**
* Varnish instance
*
* @var Varnish
*/
private $varnish;
/**
* WP Rocket options instance
*
* @var Options_Data
*/
private $options;
/**
* Constructor
*
* @param Varnish $varnish Varnish instance.
* @param Options_Data $options WP Rocket options instance.
*/
public function __construct( Varnish $varnish, Options_Data $options ) {
$this->varnish = $varnish;
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'before_rocket_clean_domain' => [ 'clean_domain', 10, 3 ],
'before_rocket_clean_file' => [ 'clean_file' ],
'before_rocket_clean_home' => [ 'clean_home', 10, 2 ],
];
}
/**
* Checks if Varnish cache should be purged
*
* @since 3.5
*
* @return bool
*/
private function should_purge() {
return (
/**
* Filters the use of the Varnish compatibility add-on
*
* @param bool $varnish_purge True to use, false otherwise.
*/
apply_filters( 'do_rocket_varnish_http_purge', false ) // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
||
(bool) $this->options->get( 'varnish_auto_purge', 0 )
);
}
/**
* Clears Varnish cache for the whole domain
*
* @param string $root The path of home cache file.
* @param string $lang The current lang to purge.
* @param string $url The home url.
* @return void
*/
public function clean_domain( $root, $lang, $url ) {
if ( ! $this->should_purge() ) {
return;
}
$this->varnish->purge( trailingslashit( $url ) . '?regex' );
}
/**
* Clears a specific page in Varnish cache
*
* @param string $url The url to purge.
* @return void
*/
public function clean_file( $url ) {
if ( ! $this->should_purge() ) {
return;
}
$this->varnish->purge( trailingslashit( $url ) . '?regex' );
}
/**
* Clears the homepage in Varnish cache
*
* @param string $root The path of home cache file.
* @param string $lang The current lang to purge.
* @return void
*/
public function clean_home( $root, $lang ) {
if ( ! $this->should_purge() ) {
return;
}
$home_url = trailingslashit( get_rocket_i18n_home_url( $lang ) );
$home_pagination_url = $home_url . trailingslashit( $GLOBALS['wp_rewrite']->pagination_base ) . '?regex';
$this->varnish->purge( $home_url );
$this->varnish->purge( $home_pagination_url );
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace WP_Rocket\Addon\Varnish;
/**
* Varnish cache purge
*
* @since 3.5
*/
class Varnish {
/**
* Send purge request to Varnish
*
* @since 3.5
*
* @param string $url The URL to purge.
* @return void
*/
public function purge( $url ) {
$parse_url = get_rocket_parse_url( $url );
$x_purge_method = 'default';
$regex = '';
if ( 'regex' === $parse_url['query'] ) {
$x_purge_method = 'regex';
$regex = '.*';
}
/**
* Filter the HTTP protocol (scheme)
*
* @since 2.7.3
* @param string $scheme The HTTP protocol
*/
$scheme = apply_filters( 'rocket_varnish_http_purge_scheme', 'http' );
/**
* Filters the headers to send with the Varnish purge request
*
* @since 3.1
*
* @param array $headers Headers to send.
*/
$headers = apply_filters(
'rocket_varnish_purge_headers',
[
/**
* Filters the host value passed in the request headers
*
* @since 2.8.15
* @param string $host The host value.
*/
'host' => apply_filters( 'rocket_varnish_purge_request_host', $parse_url['host'] ),
'X-Purge-Method' => $x_purge_method,
]
);
/**
* Filters the arguments passed to the Varnish purge request
*
* @since 3.5
*
* @param array $args Array of arguments for the request.
*/
$args = apply_filters(
'rocket_varnish_purge_request_args',
[
'method' => 'PURGE',
'blocking' => false,
'redirection' => 0,
'headers' => $headers,
]
);
foreach ( $this->get_varnish_ips() as $ip ) {
$host = ! empty( $ip ) ? $ip : str_replace( '*', '', $parse_url['host'] );
$purge_url_main = $scheme . '://' . $host . $parse_url['path'];
/**
* Filters the purge url.
*
* @since 3.6.3
*
* @param string $purge_url_full Full url contains the main url plus regex pattern.
* @param string $purge_url_main Main purge url without any additions params.
* @param string $regex Regex string.
*/
$purge_url = apply_filters(
'rocket_varnish_purge_url',
$purge_url_main . $regex,
$purge_url_main,
$regex
);
wp_remote_request( $purge_url, $args );
}
}
/**
* Gets an array of Varnish IPs to send the purge request to
*
* @return array
*/
private function get_varnish_ips() {
/**
* Filter the Varnish IP to call
*
* @since 2.6.8
* @param string|array $varnish_ip The Varnish IP
*/
$varnish_ip = apply_filters( 'rocket_varnish_ip', [] );
$constant = rocket_get_constant( 'WP_ROCKET_VARNISH_IP' );
if (
! empty( $constant )
&&
empty( $varnish_ip )
) {
$varnish_ip = $constant;
}
if ( empty( $varnish_ip ) ) {
$varnish_ip = [ '' ];
} elseif ( is_string( $varnish_ip ) ) {
$varnish_ip = (array) $varnish_ip;
}
return $varnish_ip;
}
}

View File

@@ -0,0 +1,54 @@
{
"name": "wp-media/module-varnish",
"description": "Varnish Addon for WP Rocket",
"homepage": "https://github.com/wp-media/module-varnish",
"license": "GPL-2.0+",
"authors": [
{
"name": "WP Media",
"email": "contact@wp-media.me",
"homepage": "https://wp-media.me"
}
],
"type": "library",
"config": {
"sort-packages": true
},
"support": {
"issues": "https://github.com/wp-media/module-varnish/issues",
"source": "https://github.com/wp-media/module-varnish"
},
"require-dev": {
"php": "^5.6 || ^7",
"brain/monkey": "^2.0",
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
"phpcompatibility/phpcompatibility-wp": "^2.0",
"phpstan/phpstan": "^0.12.3",
"phpunit/phpunit": "^5.7 || ^7",
"roave/security-advisories": "dev-master",
"szepeviktor/phpstan-wordpress": "^0.6",
"wp-coding-standards/wpcs": "^2",
"wp-media/event-manager": "^3.1",
"wp-media/module-container": "^2.4",
"wp-media/options": "^3.0",
"wp-media/phpunit": "^1.0"
},
"autoload": {
"psr-4": { "WP_Rocket\\Addon\\Varnish\\": "." }
},
"autoload-dev": {
"psr-4": { "WP_Rocket\\Tests\\": "Tests/" }
},
"scripts": {
"test-unit": "\"vendor/bin/wpmedia-phpunit\" unit path=Tests/Unit",
"test-integration": "\"vendor/bin/wpmedia-phpunit\" integration path=Tests/Integration/",
"run-tests": [
"@test-unit",
"@test-integration"
],
"install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run",
"phpcs": "phpcs --basepath=.",
"phpcs-changed": "./bin/phpcs-changed.sh",
"phpcs:fix": "phpcbf"
}
}