add wp-rocket

This commit is contained in:
nguyen dung
2022-02-18 19:09:35 +07:00
parent 39b8cb3612
commit 3110d00ee7
927 changed files with 271703 additions and 2 deletions

View File

@@ -0,0 +1,371 @@
<?php
namespace WP_Rocket\Engine\CDN;
use WP_Rocket\Admin\Options_Data;
/**
* CDN class
*
* @since 3.4
*/
class CDN {
/**
* WP Rocket Options instance
*
* @var Options_Data
*/
private $options;
/**
* Home URL host
*
* @var string
*/
private $home_host;
/**
* Constructor
*
* @param Options_Data $options WP Rocket Options instance.
*/
public function __construct( Options_Data $options ) {
$this->options = $options;
}
/**
* Search & Replace URLs with the CDN URLs in the provided content
*
* @since 3.4
*
* @param string $html HTML content.
* @return string
*/
public function rewrite( $html ) {
$pattern = '#[("\']\s*(?<url>(?:(?:https?:|)' . preg_quote( $this->get_base_url(), '#' ) . ')\/(?:(?:(?:' . $this->get_allowed_paths() . ')[^"\',)]+))|\/[^/](?:[^"\')\s>]+\.[[:alnum:]]+))\s*["\')]#i';
return preg_replace_callback(
$pattern,
function( $matches ) {
return str_replace( $matches['url'], $this->rewrite_url( $matches['url'] ), $matches[0] );
},
$html
);
}
/**
* Rewrites URLs in a srcset attribute using the CDN URL
*
* @since 3.4.0.4
*
* @param string $html HTML content.
* @return string
*/
public function rewrite_srcset( $html ) {
$pattern = '#\s+(?:data-lazy-|data-)?srcset\s*=\s*["\']\s*(?<sources>[^"\',\s]+\.[^"\',\s]+(?:\s+\d+[wx])?(?:\s*,\s*[^"\',\s]+\.[^"\',\s]+\s+\d+[wx])*)\s*["\']#i';
if ( ! preg_match_all( $pattern, $html, $srcsets, PREG_SET_ORDER ) ) {
return $html;
}
foreach ( $srcsets as $srcset ) {
$sources = explode( ',', $srcset['sources'] );
$sources = array_unique( array_map( 'trim', $sources ) );
$cdn_srcset = $srcset['sources'];
foreach ( $sources as $source ) {
$url = preg_split( '#\s+#', trim( $source ) );
$cdn_srcset = str_replace( $url[0], $this->rewrite_url( $url[0] ), $cdn_srcset );
}
$cdn_srcsets = str_replace( $srcset['sources'], $cdn_srcset, $srcset[0] );
$html = str_replace( $srcset[0], $cdn_srcsets, $html );
}
return $html;
}
/**
* Rewrites an URL with the CDN URL
*
* @since 3.4
*
* @param string $url Original URL.
* @return string
*/
public function rewrite_url( $url ) {
if ( ! $this->options->get( 'cdn', 0 ) ) {
return $url;
}
if ( $this->is_excluded( $url ) ) {
return $url;
}
$cdn_urls = $this->get_cdn_urls( $this->get_zones_for_url( $url ) );
if ( ! $cdn_urls ) {
return $url;
}
$parsed_url = wp_parse_url( $url );
$cdn_url = untrailingslashit( $cdn_urls[ ( abs( crc32( $parsed_url['path'] ) ) % count( $cdn_urls ) ) ] );
if ( ! isset( $parsed_url['host'] ) ) {
return rocket_add_url_protocol( $cdn_url . '/' . ltrim( $url, '/' ) );
}
$home_host = $this->get_home_host();
if ( ! isset( $parsed_url['scheme'] ) ) {
return str_replace( $home_host, rocket_remove_url_protocol( $cdn_url ), $url );
}
$home_url = [
'http://' . $home_host,
'https://' . $home_host,
];
return str_replace( $home_url, rocket_add_url_protocol( $cdn_url ), $url );
}
/**
* Rewrites URLs to CDN URLs in CSS content
*
* @since 3.4
*
* @param string $content CSS content.
* @return string
*/
public function rewrite_css_properties( $content ) {
if ( ! preg_match_all( '#url\(\s*(\'|")?\s*(?![\'"]?data)(?<url>(?:https?:|)' . preg_quote( $this->get_base_url(), '#' ) . '\/[^"|\'|\)|\s]+)\s*#i', $content, $matches, PREG_SET_ORDER ) ) {
return $content;
}
foreach ( $matches as $property ) {
/**
* Filters the URL of the CSS property
*
* @since 2.8
*
* @param string $url URL of the CSS property.
*/
$cdn_url = $this->rewrite_url( apply_filters( 'rocket_cdn_css_properties_url', $property['url'] ) );
$replacement = str_replace( $property['url'], $cdn_url, $property[0] );
$content = str_replace( $property[0], $replacement, $content );
}
return $content;
}
/**
* Get all CDN URLs for one or more zones.
*
* @since 2.1
* @since 3.0 Don't check for WP Rocket CDN option activated to be able to use the function on Hosting with CDN auto-enabled.
*
* @param array $zones List of zones. Default is [ 'all' ].
* @return array
*/
public function get_cdn_urls( $zones = [ 'all' ] ) {
$hosts = [];
$zones = (array) $zones;
$cdn_urls = $this->options->get( 'cdn_cnames', [] );
if ( $cdn_urls ) {
$cdn_zones = $this->options->get( 'cdn_zone', [] );
foreach ( $cdn_urls as $k => $urls ) {
if ( ! in_array( $cdn_zones[ $k ], $zones, true ) ) {
continue;
}
$urls = explode( ',', $urls );
$urls = array_map( 'trim', $urls );
foreach ( $urls as $url ) {
$hosts[] = $url;
}
}
}
/**
* Filter all CDN URLs.
*
* @since 2.7
* @since 3.4 Added $zone parameter.
*
* @param array $hosts List of CDN URLs.
* @param array $zones List of zones. Default is [ 'all' ].
*/
$hosts = (array) apply_filters( 'rocket_cdn_cnames', $hosts, $zones );
$hosts = array_filter( $hosts );
$hosts = array_flip( array_flip( $hosts ) );
$hosts = array_values( $hosts );
return $hosts;
}
/**
* Gets the base URL for the website
*
* @since 3.4
* @author Remy Perona
*
* @return string
*/
private function get_base_url() {
return '//' . $this->get_home_host();
}
/**
* Gets the allowed paths as a regex pattern for the CDN rewrite
*
* @since 3.4
* @author Remy Perona
*
* @return string
*/
private function get_allowed_paths() {
$wp_content_dirname = ltrim( trailingslashit( wp_parse_url( content_url(), PHP_URL_PATH ) ), '/' );
$wp_includes_dirname = ltrim( trailingslashit( wp_parse_url( includes_url(), PHP_URL_PATH ) ), '/' );
$upload_dirname = '';
$uploads_info = wp_upload_dir();
if ( ! empty( $uploads_info['baseurl'] ) ) {
$upload_dirname = '|' . ltrim( trailingslashit( wp_parse_url( $uploads_info['baseurl'], PHP_URL_PATH ) ), '/' );
}
return $wp_content_dirname . $upload_dirname . '|' . $wp_includes_dirname;
}
/**
* Checks if the provided URL can be rewritten with the CDN URL
*
* @since 3.4
* @author Remy Perona
*
* @param string $url URL to check.
* @return boolean
*/
public function is_excluded( $url ) {
$path = wp_parse_url( $url, PHP_URL_PATH );
$excluded_extensions = [
'php',
'html',
'htm',
];
if ( in_array( pathinfo( $path, PATHINFO_EXTENSION ), $excluded_extensions, true ) ) {
return true;
}
if ( ! $path ) {
return true;
}
if ( '/' === $path ) {
return true;
}
if ( preg_match( '#^(' . $this->get_excluded_files( '#' ) . ')$#', $path ) ) {
return true;
}
return false;
}
/**
* Gets the home URL host
*
* @since 3.5.5
*
* @return string
*/
private function get_home_host() {
if ( empty( $this->home_host ) ) {
$this->home_host = wp_parse_url( home_url(), PHP_URL_HOST );
}
return $this->home_host;
}
/**
* Gets the CDN zones for the provided URL
*
* @since 3.4
* @author Remy Perona
*
* @param string $url URL to check.
* @return array
*/
private function get_zones_for_url( $url ) {
$zones = [ 'all' ];
$ext = pathinfo( wp_parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION );
$image_types = [
'jpg',
'jpeg',
'jpe',
'png',
'gif',
'webp',
'bmp',
'tiff',
'svg',
];
if ( 'css' === $ext || 'js' === $ext ) {
$zones[] = 'css_and_js';
}
if ( 'css' === $ext ) {
$zones[] = 'css';
}
if ( 'js' === $ext ) {
$zones[] = 'js';
}
if ( in_array( $ext, $image_types, true ) ) {
$zones[] = 'images';
}
return $zones;
}
/**
* Get all files we don't allow to get in CDN.
*
* @since 2.5
*
* @param string $delimiter RegEx delimiter.
* @return string A pipe-separated list of excluded files.
*/
private function get_excluded_files( $delimiter ) {
$files = $this->options->get( 'cdn_reject_files', [] );
/**
* Filter the excluded files.
*
* @since 2.5
*
* @param array $files List of excluded files.
*/
$files = (array) apply_filters( 'rocket_cdn_reject_files', $files );
$files = array_filter( $files );
if ( ! $files ) {
return '';
}
$files = array_flip( array_flip( $files ) );
$files = array_map(
function ( $file ) use ( $delimiter ) {
return str_replace( $delimiter, '\\' . $delimiter, $file );
},
$files
);
return implode( '|', $files );
}
}

View File

@@ -0,0 +1,273 @@
<?php
namespace WP_Rocket\Engine\CDN\RocketCDN;
use WP_Error;
/**
* Class to Interact with the RocketCDN API
*/
class APIClient {
const ROCKETCDN_API = 'https://rocketcdn.me/api/';
/**
* Gets current RocketCDN subscription data from cache if it exists
*
* Else do a request to the API to get fresh data
*
* @since 3.5
*
* @return array
*/
public function get_subscription_data() {
$status = get_transient( 'rocketcdn_status' );
if ( false !== $status ) {
return $status;
}
return $this->get_remote_subscription_data();
}
/**
* Gets fresh RocketCDN subscription data from the API
*
* @since 3.5
*
* @return array
*/
private function get_remote_subscription_data() {
$default = [
'id' => 0,
'is_active' => false,
'cdn_url' => '',
'subscription_next_date_update' => 0,
'subscription_status' => 'cancelled',
];
$token = get_option( 'rocketcdn_user_token' );
if ( empty( $token ) ) {
return $default;
}
$args = [
'headers' => [
'Authorization' => 'Token ' . $token,
],
];
$response = wp_remote_get(
self::ROCKETCDN_API . 'website/search/?url=' . home_url(),
$args
);
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
$this->set_status_transient( $default, 3 * MINUTE_IN_SECONDS );
return $default;
}
$data = wp_remote_retrieve_body( $response );
if ( empty( $data ) ) {
$this->set_status_transient( $default, 3 * MINUTE_IN_SECONDS );
return $default;
}
$data = json_decode( $data, true );
$data = array_intersect_key( (array) $data, $default );
$data = array_merge( $default, $data );
$this->set_status_transient( $data, WEEK_IN_SECONDS );
return $data;
}
/**
* Sets the RocketCDN status transient with the provided value
*
* @since 3.5
*
* @param array $value Transient value.
* @param int $duration Transient duration.
* @return void
*/
private function set_status_transient( $value, $duration ) {
set_transient( 'rocketcdn_status', $value, $duration );
}
/**
* Gets pricing & promotion data for RocketCDN from cache if it exists
*
* Else do a request to the API to get fresh data
*
* @since 3.5
*
* @return array
*/
public function get_pricing_data() {
$pricing = get_transient( 'rocketcdn_pricing' );
if ( false !== $pricing ) {
return $pricing;
}
return $this->get_remote_pricing_data();
}
/**
* Gets fresh pricing & promotion data for RocketCDN
*
* @since 3.5
*
* @return array|WP_Error
*/
private function get_remote_pricing_data() {
$response = wp_remote_get( self::ROCKETCDN_API . 'pricing' );
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return $this->get_wp_error();
}
$data = wp_remote_retrieve_body( $response );
if ( empty( $data ) ) {
return $this->get_wp_error();
}
$data = json_decode( $data, true );
set_transient( 'rocketcdn_pricing', $data, 6 * HOUR_IN_SECONDS );
return $data;
}
/**
* Gets a new WP_Error instance
*
* @since 3.5
*
* @return WP_Error
*/
private function get_wp_error() {
return new WP_Error( 'rocketcdn_error', __( 'RocketCDN is not available at the moment. Please retry later', 'rocket' ) );
}
/**
* Sends a request to the API to purge the CDN cache
*
* @since 3.5
*
* @return array
*/
public function purge_cache_request() {
$subscription = $this->get_subscription_data();
$status = 'error';
if ( ! isset( $subscription['id'] ) || 0 === $subscription['id'] ) {
return [
'status' => $status,
'message' => __( 'RocketCDN cache purge failed: Missing identifier parameter.', 'rocket' ),
];
}
$token = get_option( 'rocketcdn_user_token' );
if ( empty( $token ) ) {
return [
'status' => $status,
'message' => __( 'RocketCDN cache purge failed: Missing user token.', 'rocket' ),
];
}
$args = [
'method' => 'DELETE',
'headers' => [
'Authorization' => 'Token ' . $token,
],
];
$response = wp_remote_request(
self::ROCKETCDN_API . 'website/' . $subscription['id'] . '/purge/',
$args
);
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return [
'status' => $status,
'message' => __( 'RocketCDN cache purge failed: The API returned an unexpected response code.', 'rocket' ),
];
}
$data = wp_remote_retrieve_body( $response );
if ( empty( $data ) ) {
return [
'status' => $status,
'message' => __( 'RocketCDN cache purge failed: The API returned an empty response.', 'rocket' ),
];
}
$data = json_decode( $data );
if ( ! isset( $data->success ) ) {
return [
'status' => $status,
'message' => __( 'RocketCDN cache purge failed: The API returned an unexpected response.', 'rocket' ),
];
}
if ( ! $data->success ) {
return [
'status' => $status,
'message' => sprintf(
// translators: %s = message returned by the API.
__( 'RocketCDN cache purge failed: %s.', 'rocket' ),
$data->message
),
];
}
return [
'status' => 'success',
'message' => __( 'RocketCDN cache purge successful.', 'rocket' ),
];
}
/**
* Filter the arguments used in an HTTP request, to make sure our user token has not been overwritten
* by some other plugin.
*
* @since 3.5
*
* @param array $args An array of HTTP request arguments.
* @param string $url The request URL.
* @return array
*/
public function preserve_authorization_token( $args, $url ) {
if ( strpos( $url, self::ROCKETCDN_API ) === false ) {
return $args;
}
if ( empty( $args['headers']['Authorization'] ) && self::ROCKETCDN_API . 'pricing' === $url ) {
return $args;
}
$token = get_option( 'rocketcdn_user_token' );
if ( empty( $token ) ) {
return $args;
}
$value = 'token ' . $token;
if ( isset( $args['headers']['Authorization'] ) && $value === $args['headers']['Authorization'] ) {
return $args;
}
$args['headers']['Authorization'] = $value;
return $args;
}
}

View File

@@ -0,0 +1,277 @@
<?php
namespace WP_Rocket\Engine\CDN\RocketCDN;
use WP_Rocket\Abstract_Render;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Engine\Admin\Beacon\Beacon;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the RocketCDN integration in WP Rocket settings page
*
* @since 3.5
*/
class AdminPageSubscriber extends Abstract_Render implements Subscriber_Interface {
/**
* RocketCDN API Client instance.
*
* @var APIClient
*/
private $api_client;
/**
* WP Rocket options instance
*
* @var Options_Data
*/
private $options;
/**
* Beacon instance
*
* @var Beacon
*/
private $beacon;
/**
* Constructor
*
* @param APIClient $api_client RocketCDN API Client instance.
* @param Options_Data $options WP Rocket options instance.
* @param Beacon $beacon Beacon instance.
* @param string $template_path Path to the templates.
*/
public function __construct( APIClient $api_client, Options_Data $options, Beacon $beacon, $template_path ) {
parent::__construct( $template_path );
$this->api_client = $api_client;
$this->options = $options;
$this->beacon = $beacon;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'rocket_dashboard_after_account_data' => 'display_rocketcdn_status',
'rocket_after_cdn_sections' => 'display_manage_subscription',
'rocket_cdn_settings_fields' => 'rocketcdn_field',
'admin_post_rocket_purge_rocketcdn' => 'purge_cdn_cache',
'rocket_settings_page_footer' => 'add_subscription_modal',
'http_request_args' => [ 'preserve_authorization_token', PHP_INT_MAX, 2 ],
];
}
/**
* Displays the RocketCDN section on the dashboard tab
*
* @since 3.5
*
* @return void
*/
public function display_rocketcdn_status() {
if ( $this->is_white_label_account() ) {
return;
}
$subscription_data = $this->api_client->get_subscription_data();
if ( 'running' === $subscription_data['subscription_status'] ) {
$label = __( 'Next Billing Date', 'rocket' );
$status_class = ' wpr-isValid';
$container_class = '';
$status_text = date_i18n( get_option( 'date_format' ), strtotime( $subscription_data['subscription_next_date_update'] ) );
$is_active = true;
} elseif ( 'cancelled' === $subscription_data['subscription_status'] ) {
$label = '';
$status_class = ' wpr-isInvalid';
$container_class = ' wpr-flex--egal';
$status_text = __( 'No Subscription', 'rocket' );
$is_active = false;
}
$data = [
'is_live_site' => rocket_is_live_site(),
'container_class' => $container_class,
'label' => $label,
'status_class' => $status_class,
'status_text' => $status_text,
'is_active' => $is_active,
];
echo $this->generate( 'dashboard-status', $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Dynamic content is properly escaped in the view.
}
/**
* Adds the RocketCDN fields to the CDN section
*
* @since 3.5
*
* @param array $fields CDN settings fields.
*
* @return array
*/
public function rocketcdn_field( $fields ) {
if ( $this->is_white_label_account() ) {
return $fields;
}
$subscription_data = $this->api_client->get_subscription_data();
if ( 'running' !== $subscription_data['subscription_status'] ) {
return $fields;
}
$helper_text = __( 'Your RocketCDN subscription is currently active.', 'rocket' );
$cdn_cnames = $this->options->get( 'cdn_cnames', [] );
if ( empty( $cdn_cnames ) || $cdn_cnames[0] !== $subscription_data['cdn_url'] ) {
$helper_text = sprintf(
// translators: %1$s = opening <code> tag, %2$s = CDN URL, %3$s = closing </code> tag.
__( 'To use RocketCDN, replace your CNAME with %1$s%2$s%3$s.', 'rocket' ),
'<code>',
$subscription_data['cdn_url'],
'</code>'
);
}
$beacon = $this->beacon->get_suggest( 'rocketcdn' );
$more_info = sprintf(
// translators: %1$is = opening link tag, %2$s = closing link tag.
__( '%1$sMore Info%2$s', 'rocket' ),
'<a href="' . esc_url( $beacon['url'] ) . '" data-beacon-article="' . esc_attr( $beacon['id'] ) . '" rel="noopener noreferrer" target="_blank">',
'</a>'
);
$fields['cdn_cnames'] = [
'type' => 'rocket_cdn',
'label' => __( 'CDN CNAME(s)', 'rocket' ),
'description' => __( 'Specify the CNAME(s) below', 'rocket' ),
'helper' => $helper_text . ' ' . $more_info,
'default' => '',
'section' => 'cnames_section',
'page' => 'page_cdn',
'beacon' => [
'url' => $beacon['url'],
'id' => $beacon['id'],
],
];
return $fields;
}
/**
* Displays the button to open the subscription modal
*
* @since 3.5
*
* @return void
*/
public function display_manage_subscription() {
if ( $this->is_white_label_account() ) {
return;
}
if ( ! rocket_is_live_site() ) {
return;
}
$subscription_data = $this->api_client->get_subscription_data();
if ( 'running' !== $subscription_data['subscription_status'] ) {
return;
}
?>
<p class="wpr-rocketcdn-subscription">
<button class="wpr-rocketcdn-open" data-micromodal-trigger="wpr-rocketcdn-modal"><?php esc_html_e( 'Manage Subscription', 'rocket' ); ?></button>
</p>
<?php
}
/**
* Purges the CDN cache and store the response in a transient.
*
* @since 3.5
*
* @return void
*/
public function purge_cdn_cache() {
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'rocket_purge_rocketcdn' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
wp_nonce_ays( '' );
}
if ( ! current_user_can( 'rocket_manage_options' ) ) {
wp_die();
}
set_transient( 'rocketcdn_purge_cache_response', $this->api_client->purge_cache_request(), HOUR_IN_SECONDS );
wp_safe_redirect( esc_url_raw( wp_get_referer() ) );
rocket_get_constant( 'WP_ROCKET_IS_TESTING', false ) ? wp_die() : exit;
}
/**
* Adds the subscription modal on the WP Rocket settings page
*
* @since 3.5
*
* @return void
*/
public function add_subscription_modal() {
if ( $this->is_white_label_account() ) {
return;
}
if ( ! rocket_is_live_site() ) {
return;
}
$iframe_src = add_query_arg(
[
'website' => home_url(),
'callback' => rest_url( 'wp-rocket/v1/rocketcdn/' ),
],
rocket_get_constant( 'WP_ROCKET_WEB_MAIN' ) . 'cdn/iframe'
);
?>
<div class="wpr-rocketcdn-modal" id="wpr-rocketcdn-modal" aria-hidden="true">
<div class="wpr-rocketcdn-modal__overlay" tabindex="-1">
<div class="wpr-rocketcdn-modal__container" role="dialog" aria-modal="true" aria-labelledby="wpr-rocketcdn-modal-title">
<div id="wpr-rocketcdn-modal-content">
<iframe id="rocketcdn-iframe" src="<?php echo esc_url( $iframe_src ); ?>" width="674" height="425"></iframe>
</div>
</div>
</div>
</div>
<?php
}
/**
* Filter the arguments used in an HTTP request, to make sure our user token has not been overwritten
* by some other plugin.
*
* @since 3.5
*
* @param array $args An array of HTTP request arguments.
* @param string $url The request URL.
* @return array
*/
public function preserve_authorization_token( $args, $url ) {
return $this->api_client->preserve_authorization_token( $args, $url );
}
/**
* Checks if white label is enabled
*
* @since 3.6
*
* @return bool
*/
private function is_white_label_account() {
return (bool) rocket_get_constant( 'WP_ROCKET_WHITE_LABEL_ACCOUNT' );
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace WP_Rocket\Engine\CDN\RocketCDN;
use WP_Rocket\Admin\Options;
use WP_Rocket\Admin\Options_Data;
/**
* Manager for WP Rocket CDN options
*
* @since 3.5
*/
class CDNOptionsManager {
/**
* WP Options API instance
*
* @var Options
*/
private $options_api;
/**
* WP Rocket Options instance
*
* @var Options_Data
*/
private $options;
/**
* Constructor
*
* @param Options $options_api WP Options API instance.
* @param Options_Data $options WP Rocket Options instance.
*/
public function __construct( Options $options_api, Options_Data $options ) {
$this->options_api = $options_api;
$this->options = $options;
}
/**
* Enable CDN option, save CDN URL & delete RocketCDN status transient
*
* @since 3.5
*
* @param string $cdn_url CDN URL.
* @return void
*/
public function enable( $cdn_url ) {
$this->options->set( 'cdn', 1 );
$this->options->set( 'cdn_cnames', [ $cdn_url ] );
$this->options->set( 'cdn_zone', [ 'all' ] );
$this->options_api->set( 'settings', $this->options->get_options() );
delete_transient( 'rocketcdn_status' );
rocket_clean_domain();
}
/**
* Disable CDN option, remove CDN URL & user token, delete RocketCDN status transient
*
* @since 3.5
*
* @return void
*/
public function disable() {
$this->options->set( 'cdn', 0 );
$this->options->set( 'cdn_cnames', [] );
$this->options->set( 'cdn_zone', [] );
$this->options_api->set( 'settings', $this->options->get_options() );
delete_option( 'rocketcdn_user_token' );
delete_transient( 'rocketcdn_status' );
rocket_clean_domain();
}
}

View File

@@ -0,0 +1,289 @@
<?php
namespace WP_Rocket\Engine\CDN\RocketCDN;
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 {
const CRON_EVENT = 'rocketcdn_check_subscription_status_event';
/**
* RocketCDN API Client instance.
*
* @var APIClient
*/
private $api_client;
/**
* CDNOptionsManager instance.
*
* @var CDNOptionsManager
*/
private $cdn_options;
/**
* Constructor
*
* @param APIClient $api_client RocketCDN API Client instance.
* @param CDNOptionsManager $cdn_options CDNOptionsManager instance.
*/
public function __construct( APIClient $api_client, CDNOptionsManager $cdn_options ) {
$this->api_client = $api_client;
$this->cdn_options = $cdn_options;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'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',
self::CRON_EVENT => 'maybe_disable_cdn',
];
}
/**
* 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' );
return;
}
if ( empty( $_POST['value'] ) ) {
delete_option( 'rocketcdn_user_token' );
wp_send_json_success( 'user_token_deleted' );
return;
}
if ( ! is_string( $_POST['value'] ) ) {
wp_send_json_error( 'invalid_token' );
return;
}
$token = sanitize_key( $_POST['value'] );
if ( 40 !== strlen( $token ) ) {
wp_send_json_error( 'invalid_token_length' );
return;
}
update_option( 'rocketcdn_user_token', $token );
wp_send_json_success( 'user_token_saved' );
}
/**
* 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 );
return;
}
if ( empty( $_POST['cdn_url'] ) ) {
$data['message'] = 'cdn_url_empty';
wp_send_json_error( $data );
return;
}
$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 );
return;
}
$this->cdn_options->enable( esc_url_raw( $cdn_url ) );
$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 );
return;
}
$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();
return;
}
if ( get_option( 'rocketcdn_process' ) ) {
wp_send_json_success();
return;
}
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;
}
$this->cdn_options->disable();
}
/**
* 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 );
}
}
}

View File

@@ -0,0 +1,300 @@
<?php
namespace WP_Rocket\Engine\CDN\RocketCDN;
use WP_Rocket\Abstract_Render;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the RocketCDN notices on WP Rocket settings page
*
* @since 3.5
*/
class NoticesSubscriber extends Abstract_Render implements Subscriber_Interface {
/**
* RocketCDN API Client instance.
*
* @var APIClient
*/
private $api_client;
/**
* Constructor
*
* @param APIClient $api_client RocketCDN API Client instance.
* @param string $template_path Path to the templates.
*/
public function __construct( APIClient $api_client, $template_path ) {
parent::__construct( $template_path );
$this->api_client = $api_client;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'admin_notices' => [
[ 'promote_rocketcdn_notice' ],
[ 'purge_cache_notice' ],
],
'rocket_before_cdn_sections' => 'display_rocketcdn_cta',
'wp_ajax_toggle_rocketcdn_cta' => 'toggle_cta',
'wp_ajax_rocketcdn_dismiss_notice' => 'dismiss_notice',
'admin_footer' => 'add_dismiss_script',
];
}
/**
* Adds notice to promote RocketCDN on settings page
*
* @since 3.5
*
* @return void
*/
public function promote_rocketcdn_notice() {
if ( $this->is_white_label_account() ) {
return;
}
if ( ! rocket_is_live_site() ) {
return;
}
if ( ! $this->should_display_notice() ) {
return;
}
echo $this->generate( 'promote-notice' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Dynamic content is properly escaped in the view.
}
/**
* Adds inline script to permanently dismissing the RocketCDN promotion notice
*
* @since 3.5
*
* @return void
*/
public function add_dismiss_script() {
if ( $this->is_white_label_account() ) {
return;
}
if ( ! rocket_is_live_site() ) {
return;
}
if ( ! $this->should_display_notice() ) {
return;
}
$nonce = wp_create_nonce( 'rocketcdn_dismiss_notice' );
?>
<script>
window.addEventListener( 'load', function() {
var dismissBtn = document.querySelectorAll( '#rocketcdn-promote-notice .notice-dismiss, #rocketcdn-promote-notice #rocketcdn-learn-more-dismiss' );
dismissBtn.forEach(function(element) {
element.addEventListener( 'click', function( event ) {
var httpRequest = new XMLHttpRequest(),
postData = '';
postData += 'action=rocketcdn_dismiss_notice';
postData += '&nonce=<?php echo esc_attr( $nonce ); ?>';
httpRequest.open( 'POST', '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>' );
httpRequest.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' )
httpRequest.send( postData );
});
});
});
</script>
<?php
}
/**
* Checks if the promotion notice should be displayed
*
* @since 3.5
*
* @return boolean
*/
private function should_display_notice() {
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return false;
}
if ( 'settings_page_wprocket' !== get_current_screen()->id ) {
return false;
}
if ( get_user_meta( get_current_user_id(), 'rocketcdn_dismiss_notice', true ) ) {
return false;
}
$subscription_data = $this->api_client->get_subscription_data();
return 'running' !== $subscription_data['subscription_status'];
}
/**
* Ajax callback to save the dismiss as a user meta
*
* @since 3.5
*
* @return void
*/
public function dismiss_notice() {
check_ajax_referer( 'rocketcdn_dismiss_notice', 'nonce', true );
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
update_user_meta( get_current_user_id(), 'rocketcdn_dismiss_notice', true );
}
/**
* Displays the RocketCDN Call to Action on the CDN tab of WP Rocket settings page
*
* @since 3.5
*
* @return void
*/
public function display_rocketcdn_cta() {
if ( $this->is_white_label_account() ) {
return;
}
if ( ! rocket_is_live_site() ) {
return;
}
$subscription_data = $this->api_client->get_subscription_data();
if ( 'running' === $subscription_data['subscription_status'] ) {
return;
}
$pricing = $this->api_client->get_pricing_data();
$regular_price = '';
$nopromo_variant = '--no-promo';
$cta_small_class = 'wpr-isHidden';
$cta_big_class = '';
if ( get_user_meta( get_current_user_id(), 'rocket_rocketcdn_cta_hidden', true ) ) {
$cta_small_class = '';
$cta_big_class = 'wpr-isHidden';
}
$small_cta_data = [
'container_class' => $cta_small_class,
];
if ( is_wp_error( $pricing ) ) {
$big_cta_data = [
'container_class' => $cta_big_class,
'nopromo_variant' => $nopromo_variant,
'error' => true,
'message' => $pricing->get_error_message(),
];
} else {
$current_price = number_format_i18n( $pricing['monthly_price'], 2 );
$promotion_campaign = '';
$end_date = strtotime( $pricing['end_date'] );
$promotion_end_date = '';
if (
$pricing['is_discount_active']
&&
$end_date > time()
) {
$promotion_campaign = $pricing['discount_campaign_name'];
$regular_price = $current_price;
$current_price = number_format_i18n( $pricing['discounted_price_monthly'], 2 ) . '*';
$nopromo_variant = '';
$promotion_end_date = date_i18n( get_option( 'date_format' ), $end_date );
}
$big_cta_data = [
'container_class' => $cta_big_class,
'promotion_campaign' => $promotion_campaign,
'promotion_end_date' => $promotion_end_date,
'nopromo_variant' => $nopromo_variant,
'regular_price' => $regular_price,
'current_price' => $current_price,
];
}
echo $this->generate( 'cta-small', $small_cta_data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Dynamic content is properly escaped in the view.
echo $this->generate( 'cta-big', $big_cta_data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Dynamic content is properly escaped in the view.
}
/**
* Toggles display of the RocketCDN CTAs on the settings page
*
* @since 3.5
*
* @return void
*/
public function toggle_cta() {
check_ajax_referer( 'rocket-ajax', 'nonce', true );
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
if ( ! isset( $_POST['status'] ) ) {
return;
}
if ( 'big' === $_POST['status'] ) {
delete_user_meta( get_current_user_id(), 'rocket_rocketcdn_cta_hidden' );
} elseif ( 'small' === $_POST['status'] ) {
update_user_meta( get_current_user_id(), 'rocket_rocketcdn_cta_hidden', true );
}
}
/**
* Displays a notice after purging the RocketCDN cache.
*
* @since 3.5
*
* @return void
*/
public function purge_cache_notice() {
if ( ! current_user_can( 'rocket_manage_options' ) ) {
return;
}
if ( 'settings_page_wprocket' !== get_current_screen()->id ) {
return;
}
$purge_response = get_transient( 'rocketcdn_purge_cache_response' );
if ( false === $purge_response ) {
return;
}
delete_transient( 'rocketcdn_purge_cache_response' );
rocket_notice_html(
[
'status' => $purge_response['status'],
'message' => $purge_response['message'],
]
);
}
/**
* Checks if white label is enabled
*
* @since 3.6
*
* @return bool
*/
private function is_white_label_account() {
return (bool) rocket_get_constant( 'WP_ROCKET_WHITE_LABEL_ACCOUNT' );
}
}

View File

@@ -0,0 +1,194 @@
<?php
namespace WP_Rocket\Engine\CDN\RocketCDN;
use WP_Rocket\Event_Management\Subscriber_Interface;
use WP_Rocket\Admin\Options_Data;
/**
* Subscriber for RocketCDN REST API Integration
*
* @since 3.5
*/
class RESTSubscriber implements Subscriber_Interface {
const ROUTE_NAMESPACE = 'wp-rocket/v1';
/**
* CDNOptionsManager instance
*
* @var CDNOptionsManager
*/
private $cdn_options;
/**
* WP Rocket Options instance
*
* @var Options_Data
*/
private $options;
/**
* Constructor
*
* @param CDNOptionsManager $cdn_options CDNOptionsManager instance.
* @param Options_Data $options WP Rocket Options instance.
*/
public function __construct( CDNOptionsManager $cdn_options, Options_Data $options ) {
$this->cdn_options = $cdn_options;
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public static function get_subscribed_events() {
return [
'rest_api_init' => [
[ 'register_enable_route' ],
[ 'register_disable_route' ],
],
];
}
/**
* Register Enable route in the WP REST API
*
* @since 3.5
*
* @return void
*/
public function register_enable_route() {
register_rest_route(
self::ROUTE_NAMESPACE,
'rocketcdn/enable',
[
'methods' => 'PUT',
'callback' => [ $this, 'enable' ],
'args' => [
'email' => [
'required' => true,
'validate_callback' => [ $this, 'validate_email' ],
],
'key' => [
'required' => true,
'validate_callback' => [ $this, 'validate_key' ],
],
'url' => [
'required' => true,
'validate_callback' => function ( $param ) {
$url = esc_url_raw( $param );
return ! empty( $url );
},
'sanitize_callback' => function ( $param ) {
return esc_url_raw( $param );
},
],
],
'permission_callback' => '__return_true',
]
);
}
/**
* Register Disable route in the WP REST API
*
* @since 3.5
*
* @return void
*/
public function register_disable_route() {
register_rest_route(
self::ROUTE_NAMESPACE,
'rocketcdn/disable',
[
'methods' => 'PUT',
'callback' => [ $this, 'disable' ],
'args' => [
'email' => [
'required' => true,
'validate_callback' => [ $this, 'validate_email' ],
],
'key' => [
'required' => true,
'validate_callback' => [ $this, 'validate_key' ],
],
],
'permission_callback' => '__return_true',
]
);
}
/**
* Enable CDN and add RocketCDN URL to WP Rocket options
*
* @since 3.5
*
* @param \WP_REST_Request $request the WP REST Request object.
*
* @return string
*/
public function enable( \WP_REST_Request $request ) {
$params = $request->get_body_params();
$this->cdn_options->enable( $params['url'] );
$response = [
'code' => 'success',
'message' => __( 'RocketCDN enabled', 'rocket' ),
'data' => [
'status' => 200,
],
];
return rest_ensure_response( $response );
}
/**
* Disable the CDN and remove the RocketCDN URL from WP Rocket options
*
* @since 3.5
*
* @param \WP_REST_Request $request the WP Rest Request object.
*
* @return string
*/
public function disable( \WP_REST_Request $request ) {
$this->cdn_options->disable();
$response = [
'code' => 'success',
'message' => __( 'RocketCDN disabled', 'rocket' ),
'data' => [
'status' => 200,
],
];
return rest_ensure_response( $response );
}
/**
* Checks that the email sent along the request corresponds to the one saved in the DB
*
* @since 3.5
*
* @param string $param Parameter value to validate.
*
* @return bool
*/
public function validate_email( $param ) {
return $param === $this->options->get( 'consumer_email' );
}
/**
* Checks that the key sent along the request corresponds to the one saved in the DB
*
* @since 3.5
*
* @param string $param Parameter value to validate.
*
* @return bool
*/
public function validate_key( $param ) {
return $param === $this->options->get( 'consumer_key' );
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace WP_Rocket\Engine\CDN\RocketCDN;
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
/**
* Service provider for RocketCDN
*
* @since 3.5
*/
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 = [
'rocketcdn_api_client',
'rocketcdn_options_manager',
'rocketcdn_data_manager_subscriber',
'rocketcdn_rest_subscriber',
'rocketcdn_admin_subscriber',
'rocketcdn_notices_subscriber',
];
/**
* Registers the RocketCDN classes in the container
*
* @since 3.5
*
* @return void
*/
public function register() {
$options = $this->getContainer()->get( 'options' );
// RocketCDN API Client.
$this->getContainer()->add( 'rocketcdn_api_client', 'WP_Rocket\Engine\CDN\RocketCDN\APIClient' );
// RocketCDN CDN options manager.
$this->getContainer()->add( 'rocketcdn_options_manager', 'WP_Rocket\Engine\CDN\RocketCDN\CDNOptionsManager' )
->withArgument( $this->getContainer()->get( 'options_api' ) )
->withArgument( $options );
// RocketCDN Data manager subscriber.
$this->getContainer()->share( 'rocketcdn_data_manager_subscriber', 'WP_Rocket\Engine\CDN\RocketCDN\DataManagerSubscriber' )
->withArgument( $this->getContainer()->get( 'rocketcdn_api_client' ) )
->withArgument( $this->getContainer()->get( 'rocketcdn_options_manager' ) );
// RocketCDN REST API Subscriber.
$this->getContainer()->share( 'rocketcdn_rest_subscriber', 'WP_Rocket\Engine\CDN\RocketCDN\RESTSubscriber' )
->withArgument( $this->getContainer()->get( 'rocketcdn_options_manager' ) )
->withArgument( $options );
// RocketCDN Notices Subscriber.
$this->getContainer()->share( 'rocketcdn_notices_subscriber', 'WP_Rocket\Engine\CDN\RocketCDN\NoticesSubscriber' )
->withArgument( $this->getContainer()->get( 'rocketcdn_api_client' ) )
->withArgument( __DIR__ . '/views' );
// RocketCDN settings page subscriber.
$this->getContainer()->share( 'rocketcdn_admin_subscriber', 'WP_Rocket\Engine\CDN\RocketCDN\AdminPageSubscriber' )
->withArgument( $this->getContainer()->get( 'rocketcdn_api_client' ) )
->withArgument( $options )
->withArgument( $this->getContainer()->get( 'beacon' ) )
->withArgument( __DIR__ . '/views' );
}
}

View File

@@ -0,0 +1,55 @@
{
"name": "wp-media/module-rocketcdn",
"description": "Module for RocketCDN integration",
"homepage": "https://github.com/wp-media/module-rocketcdn",
"license": "GPL-2.0+",
"authors": [
{
"name": "WP Media",
"email": "contact@wp-media.me",
"homepage": "https://wp-media.me"
}
],
"type": "library",
"config": {
"sort-packages": true
},
"support": {
"issues": "https://github.com/wp-media/module-rocketcdn/issues",
"source": "https://github.com/wp-media/module-rocketcdn"
},
"require-dev": {
"php": "^5.6 || ^7",
"brain/monkey": "^2.0",
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
"phpcompatibility/phpcompatibility-wp": "^2.0",
"phpunit/phpunit": "^5.7 || ^7",
"roave/security-advisories": "dev-master",
"wp-coding-standards/wpcs": "^2",
"wp-media/event-manager": "^3.1",
"wp-media/options": "^3.0",
"wp-media/module-container": "^2.4",
"wp-media/phpunit": "^1.0",
"wp-media/phpunit-wp-rocket": "^1.0"
},
"autoload": {
"psr-4": { "WP_Rocket\\Engine\\CDN\\RocketCDN\\": "." }
},
"autoload-dev": {
"psr-4": { "WP_Rocket\\Tests\\": "Tests/" }
},
"scripts": {
"test-unit": "\"vendor/bin/phpunit\" --testsuite unit --colors=always --configuration Tests/Unit/phpunit.xml.dist",
"test-integration": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration Tests/Integration/phpunit.xml.dist --exclude-group AdminOnly",
"test-integration-adminonly": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration Tests/Integration/phpunit.xml.dist --group AdminOnly",
"run-tests": [
"@test-unit",
"@test-integration",
"@test-integration-adminonly"
],
"install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run",
"phpcs": "phpcs --basepath=.",
"phpcs-changed": "./bin/phpcs-changed.sh",
"phpcs:fix": "phpcbf"
}
}

View File

@@ -0,0 +1,95 @@
<?php
/**
* RocketCDN small CTA template.
*
* @since 3.5
*
* @param array $data {
* @type string $container_class container CSS class.
* @type string $promotion_campaign Promotion campaign title.
* @type string $promotion_end_date Promotion end date.
* @type string $nopromo_variant CSS modifier for the no promotion display.
* @type string $regular_price RocketCDN regular price.
* @type string $current_price RocketCDN current price.
* }
*/
defined( 'ABSPATH' ) || die( 'Cheatin&#8217; uh?' );
?>
<div class="wpr-rocketcdn-cta <?php echo esc_attr( $data['container_class'] ); ?>" id="wpr-rocketcdn-cta">
<?php if ( ! empty( $data['promotion_campaign'] ) ) : ?>
<div class="wpr-flex wpr-rocketcdn-promo">
<h3 class="wpr-title1"><?php echo esc_html( $data['promotion_campaign'] ); ?></h3>
<p class="wpr-title2 wpr-rocketcdn-promo-date">
<?php
printf(
// Translators: %s = date formatted using date_i18n() and get_option( 'date_format' ).
esc_html__( 'Valid until %s only!', 'rocket' ),
esc_html( $data['promotion_end_date'] )
);
?>
</p>
</div>
<?php endif; ?>
<section class="wpr-rocketcdn-cta-content<?php echo esc_attr( $data['nopromo_variant'] ); ?>">
<h3 class="wpr-title2">RocketCDN</h3>
<p class="wpr-rocketcdn-cta-subtitle"><?php esc_html_e( 'Speed up your website thanks to:', 'rocket' ); ?></p>
<div class="wpr-flex">
<ul class="wpr-rocketcdn-features">
<li class="wpr-rocketcdn-feature wpr-rocketcdn-bandwidth">
<?php
// translators: %1$s = opening strong tag, %2$s = closing strong tag.
printf( esc_html__( 'High performance Content Delivery Network (CDN) with %1$sunlimited bandwith%2$s', 'rocket' ), '<strong>', '</strong>' );
?>
</li>
<li class="wpr-rocketcdn-feature wpr-rocketcdn-configuration">
<?php
// translators: %1$s = opening strong tag, %2$s = closing strong tag.
printf( esc_html__( 'Easy configuration: the %1$sbest CDN settings%2$s are automatically applied', 'rocket' ), '<strong>', '</strong>' );
?>
</li>
<li class="wpr-rocketcdn-feature wpr-rocketcdn-automatic">
<?php
// translators: %1$s = opening strong tag, %2$s = closing strong tag.
printf( esc_html__( 'WP Rocket integration: the CDN option is %1$sautomatically configured%2$s in our plugin', 'rocket' ), '<strong>', '</strong>' );
?>
</li>
</ul>
<div class="wpr-rocketcdn-pricing">
<?php if ( ! empty( $data['error'] ) ) : ?>
<p><?php echo esc_html( $data['message'] ); ?></p>
<?php else : ?>
<?php if ( ! empty( $data['regular_price'] ) ) : ?>
<h4 class="wpr-title2 wpr-rocketcdn-pricing-regular"><del>$<?php echo esc_html( $data['regular_price'] ); ?></del></h4>
<?php endif; ?>
<h4 class="wpr-rocketcdn-pricing-current">
<?php
printf(
// translators: %s = price of RocketCDN subscription.
esc_html__( '%s / month', 'rocket' ),
'<span class="wpr-title1">$' . esc_html( $data['current_price'] ) . '</span>'
);
?>
</h4>
<button class="wpr-button wpr-rocketcdn-open" data-micromodal-trigger="wpr-rocketcdn-modal"><?php esc_html_e( 'Get Started', 'rocket' ); ?></button>
<?php endif; ?>
</div>
</div>
</section>
<div class="wpr-rocketcdn-cta-footer">
<a href="https://go.wp-rocket.me/rocket-cdn" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Learn more about RocketCDN', 'rocket' ); ?></a>
</div>
<button class="wpr-rocketcdn-cta-close<?php echo esc_attr( $data['nopromo_variant'] ); ?>" id="wpr-rocketcdn-close-cta"><span class="screen-reader-text"><?php esc_html_e( 'Reduce this banner', 'rocket' ); ?></span></button>
<?php if ( ! empty( $data['promotion_campaign'] ) ) : ?>
<p>
<?php
printf(
// translators: %1$s = discounted price, %2$s = regular price.
esc_html__( '* $%1$s/month for 12 months then $%2$s/month. You can cancel your subscription at any time.', 'rocket' ),
esc_html( str_replace( '*', '', $data['current_price'] ) ),
esc_html( $data['regular_price'] )
);
?>
</p>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,23 @@
<?php
/**
* RocketCDN small CTA template.
*
* @since 3.5
*
* @param array $data {
* @type string $container_class container CSS class.
* }
*/
defined( 'ABSPATH' ) || die( 'Cheatin&#8217; uh?' );
?>
<div class="wpr-rocketcdn-cta-small notice-alt notice-warning <?php echo esc_attr( $data['container_class'] ); ?>" id="wpr-rocketcdn-cta-small">
<div class="wpr-flex">
<section>
<h3 class="notice-title"><?php esc_html_e( 'Speed up your website with RocketCDN, WP Rockets Content Delivery Network.', 'rocket' ); ?></strong></h3>
</section>
<div>
<button class="wpr-button" id="wpr-rocketcdn-open-cta"><?php esc_html_e( 'Learn More', 'rocket' ); ?></button>
</div>
</div>
</div>

View File

@@ -0,0 +1,37 @@
<?php
/**
* RocketCDN status on dashboard tab template.
*
* @since 3.5
*
* @param array $data {
* @type bool $is_live_site Identifies if the current website is a live or local/staging one
* @type string $container_class Flex container CSS class.
* @type string $label Content label.
* @type string $status_class CSS Class to display the status.
* @type string $status_text Text to display the subscription status.
* @type bool $is_active Boolean identifying the activation status.
* }
*/
?>
<div class="wpr-optionHeader">
<h3 class="wpr-title2">RocketCDN</h3>
</div>
<div class="wpr-field wpr-field-account">
<?php if ( ! $data['is_live_site'] ) : ?>
<span class="wpr-infoAccount wpr-isInvalid"><?php esc_html_e( 'RocketCDN is unavailable on local domains and staging sites.', 'rocket' ); ?></span>
<?php else : ?>
<div class="wpr-flex<?php echo esc_attr( $data['container_class'] ); ?>">
<div>
<span class="wpr-title3"><?php echo esc_html( $data['label'] ); ?></span>
<span class="wpr-infoAccount<?php echo esc_attr( $data['status_class'] ); ?>"><?php echo esc_html( $data['status_text'] ); ?></span>
</div>
<?php if ( ! $data['is_active'] ) : ?>
<div>
<a href="#page_cdn" class="wpr-button"><?php esc_html_e( 'Get RocketCDN', 'rocket' ); ?></a>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,14 @@
<?php
/**
* Promote RocketCDN notice template.
*
* @since 3.5
*/
defined( 'ABSPATH' ) || die( 'Cheatin&#8217; uh?' );
?>
<div class="notice notice-alt notice-warning is-dismissible" id="rocketcdn-promote-notice">
<h2 class="notice-title"><?php esc_html_e( 'New!', 'rocket' ); ?></h2>
<p><?php esc_html_e( 'Speed up your website with RocketCDN, WP Rockets Content Delivery Network!', 'rocket' ); ?></p>
<p><a href="#page_cdn" class="wpr-button" id="rocketcdn-learn-more-dismiss"><?php esc_html_e( 'Learn More', 'rocket' ); ?></a></p>
</div>

View File

@@ -0,0 +1,40 @@
<?php
namespace WP_Rocket\Engine\CDN;
use WP_Rocket\Engine\Container\ServiceProvider\AbstractServiceProvider;
/**
* Service provider for WP Rocket CDN
*
* @since 3.5.5
*/
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 = [
'cdn',
'cdn_subscriber',
];
/**
* Registers the services in the container
*
* @return void
*/
public function register() {
$options = $this->getContainer()->get( 'options' );
$this->getContainer()->share( 'cdn', 'WP_Rocket\Engine\CDN\CDN' )
->withArgument( $options );
$this->getContainer()->share( 'cdn_subscriber', 'WP_Rocket\Engine\CDN\Subscriber' )
->withArgument( $options )
->withArgument( $this->getContainer()->get( 'cdn' ) );
}
}

View File

@@ -0,0 +1,277 @@
<?php
namespace WP_Rocket\Engine\CDN;
use WP_Rocket\Admin\Options_Data;
use WP_Rocket\Event_Management\Subscriber_Interface;
/**
* Subscriber for the CDN feature
*
* @since 3.4
*/
class Subscriber implements Subscriber_Interface {
/**
* WP Rocket Options instance
*
* @var Options_Data
*/
private $options;
/**
* CDN instance
*
* @var CDN
*/
private $cdn;
/**
* Constructor
*
* @param Options_Data $options WP Rocket Options instance.
* @param CDN $cdn CDN instance.
*/
public function __construct( Options_Data $options, CDN $cdn ) {
$this->options = $options;
$this->cdn = $cdn;
}
/**
* Return an array of events that this subscriber wants to listen to.
*
* @since 3.4
*
* @return array
*/
public static function get_subscribed_events() {
return [
'rocket_buffer' => [
[ 'rewrite', 20 ],
[ 'rewrite_srcset', 21 ],
],
'rocket_css_content' => 'rewrite_css_properties',
'rocket_cdn_hosts' => [ 'get_cdn_hosts', 10, 2 ],
'rocket_dns_prefetch' => 'add_dns_prefetch_cdn',
'rocket_facebook_sdk_url' => 'add_cdn_url',
'rocket_css_url' => [ 'add_cdn_url', 10, 2 ],
'rocket_js_url' => [ 'add_cdn_url', 10, 2 ],
'rocket_asset_url' => [ 'maybe_replace_url', 10, 2 ],
];
}
/**
* Rewrites URLs to the CDN URLs if allowed
*
* @since 3.4
*
* @param string $html HTML content.
* @return string
*/
public function rewrite( $html ) {
if ( ! $this->is_allowed() ) {
return $html;
}
return $this->cdn->rewrite( $html );
}
/**
* Rewrites URLs in srcset attributes to the CDN URLs if allowed
*
* @since 3.4.0.4
*
* @param string $html HTML content.
* @return string
*/
public function rewrite_srcset( $html ) {
if ( ! $this->is_allowed() ) {
return $html;
}
return $this->cdn->rewrite_srcset( $html );
}
/**
* Rewrites URLs to the CDN URLs in CSS files
*
* @since 3.4
*
* @param string $content CSS content.
* @return string
*/
public function rewrite_css_properties( $content ) {
/**
* Filters the application of the CDN on CSS properties
*
* @since 2.6
*
* @param bool true to apply CDN to properties, false otherwise
*/
$do_rewrite = apply_filters( 'do_rocket_cdn_css_properties', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
if ( ! $do_rewrite ) {
return $content;
}
if ( ! $this->is_cdn_enabled() ) {
return $content;
}
return $this->cdn->rewrite_css_properties( $content );
}
/**
* Gets the host value for each CDN URLs
*
* @since 3.4
*
* @param array $hosts Base hosts.
* @param array $zones Zones to get the CND URLs associated with.
*
* @return array
*/
public function get_cdn_hosts( array $hosts = [], array $zones = [ 'all' ] ) {
$cdn_urls = $this->cdn->get_cdn_urls( $zones );
if ( empty( $cdn_urls ) ) {
return $hosts;
}
foreach ( $cdn_urls as $cdn_url ) {
$parsed = get_rocket_parse_url( rocket_add_url_protocol( $cdn_url ) );
if ( empty( $parsed['host'] ) ) {
continue;
}
$hosts[] = untrailingslashit( $parsed['host'] . $parsed['path'] );
}
return array_unique( $hosts );
}
/**
* Adds CDN URLs to the DNS prefetch links
*
* @since 3.4
*
* @param array $domains Domain names to DNS prefetch.
* @return array
*/
public function add_dns_prefetch_cdn( $domains ) {
if ( ! $this->is_allowed() ) {
return $domains;
}
$cdn_urls = $this->cdn->get_cdn_urls( [ 'all', 'images', 'css_and_js', 'css', 'js' ] );
if ( ! $cdn_urls ) {
return $domains;
}
return array_merge( $domains, $cdn_urls );
}
/**
* Adds the CDN URL on the provided URL
*
* @since 3.4
*
* @param string $url URL to rewrite.
* @param string $original_url Original URL for this URL. Optional.
* @return string
*/
public function add_cdn_url( $url, $original_url = '' ) {
if ( ! empty( $original_url ) ) {
if ( $this->cdn->is_excluded( $original_url ) ) {
return $url;
}
}
return $this->cdn->rewrite_url( $url );
}
/**
* Replace CDN URL with site URL on the provided asset URL.
*
* @since 3.5.3
*
* @param string $url URL of the asset.
* @param array $zones Array of corresponding zones for the asset.
* @return string
*/
public function maybe_replace_url( $url, array $zones = [ 'all' ] ) {
if ( ! $this->is_allowed() ) {
return $url;
}
$url_parts = get_rocket_parse_url( $url );
if ( empty( $url_parts['host'] ) ) {
return $url;
}
$site_url_parts = get_rocket_parse_url( site_url() );
if ( empty( $site_url_parts['host'] ) ) {
return $url;
}
if ( $url_parts['host'] === $site_url_parts['host'] ) {
return $url;
}
$cdn_urls = $this->cdn->get_cdn_urls( $zones );
if ( empty( $cdn_urls ) ) {
return $url;
}
$cdn_urls = array_map( 'rocket_add_url_protocol', $cdn_urls );
$site_url = $site_url_parts['scheme'] . '://' . $site_url_parts['host'];
foreach ( $cdn_urls as $cdn_url ) {
if ( false === strpos( $url, $cdn_url ) ) {
continue;
}
return str_replace( $cdn_url, $site_url, $url );
}
return $url;
}
/**
* Checks if CDN can be applied
*
* @since 3.4
*
* @return boolean
*/
private function is_allowed() {
if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE' ) ) {
return false;
}
if ( ! $this->is_cdn_enabled() ) {
return false;
}
if ( is_rocket_post_excluded_option( 'cdn' ) ) {
return false;
}
return true;
}
/**
* Checks if the CDN option is enabled
*
* @since 3.5.5
*
* @return bool
*/
private function is_cdn_enabled() {
return (bool) $this->options->get( 'cdn', 0 );
}
}