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,166 @@
<?php
namespace WP_Rocket\Engine\Preload;
/**
* Abstract preload class
*
* @since 3.2
* @author Remy Perona
*/
abstract class AbstractPreload {
/**
* Suffix used to identify "mobile items" to preload.
*
* @since 3.5
* @author Grégory Viguier
*
* @var string
*/
const MOBILE_SUFFIX = '##wpm-mobile##';
/**
* Background Process instance
*
* @since 3.2
* @var FullProcess
*/
protected $preload_process;
/**
* Cache processing that use get_rocket_cache_query_string().
*
* @since 3.5
* @author Grégory Viguier
*
* @var array
*/
protected $cache_query_strings;
/**
* Constructor
*
* @since 3.2
* @author Remy Perona
*
* @param FullProcess $preload_process Background Process instance.
*/
public function __construct( FullProcess $preload_process ) {
$this->preload_process = $preload_process;
}
/**
* Cancels any preload process running
*
* @since 3.2
* @author Remy Perona
*
* @return void
*/
public function cancel_preload() {
delete_transient( $this->get_running_transient_name() );
$this->preload_process->cancel_process();
}
/**
* Checks if a process is already running
*
* @since 3.2.1.1
* @author Remy Perona
*
* @return boolean
*/
public function is_process_running() {
return $this->preload_process->is_process_running();
}
/**
* Tell if mobile preload is enabled.
*
* @since 3.5
* @author Grégory Viguier
*
* @return bool
*/
public function is_mobile_preload_enabled() {
return $this->preload_process->is_mobile_preload_enabled();
}
/**
* Get the prefix to prepend to the user agent used for preload to make a HTTP request detected as a mobile device.
*
* @since 3.5.0.2
* @author Grégory Viguier
*
* @return string
*/
public function get_mobile_user_agent_prefix() {
return $this->preload_process->get_mobile_user_agent_prefix();
}
/**
* Get the number of preloaded URLs.
*
* @since 3.5
* @author Grégory Viguier
*
* @return int|bool The number of preloaded URLs. False if the process is not running.
*/
public function get_number_of_preloaded_items() {
$nbr = get_transient( $this->get_running_transient_name() );
if ( false === $nbr ) {
return false;
}
return absint( $nbr );
}
/**
* Create a unique identifier for a given URL.
* This is used for the "mobile items"
*
* @since 3.5
* @author Grégory Viguier
*
* @param string $url A URL.
* @return string
*/
protected function get_url_identifier( $url ) {
if ( ! isset( $this->cache_query_strings ) ) {
$this->cache_query_strings = array_fill_keys( get_rocket_cache_query_string(), '' );
ksort( $this->cache_query_strings );
}
$path = (array) wp_parse_url( $url );
$query = isset( $path['query'] ) ? $path['query'] : '';
$path = isset( $path['path'] ) ? $path['path'] : '';
$path = strtolower( trailingslashit( $path ) );
if ( ! $this->cache_query_strings ) {
return $path;
}
parse_str( $query, $query_array );
$query_array = array_intersect_key( $query_array, $this->cache_query_strings );
$query_array = array_merge( $this->cache_query_strings, $query_array );
return $path . '?' . http_build_query( $query_array );
}
/**
* Get the name of the transient that stores the number of preloaded URLs.
*
* @since 3.5
* @author Grégory Viguier
*
* @return string
*/
protected function get_running_transient_name() {
return sprintf( 'rocket_%s_preload_running', static::PRELOAD_ID );
}
}

View File

@@ -0,0 +1,258 @@
<?php
namespace WP_Rocket\Engine\Preload;
use WP_Rocket_WP_Background_Process;
/**
* Abstract class to be extended by preload process classes.
* Extends the background process class for the preload background process.
* The class extending this one must have a $action property and a task() method at least.
*
* @since 3.5
* @author Grégory Viguier
*
* @see WP_Background_Process
*/
abstract class AbstractProcess extends WP_Rocket_WP_Background_Process {
/**
* Prefix
*
* @since 3.2
* @var string
* @author Remy Perona
*/
protected $prefix = 'rocket';
/**
* Format the item to an array.
*
* @since 3.5
* @author Grégory Viguier
*
* @param array|string $item {
* The item to preload: an array containing the following values.
* A string is allowed for backward compatibility (for the URL).
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request. Optional.
* }
* @param string $source An identifier related to the source of the preload.
* @return array The formatted item. An empty array for invalid items.
*/
public function format_item( $item, $source = '' ) {
if ( is_string( $item ) ) {
$item = [
'url' => $item,
];
} elseif ( ! is_array( $item ) ) {
return [];
}
if ( empty( $item['url'] ) ) {
return [];
}
$item['mobile'] = ! empty( $item['mobile'] );
if ( empty( $item['source'] ) ) {
$item['source'] = is_string( $source ) ? $source : '';
}
return $item;
}
/**
* Tell if mobile preload is enabled.
*
* @since 3.5
* @author Grégory Viguier
*
* @return bool
*/
public function is_mobile_preload_enabled() {
$enabled = get_rocket_option( 'manual_preload' ) && get_rocket_option( 'cache_mobile' ) && get_rocket_option( 'do_caching_mobile_files' );
/**
* Tell if mobile preload is enabled.
*
* @since 3.5
* @author Grégory Viguier
*
* @param bool $enabled True when enabled. False otherwise.
* @param string $action Specific action identifier for the current preload type.
*/
return (bool) apply_filters( 'rocket_mobile_preload_enabled', $enabled, $this->action );
}
/**
* Get the user agent to use for the item.
*
* @since 3.5
* @author Grégory Viguier
*
* @param array $item {
* The item to preload: an array containing the following values.
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request. Optional.
* }
* @return string
*/
public function get_item_user_agent( array $item ) {
if ( $item['mobile'] ) {
return $this->get_mobile_user_agent_prefix() . ' WP Rocket/Preload';
}
return 'WP Rocket/Preload';
}
/**
* Get the prefix to prepend to the user agent used for preload to make a HTTP request detected as a mobile device.
*
* @since 3.5.0.2
* @author Grégory Viguier
*
* @return string
*/
public function get_mobile_user_agent_prefix() {
$prefix = 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1';
/**
* Filter the prefix to prepend to the user agent used for preload to make a HTTP request detected as a mobile device.
*
* @since 3.5.0.2
* @author Grégory Viguier
*
* @param string $prefix The prefix.
*/
$new_prefix = apply_filters( 'rocket_mobile_preload_user_agent_prefix', $prefix );
if ( empty( $new_prefix ) || ! is_string( $new_prefix ) ) {
return $prefix;
}
return $new_prefix;
}
/**
* Preload the URL provided by $item.
*
* @since 3.5
* @author Grégory Viguier
*
* @param array|string $item {
* The item to preload: an array containing the following values.
* A string is allowed for backward compatibility (for the URL).
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request.
* @type string $source An identifier related to the source of the preload.
* }
* @return bool True when preload has been launched. False otherwise.
*/
protected function maybe_preload( $item ) {
$item = $this->format_item( $item );
if ( ! $item || $this->is_already_cached( $item ) ) {
return false;
}
$result = $this->preload( $item );
usleep( absint( get_rocket_option( 'sitemap_preload_url_crawl', 500000 ) ) );
return ! is_wp_error( $result );
}
/**
* Preload the URL provided by $item.
*
* @since 3.5
* @author Grégory Viguier
*
* @param array $item {
* The item to preload: an array containing the following values.
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request.
* @type string $source An identifier related to the source of the preload.
* }
* @return array|WP_Error An array on success. A WP_Error object on failure.
*/
private function preload( array $item ) {
/**
* Filters the arguments for the partial preload request.
*
* @since 2.10.8 'rocket_preload_url_request_args'
* @since 3.2 'rocket_partial_preload_url_request_args'
* @since 3.5 "rocket_{$this->action}_url_request_args"
* @author Remy Perona
*
* @param array $args Request arguments.
*/
$args = apply_filters(
"rocket_{$this->action}_url_request_args",
[
'timeout' => 0.01,
'blocking' => false,
'user-agent' => $this->get_item_user_agent( $item ),
'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
]
);
return wp_remote_get( esc_url_raw( $item['url'] ), $args );
}
/**
* Check if the cache file for $item already exists.
*
* @since 3.2
* @since 3.5 $item is an array.
* @author Remy Perona
*
* @param array $item {
* The item to preload: an array containing the following values.
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request.
* @type string $source An identifier related to the source of the preload.
* }
* @return bool
*/
protected function is_already_cached( $item ) {
static $https;
if ( ! isset( $https ) ) {
$https = is_ssl() && get_rocket_option( 'cache_ssl' ) ? '-https' : '';
}
$url = get_rocket_parse_url( $item['url'] );
/** This filter is documented in inc/functions/htaccess.php */
if ( apply_filters( 'rocket_url_no_dots', false ) ) {
$url['host'] = str_replace( '.', '_', $url['host'] );
}
$url['path'] = trailingslashit( $url['path'] );
if ( '' !== $url['query'] ) {
$url['query'] = '#' . $url['query'] . '/';
}
$mobile = $item['mobile'] ? '-mobile' : '';
$file_cache_path = rocket_get_constant( 'WP_ROCKET_CACHE_PATH' ) . $url['host'] . strtolower( $url['path'] . $url['query'] ) . 'index' . $mobile . $https . '.html';
return rocket_direct_filesystem()->exists( $file_cache_path );
}
/**
* Stop the process.
*
* @since 3.5
* @author Grégory Viguier
*/
public function cancel_process() {
if ( method_exists( get_parent_class( $this ), 'cancel_process' ) ) {
parent::cancel_process();
}
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace WP_Rocket\Engine\Preload;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Engine\CDN\CDN;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Fonts preload.
*
* @since 3.6
*/
class Fonts implements Subscriber_Interface {
/**
* WP Rocket Options instance.
*
* @since 3.6
*
* @var Options_Data
*/
private $options;
/**
* WP Rocket CDN instance.
*
* @since 3.6
*
* @var CDN
*/
private $cdn;
/**
* Font formats allowed to be preloaded.
*
* @since 3.6
* @see $this->sanitize_font()
*
* @var string|bool
*/
private $font_formats = [
'otf',
'ttf',
'svg',
'woff',
'woff2',
];
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.6
*
* @param Options_Data $options WP Rocket Options instance.
* @param CDN $cdn WP Rocket CDN instance.
*/
public function __construct( Options_Data $options, CDN $cdn ) {
$this->options = $options;
$this->cdn = $cdn;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.6
*
* @return array
*/
public static function get_subscribed_events() {
return [
'wp_head' => [ 'preload_fonts' ],
];
}
/**
* Add the required <link/> tags used to preload fonts.
*
* @since 3.6
*/
public function preload_fonts() {
if ( rocket_bypass() ) {
return;
}
$fonts = $this->options->get( 'preload_fonts', [] );
/**
* Filters the list of fonts to preload
*
* @since 3.6
*
* @param array $fonts Array of fonts paths.
*/
$fonts = (array) apply_filters( 'rocket_preload_fonts', $fonts );
$fonts = array_map( [ $this, 'sanitize_font' ], $fonts );
$fonts = array_filter( $fonts );
if ( empty( $fonts ) ) {
return;
}
$base_url = get_rocket_parse_url( home_url() );
$base_url = "{$base_url['scheme']}://{$base_url['host']}";
foreach ( array_unique( $fonts ) as $font ) {
printf(
"\n<link rel=\"preload\" as=\"font\" href=\"%s\" crossorigin>",
esc_url( $this->cdn->rewrite_url( $base_url . $font ) )
);
}
}
/**
* Sanitize a font file path.
*
* @since 3.6
*
* @param string $file Filepath to sanitize.
* @return string|bool Sanitized filepath. False if not a font file.
*/
private function sanitize_font( $file ) {
if ( ! is_string( $file ) ) {
return false;
}
$file = trim( $file );
if ( empty( $file ) ) {
return false;
}
$ext = strtolower( pathinfo( wp_parse_url( $file, PHP_URL_PATH ), PATHINFO_EXTENSION ) );
if ( ! in_array( $ext, $this->font_formats, true ) ) {
return false;
}
return $file;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace WP_Rocket\Engine\Preload;
defined( 'ABSPATH' ) || exit;
/**
* Extends the background process class for the preload background process.
*
* @since 3.2
* @author Remy Perona
*
* @see WP_Background_Process
*/
class FullProcess extends AbstractProcess {
/**
* Specific action identifier for the current preload type.
*
* @since 3.2
* @author Remy Perona
*
* @var string
*/
protected $action = 'preload';
/**
* Preload the URL provided by $item.
*
* @since 3.2
* @since 3.5 $item can be an array.
* @author Remy Perona
*
* @param array|string $item {
* The item to preload: an array containing the following values.
* A string is allowed for backward compatibility (for the URL).
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request.
* @type string $source An identifier related to the source of the preload.
* }
* @return bool False.
*/
protected function task( $item ) {
$result = $this->maybe_preload( $item );
if ( $result && ! empty( $item['source'] ) && ( ! is_array( $item ) || empty( $item['mobile'] ) ) ) {
// Count only successful non mobile items.
$transient_name = sprintf( 'rocket_%s_preload_running', $item['source'] );
$preload_count = get_transient( $transient_name );
set_transient( $transient_name, $preload_count + 1 );
}
return false;
}
/**
* Updates transients on complete
*
* @since 3.2
* @author Remy Perona
*/
public function complete() {
$homepage_count = get_transient( 'rocket_homepage_preload_running' );
$sitemap_count = get_transient( 'rocket_sitemap_preload_running' );
set_transient( 'rocket_preload_complete', $homepage_count + $sitemap_count );
set_transient( 'rocket_preload_complete_time', date_i18n( get_option( 'date_format' ) ) . ' @ ' . date_i18n( get_option( 'time_format' ) ) );
delete_transient( 'rocket_homepage_preload_running' );
delete_transient( 'rocket_sitemap_preload_running' );
parent::complete();
}
/**
* Checks if a process is already running.
* This allows the method to be public.
*
* @since 3.2.1.1
* @access public
* @author Remy Perona
* @see WP_Background_Process::is_process_running()
*
* @return boolean
*/
public function is_process_running() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found
return parent::is_process_running();
}
}

View File

@@ -0,0 +1,315 @@
<?php
namespace WP_Rocket\Engine\Preload;
/**
* Preloads the homepage and the internal URLs on it.
*
* @since 3.2
* @author Remy Perona
*/
class Homepage extends AbstractPreload {
/**
* An ID used in the "running" transients name.
*
* @since 3.5
* @author Grégory Viguier
*
* @var string
*/
const PRELOAD_ID = 'homepage';
/**
* Gets the internal URLs on the homepage and sends them to the preload queue.
*
* @since 3.2
* @author Remy Perona
*
* @param array $home_urls Homepages URLs to preload.
* @return void
*/
public function preload( array $home_urls ) {
if ( ! $home_urls ) {
return;
}
$home_urls = array_map( [ $this->preload_process, 'format_item' ], $home_urls );
$home_urls = array_filter( $home_urls );
if ( ! $home_urls ) {
return;
}
if ( $this->preload_process->is_mobile_preload_enabled() ) {
foreach ( $home_urls as $home_item ) {
if ( $home_item['mobile'] ) {
continue;
}
$home_urls[] = [
'url' => $home_item['url'],
'mobile' => true,
];
}
}
$preload_urls = [];
$nbr_homes = 0;
foreach ( $home_urls as $home_item ) {
$urls = $this->get_urls( $home_item );
if ( false !== $urls && ! $home_item['mobile'] ) {
// Homepage successfully preloaded (and not mobile).
++$nbr_homes;
}
if ( ! $urls ) {
continue;
}
$home_host = wp_parse_url( $home_item['url'], PHP_URL_HOST );
foreach ( $urls as $url ) {
if ( ! $this->should_preload( $url, $home_item['url'], $home_host ) ) {
continue;
}
$path = $this->get_url_identifier( $url );
if ( ! $home_item['mobile'] && ! isset( $preload_urls[ $path ] ) ) {
// Not a URL for mobile.
$preload_urls[ $path ] = [
'url' => $url,
'mobile' => false,
'source' => self::PRELOAD_ID,
];
}
if ( $home_item['mobile'] && ! isset( $preload_urls[ $path . self::MOBILE_SUFFIX ] ) ) {
// A URL for mobile.
$preload_urls[ $path . self::MOBILE_SUFFIX ] = [
'url' => $url,
'mobile' => true,
'source' => self::PRELOAD_ID,
];
}
}
}
if ( ! $preload_urls ) {
return;
}
array_map( [ $this->preload_process, 'push_to_queue' ], $preload_urls );
set_transient( $this->get_running_transient_name(), $nbr_homes );
$this->preload_process->save()->dispatch();
}
/**
* Gets links in the content of the URL provided.
*
* @since 3.2.2
* @since 3.5 $item is an array.
* @author Remy Perona
*
* @param array $item {
* The item to get content and links from: an array containing the following values.
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request. Optional.
* }
* @return bool|array
*/
private function get_urls( array $item ) {
$user_agent = $this->preload_process->get_item_user_agent( $item );
/**
* Filters the arguments for the partial preload request.
*
* @since 3.2
* @author Remy Perona
*
* @param array $args Request arguments.
*/
$args = apply_filters(
'rocket_homepage_preload_url_request_args',
[
'timeout' => 10,
'user-agent' => $user_agent,
'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
]
);
$response = wp_remote_get( esc_url_raw( $item['url'] ), $args );
$errors = get_transient( 'rocket_preload_errors' );
$errors = is_array( $errors ) ? $errors : [];
$errors['errors'] = isset( $errors['errors'] ) && is_array( $errors['errors'] ) ? $errors['errors'] : [];
if ( is_wp_error( $response ) ) {
// Translators: %1$s is an URL, %2$s is the error message, %3$s = opening link tag, %4$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Preload encountered an error. Could not gather links on %1$s because of the following error: %2$s. %3$sLearn more%4$s.', 'rocket' ), $item['url'], $response->get_error_message(), '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
set_transient( 'rocket_preload_errors', $errors );
return false;
}
$response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $response_code ) {
switch ( $response_code ) {
case 401:
case 403:
// Translators: %1$s is an URL, %2$s is the HTTP response code, %3$s = opening link tag, %4$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Preload encountered an error. %1$s is not accessible to due to the following response code: %2$s. Security measures could be preventing access. %3$sLearn more%4$s.', 'rocket' ), $item['url'], $response_code, '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
set_transient( 'rocket_preload_errors', $errors );
break;
case 404:
// Translators: %1$s is an URL, %2$s = opening link tag, %3$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Preload encountered an error. %1$s is not accessible to due to the following response code: 404. Please make sure your homepage is accessible in your browser. %2$sLearn more%3$s.', 'rocket' ), $item['url'], '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
set_transient( 'rocket_preload_errors', $errors );
break;
case 500:
// Translators: %1$s is an URL, %2$s = opening link tag, %3$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Preload encountered an error. %1$s is not accessible to due to the following response code: 500. Please check with your web host about server access. %2$sLearn more%3$s.', 'rocket' ), $item['url'], '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
set_transient( 'rocket_preload_errors', $errors );
break;
default:
// Translators: %1$s is an URL, %2$s is the HTTP response code, %3$s = opening link tag, %4$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Preload encountered an error. Could not gather links on %1$s because it returned the following response code: %2$s. %3$sLearn more%4$s.', 'rocket' ), $item['url'], $response_code, '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
set_transient( 'rocket_preload_errors', $errors );
break;
}
return false;
}
$content = wp_remote_retrieve_body( $response );
preg_match_all( '/<a\s+(?:[^>]+?[\s"\']|)href\s*=\s*(["\'])(?<href>[^"\']+)\1/imU', $content, $urls );
return array_unique( $urls['href'] );
}
/**
* Checks if the URL should be preloaded.
*
* @since 3.2.2
* @access private
* @author Remy Perona
*
* @param string $url URL to check.
* @param string $home_url Homepage URL.
* @param string $home_host Homepage host.
* @return bool
*/
private function should_preload( $url, $home_url, $home_host ) {
$url = html_entity_decode( $url ); // & symbols in URLs are changed to &#038; when using WP Menu editor
$url_data = get_rocket_parse_url( $url );
if ( empty( $url_data ) ) {
return false;
}
if ( ! empty( $url_data['fragment'] ) ) {
return false;
}
if ( empty( $url_data['host'] ) ) {
$url = home_url( $url );
}
$url = \rocket_add_url_protocol( $url );
if ( untrailingslashit( $url ) === untrailingslashit( $home_url ) ) {
return false;
}
if ( $home_host !== $url_data['host'] ) {
return false;
}
if ( $this->is_file_url( $url ) ) {
return false;
}
if ( ! empty( $url_data['path'] ) && preg_match( '#^(' . \get_rocket_cache_reject_uri() . ')$#', $url_data['path'] ) ) {
return false;
}
$cache_query_strings = implode( '|', \get_rocket_cache_query_string() );
if ( ! empty( $url_data['query'] ) && ! preg_match( '/(' . $cache_query_strings . ')/iU', $url_data['query'] ) ) {
return false;
}
return true;
}
/**
* Checks if URL is an URL to a file.
*
* @since 3.2.2
* @access private
* @author Remy Perona
*
* @param string $url URL to check.
* @return bool
*/
private function is_file_url( $url ) {
/**
* Filters the list of files types to check when getting URLs on the homepage
*
* @since 3.2.2
* @author Remy Perona
*
* @param array $file_types Array of file extensions.
*/
$file_types = apply_filters(
'rocket_preload_file_types',
[
'jpg',
'jpeg',
'jpe',
'png',
'gif',
'webp',
'bmp',
'tiff',
'mp3',
'ogg',
'mp4',
'm4v',
'avi',
'mov',
'flv',
'swf',
'webm',
'pdf',
'doc',
'docx',
'txt',
'zip',
'tar',
'bz2',
'tgz',
'rar',
]
);
$file_types = implode( '|', $file_types );
if ( preg_match( '#\.(?:' . $file_types . ')$#iU', $url ) ) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace WP_Rocket\Engine\Preload\Links;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
class AdminSubscriber implements Subscriber_Interface {
/**
* Options Data instance
*
* @var Options_Data
*/
private $options;
/**
* Instantiate the class.
*
* @param Options_Data $options Options Data instance.
*/
public function __construct( Options_Data $options ) {
$this->options = $options;
}
/**
* Events this subscriber wants to listen to.
*
* @return array
*/
public static function get_subscribed_events() {
return [
'rocket_first_install_options' => 'add_option',
'rocket_plugins_to_deactivate' => 'add_incompatible_plugins',
];
}
/**
* Adds the option key & value to the WP Rocket options array
*
* @since 3.7
*
* @param array $options WP Rocket options array.
* @return array
*/
public function add_option( $options ) {
$options = (array) $options;
$options['preload_links'] = 0;
return $options;
}
/**
* Adds plugins incompatible with preload links to our notice.
*
* @since 3.7
*
* @param array $plugins Array of incompatible plugins.
* @return array
*/
public function add_incompatible_plugins( $plugins ) {
if ( ! (bool) $this->options->get( 'preload_links', 0 ) ) {
return $plugins;
}
$plugins['flying-pages'] = 'flying-pages/flying-pages.php';
$plugins['instant-page'] = 'instant-page/instantpage.php';
$plugins['quicklink'] = 'quicklink/quicklink.php';
return $plugins;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace WP_Rocket\Engine\Preload\Links;
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
/**
* Service provider for WP Rocket preload links.
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* The provides array is a way to let the container
* know that a service is provided by this service
* provider. Every service that is registered via
* this service provider must have an alias added
* to this array or it will be ignored.
*
* @var array
*/
protected $provides = [
'preload_links_admin_subscriber',
'preload_links_subscriber',
];
/**
* Registers the subscribers in the container
*
* @return void
*/
public function register() {
$options = $this->getContainer()->get( 'options' );
$this->getContainer()->share( 'preload_links_admin_subscriber', 'WP_Rocket\Engine\Preload\Links\AdminSubscriber' )
->withArgument( $options );
$this->getContainer()->share( 'preload_links_subscriber', 'WP_Rocket\Engine\Preload\Links\Subscriber' )
->withArgument( $options )
->withArgument( rocket_direct_filesystem() );
}
}

View File

@@ -0,0 +1,209 @@
<?php
namespace WP_Rocket\Engine\Preload\Links;
use WP_Filesystem_Direct;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
class Subscriber implements Subscriber_Interface {
/**
* Options Data instance
*
* @var Options_Data
*/
private $options;
/**
* WP_Filesystem_Direct instance.
*
* @var WP_Filesystem_Direct
*/
private $filesystem;
/**
* Script enqueued status.
*
* @var bool
*/
private $is_enqueued = false;
/**
* Instantiate the class.
*
* @param Options_Data $options Options Data instance.
* @param WP_Filesystem_Direct $filesystem The Filesystem object.
*/
public function __construct( Options_Data $options, $filesystem ) {
$this->options = $options;
$this->filesystem = $filesystem;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @return array
*/
public static function get_subscribed_events() {
return [
'wp_enqueue_scripts' => 'add_preload_script',
];
}
/**
* Adds the inline script to the footer when the option is enabled
*
* @since 3.7
*
* @return void
*/
public function add_preload_script() {
if ( $this->is_enqueued ) {
return;
}
if ( ! (bool) $this->options->get( 'preload_links', 0 ) || rocket_bypass() ) {
return;
}
$js_assets_path = rocket_get_constant( 'WP_ROCKET_PATH' ) . 'assets/js/';
if ( ! wp_script_is( 'rocket-browser-checker' ) ) {
$checker_filename = rocket_get_constant( 'SCRIPT_DEBUG' ) ? 'browser-checker.js' : 'browser-checker.min.js';
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion
wp_register_script(
'rocket-browser-checker',
'',
[],
'',
true
);
wp_enqueue_script( 'rocket-browser-checker' );
wp_add_inline_script(
'rocket-browser-checker',
$this->filesystem->get_contents( "{$js_assets_path}{$checker_filename}" )
);
}
$preload_filename = rocket_get_constant( 'SCRIPT_DEBUG' ) ? 'preload-links.js' : 'preload-links.min.js';
// Register handle with no src to add the inline script after.
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion
wp_register_script(
'rocket-preload-links',
'',
[
'rocket-browser-checker',
],
'',
true
);
wp_enqueue_script( 'rocket-preload-links' );
wp_add_inline_script(
'rocket-preload-links',
$this->filesystem->get_contents( "{$js_assets_path}{$preload_filename}" )
);
wp_localize_script(
'rocket-preload-links',
'RocketPreloadLinksConfig',
$this->get_preload_links_config()
);
$this->is_enqueued = true;
}
/**
* Gets the Preload Links script configuration parameters.
*
* @since 3.7
*
* @return string[] Preload Links script configuration parameters.
*/
private function get_preload_links_config() {
$use_trailing_slash = $this->use_trailing_slash();
$images_ext = 'jpg|jpeg|gif|png|tiff|bmp|webp|avif';
$config = [
'excludeUris' => $this->get_uris_to_exclude( $use_trailing_slash ),
'usesTrailingSlash' => $use_trailing_slash,
'imageExt' => $images_ext,
'fileExt' => $images_ext . '|php|pdf|html|htm',
'siteUrl' => home_url(),
'onHoverDelay' => 100, // milliseconds. -1 disables the "on hover" feature.
'rateThrottle' => 3, // on hover: limits the number of links preloaded per second.
];
/**
* Preload Links script configuration parameters.
*
* This array of parameters are passed as RocketPreloadLinksConfig object and used by the
* `preload-links.min.js` script to configure the behavior of the Preload Links feature.
*
* @since 3.7
*
* @param string[] $config Preload Links script configuration parameters.
*/
$filtered_config = apply_filters( 'rocket_preload_links_config', $config );
if ( ! is_array( $filtered_config ) ) {
return $config;
}
return array_merge( $config, $filtered_config );
}
/**
* Gets the URIs to exclude.
*
* @since 3.7
*
* @param bool $use_trailing_slash When true, uses trailing slash.
*
* @return string
*/
private function get_uris_to_exclude( $use_trailing_slash ) {
$site_url = site_url();
$uris = get_rocket_cache_reject_uri();
$uris = str_replace( [ '/(.*)|', '/(.*)/|' ], '/|', $uris );
foreach ( [ '/wp-admin', '/logout' ] as $uri ) {
$uris .= "|{$uri}";
if ( $use_trailing_slash ) {
$uris .= '/';
}
}
foreach ( [ wp_logout_url(), wp_login_url() ] as $uri ) {
if ( strpos( $uri, '?' ) !== false ) {
continue;
}
$uris .= '|' . str_replace( $site_url, '', $uri );
}
return $uris;
}
/**
* Checks if the given URL has a trailing slash.
*
* @since 3.7
*
* @param string $url URL to check.
*
* @return bool
*/
private function has_trailing_slash( $url ) {
return substr( $url, -1 ) === '/';
}
/**
* Indicates if the site uses a trailing slash in the permalink structure.
*
* @since 3.7
*
* @return bool when true, uses `/`; else, no.
*/
private function use_trailing_slash() {
return $this->has_trailing_slash( get_permalink() );
}
}

View File

@@ -0,0 +1,236 @@
<?php
namespace WP_Rocket\Engine\Preload;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Admin\Options_Data;
/**
* Subscriber for the partial preload
*
* @since 3.2
* @author Remy Perona
*/
class PartialPreloadSubscriber implements Subscriber_Interface {
/**
* Partial preload process instance
*
* @since 3.2
* @author Remy Perona
*
* @var PartialProcess
*/
private $partial_preload;
/**
* Options instance
*
* @since 3.2
* @author Remy Perona
*
* @var Options_Data
*/
private $options;
/**
* Stores the URLs to preload
*
* @since 3.2.1
* @author Remy Perona
*
* @var array
*/
private $urls = [];
/**
* Constructor
*
* @since 3.2
* @author Remy Perona
*
* @param PartialProcess $partial Partial preload instance.
* @param Options_Data $options Options instance.
*/
public function __construct( PartialProcess $partial, Options_Data $options ) {
$this->partial_preload = $partial;
$this->options = $options;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.2
* @author Remy Perona
*
* @return array
*/
public static function get_subscribed_events() {
return [
'after_rocket_clean_post' => [ 'preload_after_clean_post', 10, 3 ],
'after_rocket_clean_term' => [ 'preload_after_clean_term', 10, 3 ],
'rocket_after_automatic_cache_purge' => 'preload_after_automatic_cache_purge',
'shutdown' => [ 'maybe_dispatch', PHP_INT_MAX ],
];
}
/**
* Pushes URLs to preload to the queue after a post has been updated
*
* @since 3.2
*
* @param object $post The post object.
* @param array $purge_urls An array of URLs to clean.
* @param string $lang The language to clean.
*/
public function preload_after_clean_post( $post, $purge_urls, $lang ) {
if ( ! $this->options->get( 'manual_preload' ) ) {
return;
}
// Run preload only if post is published.
if ( 'publish' !== $post->post_status ) {
return false;
}
// Add Homepage URL to $purge_urls for preload.
array_push( $purge_urls, get_rocket_i18n_home_url( $lang ) );
// Get the author page.
$purge_author = [ get_author_posts_url( $post->post_author ) ];
// Remove author page from preload cache.
$purge_urls = array_diff( $purge_urls, $purge_author );
$purge_urls = array_filter( $purge_urls );
$this->urls = array_merge( $this->urls, $purge_urls );
}
/**
* Pushes URLs to preload to the queue after cache directories are purged.
*
* @since 3.4
* @access public
* @author Grégory Viguier
*
* @param array $deleted {
* An array of arrays, described like: {.
* @type string $home_url The home URL.
* @type string $home_path Path to home.
* @type bool $logged_in True if the home path corresponds to a logged in users folder.
* @type array $files A list of paths of files that have been deleted.
* }
* }
*/
public function preload_after_automatic_cache_purge( $deleted ) {
if ( ! $deleted || ! $this->options->get( 'manual_preload' ) ) {
return;
}
foreach ( $deleted as $data ) {
if ( $data['logged_in'] ) {
// Logged in user: no need to preload those since we would need the corresponding cookies.
continue;
}
foreach ( $data['files'] as $file_path ) {
if ( strpos( $file_path, '#' ) ) {
// URL with query string.
$file_path = preg_replace( '/#/', '?', $file_path, 1 );
} else {
$file_path = untrailingslashit( $file_path );
$data['home_path'] = untrailingslashit( $data['home_path'] );
$data['home_url'] = untrailingslashit( $data['home_url'] );
if ( '/' === substr( get_option( 'permalink_structure' ), -1 ) ) {
$file_path .= '/';
$data['home_path'] .= '/';
$data['home_url'] .= '/';
}
}
$this->urls[] = str_replace( $data['home_path'], $data['home_url'], $file_path );
}
}
}
/**
* Pushes URLs to preload to the queue after a term has been updated
*
* @since 3.2
*
* @param object $term The term object.
* @param array $purge_urls An array of URLs to clean.
* @param string $lang The language to clean.
*/
public function preload_after_clean_term( $term, $purge_urls, $lang ) {
if ( ! $this->options->get( 'manual_preload' ) ) {
return;
}
// Add Homepage URL to $purge_urls for preload.
array_push( $purge_urls, get_rocket_i18n_home_url( $lang ) );
$purge_urls = array_filter( $purge_urls );
$this->urls = array_merge( $this->urls, $purge_urls );
}
/**
* Starts the partial preload process if there is any URLs saved
*
* @since 3.2.1
* @author Remy Perona
*
* @return void
*/
public function maybe_dispatch() {
if ( wp_doing_ajax() ) {
return;
}
if ( empty( $this->urls ) ) {
return;
}
$this->urls = array_unique( $this->urls );
/**
* Limit the number of URLs to preload.
* The value may change in the future, depending on the results.
*
* @since 3.4
* @author Grégory Viguier
*
* @param int $limit Maximum number of URLs to preload at once.
*/
$limit = (int) apply_filters( 'rocket_preload_limit_number', 100 );
$count = 0;
$mobile = $this->partial_preload->is_mobile_preload_enabled();
foreach ( $this->urls as $url ) {
$path = wp_parse_url( $url, PHP_URL_PATH );
if ( isset( $path ) && preg_match( '#^(' . \get_rocket_cache_reject_uri() . ')$#', $path ) ) {
continue;
}
$this->partial_preload->push_to_queue( $url );
if ( $mobile ) {
$this->partial_preload->push_to_queue(
[
'url' => $url,
'mobile' => true,
]
);
}
++$count;
if ( $count >= $limit ) {
break;
}
}
$this->partial_preload->save()->dispatch();
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace WP_Rocket\Engine\Preload;
/**
* Extends the background process class for the partial preload background process.
*
* @since 3.2
* @author Remy Perona
*
* @see WP_Background_Process
*/
class PartialProcess extends AbstractProcess {
/**
* Specific action identifier for partial preload
*
* @since 3.2
* @var string
*/
protected $action = 'partial_preload';
/**
* Preload the URL provided by $item.
*
* @since 3.2
* @since 3.5 $item can be an array.
* @author Remy Perona
*
* @param array|string $item {
* The item to preload: an array containing the following values.
* A string is allowed for backward compatibility (for the URL).
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request.
* @type string $source An identifier related to the source of the preload (e.g. RELOAD_ID).
* }
* @return bool False.
*/
protected function task( $item ) {
$this->maybe_preload( $item );
return false;
}
}

View File

@@ -0,0 +1,375 @@
<?php
namespace WP_Rocket\Engine\Preload;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Preload Subscriber
*
* @since 3.2
* @author Remy Perona
*/
class PreloadSubscriber implements Subscriber_Interface {
/**
* Homepage Preload instance
*
* @since 3.2
* @author Remy Perona
*
* @var Homepage
*/
private $homepage_preloader;
/**
* WP Rocket Options instance.
*
* @since 3.2
* @author Remy Perona
*
* @var Options_Data
*/
private $options;
/**
* Constructor.
*
* @since 3.2
* @author Remy Perona
*
* @param Homepage $homepage_preloader Homepage Preload instance.
* @param Options_Data $options WP Rocket Options instance.
*/
public function __construct( Homepage $homepage_preloader, Options_Data $options ) {
$this->homepage_preloader = $homepage_preloader;
$this->options = $options;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.2
* @author Remy Perona
*
* @return array
*/
public static function get_subscribed_events() {
return [
'admin_notices' => [
[ 'notice_preload_triggered' ],
[ 'notice_preload_running' ],
[ 'notice_preload_complete' ],
],
'admin_post_rocket_stop_preload' => [ 'do_admin_post_stop_preload' ],
'pagely_cache_purge_after' => [ 'run_preload', 11 ],
'update_option_' . WP_ROCKET_SLUG => [
[ 'maybe_launch_preload', 11, 2 ],
[ 'maybe_cancel_preload', 10, 2 ],
],
'rocket_after_preload_after_purge_cache' => [
[ 'maybe_preload_mobile_homepage', 10, 3 ],
],
];
}
/**
* Launches the homepage preload
*
* @since 3.2
* @author Remy Perona
*
* @param string $lang The language code to preload.
* @return void
*/
protected function preload( $lang = '' ) {
if ( $lang ) {
$urls = (array) get_rocket_i18n_home_url( $lang );
} else {
$urls = get_rocket_i18n_uri();
}
$this->homepage_preloader->preload( $urls );
}
/**
* Launches the homepage preload if the option is active
*
* @since 3.2
* @author Remy Perona
*/
public function run_preload() {
if ( ! $this->options->get( 'manual_preload' ) ) {
return;
}
delete_transient( 'rocket_preload_errors' );
$this->preload();
}
/**
* Cancels any preload currently running if the option is deactivated
*
* @since 3.2
* @author Remy Perona
*
* @param array $old_value Previous option values.
* @param array $value New option values.
* @return void
*/
public function maybe_cancel_preload( $old_value, $value ) {
if ( isset( $old_value['manual_preload'], $value['manual_preload'] ) && $old_value['manual_preload'] !== $value['manual_preload'] && 0 === (int) $value['manual_preload'] ) {
delete_transient( 'rocket_preload_errors' );
$this->homepage_preloader->cancel_preload();
}
}
/**
* Launches the preload if the option is activated
*
* @since 3.2
* @author Remy Perona
*
* @param array $old_value Previous option values.
* @param array $value New option values.
* @return void
*/
public function maybe_launch_preload( $old_value, $value ) {
if ( $this->homepage_preloader->is_process_running() ) {
return;
}
// These values are ignored because they don't impact the cache content.
$ignored_options = [
'cache_mobile' => true,
'purge_cron_interval' => true,
'purge_cron_unit' => true,
'sitemap_preload' => true,
'sitemaps' => true,
'database_revisions' => true,
'database_auto_drafts' => true,
'database_trashed_posts' => true,
'database_spam_comments' => true,
'database_trashed_comments' => true,
'database_expired_transients' => true,
'database_all_transients' => true,
'database_optimize_tables' => true,
'schedule_automatic_cleanup' => true,
'automatic_cleanup_frequency' => true,
'do_cloudflare' => true,
'cloudflare_email' => true,
'cloudflare_api_key' => true,
'cloudflare_zone_id' => true,
'cloudflare_devmode' => true,
'cloudflare_auto_settings' => true,
'cloudflare_old_settings' => true,
'heartbeat_admin_behavior' => true,
'heartbeat_editor_behavior' => true,
'varnish_auto_purge' => true,
'do_beta' => true,
'analytics_enabled' => true,
'sucury_waf_cache_sync' => true,
'sucury_waf_api_key' => true,
];
// Create 2 arrays to compare.
$old_value_diff = array_diff_key( $old_value, $ignored_options );
$value_diff = array_diff_key( $value, $ignored_options );
// If it's different, preload.
if ( md5( wp_json_encode( $old_value_diff ) ) === md5( wp_json_encode( $value_diff ) ) ) {
return;
}
if ( isset( $value['manual_preload'] ) && 1 === (int) $value['manual_preload'] ) {
$this->preload();
}
}
/**
* After automatically preloading the homepage (after purging the cache), also preload the homepage for mobile.
*
* @since 3.5
* @author Grégory Viguier
*
* @param string $home_url URL to the homepage being preloaded.
* @param string $lang The lang of the homepage.
* @param array $args Arguments used for the preload request.
*/
public function maybe_preload_mobile_homepage( $home_url, $lang, $args ) {
if ( ! $this->homepage_preloader->is_mobile_preload_enabled() ) {
return;
}
if ( empty( $args['user-agent'] ) ) {
$args['user-agent'] = 'WP Rocket/Homepage_Preload_After_Purge_Cache';
}
$args['user-agent'] = $this->homepage_preloader->get_mobile_user_agent_prefix() . ' ' . $args['user-agent'];
wp_safe_remote_get( $home_url, $args );
}
/**
* This notice is displayed when the preload is triggered from a different page than WP Rocket settings page
*
* @since 3.2
* @author Remy Perona
*/
public function notice_preload_triggered() {
if ( ! current_user_can( 'rocket_preload_cache' ) ) {
return;
}
$screen = get_current_screen();
if ( 'settings_page_wprocket' === $screen->id ) {
return;
}
if ( false === get_transient( 'rocket_preload_triggered' ) ) {
return;
}
delete_transient( 'rocket_preload_triggered' );
$message = __( 'Preload: WP Rocket has started preloading your website.', 'rocket' );
if ( current_user_can( 'rocket_manage_options' ) ) {
$message .= ' ' . sprintf(
// Translators: %1$s = opening link tag, %2$s = closing link tag.
__( 'Go to the %1$sWP Rocket settings%2$s page to track progress.', 'rocket' ),
'<a href="' . esc_url( admin_url( 'options-general.php?page=' . WP_ROCKET_PLUGIN_SLUG ) ) . '">',
'</a>'
);
}
\rocket_notice_html(
[
'status' => 'info',
'message' => $message,
]
);
}
/**
* This notice is displayed when the preload is running
*
* @since 3.2
* @author Remy Perona
*/
public function notice_preload_running() {
if ( ! current_user_can( 'rocket_preload_cache' ) ) {
return;
}
$screen = get_current_screen();
if ( 'settings_page_wprocket' !== $screen->id ) {
return;
}
$homepage_count = get_transient( 'rocket_homepage_preload_running' );
$sitemap_count = get_transient( 'rocket_sitemap_preload_running' );
if ( false === $homepage_count && false === $sitemap_count ) {
return;
}
$running = $homepage_count + $sitemap_count;
$status = 'info';
// translators: %1$s = Number of pages preloaded.
$message = '<p>' . sprintf( _n( 'Preload: %1$s uncached page has now been preloaded. (refresh to see progress)', 'Preload: %1$s uncached pages have now been preloaded. (refresh to see progress)', $running, 'rocket' ), number_format_i18n( $running ) );
$message .= ' <em> - (' . date_i18n( get_option( 'date_format' ) ) . ' @ ' . date_i18n( get_option( 'time_format' ) ) . ') </em></p>';
if ( defined( 'WP_ROCKET_DEBUG' ) && WP_ROCKET_DEBUG ) {
$errors = get_transient( 'rocket_preload_errors' );
if ( false !== $errors ) {
$status = 'warning';
$message .= '<p>' . _n( 'The following error happened during gathering of the URLs to preload:', 'The following errors happened during gathering of the URLs to preload:', count( $errors['errors'] ), 'rocket' ) . '</p>';
foreach ( $errors['errors'] as $error ) {
$message .= '<p>' . $error . '</p>';
}
}
}
\rocket_notice_html(
[
'status' => $status,
'message' => $message,
'dismissible' => 'notice-preload-running',
'action' => 'stop_preload',
]
);
}
/**
* This notice is displayed after the sitemap preload is complete
*
* @since 3.2
* @author Remy Perona
*/
public function notice_preload_complete() {
if ( ! current_user_can( 'rocket_preload_cache' ) ) {
return;
}
$screen = get_current_screen();
if ( 'settings_page_wprocket' !== $screen->id ) {
return;
}
$result = get_transient( 'rocket_preload_complete' );
if ( false === $result ) {
return;
}
$result_timestamp = get_transient( 'rocket_preload_complete_time' );
if ( false === $result_timestamp ) {
return;
}
delete_transient( 'rocket_preload_complete' );
delete_transient( 'rocket_preload_errors' );
delete_transient( 'rocket_preload_complete_time' );
// translators: %d is the number of pages preloaded.
$notice_message = sprintf( __( 'Preload complete: %d pages have been cached.', 'rocket' ), $result );
$notice_message .= ' <em> (' . $result_timestamp . ') </em>';
\rocket_notice_html(
[
'message' => $notice_message,
]
);
}
/**
* Stops currently running preload from the notice action button
*
* @since 3.2
* @author Remy Perona
*/
public function do_admin_post_stop_preload() {
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'rocket_stop_preload' ) ) {
wp_nonce_ays( '' );
}
if ( ! current_user_can( 'rocket_preload_cache' ) ) {
wp_safe_redirect( wp_get_referer() );
die();
}
$this->homepage_preloader->cancel_preload();
wp_safe_redirect( wp_get_referer() );
die();
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace WP_Rocket\Engine\Preload;
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
/**
* Service provider for the WP Rocket preload.
*
* @since 3.3
* @author Remy Perona
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* The provides array is a way to let the container
* know that a service is provided by this service
* provider. Every service that is registered via
* this service provider must have an alias added
* to this array or it will be ignored.
*
* @var array
*/
protected $provides = [
'full_preload_process',
'partial_preload_process',
'homepage_preload',
'sitemap_preload',
'preload_subscriber',
'sitemap_preload_subscriber',
'partial_preload_subscriber',
'fonts_preload_subscriber',
];
/**
* Registers the subscribers in the container
*
* @since 3.3
* @author Remy Perona
*
* @return void
*/
public function register() {
$this->getContainer()->add( 'full_preload_process', 'WP_Rocket\Engine\Preload\FullProcess' );
$this->getContainer()->add( 'partial_preload_process', 'WP_Rocket\Engine\Preload\PartialProcess' );
$full_preload_process = $this->getContainer()->get( 'full_preload_process' );
$this->getContainer()->add( 'homepage_preload', 'WP_Rocket\Engine\Preload\Homepage' )
->withArgument( $full_preload_process );
$this->getContainer()->add( 'sitemap_preload', 'WP_Rocket\Engine\Preload\Sitemap' )
->withArgument( $full_preload_process );
// Subscribers.
$options = $this->getContainer()->get( 'options' );
$this->getContainer()->share( 'preload_subscriber', 'WP_Rocket\Engine\Preload\PreloadSubscriber' )
->withArgument( $this->getContainer()->get( 'homepage_preload' ) )
->withArgument( $options );
$this->getContainer()->share( 'sitemap_preload_subscriber', 'WP_Rocket\Engine\Preload\SitemapPreloadSubscriber' )
->withArgument( $this->getContainer()->get( 'sitemap_preload' ) )
->withArgument( $options );
$this->getContainer()->share( 'partial_preload_subscriber', 'WP_Rocket\Engine\Preload\PartialPreloadSubscriber' )
->withArgument( $this->getContainer()->get( 'partial_preload_process' ) )
->withArgument( $options );
$this->getContainer()->share( 'fonts_preload_subscriber', 'WP_Rocket\Engine\Preload\Fonts' )
->withArgument( $options )
->withArgument( $this->getContainer()->get( 'cdn' ) );
}
}

View File

@@ -0,0 +1,367 @@
<?php
namespace WP_Rocket\Engine\Preload;
/**
* Sitemap preload.
*
* @since 3.2
* @author Remy Perona
*/
class Sitemap extends AbstractPreload {
/**
* An ID used in the "running" transients name.
*
* @since 3.5
* @author Grégory Viguier
*
* @var string
*/
const PRELOAD_ID = 'sitemap';
/**
* Flag to track sitemap read failures.
*
* @since 3.3
* @author Arun Basil Lal
*
* @var bool
* @access private
*/
private $sitemap_error = false;
/**
* Launches the sitemap preload.
*
* @since 3.2
* @access public
* @author Remy Perona
*
* @param array $sitemaps Sitemaps to use for preloading.
* @return void
*/
public function run_preload( array $sitemaps ) {
if ( ! $sitemaps ) {
return;
}
$urls = [];
foreach ( $sitemaps as $sitemap_type => $sitemap_url ) {
/**
* Fires before WP Rocket sitemap preload is called for a sitemap URL.
*
* @since 2.8
*
* @param string $sitemap_type The sitemap identifier.
* @param string $sitemap_url Sitemap URL to be crawled.
*/
do_action( 'before_run_rocket_sitemap_preload', $sitemap_type, $sitemap_url ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
$urls = $this->process_sitemap( $sitemap_url, $urls );
/**
* Fires after WP Rocket sitemap preload was called for a sitemap URL.
*
* @since 2.8
*
* @param string $sitemap_type The sitemap identifier.
* @param string $sitemap_url Sitemap URL crawled.
*/
do_action( 'after_run_rocket_sitemap_preload', $sitemap_type, $sitemap_url ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
}
if ( true === $this->sitemap_error ) {
// Attempt to use the fallback method.
$urls = $this->get_urls( $urls );
}
if ( ! $urls ) {
return;
}
$preload = 0;
foreach ( $urls as $item ) {
$path = wp_parse_url( $item['url'], PHP_URL_PATH );
if ( isset( $path ) && preg_match( '#^(' . \get_rocket_cache_reject_uri() . ')$#', $path ) ) {
continue;
}
$this->preload_process->push_to_queue( $item );
$preload++;
}
if ( ! $preload ) {
return;
}
set_transient( $this->get_running_transient_name(), 0 );
$this->preload_process->save()->dispatch();
}
/**
* Processes the sitemaps recursively.
*
* @since 3.2
* @since 3.5 Now private.
* @author Remy Perona
*
* @param string $sitemap_url URL of the sitemap.
* @param array $urls An array of arrays.
* @return array {
* Array values are arrays described as follow.
* Array keys are an identifier based on the URL path.
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request. Optional.
* @type string $source An identifier related to the source of the preload (e.g. RELOAD_ID).
* }
*/
private function process_sitemap( $sitemap_url, array $urls = [] ) {
$this->sitemap_error = false;
/**
* Filters the arguments for the sitemap preload request.
*
* @since 2.10.8
* @author Remy Perona
*
* @param array $args Arguments for the request.
*/
$args = apply_filters(
'rocket_preload_sitemap_request_args',
[
'timeout' => 10,
'user-agent' => 'WP Rocket/Sitemaps',
'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
]
);
$sitemap = wp_remote_get( esc_url_raw( $sitemap_url ), $args );
$errors = get_transient( 'rocket_preload_errors' );
$errors = is_array( $errors ) ? $errors : [];
$errors['errors'] = isset( $errors['errors'] ) && is_array( $errors['errors'] ) ? $errors['errors'] : [];
if ( is_wp_error( $sitemap ) ) {
// Translators: %1$s is a XML sitemap URL, %2$s is the error message, %3$s = opening link tag, %4$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Sitemap preload encountered an error. Could not gather links on %1$s because of the following error: %2$s. %3$sLearn more%4$s.', 'rocket' ), $sitemap_url, $sitemap->get_error_message(), '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
$this->sitemap_error = true;
set_transient( 'rocket_preload_errors', $errors );
return $urls;
}
$response_code = wp_remote_retrieve_response_code( $sitemap );
if ( 200 !== $response_code ) {
switch ( $response_code ) {
case 401:
case 403:
// Translators: %1$s is an URL, %2$s is the HTTP response code, %3$s = opening link tag, %4$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Sitemap preload encountered an error. %1$s is not accessible to due to the following response code: %2$s. Security measures could be preventing access. %3$sLearn more%4$s.', 'rocket' ), $sitemap_url, $response_code, '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
break;
case 404:
// Translators: %1$s is an URL, %2$s = opening link tag, %3$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Sitemap preload encountered an error. %1$s is not accessible to due to the following response code: 404. Please make sure you entered the correct sitemap URL and it is accessible in your browser. %2$sLearn more%3$s.', 'rocket' ), $sitemap_url, '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
break;
case 500:
// Translators: %1$s is an URL, %2$s = opening link tag, %3$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Sitemap preload encountered an error. %1$s is not accessible to due to the following response code: 500. Please check with your web host about server access. %2$sLearn more%3$s.', 'rocket' ), $sitemap_url, '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
break;
default:
// Translators: %1$s is an URL, %2$s is the HTTP response code, %3$s = opening link tag, %4$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Sitemap preload encountered an error. Could not gather links on %1$s because it returned the following response code: %2$s. %3$sLearn more%4$s.', 'rocket' ), $sitemap_url, $response_code, '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
break;
}
$this->sitemap_error = true;
set_transient( 'rocket_preload_errors', $errors );
return $urls;
}
$xml_data = wp_remote_retrieve_body( $sitemap );
if ( empty( $xml_data ) ) {
// Translators: %1$s is a XML sitemap URL, %2$s = opening link tag, %3$s = closing link tag.
$errors['errors'][] = sprintf( __( 'Sitemap preload encountered an error. Could not collect links from %1$s because the file is empty. %2$sLearn more%3$s.', 'rocket' ), $sitemap_url, '<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">', '</a>' );
$this->sitemap_error = true;
set_transient( 'rocket_preload_errors', $errors );
return $urls;
}
if ( ! function_exists( 'simplexml_load_string' ) ) {
$this->sitemap_error = true;
return $urls;
}
libxml_use_internal_errors( true );
$xml = simplexml_load_string( $xml_data );
if ( false === $xml ) {
$errors['errors'][] = sprintf(
// Translators: %1$s is a XML sitemap URL, %2$s = opening link tag, %3$s = closing link tag.
__( 'Sitemap preload encountered an error. Could not collect links from %1$s because of an error during the XML sitemap parsing. %2$sLearn more%3$s.', 'rocket' ),
$sitemap_url,
'<a href="https://docs.wp-rocket.me/article/1065-sitemap-preload-is-slow-or-some-pages-are-not-preloaded-at-all#failed-preload" rel="noopener noreferrer" target=_"blank">',
'</a>'
);
$this->sitemap_error = true;
set_transient( 'rocket_preload_errors', $errors );
return $urls;
}
$url_count = count( $xml->url );
$sitemap_children = count( $xml->sitemap );
if ( $url_count > 0 ) {
$mobile_preload = $this->preload_process->is_mobile_preload_enabled();
for ( $i = 0; $i < $url_count; $i++ ) {
$url = (string) $xml->url[ $i ]->loc;
if ( ! $url ) {
continue;
}
$namespaces = $xml->url[ $i ]->getNamespaces( true );
$path = $this->get_url_identifier( $url );
$mobile_key = $path . self::MOBILE_SUFFIX;
if ( ! empty( $namespaces['mobile'] ) ) {
// According to the sitemap, this URL is dedicated to mobile devices.
if ( isset( $urls[ $mobile_key ] ) ) {
continue;
}
$urls[ $mobile_key ] = [
'url' => $url,
'mobile' => true,
'source' => self::PRELOAD_ID,
];
} else {
if ( ! isset( $urls[ $path ] ) ) {
$urls[ $path ] = [
'url' => $url,
'mobile' => false,
'source' => self::PRELOAD_ID,
];
}
if ( $mobile_preload && ! isset( $urls[ $mobile_key ] ) ) {
$urls[ $mobile_key ] = [
'url' => $url,
'mobile' => true,
'source' => self::PRELOAD_ID,
];
}
}
}
return $urls;
}
if ( ! $sitemap_children ) {
return $urls;
}
for ( $i = 0; $i < $sitemap_children; $i++ ) {
$sub_sitemap_url = (string) $xml->sitemap[ $i ]->loc;
$urls = $this->process_sitemap( $sub_sitemap_url, $urls );
}
return $urls;
}
/**
* Get URLs from WordPress.
*
* Used as a fallback when extracting URLs from sitemap fails.
*
* @since 3.3
* @since 3.5 New $urls argument.
* @since 3.5 Now private.
* @author Arun Basil Lal
*
* @link https://github.com/wp-media/wp-rocket/issues/1306
*
* @param array $urls An array of arrays.
* @return array {
* Array values are arrays described as follow.
* Array keys are an identifier based on the URL path.
*
* @type string $url The URL to preload.
* @type bool $mobile True when we want to send a "mobile" user agent with the request. Optional.
* @type string $source An identifier related to the source of the preload (e.g. RELOAD_ID).
* }
*/
private function get_urls( array $urls = [] ) {
// Get public post types.
$post_types = get_post_types( [ 'public' => true ] );
$post_types = array_filter( $post_types, 'is_post_type_viewable' );
/**
* Filters the arguments for get_posts.
*
* @since 3.3
* @author Arun Basil Lal
*
* @param array $args Arguments for get_posts.
*/
$args = apply_filters(
'rocket_preload_sitemap_fallback_request_args',
[
'fields' => 'ids',
'numberposts' => 1000, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_numberposts
'posts_per_page' => -1,
'post_type' => $post_types,
]
);
$all_posts = get_posts( $args );
$mobile_preload = $this->preload_process->is_mobile_preload_enabled();
foreach ( $all_posts as $post ) {
$permalink = get_permalink( $post );
if ( false === $permalink ) {
continue;
}
$path = $this->get_url_identifier( $permalink );
$urls[ $path ] = [
'url' => $permalink,
'mobile' => false,
'source' => self::PRELOAD_ID,
];
if ( ! $mobile_preload ) {
continue;
}
$urls[ $path . self::MOBILE_SUFFIX ] = [
'url' => $permalink,
'mobile' => true,
'source' => self::PRELOAD_ID,
];
}
return $urls;
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace WP_Rocket\Engine\Preload;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Sitemap Preload Subscriber.
*
* @since 3.2
* @author Remy Perona
*/
class SitemapPreloadSubscriber implements Subscriber_Interface {
/**
* Constructor
*
* @since 3.2
* @author Remy Perona
*
* @param Sitemap $sitemap_preload Sitemap Preload instance.
* @param Options_Data $options Options instance.
*/
public function __construct( Sitemap $sitemap_preload, Options_Data $options ) {
$this->options = $options;
$this->sitemap_preload = $sitemap_preload;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.2
* @author Remy Perona
*
* @return array
*/
public static function get_subscribed_events() {
return [
'pagely_cache_purge_after' => [ 'preload', 12 ],
'update_option_' . WP_ROCKET_SLUG => [ 'maybe_cancel_preload', 10, 2 ],
'admin_notices' => [ 'simplexml_notice' ],
];
}
/**
* Launches the sitemap preload
*
* @since 3.2
* @author Remy Perona
*
* @return void
*/
public function preload() {
if ( ! $this->options->get( 'sitemap_preload' ) || ! $this->options->get( 'manual_preload' ) ) {
return;
}
/**
* Filters the sitemaps list to preload
*
* @since 2.8
*
* @param array Array of sitemaps URL
*/
$sitemaps = apply_filters( 'rocket_sitemap_preload_list', $this->options->get( 'sitemaps', false ) );
$sitemaps = array_flip( array_flip( $sitemaps ) );
if ( ! $sitemaps ) {
return;
}
$this->sitemap_preload->run_preload( $sitemaps );
}
/**
* Cancels any running sitemap preload if the option is deactivated
*
* @since 3.2
* @author Remy Perona
*
* @param array $old_value Previous option values.
* @param array $value New option values.
* @return void
*/
public function maybe_cancel_preload( $old_value, $value ) {
if ( isset( $old_value['sitemap_preload'], $value['sitemap_preload'] ) && $old_value['sitemap_preload'] !== $value['sitemap_preload'] && 0 === (int) $value['sitemap_preload'] ) {
$this->sitemap_preload->cancel_preload();
}
}
/**
* Displays a notice if SimpleXML PHP extension is not enabled
*
* @since 3.2.5
* @author Remy Perona
* @return void
*/
public function simplexml_notice() {
if ( ! current_user_can( 'rocket_preload_cache' ) ) {
return;
}
$screen = get_current_screen();
if ( 'settings_page_wprocket' !== $screen->id ) {
return;
}
if ( ! $this->options->get( 'sitemap_preload' ) ) {
return;
}
if ( function_exists( 'simplexml_load_string' ) ) {
return;
}
$message = sprintf(
// Translators: %1$s = opening link tag, %2$s = closing link tag.
__( '%1$sSimpleXML PHP extension%2$s is not enabled on your server. Please contact your host to enable it before running sitemap-based cache preloading.', 'rocket' ),
'<a href="http://php.net/manual/en/book.simplexml.php" target="_blank" rel="noopener noreferrer">',
'</a>'
);
\rocket_notice_html(
[
'status' => 'warning',
'message' => $message,
]
);
}
}