add wp-rocket
This commit is contained in:
@@ -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 );
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
140
wp-content/plugins/wp-rocket/inc/Engine/Preload/Fonts.php
Normal file
140
wp-content/plugins/wp-rocket/inc/Engine/Preload/Fonts.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
315
wp-content/plugins/wp-rocket/inc/Engine/Preload/Homepage.php
Normal file
315
wp-content/plugins/wp-rocket/inc/Engine/Preload/Homepage.php
Normal 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" transient’s 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 & 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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
}
|
||||
}
|
@@ -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 user’s 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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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' ) );
|
||||
}
|
||||
}
|
367
wp-content/plugins/wp-rocket/inc/Engine/Preload/Sitemap.php
Normal file
367
wp-content/plugins/wp-rocket/inc/Engine/Preload/Sitemap.php
Normal 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" transient’s 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;
|
||||
}
|
||||
}
|
@@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user