add wp-rocket
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use stdClass;
|
||||
use WP_Error;
|
||||
|
||||
class APIClient {
|
||||
|
||||
/**
|
||||
* Constant url for Critical Path API job.
|
||||
*/
|
||||
const API_URL = 'https://cpcss.wp-rocket.me/api/job/';
|
||||
|
||||
/**
|
||||
* Sends a generation request to the Critical Path API.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $url The URL to send a CPCSS generation request for.
|
||||
* @param array $params Optional. Parameters needed to be sent in the body. Default: [].
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
* @return array
|
||||
*/
|
||||
public function send_generation_request( $url, $params = [], $item_type = 'custom' ) {
|
||||
$params['url'] = $url;
|
||||
$is_mobile = isset( $params['mobile'] ) && $params['mobile'];
|
||||
$response = wp_remote_post(
|
||||
self::API_URL,
|
||||
[
|
||||
/**
|
||||
* Filters the parameters sent to the Critical CSS generator API.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param array $params An array of parameters to send to the API.
|
||||
*/
|
||||
'body' => apply_filters( 'rocket_cpcss_job_request', $params ),
|
||||
]
|
||||
);
|
||||
|
||||
return $this->prepare_response( $response, $url, $is_mobile, $item_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the response to be returned.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array|WP_Error $response The response or WP_Error on failure.
|
||||
* @param string $url Url to be checked.
|
||||
* @param bool $is_mobile Optional. Flag for if this is cpcss for mobile or not. Default: false.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function prepare_response( $response, $url, $is_mobile = false, $item_type = 'custom' ) {
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error(
|
||||
$this->get_response_code( $response ),
|
||||
sprintf(
|
||||
// translators: %1$s = type of content, %2$s = error message.
|
||||
__( 'Critical CSS for %1$s not generated. Error: %2$s', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $url : $item_type,
|
||||
$response->get_error_message()
|
||||
),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$response_data = $this->get_response_data( $response );
|
||||
$response_status_code = $this->get_response_status( $response, ( isset( $response_data->status ) ) ? $response_data->status : null );
|
||||
$succeeded = $this->get_response_success( $response_status_code, $response_data );
|
||||
|
||||
if ( $succeeded ) {
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
$response_message = $this->get_response_message( $response_status_code, $response_data, $url, $is_mobile, $item_type );
|
||||
|
||||
if ( 200 === $response_status_code ) {
|
||||
$response_status_code = 400;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
$this->get_response_code( $response ),
|
||||
$response_message,
|
||||
[
|
||||
'status' => $response_status_code,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of response.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $response_code Response code to check success or failure.
|
||||
* @param stdClass $response_data Object of data returned from request.
|
||||
*
|
||||
* @return bool success or failed.
|
||||
*/
|
||||
private function get_response_success( $response_code, $response_data ) {
|
||||
return (
|
||||
200 === $response_code
|
||||
&&
|
||||
! empty( $response_data )
|
||||
&&
|
||||
(
|
||||
(
|
||||
isset( $response_data->status )
|
||||
&&
|
||||
200 === $response_data->status
|
||||
)
|
||||
||
|
||||
(
|
||||
isset( $response_data->data )
|
||||
&&
|
||||
isset( $response_data->data->id )
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response status code/number.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array|WP_Error $response The response or WP_Error on failure.
|
||||
* @param null|int $status Optional. Status code to overwrite the response status. Default: null.
|
||||
*
|
||||
* @return int status code|number of response.
|
||||
*/
|
||||
private function get_response_status( $response, $status = null ) {
|
||||
if ( ! is_null( $status ) ) {
|
||||
return (int) $status;
|
||||
}
|
||||
|
||||
return (int) wp_remote_retrieve_response_code( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response message.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $response_status_code Response status code.
|
||||
* @param stdClass $response_data Object of data returned from request.
|
||||
* @param string $url Url for the web page to be checked.
|
||||
* @param bool $is_mobile Optional. Flag for if this is cpcss for mobile or not. Default: false.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_response_message( $response_status_code, $response_data, $url, $is_mobile = false, $item_type = 'custom' ) {
|
||||
$message = '';
|
||||
|
||||
switch ( $response_status_code ) {
|
||||
case 200:
|
||||
if ( ! isset( $response_data->data->id ) ) {
|
||||
$message .= sprintf(
|
||||
$is_mobile
|
||||
?
|
||||
// translators: %s = item URL.
|
||||
__( 'Critical CSS for %1$s on mobile not generated. Error: The API returned an empty response.', 'rocket' )
|
||||
:
|
||||
// translators: %s = item URL.
|
||||
__( 'Critical CSS for %1$s not generated. Error: The API returned an empty response.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $url : $item_type
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 400:
|
||||
case 440:
|
||||
case 404:
|
||||
// translators: %s = item URL.
|
||||
$message .= sprintf(
|
||||
$is_mobile
|
||||
// translators: %s = item URL.
|
||||
? __( 'Critical CSS for %1$s on mobile not generated.', 'rocket' )
|
||||
// translators: %s = item URL.
|
||||
: __( 'Critical CSS for %1$s not generated.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $url : $item_type
|
||||
);
|
||||
break;
|
||||
default:
|
||||
$message .= sprintf(
|
||||
$is_mobile
|
||||
// translators: %s = URL.
|
||||
? __( 'Critical CSS for %1$s on mobile not generated. Error: The API returned an invalid response code.', 'rocket' )
|
||||
// translators: %s = URL.
|
||||
: __( 'Critical CSS for %1$s not generated. Error: The API returned an invalid response code.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $url : $item_type
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if ( isset( $response_data->message ) ) {
|
||||
// translators: %1$s = error message.
|
||||
$message .= ' ' . sprintf( __( 'Error: %1$s', 'rocket' ), $response_data->message );
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response data from the API.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array|WP_Error $response The response or WP_Error on failure.
|
||||
*
|
||||
* @return mixed response of API.
|
||||
*/
|
||||
private function get_response_data( $response ) {
|
||||
return json_decode( wp_remote_retrieve_body( $response ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get our internal response code [Not the standard HTTP codes].
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array|WP_Error $response The response or WP_Error on failure.
|
||||
*
|
||||
* @return string response code.
|
||||
*/
|
||||
private function get_response_code( $response ) {
|
||||
// Todo: we can return code based on the response status number, for example 404 not_found.
|
||||
return 'cpcss_generation_failed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get job details by calling API with job ID.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $job_id ID for the job to get details.
|
||||
* @param string $url URL to be used in error messages.
|
||||
* @param bool $is_mobile Optional. Flag for if this is cpcss for mobile or not. Default: false.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return mixed|WP_Error Details for job.
|
||||
*/
|
||||
public function get_job_details( $job_id, $url, $is_mobile = false, $item_type = 'custom' ) {
|
||||
$response = wp_remote_get(
|
||||
self::API_URL . "{$job_id}/"
|
||||
);
|
||||
|
||||
return $this->prepare_response( $response, $url, $is_mobile, $item_type );
|
||||
}
|
||||
}
|
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath\Admin;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Engine\CriticalPath\ProcessorService;
|
||||
use WP_Rocket\Engine\CriticalPath\TransientTrait;
|
||||
|
||||
class Admin {
|
||||
use TransientTrait;
|
||||
|
||||
/**
|
||||
* Instance of options handler.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Instance of ProcessorService.
|
||||
*
|
||||
* @var ProcessorService
|
||||
*/
|
||||
private $processor;
|
||||
|
||||
/**
|
||||
* Creates an instance of the class.
|
||||
*
|
||||
* @param Options_Data $options Options instance.
|
||||
* @param ProcessorService $processor ProcessorService instance.
|
||||
*/
|
||||
public function __construct( Options_Data $options, ProcessorService $processor ) {
|
||||
$this->options = $options;
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the CPCSS heartbeat.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public function cpcss_heartbeat() {
|
||||
check_ajax_referer( 'cpcss_heartbeat_nonce', '_nonce', true );
|
||||
|
||||
if (
|
||||
! $this->is_async_css_enabled()
|
||||
||
|
||||
! current_user_can( 'rocket_manage_options' )
|
||||
||
|
||||
! current_user_can( 'rocket_regenerate_critical_css' )
|
||||
) {
|
||||
wp_send_json_error();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$cpcss_pending = get_transient( 'rocket_cpcss_generation_pending' );
|
||||
|
||||
if ( ! empty( $cpcss_pending ) ) {
|
||||
$cpcss_pending = $this->process_cpcss_pending_queue( (array) $cpcss_pending );
|
||||
}
|
||||
|
||||
if ( false !== $cpcss_pending && empty( $cpcss_pending ) ) {
|
||||
delete_transient( 'rocket_cpcss_generation_pending' );
|
||||
}
|
||||
|
||||
if ( empty( $cpcss_pending ) ) {
|
||||
$this->generation_complete();
|
||||
wp_send_json_success( [ 'status' => 'cpcss_complete' ] );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
set_transient( 'rocket_cpcss_generation_pending', $cpcss_pending, HOUR_IN_SECONDS );
|
||||
wp_send_json_success( [ 'status' => 'cpcss_running' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull one item off of the CPCSS Pending Queue and process it.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $cpcss_pending CPCSS Pending Queue.
|
||||
*
|
||||
* @return array remaining queue.
|
||||
*/
|
||||
private function process_cpcss_pending_queue( array $cpcss_pending ) {
|
||||
$cpcss_item = reset( $cpcss_pending );
|
||||
if ( empty( $cpcss_item ) ) {
|
||||
return $cpcss_pending;
|
||||
}
|
||||
|
||||
// Threshold 'check' > 10 = timed out.
|
||||
$timeout = ( $cpcss_item['check'] > 10 );
|
||||
$additional_params = [
|
||||
'timeout' => $timeout,
|
||||
'is_mobile' => ! empty( $cpcss_item['mobile'] ) ? (bool) $cpcss_item['mobile'] : false,
|
||||
'item_type' => $cpcss_item['type'],
|
||||
];
|
||||
$cpcss_generation = $this->processor->process_generate(
|
||||
$cpcss_item['url'],
|
||||
$cpcss_item['path'],
|
||||
$additional_params
|
||||
);
|
||||
|
||||
// Increment this item's threshold count.
|
||||
$cpcss_pending[ $cpcss_item['path'] ]['check']++;
|
||||
|
||||
$this->cpcss_heartbeat_notices( $cpcss_generation, $cpcss_item );
|
||||
|
||||
// Remove the item from the queue when (a) the CPCSS API returns success or error or (b) timeouts.
|
||||
if (
|
||||
is_wp_error( $cpcss_generation )
|
||||
||
|
||||
'cpcss_generation_successful' === $cpcss_generation['code']
|
||||
||
|
||||
'cpcss_generation_failed' === $cpcss_generation['code']
|
||||
||
|
||||
$timeout
|
||||
) {
|
||||
unset( $cpcss_pending[ $cpcss_item['path'] ] );
|
||||
}
|
||||
|
||||
return $cpcss_pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* CPCSS heartbeat update notices transients.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array|WP_Error $cpcss_generation CPCSS regeneration reply.
|
||||
* @param array $cpcss_item Item processed.
|
||||
*/
|
||||
private function cpcss_heartbeat_notices( $cpcss_generation, $cpcss_item ) {
|
||||
$mobile = isset( $cpcss_item['mobile'] ) ? $cpcss_item['mobile'] : 0;
|
||||
$transient = (array) get_transient( 'rocket_critical_css_generation_process_running' );
|
||||
|
||||
// Initializes the transient.
|
||||
if ( ! isset( $transient['items'] ) ) {
|
||||
$transient['items'] = [];
|
||||
}
|
||||
|
||||
if ( is_wp_error( $cpcss_generation ) ) {
|
||||
$this->update_running_transient( $transient, $cpcss_item['path'], $mobile, $cpcss_generation->get_error_message(), false );
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isset( $cpcss_generation['code'] )
|
||||
&&
|
||||
(
|
||||
'cpcss_generation_successful' === $cpcss_generation['code']
|
||||
||
|
||||
'cpcss_generation_failed' === $cpcss_generation['code']
|
||||
)
|
||||
) {
|
||||
$this->update_running_transient( $transient, $cpcss_item['path'], $mobile, $cpcss_generation['message'], ( 'cpcss_generation_successful' === $cpcss_generation['code'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches when the CPCSS generation is complete.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
private function generation_complete() {
|
||||
$running = get_transient( 'rocket_critical_css_generation_process_running' );
|
||||
|
||||
if ( false === $running ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $running['total'], $running['items'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $running['total'] > count( $running['items'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the critical CSS generation process is complete.
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
do_action( 'rocket_critical_css_generation_process_complete' );
|
||||
|
||||
rocket_clean_domain();
|
||||
set_transient( 'rocket_critical_css_generation_process_complete', get_transient( 'rocket_critical_css_generation_process_running' ), HOUR_IN_SECONDS );
|
||||
delete_transient( 'rocket_critical_css_generation_process_running' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue CPCSS heartbeat script on all admin pages.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public function enqueue_admin_cpcss_heartbeat_script() {
|
||||
if ( ! $this->is_async_css_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpr-heartbeat-cpcss-script',
|
||||
rocket_get_constant( 'WP_ROCKET_ASSETS_JS_URL' ) . 'wpr-cpcss-heartbeat.js',
|
||||
[],
|
||||
rocket_get_constant( 'WP_ROCKET_VERSION' ),
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wpr-heartbeat-cpcss-script',
|
||||
'rocket_cpcss_heartbeat',
|
||||
[
|
||||
'nonce' => wp_create_nonce( 'cpcss_heartbeat_nonce' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Regenerate Critical CSS link to WP Rocket admin bar item
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance, passed by reference.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_regenerate_menu_item( $wp_admin_bar ) {
|
||||
if ( ! current_user_can( 'rocket_regenerate_critical_css' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->is_async_css_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This filter is documented in inc/Engine/CriticalPath/CriticalCSS.php.
|
||||
if ( ! apply_filters( 'do_rocket_critical_css_generation', true ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
|
||||
return;
|
||||
}
|
||||
|
||||
$referer = '';
|
||||
$action = 'rocket_generate_critical_css';
|
||||
|
||||
if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
|
||||
$referer_url = filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL );
|
||||
$referer = '&_wp_http_referer=' . rawurlencode( remove_query_arg( 'fl_builder', $referer_url ) );
|
||||
}
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
[
|
||||
'parent' => 'wp-rocket',
|
||||
'id' => 'regenerate-critical-path',
|
||||
'title' => __( 'Regenerate Critical Path CSS', 'rocket' ),
|
||||
'href' => wp_nonce_url( admin_url( "admin-post.php?action={$action}{$referer}" ), $action ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the "async_css" option is enabled.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return bool true when "async_css" option is enabled.
|
||||
*/
|
||||
private function is_async_css_enabled() {
|
||||
return (bool) $this->options->get( 'async_css', 0 );
|
||||
}
|
||||
}
|
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath\Admin;
|
||||
|
||||
use WP_Rocket\Abstract_Render;
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Engine\Admin\Beacon\Beacon;
|
||||
|
||||
class Post extends Abstract_Render {
|
||||
/**
|
||||
* Instance of the Beacon handler.
|
||||
*
|
||||
* @var Beacon
|
||||
*/
|
||||
private $beacon;
|
||||
|
||||
/**
|
||||
* Instance of options handler.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Path to the critical-css directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $critical_css_path;
|
||||
|
||||
/**
|
||||
* Array of reasons to disable actions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $disabled_data;
|
||||
|
||||
/**
|
||||
* Creates an instance of the subscriber.
|
||||
*
|
||||
* @param Options_Data $options WP Rocket Options instance.
|
||||
* @param Beacon $beacon Beacon instance.
|
||||
* @param string $critical_path Path to the critical CSS base folder.
|
||||
* @param string $template_path Path to the templates folder.
|
||||
*/
|
||||
public function __construct( Options_Data $options, Beacon $beacon, $critical_path, $template_path ) {
|
||||
parent::__construct( $template_path );
|
||||
|
||||
$this->beacon = $beacon;
|
||||
$this->options = $options;
|
||||
$this->critical_css_path = $critical_path . get_current_blog_id() . '/posts/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the critical CSS block in WP Rocket options metabox.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cpcss_section() {
|
||||
$data = [
|
||||
'disabled_description' => $this->get_disabled_description(),
|
||||
];
|
||||
|
||||
echo $this->generate( 'metabox/container', $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the content inside the critical CSS block.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cpcss_actions() {
|
||||
$data = [
|
||||
'disabled' => $this->is_enabled(),
|
||||
'beacon' => $this->beacon->get_suggest( 'async' ),
|
||||
'cpcss_exists' => $this->cpcss_exists(),
|
||||
];
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $this->generate(
|
||||
'metabox/generate',
|
||||
$data // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue CPCSS generation / deletion script on edit.php page.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $page The current admin page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_admin_edit_script( $page ) {
|
||||
global $post, $pagenow;
|
||||
|
||||
// Bailout if the page is not Post / Page.
|
||||
if ( ! in_array( $page, [ 'edit.php', 'post.php' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! in_array( $pagenow, [ 'post-new.php', 'post.php' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bailout if the CPCSS is not enabled for this Post / Page.
|
||||
if ( $this->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = ( 'post-new.php' === $pagenow ) ? '' : $post->ID;
|
||||
|
||||
wp_enqueue_script(
|
||||
'wpr-edit-cpcss-script',
|
||||
rocket_get_constant( 'WP_ROCKET_ASSETS_JS_URL' ) . 'wpr-cpcss.js',
|
||||
[],
|
||||
rocket_get_constant( 'WP_ROCKET_VERSION' ),
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wpr-edit-cpcss-script',
|
||||
'rocket_cpcss',
|
||||
[
|
||||
'rest_url' => rest_url( "wp-rocket/v1/cpcss/post/{$post_id}" ),
|
||||
'rest_nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'generate_btn' => __( 'Generate Specific CPCSS', 'rocket' ),
|
||||
'regenerate_btn' => __( 'Regenerate specific CPCSS', 'rocket' ),
|
||||
'wprMobileCpcssEnabled' => $this->options->get( 'async_css_mobile', 0 ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data for the disabled checks.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_disabled_data() {
|
||||
global $post;
|
||||
|
||||
if ( rocket_get_constant( 'WP_ROCKET_IS_TESTING', false ) ) {
|
||||
$this->disabled_data = null;
|
||||
}
|
||||
|
||||
if ( isset( $this->disabled_data ) ) {
|
||||
return $this->disabled_data;
|
||||
}
|
||||
|
||||
if ( 'publish' !== $post->post_status ) {
|
||||
$this->disabled_data['not_published'] = 1;
|
||||
}
|
||||
|
||||
if ( ! $this->options->get( 'async_css', 0 ) ) {
|
||||
$this->disabled_data['option_disabled'] = 1;
|
||||
}
|
||||
|
||||
if ( get_post_meta( $post->ID, '_rocket_exclude_async_css', true ) ) {
|
||||
$this->disabled_data['option_excluded'] = 1;
|
||||
}
|
||||
|
||||
return $this->disabled_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if critical CSS generation is enabled for the current post.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_enabled() {
|
||||
return ! empty( $this->get_disabled_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reason why actions are disabled.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_disabled_description() {
|
||||
global $post;
|
||||
|
||||
$disabled_data = $this->get_disabled_data();
|
||||
|
||||
if ( empty( $disabled_data ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$notice = __( '%l to use this feature.', 'rocket' );
|
||||
$list = [
|
||||
// translators: %s = post type.
|
||||
'not_published' => sprintf( __( 'Publish the %s', 'rocket' ), $post->post_type ),
|
||||
'option_disabled' => __( 'Enable Optimize CSS delivery in WP Rocket settings', 'rocket' ),
|
||||
'option_excluded' => __( 'Enable Optimize CSS delivery in the options above', 'rocket' ),
|
||||
];
|
||||
|
||||
return wp_sprintf_l( $notice, array_intersect_key( $list, $disabled_data ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific critical css file exists for the current post.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function cpcss_exists() {
|
||||
global $post;
|
||||
|
||||
$post_cpcss = "{$this->critical_css_path}{$post->post_type}-{$post->ID}.css";
|
||||
|
||||
return rocket_direct_filesystem()->exists( $post_cpcss );
|
||||
}
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath\Admin;
|
||||
|
||||
use WP_Rocket\Abstract_Render;
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Engine\Admin\Beacon\Beacon;
|
||||
use WP_Rocket\Engine\CriticalPath\CriticalCSS;
|
||||
|
||||
class Settings extends Abstract_Render {
|
||||
/**
|
||||
* Instance of the Beacon handler.
|
||||
*
|
||||
* @var Beacon
|
||||
*/
|
||||
private $beacon;
|
||||
|
||||
/**
|
||||
* Instance of options handler.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Instance of CriticalCSS.
|
||||
*
|
||||
* @var CriticalCSS
|
||||
*/
|
||||
private $critical_css;
|
||||
|
||||
/**
|
||||
* Creates an instance of the subscriber.
|
||||
*
|
||||
* @param Options_Data $options WP Rocket Options instance.
|
||||
* @param Beacon $beacon Beacon instance.
|
||||
* @param CriticalCSS $critical_css CriticalCSS instance.
|
||||
* @param string $template_path Path to the templates folder.
|
||||
*/
|
||||
public function __construct( Options_Data $options, Beacon $beacon, CriticalCSS $critical_css, $template_path ) {
|
||||
parent::__construct( $template_path );
|
||||
|
||||
$this->beacon = $beacon;
|
||||
$this->options = $options;
|
||||
$this->critical_css = $critical_css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display CPCSS mobile section tool admin view.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_cpcss_mobile_section() {
|
||||
if ( ! current_user_can( 'rocket_manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bailout if CPCSS is not enabled & separate cache for mobiles is not enabled.
|
||||
// Or bailout if CPCSS mobile option is false.
|
||||
if (
|
||||
! (
|
||||
$this->options->get( 'async_css', 0 )
|
||||
&&
|
||||
$this->options->get( 'cache_mobile', 0 )
|
||||
&&
|
||||
$this->options->get( 'do_caching_mobile_files', 0 )
|
||||
)
|
||||
||
|
||||
$this->options->get( 'async_css_mobile', 0 )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'beacon' => $this->beacon->get_suggest( 'async' ),
|
||||
];
|
||||
|
||||
echo $this->generate( 'activate-cpcss-mobile', $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable CPCSS mobile.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enable_mobile_cpcss() {
|
||||
check_ajax_referer( 'rocket-ajax', 'nonce', true );
|
||||
|
||||
if ( ! current_user_can( 'rocket_manage_options' ) || ! current_user_can( 'rocket_regenerate_critical_css' ) ) {
|
||||
wp_send_json_error();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->options->set( 'async_css_mobile', 1 );
|
||||
update_option( rocket_get_constant( 'WP_ROCKET_SLUG', 'wp_rocket_settings' ), $this->options->get_options() );
|
||||
|
||||
// Start Mobile CPCSS process.
|
||||
$this->critical_css->process_handler( 'mobile' );
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds async_css_mobile option to WP Rocket options.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $options WP Rocket options array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_async_css_mobile_option( $options ) {
|
||||
$options = (array) $options;
|
||||
|
||||
$options['async_css_mobile'] = 1;
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value of async_css_mobile to 0 when upgrading from < 3.6.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $new_version New WP Rocket version.
|
||||
* @param string $old_version Previous WP Rocket version.
|
||||
*/
|
||||
public function set_async_css_mobile_default_value( $new_version, $old_version ) {
|
||||
if ( version_compare( $old_version, '3.6', '>' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$options = get_option( 'wp_rocket_settings', [] );
|
||||
|
||||
$options['async_css_mobile'] = 0;
|
||||
|
||||
update_option( 'wp_rocket_settings', $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds async_css_mobile to the hidden settings fields.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $hidden_settings_fields An array of hidden settings fields ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_hidden_async_css_mobile( $hidden_settings_fields ) {
|
||||
$hidden_settings_fields = (array) $hidden_settings_fields;
|
||||
|
||||
$hidden_settings_fields[] = 'async_css_mobile';
|
||||
|
||||
return $hidden_settings_fields;
|
||||
}
|
||||
}
|
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath\Admin;
|
||||
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
class Subscriber implements Subscriber_Interface {
|
||||
/**
|
||||
* Instance of the CPCSS Settings handler.
|
||||
*
|
||||
* @var Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* Instance of the Post handler
|
||||
*
|
||||
* @var Post
|
||||
*/
|
||||
private $post;
|
||||
|
||||
/**
|
||||
* Instance of the Admin handler
|
||||
*
|
||||
* @var Admin
|
||||
*/
|
||||
private $admin;
|
||||
|
||||
/**
|
||||
* Creates an instance of the subscriber.
|
||||
*
|
||||
* @param Post $post Post instance.
|
||||
* @param Settings $settings CPCSS Settings instance.
|
||||
* @param Admin $admin Admin instance.
|
||||
*/
|
||||
public function __construct( Post $post, Settings $settings, Admin $admin ) {
|
||||
$this->post = $post;
|
||||
$this->settings = $settings;
|
||||
$this->admin = $admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Events this subscriber wants to listen to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
return [
|
||||
'rocket_after_options_metabox' => 'cpcss_section',
|
||||
'rocket_metabox_cpcss_content' => 'cpcss_actions',
|
||||
'rocket_first_install_options' => 'add_async_css_mobile_option',
|
||||
'wp_rocket_upgrade' => [ 'set_async_css_mobile_default_value', 12, 2 ],
|
||||
'rocket_hidden_settings_fields' => 'add_hidden_async_css_mobile',
|
||||
'rocket_settings_tools_content' => 'display_cpcss_mobile_section',
|
||||
'wp_ajax_rocket_enable_mobile_cpcss' => 'enable_mobile_cpcss',
|
||||
'wp_ajax_rocket_cpcss_heartbeat' => 'cpcss_heartbeat',
|
||||
'admin_enqueue_scripts' => [
|
||||
[ 'enqueue_admin_edit_script' ],
|
||||
[ 'enqueue_admin_cpcss_heartbeat_script' ],
|
||||
],
|
||||
'rocket_admin_bar_items' => 'add_regenerate_menu_item',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable CPCSS mobile.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enable_mobile_cpcss() {
|
||||
$this->settings->enable_mobile_cpcss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display CPCSS mobile section tool admin view.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_cpcss_mobile_section() {
|
||||
$this->settings->display_cpcss_mobile_section();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue CPCSS generation / deletion script on edit.php page.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $page The current admin page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_admin_edit_script( $page ) {
|
||||
$this->post->enqueue_admin_edit_script( $page );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the critical CSS block in WP Rocket options metabox.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cpcss_section() {
|
||||
$this->post->cpcss_section();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the content inside the critical CSS block.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cpcss_actions() {
|
||||
$this->post->cpcss_actions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds async_css_mobile option to WP Rocket options.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $options WP Rocket options array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_async_css_mobile_option( $options ) {
|
||||
return $this->settings->add_async_css_mobile_option( $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value of async_css_mobile to 0 when upgrading from < 3.6.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $new_version New WP Rocket version.
|
||||
* @param string $old_version Previous WP Rocket version.
|
||||
*/
|
||||
public function set_async_css_mobile_default_value( $new_version, $old_version ) {
|
||||
$this->settings->set_async_css_mobile_default_value( $new_version, $old_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds async_css_mobile to the hidden settings fields.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $hidden_settings_fields An array of hidden settings fields ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_hidden_async_css_mobile( $hidden_settings_fields ) {
|
||||
return $this->settings->add_hidden_async_css_mobile( $hidden_settings_fields );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the CPCSS heartbeat.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public function cpcss_heartbeat() {
|
||||
$this->admin->cpcss_heartbeat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue CPCSS heartbeat script on all admin pages.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public function enqueue_admin_cpcss_heartbeat_script() {
|
||||
$this->admin->enqueue_admin_cpcss_heartbeat_script();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Regenerate Critical CSS link to WP Rocket admin bar item
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance, passed by reference.
|
||||
* @return void
|
||||
*/
|
||||
public function add_regenerate_menu_item( $wp_admin_bar ) {
|
||||
$this->admin->add_regenerate_menu_item( $wp_admin_bar );
|
||||
}
|
||||
}
|
@@ -0,0 +1,578 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use FilesystemIterator;
|
||||
use UnexpectedValueException;
|
||||
use WP_Filesystem_Direct;
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
|
||||
/**
|
||||
* Handles the critical CSS generation process.
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
class CriticalCSS {
|
||||
/**
|
||||
* Background Process instance.
|
||||
*
|
||||
* @var CriticalCSSGeneration
|
||||
*/
|
||||
public $process;
|
||||
|
||||
/**
|
||||
* WP Rocket options instance.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Items for which we generate a critical CSS.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $items = [];
|
||||
|
||||
/**
|
||||
* Path to the critical CSS directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $critical_css_path;
|
||||
|
||||
/**
|
||||
* Instance of the filesystem handler.
|
||||
*
|
||||
* @var WP_Filesystem_Direct
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* Creates an instance of CriticalCSS.
|
||||
*
|
||||
* @param CriticalCSSGeneration $process Background process instance.
|
||||
* @param Options_Data $options Instance of options data handler.
|
||||
* @param WP_Filesystem_Direct $filesystem Instance of the filesystem handler.
|
||||
*/
|
||||
public function __construct( CriticalCSSGeneration $process, Options_Data $options, $filesystem ) {
|
||||
$this->process = $process;
|
||||
$this->options = $options;
|
||||
$this->critical_css_path = rocket_get_constant( 'WP_ROCKET_CRITICAL_CSS_PATH' ) . get_current_blog_id() . '/';
|
||||
$this->filesystem = $filesystem;
|
||||
$this->items['front_page'] = [
|
||||
'type' => 'front_page',
|
||||
'url' => home_url( '/' ),
|
||||
'path' => 'front_page.css',
|
||||
'check' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current site critical CSS path.
|
||||
*
|
||||
* @since 3.3.5
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_critical_css_path() {
|
||||
return $this->critical_css_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the critical CSS generation.
|
||||
*
|
||||
* @since 3.6 Added the $version parameter.
|
||||
* @since 2.11
|
||||
*
|
||||
* @param string $version Optional. Version of the CPCSS files to generate. Possible values: default, mobile, all.
|
||||
*/
|
||||
public function process_handler( $version = 'default' ) {
|
||||
/**
|
||||
* Filters the critical CSS generation process.
|
||||
*
|
||||
* Use this filter to prevent the automatic critical CSS generation.
|
||||
*
|
||||
* @since 2.11.5
|
||||
*
|
||||
* @param bool $do_rocket_critical_css_generation True to activate the automatic generation, false to prevent it.
|
||||
*/
|
||||
if ( ! apply_filters( 'do_rocket_critical_css_generation', true ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
|
||||
return;
|
||||
}
|
||||
|
||||
if ( get_transient( 'rocket_critical_css_generation_process_running' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clean_critical_css( $version );
|
||||
|
||||
$this->stop_generation();
|
||||
|
||||
$this->set_items( $version );
|
||||
|
||||
array_map( [ $this->process, 'push_to_queue' ], $this->items );
|
||||
|
||||
$this->update_process_running_transient();
|
||||
|
||||
$this->process->save()->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the critical CSS generation process.
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public function stop_generation() {
|
||||
if ( method_exists( $this->process, 'cancel_process' ) ) {
|
||||
$this->process->cancel_process();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches when the CPCSS generation is complete.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
protected function generation_complete() {
|
||||
/**
|
||||
* Fires when the critical CSS generation process is complete.
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
do_action( 'rocket_critical_css_generation_process_complete' );
|
||||
|
||||
set_transient( 'rocket_critical_css_generation_process_complete', get_transient( 'rocket_critical_css_generation_process_running' ), HOUR_IN_SECONDS );
|
||||
delete_transient( 'rocket_critical_css_generation_process_running' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes critical CSS files.
|
||||
*
|
||||
* @since 3.6 Replaced glob().
|
||||
* @since 3.6 Added $version parameter.
|
||||
* @since 2.11
|
||||
*
|
||||
* @param string $version Optional. Version of the CPCSS files to delete. Possible values: default, mobile, all.
|
||||
*/
|
||||
public function clean_critical_css( $version = 'default' ) {
|
||||
foreach ( $this->get_critical_css_iterator() as $file ) {
|
||||
if ( ! $this->filesystem->is_file( $file ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
'mobile' === $version
|
||||
&&
|
||||
false === strpos( $file, '-mobile' )
|
||||
) {
|
||||
continue;
|
||||
} elseif (
|
||||
'default' === $version
|
||||
&&
|
||||
false !== strpos( $file, '-mobile' )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->filesystem->delete( $file );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Critical CSS Filesystem Iterator.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return FilesystemIterator|array Returns iterator on success; else an empty array.
|
||||
*/
|
||||
private function get_critical_css_iterator() {
|
||||
try {
|
||||
return new FilesystemIterator( $this->critical_css_path );
|
||||
} catch ( UnexpectedValueException $e ) {
|
||||
// No logging yet.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all public post types.
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
private function get_public_post_types() {
|
||||
global $wpdb;
|
||||
|
||||
$post_types = get_post_types(
|
||||
[
|
||||
'public' => true,
|
||||
'publicly_queryable' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$post_types[] = 'page';
|
||||
|
||||
/**
|
||||
* Filters the post types excluded from critical CSS generation.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param array $excluded_post_types An array of post types names.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
$excluded_post_types = (array) apply_filters(
|
||||
'rocket_cpcss_excluded_post_types',
|
||||
[
|
||||
'elementor_library',
|
||||
'oceanwp_library',
|
||||
'tbuilder_layout',
|
||||
'tbuilder_layout_part',
|
||||
'slider',
|
||||
'karma-slider',
|
||||
'tt-gallery',
|
||||
'xlwcty_thankyou',
|
||||
'fusion_template',
|
||||
'blocks',
|
||||
'jet-woo-builder',
|
||||
'fl-builder-template',
|
||||
]
|
||||
);
|
||||
|
||||
$post_types = array_diff( $post_types, $excluded_post_types );
|
||||
$post_types = esc_sql( $post_types );
|
||||
$post_types = "'" . implode( "','", $post_types ) . "'";
|
||||
|
||||
return $wpdb->get_results(
|
||||
"SELECT MAX(ID) as ID, post_type
|
||||
FROM (
|
||||
SELECT ID, post_type
|
||||
FROM $wpdb->posts
|
||||
WHERE post_type IN ( $post_types )
|
||||
AND post_status = 'publish'
|
||||
ORDER BY post_date DESC
|
||||
) AS posts
|
||||
GROUP BY post_type"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all public taxonomies.
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
private function get_public_taxonomies() {
|
||||
global $wpdb;
|
||||
|
||||
$taxonomies = get_taxonomies(
|
||||
[
|
||||
'public' => true,
|
||||
'publicly_queryable' => true,
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the taxonomies excluded from critical CSS generation.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param array $excluded_taxonomies An array of taxonomies names.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
$excluded_taxonomies = (array) apply_filters(
|
||||
'rocket_cpcss_excluded_taxonomies',
|
||||
[
|
||||
'post_format',
|
||||
'product_shipping_class',
|
||||
'karma-slider-category',
|
||||
'truethemes-gallery-category',
|
||||
'coupon_campaign',
|
||||
'element_category',
|
||||
'mediamatic_wpfolder',
|
||||
'attachment_category',
|
||||
]
|
||||
);
|
||||
|
||||
$taxonomies = array_diff( $taxonomies, $excluded_taxonomies );
|
||||
$taxonomies = esc_sql( $taxonomies );
|
||||
$taxonomies = "'" . implode( "','", $taxonomies ) . "'";
|
||||
|
||||
return $wpdb->get_results(
|
||||
"SELECT MAX( term_id ) AS ID, taxonomy
|
||||
FROM (
|
||||
SELECT term_id, taxonomy
|
||||
FROM $wpdb->term_taxonomy
|
||||
WHERE taxonomy IN ( $taxonomies )
|
||||
AND count > 0
|
||||
) AS taxonomies
|
||||
GROUP BY taxonomy"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the items for which we generate critical CSS.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param string $version Optional. Version of the CPCSS files to generate. Possible values: default, mobile, all.
|
||||
*/
|
||||
private function set_items( $version = 'default' ) {
|
||||
$page_for_posts = get_option( 'page_for_posts' );
|
||||
|
||||
if ( 'page' === get_option( 'show_on_front' ) && ! empty( $page_for_posts ) ) {
|
||||
$this->items['home'] = [
|
||||
'type' => 'home',
|
||||
'url' => get_permalink( get_option( 'page_for_posts' ) ),
|
||||
'path' => 'home.css',
|
||||
'check' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$post_types = $this->get_public_post_types();
|
||||
|
||||
foreach ( $post_types as $post_type ) {
|
||||
$this->items[ $post_type->post_type ] = [
|
||||
'type' => $post_type->post_type,
|
||||
'url' => get_permalink( $post_type->ID ),
|
||||
'path' => "{$post_type->post_type}.css",
|
||||
'check' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$taxonomies = $this->get_public_taxonomies();
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
|
||||
$this->items[ $taxonomy->taxonomy ] = [
|
||||
'type' => $taxonomy->taxonomy,
|
||||
'url' => get_term_link( (int) $taxonomy->ID, $taxonomy->taxonomy ),
|
||||
'path' => "{$taxonomy->taxonomy}.css",
|
||||
'check' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
if ( in_array( $version, [ 'all', 'mobile' ], true ) ) {
|
||||
$mobile_items = [];
|
||||
|
||||
foreach ( $this->items as $key => $value ) {
|
||||
$value['mobile'] = 1;
|
||||
$value['path'] = str_replace( '.css', '-mobile.css', $value['path'] );
|
||||
$mobile_items[ "{$key}-mobile" ] = $value;
|
||||
}
|
||||
|
||||
if ( 'mobile' === $version ) {
|
||||
$this->items = $mobile_items;
|
||||
} elseif ( 'all' === $version ) {
|
||||
$this->items = array_merge( $this->items, $mobile_items );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the array containing the items to send to the critical CSS generator.
|
||||
*
|
||||
* @since 2.11.4
|
||||
*
|
||||
* @param array $items Array containing the type/url pair for each item to send.
|
||||
*/
|
||||
$this->items = (array) apply_filters( 'rocket_cpcss_items', $this->items );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the "rocket_critical_css_generation_process_running" transient.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
private function update_process_running_transient() {
|
||||
$total = 0;
|
||||
|
||||
foreach ( $this->items as $item ) {
|
||||
if ( ! isset( $item['mobile'] ) ) {
|
||||
$total ++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 1 === $item['mobile'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$total ++;
|
||||
}
|
||||
|
||||
$transient = [
|
||||
'total' => $total,
|
||||
'items' => [],
|
||||
];
|
||||
|
||||
set_transient( 'rocket_critical_css_generation_process_running', $transient, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CPCSS content to use on the current page.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function get_critical_css_content() {
|
||||
$filename = $this->get_current_page_critical_css();
|
||||
|
||||
if ( empty( $filename ) ) {
|
||||
return $this->options->get( 'critical_css', '' );
|
||||
}
|
||||
|
||||
return $this->filesystem->get_contents( $filename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CPCSS filepath for the current page.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @return string Filepath if the file exists, empty string otherwise.
|
||||
*/
|
||||
public function get_current_page_critical_css() {
|
||||
$files = $this->get_critical_css_filenames();
|
||||
|
||||
if (
|
||||
$this->is_async_css_mobile()
|
||||
&&
|
||||
wp_is_mobile()
|
||||
&&
|
||||
$this->filesystem->is_readable( $this->critical_css_path . $files['mobile'] )
|
||||
) {
|
||||
return $this->critical_css_path . $files['mobile'];
|
||||
}
|
||||
|
||||
if ( $this->filesystem->is_readable( $this->critical_css_path . $files['default'] ) ) {
|
||||
return $this->critical_css_path . $files['default'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CPCSS filenames for the current URL type.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_critical_css_filenames() {
|
||||
$default = [
|
||||
'default' => 'front_page.css',
|
||||
'mobile' => 'front_page-mobile.css',
|
||||
];
|
||||
|
||||
if ( is_home() && 'page' === get_option( 'show_on_front' ) ) {
|
||||
return [
|
||||
'default' => 'home.css',
|
||||
'mobile' => 'home-mobile.css',
|
||||
];
|
||||
}
|
||||
|
||||
if ( is_front_page() ) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if ( is_category() ) {
|
||||
return [
|
||||
'default' => 'category.css',
|
||||
'mobile' => 'category-mobile.css',
|
||||
];
|
||||
}
|
||||
|
||||
if ( is_tag() ) {
|
||||
return [
|
||||
'default' => 'post_tag.css',
|
||||
'mobile' => 'post_tag-mobile.css',
|
||||
];
|
||||
}
|
||||
|
||||
if ( is_tax() ) {
|
||||
$taxonomy = get_queried_object()->taxonomy;
|
||||
|
||||
return [
|
||||
'default' => "{$taxonomy}.css",
|
||||
'mobile' => "{$taxonomy}-mobile.css",
|
||||
];
|
||||
}
|
||||
|
||||
if ( is_singular() ) {
|
||||
return $this->get_singular_cpcss_filenames();
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filenames for a singular content.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_singular_cpcss_filenames() {
|
||||
$post_type = get_post_type();
|
||||
$post_id = get_the_ID();
|
||||
$post_cpcss = [
|
||||
'default' => "posts/{$post_type}-{$post_id}.css",
|
||||
'mobile' => "posts/{$post_type}-{$post_id}-mobile.css",
|
||||
];
|
||||
|
||||
if (
|
||||
$this->is_async_css_mobile()
|
||||
&&
|
||||
! $this->filesystem->exists( $this->critical_css_path . $post_cpcss['mobile'] )
|
||||
) {
|
||||
$post_cpcss['mobile'] = $post_cpcss['default'];
|
||||
}
|
||||
|
||||
if ( $this->filesystem->exists( $this->critical_css_path . $post_cpcss['default'] ) ) {
|
||||
return $post_cpcss;
|
||||
}
|
||||
|
||||
return [
|
||||
'default' => "{$post_type}.css",
|
||||
'mobile' => "{$post_type}-mobile.css",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we are in a situation where we need the mobile CPCSS.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_async_css_mobile() {
|
||||
if ( ! (bool) $this->options->get( 'do_caching_mobile_files', 0 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->options->get( 'async_css_mobile', 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of CSS files to be excluded from async CSS.
|
||||
*
|
||||
* @since 3.6.2
|
||||
*
|
||||
* @return array An array of URLs for the CSS files to be excluded.
|
||||
*/
|
||||
public function get_exclude_async_css() {
|
||||
/**
|
||||
* Filter list of async CSS files
|
||||
*
|
||||
* @since 2.10
|
||||
*
|
||||
* @param array $exclude_async_css An array of URLs for the CSS files to be excluded.
|
||||
*/
|
||||
$exclude_async_css = (array) apply_filters( 'rocket_exclude_async_css', [] );
|
||||
if ( empty( $exclude_async_css ) ) {
|
||||
return $exclude_async_css;
|
||||
}
|
||||
$exclude_async_css = array_filter( $exclude_async_css );
|
||||
|
||||
return array_flip( array_flip( $exclude_async_css ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_Rocket_WP_Background_Process;
|
||||
|
||||
/**
|
||||
* Extends the background process class for the critical CSS generation process.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @see WP_Background_Process
|
||||
*/
|
||||
class CriticalCSSGeneration extends WP_Rocket_WP_Background_Process {
|
||||
use TransientTrait;
|
||||
|
||||
/**
|
||||
* Process prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix = 'rocket';
|
||||
|
||||
/**
|
||||
* Specific action identifier for sitemap preload.
|
||||
*
|
||||
* @var string Action identifier
|
||||
*/
|
||||
protected $action = 'critical_css_generation';
|
||||
|
||||
/**
|
||||
* ProcessorService instance.
|
||||
*
|
||||
* @var ProcessorService
|
||||
*/
|
||||
protected $processor;
|
||||
|
||||
/**
|
||||
* Instantiate the class
|
||||
*
|
||||
* @param ProcessorService $processor ProcessorService instance.
|
||||
*/
|
||||
public function __construct( ProcessorService $processor ) {
|
||||
parent::__construct();
|
||||
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the optimization corresponding to $item.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param mixed $item Queue item to iterate over.
|
||||
*
|
||||
* @return bool false if task performed successfully, true otherwise to re-queue the item.
|
||||
*/
|
||||
protected function task( $item ) {
|
||||
if ( ! is_array( $item ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$transient = get_transient( 'rocket_critical_css_generation_process_running' );
|
||||
$mobile = isset( $item['mobile'] ) ? $item['mobile'] : 0;
|
||||
|
||||
$generation_params = [
|
||||
'is_mobile' => $mobile,
|
||||
'item_type' => $item['type'],
|
||||
];
|
||||
$generated = $this->processor->process_generate( $item['url'], $item['path'], $generation_params );
|
||||
|
||||
if ( is_wp_error( $generated ) ) {
|
||||
$this->update_running_transient( $transient, $item['path'], $mobile, $generated->get_error_message(), false );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isset( $generated['code'] ) && 'cpcss_generation_pending' === $generated['code'] ) {
|
||||
$pending = get_transient( 'rocket_cpcss_generation_pending' );
|
||||
|
||||
if ( false === $pending ) {
|
||||
$pending = [];
|
||||
}
|
||||
|
||||
$pending[ $item['path'] ] = $item;
|
||||
|
||||
set_transient( 'rocket_cpcss_generation_pending', $pending, HOUR_IN_SECONDS );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->update_running_transient( $transient, $item['path'], $mobile, $generated['message'], ( 'cpcss_generation_successful' === $generated['code'] ) );
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,728 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
use WP_Filesystem_Direct;
|
||||
|
||||
/**
|
||||
* Critical CSS Subscriber.
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
class CriticalCSSSubscriber implements Subscriber_Interface {
|
||||
|
||||
/**
|
||||
* Instance of Critical CSS.
|
||||
*
|
||||
* @var Critical_CSS
|
||||
*/
|
||||
protected $critical_css;
|
||||
|
||||
/**
|
||||
* Instance of options.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Instance of the filesystem handler.
|
||||
*
|
||||
* @var WP_Filesystem_Direct
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* CPCSS generation and deletion service.
|
||||
*
|
||||
* @var ProcessorService instance for this service.
|
||||
*/
|
||||
private $cpcss_service;
|
||||
|
||||
/**
|
||||
* Creates an instance of the Critical CSS Subscriber.
|
||||
*
|
||||
* @param CriticalCSS $critical_css Critical CSS instance.
|
||||
* @param ProcessorService $cpcss_service Has the logic for cpcss generation and deletion.
|
||||
* @param Options_Data $options WP Rocket options.
|
||||
* @param WP_Filesystem_Direct $filesystem Instance of the filesystem handler.
|
||||
*/
|
||||
public function __construct( CriticalCSS $critical_css, ProcessorService $cpcss_service, Options_Data $options, $filesystem ) {
|
||||
$this->critical_css = $critical_css;
|
||||
$this->cpcss_service = $cpcss_service;
|
||||
$this->options = $options;
|
||||
$this->filesystem = $filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.3
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
// phpcs:disable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
|
||||
return [
|
||||
'admin_post_rocket_generate_critical_css' => 'init_critical_css_generation',
|
||||
|
||||
'update_option_' . rocket_get_constant( 'WP_ROCKET_SLUG' ) => [
|
||||
[ 'generate_critical_css_on_activation', 11, 2 ],
|
||||
[ 'stop_process_on_deactivation', 11, 2 ],
|
||||
[ 'maybe_generate_cpcss_mobile', 12, 2 ],
|
||||
],
|
||||
|
||||
'admin_notices' => [
|
||||
[ 'notice_critical_css_generation_triggered' ],
|
||||
[ 'critical_css_generation_running_notice' ],
|
||||
[ 'critical_css_generation_complete_notice' ],
|
||||
[ 'warning_critical_css_dir_permissions' ],
|
||||
],
|
||||
|
||||
'wp_head' => [ 'insert_load_css', PHP_INT_MAX ],
|
||||
|
||||
'rocket_buffer' => [
|
||||
[ 'insert_critical_css_buffer', 19 ],
|
||||
[ 'async_css', 32 ],
|
||||
],
|
||||
|
||||
'switch_theme' => 'maybe_regenerate_cpcss',
|
||||
'rocket_excluded_inline_js_content' => 'exclude_inline_js',
|
||||
'before_delete_post' => 'delete_cpcss',
|
||||
];
|
||||
// phpcs:enable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the custom CPCSS files from /posts/ folder.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $post_id Deleted post id.
|
||||
*/
|
||||
public function delete_cpcss( $post_id ) {
|
||||
if ( ! current_user_can( 'rocket_regenerate_critical_css' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->options->get( 'async_css', 0 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_type = get_post_type( $post_id );
|
||||
$item_path = 'posts' . DIRECTORY_SEPARATOR . "{$post_type}-{$post_id}.css";
|
||||
$this->cpcss_service->process_delete( $item_path );
|
||||
|
||||
if ( $this->options->get( 'async_css_mobile', 0 ) ) {
|
||||
$mobile_item_path = 'posts' . DIRECTORY_SEPARATOR . "{$post_type}-{$post_id}-mobile.css";
|
||||
$this->cpcss_service->process_delete( $mobile_item_path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This notice is displayed when the Critical CSS Generation is triggered from a different page than
|
||||
* WP Rocket settings page.
|
||||
*
|
||||
* @since 3.4.1
|
||||
*/
|
||||
public function notice_critical_css_generation_triggered() {
|
||||
if ( ! current_user_can( 'rocket_regenerate_critical_css' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( 'settings_page_wprocket' === $screen->id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( false === get_transient( 'rocket_critical_css_generation_triggered' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete_transient( 'rocket_critical_css_generation_triggered' );
|
||||
|
||||
$message = __( 'Critical CSS generation is currently running.', '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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the critical CSS generation from admin.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @see CriticalCSS::process_handler()
|
||||
*/
|
||||
public function init_critical_css_generation() {
|
||||
if (
|
||||
! isset( $_GET['_wpnonce'] )
|
||||
||
|
||||
! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'rocket_generate_critical_css' )
|
||||
) {
|
||||
wp_nonce_ays( '' );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'rocket_regenerate_critical_css' ) ) {
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$version = 'default';
|
||||
|
||||
if ( $this->critical_css->is_async_css_mobile() ) {
|
||||
$version = 'all';
|
||||
}
|
||||
|
||||
$this->critical_css->process_handler( $version );
|
||||
|
||||
if ( ! strpos( wp_get_referer(), 'wprocket' ) ) {
|
||||
set_transient( 'rocket_critical_css_generation_triggered', 1 );
|
||||
}
|
||||
|
||||
wp_safe_redirect( esc_url_raw( wp_get_referer() ) );
|
||||
rocket_get_constant( 'WP_ROCKET_IS_TESTING', false ) ? wp_die() : exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the critical CSS generation when activating the async CSS option.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param array $old_value Previous values for WP Rocket settings.
|
||||
* @param array $value New values for WP Rocket settings.
|
||||
*
|
||||
* @see CriticalCSS::process_handler()
|
||||
*/
|
||||
public function generate_critical_css_on_activation( $old_value, $value ) {
|
||||
if (
|
||||
! isset( $old_value['async_css'], $value['async_css'] )
|
||||
||
|
||||
( $old_value['async_css'] === $value['async_css'] )
|
||||
|| 1 !== (int) $value['async_css']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$critical_css_path = $this->critical_css->get_critical_css_path();
|
||||
|
||||
// Check if the CPCSS path exists and create it.
|
||||
if ( ! $this->filesystem->is_dir( $critical_css_path ) ) {
|
||||
rocket_mkdir_p( $critical_css_path );
|
||||
}
|
||||
|
||||
$version = 'default';
|
||||
|
||||
if (
|
||||
isset( $value['do_caching_mobile_files'], $value['async_css_mobile'] )
|
||||
&&
|
||||
(
|
||||
1 === (int) $value['do_caching_mobile_files']
|
||||
&&
|
||||
1 === (int) $value['async_css_mobile']
|
||||
)
|
||||
) {
|
||||
$version = 'all';
|
||||
}
|
||||
|
||||
// Generate the CPCSS files.
|
||||
$this->critical_css->process_handler( $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe generate the CPCSS for Mobile.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $old_value Array of original values.
|
||||
* @param array $value Array of new values.
|
||||
*/
|
||||
public function maybe_generate_cpcss_mobile( $old_value, $value ) {
|
||||
if (
|
||||
! isset( $value['async_css_mobile'] )
|
||||
||
|
||||
1 !== (int) $value['async_css_mobile']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
! isset( $value['do_caching_mobile_files'] )
|
||||
||
|
||||
1 !== (int) $value['do_caching_mobile_files']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
! isset( $old_value['async_css'], $value['async_css'] )
|
||||
||
|
||||
( ( $old_value['async_css'] !== $value['async_css'] ) && 1 === (int) $value['async_css'] )
|
||||
||
|
||||
1 !== (int) $value['async_css']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->critical_css->process_handler( 'mobile' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the critical CSS generation when deactivating the async CSS option and remove the notices.
|
||||
*
|
||||
* @since 2.11
|
||||
*
|
||||
* @param array $old_value Previous values for WP Rocket settings.
|
||||
* @param array $value New values for WP Rocket settings.
|
||||
*/
|
||||
public function stop_process_on_deactivation( $old_value, $value ) {
|
||||
if (
|
||||
! empty( $_POST[ WP_ROCKET_SLUG ] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
&&
|
||||
isset( $old_value['async_css'], $value['async_css'] )
|
||||
&&
|
||||
( $old_value['async_css'] !== $value['async_css'] )
|
||||
&&
|
||||
0 === (int) $value['async_css']
|
||||
) {
|
||||
$this->critical_css->stop_generation();
|
||||
|
||||
delete_transient( 'rocket_critical_css_generation_process_running' );
|
||||
delete_transient( 'rocket_critical_css_generation_process_complete' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This notice is displayed when the critical CSS generation is running.
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
public function critical_css_generation_running_notice() {
|
||||
if ( ! current_user_can( 'rocket_regenerate_critical_css' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( 'settings_page_wprocket' !== $screen->id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transient = get_transient( 'rocket_critical_css_generation_process_running' );
|
||||
|
||||
if ( ! $transient ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$success_counter = 0;
|
||||
$items_message = '';
|
||||
|
||||
if ( ! empty( $transient['items'] ) ) {
|
||||
$items_message .= '<ul>';
|
||||
|
||||
foreach ( $transient['items'] as $item ) {
|
||||
$status_nonmobile = isset( $item['status']['nonmobile'] );
|
||||
$status_mobile = $this->is_mobile_cpcss_active() ? isset( $item['status']['mobile'] ) : true;
|
||||
if ( $status_nonmobile && $status_mobile ) {
|
||||
$items_message .= '<li>' . $item['status']['nonmobile']['message'] . '</li>';
|
||||
if ( $item['status']['nonmobile']['success'] ) {
|
||||
$success_counter ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$items_message .= '</ul>';
|
||||
}
|
||||
|
||||
if ( ! isset( $transient['total'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
0 === $success_counter
|
||||
&&
|
||||
0 === $transient['total']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = '<p>' . sprintf(
|
||||
// Translators: %1$d = number of critical CSS generated, %2$d = total number of critical CSS to generate.
|
||||
__( 'Critical CSS generation is currently running: %1$d of %2$d page types completed. (Refresh this page to view progress)', 'rocket' ),
|
||||
$success_counter,
|
||||
$transient['total']
|
||||
) . '</p>' . $items_message;
|
||||
|
||||
rocket_notice_html(
|
||||
[
|
||||
'status' => 'info',
|
||||
'message' => $message,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This notice is displayed when the critical CSS generation is complete.
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
public function critical_css_generation_complete_notice() {
|
||||
if ( ! current_user_can( 'rocket_regenerate_critical_css' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( 'settings_page_wprocket' !== $screen->id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transient = get_transient( 'rocket_critical_css_generation_process_complete' );
|
||||
if ( ! $transient ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = 'success';
|
||||
$success_counter = 0;
|
||||
$items_message = '';
|
||||
$desktop = false;
|
||||
|
||||
if ( ! empty( $transient['items'] ) ) {
|
||||
$items_message .= '<ul>';
|
||||
|
||||
foreach ( $transient['items'] as $item ) {
|
||||
$status_nonmobile = isset( $item['status']['nonmobile'] );
|
||||
$status_mobile = $this->is_mobile_cpcss_active() ? isset( $item['status']['mobile'] ) : true;
|
||||
if ( ! $status_nonmobile || ! $status_mobile ) {
|
||||
continue;
|
||||
}
|
||||
if ( isset( $item['status']['nonmobile']['message'] ) ) {
|
||||
$desktop = true;
|
||||
}
|
||||
$items_message .= '<li>' . $item['status']['nonmobile']['message'] . '</li>';
|
||||
if ( $item['status']['nonmobile']['success'] ) {
|
||||
$success_counter ++;
|
||||
}
|
||||
}
|
||||
|
||||
$items_message .= '</ul>';
|
||||
}
|
||||
|
||||
if ( ! $desktop || ( 0 === $success_counter && 0 === $transient['total'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 0 === $success_counter ) {
|
||||
$status = 'error';
|
||||
} elseif ( $success_counter < $transient['total'] ) {
|
||||
$status = 'warning';
|
||||
}
|
||||
|
||||
$message = '<p>' . sprintf(
|
||||
// Translators: %1$d = number of critical CSS generated, %2$d = total number of critical CSS to generate.
|
||||
__( 'Critical CSS generation finished for %1$d of %2$d page types.', 'rocket' ),
|
||||
$success_counter,
|
||||
$transient['total']
|
||||
);
|
||||
$message .= ' <em> (' . date_i18n( get_option( 'date_format' ) ) . ' @ ' . date_i18n( get_option( 'time_format' ) ) . ') </em></p>' . $items_message;
|
||||
|
||||
if ( 'error' === $status || 'warning' === $status ) {
|
||||
$message .= '<p>' . __( 'Critical CSS generation encountered one or more errors.', 'rocket' ) . ' <a href="https://docs.wp-rocket.me/article/1267-troubleshooting-critical-css-generation-issues" data-beacon-article="5d5214d10428631e94f94ae6" target="_blank" rel="noreferer noopener">' . __( 'Learn more.', 'rocket' ) . '</a>';
|
||||
}
|
||||
|
||||
rocket_notice_html(
|
||||
[
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
]
|
||||
);
|
||||
|
||||
delete_transient( 'rocket_critical_css_generation_process_complete' );
|
||||
}
|
||||
|
||||
/**
|
||||
* This warning is displayed when the critical CSS dir isn't writeable.
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
public function warning_critical_css_dir_permissions() {
|
||||
if (
|
||||
current_user_can( 'rocket_manage_options' )
|
||||
&&
|
||||
( ! $this->filesystem->is_writable( WP_ROCKET_CRITICAL_CSS_PATH ) )
|
||||
&&
|
||||
( $this->options->get( 'async_css', false ) )
|
||||
&&
|
||||
rocket_valid_key()
|
||||
) {
|
||||
|
||||
$boxes = get_user_meta( get_current_user_id(), 'rocket_boxes', true );
|
||||
|
||||
if ( in_array( __FUNCTION__, (array) $boxes, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = rocket_notice_writing_permissions(
|
||||
trim( str_replace( ABSPATH, '', WP_ROCKET_CRITICAL_CSS_PATH ), '/' )
|
||||
);
|
||||
|
||||
rocket_notice_html(
|
||||
[
|
||||
'status' => 'error',
|
||||
'dismissible' => '',
|
||||
'message' => $message,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert loadCSS script in <head>.
|
||||
*
|
||||
* @since 2.11.2 Updated loadCSS rel=preload polyfill to version 2.0.1
|
||||
* @since 2.10
|
||||
*/
|
||||
public function insert_load_css() {
|
||||
if ( ! $this->should_async_css() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This filter is documented in inc/classes/Buffer/class-tests.php.
|
||||
$rocket_cache_search = apply_filters( 'rocket_cache_search', false );
|
||||
|
||||
// Don't apply on search page.
|
||||
if ( is_search() && ! $rocket_cache_search ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't apply on 404 page.
|
||||
if ( is_404() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
empty( $this->critical_css->get_current_page_critical_css() )
|
||||
&&
|
||||
empty( $this->options->get( 'critical_css', '' ) )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Dynamic content is properly escaped in the view. */ <<<JS
|
||||
<script>
|
||||
/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
|
||||
(function(w){"use strict";if(!w.loadCSS){w.loadCSS=function(){}}
|
||||
var rp=loadCSS.relpreload={};rp.support=(function(){var ret;try{ret=w.document.createElement("link").relList.supports("preload")}catch(e){ret=!1}
|
||||
return function(){return ret}})();rp.bindMediaToggle=function(link){var finalMedia=link.media||"all";function enableStylesheet(){link.media=finalMedia}
|
||||
if(link.addEventListener){link.addEventListener("load",enableStylesheet)}else if(link.attachEvent){link.attachEvent("onload",enableStylesheet)}
|
||||
setTimeout(function(){link.rel="stylesheet";link.media="only x"});setTimeout(enableStylesheet,3000)};rp.poly=function(){if(rp.support()){return}
|
||||
var links=w.document.getElementsByTagName("link");for(var i=0;i<links.length;i++){var link=links[i];if(link.rel==="preload"&&link.getAttribute("as")==="style"&&!link.getAttribute("data-loadcss")){link.setAttribute("data-loadcss",!0);rp.bindMediaToggle(link)}}};if(!rp.support()){rp.poly();var run=w.setInterval(rp.poly,500);if(w.addEventListener){w.addEventListener("load",function(){rp.poly();w.clearInterval(run)})}else if(w.attachEvent){w.attachEvent("onload",function(){rp.poly();w.clearInterval(run)})}}
|
||||
if(typeof exports!=="undefined"){exports.loadCSS=loadCSS}
|
||||
else{w.loadCSS=loadCSS}}(typeof global!=="undefined"?global:this))
|
||||
</script>
|
||||
JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert critical CSS before combined CSS when option is active.
|
||||
*
|
||||
* @since 2.11.5
|
||||
*
|
||||
* @param string $buffer HTML output of the page.
|
||||
*
|
||||
* @return string Updated HTML output
|
||||
*/
|
||||
public function insert_critical_css_buffer( $buffer ) {
|
||||
if ( ! $this->should_async_css() ) {
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
$critical_css_content = $this->critical_css->get_critical_css_content();
|
||||
|
||||
if ( empty( $critical_css_content ) ) {
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
$critical_css_content = str_replace( '\\', '\\\\', $critical_css_content );
|
||||
|
||||
$buffer = preg_replace(
|
||||
'#</title>#iU',
|
||||
'</title><style id="rocket-critical-css">' . wp_strip_all_tags( $critical_css_content ) . '</style>',
|
||||
$buffer,
|
||||
1
|
||||
);
|
||||
|
||||
return preg_replace( '#</body>#iU', $this->return_remove_cpcss_script() . '</body>', $buffer, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JS script to remove the critical css style from frontend.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function return_remove_cpcss_script() {
|
||||
$filename = rocket_get_constant( 'SCRIPT_DEBUG' ) ? 'cpcss-removal.js' : 'cpcss-removal.min.js';
|
||||
$script = rocket_get_constant( 'WP_ROCKET_PATH' ) . "assets/js/{$filename}";
|
||||
|
||||
if ( ! is_readable( $script ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<script>%s</script>',
|
||||
$this->filesystem->get_contents( $script )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds wprRemoveCPCSS to excluded inline JS array.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $excluded_inline Array of inline JS excluded from being combined.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function exclude_inline_js( array $excluded_inline ) {
|
||||
$excluded_inline[] = 'wprRemoveCPCSS';
|
||||
|
||||
return $excluded_inline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defer loading of CSS files.
|
||||
*
|
||||
* @since 2.10
|
||||
*
|
||||
* @param string $buffer HTML code.
|
||||
*
|
||||
* @return string Updated HTML code
|
||||
*/
|
||||
public function async_css( $buffer ) {
|
||||
if ( ! $this->should_async_css() ) {
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
if (
|
||||
empty( $this->critical_css->get_current_page_critical_css() )
|
||||
&&
|
||||
empty( $this->options->get( 'critical_css', '' ) )
|
||||
) {
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
$excluded_css = array_flip( $this->critical_css->get_exclude_async_css() );
|
||||
|
||||
/**
|
||||
* Filters the pattern used to get all stylesheets in the HTML.
|
||||
*
|
||||
* @since 2.10
|
||||
*
|
||||
* @param string $css_pattern Regex pattern to get all stylesheets in the HTML.
|
||||
*/
|
||||
$css_pattern = apply_filters(
|
||||
'rocket_async_css_regex_pattern',
|
||||
'/(?=<link[^>]*\s(rel\s*=\s*[\'"]stylesheet["\']))<link[^>]*\shref\s*=\s*[\'"]([^\'"]+)[\'"](.*)>/iU'
|
||||
);
|
||||
|
||||
// Get all css files with this regex.
|
||||
preg_match_all( $css_pattern, $buffer, $tags_match );
|
||||
if ( ! isset( $tags_match[0] ) ) {
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
$noscripts = '<noscript>';
|
||||
|
||||
foreach ( $tags_match[0] as $i => $tag ) {
|
||||
// Strip query args.
|
||||
$path = wp_parse_url( $tags_match[2][ $i ], PHP_URL_PATH );
|
||||
|
||||
// Check if this file should be deferred.
|
||||
if ( isset( $excluded_css[ $path ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$preload = str_replace( 'stylesheet', 'preload', $tags_match[1][ $i ] );
|
||||
$onload = preg_replace( '~' . preg_quote( $tags_match[3][ $i ], '~' ) . '~iU', ' data-rocket-async="style" as="style" onload=""' . $tags_match[3][ $i ] . '>', $tags_match[3][ $i ] );
|
||||
$tag = str_replace( $tags_match[3][ $i ] . '>', $onload, $tag );
|
||||
$tag = str_replace( $tags_match[1][ $i ], $preload, $tag );
|
||||
$tag = str_replace( 'onload=""', 'onload="this.onload=null;this.rel=\'stylesheet\'"', $tag );
|
||||
$tag = preg_replace( '/(id\s*=\s*[\"\'](?:[^\"\']*)*[\"\'])/i', '', $tag );
|
||||
$buffer = str_replace( $tags_match[0][ $i ], $tag, $buffer );
|
||||
|
||||
$noscripts .= $tags_match[0][ $i ];
|
||||
}
|
||||
|
||||
$noscripts .= '</noscript>';
|
||||
|
||||
return str_replace( '</body>', $noscripts . '</body>', $buffer );
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerates the CPCSS when switching theme if the option is active.
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public function maybe_regenerate_cpcss() {
|
||||
if ( ! $this->options->get( 'async_css' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->critical_css->process_handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if mobile CPCSS is active.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return boolean CPCSS active or not.
|
||||
*/
|
||||
private function is_mobile_cpcss_active() {
|
||||
return (
|
||||
$this->options->get( 'async_css', 0 )
|
||||
&&
|
||||
$this->options->get( 'cache_mobile', 0 )
|
||||
&&
|
||||
$this->options->get( 'do_caching_mobile_files', 0 )
|
||||
)
|
||||
&&
|
||||
$this->options->get( 'async_css_mobile', 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we should async CSS
|
||||
*
|
||||
* @since 3.6.2.1
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function should_async_css() {
|
||||
if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->options->get( 'async_css', 0 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! is_rocket_post_excluded_option( 'async_css' );
|
||||
}
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_Error;
|
||||
use WP_Filesystem_Direct;
|
||||
|
||||
/**
|
||||
* Class DataManager
|
||||
*
|
||||
* @package WP_Rocket\Engine\CriticalPath
|
||||
*/
|
||||
class DataManager {
|
||||
|
||||
/**
|
||||
* Base critical CSS path for posts.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $critical_css_path;
|
||||
|
||||
/**
|
||||
* Instance of the filesystem handler.
|
||||
*
|
||||
* @var WP_Filesystem_Direct
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* DataManager constructor, adjust the critical css path for posts.
|
||||
*
|
||||
* @param string $critical_css_path path for main critical css folder.
|
||||
* @param WP_Filesystem_Direct $filesystem Instance of the filesystem handler.
|
||||
*/
|
||||
public function __construct( $critical_css_path, $filesystem ) {
|
||||
$this->critical_css_path = $critical_css_path . get_current_blog_id() . DIRECTORY_SEPARATOR;
|
||||
$this->filesystem = $filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save CPCSS into file.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $path Path for cpcss file related to this web page.
|
||||
* @param string $cpcss CPCSS code to be saved.
|
||||
* @param string $url URL for item to be used in error messages.
|
||||
* @param bool $is_mobile If this is cpcss for mobile or not.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function save_cpcss( $path, $cpcss, $url, $is_mobile = false, $item_type = 'custom' ) {
|
||||
$file_path_directory = dirname( $this->critical_css_path . $path );
|
||||
|
||||
if ( ! $this->filesystem->is_dir( $file_path_directory ) ) {
|
||||
if ( ! rocket_mkdir_p( $file_path_directory ) ) {
|
||||
|
||||
return new WP_Error(
|
||||
'cpcss_generation_failed',
|
||||
// translators: %s = item URL.
|
||||
sprintf(
|
||||
$is_mobile
|
||||
?
|
||||
// translators: %s = item URL.
|
||||
__( 'Critical CSS for %1$s on mobile not generated. Error: The API returned an empty response.', 'rocket' )
|
||||
:
|
||||
// translators: %s = item URL.
|
||||
__( 'Critical CSS for %1$s not generated. Error: The API returned an empty response.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $url : $item_type
|
||||
),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return rocket_put_content(
|
||||
$this->critical_css_path . $path,
|
||||
wp_strip_all_tags( $cpcss, true )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete critical css file by path.
|
||||
*
|
||||
* @param string $path Critical css file path to be deleted.
|
||||
* @param bool $is_mobile If this is cpcss for mobile or not.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function delete_cpcss( $path, $is_mobile = false ) {
|
||||
$full_path = $this->critical_css_path . $path;
|
||||
|
||||
if ( ! $this->filesystem->exists( $full_path ) ) {
|
||||
return new WP_Error(
|
||||
'cpcss_not_exists',
|
||||
$is_mobile
|
||||
?
|
||||
__( 'Critical CSS file for mobile does not exist', 'rocket' )
|
||||
:
|
||||
__( 'Critical CSS file does not exist', 'rocket' ),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $this->filesystem->delete( $full_path ) ) {
|
||||
return new WP_Error(
|
||||
'cpcss_deleted_failed',
|
||||
$is_mobile
|
||||
?
|
||||
__( 'Critical CSS file for mobile cannot be deleted', 'rocket' )
|
||||
:
|
||||
__( 'Critical CSS file cannot be deleted', 'rocket' ),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get job_id from cache based on item_url.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_url URL for item to be used in error messages.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_cache_job_id( $item_url, $is_mobile = false ) {
|
||||
$cache_key = $this->get_cache_key_from_url( $item_url, $is_mobile );
|
||||
|
||||
return get_transient( $cache_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Job_id for Item_url into cache.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_url URL for item to be used in error messages.
|
||||
* @param string $job_id ID for the job to get details.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set_cache_job_id( $item_url, $job_id, $is_mobile = false ) {
|
||||
$cache_key = $this->get_cache_key_from_url( $item_url, $is_mobile );
|
||||
|
||||
return set_transient( $cache_key, $job_id, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete job_id from cache based on item_url.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_url URL for item to be used in error messages.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_cache_job_id( $item_url, $is_mobile = false ) {
|
||||
$cache_key = $this->get_cache_key_from_url( $item_url, $is_mobile );
|
||||
|
||||
return delete_transient( $cache_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key from url to be used in caching job_id.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_url URL for item to be used in error messages.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_cache_key_from_url( $item_url, $is_mobile = false ) {
|
||||
$encoded_url = md5( $item_url );
|
||||
|
||||
if ( $is_mobile ) {
|
||||
$encoded_url .= '_mobile';
|
||||
}
|
||||
|
||||
return 'rocket_specific_cpcss_job_' . $encoded_url;
|
||||
}
|
||||
}
|
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
class ProcessorService {
|
||||
|
||||
/**
|
||||
* Responsible for dealing with data/database.
|
||||
*
|
||||
* @var DataManager datamanager instance.
|
||||
*/
|
||||
private $data_manager;
|
||||
|
||||
/**
|
||||
* Responsible for dealing with CPCSS APIs.
|
||||
*
|
||||
* @var APIClient api_client instance.
|
||||
*/
|
||||
private $api_client;
|
||||
|
||||
/**
|
||||
* RESTWP constructor.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param DataManager $data_manager Data manager instance, responsible for dealing with data/database.
|
||||
* @param APIClient $api_client API Client instance to deal with CPCSS APIs.
|
||||
*/
|
||||
public function __construct( DataManager $data_manager, APIClient $api_client ) {
|
||||
$this->data_manager = $data_manager;
|
||||
$this->api_client = $api_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process CPCSS generation, Check timeout and send the generation request.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_url URL for item to be used in error messages.
|
||||
* @param string $item_path Path for item to be processed.
|
||||
* @param array $additional_parameters additional parameters for generation.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function process_generate( $item_url, $item_path, $additional_parameters = [] ) {
|
||||
$defaults = [
|
||||
'timeout' => false,
|
||||
'is_mobile' => false,
|
||||
'item_type' => 'custom',
|
||||
];
|
||||
$args = array_merge( $defaults, $additional_parameters );
|
||||
|
||||
// Ajax call requested a timeout.
|
||||
if ( $args['timeout'] ) {
|
||||
return $this->process_timeout( $item_url, $args['is_mobile'], $args['item_type'] );
|
||||
}
|
||||
|
||||
$cpcss_job_id = $this->data_manager->get_cache_job_id( $item_url, $args['is_mobile'] );
|
||||
if ( false === $cpcss_job_id ) {
|
||||
return $this->send_generation_request( $item_url, $item_path, $args['is_mobile'], $args['item_type'] );
|
||||
}
|
||||
|
||||
// job_id is found and we need to check status for it.
|
||||
return $this->check_cpcss_job_status( $cpcss_job_id, $item_path, $item_url, $args['is_mobile'], $args['item_type'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Generation first request.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_url Url for item to send the generation request for.
|
||||
* @param string $item_path Path for item to send the generation request for.
|
||||
* @param bool $is_mobile If this request is for mobile cpcss.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function send_generation_request( $item_url, $item_path, $is_mobile = false, $item_type = 'custom' ) {
|
||||
// call send generation request from APIClient for the first time.
|
||||
$params = [
|
||||
'mobile' => (int) $is_mobile,
|
||||
];
|
||||
$generated_job = $this->api_client->send_generation_request( $item_url, $params, $item_type );
|
||||
|
||||
// validate generate response.
|
||||
if ( is_wp_error( $generated_job ) ) {
|
||||
// Failed so return back the data.
|
||||
return $generated_job;
|
||||
}
|
||||
|
||||
// Send generation request succeeded.
|
||||
// Save job_id into cache.
|
||||
$this->data_manager->set_cache_job_id( $item_url, $generated_job->data->id, $is_mobile );
|
||||
|
||||
return $this->check_cpcss_job_status( $generated_job->data->id, $item_path, $item_url, $is_mobile, $item_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get job details by job_id.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $job_id ID for the job to get details.
|
||||
* @param string $item_url URL for item to be used in error messages.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return array|mixed|WP_Error
|
||||
*/
|
||||
private function get_cpcss_job_details( $job_id, $item_url, $item_type = 'custom' ) {
|
||||
$job_details = $this->api_client->get_job_details( $job_id, $item_url, $item_type );
|
||||
|
||||
if ( is_wp_error( $job_details ) ) {
|
||||
return $job_details;
|
||||
}
|
||||
|
||||
return $job_details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check status and process the output for a job.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $job_id ID for the job to get details.
|
||||
* @param string $item_path Path for this item to be validated.
|
||||
* @param string $item_url URL for item to be used in error messages.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return array|WP_Error Response in case of success, failure or pending.
|
||||
*/
|
||||
private function check_cpcss_job_status( $job_id, $item_path, $item_url, $is_mobile = false, $item_type = 'custom' ) {
|
||||
$job_details = $this->api_client->get_job_details( $job_id, $item_url, $is_mobile, $item_type );
|
||||
|
||||
if ( is_wp_error( $job_details ) ) {
|
||||
$this->data_manager->delete_cache_job_id( $item_url, $is_mobile );
|
||||
|
||||
return $job_details;
|
||||
}
|
||||
|
||||
if ( 200 !== $job_details->status ) {
|
||||
// On job error.
|
||||
return $this->on_job_error( $job_details, $item_url, $is_mobile, $item_type );
|
||||
}
|
||||
|
||||
// On job status 200.
|
||||
$job_state = $job_details->data->state;
|
||||
|
||||
// For pending job status.
|
||||
if ( isset( $job_state ) && 'complete' !== $job_state ) {
|
||||
return $this->on_job_pending( $item_url, $item_type );
|
||||
}
|
||||
|
||||
// For successful job status.
|
||||
if (
|
||||
isset( $job_state, $job_details->data->critical_path )
|
||||
&&
|
||||
'complete' === $job_state
|
||||
) {
|
||||
return $this->on_job_success( $item_path, $item_url, $job_details->data->critical_path, $is_mobile, $item_type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process logic for job error.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $job_details Job details array.
|
||||
* @param string $item_url Url for web page to be processed, used for error messages.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return WP_Error
|
||||
*/
|
||||
private function on_job_error( $job_details, $item_url, $is_mobile = false, $item_type = 'custom' ) {
|
||||
$this->data_manager->delete_cache_job_id( $item_url, $is_mobile );
|
||||
|
||||
if ( $is_mobile ) {
|
||||
|
||||
$error = sprintf(
|
||||
// translators: %1$s = item URL or item type.
|
||||
__( 'Mobile Critical CSS for %1$s not generated.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $item_url : $item_type
|
||||
);
|
||||
} else {
|
||||
|
||||
$error = sprintf(
|
||||
// translators: %1$s = item URL or item type.
|
||||
__( 'Critical CSS for %1$s not generated.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $item_url : $item_type
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $job_details->message ) ) {
|
||||
// translators: %1$s = error message.
|
||||
$error .= ' ' . sprintf( __( 'Error: %1$s', 'rocket' ), $job_details->message );
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'cpcss_generation_failed',
|
||||
$error,
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process logic for job pending status.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_url Url for web page to be processed, used for error messages.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function on_job_pending( $item_url, $item_type = 'custom' ) {
|
||||
return [
|
||||
'code' => 'cpcss_generation_pending',
|
||||
'message' => sprintf(
|
||||
// translators: %1$s = Item URL or item type.
|
||||
__( 'Critical CSS for %s in progress.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $item_url : $item_type
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process logic for job success status.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_path Item Path for web page to be processed.
|
||||
* @param string $item_url Item Url for web page to be processed.
|
||||
* @param string $cpcss_code CPCSS Code to be saved.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function on_job_success( $item_path, $item_url, $cpcss_code, $is_mobile = false, $item_type = 'custom' ) {
|
||||
// delete cache job_id for this item.
|
||||
$this->data_manager->delete_cache_job_id( $item_url, $is_mobile );
|
||||
|
||||
// save the generated CPCSS code into file.
|
||||
$saved = $this->data_manager->save_cpcss( $item_path, $cpcss_code, $item_url, $is_mobile, $item_type );
|
||||
if ( is_wp_error( $saved ) ) {
|
||||
return $saved;
|
||||
}
|
||||
|
||||
if ( $is_mobile ) {
|
||||
return [
|
||||
'code' => 'cpcss_generation_successful',
|
||||
'message' => sprintf(
|
||||
// translators: %1$s = Item URL or item type.
|
||||
__( 'Mobile Critical CSS for %s generated.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $item_url : $item_type
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// Send the current status of job.
|
||||
return [
|
||||
'code' => 'cpcss_generation_successful',
|
||||
'message' => sprintf(
|
||||
// translators: %1$s = Item URL or item type.
|
||||
__( 'Critical CSS for %s generated.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $item_url : $item_type
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the login for CPCSS deletion.
|
||||
*
|
||||
* @param string $item_path Path for item to delete CPCSS code.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function process_delete( $item_path ) {
|
||||
$deleted = $this->data_manager->delete_cpcss( $item_path );
|
||||
|
||||
if ( is_wp_error( $deleted ) ) {
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => 'success',
|
||||
'message' => __( 'Critical CSS file deleted successfully.', 'rocket' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process timeout action for CPCSS generation.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param string $item_url URL for item to be used in error messages.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
* @param string $item_type Optional. Type for this item if it's custom or specific type. Default: custom.
|
||||
* @return WP_Error
|
||||
*/
|
||||
private function process_timeout( $item_url, $is_mobile = false, $item_type = 'custom' ) {
|
||||
$this->data_manager->delete_cache_job_id( $item_url, $is_mobile );
|
||||
|
||||
if ( $is_mobile ) {
|
||||
return new WP_Error(
|
||||
'cpcss_generation_timeout',
|
||||
sprintf(
|
||||
// translators: %1$s = Item URL or item type.
|
||||
__( 'Mobile Critical CSS for %1$s timeout. Please retry a little later.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $item_url : $item_type
|
||||
),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'cpcss_generation_timeout',
|
||||
sprintf(
|
||||
// translators: %1$s = Item URL or item type.
|
||||
__( 'Critical CSS for %1$s timeout. Please retry a little later.', 'rocket' ),
|
||||
( 'custom' === $item_type ) ? $item_url : $item_type
|
||||
),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_Rocket\Event_Management\Subscriber_Interface;
|
||||
|
||||
/**
|
||||
* Class RESTCSSSubscriber
|
||||
*
|
||||
* @package WP_Rocket\Engine\CriticalPath
|
||||
*/
|
||||
class RESTCSSSubscriber implements Subscriber_Interface {
|
||||
|
||||
/**
|
||||
* REST manager that has generate and delete methods.
|
||||
*
|
||||
* @var RESTWPInterface
|
||||
*/
|
||||
private $rest_manager;
|
||||
|
||||
/**
|
||||
* RESTCSSSubscriber constructor.
|
||||
*
|
||||
* @param RESTWPInterface $rest_manager REST manager instance.
|
||||
*/
|
||||
public function __construct( RESTWPInterface $rest_manager ) {
|
||||
$this->rest_manager = $rest_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of events that this subscriber wants to listen to.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscribed_events() {
|
||||
return [
|
||||
'rest_api_init' => [ 'register_routes' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers generate/delete routes in the API.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_routes() {
|
||||
$this->rest_manager->register_generate_route();
|
||||
$this->rest_manager->register_delete_route();
|
||||
}
|
||||
|
||||
}
|
322
wp-content/plugins/wp-rocket/inc/Engine/CriticalPath/RESTWP.php
Normal file
322
wp-content/plugins/wp-rocket/inc/Engine/CriticalPath/RESTWP.php
Normal file
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_Error;
|
||||
use WP_Rocket\Admin\Options_Data;
|
||||
|
||||
/**
|
||||
* Class RESTWP
|
||||
*
|
||||
* @package WP_Rocket\Engine\CriticalPath
|
||||
*/
|
||||
abstract class RESTWP implements RESTWPInterface {
|
||||
|
||||
/**
|
||||
* Namespace for REST Route.
|
||||
*/
|
||||
const ROUTE_NAMESPACE = 'wp-rocket/v1';
|
||||
|
||||
/**
|
||||
* Part of route namespace for this inherited class item type.
|
||||
*
|
||||
* @var string $route_namespace to be set with like post, term.
|
||||
*/
|
||||
protected $route_namespace;
|
||||
|
||||
/**
|
||||
* CPCSS generation and deletion service.
|
||||
*
|
||||
* @var ProcessorService instance for this service.
|
||||
*/
|
||||
private $cpcss_service;
|
||||
|
||||
/**
|
||||
* WP Rocket options instance.
|
||||
*
|
||||
* @var Options_Data
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* RESTWP constructor.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param ProcessorService $cpcss_service Has the logic for cpcss generation and deletion.
|
||||
* @param Options_Data $options Instance of options data handler.
|
||||
*/
|
||||
public function __construct( ProcessorService $cpcss_service, Options_Data $options ) {
|
||||
$this->cpcss_service = $cpcss_service;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the generate route in the WP REST API
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_generate_route() {
|
||||
register_rest_route(
|
||||
self::ROUTE_NAMESPACE,
|
||||
'cpcss/' . $this->route_namespace . '/(?P<id>[\d]+)',
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'generate' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Delete CPCSS route in the WP REST API.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public function register_delete_route() {
|
||||
register_rest_route(
|
||||
self::ROUTE_NAMESPACE,
|
||||
'cpcss/' . $this->route_namespace . '/(?P<id>[\d]+)',
|
||||
[
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [ $this, 'delete' ],
|
||||
'permission_callback' => [ $this, 'check_permissions' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks user's permissions. This is a callback registered to REST route's "permission_callback" parameter.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return bool true if the user has permission; else false.
|
||||
*/
|
||||
public function check_permissions() {
|
||||
return current_user_can( 'rocket_regenerate_critical_css' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean post cache files on CPCSS generation or deletion.
|
||||
*
|
||||
* @since 3.6.1
|
||||
*
|
||||
* @param int $item_id ID for this item to get Url for.
|
||||
*/
|
||||
private function clean_post_cache( $item_id ) {
|
||||
rocket_clean_files( $this->get_url( $item_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the CPCSS for the requested post ID.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param WP_REST_Request $request WP REST request response.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function generate( WP_REST_Request $request ) {
|
||||
$item_id = (int) $request->get_param( 'id' );
|
||||
$is_mobile = (bool) $request->get_param( 'is_mobile' );
|
||||
|
||||
// Bailout in case mobile CPCSS generation is called but this option is disabled.
|
||||
if (
|
||||
$is_mobile
|
||||
&&
|
||||
(
|
||||
! $this->options->get( 'async_css_mobile', 0 )
|
||||
||
|
||||
! $this->options->get( 'do_caching_mobile_files', 0 )
|
||||
)
|
||||
) {
|
||||
return rest_ensure_response(
|
||||
$this->return_error(
|
||||
new WP_Error(
|
||||
'mobile_cpcss_not_enabled',
|
||||
__( 'Mobile CPCSS generation not enabled.', 'rocket' ),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// validate item.
|
||||
$validated = $this->validate_item_for_generate( $item_id );
|
||||
if ( is_wp_error( $validated ) ) {
|
||||
return rest_ensure_response( $this->return_error( $validated ) );
|
||||
}
|
||||
|
||||
// get item url.
|
||||
$item_url = $this->get_url( $item_id );
|
||||
$timeout = ( isset( $request['timeout'] ) && ! empty( $request['timeout'] ) );
|
||||
$item_path = $this->get_path( $item_id, $is_mobile );
|
||||
|
||||
$additional_params = [
|
||||
'timeout' => $timeout,
|
||||
'is_mobile' => $is_mobile,
|
||||
'item_type' => 'custom',
|
||||
];
|
||||
$generated = $this->cpcss_service->process_generate( $item_url, $item_path, $additional_params );
|
||||
|
||||
if ( is_wp_error( $generated ) ) {
|
||||
return rest_ensure_response(
|
||||
$this->return_error( $generated )
|
||||
);
|
||||
}
|
||||
|
||||
$this->clean_post_cache( $item_id );
|
||||
|
||||
return rest_ensure_response(
|
||||
$this->return_success( $generated )
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the item to be sent to generate CPCSS.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $item_id ID for this item to be validated.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
abstract protected function validate_item_for_generate( $item_id );
|
||||
|
||||
/**
|
||||
* Validate the item to be sent to Delete CPCSS.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $item_id ID for this item to be validated.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
abstract protected function validate_item_for_delete( $item_id );
|
||||
|
||||
/**
|
||||
* Get url for this item.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $item_id ID for this item to get Url for.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
abstract protected function get_url( $item_id );
|
||||
|
||||
/**
|
||||
* Get CPCSS file path to save CPCSS code into.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $item_id ID for this item to get the path for.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_path( $item_id, $is_mobile = false );
|
||||
|
||||
/**
|
||||
* Delete Post ID CPCSS file.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param WP_REST_Request $request the WP Rest Request object.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function delete( WP_REST_Request $request ) {
|
||||
$item_id = (int) $request->get_param( 'id' );
|
||||
|
||||
// validate item.
|
||||
$validated = $this->validate_item_for_delete( $item_id );
|
||||
if ( is_wp_error( $validated ) ) {
|
||||
return rest_ensure_response( $this->return_error( $validated ) );
|
||||
}
|
||||
|
||||
if ( $this->options->get( 'async_css_mobile', 0 ) ) {
|
||||
$mobile_item_path = $this->get_path( $item_id, true );
|
||||
$this->cpcss_service->process_delete( $mobile_item_path );
|
||||
}
|
||||
|
||||
$item_path = $this->get_path( $item_id );
|
||||
$deleted = $this->cpcss_service->process_delete( $item_path );
|
||||
if ( is_wp_error( $deleted ) ) {
|
||||
return rest_ensure_response( $this->return_error( $deleted ) );
|
||||
}
|
||||
|
||||
$this->clean_post_cache( $item_id );
|
||||
|
||||
return rest_ensure_response( $this->return_success( $deleted ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted array response
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param bool $success True for success, false otherwise.
|
||||
* @param string $code The code to use for the response.
|
||||
* @param string $message The message to send in the response.
|
||||
* @param int $status The status code to send for the response.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function return_array_response( $success = false, $code = '', $message = '', $status = 200 ) {
|
||||
return [
|
||||
'success' => $success,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => [
|
||||
'status' => $status,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert WP_Error into array to be used in response.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param WP_Error $error Error that will be converted to array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function return_error( $error ) {
|
||||
$error_data = $error->get_error_data();
|
||||
|
||||
return $this->return_array_response(
|
||||
false,
|
||||
$error->get_error_code(),
|
||||
$error->get_error_message(),
|
||||
isset( $error_data['status'] ) ? $error_data['status'] : 400
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return success to be used in response.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $data which has success parameters with two keys: code and message.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function return_success( $data ) {
|
||||
return $this->return_array_response(
|
||||
true,
|
||||
$data['code'],
|
||||
$data['message'],
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
interface RESTWPInterface {
|
||||
/**
|
||||
* Registers the generate route in the WP REST API
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_generate_route();
|
||||
|
||||
/**
|
||||
* Register Delete CPCSS route in the WP REST API.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_delete_route();
|
||||
|
||||
/**
|
||||
* Checks user's permissions. This is a callback registered to REST route's "permission_callback" parameter.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @return bool true if the user has permission; else false.
|
||||
*/
|
||||
public function check_permissions();
|
||||
|
||||
/**
|
||||
* Generates the CPCSS for the requested post ID.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param WP_REST_Request $request WP REST request response.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function generate( WP_REST_Request $request );
|
||||
|
||||
/**
|
||||
* Delete Post ID CPCSS file.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param WP_REST_Request $request the WP Rest Request object.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function delete( WP_REST_Request $request );
|
||||
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class RESTWPPost
|
||||
*
|
||||
* @package WP_Rocket\Engine\CriticalPath
|
||||
*/
|
||||
class RESTWPPost extends RESTWP {
|
||||
|
||||
/**
|
||||
* Part of route namespace for this inherited class item type.
|
||||
*
|
||||
* @var string $route_namespace to be set with like post, term.
|
||||
*/
|
||||
protected $route_namespace = 'post';
|
||||
|
||||
/**
|
||||
* Validate the item to be sent to generate CPCSS.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $post_id ID for this post to be validated.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
protected function validate_item_for_generate( $post_id ) {
|
||||
$status = get_post_status( $post_id );
|
||||
|
||||
if ( ! $status ) {
|
||||
return new WP_Error(
|
||||
'post_not_exists',
|
||||
__( 'Requested post does not exist.', 'rocket' ),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'publish' !== $status ) {
|
||||
return new WP_Error(
|
||||
'post_not_published',
|
||||
__( 'Cannot generate CPCSS for unpublished post.', 'rocket' ),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the item to be sent to delete CPCSS.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $post_id ID for this post to be validated.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
protected function validate_item_for_delete( $post_id ) {
|
||||
if ( empty( get_permalink( $post_id ) ) ) {
|
||||
return new WP_Error(
|
||||
'post_not_exists',
|
||||
__( 'Requested post does not exist.', 'rocket' ),
|
||||
[
|
||||
'status' => 400,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get url for this item.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $post_id ID for this post to be validated.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
protected function get_url( $post_id ) {
|
||||
return get_permalink( $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CPCSS file path to save CPCSS code into.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param int $post_id ID for this post to be validated.
|
||||
* @param bool $is_mobile Bool identifier for is_mobile CPCSS generation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_path( $post_id, $is_mobile = false ) {
|
||||
$post_type = get_post_type( $post_id );
|
||||
|
||||
return 'posts' . DIRECTORY_SEPARATOR . "{$post_type}-{$post_id}" . ( $is_mobile ? '-mobile' : '' ) . '.css';
|
||||
}
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
|
||||
|
||||
/**
|
||||
* Service provider for the Critical CSS classes
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
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 = [
|
||||
'critical_css_generation',
|
||||
'critical_css',
|
||||
'critical_css_subscriber',
|
||||
'cpcss_api_client',
|
||||
'cpcss_data_manager',
|
||||
'cpcss_service',
|
||||
'rest_cpcss_wp_post',
|
||||
'rest_cpcss_subscriber',
|
||||
'cpcss_settings',
|
||||
'cpcss_post',
|
||||
'cpcss_admin',
|
||||
'critical_css_admin_subscriber',
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the subscribers in the container.
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public function register() {
|
||||
$filesystem = rocket_direct_filesystem();
|
||||
$critical_css_path = rocket_get_constant( 'WP_ROCKET_CRITICAL_CSS_PATH' );
|
||||
$options = $this->getContainer()->get( 'options' );
|
||||
$beacon = $this->getContainer()->get( 'beacon' );
|
||||
$template_path = $this->getContainer()->get( 'template_path' ) . '/cpcss';
|
||||
|
||||
$this->getContainer()->share( 'cpcss_api_client', 'WP_Rocket\Engine\CriticalPath\APIClient' );
|
||||
$this->getContainer()->share( 'cpcss_data_manager', 'WP_Rocket\Engine\CriticalPath\DataManager' )
|
||||
->withArgument( $critical_css_path )
|
||||
->withArgument( $filesystem );
|
||||
$this->getContainer()->share( 'cpcss_service', 'WP_Rocket\Engine\CriticalPath\ProcessorService' )
|
||||
->withArgument( $this->getContainer()->get( 'cpcss_data_manager' ) )
|
||||
->withArgument( $this->getContainer()->get( 'cpcss_api_client' ) );
|
||||
|
||||
$processor_service = $this->getContainer()->get( 'cpcss_service' );
|
||||
|
||||
// REST CPCSS START.
|
||||
$this->getContainer()->share( 'rest_cpcss_wp_post', 'WP_Rocket\Engine\CriticalPath\RESTWPPost' )
|
||||
->withArgument( $processor_service )
|
||||
->withArgument( $options );
|
||||
$this->getContainer()->share( 'rest_cpcss_subscriber', 'WP_Rocket\Engine\CriticalPath\RESTCSSSubscriber' )
|
||||
->withArgument( $this->getContainer()->get( 'rest_cpcss_wp_post' ) );
|
||||
// REST CPCSS END.
|
||||
|
||||
$this->getContainer()->add( 'critical_css_generation', 'WP_Rocket\Engine\CriticalPath\CriticalCSSGeneration' )
|
||||
->withArgument( $processor_service );
|
||||
$this->getContainer()->add( 'critical_css', 'WP_Rocket\Engine\CriticalPath\CriticalCSS' )
|
||||
->withArgument( $this->getContainer()->get( 'critical_css_generation' ) )
|
||||
->withArgument( $options )
|
||||
->withArgument( $filesystem );
|
||||
|
||||
$critical_css = $this->getContainer()->get( 'critical_css' );
|
||||
|
||||
$this->getContainer()->share( 'critical_css_subscriber', 'WP_Rocket\Engine\CriticalPath\CriticalCSSSubscriber' )
|
||||
->withArgument( $critical_css )
|
||||
->withArgument( $processor_service )
|
||||
->withArgument( $options )
|
||||
->withArgument( $filesystem );
|
||||
|
||||
$this->getContainer()->add( 'cpcss_post', 'WP_Rocket\Engine\CriticalPath\Admin\Post' )
|
||||
->withArgument( $options )
|
||||
->withArgument( $beacon )
|
||||
->withArgument( $critical_css_path )
|
||||
->withArgument( $template_path );
|
||||
$this->getContainer()->add( 'cpcss_settings', 'WP_Rocket\Engine\CriticalPath\Admin\Settings' )
|
||||
->withArgument( $options )
|
||||
->withArgument( $beacon )
|
||||
->withArgument( $critical_css )
|
||||
->withArgument( $template_path );
|
||||
$this->getContainer()->add( 'cpcss_admin', 'WP_Rocket\Engine\CriticalPath\Admin\Admin' )
|
||||
->withArgument( $options )
|
||||
->withArgument( $processor_service );
|
||||
$this->getContainer()->share( 'critical_css_admin_subscriber', 'WP_Rocket\Engine\CriticalPath\Admin\Subscriber' )
|
||||
->withArgument( $this->getContainer()->get( 'cpcss_post' ) )
|
||||
->withArgument( $this->getContainer()->get( 'cpcss_settings' ) )
|
||||
->withArgument( $this->getContainer()->get( 'cpcss_admin' ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Rocket\Engine\CriticalPath;
|
||||
|
||||
trait TransientTrait {
|
||||
/**
|
||||
* Updates CPCSS running transient with status notices.
|
||||
*
|
||||
* @since 3.6
|
||||
*
|
||||
* @param array $transient Transient to be updated.
|
||||
* @param string $item_path Path for processed item.
|
||||
* @param bool $mobile If this request is for mobile cpcss.
|
||||
* @param string $message CPCSS reply message.
|
||||
* @param bool/string $success CPCSS success or failure.
|
||||
* @return void
|
||||
*/
|
||||
private function update_running_transient( $transient, $item_path, $mobile, $message, $success ) {
|
||||
$path = ! (bool) $mobile ? $item_path : str_replace( '-mobile.css', '.css', $item_path );
|
||||
|
||||
$transient['items'][ $path ]['status'][ ! (bool) $mobile ? 'nonmobile' : 'mobile' ]['message'] = $message;
|
||||
$transient['items'][ $path ]['status'][ ! (bool) $mobile ? 'nonmobile' : 'mobile' ]['success'] = $success;
|
||||
set_transient( 'rocket_critical_css_generation_process_running', $transient, HOUR_IN_SECONDS );
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user