* : Key for the transient. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * [--network] * : Get the value of a network|site transient. On single site, this is * is a specially-named cache key. On multisite, this is a global cache * (instead of local to the site). * * ## EXAMPLES * * $ wp transient get sample_key * test data * * $ wp transient get random_key * Warning: Transient with key "random_key" is not set. */ public function get( $args, $assoc_args ) { list( $key ) = $args; $func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'get_site_transient' : 'get_transient'; $value = $func( $key ); if ( false === $value ) { WP_CLI::warning( 'Transient with key "' . $key . '" is not set.' ); exit; } WP_CLI::print_value( $value, $assoc_args ); } /** * Sets a transient value. * * `` is the time until expiration, in seconds. * * For a more complete explanation of the transient cache, including the * network|site cache, please see docs for `wp transient`. * * ## OPTIONS * * * : Key for the transient. * * * : Value to be set for the transient. * * [] * : Time until expiration, in seconds. * * [--network] * : Set the value of a network|site transient. On single site, this is * is a specially-named cache key. On multisite, this is a global cache * (instead of local to the site). * * ## EXAMPLES * * $ wp transient set sample_key "test data" 3600 * Success: Transient added. */ public function set( $args, $assoc_args ) { list( $key, $value ) = $args; $expiration = Utils\get_flag_value( $args, 2, 0 ); $func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'set_site_transient' : 'set_transient'; if ( $func( $key, $value, $expiration ) ) { WP_CLI::success( 'Transient added.' ); } else { WP_CLI::error( 'Transient could not be set.' ); } } /** * Deletes a transient value. * * For a more complete explanation of the transient cache, including the * network|site cache, please see docs for `wp transient`. * * ## OPTIONS * * [] * : Key for the transient. * * [--network] * : Delete the value of a network|site transient. On single site, this is * is a specially-named cache key. On multisite, this is a global cache * (instead of local to the site). * * [--all] * : Delete all transients. * * [--expired] * : Delete all expired transients. * * ## EXAMPLES * * # Delete transient. * $ wp transient delete sample_key * Success: Transient deleted. * * # Delete expired transients. * $ wp transient delete --expired * Success: 12 expired transients deleted from the database. * * # Delete expired site transients. * $ wp transient delete --expired --network * Success: 1 expired transient deleted from the database. * * # Delete all transients. * $ wp transient delete --all * Success: 14 transients deleted from the database. * * # Delete all site transients. * $ wp transient delete --all --network * Success: 2 transients deleted from the database. * * # Delete all transients in a multisite. * $ wp transient delete --all --network && wp site list --field=url | xargs -n1 -I % wp --url=% transient delete --all */ public function delete( $args, $assoc_args ) { $key = ( ! empty( $args ) ) ? $args[0] : null; $all = Utils\get_flag_value( $assoc_args, 'all' ); $expired = Utils\get_flag_value( $assoc_args, 'expired' ); $network = Utils\get_flag_value( $assoc_args, 'network' ); if ( true === $all ) { $this->delete_all( $network ); return; } elseif ( true === $expired ) { $this->delete_expired( $network ); return; } if ( ! $key ) { WP_CLI::error( 'Please specify transient key, or use --all or --expired.' ); } $func = $network ? 'delete_site_transient' : 'delete_transient'; if ( $func( $key ) ) { WP_CLI::success( 'Transient deleted.' ); } else { $func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'get_site_transient' : 'get_transient'; if ( $func( $key ) ) { WP_CLI::error( 'Transient was not deleted even though the transient appears to exist.' ); } else { WP_CLI::warning( 'Transient was not deleted; however, the transient does not appear to exist.' ); } } } /** * Determines the type of transients implementation. * * Indicates whether the transients API is using an object cache or the * database. * * For a more complete explanation of the transient cache, including the * network|site cache, please see docs for `wp transient`. * * ## EXAMPLES * * $ wp transient type * Transients are saved to the database. */ public function type() { if ( wp_using_ext_object_cache() ) { $message = 'Transients are saved to the object cache.'; } else { $message = 'Transients are saved to the database.'; } WP_CLI::line( $message ); } /** * Lists transients and their values. * * ## OPTIONS * * [--search=] * : Use wildcards ( * and ? ) to match transient name. * * [--exclude=] * : Pattern to exclude. Use wildcards ( * and ? ) to match transient name. * * [--network] * : Get the values of network|site transients. On single site, this is * a specially-named cache key. On multisite, this is a global cache * (instead of local to the site). * * [--unserialize] * : Unserialize transient values in output. * * [--human-readable] * : Human-readable output for expirations. * * [--fields=] * : Limit the output to specific object fields. * * [--format=] * : The serialization format for the value. * --- * default: table * options: * - table * - json * - csv * - count * - yaml * --- * * ## AVAILABLE FIELDS * * This field will be displayed by default for each matching option: * * * name * * value * * expiration * * ## EXAMPLES * * # List all transients * $ wp transient list * +------+-------+---------------+ * | name | value | expiration | * +------+-------+---------------+ * | foo | bar | 39 mins | * | foo2 | bar2 | no expiration | * | foo3 | bar2 | expired | * | foo4 | bar4 | 4 hours | * +------+-------+---------------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { global $wpdb; if ( wp_using_ext_object_cache() ) { WP_CLI::warning( 'Transients are stored in an external object cache, and this command only shows those stored in the database.' ); } $network = Utils\get_flag_value( $assoc_args, 'network', false ); $unserialize = Utils\get_flag_value( $assoc_args, 'unserialize', false ); $human_readable = Utils\get_flag_value( $assoc_args, 'human-readable', false ); $fields = array( 'name', 'value', 'expiration' ); if ( isset( $assoc_args['fields'] ) ) { $fields = explode( ',', $assoc_args['fields'] ); } $pattern = '%'; $exclude = ''; if ( isset( $assoc_args['search'] ) ) { $pattern = Utils\esc_like( $assoc_args['search'] ); // Substitute wildcards. $pattern = str_replace( array( '*', '?' ), array( '%', '_' ), $pattern ); } if ( isset( $assoc_args['exclude'] ) ) { $exclude = Utils\esc_like( $assoc_args['exclude'] ); // Substitute wildcards. $exclude = str_replace( array( '*', '?' ), array( '%', '_' ), $exclude ); } if ( $network ) { if ( is_multisite() ) { $where = $wpdb->prepare( 'WHERE `meta_key` LIKE %s', Utils\esc_like( '_site_transient_' ) . $pattern ); $where .= $wpdb->prepare( ' AND meta_key NOT LIKE %s', Utils\esc_like( '_site_transient_timeout_' ) . '%' ); if ( $exclude ) { $where .= $wpdb->prepare( ' AND meta_key NOT LIKE %s', Utils\esc_like( '_site_transient_' ) . $exclude ); } $query = "SELECT `meta_key` as `name`, `meta_value` as `value` FROM {$wpdb->sitemeta} {$where}"; } else { $where = $wpdb->prepare( 'WHERE `option_name` LIKE %s', Utils\esc_like( '_site_transient_' ) . $pattern ); $where .= $wpdb->prepare( ' AND option_name NOT LIKE %s', Utils\esc_like( '_site_transient_timeout_' ) . '%' ); if ( $exclude ) { $where .= $wpdb->prepare( ' AND option_name NOT LIKE %s', Utils\esc_like( '_site_transient_' ) . $exclude ); } $query = "SELECT `option_name` as `name`, `option_value` as `value` FROM {$wpdb->options} {$where}"; } } else { $where = $wpdb->prepare( 'WHERE `option_name` LIKE %s', Utils\esc_like( '_transient_' ) . $pattern ); $where .= $wpdb->prepare( ' AND option_name NOT LIKE %s', Utils\esc_like( '_transient_timeout_' ) . '%' ); if ( $exclude ) { $where .= $wpdb->prepare( ' AND option_name NOT LIKE %s', Utils\esc_like( '_transient_' ) . $exclude ); } $query = "SELECT `option_name` as `name`, `option_value` as `value` FROM {$wpdb->options} {$where}"; } // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Prepared properly above. $results = $wpdb->get_results( $query ); foreach ( $results as $result ) { $result->name = str_replace( array( '_site_transient_', '_transient_' ), '', $result->name ); $result->expiration = $this->get_transient_expiration( $result->name, $network, $human_readable ); if ( $unserialize ) { $result->value = maybe_unserialize( $result->value ); } } $formatter = new \WP_CLI\Formatter( $assoc_args, $fields ); $formatter->display_items( $results ); } /** * Get a nested value from a transient. * * ## OPTIONS * * * : Key for the transient. * * ... * : The name(s) of the keys within the value to locate the value to pluck. * * [--format=] * : The output format of the value. * --- * default: plaintext * options: * - plaintext * - json * - yaml * --- * * [--network] * : Get the value of a network|site transient. On single site, this is * a specially-named cache key. On multisite, this is a global cache * (instead of local to the site). */ public function pluck( $args, $assoc_args ) { list( $key ) = $args; $func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'get_site_transient' : 'get_transient'; $value = $func( $key ); if ( false === $value ) { WP_CLI::warning( 'Transient with key "' . $key . '" is not set.' ); exit; } $key_path = array_map( function ( $key ) { if ( is_numeric( $key ) && ( (string) intval( $key ) === $key ) ) { return (int) $key; } return $key; }, array_slice( $args, 1 ) ); $traverser = new RecursiveDataStructureTraverser( $value ); try { $value = $traverser->get( $key_path ); } catch ( \Exception $e ) { die( 1 ); } WP_CLI::print_value( $value, $assoc_args ); } /** * Update a nested value from a transient. * * ## OPTIONS * * * : Patch action to perform. * --- * options: * - insert * - update * - delete * --- * * * : Key for the transient. * * ... * : The name(s) of the keys within the value to locate the value to patch. * * [] * : The new value. If omitted, the value is read from STDIN. * * [--format=] * : The serialization format for the value. * --- * default: plaintext * options: * - plaintext * - json * --- * * [--expiration=] * : Time until expiration, in seconds. * * [--network] * : Get the value of a network|site transient. On single site, this is * a specially-named cache key. On multisite, this is a global cache * (instead of local to the site). */ public function patch( $args, $assoc_args ) { list( $action, $key ) = $args; $expiration = (int) Utils\get_flag_value( $assoc_args, 'expiration', 0 ); $read_func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'get_site_transient' : 'get_transient'; $write_func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'set_site_transient' : 'set_transient'; $key_path = array_map( function ( $key ) { if ( is_numeric( $key ) && ( (string) intval( $key ) === $key ) ) { return (int) $key; } return $key; }, array_slice( $args, 2 ) ); if ( 'delete' === $action ) { $patch_value = null; } else { $stdin_value = Utils\has_stdin() ? trim( WP_CLI::get_value_from_arg_or_stdin( $args, -1 ) ) : null; if ( ! empty( $stdin_value ) ) { $patch_value = WP_CLI::read_value( $stdin_value, $assoc_args ); } elseif ( count( $key_path ) > 1 ) { $patch_value = WP_CLI::read_value( array_pop( $key_path ), $assoc_args ); } else { $patch_value = null; } if ( null === $patch_value ) { WP_CLI::error( 'Please provide value to update.' ); } } /* Need to make a copy of $current_value here as it is modified by reference */ $old_value = $read_func( $key ); $current_value = $old_value; if ( is_object( $old_value ) ) { $current_value = clone $old_value; } $traverser = new RecursiveDataStructureTraverser( $current_value ); try { $traverser->$action( $key_path, $patch_value ); } catch ( \Exception $e ) { WP_CLI::error( $e->getMessage() ); } $patched_value = $traverser->value(); if ( $patched_value === $old_value ) { WP_CLI::success( "Value passed for transient '$key' is unchanged." ); } else { $success = $write_func( $key, $patched_value, $expiration ); if ( $success ) { WP_CLI::success( "Updated transient '$key'." ); } else { WP_CLI::error( "Could not update transient '$key'." ); } } } /** * Retrieves the expiration time. * * @param string $name Transient name. * @param bool $is_site_transient Optional. Whether this is a site transient. Default false. * @param bool $human_readable Optional. Whether to return the difference between now and the * expiration time in a human-readable format. Default false. * @return string Expiration time string. */ private function get_transient_expiration( $name, $is_site_transient = false, $human_readable = false ) { if ( $is_site_transient ) { if ( is_multisite() ) { $expiration = (int) get_site_option( '_site_transient_timeout_' . $name ); } else { $expiration = (int) get_option( '_site_transient_timeout_' . $name ); } } else { $expiration = (int) get_option( '_transient_timeout_' . $name ); } if ( 0 === $expiration ) { return $human_readable ? 'never expires' : 'false'; } if ( ! $human_readable ) { return $expiration; } $now = time(); if ( $now > $expiration ) { return 'expired'; } return human_time_diff( $now, $expiration ); } /** * Deletes all expired transients. * * Only deletes the expired transients from the database. * * @param bool $network Whether to delete transients or network|site transients. */ private function delete_expired( $network ) { global $wpdb; $count = 0; if ( ! $network ) { $count += $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b WHERE a.option_name LIKE %s AND a.option_name NOT LIKE %s AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) ) AND b.option_value < %d", Utils\esc_like( '_transient_' ) . '%', Utils\esc_like( '_transient_timeout_' ) . '%', time() ) ); } elseif ( ! is_multisite() ) { // Non-Multisite stores site transients in the options table. $count += $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b WHERE a.option_name LIKE %s AND a.option_name NOT LIKE %s AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) ) AND b.option_value < %d", Utils\esc_like( '_site_transient_' ) . '%', Utils\esc_like( '_site_transient_timeout_' ) . '%', time() ) ); } else { // Multisite stores site transients in the sitemeta table. $count += $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->sitemeta} a, {$wpdb->sitemeta} b WHERE a.meta_key LIKE %s AND a.meta_key NOT LIKE %s AND b.meta_key = CONCAT( '_site_transient_timeout_', SUBSTRING( a.meta_key, 17 ) ) AND b.meta_value < %d", Utils\esc_like( '_site_transient_' ) . '%', Utils\esc_like( '_site_transient_timeout_' ) . '%', time() ) ); } // The above queries delete the transient and the transient timeout // thus each transient is counted twice. $count = $count / 2; if ( $count > 0 ) { WP_CLI::success( sprintf( '%d expired %s deleted from the database.', $count, Utils\pluralize( 'transient', $count ) ) ); } else { WP_CLI::success( 'No expired transients found.' ); } if ( wp_using_ext_object_cache() ) { WP_CLI::warning( 'Transients are stored in an external object cache, and this command only deletes those stored in the database. You must flush the cache to delete all transients.' ); } } /** * Deletes all transients. * * Only deletes the transients from the database. * * @param bool $network Whether to delete transients or network|site transients. */ private function delete_all( $network ) { global $wpdb; // To ensure proper count values we first delete all transients with a timeout // and then the remaining transients without a timeout. $count = 0; if ( ! $network ) { $deleted = $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b WHERE a.option_name LIKE %s AND a.option_name NOT LIKE %s AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )", Utils\esc_like( '_transient_' ) . '%', Utils\esc_like( '_transient_timeout_' ) . '%' ) ); $count += $deleted / 2; // Ignore affected rows for timeouts. $count += $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name LIKE %s", Utils\esc_like( '_transient_' ) . '%' ) ); } elseif ( ! is_multisite() ) { // Non-Multisite stores site transients in the options table. $deleted = $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b WHERE a.option_name LIKE %s AND a.option_name NOT LIKE %s AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )", Utils\esc_like( '_site_transient_' ) . '%', Utils\esc_like( '_site_transient_timeout_' ) . '%' ) ); $count += $deleted / 2; // Ignore affected rows for timeouts. $count += $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name LIKE %s", Utils\esc_like( '_site_transient_' ) . '%' ) ); } else { // Multisite stores site transients in the sitemeta table. $deleted = $wpdb->query( $wpdb->prepare( "DELETE a, b FROM {$wpdb->sitemeta} a, {$wpdb->sitemeta} b WHERE a.meta_key LIKE %s AND a.meta_key NOT LIKE %s AND b.meta_key = CONCAT( '_site_transient_timeout_', SUBSTRING( a.meta_key, 17 ) )", Utils\esc_like( '_site_transient_' ) . '%', Utils\esc_like( '_site_transient_timeout_' ) . '%' ) ); $count += $deleted / 2; // Ignore affected rows for timeouts. $count += $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->sitemeta WHERE meta_key LIKE %s", Utils\esc_like( '_site_transient_' ) . '%' ) ); } if ( $count > 0 ) { WP_CLI::success( sprintf( '%d %s deleted from the database.', $count, Utils\pluralize( 'transient', $count ) ) ); } else { WP_CLI::success( 'No transients found.' ); } if ( wp_using_ext_object_cache() ) { WP_CLI::warning( 'Transients are stored in an external object cache, and this command only deletes those stored in the database. You must flush the cache to delete all transients.' ); } } }