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,226 @@
<?php
namespace WP_Rocket\Subscriber\Cache;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Cache\Expired_Cache_Purge;
/**
* Event subscriber to clear cached files after lifespan.
*
* @since 3.4
* @author Grégory Viguier
*/
class Expired_Cache_Purge_Subscriber implements Subscriber_Interface {
/**
* Cron name.
*
* @since 3.4
* @author Grégory Viguier
*
* @var string
*/
const EVENT_NAME = 'rocket_purge_time_event';
/**
* WP Rocket Options instance.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @var Options_Data
*/
private $options;
/**
* Expired Cache Purge instance.
*
* @since 3.4
* @access private
* @author Remy Perona
*
* @var Expired_Cache_Purge
*/
private $purge;
/**
* Constructor.
*
* @param Options_Data $options Options instance.
* @param Expired_Cache_Purge $purge Purge instance.
*/
public function __construct( Options_Data $options, Expired_Cache_Purge $purge ) {
$this->options = $options;
$this->purge = $purge;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'init' => 'schedule_event',
'rocket_deactivation' => 'unschedule_event',
static::EVENT_NAME => 'purge_expired_files',
'cron_schedules' => 'custom_cron_schedule',
'update_option_' . WP_ROCKET_SLUG => [ 'clean_expired_cache_scheduled_event', 10, 2 ],
];
}
/**
* Clean expired cache scheduled event when Lifespan is changed to minutes.
*
* @since 3.4.3
* @author Soponar Cristina
*
* @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 clean_expired_cache_scheduled_event( $old_value, $value ) {
if ( empty( $value['purge_cron_unit'] ) ) {
return;
}
$old_value['purge_cron_unit'] = isset( $old_value['purge_cron_unit'] ) ? $old_value['purge_cron_unit'] : '';
$unit_list = [ 'HOUR_IN_SECONDS', 'DAY_IN_SECONDS' ];
// Bail out if the cron unit is changed from hours to days.
// Allow clean scheduled event when is changed from Minutes to Hours or Days, or the other way around.
$allow_clear_event = false;
if ( in_array( $old_value['purge_cron_unit'], $unit_list, true ) && 'MINUTE_IN_SECONDS' === $value['purge_cron_unit'] ) {
$allow_clear_event = true;
}
if ( in_array( $value['purge_cron_unit'], $unit_list, true ) && 'MINUTE_IN_SECONDS' === $old_value['purge_cron_unit'] ) {
$allow_clear_event = true;
}
// Allow if interval is changed when unit is set to minutes.
if (
'MINUTE_IN_SECONDS' === $old_value['purge_cron_unit']
&&
'MINUTE_IN_SECONDS' === $value['purge_cron_unit']
&&
$old_value['purge_cron_interval'] !== $value['purge_cron_interval']
) {
$allow_clear_event = true;
}
// Bail out if the cron unit is not changed from minutes to hours / days or other way around.
if ( ! $allow_clear_event ) {
return;
}
$this->unschedule_event();
}
/**
* Adds a custom cron schedule based on purge lifespan interval.
*
* @since 3.4.3
* @access public
* @author Soponar Cristina
*
* @param array $schedules An array of non-default cron schedules.
*/
public function custom_cron_schedule( $schedules ) {
$schedules['rocket_expired_cache_cron_interval'] = [
'interval' => $this->get_interval(),
'display' => __( 'WP Rocket Expired Cache Interval', 'rocket' ),
];
return $schedules;
}
/** ----------------------------------------------------------------------------------------- */
/** HOOK CALLBACKS ========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Scheduling the cron event.
* If the task is not programmed, it is automatically added.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function schedule_event() {
if ( $this->get_cache_lifespan() && ! wp_next_scheduled( static::EVENT_NAME ) ) {
$interval = $this->get_interval();
wp_schedule_event( time() + $interval, 'rocket_expired_cache_cron_interval', static::EVENT_NAME );
}
}
/**
* Gets the interval when the scheduled clean cache purge needs to run.
* If Minutes option is selected, then the interval will be set to minutes.
* If Hours / Days options are selected, then it will be set to 1 hour.
*
* @since 3.4.3
* @access private
* @author Soponar Cristina
*
* @return int $interval Interval time in seconds.
*/
private function get_interval() {
$unit = $this->options->get( 'purge_cron_unit' );
$lifespan = $this->options->get( 'purge_cron_interval', 10 );
$interval = HOUR_IN_SECONDS;
if ( ! $unit || ! defined( $unit ) ) {
$unit = 'HOUR_IN_SECONDS';
}
if ( 'MINUTE_IN_SECONDS' === $unit ) {
$interval = $lifespan * MINUTE_IN_SECONDS;
}
return $interval;
}
/**
* Unschedule the event.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function unschedule_event() {
wp_clear_scheduled_hook( static::EVENT_NAME );
}
/**
* Perform the event action.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function purge_expired_files() {
$this->purge->purge_expired_files( $this->get_cache_lifespan() );
}
/**
* Get the cache lifespan in seconds.
* If no value is filled in the settings, return 0. It means the purge is disabled.
* If the value from the settings is filled but invalid, fallback to the initial value (10 hours).
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return int The cache lifespan in seconds.
*/
public function get_cache_lifespan() {
$lifespan = $this->options->get( 'purge_cron_interval' );
if ( ! $lifespan ) {
return 0;
}
$unit = $this->options->get( 'purge_cron_unit' );
if ( $lifespan < 0 || ! $unit || ! defined( $unit ) ) {
return 10 * HOUR_IN_SECONDS;
}
return $lifespan * constant( $unit );
}
}

View File

@@ -0,0 +1,847 @@
<?php
namespace WP_Rocket\Subscriber\Media;
use WP_Rocket\Admin\Options;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Engine\Admin\Beacon\Beacon;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Engine\CDN\Subscriber as CDNSubscriber;
/**
* Subscriber for the WebP support.
*
* @since 3.4
* @author Remy Perona
* @author Grégory Viguier
*/
class Webp_Subscriber implements Subscriber_Interface {
/**
* Options_Data instance.
*
* @var Options_Data
* @access private
* @author Remy Perona
*/
private $options_data;
/**
* Options instance.
*
* @var Options
* @access private
* @author Grégory Viguier
*/
private $options_api;
/**
* CDNSubscriber instance.
*
* @var CDNSubscriber
* @access private
* @author Grégory Viguier
*/
private $cdn_subscriber;
/**
* Beacon instance
*
* @var Beacon
* @access private
* @author Grégory Viguier
*/
private $beacon;
/**
* Values of $_SERVER to use for some tests.
*
* @var array
* @access private
* @author Grégory Viguier
*/
private $server;
/**
* \WP_Filesystem_Direct instance.
*
* @var \WP_Filesystem_Direct
* @access private
* @author Grégory Viguier
*/
private $filesystem;
/**
* Constructor.
*
* @since 3.4
* @access public
* @author Remy Perona
*
* @param Options_Data $options_data Options_Data instance.
* @param Options $options_api Options instance.
* @param CDNSubscriber $cdn_subscriber CDNSubscriber instance.
* @param Beacon $beacon Beacon instance.
* @param array $server Values of $_SERVER to use for the tests. Default is $_SERVER.
*/
public function __construct( Options_Data $options_data, Options $options_api, CDNSubscriber $cdn_subscriber, Beacon $beacon, $server = null ) {
$this->options_data = $options_data;
$this->options_api = $options_api;
$this->cdn_subscriber = $cdn_subscriber;
$this->beacon = $beacon;
if ( ! isset( $server ) && ! empty( $_SERVER ) && is_array( $_SERVER ) ) {
$server = $_SERVER;
}
$this->server = $server && is_array( $server ) ? $server : [];
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'rocket_buffer' => [ 'convert_to_webp', 16 ],
'rocket_cache_webp_setting_field' => [
[ 'maybe_disable_setting_field' ],
[ 'webp_section_description' ],
],
'rocket_disable_webp_cache' => 'maybe_disable_webp_cache',
'rocket_third_party_webp_change' => 'sync_webp_cache_with_third_party_plugins',
'rocket_homepage_preload_url_request_args' => 'add_accept_header',
'rocket_preload_after_purge_cache_request_args' => 'add_accept_header',
'rocket_preload_url_request_args' => 'add_accept_header',
'rocket_partial_preload_url_request_args' => 'add_accept_header',
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Converts images extension to WebP if the file exists.
*
* @since 3.4
* @access public
* @author Remy Perona
* @author Grégory Viguier
*
* @param string $html HTML content.
* @return string
*/
public function convert_to_webp( $html ) {
if ( ! $this->options_data->get( 'cache_webp' ) ) {
return $html;
}
/** This filter is documented in inc/classes/buffer/class-cache.php */
if ( apply_filters( 'rocket_disable_webp_cache', false ) ) {
return $html;
}
// Only to supporting browsers.
$http_accept = isset( $this->server['HTTP_ACCEPT'] ) ? $this->server['HTTP_ACCEPT'] : '';
if ( ! $http_accept && function_exists( 'apache_request_headers' ) ) {
$headers = apache_request_headers();
$http_accept = isset( $headers['Accept'] ) ? $headers['Accept'] : '';
}
if ( ! $http_accept || false === strpos( $http_accept, 'webp' ) ) {
$user_agent = isset( $this->server['HTTP_USER_AGENT'] ) ? $this->server['HTTP_USER_AGENT'] : '';
if ( $user_agent && preg_match( '#Firefox/(?<version>[0-9]{2,})#i', $this->server['HTTP_USER_AGENT'], $matches ) ) {
if ( 66 >= (int) $matches['version'] ) {
return $html;
}
} else {
return $html;
}
}
$extensions = $this->get_extensions();
$attribute_names = $this->get_attribute_names();
if ( ! $extensions || ! $attribute_names ) {
return $html . '<!-- Rocket no webp -->';
}
$extensions = implode( '|', $extensions );
$attribute_names = implode( '|', $attribute_names );
if ( ! preg_match_all( '@["\'\s](?<name>(?:data-(?:[a-z0-9_-]+-)?)?(?:' . $attribute_names . '))\s*=\s*["\']\s*(?<value>(?:https?:/)?/[^"\']+\.(?:' . $extensions . ')[^"\']*?)\s*["\']@is', $html, $attributes, PREG_SET_ORDER ) ) {
return $html . '<!-- Rocket no webp -->';
}
if ( ! isset( $this->filesystem ) ) {
$this->filesystem = \rocket_direct_filesystem();
}
$has_hebp = false;
foreach ( $attributes as $attribute ) {
if ( preg_match( '@srcset$@i', strtolower( $attribute['name'] ) ) ) {
/**
* This is a srcset attribute, with probably multiple URLs.
*/
$new_value = $this->srcset_to_webp( $attribute['value'], $extensions );
} else {
/**
* A single URL attibute.
*/
$new_value = $this->url_to_webp( $attribute['value'], $extensions );
}
if ( ! $new_value ) {
// No webp here.
continue;
}
// Replace in content.
$has_hebp = true;
$new_attr = preg_replace( '@' . $attribute['name'] . '\s*=\s*["\'][^"\']+["\']@s', $attribute['name'] . '="' . $new_value . '"', $attribute[0] );
$html = str_replace( $attribute[0], $new_attr, $html );
}
/**
* Tell if the page contains webp files.
*
* @since 3.4
* @author Grégory Viguier
*
* @param bool $has_hebp True if the page contains webp files. False otherwise.
* @param string $html The pages html contents.
*/
$has_hebp = apply_filters( 'rocket_page_has_hebp_files', $has_hebp, $html );
// Tell the cache process if some URLs have been replaced.
if ( $has_hebp ) {
$html .= '<!-- Rocket has webp -->';
} else {
$html .= '<!-- Rocket no webp -->';
}
return $html;
}
/**
* Modifies the WebP section description of WP Rocket settings.
*
* @since 3.4
* @access public
* @author Remy Perona
* @author Grégory Viguier
*
* @param array $cache_webp_field Section description.
* @return string
*/
public function webp_section_description( $cache_webp_field ) {
$webp_beacon = $this->beacon->get_suggest( 'webp' );
$webp_plugins = $this->get_webp_plugins();
$serving = [];
$serving_not_compatible = [];
$creating = [];
if ( $webp_plugins ) {
$is_using_cdn = $this->is_using_cdn();
foreach ( $webp_plugins as $plugin ) {
if ( $plugin->is_serving_webp() ) {
if ( $is_using_cdn && ! $plugin->is_serving_webp_compatible_with_cdn() ) {
// Serving WebP using a method not compatible with CDN.
$serving_not_compatible[ $plugin->get_id() ] = $plugin->get_name();
} else {
// Serving WebP when no CDN or with a method compatible with CDN.
$serving[ $plugin->get_id() ] = $plugin->get_name();
}
}
if ( $plugin->is_converting_to_webp() ) {
// Generating WebP.
$creating[ $plugin->get_id() ] = $plugin->get_name();
}
}
}
if ( $serving ) {
// 5, 8.
$cache_webp_field['helper'] = sprintf(
// Translators: %1$s = plugin name(s), %2$s = opening <a> tag, %3$s = closing </a> tag.
esc_html( _n( 'You are using %1$s to serve WebP images so you do not need to enable this option. If you prefer to have WP Rocket serve WebP for you instead, please disable them from serving in %1$s. %2$sMore info%3$s', 'You are using %1$s to serve WebP images so you do not need to enable this option. If you prefer to have WP Rocket serve WebP for you instead, please disable them from serving in %1$s. %2$sMore info%3$s', count( $serving ), 'rocket' ) ),
esc_html( wp_sprintf_l( '%l', $serving ) ),
'<a href="' . esc_url( $webp_beacon['url'] ) . '" data-beacon-article="' . esc_attr( $webp_beacon['id'] ) . '" target="_blank" rel="noopener noreferrer">',
'</a>'
);
return $cache_webp_field;
}
/** This filter is documented in inc/classes/buffer/class-cache.php */
if ( apply_filters( 'rocket_disable_webp_cache', false ) ) {
$cache_webp_field['helper'] = esc_html__( 'WebP cache is disabled by filter.', 'rocket' );
return $cache_webp_field;
}
if ( $serving_not_compatible ) {
if ( ! $this->options_data->get( 'cache_webp' ) ) {
// 6.
$cache_webp_field['helper'] = sprintf(
// Translators: %1$s = plugin name(s), %2$s = opening <a> tag, %3$s = closing </a> tag.
esc_html( _n( 'You are using %1$s to convert images to WebP. If you want WP Rocket to serve them for you, activate this option. %2$sMore info%3$s', 'You are using %1$s to convert images to WebP. If you want WP Rocket to serve them for you, activate this option. %2$sMore info%3$s', count( $serving_not_compatible ), 'rocket' ) ),
esc_html( wp_sprintf_l( '%l', $serving_not_compatible ) ),
'<a href="' . esc_url( $webp_beacon['url'] ) . '" data-beacon-article="' . esc_attr( $webp_beacon['id'] ) . '" target="_blank" rel="noopener noreferrer">',
'</a>'
);
return $cache_webp_field;
}
// 7.
$cache_webp_field['helper'] = sprintf(
// Translators: %1$s = plugin name(s), %2$s = opening <a> tag, %3$s = closing </a> tag.
esc_html( _n( 'You are using %1$s to convert images to WebP. WP Rocket will create separate cache files to serve your WebP images. %2$sMore info%3$s', 'You are using %1$s to convert images to WebP. WP Rocket will create separate cache files to serve your WebP images. %2$sMore info%3$s', count( $serving_not_compatible ), 'rocket' ) ),
esc_html( wp_sprintf_l( '%l', $serving_not_compatible ) ),
'<a href="' . esc_url( $webp_beacon['url'] ) . '" data-beacon-article="' . esc_attr( $webp_beacon['id'] ) . '" target="_blank" rel="noopener noreferrer">',
'</a>'
);
return $cache_webp_field;
}
if ( $creating ) {
if ( ! $this->options_data->get( 'cache_webp' ) ) {
// 3.
$cache_webp_field['helper'] = sprintf(
// Translators: %1$s = plugin name(s), %2$s = opening <a> tag, %3$s = closing </a> tag.
esc_html( _n( 'You are using %1$s to convert images to WebP. If you want WP Rocket to serve them for you, activate this option. %2$sMore info%3$s', 'You are using %1$s to convert images to WebP. If you want WP Rocket to serve them for you, activate this option. %2$sMore info%3$s', count( $creating ), 'rocket' ) ),
esc_html( wp_sprintf_l( '%l', $creating ) ),
'<a href="' . esc_url( $webp_beacon['url'] ) . '" data-beacon-article="' . esc_attr( $webp_beacon['id'] ) . '" target="_blank" rel="noopener noreferrer">',
'</a>'
);
return $cache_webp_field;
}
// 4.
$cache_webp_field['helper'] = sprintf(
// Translators: %1$s = plugin name(s), %2$s = opening <a> tag, %3$s = closing </a> tag.
esc_html( _n( 'You are using %1$s to convert images to WebP. WP Rocket will create separate cache files to serve your WebP images. %2$sMore info%3$s', 'You are using %1$s to convert images to WebP. WP Rocket will create separate cache files to serve your WebP images. %2$sMore info%3$s', count( $creating ), 'rocket' ) ),
esc_html( wp_sprintf_l( '%l', $creating ) ),
'<a href="' . esc_url( $webp_beacon['url'] ) . '" data-beacon-article="' . esc_attr( $webp_beacon['id'] ) . '" target="_blank" rel="noopener noreferrer">',
'</a>'
);
return $cache_webp_field;
}
if ( ! $this->options_data->get( 'cache_webp' ) ) {
// 1.
if ( rocket_valid_key() && ! \Imagify_Partner::has_imagify_api_key() ) {
$imagify_link = '<a href="#imagify">';
} else {
// The Imagify page is not displayed.
$imagify_link = '<a href="https://wordpress.org/plugins/imagify/" target="_blank" rel="noopener noreferrer">';
}
$cache_webp_field['container_class'][] = 'wpr-field--parent';
$cache_webp_field['helper'] = sprintf(
// Translators: %1$s = opening <a> tag, %2$s = closing </a> tag.
esc_html__( 'You dont seem to be using a method to create and serve WebP that we are auto-compatible with. If you are not using WebP do not enable this option. %1$sMore info%2$s', 'rocket' ),
'<a href="' . esc_url( $webp_beacon['url'] ) . '" data-beacon-article="' . esc_attr( $webp_beacon['id'] ) . '" target="_blank" rel="noopener noreferrer">',
'</a>'
);
$cache_webp_field['warning'] = [
'title' => __( 'We have not detected any compatible WebP plugin!', 'rocket' ),
'description' => sprintf(
// Translators: %1$s and %2$s = opening <a> tags, %3$s = closing </a> tag.
esc_html__( 'If you activate this option WP Rocket will create separate cache files to serve WebP images. Any WebP images you have on your site will be served from these files to compatible browsers. If you dont already have WebP images on your site consider using %1$sImagify%3$s or another supported plugin. %2$sMore info%3$s', 'rocket' ),
$imagify_link,
'<a href="' . esc_url( $webp_beacon['url'] ) . '" data-beacon-article="' . esc_attr( $webp_beacon['id'] ) . '" target="_blank" rel="noopener noreferrer">',
'</a>'
),
'button_label' => esc_html__( 'Enable WebP caching', 'rocket' ),
];
return $cache_webp_field;
}
// 2.
$cache_webp_field['helper'] = esc_html__( 'WP Rocket will create separate cache files to serve your WebP images.', 'rocket' );
return $cache_webp_field;
}
/**
* Disable 'cache_webp' setting field if another plugin serves WebP.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param array $cache_webp_field Data to be added to the setting field.
* @return array
*/
public function maybe_disable_setting_field( $cache_webp_field ) {
/** This filter is documented in inc/classes/buffer/class-cache.php */
if ( ! apply_filters( 'rocket_disable_webp_cache', false ) ) {
return $cache_webp_field;
}
foreach ( [ 'input_attr', 'container_class' ] as $attr ) {
if ( ! isset( $cache_webp_field[ $attr ] ) || ! is_array( $cache_webp_field[ $attr ] ) ) {
$cache_webp_field[ $attr ] = [];
}
}
$cache_webp_field['input_attr']['disabled'] = 1;
$cache_webp_field['container_class'][] = 'wpr-isDisabled';
$cache_webp_field['container_class'][] = 'wpr-isParent';
return $cache_webp_field;
}
/**
* Disable the WebP cache if a WebP plugin is in use.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param bool $disable_webp_cache True to allow WebP cache (default). False otherwise.
* @return bool
*/
public function maybe_disable_webp_cache( $disable_webp_cache ) {
return ! $disable_webp_cache && $this->get_plugins_serving_webp() ? true : (bool) $disable_webp_cache;
}
/**
* When a 3rd party plugin enables or disables its webp feature, disable or enable WPR feature accordingly.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function sync_webp_cache_with_third_party_plugins() {
if ( $this->options_data->get( 'cache_webp' ) && $this->get_plugins_serving_webp() ) {
// Disable the cache webp option.
$this->options_data->set( 'cache_webp', 0 );
$this->options_api->set( 'settings', $this->options_data->get_options() );
}
rocket_generate_config_file();
}
/**
* Add WebP to the HTTP_ACCEPT headers on preload request when the WebP option is active
*
* @since 3.4
* @author Remy Perona
*
* @param array $args Arguments for the request.
* @return array
*/
public function add_accept_header( $args ) {
if ( ! $this->options_data->get( 'cache_webp' ) ) {
return $args;
}
$args['headers']['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8';
$args['headers']['HTTP_ACCEPT'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8';
return $args;
}
/** ----------------------------------------------------------------------------------------- */
/** TOOLS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the list of file extensions that may have a webp version.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return array
*/
private function get_extensions() {
$extensions = [ 'jpg', 'jpeg', 'jpe', 'png', 'gif' ];
/**
* Filter the list of file extensions that may have a webp version.
*
* @since 3.4
* @author Grégory Viguier
*
* @param array $extensions An array of file extensions.
*/
$extensions = apply_filters( 'rocket_file_extensions_for_webp', $extensions );
$extensions = array_filter(
(array) $extensions,
function( $extension ) {
return $extension && is_string( $extension );
}
);
return array_unique( $extensions );
}
/**
* Get the names of the HTML attributes where WP Rocket must search for image files.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return array
*/
private function get_attribute_names() {
$attributes = [ 'href', 'src', 'srcset', 'content' ];
/**
* Filter the names of the HTML attributes where WP Rocket must search for image files.
* Don't prepend new names with `data-`, WPR will do it. For example if you want to add `data-foo-bar`, you only need to add `foo-bar` or `bar` to the list.
*
* @since 3.4
* @author Grégory Viguier
*
* @param array $attributes An array of HTML attribute names.
*/
$attributes = apply_filters( 'rocket_attributes_for_webp', $attributes );
$attributes = array_filter(
(array) $attributes,
function( $attributes ) {
return $attributes && is_string( $attributes );
}
);
return array_unique( $attributes );
}
/**
* Convert a URL to an absolute path.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @param string $url URL to convert.
* @return string|bool
*/
private function url_to_path( $url ) {
static $hosts, $site_host, $subdir_levels;
$url_host = wp_parse_url( $url, PHP_URL_HOST );
// Relative path.
if ( null === $url_host ) {
if ( ! isset( $subdir_levels ) ) {
$subdir_levels = substr_count( preg_replace( '@^https?://@', '', site_url() ), '/' );
}
if ( $subdir_levels ) {
$url = ltrim( $url, '/' );
$url = explode( '/', $url );
array_splice( $url, 0, $subdir_levels );
$url = implode( '/', $url );
}
$url = site_url( $url );
}
// CDN.
if ( ! isset( $hosts ) ) {
$hosts = $this->cdn_subscriber->get_cdn_hosts( [], [ 'all', 'images' ] );
$hosts = array_flip( $hosts );
}
if ( isset( $hosts[ $url_host ] ) ) {
if ( ! isset( $site_host ) ) {
$site_host = wp_parse_url( site_url( '/' ), PHP_URL_HOST );
}
if ( $site_host ) {
$url = preg_replace( '@^(https?://)' . $url_host . '/@', '$1' . $site_host . '/', $url );
}
}
// URL to path.
$url = preg_replace( '@^https?:@', '', $url );
$paths = $this->get_url_to_path_associations();
if ( ! $paths ) {
// Uh?
return false;
}
foreach ( $paths as $asso_url => $asso_path ) {
if ( 0 === strpos( $url, $asso_url ) ) {
$file = str_replace( $asso_url, $asso_path, $url );
break;
}
}
if ( empty( $file ) ) {
return false;
}
/** This filter is documented in inc/functions/formatting.php. */
return (string) apply_filters( 'rocket_url_to_path', $file, $url );
}
/**
* Add a webp extension to a URL.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @param string $url A URL (I see you're very surprised).
* @param string $extensions Allowed image extensions.
* @return string|bool The same URL with a webp extension if the file exists. False if the webp image doesn't exist.
*/
private function url_to_webp( $url, $extensions ) {
if ( ! preg_match( '@^(?<src>.+\.(?<extension>' . $extensions . '))(?<query>(?:\?.*)?)$@i', $url, $src_url ) ) {
// Probably something like "image.jpg.webp".
return false;
}
$src_path = $this->url_to_path( $src_url['src'] );
if ( ! $src_path ) {
return false;
}
$src_path_webp = preg_replace( '@\.' . $src_url['extension'] . '$@', '.webp', $src_path );
if ( $this->filesystem->exists( $src_path_webp ) ) {
// File name: image.jpg => image.webp.
return preg_replace( '@\.' . $src_url['extension'] . '$@', '.webp', $src_url['src'] ) . $src_url['query'];
}
if ( $this->filesystem->exists( $src_path . '.webp' ) ) {
// File name: image.jpg => image.jpg.webp.
return $src_url['src'] . '.webp' . $src_url['query'];
}
return false;
}
/**
* Add webp extension to URLs in a srcset attribute.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @param array|string $srcset_values Value of a srcset attribute.
* @param string $extensions Allowed image extensions.
* @return string|bool An array similar to $srcset_values, with webp extensions when the files exist. False if no images have webp versions.
*/
private function srcset_to_webp( $srcset_values, $extensions ) {
if ( ! $srcset_values ) {
return false;
}
if ( ! is_array( $srcset_values ) ) {
$srcset_values = explode( ',', $srcset_values );
}
$has_webp = false;
foreach ( $srcset_values as $i => $srcset_value ) {
$srcset_value = preg_split( '/\s+/', trim( $srcset_value ) );
if ( count( $srcset_value ) > 2 ) {
// Not a good idea to have space characters in file name.
$descriptor = array_pop( $srcset_value );
$srcset_value = [
'url' => implode( ' ', $srcset_value ),
'descriptor' => $descriptor,
];
} else {
$srcset_value = [
'url' => $srcset_value[0],
'descriptor' => ! empty( $srcset_value[1] ) ? $srcset_value[1] : '1x',
];
}
$url_webp = $this->url_to_webp( $srcset_value['url'], $extensions );
if ( ! $url_webp ) {
$srcset_values[ $i ] = implode( ' ', $srcset_value );
continue;
}
$srcset_values[ $i ] = $url_webp . ' ' . $srcset_value['descriptor'];
$has_webp = true;
}
if ( ! $has_webp ) {
return false;
}
return implode( ',', $srcset_values );
}
/**
* Get a list of URL/path associations.
* URLs are schema-less, starting by a double slash.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return array A list of URLs as keys and paths as values.
*/
private function get_url_to_path_associations() {
static $list;
if ( isset( $list ) ) {
return $list;
}
$content_url = preg_replace( '@^https?:@', '', content_url( '/' ) );
$content_dir = trailingslashit( rocket_get_constant( 'WP_CONTENT_DIR' ) );
$list = [ $content_url => $content_dir ];
/**
* Filter the list of URL/path associations.
* The URLs with the most levels must come first.
*
* @since 3.4
* @author Grégory Viguier
*
* @param array $list The list of URL/path associations. URLs are schema-less, starting by a double slash.
*/
$list = apply_filters( 'rocket_url_to_path_associations', $list );
$list = array_filter(
$list,
function( $path, $url ) {
return $path && $url && is_string( $path ) && is_string( $url );
},
ARRAY_FILTER_USE_BOTH
);
if ( $list ) {
$list = array_unique( $list );
}
return $list;
}
/**
* Get a list of plugins that serve webp images on frontend.
* If the CDN is used, this won't list plugins that use a technique not compatible with CDN.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return array The WebP plugin names.
*/
private function get_plugins_serving_webp() {
$webp_plugins = $this->get_webp_plugins();
if ( ! $webp_plugins ) {
// Somebody probably messed up.
return [];
}
$checks = [];
$is_using_cdn = $this->is_using_cdn();
foreach ( $webp_plugins as $plugin ) {
if ( $is_using_cdn && $plugin->is_serving_webp_compatible_with_cdn() ) {
$checks[ $plugin->get_id() ] = $plugin->get_name();
} elseif ( ! $is_using_cdn && $plugin->is_serving_webp() ) {
$checks[ $plugin->get_id() ] = $plugin->get_name();
}
}
return $checks;
}
/**
* Get a list of active plugins that convert and/or serve webp images.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return array An array of Webp_Interface objects.
*/
private function get_webp_plugins() {
/**
* Add Webp plugins.
*
* @since 3.4
* @author Grégory Viguier
*
* @param array $webp_plugins An array of Webp_Interface objects.
*/
$webp_plugins = (array) apply_filters( 'rocket_webp_plugins', [] );
if ( ! $webp_plugins ) {
// Somebody probably messed up.
return [];
}
foreach ( $webp_plugins as $i => $plugin ) {
if ( ! is_a( $plugin, '\WP_Rocket\Subscriber\Third_Party\Plugins\Images\Webp\Webp_Interface' ) ) {
unset( $webp_plugins[ $i ] );
continue;
}
if ( ! $this->is_plugin_active( $plugin->get_basename() ) ) {
unset( $webp_plugins[ $i ] );
continue;
}
}
return $webp_plugins;
}
/**
* Tell if a plugin is active.
*
* @since 3.4
* @access public
* @see \plugin_basename()
* @author Grégory Viguier
*
* @param string $plugin_basename A plugin basename.
* @return bool
*/
private function is_plugin_active( $plugin_basename ) {
if ( \doing_action( 'deactivate_' . $plugin_basename ) ) {
return false;
}
if ( \doing_action( 'activate_' . $plugin_basename ) ) {
return true;
}
return \rocket_is_plugin_active( $plugin_basename );
}
/**
* Tell if WP Rocket uses a CDN for images.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return bool
*/
private function is_using_cdn() {
// Don't use `$this->options_data->get( 'cdn' )` here, we need an up-to-date value when the CDN option changes.
$use = get_rocket_option( 'cdn' ) && $this->cdn_subscriber->get_cdn_hosts( [], [ 'all', 'images' ] );
/**
* Filter whether WP Rocket is using a CDN for webp images.
*
* @since 3.4
* @author Grégory Viguier
*
* @param bool $use True if WP Rocket is using a CDN for webp images. False otherwise.
*/
return (bool) apply_filters( 'rocket_webp_is_using_cdn', $use );
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace WP_Rocket\Subscriber\Optimization;
use WP_Rocket\Buffer\Optimization;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Event subscriber to buffer and process a page content.
*
* @since 3.3
* @author Grégory Viguier
*/
class Buffer_Subscriber implements Subscriber_Interface {
/**
* Optimization instance
*
* @var Optimization
*/
private $optimizer;
/**
* Constructor
*
* @param Optimization $optimizer Optimization instance.
*/
public function __construct( Optimization $optimizer ) {
$this->optimizer = $optimizer;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'template_redirect' => [ 'start_content_process', 2 ],
];
}
/**
* Start buffering the page content and apply optimizations if we can.
*
* @since 3.3
* @access public
* @author Grégory Viguier
*/
public function start_content_process() {
return $this->optimizer->maybe_init_process();
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace WP_Rocket\Subscriber\Optimization;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Admin\Options_Data as Options;
use WP_Scripts;
/**
* Dequeue jQuery Migrate
*
* @since 3.5
* @author Soponar Cristina
*/
class Dequeue_JQuery_Migrate_Subscriber implements Subscriber_Interface {
/**
* Plugin options
*
* @since 3.5
* @author Soponar Cristina
*
* @var Options
*/
private $options;
/**
* Constructor
*
* @since 3.5
* @author Soponar Cristina
*
* @param Options $options Plugin options.
*/
public function __construct( Options $options ) {
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'wp_default_scripts' => [ 'dequeue_jquery_migrate' ],
];
}
/**
* Dequeue jquery migrate
*
* @since 3.5
* @author Soponar Cristina
*
* @param WP_Scripts $scripts WP_Scripts instance.
* @return bool|void
*/
public function dequeue_jquery_migrate( $scripts ) {
if ( ! $this->is_allowed() ) {
return false;
}
if ( ! empty( $scripts->registered['jquery'] ) ) {
$jquery_dependencies = $scripts->registered['jquery']->deps;
$scripts->registered['jquery']->deps = array_diff( $jquery_dependencies, [ 'jquery-migrate' ] );
}
}
/**
* Check if dequeue jquery migrate option is enabled
*
* @since 3.5
* @author Soponar Cristina
*
* @return boolean
*/
protected function is_allowed() {
if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE', false ) ) {
return false;
}
if ( ! $this->options->get( 'dequeue_jquery_migrate' ) ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace WP_Rocket\Subscriber\Plugin;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Manages the plugin information.
*
* @since 3.3.6
* @author Grégory Viguier
*/
class Information_Subscriber implements Subscriber_Interface {
use \WP_Rocket\Traits\Updater_Api_Tools;
/**
* Plugin slug.
*
* @var string
* @since 3.3.6
* @access private
* @author Grégory Viguier
*/
private $plugin_slug;
/**
* URL to contact to get plugin info.
*
* @var string
* @since 3.3.6
* @access private
* @author Grégory Viguier
*/
private $api_url;
/**
* An ID to use when a API request fails.
*
* @var string
* @since 3.3.6
* @access protected
* @author Grégory Viguier
*/
protected $request_error_id = 'plugins_api_failed';
/**
* Constructor
*
* @since 3.3.6
* @access public
* @author Grégory Viguier
*
* @param array $args {
* Required arguments to populate the class properties.
*
* @type string $plugin_file Full path to the plugin.
* @type string $api_url URL to contact to get update info.
* }
*/
public function __construct( $args ) {
if ( isset( $args['plugin_file'] ) ) {
$this->plugin_slug = $this->get_plugin_slug( $args['plugin_file'] );
}
if ( isset( $args['api_url'] ) ) {
$this->api_url = $args['api_url'];
}
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'plugins_api' => [ 'exclude_rocket_from_wp_info', 10, 3 ],
'plugins_api_result' => [ 'add_rocket_info', 10, 3 ],
];
}
/** ----------------------------------------------------------------------------------------- */
/** PLUGIN INFO ============================================================================= */
/** ----------------------------------------------------------------------------------------- */
/**
* Dont ask for plugin info to the repository.
*
* @since 3.3.6
* @access public
* @see plugins_api()
* @author Grégory Viguier
*
* @param false|object|array $bool The result object or array. Default false.
* @param string $action The type of information being requested from the Plugin Install API.
* @param object $args Plugin API arguments.
* @return false|object|array Empty object if slug is WP Rocket, default value otherwise.
*/
public function exclude_rocket_from_wp_info( $bool, $action, $args ) {
if ( ! $this->is_requesting_rocket_info( $action, $args ) ) {
return $bool;
}
return new \stdClass();
}
/**
* Insert WP Rocket plugin info.
*
* @since 3.3.6
* @access public
* @see plugins_api()
* @author Grégory Viguier
*
* @param object|\WP_Error $res Response object or WP_Error.
* @param string $action The type of information being requested from the Plugin Install API.
* @param object $args Plugin API arguments.
* @return object|\WP_Error Updated response object or WP_Error.
*/
public function add_rocket_info( $res, $action, $args ) {
if ( ! $this->is_requesting_rocket_info( $action, $args ) || empty( $res->external ) ) {
return $res;
}
$request = wp_remote_post(
$this->api_url,
[
'timeout' => 30,
'action' => 'plugin_information',
'request' => maybe_serialize( $args ),
]
);
if ( is_wp_error( $request ) ) {
return $this->get_request_error( $request->get_error_message() );
}
$res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
$code = wp_remote_retrieve_response_code( $request );
if ( 200 !== $code || ! ( is_object( $res ) || is_array( $res ) ) ) {
return $this->get_request_error( wp_remote_retrieve_body( $request ) );
}
return $res;
}
/** ----------------------------------------------------------------------------------------- */
/** TOOLS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Tell if requesting WP Rocket plugin info.
*
* @since 3.3.6
* @access private
* @author Grégory Viguier
*
* @param string $action The type of information being requested from the Plugin Install API.
* @param object $args Plugin API arguments.
* @return bool
*/
private function is_requesting_rocket_info( $action, $args ) {
return ( 'query_plugins' === $action || 'plugin_information' === $action ) && isset( $args->slug ) && $args->slug === $this->plugin_slug;
}
}

View File

@@ -0,0 +1,189 @@
<?php
namespace WP_Rocket\Subscriber\Plugin;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Manages common hooks for the plugin updater.
*
* @since 3.3.6
* @author Grégory Viguier
*/
class Updater_Api_Common_Subscriber implements Subscriber_Interface {
/**
* APIs URL domain.
*
* @var string
* @since 3.3.6
* @access private
* @author Grégory Viguier
*/
private $api_host;
/**
* URL to the sites home.
*
* @var string
* @since 3.3.6
* @access private
* @author Grégory Viguier
*/
private $site_url;
/**
* Current version of the plugin.
*
* @var string
* @since 3.3.6
* @access private
* @author Grégory Viguier
*/
private $plugin_version;
/**
* Key slug used when submitting new settings (POST).
*
* @var string
* @since 3.3.6
* @access private
* @author Grégory Viguier
*/
private $settings_slug;
/**
* The key (1st part of the action) used for the nonce field used on the settings page. It is also used in the page URL.
*
* @var string
* @since 3.3.6
* @access private
* @author Grégory Viguier
*/
private $settings_nonce_key;
/**
* Options instance.
*
* @var \WP_Rocket\Admin\Options
* @since 3.3.6
* @access private
* @author Grégory Viguier
*/
private $plugin_options;
/**
* Constructor
*
* @since 3.3.6
* @access public
* @author Grégory Viguier
*
* @param array $args {
* Required arguments to populate the class properties.
*
* @type string $api_host APIs URL domain.
* @type string $site_url URL to the sites home.
* @type string $plugin_version Current version of the plugin.
* @type string $settings_slug Key slug used when submitting new settings (POST).
* @type string $settings_nonce_key The key (1st part of the action) used for the nonce field used on the settings page. It is also used in the page URL.
* @type Options $plugin_options Options instance.
* }
*/
public function __construct( $args ) {
foreach ( [ 'api_host', 'site_url', 'plugin_version', 'settings_slug', 'settings_nonce_key', 'plugin_options' ] as $setting ) {
if ( isset( $args[ $setting ] ) ) {
$this->$setting = $args[ $setting ];
}
}
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'http_request_args' => [ 'maybe_set_rocket_user_agent', 10, 2 ],
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Force our user agent header when we hit our URLs.
*
* @since 3.3.6
* @access public
*
* @param array $request An array of request arguments.
* @param string $url Requested URL.
* @return array An array of requested arguments
*/
public function maybe_set_rocket_user_agent( $request, $url ) {
if ( ! is_string( $url ) ) {
return $request;
}
if ( $this->api_host && strpos( $url, $this->api_host ) !== false ) {
$request['user-agent'] = sprintf( '%s;%s', $request['user-agent'], $this->get_rocket_user_agent() );
}
return $request;
}
/** ----------------------------------------------------------------------------------------- */
/** TOOLS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the user agent to use when requesting the API.
*
* @since 3.3.6
* @access protected
* @author Grégory Viguier
*
* @return string WP Rocket user agent
*/
public function get_rocket_user_agent() {
$consumer_key = $this->get_current_option( 'consumer_key' );
$consumer_email = $this->get_current_option( 'consumer_email' );
$bonus = $this->plugin_options && $this->plugin_options->get( 'do_beta' ) ? '+' : '';
$php_version = preg_replace( '@^(\d+\.\d+).*@', '\1', phpversion() );
return sprintf( 'WP-Rocket|%s%s|%s|%s|%s|%s;', $this->plugin_version, $bonus, $consumer_key, $consumer_email, esc_url( $this->site_url ), $php_version );
}
/**
* Get a plugin option. If the value is currently being posted through the settings page, it is returned instead of the one stored in the database.
*
* @since 3.3.6
* @access protected
* @author Grégory Viguier
*
* @param string $field_name Name of a plugin option.
* @return string
*/
protected function get_current_option( $field_name ) {
if ( current_user_can( 'rocket_manage_options' ) && wp_verify_nonce( filter_input( INPUT_POST, '_wpnonce' ), $this->settings_nonce_key . '-options' ) ) {
$posted = filter_input( INPUT_POST, $this->settings_slug, FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
if ( ! empty( $posted[ $field_name ] ) && is_string( $posted[ $field_name ] ) ) {
// The value has been posted through the settings page.
return sanitize_text_field( $posted[ $field_name ] );
}
}
if ( ! $this->plugin_options ) {
return '';
}
$option_value = $this->plugin_options->get( $field_name );
if ( $option_value && is_string( $option_value ) ) {
return $option_value;
}
return '';
}
}

View File

@@ -0,0 +1,435 @@
<?php
namespace WP_Rocket\Subscriber\Plugin;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Traits\Updater_Api_Tools;
/**
* Manages the plugin updates.
*
* @since 3.3.6
*/
class Updater_Subscriber implements Subscriber_Interface {
use Updater_Api_Tools;
/**
* Full path to the plugin.
*
* @var string
* @since 3.3.6
*/
private $plugin_file;
/**
* Current version of the plugin.
*
* @var string
* @since 3.3.6
*/
private $plugin_version;
/**
* URL to the plugin provider.
*
* @var string
* @since 3.3.6
*/
private $vendor_url;
/**
* URL to contact to get update info.
*
* @var string
* @since 3.3.6
*/
private $api_url;
/**
* A list of plugins icon URLs.
*
* @var array {
* @type string $2x URL to the High-DPI size (png or jpg). Optional.
* @type string $1x URL to the normal icon size (png or jpg). Mandatory.
* @type string $svg URL to the svg version of the icon. Optional.
* }
* @since 3.3.6
* @see https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/#plugin-icons
*/
private $icons;
/**
* An ID to use when a API request fails.
*
* @var string
* @since 3.3.6
*/
protected $request_error_id = 'rocket_update_failed';
/**
* Name of the transient that caches the update data.
*
* @var string
* @since 3.3.6
*/
protected $cache_transient_name = 'wp_rocket_update_data';
/**
* Constructor
*
* @since 3.3.6
*
* @param array $args {
* Required arguments to populate the class properties.
*
* @type string $plugin_file Full path to the plugin.
* @type string $plugin_version Current version of the plugin.
* @type string $vendor_url URL to the plugin provider.
* @type string $api_url URL to contact to get update info.
* }
*/
public function __construct( $args ) {
foreach ( [ 'plugin_file', 'plugin_version', 'vendor_url', 'api_url', 'icons' ] as $setting ) {
if ( isset( $args[ $setting ] ) ) {
$this->$setting = $args[ $setting ];
}
}
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'http_request_args' => [ 'exclude_rocket_from_wp_updates', 5, 2 ],
'pre_set_site_transient_update_plugins' => 'maybe_add_rocket_update_data',
'deleted_site_transient' => 'maybe_delete_rocket_update_data_cache',
'wp_rocket_loaded' => 'maybe_force_check',
'auto_update_plugin' => [ 'disable_auto_updates', 10, 2 ],
];
}
/** ----------------------------------------------------------------------------------------- */
/** PLUGIN UPDATE DATA ====================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* When WP checks plugin versions against the latest versions hosted on WordPress.org, remove WPR from the list.
*
* @since 3.3.6
* @see wp_update_plugins()
*
* @param array $request An array of HTTP request arguments.
* @param string $url The request URL.
* @return array Updated array of HTTP request arguments.
*/
public function exclude_rocket_from_wp_updates( $request, $url ) {
if ( ! is_string( $url ) ) {
return $request;
}
if ( ! preg_match( '@^https?://api.wordpress.org/plugins/update-check(/|\?|$)@', $url ) || empty( $request['body']['plugins'] ) ) {
// Not a plugin update request. Stop immediately.
return $request;
}
/**
* Depending on the API version, the data can have several forms:
* - Can be serialized or JSON encoded,
* - Can be an object of arrays or an object of objects.
*/
$is_serialized = is_serialized( $request['body']['plugins'] );
$basename = plugin_basename( $this->plugin_file );
$edited = false;
if ( $is_serialized ) {
$plugins = maybe_unserialize( $request['body']['plugins'] );
} else {
$plugins = json_decode( $request['body']['plugins'] );
}
if ( ! empty( $plugins->plugins ) ) {
if ( is_object( $plugins->plugins ) ) {
if ( isset( $plugins->plugins->$basename ) ) {
unset( $plugins->plugins->$basename );
$edited = true;
}
} elseif ( is_array( $plugins->plugins ) ) {
if ( isset( $plugins->plugins[ $basename ] ) ) {
unset( $plugins->plugins[ $basename ] );
$edited = true;
}
}
}
if ( ! empty( $plugins->active ) ) {
$active_is_object = is_object( $plugins->active );
if ( $active_is_object || is_array( $plugins->active ) ) {
foreach ( $plugins->active as $key => $plugin_basename ) {
if ( $plugin_basename !== $basename ) {
continue;
}
if ( $active_is_object ) {
unset( $plugins->active->$key );
} else {
unset( $plugins->active[ $key ] );
}
$edited = true;
break;
}
}
}
if ( $edited ) {
if ( $is_serialized ) {
$request['body']['plugins'] = maybe_serialize( $plugins );
} else {
$request['body']['plugins'] = wp_json_encode( $plugins );
}
}
return $request;
}
/**
* Add WPR update data to the "WP update" transient.
*
* @since 3.3.6
*
* @param \stdClass $transient_value New value of site transient.
* @return \stdClass
*/
public function maybe_add_rocket_update_data( $transient_value ) {
if ( defined( 'WP_INSTALLING' ) ) {
return $transient_value;
}
// Get the remote version data.
$remote_data = $this->get_cached_latest_version_data();
if ( is_wp_error( $remote_data ) ) {
return $transient_value;
}
// Make sure the transient value is well formed.
if ( ! is_object( $transient_value ) ) {
$transient_value = new \stdClass();
}
if ( empty( $transient_value->response ) ) {
$transient_value->response = [];
}
if ( empty( $transient_value->checked ) ) {
$transient_value->checked = [];
}
// If a newer version is available, add the update.
if ( version_compare( $this->plugin_version, $remote_data->new_version, '<' ) ) {
$transient_value->response[ $remote_data->plugin ] = $remote_data;
}
$transient_value->checked[ $remote_data->plugin ] = $this->plugin_version;
return $transient_value;
}
/**
* Delete WPR update data cache when the "WP update" transient is deleted.
*
* @since 3.3.6
*
* @param string $transient_name Deleted transient name.
*/
public function maybe_delete_rocket_update_data_cache( $transient_name ) {
if ( 'update_plugins' === $transient_name ) {
$this->delete_rocket_update_data_cache();
}
}
/**
* If the `rocket_force_update` query arg is set, force WP to refresh the list of plugins to update.
*
* @since 3.3.6
*/
public function maybe_force_check() {
if ( is_string( filter_input( INPUT_GET, 'rocket_force_update' ) ) ) {
delete_site_transient( 'update_plugins' );
}
}
/**
* Disable auto-updates for WP Rocket
*
* @since 3.7.5
*
* @param bool|null $update Whether to update. The value of null is internally used to detect whether nothing has hooked into this filter.
* @param object $item The update offer.
* @return bool|null
*/
public function disable_auto_updates( $update, $item ) {
if ( 'wp-rocket/wp-rocket.php' === $item->plugin ) {
return false;
}
return $update;
}
/** ----------------------------------------------------------------------------------------- */
/** TOOLS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the latest WPR update data from our server.
*
* @since 3.3.6
*
* @return \stdClass|\WP_Error {
* A \WP_Error object on failure. An object on success:
*
* @type string $slug The plugin slug.
* @type string $plugin The plugin base name.
* @type string $new_version The plugin new version.
* @type string $url URL to the plugin provider.
* @type string $package URL to the zip file of the new version.
* @type array $icons {
* A list of plugins icon URLs.
*
* @type string $2x URL to the High-DPI size (png or jpg). Optional.
* @type string $1x URL to the normal icon size (png or jpg). Mandatory.
* @type string $svg URL to the svg version of the icon. Optional.
* }
* }
*/
public function get_latest_version_data() {
$request = wp_remote_get(
$this->api_url,
[
'timeout' => 30,
]
);
if ( is_wp_error( $request ) ) {
return $this->get_request_error(
[
'error_code' => $request->get_error_code(),
'response' => $request->get_error_message(),
]
);
}
$res = trim( wp_remote_retrieve_body( $request ) );
$code = wp_remote_retrieve_response_code( $request );
if ( 200 !== $code ) {
/**
* If the response doesnt have a status 200: it is an error, or there is no new update.
*/
return $this->get_request_error(
[
'http_code' => $code,
'response' => $res,
]
);
}
/**
* This will match:
* - `2.3.4.5-beta1||1.2.3.4-beta2||||||||||||||||||||||||||||||||`: expired license.
* - `2.3.4.5-beta1|https://wp-rocket.me/i-should-write-a-funny-thing-here/wp-rocket_1.2.3.4-beta2.zip|1.2.3.4-beta2`: valid license.
*/
if ( ! preg_match( '@^(?<stable_version>\d+(?:\.\d+){1,3}[^|]*)\|(?<package>(?:http.+\.zip)?)\|(?<user_version>\d+(?:\.\d+){1,3}[^|]*)(?:\|+)?$@', $res, $match ) ) {
/**
* If the response doesnt have the right format, it is an error.
*/
return $this->get_request_error( $res );
}
$obj = new \stdClass();
$obj->slug = $this->get_plugin_slug( $this->plugin_file );
$obj->plugin = plugin_basename( $this->plugin_file );
$obj->new_version = $match['user_version'];
$obj->url = $this->vendor_url;
$obj->package = $match['package'];
$obj->tested = WP_ROCKET_WP_VERSION_TESTED;
if ( $this->icons && ! empty( $this->icons['1x'] ) ) {
$obj->icons = $this->icons;
}
return $obj;
}
/**
* Get the cached version of the latest WPR update data.
*
* @since 3.3.6
*
* @return \stdClass|\WP_Error {
* A \WP_Error object on failure. An object on success:
*
* @type string $slug The plugin slug.
* @type string $plugin The plugin base name.
* @type string $new_version The plugin new version.
* @type string $url URL to the plugin provider.
* @type string $package URL to the zip file of the new version.
* @type array $icons {
* A list of plugins icon URLs.
*
* @type string $2x URL to the High-DPI size (png or jpg). Optional.
* @type string $1x URL to the normal icon size (png or jpg). Mandatory.
* @type string $svg URL to the svg version of the icon. Optional.
* }
* }
*/
public function get_cached_latest_version_data() {
static $response;
if ( isset( $response ) ) {
// "force update" wont bypass the static cache: only one http request by page load.
return $response;
}
$force_update = is_string( filter_input( INPUT_GET, 'rocket_force_update' ) );
if ( ! $force_update ) {
// No "force update": try to get the result from a transient.
$response = get_site_transient( $this->cache_transient_name );
if ( $response && is_object( $response ) ) {
// Got something in cache.
return $response;
}
}
// Get fresh data.
$response = $this->get_latest_version_data();
$cache_duration = 12 * HOUR_IN_SECONDS;
if ( is_wp_error( $response ) ) {
$error_data = $response->get_error_data();
if ( ! empty( $error_data['error_code'] ) ) {
// `wp_remote_get()` returned an internal error ('error_code' contains a WP_Error code ).
$cache_duration = HOUR_IN_SECONDS;
} elseif ( ! empty( $error_data['http_code'] ) && $error_data['http_code'] >= 400 ) {
// We got a 4xx or 5xx HTTP error.
$cache_duration = 2 * HOUR_IN_SECONDS;
}
}
set_site_transient( $this->cache_transient_name, $response, $cache_duration );
return $response;
}
/**
* Delete WP Rocket update data cache.
*
* @since 3.3.6
*/
public function delete_rocket_update_data_cache() {
delete_site_transient( $this->cache_transient_name );
}
}

View File

@@ -0,0 +1,202 @@
<?php
namespace WP_Rocket\Subscriber\Tools;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Logger\Logger;
/**
* Detect and report when <html>, wp_footer() and <body> tags are missing.
*
* @since 3.4.2
* @author Soponar Cristina
*/
class Detect_Missing_Tags_Subscriber implements Subscriber_Interface {
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.4.2
* @author Soponar Cristina
*
* @return array
*/
public static function get_subscribed_events() {
return [
'admin_notices' => 'rocket_notice_missing_tags',
'rocket_before_maybe_process_buffer' => 'maybe_missing_tags',
'wp_rocket_upgrade' => 'delete_transient_after_upgrade',
];
}
/**
* Check if there is a missing </html> or </body> tag
*
* @since 3.4.2
* @author Soponar Cristina
*
* @param string $html HTML content.
*/
public function maybe_missing_tags( $html ) {
// If there is a redirect the content is empty and can display a false positive notice.
if ( strlen( $html ) <= 255 ) {
return;
}
// If the http response is not 200 do not report missing tags.
if ( http_response_code() !== 200 ) {
return;
}
// If content type is not HTML do not report missing tags.
if ( empty( $_SERVER['content_type'] ) || false === strpos( wp_unslash( $_SERVER['content_type'] ), 'text/html' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
return;
}
// If the content does not contain HTML Doctype, do not report missing tags.
if ( false === stripos( $html, '<!DOCTYPE html' ) ) {
return;
}
Logger::info(
'START Detect Missing closing tags ( <html>, </body> or wp_footer() )',
[
'maybe_missing_tags',
'URI' => $this->get_raw_request_uri(),
]
);
// Remove all comments before testing tags. If </html> or </body> tags are commented this will identify it as a missing tag.
$html = preg_replace( '/<!--([\\s\\S]*?)-->/', '', $html );
$missing_tags = [];
if ( false === strpos( $html, '</html>' ) ) {
$missing_tags[] = '</html>';
Logger::debug(
'Not found closing </html> tag.',
[
'maybe_missing_tags',
'URI' => $this->get_raw_request_uri(),
]
);
}
if ( false === strpos( $html, '</body>' ) ) {
$missing_tags[] = '</body>';
Logger::debug(
'Not found closing </body> tag.',
[
'maybe_missing_tags',
'URI' => $this->get_raw_request_uri(),
]
);
}
if ( did_action( 'wp_footer' ) === 0 ) {
$missing_tags[] = 'wp_footer()';
Logger::debug(
'wp_footer() function did not run.',
[
'maybe_missing_tags',
'URI' => $this->get_raw_request_uri(),
]
);
}
if ( ! $missing_tags ) {
return;
}
$transient = get_transient( 'rocket_notice_missing_tags' );
$transient = is_array( $transient ) ? $transient : [];
$missing_tags = array_unique( array_merge( $transient, $missing_tags ) );
if ( count( $transient ) === count( $missing_tags ) ) {
return;
}
// Prevent saving the transient if the notice is dismissed.
$boxes = get_user_meta( get_current_user_id(), 'rocket_boxes', true );
if ( in_array( 'rocket_notice_missing_tags', (array) $boxes, true ) ) {
return;
}
set_transient( 'rocket_notice_missing_tags', $missing_tags );
}
/**
* This notice is displayed if there is a missing required tag or function: </html>, </body> or wp_footer()
*
* @since 3.4.2
* @author Soponar Cristina
*/
public function rocket_notice_missing_tags() {
$screen = get_current_screen();
if ( ! current_user_can( 'rocket_manage_options' ) || 'settings_page_wprocket' !== $screen->id ) {
return;
}
$boxes = get_user_meta( get_current_user_id(), 'rocket_boxes', true );
if ( in_array( __FUNCTION__, (array) $boxes, true ) ) {
return;
}
$notice = get_transient( 'rocket_notice_missing_tags' );
if ( empty( $notice ) || ! is_array( $notice ) ) {
return;
}
foreach ( $notice as $i => $tag ) {
$notice[ $i ] = '<code>' . esc_html( $tag ) . '</code>';
}
$msg = '<b>' . __( 'WP Rocket: ', 'rocket' ) . '</b>';
$msg .= sprintf(
/* translators: %1$s = missing tags; */
esc_html( _n( 'Failed to detect the following requirement in your theme: closing %1$s.', 'Failed to detect the following requirements in your theme: closing %1$s.', count( $notice ), 'rocket' ) ),
// translators: Documentation exists in EN, FR.
wp_sprintf_l( '%l', $notice )
);
$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/99-pages-not-cached-or-minify-cssjs-not-working/?utm_source=wp_plugin&utm_medium=wp_rocket#theme', 'rocket' ) ) . '" rel="noopener noreferrer" target="_blank">',
'</a>'
);
\rocket_notice_html(
[
'status' => 'info',
'dismissible' => '',
'message' => $msg,
'dismiss_button' => __FUNCTION__,
]
);
}
/**
* Get the request URI.
*
* @since 3.4.2
* @author Soponar Cristina
*
* @return string
*/
public function get_raw_request_uri() {
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
return '';
}
if ( '' === $_SERVER['REQUEST_URI'] ) {
return '';
}
return '/' . esc_html( ltrim( wp_unslash( $_SERVER['REQUEST_URI'] ), '/' ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
}
/**
* Deletes the transient storing the missing tags when updating the plugin
*
* @since 3.4.2.2
* @author Soponar Cristina
*/
public function delete_transient_after_upgrade() {
delete_transient( 'rocket_notice_missing_tags' );
}
}

View File

@@ -0,0 +1,265 @@
<?php
namespace WP_Rocket\Subscriber\Admin\Database;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Admin\Database\Optimization;
use WP_Rocket\Admin\Options_Data;
defined( 'ABSPATH' ) || exit;
/**
* Subscriber for the database optimization
*
* @since 3.3
* @author Remy Perona
*/
class Optimization_Subscriber implements Subscriber_Interface {
/**
* Optimization process instance.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @var Optimization
*/
private $optimize;
/**
* WP Rocket Options instance.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @var Options_Data
*/
private $options;
/**
* Constructor
*
* @param Optimization $optimize Optimize instance.
* @param Options_Data $options WP Rocket options.
*/
public function __construct( Optimization $optimize, Options_Data $options ) {
$this->optimize = $optimize;
$this->options = $options;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.3
* @author Remy Perona
*
* @return array
*/
public static function get_subscribed_events() {
return [
'cron_schedules' => 'add_cron_schedule',
'init' => 'database_optimization_scheduled',
'rocket_database_optimization_time_event' => 'cron_optimize',
'pre_update_option_' . WP_ROCKET_SLUG => 'save_optimize',
'admin_notices' => [
[ 'notice_process_running' ],
[ 'notice_process_complete' ],
],
];
}
/**
* Add a new interval for the cron job.
* This adds a weekly/monthly interval for database optimization.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param array $schedules An array of intervals used by cron jobs.
* @return array Updated array of intervals.
*/
public function add_cron_schedule( $schedules ) {
if ( ! $this->options->get( 'schedule_automatic_cleanup', false ) ) {
return $schedules;
}
switch ( $this->options->get( 'automatic_cleanup_frequency', 'weekly' ) ) {
case 'weekly':
$schedules['weekly'] = [
'interval' => 604800,
'display' => __( 'weekly', 'rocket' ),
];
break;
case 'monthly':
$schedules['monthly'] = [
'interval' => 2592000,
'display' => __( 'monthly', 'rocket' ),
];
break;
}
return $schedules;
}
/**
* Plans database optimization cron
* If the task is not programmed, it is automatically triggered
*
* @since 2.8
* @author Remy Perona
*
* @see process_handler()
*/
public function database_optimization_scheduled() {
if ( ! $this->options->get( 'schedule_automatic_cleanup', false ) ) {
return;
}
if ( ! wp_next_scheduled( 'rocket_database_optimization_time_event' ) ) {
wp_schedule_event( time(), $this->options->get( 'automatic_cleanup_frequency', 'weekly' ), 'rocket_database_optimization_time_event' );
}
}
/**
* Database Optimization cron callback
*
* @since 3.0.4
* @author Remy Perona
*/
public function cron_optimize() {
$items = array_filter( array_keys( $this->optimize->get_options() ), [ $this->options, 'get' ] );
if ( empty( $items ) ) {
return;
}
$this->optimize->process_handler( $items );
}
/**
* Launches the database optimization when the settings are saved with optimize button
*
* @since 2.8
* @author Remy Perona
*
* @see process_handler()
*
* @param array $value The new, unserialized option value.
* @return array
*/
public function save_optimize( $value ) {
if ( empty( $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
return $value;
}
if ( empty( $value ) || ! isset( $value['submit_optimize'] ) ) {
return $value;
}
unset( $value['submit_optimize'] );
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return $value;
}
$items = [];
$db_options = $this->optimize->get_options();
foreach ( $value as $key => $option_value ) {
if ( isset( $db_options[ $key ] ) && 1 === $option_value ) {
$items[] = $key;
}
}
if ( empty( $items ) ) {
return $value;
}
$this->optimize->process_handler( $items );
return $value;
}
/**
* This notice is displayed after launching the database optimization process
*
* @since 2.11
* @author Remy Perona
*/
public function notice_process_running() {
$screen = get_current_screen();
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
if ( 'settings_page_wprocket' !== $screen->id ) {
return;
}
$notice = get_transient( 'rocket_database_optimization_process' );
if ( ! $notice ) {
return;
}
\rocket_notice_html(
[
'status' => 'info',
'message' => esc_html__( 'Database optimization process is running', 'rocket' ),
]
);
}
/**
* This notice is displayed when the database optimization process is complete
*
* @since 2.11
* @author Remy Perona
*/
public function notice_process_complete() {
$screen = get_current_screen();
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
if ( 'settings_page_wprocket' !== $screen->id ) {
return;
}
$optimized = get_transient( 'rocket_database_optimization_process_complete' );
if ( false === $optimized ) {
return;
}
$db_options = $this->optimize->get_options();
delete_transient( 'rocket_database_optimization_process_complete' );
$message = esc_html__( 'Database optimization process is complete. Everything was already optimized!', 'rocket' );
if ( ! empty( $optimized ) ) {
$message = esc_html__( 'Database optimization process is complete. List of optimized items below:', 'rocket' );
}
if ( ! empty( $optimized ) ) {
$message .= '<ul>';
foreach ( $optimized as $key => $number ) {
$message .= '<li>' .
/* translators: %1$d = number of items optimized, %2$s = type of optimization */
sprintf( esc_html__( '%1$d %2$s optimized.', 'rocket' ), $number, $db_options[ $key ] )
. '</li>';
}
$message .= '</ul>';
}
\rocket_notice_html(
[
'message' => $message,
]
);
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Hostings;
use WP_Rocket\Logger\Logger;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for compatibility with Litespeed
*
* @since 3.4.1
* @author Soponar Cristina
*/
class Litespeed_Subscriber implements Subscriber_Interface {
/**
* Subscribed events for Litespeed.
*
* @since 3.4.1
* @author Soponar Cristina
* @inheritDoc
*/
public static function get_subscribed_events() {
if ( ! isset( $_SERVER['HTTP_X_LSCACHE'] ) ) {
return [];
}
return [
'before_rocket_clean_domain' => 'litespeed_clean_domain',
'before_rocket_clean_file' => 'litespeed_clean_file',
'before_rocket_clean_home' => [ 'litespeed_clean_home', 10, 2 ],
];
}
/**
* Purge Litespeed all domain.
*
* @since 3.4.1
* @author Soponar Cristina
*/
public function litespeed_clean_domain() {
$this->litespeed_header_purge_all();
}
/**
* Purge a specific page
*
* @since 3.4.1
* @author Soponar Cristina
*
* @param string $url The url to purge.
*/
public function litespeed_clean_file( $url ) {
$this->litespeed_header_purge_url( trailingslashit( $url ) );
}
/**
* Purge the homepage and its pagination
*
* @since 3.4.1
* @author Soponar Cristina
*
* @param string $root The path of home cache file.
* @param string $lang The current lang to purge.
*/
public function litespeed_clean_home( $root, $lang ) {
$home_url = trailingslashit( get_rocket_i18n_home_url( $lang ) );
$home_pagination_url = $home_url . trailingslashit( $GLOBALS['wp_rewrite']->pagination_base );
$this->litespeed_header_purge_url( $home_url );
$this->litespeed_header_purge_url( $home_pagination_url );
}
/**
* Purge Litespeed URL
*
* @since 3.4.1
* @author Soponar Cristina
*
* @param string $url The URL to purge.
* @return void
*/
public function litespeed_header_purge_url( $url ) {
if ( headers_sent() ) {
Logger::debug(
'X-LiteSpeed Headers already sent',
[ 'headers_sent' ]
);
return;
}
$parse_url = get_rocket_parse_url( $url );
$path = rtrim( $parse_url['path'], '/' );
$private_prefix = 'X-LiteSpeed-Purge: ' . $path;
Logger::debug(
'X-LiteSpeed',
[
'litespeed_header_purge_url',
'path' => $private_prefix,
]
);
@header( $private_prefix );
}
/**
* Purge Litespeed Cache
*
* @since 3.4.1
* @author Soponar Cristina
*
* @return void
*/
public function litespeed_header_purge_all() {
if ( headers_sent() ) {
return;
}
$private_prefix = 'X-LiteSpeed-Purge: *';
@header( $private_prefix );
}
}

View File

@@ -0,0 +1,390 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins\Images\Webp;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the WebP support with EWWW.
*
* @since 3.4
* @author Grégory Viguier
*/
class EWWW_Subscriber implements Webp_Interface, Subscriber_Interface {
use Webp_Common;
/**
* Options_Data instance.
*
* @var Options_Data
* @access private
* @author Remy Perona
*/
private $options;
/**
* EWWW basename.
*
* @var string
* @access private
* @author Grégory Viguier
*/
private $plugin_basename;
/**
* Constructor.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param Options_Data $options Options instance.
*/
public function __construct( Options_Data $options ) {
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'rocket_webp_plugins' => 'register',
'wp_rocket_loaded' => 'load_hooks',
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Launch filters.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function load_hooks() {
if ( ! $this->options->get( 'cache_webp' ) ) {
return;
}
/**
* Every time EWWW is (de)activated, we must "sync" our webp cache option.
*/
if ( did_action( 'activate_' . $this->get_basename() ) ) {
$this->plugin_activation();
}
if ( did_action( 'deactivate_' . $this->get_basename() ) ) {
$this->plugin_deactivation();
}
add_action( 'activate_' . $this->get_basename(), [ $this, 'plugin_activation' ], 20 );
add_action( 'deactivate_' . $this->get_basename(), [ $this, 'plugin_deactivation' ], 20 );
if ( ! function_exists( 'ewww_image_optimizer_get_option' ) ) {
return;
}
/**
* Since Rocket already updates the config file after updating its options, there is no need to do it again if the CDN or zone options change.
* Sadly, we cant monitor EWWW options accurately to update our config file.
*/
add_filter( 'rocket_cdn_cnames', [ $this, 'maybe_remove_images_cnames' ], 1000, 2 );
add_filter( 'rocket_allow_cdn_images', [ $this, 'maybe_remove_images_from_cdn_dropdown' ] );
$option_names = [
'ewww_image_optimizer_exactdn',
'ewww_image_optimizer_webp_for_cdn',
];
foreach ( $option_names as $option_name ) {
if ( $this->is_active_for_network() ) {
add_filter( 'add_site_option_' . $option_name, [ $this, 'trigger_webp_change' ] );
add_filter( 'update_site_option_' . $option_name, [ $this, 'trigger_webp_change' ] );
add_filter( 'delete_site_option_' . $option_name, [ $this, 'trigger_webp_change' ] );
} else {
add_filter( 'add_option_' . $option_name, [ $this, 'trigger_webp_change' ] );
add_filter( 'update_option_' . $option_name, [ $this, 'trigger_webp_change' ] );
add_filter( 'delete_option_' . $option_name, [ $this, 'trigger_webp_change' ] );
}
}
}
/**
* Remove CDN hosts for images if EWWW uses ExactDN.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param array $hosts List of CDN URLs.
* @param array $zones List of zones. Default is [ 'all' ].
* @return array
*/
public function maybe_remove_images_cnames( $hosts, $zones ) {
if ( ! $hosts ) {
return $hosts;
}
if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
return $hosts;
}
// EWWW uses ExactDN: WPR CDN should be disabled for images.
if ( ! in_array( 'images', $zones, true ) ) {
// Not asking for images.
return $hosts;
}
if ( ! array_diff( $zones, [ 'all', 'images' ] ) ) {
// This is clearly for images: return an empty list of hosts.
return [];
}
// We also want other things, like js and css: let's only remove the hosts for 'images'.
$cdn_urls = $this->options->get( 'cdn_cnames', [] );
if ( ! $cdn_urls ) {
return $hosts;
}
// Separate image hosts from the other ones.
$image_hosts = [];
$other_hosts = [];
$cdn_zones = $this->options->get( 'cdn_zone', [] );
foreach ( $cdn_urls as $k => $urls ) {
if ( ! in_array( $cdn_zones[ $k ], $zones, true ) ) {
continue;
}
$urls = explode( ',', $urls );
$urls = array_map( 'trim', $urls );
if ( 'images' === $cdn_zones[ $k ] ) {
foreach ( $urls as $url ) {
$image_hosts[] = $url;
}
} else {
foreach ( $urls as $url ) {
$other_hosts[] = $url;
}
}
}
// Make sure the image hosts are not also used for other things (duplicate).
$image_hosts = array_diff( $image_hosts, $other_hosts );
// Then remove the remaining from the final list.
return array_diff( $hosts, $image_hosts );
}
/**
* Maybe remove the images option from the CDN dropdown.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param bool $allow true to add the option, false otherwise.
* @return bool
*/
public function maybe_remove_images_from_cdn_dropdown( $allow ) {
if ( ! $allow ) {
return $allow;
}
if ( ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
return $allow;
}
// EWWW uses ExactDN: WPR CDN should be disabled for images.
return false;
}
/** ----------------------------------------------------------------------------------------- */
/** PUBLIC TOOLS ============================================================================ */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the plugin name.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_name() {
return 'EWWW';
}
/**
* Get the plugin identifier.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_id() {
return 'ewww';
}
/**
* Tell if the plugin converts images to webp.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_converting_to_webp() {
if ( ! function_exists( 'ewww_image_optimizer_get_option' ) ) {
// No EWWW, no webp.
return false;
}
return (bool) ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp' );
}
/**
* Tell if the plugin serves webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp() {
if ( ! function_exists( 'ewww_image_optimizer_get_option' ) ) {
// No EWWW, no webp.
return false;
}
if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
// EWWW uses ExactDN (WPR CDN should be disabled for images).
return true;
}
if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) ) {
// EWWW uses JS to rewrite file extensions.
return true;
}
// Decide if rewrite rules are used.
if ( ! function_exists( 'ewww_image_optimizer_webp_rewrite_verify' ) ) {
// Uh?
return false;
}
if ( ! function_exists( 'get_home_path' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
if ( ! function_exists( 'extract_from_markers' ) ) {
require_once ABSPATH . 'wp-admin/includes/misc.php';
}
/**
* This function returns null if rules are present and valid. Otherwise it returns rules to be inserted.
* Note: this also returns null if WP Fastest Cache rules for webp are found in the file.
*
* @see ewww_image_optimizer_wpfc_webp_enabled()
*/
$use_rewrite_rules = ! ewww_image_optimizer_webp_rewrite_verify();
/**
* Filter wether EWW is using rewrite rules for webp.
*
* @since 3.4
* @author Grégory Viguier
*
* @param bool $use_rewrite_rules True when EWWW uses rewrite rules. False otherwise.
*/
return (bool) apply_filters( 'rocket_webp_ewww_use_rewrite_rules', $use_rewrite_rules );
}
/**
* Tell if the plugin uses a CDN-compatible technique to serve webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp_compatible_with_cdn() {
if ( ! function_exists( 'ewww_image_optimizer_get_option' ) ) {
// No EWWW, no webp.
return false;
}
if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_exactdn' ) ) {
// EWWW uses ExactDN.
return true;
}
if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_webp_for_cdn' ) ) {
// EWWW uses JS to rewrite file extensions.
return true;
}
// At this point, the plugin is using rewrite rules or nothing.
return false;
}
/**
* Get the plugin basename.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function get_basename() {
if ( empty( $this->plugin_basename ) ) {
$this->plugin_basename = rocket_has_constant( 'EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE' )
? plugin_basename( rocket_get_constant( 'EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE' ) )
: 'ewww-image-optimizer/ewww-image-optimizer.php';
}
return $this->plugin_basename;
}
/** ----------------------------------------------------------------------------------------- */
/** PRIVATE TOOLS =========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Tell if EWWW is active for network.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return bool
*/
private function is_active_for_network() {
static $is;
if ( isset( $is ) ) {
return $is;
}
if ( ! is_multisite() ) {
$is = false;
return $is;
}
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$is = is_plugin_active_for_network( $this->get_basename() ) && ! get_site_option( 'ewww_image_optimizer_allow_multisite_override' );
return $is;
}
}

View File

@@ -0,0 +1,435 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins\Images\Webp;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the WebP support with Imagify.
*
* @since 3.4
* @author Grégory Viguier
*/
class Imagify_Subscriber implements Webp_Interface, Subscriber_Interface {
use Webp_Common;
/**
* Options_Data instance.
*
* @var Options_Data
* @access private
* @author Remy Perona
*/
private $options;
/**
* Imagify basename.
*
* @var string
* @access private
* @author Grégory Viguier
*/
private $plugin_basename;
/**
* Imagifys "serve webp" option name.
*
* @var string
* @access private
* @author Grégory Viguier
*/
private $plugin_option_name_to_serve_webp;
/**
* Temporarily store the result of $this->is_serving_webp().
*
* @var bool
* @access private
* @author Grégory Viguier
*/
private $tmp_is_serving_webp;
/**
* Constructor.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param Options_Data $options Options instance.
*/
public function __construct( Options_Data $options ) {
$this->options = $options;
}
/**
* Returns an array of events that this subscriber wants to listen to.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return array
*/
public static function get_subscribed_events() {
return [
'rocket_webp_plugins' => 'register',
'wp_rocket_loaded' => 'load_hooks',
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Launch filters.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function load_hooks() {
if ( ! $this->options->get( 'cache_webp' ) ) {
return;
}
/**
* Every time Imagify is (de)activated, we must "sync" our webp cache option.
*/
add_action( 'imagify_activation', [ $this, 'plugin_activation' ], 20 );
add_action( 'imagify_deactivation', [ $this, 'plugin_deactivation' ], 20 );
if ( ! rocket_has_constant( 'IMAGIFY_VERSION' ) ) {
return;
}
/**
* Since Rocket already updates the config file after updating its options, there is no need to do it again if the CDN or zone options change.
*/
/**
* Every time Imagifys option changes, we must "sync" our webp cache option.
*/
$option_name = $this->get_option_name_to_serve_webp();
if ( $this->is_active_for_network() ) {
add_filter( 'add_site_option_' . $option_name, [ $this, 'sync_on_network_option_add' ], 10, 3 );
add_filter( 'update_site_option_' . $option_name, [ $this, 'sync_on_network_option_update' ], 10, 4 );
add_filter( 'pre_delete_site_option_' . $option_name, [ $this, 'store_option_value_before_network_delete' ], 10, 2 );
add_filter( 'delete_site_option_' . $option_name, [ $this, 'sync_on_network_option_delete' ], 10, 2 );
return;
}
add_filter( 'add_option_' . $option_name, [ $this, 'sync_on_option_add' ], 10, 2 );
add_filter( 'update_option_' . $option_name, [ $this, 'sync_on_option_update' ], 10, 2 );
add_filter( 'delete_option', [ $this, 'store_option_value_before_delete' ] );
add_filter( 'delete_option_' . $option_name, [ $this, 'sync_on_option_delete' ] );
}
/**
* Maybe deactivate webp cache after Imagify network option has been successfully added.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the network option.
* @param mixed $value Value of the network option.
* @param int $network_id ID of the network.
*/
public function sync_on_network_option_add( $option, $value, $network_id ) {
if ( get_current_network_id() === $network_id && ! empty( $value['display_webp'] ) ) {
$this->trigger_webp_change();
}
}
/**
* Maybe activate or deactivate webp cache after Imagify network option has been modified.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the network option.
* @param mixed $value Current value of the network option.
* @param mixed $old_value Old value of the network option.
* @param int $network_id ID of the network.
*/
public function sync_on_network_option_update( $option, $value, $old_value, $network_id ) {
if ( get_current_network_id() === $network_id ) {
$this->sync_on_option_update( $old_value, $value );
}
}
/**
* Store the Imagify network option value before it is deleted.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Option name.
* @param int $network_id ID of the network.
*/
public function store_option_value_before_network_delete( $option, $network_id ) {
if ( get_current_network_id() === $network_id ) {
$this->tmp_is_serving_webp = $this->is_serving_webp();
}
}
/**
* Maybe activate webp cache after Imagify network option has been deleted.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the network option.
* @param int $network_id ID of the network.
*/
public function sync_on_network_option_delete( $option, $network_id ) {
if ( get_current_network_id() === $network_id && false !== $this->tmp_is_serving_webp ) {
$this->trigger_webp_change();
}
}
/**
* Maybe deactivate webp cache after Imagify option has been successfully added.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the option to add.
* @param mixed $value Value of the option.
*/
public function sync_on_option_add( $option, $value ) {
if ( ! empty( $value['display_webp'] ) ) {
$this->trigger_webp_change();
}
}
/**
* Maybe activate or deactivate webp cache after Imagify option has been modified.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param mixed $old_value The old option value.
* @param mixed $value The new option value.
*/
public function sync_on_option_update( $old_value, $value ) {
$old_display = ! empty( $old_value['display_webp'] );
$display = ! empty( $value['display_webp'] );
if ( $old_display !== $display || $old_value['display_webp_method'] !== $value['display_webp_method'] ) {
$this->trigger_webp_change();
}
}
/**
* Store the Imagify option value before it is deleted.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the option to delete.
*/
public function store_option_value_before_delete( $option ) {
if ( $this->get_option_name_to_serve_webp() === $option ) {
$this->tmp_is_serving_webp = $this->is_serving_webp();
}
}
/**
* Maybe activate webp cache after Imagify option has been deleted.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the deleted option.
*/
public function sync_on_option_delete( $option ) {
if ( false !== $this->tmp_is_serving_webp ) {
$this->trigger_webp_change();
}
}
/** ----------------------------------------------------------------------------------------- */
/** PUBLIC TOOLS ============================================================================ */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the plugin name.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_name() {
return 'Imagify';
}
/**
* Get the plugin identifier.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_id() {
return 'imagify';
}
/**
* Tell if the plugin converts images to webp.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_converting_to_webp() {
if ( ! function_exists( 'get_imagify_option' ) ) {
// No Imagify, no webp.
return false;
}
return (bool) get_imagify_option( 'convert_to_webp' );
}
/**
* Tell if the plugin serves webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp() {
if ( ! function_exists( 'get_imagify_option' ) ) {
// No Imagify, no webp.
return false;
}
return (bool) get_imagify_option( 'display_webp' );
}
/**
* Tell if the plugin uses a CDN-compatible technique to serve webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp_compatible_with_cdn() {
if ( ! $this->is_serving_webp() ) {
return false;
}
return 'rewrite' !== get_imagify_option( 'display_webp_method' );
}
/**
* Get the plugin basename.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function get_basename() {
if ( empty( $this->plugin_basename ) ) {
$this->plugin_basename = rocket_has_constant( 'IMAGIFY_FILE' )
? plugin_basename( rocket_get_constant( 'IMAGIFY_FILE' ) )
: 'imagify/imagify.php';
}
return $this->plugin_basename;
}
/** ----------------------------------------------------------------------------------------- */
/** PRIVATE TOOLS =========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the name of the Imagifys "serve webp" option.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return string
*/
private function get_option_name_to_serve_webp() {
if ( ! empty( $this->plugin_option_name_to_serve_webp ) ) {
return $this->plugin_option_name_to_serve_webp;
}
$default = 'imagify_settings';
if ( ! class_exists( '\Imagify_Options' ) || ! method_exists( '\Imagify_Options', 'get_instance' ) ) {
$this->plugin_option_name_to_serve_webp = $default;
return $this->plugin_option_name_to_serve_webp;
}
$instance = \Imagify_Options::get_instance();
if ( ! method_exists( $instance, 'get_option_name' ) ) {
$this->plugin_option_name_to_serve_webp = $default;
return $this->plugin_option_name_to_serve_webp;
}
$this->plugin_option_name_to_serve_webp = $instance->get_option_name();
return $this->plugin_option_name_to_serve_webp;
}
/**
* Tell if Imagify is active for network.
*
* @since 3.4
* @access private
* @author Grégory Viguier
*
* @return bool
*/
private function is_active_for_network() {
static $is;
if ( isset( $is ) ) {
return $is;
}
if ( function_exists( 'imagify_is_active_for_network' ) ) {
$is = imagify_is_active_for_network();
return $is;
}
if ( ! is_multisite() ) {
$is = false;
return $is;
}
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$is = is_plugin_active_for_network( $this->get_basename() );
return $is;
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins\Images\Webp;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the WebP support with Optimus.
*
* @since 3.4
* @author Grégory Viguier
*/
class Optimus_Subscriber implements Webp_Interface, Subscriber_Interface {
/**
* Optimus basename.
*
* @var string
* @access private
* @author Grégory Viguier
*/
private $plugin_basename;
/**
* Returns an array of events that this subscriber wants to listen to.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return array
*/
public static function get_subscribed_events() {
if ( ! defined( 'OPTIMUS_FILE' ) ) {
return [];
}
return [
'rocket_webp_plugins' => 'register',
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Register the plugin.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param array $webp_plugins An array of Webp_Interface objects.
* @return array
*/
public function register( $webp_plugins ) {
$webp_plugins[] = $this;
return $webp_plugins;
}
/** ----------------------------------------------------------------------------------------- */
/** PUBLIC TOOLS ============================================================================ */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the plugin name.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_name() {
return 'Optimus';
}
/**
* Get the plugin identifier.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_id() {
return 'optimus';
}
/**
* Tell if the plugin converts images to webp.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_converting_to_webp() {
if ( class_exists( '\Optimus' ) && method_exists( '\Optimus', 'get_options' ) ) {
$options = \Optimus::get_options();
} else {
$options = get_option( 'optimus' );
}
return ! empty( $options['webp_convert'] );
}
/**
* Tell if the plugin serves webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp() {
return false;
}
/**
* Tell if the plugin uses a CDN-compatible technique to serve webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp_compatible_with_cdn() {
return false;
}
/**
* Get the plugin basename.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function get_basename() {
if ( empty( $this->plugin_basename ) ) {
$this->plugin_basename = defined( 'OPTIMUS_FILE' ) ? plugin_basename( OPTIMUS_FILE ) : 'optimus/optimus.php';
}
return $this->plugin_basename;
}
}

View File

@@ -0,0 +1,294 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins\Images\Webp;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the WebP support with ShortPixel.
*
* @since 3.4
* @author Grégory Viguier
*/
class ShortPixel_Subscriber implements Webp_Interface, Subscriber_Interface {
use Webp_Common;
/**
* Options_Data instance.
*
* @var Options_Data
* @access private
* @author Remy Perona
*/
private $options;
/**
* ShortPixel basename.
*
* @var string
* @access private
* @author Grégory Viguier
*/
private $plugin_basename;
/**
* ShortPixels "serve webp" option name.
*
* @var string
* @access private
* @author Grégory Viguier
*/
private $plugin_option_name_to_serve_webp = 'wp-short-pixel-create-webp-markup';
/**
* Temporarily store the result of $this->is_serving_webp().
*
* @var bool
* @access private
* @author Grégory Viguier
*/
private $tmp_is_serving_webp;
/**
* Constructor.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param Options_Data $options Options instance.
*/
public function __construct( Options_Data $options ) {
$this->options = $options;
}
/**
* Returns an array of events that this subscriber wants to listen to.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return array
*/
public static function get_subscribed_events() {
return [
'rocket_webp_plugins' => 'register',
'wp_rocket_loaded' => 'load_hooks',
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Launch filters.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function load_hooks() {
if ( ! $this->options->get( 'cache_webp' ) ) {
return;
}
/**
* Every time ShortPixel is (de)activated, we must "sync" our webp cache option.
*/
if ( did_action( 'activate_' . $this->get_basename() ) ) {
$this->plugin_activation();
}
if ( did_action( 'deactivate_' . $this->get_basename() ) ) {
$this->plugin_deactivation();
}
add_action( 'activate_' . $this->get_basename(), [ $this, 'plugin_activation' ], 20 );
add_action( 'deactivate_' . $this->get_basename(), [ $this, 'plugin_deactivation' ], 20 );
if ( ! defined( 'SHORTPIXEL_IMAGE_OPTIMISER_VERSION' ) ) {
return;
}
/**
* Since Rocket already updates the config file after updating its options, there is no need to do it again if the CDN or zone options change.
*/
/**
* Every time ShortPixels option changes, we must "sync" our webp cache option.
*/
$option_name = $this->plugin_option_name_to_serve_webp;
add_filter( 'add_option_' . $option_name, [ $this, 'sync_on_option_add' ], 10, 2 );
add_filter( 'update_option_' . $option_name, [ $this, 'sync_on_option_update' ], 10, 2 );
add_filter( 'delete_option', [ $this, 'store_option_value_before_delete' ] );
add_filter( 'delete_option_' . $option_name, [ $this, 'sync_on_option_delete' ] );
}
/**
* Maybe deactivate webp cache after ShortPixel option has been successfully added.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the option to add.
* @param mixed $value Value of the option.
*/
public function sync_on_option_add( $option, $value ) {
if ( $value ) {
$this->trigger_webp_change();
}
}
/**
* Maybe activate or deactivate webp cache after ShortPixel option has been modified.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param mixed $old_value The old option value.
* @param mixed $value The new option value.
*/
public function sync_on_option_update( $old_value, $value ) {
/**
* 0 = Dont serve webp.
* 1 = <picture> + buffer
* 2 = <picture> + hooks
* 3 = .htaccess
*/
$old_value = $old_value > 0;
$value = $value > 0;
if ( $old_value !== $value ) {
$this->trigger_webp_change();
}
}
/**
* Store the ShortPixel option value before it is deleted.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the option to delete.
*/
public function store_option_value_before_delete( $option ) {
if ( $this->plugin_option_name_to_serve_webp === $option ) {
$this->tmp_is_serving_webp = $this->is_serving_webp();
}
}
/**
* Maybe activate webp cache after ShortPixel option has been deleted.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function sync_on_option_delete() {
if ( false !== $this->tmp_is_serving_webp ) {
$this->trigger_webp_change();
}
}
/** ----------------------------------------------------------------------------------------- */
/** PUBLIC TOOLS ============================================================================ */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the plugin name.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_name() {
return 'ShortPixel';
}
/**
* Get the plugin identifier.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_id() {
return 'shortpixel';
}
/**
* Tell if the plugin converts images to webp.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_converting_to_webp() {
return (bool) get_option( 'wp-short-create-webp' );
}
/**
* Tell if the plugin serves webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp() {
return (bool) get_option( $this->plugin_option_name_to_serve_webp );
}
/**
* Tell if the plugin uses a CDN-compatible technique to serve webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp_compatible_with_cdn() {
$display = (int) get_option( $this->plugin_option_name_to_serve_webp );
if ( ! $display ) {
// The option is not enabled, no webp.
return false;
}
if ( 3 === $display ) {
// The option is set to "rewrite rules".
return false;
}
return true;
}
/**
* Get the plugin basename.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function get_basename() {
if ( empty( $this->plugin_basename ) ) {
$this->plugin_basename = defined( 'SHORTPIXEL_PLUGIN_FILE' ) ? plugin_basename( SHORTPIXEL_PLUGIN_FILE ) : 'shortpixel-image-optimiser/wp-shortpixel.php';
}
return $this->plugin_basename;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins\Images\Webp;
/**
* Trait for webp subscribers, focussed on plugins that serve webp images on frontend.
*
* @since 3.4
* @author Grégory Viguier
*/
trait Webp_Common {
/**
* Register the plugin.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param array $webp_plugins An array of Webp_Interface objects.
* @return array
*/
public function register( $webp_plugins ) {
$webp_plugins[] = $this;
return $webp_plugins;
}
/**
* On plugin activation, deactivate Rocket webp cache if the plugin is serving webp.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function plugin_activation() {
if ( $this->is_serving_webp() ) {
$this->trigger_webp_change();
}
}
/**
* On plugin deactivation, activate Rocket webp cache if the plugin is serving webp.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function plugin_deactivation() {
if ( $this->is_serving_webp() ) {
$this->trigger_webp_change();
}
}
/**
* Trigger an action when the webp feature is enabled/disabled in a third party plugin.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*/
public function trigger_webp_change() {
/**
* Trigger an action when the webp feature is enabled/disabled in a third party plugin.
*
* @since 3.4
* @author Grégory Viguier
*/
do_action( 'rocket_third_party_webp_change' );
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins\Images\Webp;
/**
* Interface to use for webp subscribers.
*
* @since 3.4
* @author Grégory Viguier
*/
interface Webp_Interface {
/**
* Get the plugin name.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_name();
/**
* Get the plugin identifier.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return string
*/
public function get_id();
/**
* Tell if the plugin converts images to webp.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_converting_to_webp();
/**
* Tell if the plugin serves webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp();
/**
* Tell if the plugin uses a CDN-compatible technique to serve webp images on frontend.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function is_serving_webp_compatible_with_cdn();
/**
* Get the plugin basename.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @return bool
*/
public function get_basename();
}

View File

@@ -0,0 +1,396 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins;
use WP_Rocket\Event_Management\Subscriber_Interface;
defined( 'ABSPATH' ) || exit;
/**
* Class that handles events related to plugins that add mobile themes.
*
* @since 3.2
* @author Grégory Viguier
*/
class Mobile_Subscriber implements Subscriber_Interface {
/**
* Options to activate when a mobile plugin is active.
*
* @since 3.2
* @access protected
* @author Grégory Viguier
*
* @var array
*/
protected static $options = [
'cache_mobile' => 1,
'do_caching_mobile_files' => 1,
];
/**
* Cache the value of self::is_mobile_plugin_active().
*
* @since 3.2
* @access protected
* @author Grégory Viguier
*
* @var array An array of arrays of booleans.
* First level of keys corresponds to the network ID. Second level of keys corresponds to the blog ID.
*/
protected static $is_mobile_active = [];
/**
* Returns an array of events that this subscriber wants to listen to.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @return array
*/
public static function get_subscribed_events() {
// In case a mobile plugin has already been activated.
$do = [];
$undo = [];
$plugin_events = [];
if ( ! function_exists( '\is_plugin_active' ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
}
foreach ( static::get_mobile_plugins() as $plugin => $plugin_data ) {
if ( \did_action( 'activate_' . $plugin ) &&
! isset( $plugin_data['is_active_callback'] ) ) {
$do[] = $plugin;
}
if ( \did_action( 'activate_' . $plugin ) &&
isset( $plugin_data['is_active_callback'] ) &&
call_user_func( $plugin_data['is_active_callback'] ) ) {
$do[] = $plugin;
}
if ( \did_action( 'deactivate_' . $plugin ) ) {
$undo[] = $plugin;
}
if ( \is_plugin_active( $plugin ) ) {
if ( isset( $plugin_data['activation_hook'] ) ) {
$plugin_events[ $plugin_data['activation_hook'] ] = 'maybe_update_mobile_cache_activation_plugin_hook';
}
if ( isset( $plugin_data['deactivation_hook'] ) ) {
$plugin_events[ $plugin_data['deactivation_hook'] ] = 'maybe_update_mobile_cache_activation_plugin_hook';
}
}
}
if ( array_diff( $do, $undo ) || array_diff( $undo, $do ) ) {
static::update_mobile_cache_activation();
}
// Register events.
$events = [
// Plugin activation/deactivation.
'add_option_active_plugins' => [ 'add_option_callback', 10, 2 ],
'update_option_active_plugins' => [ 'update_option_callback', 10, 2 ],
'delete_option_active_plugins' => 'delete_option_callback',
'add_site_option_active_sitewide_plugins' => [ 'add_site_option_callback', 10, 3 ],
'update_site_option_active_sitewide_plugins' => [ 'update_site_option_callback', 10, 4 ],
'delete_site_option_active_sitewide_plugins' => [ 'delete_site_option_callback', 10, 2 ],
// WPR settings (`get_option()`).
'option_' . WP_ROCKET_SLUG => 'mobile_options_filter',
];
foreach ( static::$options as $option => $value ) {
// WPR settings (`get_rocket_option()`).
$events[ 'pre_get_rocket_option_' . $option ] = 'is_mobile_plugin_active_callback';
}
$events = array_merge( $events, $plugin_events );
return $events;
}
/** ----------------------------------------------------------------------------------------- */
/** HOOK CALLBACKS ========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Callback triggered after the option `active_plugins` is created.
* This should normally never be triggered.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the option to add.
* @param mixed $value Value of the option.
*/
public function add_option_callback( $option, $value ) {
$this->maybe_update_mobile_cache_activation( $value, [] );
}
/**
* Callback triggered after the option `active_plugins` is updated.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param mixed $old_value The old option value.
* @param mixed $value Value of the option.
*/
public function update_option_callback( $old_value, $value ) {
$this->maybe_update_mobile_cache_activation( $value, $old_value );
}
/**
* Callback triggered after the option `active_plugins` is deleted.
* Very low probability to be triggered.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*/
public function delete_option_callback() {
static::update_mobile_cache_activation();
}
/**
* Callback triggered after the option `active_sitewide_plugins` is created.
* This should normally never be triggered.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the option to add.
* @param mixed $value Value of the option.
* @param int $network_id ID of the network.
*/
public function add_site_option_callback( $option, $value, $network_id ) {
if ( get_current_network_id() === $network_id ) {
$this->maybe_update_mobile_cache_activation( $value, [] );
}
}
/**
* Callback triggered after the option `active_sitewide_plugins` is updated.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the option to add.
* @param mixed $value Value of the option.
* @param mixed $old_value The old option value.
* @param int $network_id ID of the network.
*/
public function update_site_option_callback( $option, $value, $old_value, $network_id ) {
if ( get_current_network_id() === $network_id ) {
$this->maybe_update_mobile_cache_activation( $value, $old_value );
}
}
/**
* Callback triggered after the option `active_sitewide_plugins` is deleted.
* Very low probability to be triggered.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param string $option Name of the option to add.
* @param int $network_id ID of the network.
*/
public function delete_site_option_callback( $option, $network_id ) {
if ( get_current_network_id() === $network_id ) {
static::update_mobile_cache_activation();
}
}
/**
* Enable mobile caching when a mobile plugin is activated, or revert it back to its previous state when a mobile plugin is deactivated.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param mixed $value The new option value.
* @param mixed $old_value The old option value.
*/
public function maybe_update_mobile_cache_activation( $value, $old_value ) {
$plugins = static::get_mobile_plugins();
$plugins = array_keys( $plugins );
$value = array_intersect( $plugins, (array) $value );
$old_value = array_intersect( $plugins, (array) $old_value );
if ( $value !== $old_value ) {
static::update_mobile_cache_activation();
}
}
/**
* Enables mobile caching when a mobile plugin option is activated, or reverts it back to its previous state when a mobile plugin option is deactivated.
*
* @since 3.4.2
* @access public
* @author Soponar Cristina
*
* @return void
*/
public function maybe_update_mobile_cache_activation_plugin_hook() {
$is_mobile_plugin_active = static::is_mobile_plugin_active();
static::reset_class_cache();
$is_new_mobile_plugin_active = static::is_mobile_plugin_active();
if ( $is_mobile_plugin_active !== $is_new_mobile_plugin_active ) {
static::update_mobile_cache_activation();
}
}
/**
* Forces the values for the mobile options if a mobile plugin is active.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param array $values Option values.
* @return array
*/
public function mobile_options_filter( $values ) {
if ( static::is_mobile_plugin_active() ) {
return array_merge( (array) $values, static::$options );
}
return $values;
}
/**
* Forces the value for a mobile option if a mobile plugin is active.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param int|null $value Option value.
* @return int|null
*/
public function is_mobile_plugin_active_callback( $value ) {
if ( static::is_mobile_plugin_active() ) {
return 1;
}
return $value;
}
/** ----------------------------------------------------------------------------------------- */
/** MAIN HELPERS ============================================================================ */
/** ----------------------------------------------------------------------------------------- */
/**
* Update the config file and the advanced cache file.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*/
public static function update_mobile_cache_activation() {
// Reset class cache.
static::reset_class_cache();
// Update the config file.
rocket_generate_config_file();
// Update the advanced cache file.
rocket_generate_advanced_cache_file();
// Flush htaccess file.
flush_rocket_htaccess();
}
/**
* Reset `is_mobile_plugin_active()` cache.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*/
public static function reset_class_cache() {
// Reset class cache.
unset( static::$is_mobile_active[ get_current_network_id() ][ get_current_blog_id() ] );
}
/**
* Get the concerned plugins.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @return array
*/
public static function get_mobile_plugins() {
return [
'jetpack/jetpack.php' => [
'is_active_callback' => function() {
if ( ! class_exists( 'Jetpack' ) ) {
return false;
}
return \Jetpack::is_active() && \Jetpack::is_module_active( 'minileven' );
},
'activation_hook' => 'jetpack_activate_module_minileven',
'deactivation_hook' => 'jetpack_deactivate_module_minileven',
],
'wptouch/wptouch.php' => [],
'wiziapp-create-your-own-native-iphone-app/wiziapp.php' => [],
'wordpress-mobile-pack/wordpress-mobile-pack.php' => [],
'wp-mobilizer/wp-mobilizer.php' => [],
'wp-mobile-edition/wp-mobile-edition.php' => [],
'device-theme-switcher/dts_controller.php' => [],
'wp-mobile-detect/wp-mobile-detect.php' => [],
'easy-social-share-buttons3/easy-social-share-buttons3.php' => [],
];
}
/**
* Tell if a mobile plugin is active.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @return bool True if a mobile plugin in the list is active, false otherwise.
*/
public static function is_mobile_plugin_active() {
$network_id = get_current_network_id();
$blog_id = get_current_blog_id();
if ( isset( static::$is_mobile_active[ $network_id ][ $blog_id ] ) ) {
return static::$is_mobile_active[ $network_id ][ $blog_id ];
}
if ( ! function_exists( '\is_plugin_active' ) ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if ( ! isset( static::$is_mobile_active[ $network_id ] ) ) {
static::$is_mobile_active[ $network_id ] = [];
}
foreach ( static::get_mobile_plugins() as $mobile_plugin => $mobile_plugin_data ) {
if ( \is_plugin_active( $mobile_plugin ) &&
isset( $mobile_plugin_data['is_active_callback'] ) &&
call_user_func( $mobile_plugin_data['is_active_callback'] ) ) {
static::$is_mobile_active[ $network_id ][ $blog_id ] = true;
return true;
}
if ( \is_plugin_active( $mobile_plugin ) &&
! isset( $mobile_plugin_data['is_active_callback'] ) ) {
static::$is_mobile_active[ $network_id ][ $blog_id ] = true;
return true;
}
}
static::$is_mobile_active[ $network_id ][ $blog_id ] = false;
return false;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins;
use WP_Rocket\Event_Management\Subscriber_Interface;
defined( 'ABSPATH' ) || exit;
/**
* Class that handles events related to Next Gen Gallery.
*
* @since 3.3.1
* @author Remy Perona
*/
class NGG_Subscriber implements Subscriber_Interface {
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.3.1
* @author Remy Perona
*
* @return array
*/
public static function get_subscribed_events() {
if ( ! class_exists( 'C_NextGEN_Bootstrap' ) ) {
return;
}
return [
'run_ngg_resource_manager' => 'deactivate_resource_manager',
];
}
/**
* Deactivate NGG Resource Manager to prevent conflict with WP Rocket output buffering
*
* @since 3.3.1
* @author Remy Perona
*
* @return bool
*/
public function deactivate_resource_manager() {
return false;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Compatibility class for SyntaxHighlighter plugin
*
* @since 3.3.1
* @author Remy Perona
*/
class SyntaxHighlighter_Subscriber implements Subscriber_Interface {
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.3.1
* @author Remy Perona
*
* @return array
*/
public static function get_subscribed_events() {
if ( ! class_exists( 'SyntaxHighlighter' ) ) {
return [];
}
return [
'rocket_exclude_defer_js' => 'exclude_defer_js_syntaxhighlighter_scripts',
'rocket_exclude_js' => 'exclude_minify_js_syntaxhighlighter_scripts',
];
}
/**
* Adds SyntaxHighlighter scripts to defer JS exclusion
*
* @since 3.3.1
* @author Remy Perona
*
* @param array $excluded_scripts Array of scripts to exclude.
* @return array
*/
public function exclude_defer_js_syntaxhighlighter_scripts( $excluded_scripts ) {
return array_merge(
$excluded_scripts,
[
'syntaxhighlighter/syntaxhighlighter3/scripts/(.*).js',
'syntaxhighlighter/syntaxhighlighter2/scripts/(.*).js',
]
);
}
/**
* Adds SyntaxHighlighter scripts to minify/combine JS exclusion
*
* @since 3.3.1
* @author Remy Perona
*
* @param array $excluded_scripts Array of scripts to exclude.
* @return array
*/
public function exclude_minify_js_syntaxhighlighter_scripts( $excluded_scripts ) {
return array_merge(
$excluded_scripts,
[
rocket_clean_exclude_file( plugins_url( 'syntaxhighlighter/syntaxhighlighter3/scripts/(.*).js' ) ),
rocket_clean_exclude_file( plugins_url( 'syntaxhighlighter/syntaxhighlighter2/scripts/(.*).js' ) ),
]
);
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins\Ecommerce;
use WP_Rocket\Event_Management\Event_Manager;
use WP_Rocket\Event_Management\Event_Manager_Aware_Subscriber_Interface;
/**
* BigCommerce compatibility subscriber
*
* @since 3.3.7
* @author Remy Perona
*/
class BigCommerce_Subscriber implements Event_Manager_Aware_Subscriber_Interface {
use \WP_Rocket\Traits\Config_Updater;
/**
* The WordPress Event Manager
*
* @var Event_Manager;
*/
protected $event_manager;
/**
* {@inheritdoc}
*
* @param Event_Manager $event_manager The WordPress Event Manager.
*/
public function set_event_manager( Event_Manager $event_manager ) {
$this->event_manager = $event_manager;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
$events = [
'activate_bigcommerce/bigcommerce.php' => [ 'activate_bigcommerce', 11 ],
'deactivate_bigcommerce/bigcommerce.php' => [ 'deactivate_bigcommerce', 11 ],
];
if ( function_exists( 'bigcommerce_init' ) ) {
$events['update_option_bigcommerce_login_page_id'] = [ 'after_update_single_option', 10, 2 ];
$events['update_option_bigcommerce_account_page_id'] = [ 'after_update_single_option', 10, 2 ];
$events['update_option_bigcommerce_address_page_id'] = [ 'after_update_single_option', 10, 2 ];
$events['update_option_bigcommerce_orders_page_id'] = [ 'after_update_single_option', 10, 2 ];
$events['update_option_bigcommerce_cart_page_id'] = [ 'after_update_single_option', 10, 2 ];
$events['update_option_bigcommerce_checkout_page_id'] = [ 'after_update_single_option', 10, 2 ];
$events['shutdown'] = 'maybe_update_config';
$events['transition_post_status'] = [ 'maybe_exclude_page', 10, 3 ];
$events['rocket_cache_reject_uri'] = [
[ 'exclude_pages' ],
];
}
return $events;
}
/**
* Add exclusions when activating the BigCommerce plugin
*
* @since 3.3.7
* @author Rémy Perona
*/
public function activate_bigcommerce() {
$this->event_manager->add_callback( 'rocket_cache_reject_uri', [ $this, 'exclude_pages' ] );
// Update .htaccess file rules.
flush_rocket_htaccess();
// Regenerate the config file.
rocket_generate_config_file();
}
/**
* Remove exclusions when deactivating the BigCommerce plugin
*
* @since 3.3.7
* @author Rémy Perona
*/
public function deactivate_woocommerce() {
$this->event_manager->remove_callback( 'rocket_cache_reject_uri', [ $this, 'exclude_pages' ] );
// Update .htaccess file rules.
flush_rocket_htaccess();
// Regenerate the config file.
rocket_generate_config_file();
}
/**
* Maybe regenerate the htaccess & config file if a BigCommerce page is published
*
* @since 3.3.7
* @author Remy Perona
*
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param WP_Post $post Post object.
* @return bool
*/
public function maybe_exclude_page( $new_status, $old_status, $post ) {
if ( 'publish' === $old_status || 'publish' !== $new_status ) {
return false;
}
if ( get_option( 'bigcommerce_login_page_id' ) !== $post->ID && get_option( 'bigcommerce_account_page_id' ) !== $post->ID && get_option( 'bigcommerce_address_page_id' ) !== $post->ID && get_option( 'bigcommerce_orders_page_id' ) !== $post->ID && get_option( 'bigcommerce_cart_page_id' ) !== $post->ID && get_option( 'bigcommerce_checkout_page_id' ) !== $post->ID ) {
return false;
}
// Update .htaccess file rules.
flush_rocket_htaccess();
// Regenerate the config file.
rocket_generate_config_file();
return true;
}
/**
* Exclude BigCommerce login, cart, checkout, account, address and orders pages from caching
*
* @since 3.3.7
*
* @param array $urls An array of excluded pages.
* @return array
*/
public function exclude_pages( $urls ) {
$checkout_urls = $this->exclude_page( get_option( 'bigcommerce_checkout_page_id' ) );
$cart_urls = $this->exclude_page( get_option( 'bigcommerce_cart_page_id' ) );
$account_urls = $this->exclude_page( get_option( 'bigcommerce_account_page_id' ) );
$login_urls = $this->exclude_page( get_option( 'bigcommerce_login_page_id' ) );
$address_urls = $this->exclude_page( get_option( 'bigcommerce_address_page_id' ) );
$orders_urls = $this->exclude_page( get_option( 'bigcommerce_orders_page_id' ) );
return array_merge( $urls, $checkout_urls, $cart_urls, $account_urls, $login_urls, $address_urls, $orders_urls );
}
/**
* Excludes BigCommerce checkout page from cache
*
* @since 3.3.7
* @author Remy Perona
*
* @param int $page_id ID of page to exclude.
* @param string $post_type Post type of the page.
* @param string $pattern Pattern to use for the exclusion.
* @return array
*/
private function exclude_page( $page_id, $post_type = 'page', $pattern = '' ) {
$urls = [];
if ( ! $page_id ) {
return $urls;
}
if ( $page_id <= 0 || (int) get_option( 'page_on_front' ) === $page_id ) {
return $urls;
}
if ( 'publish' !== get_post_status( $page_id ) ) {
return $urls;
}
$urls = get_rocket_i18n_translated_post_urls( $page_id, $post_type, $pattern );
return $urls;
}
}

View File

@@ -0,0 +1,395 @@
<?php
namespace WP_Rocket\Subscriber\Third_Party\Plugins\Security;
use WP_Rocket\Admin\Options_Data as Options;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Logger\Logger;
defined( 'ABSPATH' ) || exit;
/**
* Sucuri Security compatibility.
* %s is here for the other query args.
*
* @since 3.2
* @author Grégory Viguier
*/
class Sucuri_Subscriber implements Subscriber_Interface {
/**
* URL of the API.
*
* @var string
* @since 3.2
* @author Grégory Viguier
*/
const API_URL = 'https://waf.sucuri.net/api?v2&%s';
/**
* Instance of the Option_Data class.
*
* @var Options
* @since 3.2
* @access private
* @author Grégory Viguier
*/
private $options;
/**
* Constructor.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*
* @param Options $options Instance of the Option_Data class.
*/
public function __construct( Options $options ) {
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'after_rocket_clean_domain' => 'maybe_clean_firewall_cache',
'after_rocket_clean_post' => 'maybe_clean_firewall_cache',
'after_rocket_clean_term' => 'maybe_clean_firewall_cache',
'after_rocket_clean_user' => 'maybe_clean_firewall_cache',
'after_rocket_clean_home' => 'maybe_clean_firewall_cache',
'after_rocket_clean_files' => 'maybe_clean_firewall_cache',
'admin_post_rocket_purge_sucuri' => 'do_admin_post_rocket_purge_sucuri',
'admin_notices' => 'maybe_print_notice',
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOK CALLBACKS ========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Clear Sucuri firewall cache.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*/
public function maybe_clean_firewall_cache() {
static $done = false;
if ( $done ) {
return;
}
$done = true;
if ( ! $this->options->get( 'sucury_waf_cache_sync', 0 ) ) {
return;
}
$this->clean_firewall_cache();
}
/**
* Ajax callback to empty Sucury cache.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*/
public function do_admin_post_rocket_purge_sucuri() {
if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'rocket_purge_sucuri' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
wp_nonce_ays( '' );
}
if ( ! current_user_can( 'rocket_purge_sucuri_cache' ) ) {
wp_nonce_ays( '' );
}
$purged = $this->clean_firewall_cache();
if ( is_wp_error( $purged ) ) {
$purged_result = [
'result' => 'error',
/* translators: %s is the error message returned by the API. */
'message' => sprintf( __( 'Sucuri cache purge error: %s', 'rocket' ), $purged->get_error_message() ),
];
} else {
$purged_result = [
'result' => 'success',
'message' => __( 'The Sucuri cache is being cleared. Note that it may take up to two minutes for it to be fully flushed.', 'rocket' ),
];
}
set_transient( get_current_user_id() . '_sucuri_purge_result', $purged_result );
wp_safe_redirect( esc_url_raw( wp_get_referer() ) );
die();
}
/**
* Print an admin notice if the cache failed to be cleared.
*
* @since 3.2
* @access public
* @author Grégory Viguier
*/
public function maybe_print_notice() {
if ( ! current_user_can( 'rocket_purge_sucuri_cache' ) ) {
return;
}
if ( ! is_admin() ) {
return;
}
$user_id = get_current_user_id();
$notice = get_transient( $user_id . '_sucuri_purge_result' );
if ( ! $notice ) {
return;
}
delete_transient( $user_id . '_sucuri_purge_result' );
rocket_notice_html(
[
'status' => $notice['result'],
'message' => $notice['message'],
]
);
}
/** ----------------------------------------------------------------------------------------- */
/** TOOLS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Tell if a API key is well formatted.
*
* @since 3.2.3
* @access public
* @author Grégory Viguier
*
* @param string $api_key An API kay.
* @return array|bool An array with the keys 'k' and 's' (required by the API) if valid. False otherwise.
*/
public static function is_api_key_valid( $api_key ) {
if ( '' !== $api_key && preg_match( '@^(?<k>[a-z0-9]{32})/(?<s>[a-z0-9]{32})$@', $api_key, $matches ) ) {
return $matches;
}
return false;
}
/**
* Clear Sucuri firewall cache.
*
* @since 3.2
* @access private
* @author Grégory Viguier
*
* @return bool|object True on success. A WP_Error object on failure.
*/
private function clean_firewall_cache() {
$api_key = $this->get_api_key();
if ( is_wp_error( $api_key ) ) {
return $api_key;
}
$response = $this->request_api(
[
'a' => 'clear_cache',
'k' => $api_key['k'],
's' => $api_key['s'],
]
);
if ( is_wp_error( $response ) ) {
return $response;
}
Logger::info(
'Sucuri firewall cache cleared.',
[
'sucuri firewall cache',
]
);
return true;
}
/**
* Get the API key.
*
* @since 3.2
* @access private
* @author Grégory Viguier
*
* @return array|object An array with the keys 'k' and 's', required by the API. A WP_Error object if no key or invalid key.
*/
private function get_api_key() {
$api_key = trim( $this->options->get( 'sucury_waf_api_key', '' ) );
if ( ! $api_key ) {
Logger::error(
'API key was not found.',
[
'sucuri firewall cache',
]
);
return new \WP_Error( 'no_sucuri_api_key', __( 'Sucuri firewall API key was not found.', 'rocket' ) );
}
$matches = self::is_api_key_valid( $api_key );
if ( ! $matches ) {
Logger::error(
'API key is invalid.',
[
'sucuri firewall cache',
]
);
return new \WP_Error( 'invalid_sucuri_api_key', __( 'Sucuri firewall API key is invalid.', 'rocket' ) );
}
return [
'k' => $matches['k'],
's' => $matches['s'],
];
}
/**
* Request against the API.
*
* @since 3.2
* @access private
* @author Grégory Viguier
*
* @param array $params Parameters to send.
* @return array|object The response data on success. A WP_Error object on failure.
*/
private function request_api( $params = [] ) {
$params['time'] = time();
$params = $this->build_query( $params );
$url = sprintf( static::API_URL, $params );
try {
/**
* Filters the arguments for the Sucuri API request
*
* @since 3.3.4
* @author Soponar Cristina
*
* @param array $args Arguments for the request.
*/
$args = apply_filters(
'rocket_sucuri_api_request_args',
[
'timeout' => 5,
'redirection' => 5,
'httpversion' => '1.1',
'blocking' => true,
/** This filter is documented in wp-includes/class-wp-http-streams.php */
'sslverify' => apply_filters( 'https_ssl_verify', true ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
]
);
$response = wp_remote_get( $url, $args );
} catch ( \Exception $e ) {
Logger::error(
'Error when contacting the API.',
[
'sucuri firewall cache',
'url' => $url,
'response' => $e->getMessage(),
]
);
return new \WP_Error( 'error_sucuri_api', __( 'Error when contacting Sucuri firewall API.', 'rocket' ) );
}
if ( is_wp_error( $response ) ) {
Logger::error(
'Error when contacting the API.',
[
'sucuri firewall cache',
'url' => $url,
'response' => $response->get_error_message(),
]
);
/* translators: %s is an error message. */
return new \WP_Error( 'wp_error_sucuri_api', sprintf( __( 'Error when contacting Sucuri firewall API. Error message was: %s', 'rocket' ), $response->get_error_message() ) );
}
$contents = wp_remote_retrieve_body( $response );
if ( ! $contents ) {
Logger::error(
'Could not get a response from the API.',
[
'sucuri firewall cache',
'url' => $url,
'response' => $response,
]
);
return new \WP_Error( 'sucuri_api_no_response', __( 'Could not get a response from the Sucuri firewall API.', 'rocket' ) );
}
$data = @json_decode( $contents, true );
if ( ! $data || ! is_array( $data ) ) {
Logger::error(
'Invalid response from the API.',
[
'sucuri firewall cache',
'url' => $url,
'response_body' => $contents,
]
);
return new \WP_Error( 'sucuri_api_invalid_response', __( 'Got an invalid response from the Sucuri firewall API.', 'rocket' ) );
}
if ( empty( $data['status'] ) ) {
Logger::error(
'The action failed.',
[
'sucuri firewall cache',
'url' => $url,
'response_data' => $data,
]
);
if ( empty( $data['messages'] ) || ! is_array( $data['messages'] ) ) {
return new \WP_Error( 'sucuri_api_error_status', __( 'The Sucuri firewall API returned an unknown error.', 'rocket' ) );
}
/* translators: %s is an error message. */
$message = _n( 'The Sucuri firewall API returned the following error: %s', 'The Sucuri firewall API returned the following errors: %s', count( $data['messages'] ), 'rocket' );
$message = sprintf( $message, '<br/>' . implode( '<br/>', $data['messages'] ) );
return new \WP_Error( 'sucuri_api_error_status', $message );
}
return $data;
}
/**
* An i18n-firendly alternative to the built-in PHP method `http_build_query()`.
*
* @param array|object $params An array or object containing properties.
* @return string A URL-encoded string.
*/
private function build_query( $params ) {
if ( ! $params ) {
return '';
}
$params = (array) $params;
foreach ( $params as $param => $value ) {
$params[ $param ] = $param . '=' . rawurlencode( (string) $value );
}
return implode( '&', $params );
}
}