File: /wordpress/mu-plugins/edge-cache/class-edge-cache-cli.php
<?php
/**
* Edge Cache control - query and control the Edge Cache.
*
* The Edge Cache improves the performance of your site, allowing pages to run faster and reducing
* the load on your server. These commands allow you to check and update the status of the Edge
* Cache for your site.
*/
class Edge_Cache_CLI extends WP_CLI_Command {
private const STATUS_STRINGS = [
Edge_Cache_Plugin::EC_ENABLED => '%GEnabled%n',
Edge_Cache_Plugin::EC_DISABLED => '%rDisabled%n',
Edge_Cache_Plugin::EC_DDOS => '%YDDOS mode%n',
];
/**
* Display the current status of the Edge Cache for this site.
*/
public function status() {
$edge_cache = Edge_Cache_Plugin::get_instance();
$status = $edge_cache->get_ec_status();
if ( ! isset( self::STATUS_STRINGS[ $status ] ) ) {
WP_CLI::error( 'Failed to query edge cache status.' );
}
$ddos_until = $edge_cache->get_ec_ddos_until();
$this->success_status( $status );
if ( $ddos_until < 0 || $ddos_until > time() ) {
WP_CLI::success(
$this->format_defensive_mode_message( $ddos_until )
);
}
}
/**
* Format a message about Defensive mode for display.
*/
private function format_defensive_mode_message( $timestamp ) {
if ( $timestamp === -1 ) {
return WP_CLI::colorize( "Defensive mode enabled %Gindefinitely%n." );
} else {
return WP_CLI::colorize(
"Defensive mode enabled until %G" . date( 'Y-m-d H:i:s', $timestamp ) . "%n"
);
}
}
/**
* Purge the Edge Cache - either for the whole site, or for a URI.
*
* You must specify one of '--url' or '--domain' when purging.
*
* ## OPTIONS
* [<urls>...]
* : URLs to purge.
*
* [--domain]
* : Purge the whole domain.
*
* [--yes]
* : Answer yes to the confirmation prompt (needed when purging with --domain).
*
* ## EXAMPLES
*
* wp edge-cache purge "https://my-site.com/path" "https://my-site.com/path?get=param"
* wp edge-cache purge --domain
*
* @param array $args Positional arguments (URLs).
*/
public function purge( $urls, $assoc_args ) {
$purge_domain = ! empty( $assoc_args['domain'] );
// Require either URLs or a --domain flag.
if ( empty( $urls ) && ! $purge_domain ) {
WP_CLI::Error( 'You must either specify URLs to purge, or specify --domain to purge the whole domain.' );
}
if ( ! empty( $urls ) && $purge_domain ) {
WP_CLI::Error( 'You may only either specify URLs to purge, or specify --domain to purge the whole domain.' );
}
// Only accept urls which are for the correct domain.
$self_domain = parse_url( get_site_url(), PHP_URL_HOST );
foreach ( $urls as $url ) {
$target_domain = parse_url( $url, PHP_URL_HOST );
if ( $target_domain !== $self_domain ) {
WP_CLI::Error( 'You can only purge URLs for ' . $self_domain );
}
}
// Always verify before purging a whole domain.
if ( $purge_domain ) {
WP_CLI::warning( WP_CLI::colorize( "You are about to purge %Rall edge-cached content%n for your entire site." ) );
WP_CLI::confirm( "Are you sure you want to proceed?", $assoc_args );
}
// Actually do the purge.
$plugin = Edge_Cache_Plugin::get_instance();
if ( $purge_domain ) {
$result = $plugin->purge_domain_now( 'cli' );
} else {
$result = $plugin->purge_uris_now( $urls, 'cli' );
}
// Report errors.
if ( ! $result ) {
if ( $plugin->has_hit_purge_ratelimit() ) {
WP_CLI::Error( 'Your site has purged too many times recently. Please try again in a few minutes.' );
} else {
$error = $plugin->get_last_ec_error() ?? 'uknown error';
WP_CLI::Error( sprintf( 'Purge request failed: %s. Please try again in a few minutes.', $error ) );
}
}
WP_CLI::success( 'Purge successful.' );
}
/**
* Set the Edge Cache into Defensive mode.
*
* Enable Defensive mode when your site is receiving more traffic than it can handle. When
* it's enabled, visitors to your site will have their browsers tested using a proof-of-work
* check before they are allowed to access your site.
*
* ## OPTIONS
* [--time=<time>]
* : Specify a number of minutes (m) or hours (h) to engage Defensive mode for. Defaults to 1h.
*
* [--end]
* : Pass this argument to turn off Defensive mode and return to normal serivce.
*
* [--indefinitely]
* : Pass this argument to enable Defensive mode indefinitely. Warning: defensive mode is intended
* to be a temporary measure, and leaving it on indefinitely may lead to a poor experience for your visitors.
*
* [--yes]
* : Answer yes to the confirmation prompt (needed when using --indefinitely).
*
* ## Examples
* wp edge-cache defensive-mode --time=5h
*
* @subcommand defensive-mode
*/
public function defensive_mode( $args, $assoc_args ) {
$has_end = isset( $assoc_args['end'] );
$has_time = isset( $assoc_args['time'] );
$has_indefinitely = isset( $assoc_args['indefinitely'] );
if ( $has_end && $has_time ) {
WP_CLI::Error( 'You can only specify either --end or --time.' );
}
// Defensive mode is not available when "old school" DDOS mode is active.
$status = Edge_Cache_Plugin::get_instance()->get_ec_status();
if ( $status === Edge_Cache_Plugin::EC_DDOS ) {
WP_CLI::Error(
WP_CLI::colorize(
'Defensive mode cannot be enabled in status ' .
self::STATUS_STRINGS[ $status ]
)
);
}
if ( $has_end ) {
$timestamp = 0;
} else if ( $has_indefinitely ) {
$timestamp = -1;
WP_CLI::warning( WP_CLI::colorize( "You are about to enable defensive mode %Rindefinitely%n." ) );
WP_CLI::line( "This will cause all visitors to your site to pass a browser-check before they are allowed to access your site." );
WP_CLI::confirm( "Are you sure you want to proceed?", $assoc_args );
} else {
$time = '1h';
if ( ! empty( $assoc_args['time'] ) ) {
$time = $assoc_args['time'];
}
$unit = substr( $time, -1 );
$multiplier = 60; // default to minutes.
if ( 'h' === $unit ) {
$multiplier = 60 * 60;
} else if ( 'd' === $unit ) {
$multiplier = 24 * 60 * 60;
}
$seconds = intval( $time ) * $multiplier;
if ( $seconds < 30 * 60 ) {
WP_CLI::error( 'Time limit cannot be less than 30 minutes.' );
}
if ( $seconds > 7 * 24 * 60 * 60 ) {
WP_CLI::error( 'Time limit cannot be greater than 1 week.' );
}
$timestamp = time() + $seconds;
}
$result = $this->run_ec_command( 'ddos_until', [ 'timestamp' => $timestamp ] );
if ( is_wp_error( $result ) ) {
WP_CLI::error( $result->get_error_message() );
}
if ( $timestamp === 0 ) {
WP_CLI::success( 'Defensive mode disabled.' );
} else {
WP_CLI::success(
$this->format_defensive_mode_message( $timestamp )
);
}
}
/**
* Turn on the edge cache.
*/
public function enable() {
$result = $this->run_ec_command( 'on' );
if ( is_wp_error( $result ) ) {
WP_CLI::error( $result->get_error_message() );
}
$this->success_status( Edge_Cache_Plugin::EC_ENABLED );
}
/**
* Turn off the edge cache.
*/
public function disable() {
$result = $this->run_ec_command( 'off' );
if ( is_wp_error( $result ) ) {
WP_CLI::error( $result->get_error_message() );
}
$this->success_status( Edge_Cache_Plugin::EC_DISABLED );
}
private function success_status( $status ) {
WP_CLI::success(
WP_CLI::colorize( 'Edge cache status: ' . self::STATUS_STRINGS[ $status ] )
);
}
private function run_ec_command( $command, $args = [] ) {
$ec_plugin = Edge_Cache_Plugin::get_instance();
$body = [
'wp_action' => 'manual_cli_set',
'wp_domain' => parse_url( get_site_url(), PHP_URL_HOST ),
'at_host' => php_uname( 'n' ),
];
$body = array_merge( $body, $args );
$response = $ec_plugin->query_ec_backend( $command, array(
'timeout' => 30,
'body' => $body
) );
if ( $response['success'] === false ) {
$error = isset( $response['error'] ) ? $response['error'] : 'An error occurred while processing your request.';
return new WP_Error( 'request_failed', $error );
}
return true;
}
/**
* Maintain backwards compatibility without polluting the documentation: register
* defensive_mode as a subcommand if being invoked, as a wrapper for defensive-mode.
*/
public static function register_backwards_compatible_commands() {
$arguments = WP_CLI::get_runner()->arguments;
if ( ! in_array( 'defensive_mode', $arguments, true ) ) {
return;
}
$handler = function ( $args, $assoc_args ) {
$edge_cache_cli = new Edge_Cache_CLI();
$edge_cache_cli->defensive_mode( $args, $assoc_args );
};
WP_CLI::add_command(
'edge-cache defensive_mode',
$handler,
[
'shortdesc' => 'Deprecated; please use defensive-mode instead.',
]
);
}
}
WP_CLI::add_command('edge-cache', 'Edge_Cache_CLI');
Edge_Cache_CLI::register_backwards_compatible_commands();