File: /home/pclast/www/wp-content/plugins/wp-rocket/inc/Engine/CDN/RocketCDN/DataManagerSubscriber.php
<?php
namespace WP_Rocket\Engine\CDN\RocketCDN;
use WP_Rocket\Admin\Options;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Engine\CDN\Context;
use WP_Rocket\Engine\License\API\UserClient;
use WP_Rocket\Engine\Optimization\RegexTrait;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the RocketCDN integration in WP Rocket settings page
*
* @since 3.5
*/
class DataManagerSubscriber implements Subscriber_Interface {
use RegexTrait;
const CRON_EVENT = 'rocketcdn_check_subscription_status_event';
/**
* RocketCDN API Client instance.
*
* @var APIClient
*/
private $api_client;
/**
* CDNOptionsManager instance.
*
* @var CDNOptionsManager
*/
private $cdn_options;
/**
* WP Options API instance
*
* @var Options
*/
private $options_api;
/**
* WP Rocket Options instance
*
* @var Options_Data
*/
private $options;
/**
* UserClient instance
*
* @var UserClient
*/
private $user_client;
/**
* Subscription controller instance.
*
* @var SubscriptionController
*/
private $subscription_controller;
/**
* Constructor
*
* @param APIClient $api_client RocketCDN API Client instance.
* @param CDNOptionsManager $cdn_options CDNOptionsManager instance.
* @param Options_Data $options Options instance.
* @param Options $options_api Options API instance.
* @param UserClient $user_client UserClient instance.
* @param SubscriptionController $subscription_controller SubscriptionController instance.
*/
public function __construct( APIClient $api_client, CDNOptionsManager $cdn_options, Options_Data $options, Options $options_api, UserClient $user_client, SubscriptionController $subscription_controller ) {
$this->api_client = $api_client;
$this->cdn_options = $cdn_options;
$this->options = $options;
$this->options_api = $options_api;
$this->user_client = $user_client;
$this->subscription_controller = $subscription_controller;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @return array
*/
public static function get_subscribed_events() {
return [
'admin_init' => [
[ 'handle_rocketcdn_checkout_parameter' ],
[ 'maybe_retry_activation' ],
],
'wp_ajax_save_rocketcdn_token' => 'update_user_token',
'wp_ajax_rocketcdn_enable' => 'enable',
'wp_ajax_rocketcdn_disable' => 'disable',
'wp_ajax_rocketcdn_process_set' => 'set_process_status',
'wp_ajax_rocketcdn_process_status' => 'get_process_status',
'wp_ajax_rocketcdn_validate_token_cname' => 'validate_token_cname',
self::CRON_EVENT => 'maybe_disable_cdn',
'wp_rocket_upgrade' => [
[ 'refresh_cdn_cname', 10, 2 ],
[ 'refresh_subscription_details_with_update', 10, 2 ],
[ 'maybe_set_rocketcdn_as_cdn_type_on_upgrade', 12, 2 ],
],
'set_transient_wp_rocket_customer_data' => 'maybe_refresh_rocketcdn_details',
];
}
/**
* Updates the RocketCDN user token value
*
* @since 3.5
*
* @return void
*/
public function update_user_token() {
check_ajax_referer( 'rocket-ajax', 'nonce', true );
if ( ! current_user_can( 'rocket_manage_options' ) ) {
wp_send_json_error( 'unauthorized_user' );
}
if ( empty( $_POST['value'] ) ) {
delete_option( 'rocketcdn_user_token' );
wp_send_json_success( 'user_token_deleted' );
}
if ( ! is_string( $_POST['value'] ) ) {
wp_send_json_error( 'invalid_token' );
}
$token = sanitize_key( $_POST['value'] );
if ( 40 !== strlen( $token ) ) {
wp_send_json_error( 'invalid_token_length' );
}
update_option( 'rocketcdn_user_token', $token );
wp_send_json_success( 'user_token_saved' );
}
/**
* Handles the rocketcdn_checkout URL parameter after express checkout.
*
* Detects the URL parameter, refreshes user data to get new RocketCDN credentials,
* enables RocketCDN, and redirects to clean URL.
*
* @since 3.20.5
*
* @return void
*/
public function handle_rocketcdn_checkout_parameter(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking URL parameter for redirect, not processing form data.
if ( ! isset( $_GET['rocketcdn_checkout'] ) ) {
return;
}
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
// Refresh user data to get fresh RocketCDN credentials.
$this->user_client->flush_cache();
$user_data = $this->user_client->get_user_data();
if ( false === $user_data || empty( $user_data->rocketcdn->cdn_token ) || empty( $user_data->rocketcdn->cdn_url ) || empty( $user_data->rocketcdn->rocketcdn_website_id ) ) {
$this->remove_query_parameter_and_redirect();
return;
}
// Sanitize and store values to avoid multiple sanitization.
$token = sanitize_key( $user_data->rocketcdn->cdn_token );
$cdn_url = esc_url_raw( $user_data->rocketcdn->cdn_url );
$website_id = (int) $user_data->rocketcdn->rocketcdn_website_id;
// Validate token length (must be 40 characters).
if ( 40 !== strlen( $token ) ) {
$this->remove_query_parameter_and_redirect();
return;
}
// Activate the subscription via RocketCDN API.
$activation_result = $this->api_client->activate_subscription( $token, $website_id );
// Save token and enable CDN.
$this->cdn_options->save_token( $token );
$this->cdn_options->enable();
// Schedule subscription check.
$subscription = $this->api_client->get_subscription_data();
$this->schedule_subscription_check( $subscription );
$this->remove_query_parameter_and_redirect();
}
/**
* Removes the query parameter and redirects to clean URL.
*
* @since 3.20.5
*
* @return void
*/
private function remove_query_parameter_and_redirect(): void {
$redirect_url = remove_query_arg( 'rocketcdn_checkout' );
wp_safe_redirect( $redirect_url );
rocket_get_constant( 'WP_ROCKET_IS_TESTING', false ) ? wp_die() : exit;
}
/**
* Ajax callback to enable RocketCDN
*
* @since 3.5
*
* @return void
*/
public function enable() {
check_ajax_referer( 'rocket-ajax', 'nonce', true );
$data = [
'process' => 'subscribe',
];
if ( ! current_user_can( 'rocket_manage_options' ) ) {
$data['message'] = 'unauthorized_user';
wp_send_json_error( $data );
}
if ( empty( $_POST['cdn_url'] ) ) {
$data['message'] = 'cdn_url_empty';
wp_send_json_error( $data );
}
$cdn_url = filter_var( wp_unslash( $_POST['cdn_url'] ), FILTER_VALIDATE_URL );
if ( ! $cdn_url ) {
$data['message'] = 'cdn_url_invalid_format';
wp_send_json_error( $data );
}
$this->cdn_options->enable();
$subscription = $this->api_client->get_subscription_data();
$this->schedule_subscription_check( $subscription );
$this->delete_process();
$data['message'] = 'rocketcdn_enabled';
wp_send_json_success( $data );
}
/**
* AJAX callback to disable RocketCDN
*
* @since 3.5
*
* @return void
*/
public function disable() {
check_ajax_referer( 'rocket-ajax', 'nonce', true );
$data = [
'process' => 'unsubscribe',
];
if ( ! current_user_can( 'rocket_manage_options' ) ) {
$data['message'] = 'unauthorized_user';
wp_send_json_error( $data );
}
$this->cdn_options->disable();
$timestamp = wp_next_scheduled( self::CRON_EVENT );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, self::CRON_EVENT );
}
$this->delete_process();
$data['message'] = 'rocketcdn_disabled';
wp_send_json_success( $data );
}
/**
* Delete the option tracking the RocketCDN process state
*
* @since 3.5
*
* @return void
*/
private function delete_process() {
delete_option( 'rocketcdn_process' );
}
/**
* Set the RocketCDN subscription process status
*
* @since 3.5
*
* @return void
*/
public function set_process_status() {
check_ajax_referer( 'rocket-ajax', 'nonce', true );
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
if ( empty( $_POST['status'] ) ) {
return;
}
$status = filter_var( $_POST['status'], FILTER_VALIDATE_BOOLEAN ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Used as a boolean.
if ( false === $status ) {
delete_option( 'rocketcdn_process' );
return;
}
update_option( 'rocketcdn_process', $status );
}
/**
* Check for RocketCDN subscription process status
*
* @since 3.5
*
* @return void
*/
public function get_process_status() {
check_ajax_referer( 'rocket-ajax', 'nonce', true );
if ( ! current_user_can( 'rocket_manage_options' ) ) {
wp_send_json_error();
}
if ( get_option( 'rocketcdn_process' ) ) {
wp_send_json_success();
}
wp_send_json_error();
}
/**
* Cron job to disable CDN if the subscription expired
*
* @since 3.5
*
* @return void
*/
public function maybe_disable_cdn() {
delete_transient( 'rocketcdn_status' );
$subscription = $this->api_client->get_subscription_data();
if ( rocket_get_constant( 'WP_ROCKET_IS_TESTING', false ) ) {
$subscription = apply_filters( 'rocket_pre_get_subscription_data', $subscription );
}
if ( 'running' === $subscription['subscription_status'] ) {
$this->schedule_subscription_check( $subscription );
return;
}
// If subscription is not running and plan type is not free, clear cache.
if ( $this->subscription_controller->is_free() ) {
/**
* Fires when the rocketcdn subscription is not running.
*/
do_action( 'rocketcdn_free_plan_subscription_expired' );
}
$this->cdn_options->disable();
}
/**
* Validates and updates the token and cname from RocketCDN Iframe.
*
* @return void
*/
public function validate_token_cname() {
check_ajax_referer( 'rocket-ajax', 'nonce', true );
$data = [];
if ( ! current_user_can( 'rocket_manage_options' ) ) {
$data['message'] = 'unauthorized_user';
wp_send_json_error( $data );
}
if ( empty( $_POST['cdn_url'] ) || empty( $_POST['cdn_token'] ) ) {
$data['message'] = 'cdn_values_empty';
wp_send_json_error( $data );
}
$token = sanitize_key( $_POST['cdn_token'] );
$cdn_url = filter_var( wp_unslash( $_POST['cdn_url'] ), FILTER_VALIDATE_URL );
if ( ! $cdn_url ) {
$data['message'] = 'cdn_url_invalid_format';
wp_send_json_error( $data );
}
if ( 40 !== strlen( $token ) ) {
$data['message'] = 'invalid_token_length';
wp_send_json_error( $data );
}
$current_token = get_option( 'rocketcdn_user_token' );
$current_cname = $this->cdn_options->get_cdn_cnames();
if ( ! empty( $current_token ) ) {
$data['message'] = 'token_already_set';
wp_send_json_error( $data );
}
update_option( 'rocketcdn_user_token', $token );
$this->cdn_options->enable();
$data['message'] = 'token_updated_successfully';
wp_send_json_success( $data );
}
/**
* Schedule the next cron subscription check
*
* @since 3.5
*
* @param array $subscription Array containing the subscription data.
* @return void
*/
private function schedule_subscription_check( $subscription ) {
$timestamp = strtotime( $subscription['subscription_next_date_update'] ) + strtotime( '+2 days' );
if ( ! wp_next_scheduled( self::CRON_EVENT ) ) {
wp_schedule_single_event( $timestamp, self::CRON_EVENT );
}
}
/**
* Retries RocketCDN activation when subscription is inactive but has a cdn_url.
*
* This handles the case where site activation failed after checkout.
* When is_active is false but cdn_url is not empty, it means the subscription
* exists but this website isn't activated. We retry the activation automatically.
*
* @return void
*/
public function maybe_retry_activation(): void {
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
if ( $this->subscription_controller->is_subscription_creation_loading() ) {
return;
}
$token = get_option( 'rocketcdn_user_token' );
// If token is not saved locally, try to get it from user endpoint.
if ( empty( $token ) ) {
$user_data = $this->user_client->get_user_data();
if ( false === $user_data || empty( $user_data->rocketcdn->cdn_token ) ) {
return;
}
$token = sanitize_key( $user_data->rocketcdn->cdn_token );
}
// Validate token length (should be exactly 40 chars).
if ( 40 !== strlen( (string) $token ) ) {
return;
}
// Check cached subscription data first to avoid unnecessary API calls.
$subscription_data = $this->api_client->get_subscription_data();
// Only retry when: is_active is false AND cdn_url is not empty.
if ( $subscription_data['is_active'] || empty( $subscription_data['cdn_url'] ) ) {
return;
}
if ( empty( $subscription_data['website_id'] ) ) {
return;
}
// Retry the activation.
$activation_result = $this->api_client->activate_subscription( $token, $subscription_data['website_id'] );
if ( is_wp_error( $activation_result ) ) {
return;
}
// Refresh subscription data after successful activation.
delete_transient( 'rocketcdn_status' );
$subscription = $this->api_client->get_subscription_data();
// Guard: only enable CDN and schedule check when subscription is active with a valid CDN URL.
if ( empty( $subscription['is_active'] ) || empty( $subscription['cdn_url'] ) ) {
return;
}
// Save token if not already saved (handles case where token came from user endpoint).
$saved_token = get_option( 'rocketcdn_user_token' );
if ( empty( $saved_token ) ) {
$this->cdn_options->save_token( $token );
}
// Enable CDN and schedule check.
$this->cdn_options->enable();
$this->schedule_subscription_check( $subscription );
}
/**
* Upgrade callback.
*
* @param string $new_version Plugin new version.
* @param string $old_version Plugin old version.
* @return void
*/
public function refresh_cdn_cname( $new_version, $old_version ): void {
if ( version_compare( $old_version, '3.17.3', '>=' ) ) {
return;
}
$cdn_cnames = $this->options->get( 'cdn_cnames', [] );
if ( empty( $cdn_cnames ) ) {
return;
}
$subscription_data = $this->api_client->get_subscription_data();
if ( ! $subscription_data['is_active'] || empty( $subscription_data['cdn_url'] ) ) {
return;
}
$cdn_matches = $this->find( 'https:\/\/(?<cdn_id>[a-zA-Z0-9]{8})\.rocketcdn\.me', $cdn_cnames[0] );
if ( empty( $cdn_matches ) || empty( $cdn_matches[0]['cdn_id'] ) ) {
return;
}
$this->options_api->set( 'rocketcdn_old_url', $cdn_cnames[0] );
$cdn_cnames[0] = str_replace( $cdn_matches[0]['cdn_id'], $cdn_matches[0]['cdn_id'] . '.delivery', $cdn_cnames[0] );
$this->options->set( 'cdn_cnames', $cdn_cnames );
$this->options_api->set( 'settings', $this->options->get_options() );
}
/**
* Refresh subscription details with update to v3.22.0.2.
*
* @param string $new_version Plugin new version.
* @param string $old_version Plugin old version.
* @return void
*/
public function refresh_subscription_details_with_update( $new_version, $old_version ) {
if ( version_compare( $old_version, '3.22.0.2', '>=' ) ) {
return;
}
$this->user_client->flush_cache();// Flush customer details cache to set the transient and then refresh rocketcdn subscription details.
$this->cdn_options->flush_subscription_cache();
}
/**
* Save the token with any change in rocket customer details in case it's not saved.
*
* @param object $user_data Rocket customer data.
* @return void
*/
public function maybe_refresh_rocketcdn_details( $user_data ) {
if ( ! empty( $user_data->rocketcdn->cdn_token ) && ! $this->cdn_options->has_token() ) {
$token = sanitize_key( (string) $user_data->rocketcdn->cdn_token );
if ( 40 !== strlen( $token ) ) {
return;
}
$this->cdn_options->save_token( $token );
}
$this->cdn_options->flush_subscription_cache();
}
/**
* Sets cdn_type to rocketcdn when upgrading from a version affected by the grace period bug.
*
* @since 3.22.0.2
*
* @param string $new_version New plugin version.
* @param string $old_version Previously installed plugin version.
*
* @return void
*/
public function maybe_set_rocketcdn_as_cdn_type_on_upgrade( string $new_version, string $old_version ) {
if ( version_compare( $old_version, '3.22.0.2', '>=' ) ) {
return;
}
if ( ! $this->subscription_controller->is_in_grace_period() ) {
return;
}
$current_options = $this->options_api->get( 'settings', [] );
$current_options['cdn_type'] = Context::ROCKETCDN_TYPE;
$this->options_api->set( 'settings', $current_options );
}
}