Server IP : 104.21.14.48 / Your IP : 3.21.12.194 [ Web Server : Apache System : Linux b70eb322-3aee-0c53-7c82-0db91281f2c6.secureserver.net 6.1.90-1.el9.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 2 12:09:22 EDT 2024 x86_64 User : root ( 0) PHP Version : 8.0.30.2 Disable Function : NONE Domains : 0 Domains MySQL : ON | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/wp-content/mu-plugins/object-cache-pro/src/Console/ |
Upload File : |
<?php /** * Copyright © 2019-2024 Rhubarb Tech Inc. All Rights Reserved. * * The Object Cache Pro Software and its related materials are property and confidential * information of Rhubarb Tech Inc. Any reproduction, use, distribution, or exploitation * of the Object Cache Pro Software and its related materials, in whole or in part, * is strictly forbidden unless prior permission is obtained from Rhubarb Tech Inc. * * In addition, any reproduction, use, distribution, or exploitation of the Object Cache Pro * Software and its related materials, in whole or in part, is subject to the End-User License * Agreement accessible in the included `LICENSE` file, or at: https://objectcache.pro/eula */ declare(strict_types=1); namespace RedisCachePro\Console; use Throwable; use WP_REST_Request; use cli\Shell; use WP_CLI; use WP_CLI\NoOp; use WP_CLI_Command; use function WP_CLI\Utils\esc_cmd; use function WP_CLI\Utils\proc_open_compat; use RedisCachePro\Diagnostics\Diagnostics; use RedisCachePro\Connections\RelayConnection; use RedisCachePro\Configuration\Configuration; use RedisCachePro\ObjectCaches\ObjectCacheInterface; use RedisCachePro\ObjectCaches\MeasuredObjectCacheInterface; use RedisCachePro\Plugin\Api\Analytics; use RedisCachePro\Console\Watchers\LogWatcher; use RedisCachePro\Console\Watchers\DigestWatcher; use RedisCachePro\Console\Watchers\AggregateWatcher; /** * Enables, disabled, flushes, and checks the status of the object cache. */ class Commands extends WP_CLI_Command { /** * Enables the object cache. * * Copies the object cache drop-in into the content directory. * Will not overwrite existing files, unless the --force option is used. * * ## OPTIONS * * [--force] * : Overwrite existing files. * * [--skip-flush] * : Omit flushing the cache. * * [--skip-flush-notice] * : Omit the cache flush notice. * * [--skip-transients] * : Omit deleting transients from the database. * * ## EXAMPLES * * # Enable the object cache. * $ wp redis enable * * # Enable the object cache and overwrite existing drop-in. * $ wp redis enable --force * * # Update the object cache drop-in without flushing the cache. * $ wp redis enable --force --skip-flush --skip-flush-notice * * @alias activate * * @param array<int, string> $arguments * @param array<mixed> $options * @return void */ public function enable($arguments, $options) { global $wp_filesystem; if (! \WP_Filesystem()) { WP_CLI::error('Could not gain filesystem access.'); } if (! defined('\WP_REDIS_CONFIG')) { WP_CLI::error(WP_CLI::colorize( 'To enable the object cache, set up the %yWP_REDIS_CONFIG%n constant.' )); } $force = isset($options['force']); $dropin = \WP_CONTENT_DIR . '/object-cache.php'; $stub = realpath(__DIR__ . '/../../stubs/object-cache.php'); if (! $force && $wp_filesystem->exists($dropin)) { WP_CLI::error(WP_CLI::colorize( 'A object cache drop-in already exists. Run `%ywp redis enable --force%n` to overwrite it.' )); } if (! $wp_filesystem->copy($stub, $dropin, $force, \FS_CHMOD_FILE)) { WP_CLI::error('Object cache could not be enabled.'); } if (function_exists('wp_opcache_invalidate')) { wp_opcache_invalidate($dropin, true); } WP_CLI::success('Object cache enabled.'); if (isset($options['skip-flush'])) { if (! isset($options['skip-flush-notice'])) { WP_CLI::line(WP_CLI::colorize( 'To avoid outdated data, flush the object cache by calling `%ywp cache flush%n`.' )); } } else { $GLOBALS['ObjectCachePro']->resetCache() ? WP_CLI::success('Object cache flushed.') : WP_CLI::error('Object cache could not be flushed.'); } if (isset($options['skip-transients'])) { WP_CLI::line( WP_CLI::colorize( sprintf( 'To avoid outdated data, delete transients from the database by calling `%%y%s%%n`.', is_multisite() ? 'wp transient delete --all --network && wp site list --field=url | xargs -n1 -I % wp --url=% transient delete --all' : 'wp transient delete --all' ) ) ); } else { $GLOBALS['ObjectCachePro']->deleteTransients(); WP_CLI::success('Transients deleted from the database.'); } } /** * Disables the object cache. * * ## OPTIONS * * [--skip-flush] * : Omit flushing the cache. * * ## EXAMPLES * * # Disable the disable cache. * $ wp redis disable * * # Disable the object cache and don't flush the cache. * $ wp redis disable --skip-flush * * @alias deactivate * * @param array<int, string> $arguments * @param array<mixed> $options * @return void */ public function disable($arguments, $options) { global $wp_filesystem; if (! \WP_Filesystem()) { WP_CLI::error('Could not gain filesystem access.'); } $dropin = \WP_CONTENT_DIR . '/object-cache.php'; if (! $wp_filesystem->exists($dropin)) { WP_CLI::log('No object cache drop-in found.'); return; } if (! $wp_filesystem->delete($dropin)) { WP_CLI::error('Object cache could not be disabled.'); } if (function_exists('wp_opcache_invalidate')) { wp_opcache_invalidate($dropin, true); } WP_CLI::success('Object cache disabled.'); if (! isset($options['skip-flush'])) { $GLOBALS['ObjectCachePro']->resetCache() ? WP_CLI::success('Object cache flushed.') : WP_CLI::error('Object cache could not be flushed.'); } } /** * Shows object cache status summary. * * ## EXAMPLES * * # Show object cache status. * $ wp redis status * * @alias info * @alias health * * @return void */ public function status() { global $wp_object_cache; $diagnostics = (new Diagnostics($wp_object_cache)) ->withFilesystemAccess() ->toArray(); WP_CLI::log(WP_CLI::colorize(sprintf('%%b[%s]%%n', 'Cache'))); $diagnostic = $diagnostics[Diagnostics::GENERAL]['status']; WP_CLI::log(sprintf('%s: %s', $diagnostic->name, WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::GENERAL]['dropin']; WP_CLI::log(sprintf('%s: %s', $diagnostic->name, WP_CLI::colorize($diagnostic->withComment()->cli))); if (! empty($diagnostics[Diagnostics::ERRORS])) { WP_CLI::log(''); WP_CLI::log(WP_CLI::colorize(sprintf('%%b[%s]%%n', 'Errors'))); foreach ($diagnostics[Diagnostics::ERRORS] as $error) { WP_CLI::log(WP_CLI::colorize("%r{$error}%n")); } } WP_CLI::log(''); WP_CLI::log(WP_CLI::colorize(sprintf('%%b[%s]%%n', 'Plugin'))); $diagnostic = $diagnostics[Diagnostics::GENERAL]['license']; WP_CLI::log(sprintf('%s: %s', $diagnostic->name, WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::GENERAL]['mu']; WP_CLI::log(sprintf('%s: %s', $diagnostic->name, WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::VERSIONS]['plugin']; WP_CLI::log(sprintf('%s: %s', 'Version', WP_CLI::colorize($diagnostic->withComment()->cli))); WP_CLI::log(''); WP_CLI::log(WP_CLI::colorize(sprintf('%%b[%s]%%n', 'WordPress'))); $diagnostic = $diagnostics[Diagnostics::GENERAL]['host']; WP_CLI::log(sprintf('%s: %s', $diagnostic->name, WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::GENERAL]['filesystem']; WP_CLI::log(sprintf('%s: %s', $diagnostic->name, WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::VERSIONS]['php']; WP_CLI::log(sprintf('%s: %s', $diagnostic->name, WP_CLI::colorize($diagnostic->withComment()->cli))); WP_CLI::log(''); WP_CLI::log(WP_CLI::colorize(sprintf('%%b[%s]%%n', 'Redis'))); $diagnostic = $diagnostics[Diagnostics::GENERAL]['eviction-policy']; WP_CLI::log(sprintf('%s: %s', $diagnostic->name, WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::VERSIONS]['redis']; WP_CLI::log(sprintf('%s: %s', 'Version', WP_CLI::colorize($diagnostic->withComment()->cli))); if (extension_loaded('relay')) { WP_CLI::log(''); WP_CLI::log(WP_CLI::colorize(sprintf('%%b[%s]%%n', 'Relay'))); $diagnostic = $diagnostics[Diagnostics::RELAY]['relay-cache']; WP_CLI::log(sprintf('%s: %s', 'Cache', WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::RELAY]['relay-license']; WP_CLI::log(sprintf('%s: %s', 'License', WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::RELAY]['relay-eviction']; WP_CLI::log(sprintf('%s: %s', 'Eviction Policy', WP_CLI::colorize($diagnostic->withComment()->cli))); $diagnostic = $diagnostics[Diagnostics::VERSIONS]['relay']; WP_CLI::log(sprintf('%s: %s', 'Version', WP_CLI::colorize($diagnostic->withComment()->cli))); } } /** * Shows object cache status and diagnostics. * * ## EXAMPLES * * # Show object cache diagnostics. * $ wp redis diagnostics * * @return void */ public function diagnostics() { global $wp_object_cache; $diagnostics = (new Diagnostics($wp_object_cache))->withFilesystemAccess(); foreach ($diagnostics->toArray() as $groupName => $group) { if (empty($group)) { continue; } WP_CLI::log(WP_CLI::colorize( sprintf('%%b[%s]%%n', ucfirst($groupName)) )); foreach ($group as $diagnostic) { if ($groupName === Diagnostics::ERRORS) { WP_CLI::log(WP_CLI::colorize("%r{$diagnostic}%n")); } else { $value = WP_CLI::colorize($diagnostic->withComment()->cli); WP_CLI::log("{$diagnostic->name}: {$value}"); } } WP_CLI::log(''); } } /** * Flushes the object cache. * * ## OPTIONS * * [<site-id>...] * : One or more IDs of sites to flush. * * ## EXAMPLES * * # Flush the entire cache. * $ wp redis flush * Success: The object cache was flushed. * * # Flush multiple sites (networks only). * $ wp redis flush 42 1337 * Success: The object cache of site at 'https://example.org' was flushed. * Success: The object cache of site at 'https://help.example.org' was flushed. * * # Flush site by URL (networks only). * $ wp redis flush --url="https://example.org" * Success: The object cache of site at 'https://example.org' was flushed. * * @alias clear * * @param array<int, string> $arguments * @param array<mixed> $options * @return void */ public function flush($arguments, $options) { global $wp_object_cache; $this->abortIfNotConnected(); // unset site ids when environment is not a multisite if (! is_multisite()) { $arguments = []; } // flush cache of site set via `--url` option if (is_multisite() && empty($arguments) && WP_CLI::get_config('url')) { $arguments = [get_current_blog_id()]; } if (empty($arguments)) { try { $GLOBALS['ObjectCachePro']->logFlush(); $result = $wp_object_cache->flush(); } catch (Throwable $exception) { $result = false; } if (! $result) { WP_CLI::error('Object cache could not be flushed.'); } WP_CLI::success('Object cache flushed.'); return; } foreach ($arguments as $siteId) { try { $result = $wp_object_cache->flushBlog((int) $siteId); } catch (Throwable $exception) { WP_CLI::error($exception->getMessage()); } if ($result) { WP_CLI::success(WP_CLI::colorize( "Object cache of site [%y{$siteId}%n] was flushed." )); } else { WP_CLI::error(WP_CLI::colorize( "Object cache of site [%y{$siteId}%n] could not be flushed." ), false); } } } /** * Flushes one or more object cache groups. * * In multisite environments the group is always flushed for all sites. * * ## OPTIONS * * <group>... * : One or more groups to flush. * * ## EXAMPLES * * # Flush the entire cache. * $ wp redis flush-group comments * Success: "Object cache group [comments] was flushed. * * # Flush multiple groups. * $ wp redis flush-group posts post_meta * Success: "Object cache group [posts] was flushed. * Success: "Object cache group [post_meta] was flushed. * * @subcommand flush-group * * @param array<int, string> $arguments * @param array<mixed> $options * @return void */ public function flushGroup($arguments, $options) { global $wp_object_cache; $this->abortIfNotConnected(); foreach ($arguments as $group) { try { $GLOBALS['ObjectCachePro']->logGroupFlush($group); $result = $wp_object_cache->flush_group($group); } catch (Throwable $exception) { WP_CLI::error($exception->getMessage()); } if ($result) { WP_CLI::success(WP_CLI::colorize( "Object cache group [%y{$group}%n] was flushed." )); } else { WP_CLI::error(WP_CLI::colorize( "Object cache group [%y{$group}%n] could not be flushed." ), false); } } } /** * Resets the entire object cache database, including metadata and analytics. * * ## EXAMPLES * * # Resets the entire cache. * $ wp redis reset * Success: The object cache was reset. * * @param array<int, string> $arguments * @param array<mixed> $options * @return void */ public function reset($arguments, $options) { $GLOBALS['ObjectCachePro']->resetCache() ? WP_CLI::success('Object cache reset.') : WP_CLI::error('Object cache could not be reset.'); } /** * Launches `redis-cli` using WordPress configuration. * * ## EXAMPLES * * # Launch redis-cli. * $ wp redis cli * 127.0.0.1:6379> ping * PONG * * @alias shell * * @return void */ public function cli() { $this->abortIfNotConfigured(); $cliVersion = shell_exec('redis-cli -v'); if ($cliVersion && preg_match('/\d+\.\d+\.\d+/', $cliVersion, $matches)) { $cliVersion = $matches[0]; } else { WP_CLI::warning('Could not detect `redis-cli` version.'); $cliVersion = ''; } $config = Configuration::from(\WP_REDIS_CONFIG); $host = $config->host ?? '127.0.0.1'; $port = $config->port ?? 6379; $database = $config->database ?? 0; $username = $config->username; $password = $config->password; $info = (object) [ 'server' => null, 'scheme' => strtoupper($config->scheme), 'auth' => 'no password', ]; $command = 'redis-cli -n %s'; $arguments = [$database]; if (is_array($config->cluster)) { $command .= ' -c'; $primary = parse_url(reset($config->cluster)); $host = $primary['host']; // @phpstan-ignore-line $port = $primary['port']; // @phpstan-ignore-line if (strtolower($primary['scheme']) === 'tls') { // @phpstan-ignore-line $command .= ' --tls'; } } if ($config->sentinels) { WP_CLI::error('This command does not support Redis Sentinel.'); } if ($config->servers) { WP_CLI::error('This command does not support Redis replication.'); } $arguments[] = $host; if ($config->scheme === 'unix') { $command .= ' -s %s'; $info->server = "%y{$host}%n"; } else { $command .= ' -h %s -p %s'; $arguments[] = $port; $info->server = "%y{$host}%n:%y{$port}%n"; } if ($password) { $command .= ' -a %s'; $arguments[] = $password; $info->auth = 'with password'; } if ($username) { $command .= ' --user %s'; $arguments[] = $username; $info->auth = "as %y{$username}%n"; } if ($config->scheme === 'tls') { $command .= ' --tls'; } // The `--no-auth-warning` option was added in Redis 4.0 if (($username || $password) && version_compare($cliVersion, '4.0', '>=')) { $command .= ' --no-auth-warning'; } WP_CLI::log(WP_CLI::colorize( "Connecting via {$info->scheme} to {$info->server} ({$info->auth}) using database %y{$database}%n." )); $command = esc_cmd($command, ...$arguments); $process = proc_open_compat($command, [STDIN, STDOUT, STDERR], $pipes); exit(proc_close($process)); } /** * Watch object cache analytics as they happen. * * ## OPTIONS * * [<watcher>] * : The analytics watcher to use. * --- * default: digest * options: * - digest * - log * - aggregate * --- * * [--seconds=<number>] * : How many seconds of data to aggregate? * --- * default: 5 * --- * * [--metrics=<metrics>] * : Limit the output to specific metrics. * * ## AVAILABLE METRICS * * By default a different subset of metrics will be displayed for each watcher. * * These metrics are available: * * * hits * * misses * * hit-ratio * * bytes * * prefetches * * store-reads * * store-writes * * store-hits * * store-misses * * sql-queries * * ms-total * * ms-cache * * ms-cache-avg * * ms-cache-ratio * * redis-hits * * redis-misses * * redis-hit-ratio * * redis-ops-per-sec * * redis-evicted-keys * * redis-used-memory * * redis-used-memory-rss * * redis-memory-ratio * * redis-memory-fragmentation-ratio * * redis-connected-clients * * redis-tracking-clients * * redis-rejected-connections * * redis-keys * * relay-hits * * relay-misses * * relay-hit-ratio * * relay-ops-per-sec * * relay-keys * * relay-memory-used * * relay-memory-total * * relay-memory-human * * relay-memory-ratio * * ## EXAMPLES * * # Show an analytics digest. * $ wp redis watch * * # Tail analytics in log format. * $ wp redis watch log * * # Aggregate analytics * $ wp redis watch aggregate --seconds=2 * * @param array<int, string> $arguments * @param array<mixed> $options * @return WP_CLI\NoOp */ public function watch($arguments, $options) { global $wp_object_cache; $this->abortIfNotConfigured(); $this->abortIfNotConnected(); if ( ! $wp_object_cache instanceof ObjectCacheInterface || ! $wp_object_cache instanceof MeasuredObjectCacheInterface ) { WP_CLI::error('Object cache does not support analytics.'); } $config = Configuration::from(\WP_REDIS_CONFIG); if (! $config->analytics->enabled) { WP_CLI::error('Object cache analytics are disabled.'); } $options = array_merge([ 'compact' => false, 'seconds' => 4, 'metrics' => [], ], $options); if (is_string($options['metrics'])) { $options['metrics'] = explode(',', $options['metrics']); } switch ($arguments[0]) { default: case 'digest': if (Shell::isPiped()) { return new NoOp; } $monitor = new DigestWatcher("Showing digest of the last %g{$options['seconds']}s%n"); break; case 'log': $monitor = new LogWatcher('Waiting for measurements...'); break; case 'aggregate': $monitor = new AggregateWatcher("Showing %g{$options['seconds']}s%n aggregates..."); break; } $monitor->options = $options; $monitor->cache = $wp_object_cache; $monitor->usingRelay = $wp_object_cache->connection() instanceof RelayConnection; $nextTime = 0; while (true) { // @phpstan-ignore-line if ($nextTime) { usleep(50000); } if (microtime(true) < $nextTime) { $monitor->tick(); continue; } $nextTime = microtime(true) + 1; $monitor->prepare(); $monitor->tick(); } } /** * Returns the analytic values. * * ## OPTIONS * * [--interval=<number>] * : The interval in seconds. * --- * default: 60 * --- * * [--per_page=<number>] * : Maximum number of items to be returned in result set. * --- * default: 30 * --- * * [--page=<number>] * : Current page of the collection. * --- * default: 1 * --- * * [--fields=<metrics>] * : Limit the output to specific metrics and computations. * * [--pretty] * : Whether to pretty print the result. * --- * default: false * --- * * ## AVAILABLE METRICS * * These metrics are available: * * * hits * * misses * * hit-ratio * * bytes * * prefetches * * store-reads * * store-writes * * store-hits * * store-misses * * sql-queries * * ms-total * * ms-cache * * ms-cache-avg * * ms-cache-ratio * * redis-hits * * redis-misses * * redis-hit-ratio * * redis-ops-per-sec * * redis-evicted-keys * * redis-used-memory * * redis-used-memory-rss * * redis-memory-ratio * * redis-memory-fragmentation-ratio * * redis-connected-clients * * redis-tracking-clients * * redis-rejected-connections * * redis-keys * * relay-hits * * relay-misses * * relay-hit-ratio * * relay-ops-per-sec * * relay-keys * * relay-memory-used * * relay-memory-total * * relay-memory-human * * relay-memory-ratio * * ## EXAMPLES * * # Compute analytics for the last 30 minutes in 60 second intervals * $ wp redis analytics * * # Raw measurements for the last 30 minutes in 60 second intervals * $ wp redis analytics --context=raw * * # Compute hits and misses for the last hour * $ wp redis analytics --interval=3600 --per_page=1 --fields=hits,misses --pretty * * # Compute hit ratio median for the last hour in 10 minute intervals * $ wp redis analytics --interval=600 --per_page=6 --fields=hits.median,count,date_gmt * * @param array<int, string> $arguments * @param array<mixed> $options * @return void */ public function analytics($arguments, $options) { $this->abortIfNotConfigured(); $analytics = new Analytics; $defaults = array_map(static function ($param) { return $param['default']; }, $analytics->get_collection_params()); $options = array_merge([ 'pretty' => false, 'context' => $defaults['context'], 'interval' => $defaults['interval'], 'page' => $defaults['page'], 'per_page' => $defaults['per_page'], ], $options); $pretty = $options['pretty'] ? \JSON_PRETTY_PRINT : 0; $request = new WP_REST_Request; $request->set_param('context', (string) $options['context']); $request->set_param('interval', (int) $options['interval']); $request->set_param('page', (int) $options['page']); $request->set_param('per_page', (int) $options['per_page']); $fields = empty($options['fields']) ? $analytics->get_fields_for_response($request) : explode(',', $options['fields']); $request->set_param('_fields', $fields); $response = $analytics->get_items($request); if (is_wp_error($response)) { WP_CLI::line((string) json_encode([ 'code' => $response->get_error_code(), 'message' => $response->get_error_message(), 'data' => $response->get_error_data(), ], JSON_PRETTY_PRINT)); return; } WP_CLI::line((string) json_encode($response->get_data(), $pretty)); } /** * Abort if plugin wasn't configured. * * @return void */ protected function abortIfNotConfigured() { if (! defined('\WP_REDIS_CONFIG')) { WP_CLI::error(WP_CLI::colorize( 'The %yWP_REDIS_CONFIG%n constant has not been defined.' )); } } /** * Abort if object cache isn't connected. * * @return void */ protected function abortIfNotConnected() { global $wp_object_cache; $diagnostics = new Diagnostics($wp_object_cache); if (! $diagnostics->dropinExists()) { WP_CLI::error(WP_CLI::colorize( 'No object cache drop-in found. Run `%ywp redis enable%n` to enable the object cache.' )); } if (! $diagnostics->dropinIsValid()) { WP_CLI::error(WP_CLI::colorize( 'The object cache drop-in is invalid. Run `%ywp redis enable --force%n` to enable the object cache.' )); } } }