] * : The cache type to clear. If omitted, clears all caches. * --- * options: * - avatar * - media * - emoji * - all * default: all * --- * * [--yes] * : Skip confirmation prompt. * * ## EXAMPLES * * # Clear all caches * $ wp activitypub cache clear * * # Clear only avatar cache * $ wp activitypub cache clear --type=avatar * * # Clear emoji cache without confirmation * $ wp activitypub cache clear --type=emoji --yes * * @subcommand clear * * @param array $args The positional arguments. * @param array $assoc_args The associative arguments. * * @return void */ public function clear( $args, $assoc_args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $type = $assoc_args['type'] ?? 'all'; $types_to_clear = array(); if ( 'all' === $type ) { $types_to_clear = array( 'avatar', 'media', 'emoji' ); } else { $types_to_clear = array( $type ); } $type_label = 'all' === $type ? 'all cache types' : "{$type} cache"; \WP_CLI::confirm( "Are you sure you want to clear {$type_label}?", $assoc_args ); $total_cleared = 0; foreach ( $types_to_clear as $cache_type ) { $cleared = $this->clear_cache_type( $cache_type ); $total_cleared += $cleared; \WP_CLI::log( \sprintf( 'Cleared %d %s cache directories.', $cleared, $cache_type ) ); } \WP_CLI::success( \sprintf( 'Cache cleared. Total directories removed: %d', $total_cleared ) ); } /** * Show cache status and statistics. * * Displays information about cached files including count and total size * for each cache type. * * ## OPTIONS * * [--format=] * : Output format. * --- * default: table * options: * - table * - json * - csv * - yaml * --- * * ## EXAMPLES * * # Show cache status * $ wp activitypub cache status * * # Show status as JSON * $ wp activitypub cache status --format=json * * @subcommand status * * @param array $args The positional arguments. * @param array $assoc_args The associative arguments. * * @return void */ public function status( $args, $assoc_args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $upload_dir = \wp_upload_dir(); $cache_types = array( 'avatar' => array( 'label' => 'Avatars', 'path' => Avatar::get_base_dir(), ), 'media' => array( 'label' => 'Post Media', 'path' => Media::get_base_dir(), ), 'emoji' => array( 'label' => 'Emoji', 'path' => Emoji::get_base_dir(), ), ); $data = array(); $total_files = 0; $total_size = 0; foreach ( $cache_types as $type => $info ) { $dir = $upload_dir['basedir'] . $info['path']; $stats = $this->get_directory_stats( $dir ); $data[] = array( 'type' => $info['label'], 'enabled' => $this->is_cache_enabled( $type ) ? 'Yes' : 'No', 'files' => $stats['files'], 'size' => \size_format( $stats['size'] ), 'path' => $info['path'], ); $total_files += $stats['files']; $total_size += $stats['size']; } // Add totals row for table format. if ( 'table' === ( $assoc_args['format'] ?? 'table' ) ) { $data[] = array( 'type' => '---', 'enabled' => '---', 'files' => '---', 'size' => '---', 'path' => '---', ); $data[] = array( 'type' => 'TOTAL', 'enabled' => '', 'files' => $total_files, 'size' => \size_format( $total_size ), 'path' => '/activitypub/', ); } $format = $assoc_args['format'] ?? 'table'; \WP_CLI\Utils\format_items( $format, $data, array( 'type', 'enabled', 'files', 'size', 'path' ) ); } /** * Remove duplicate cached images left behind by earlier versions. * * Preview by default; --delete removes numbered copies, promoting the newest * if the original is missing. Unreadable directories or failed file operations * cause an error after all selected cache types have been processed. * * ## OPTIONS * * [--type=] * : The cache type to clean up. If omitted, cleans up all caches. * --- * options: * - avatar * - media * - emoji * - all * default: all * --- * * [--delete] * : Remove the duplicate files. Without it, only report them. * * ## EXAMPLES * * # See what would be removed * $ wp activitypub cache cleanup * * # Remove duplicate avatars * $ wp activitypub cache cleanup --type=avatar --delete * * @subcommand cleanup * * @param array $args The positional arguments. * @param array $assoc_args The associative arguments. * * @return void */ public function cleanup( $args, $assoc_args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $type = $assoc_args['type'] ?? 'all'; $delete = \WP_CLI\Utils\get_flag_value( $assoc_args, 'delete', false ); $base_dirs = array( 'avatar' => Avatar::get_base_dir(), 'media' => Media::get_base_dir(), 'emoji' => Emoji::get_base_dir(), ); if ( 'all' !== $type ) { $base_dirs = \array_intersect_key( $base_dirs, array( $type => true ) ); } $upload_dir = \wp_upload_dir(); $totals = array( 'removed' => 0, 'bytes' => 0, 'promoted' => 0, 'failed' => 0, ); foreach ( $base_dirs as $cache_type => $base_dir ) { $result = File::remove_duplicates( $upload_dir['basedir'] . $base_dir, $delete ); \WP_CLI::log( \sprintf( '%s: %s %d duplicate file(s), %s; %s %d file(s) to the original name.', $cache_type, $delete ? 'removed' : 'would remove', $result['removed'], \size_format( $result['bytes'] ), $delete ? 'moved' : 'would move', $result['promoted'] ) ); foreach ( $totals as $key => $value ) { $totals[ $key ] = $value + $result[ $key ]; } } if ( $totals['failed'] > 0 ) { \WP_CLI::error( \sprintf( 'Cleanup incomplete: %d file(s) or directory scan(s) could not be read, moved or removed; check the permissions and run again.', $totals['failed'] ) ); return; } if ( $delete ) { \WP_CLI::success( \sprintf( 'Removed %d duplicate file(s), %s.', $totals['removed'], \size_format( $totals['bytes'] ) ) ); } elseif ( $totals['removed'] + $totals['promoted'] > 0 ) { \WP_CLI::log( 'Run again with --delete to remove them.' ); } else { \WP_CLI::success( 'No duplicate files found.' ); } } /** * Clear a specific cache type. * * @param string $type The cache type (avatar, media, emoji). * * @return int Number of directories cleared. */ private function clear_cache_type( $type ) { $upload_dir = \wp_upload_dir(); switch ( $type ) { case 'avatar': $base_dir = $upload_dir['basedir'] . Avatar::get_base_dir(); break; case 'media': $base_dir = $upload_dir['basedir'] . Media::get_base_dir(); break; case 'emoji': $base_dir = $upload_dir['basedir'] . Emoji::get_base_dir(); break; default: return 0; } if ( ! \is_dir( $base_dir ) ) { return 0; } // Count subdirectories before clearing. $subdirs = \glob( $base_dir . '/*', GLOB_ONLYDIR ); $count = $subdirs ? \count( $subdirs ) : 0; // Remove all subdirectories using the cache's native delete_directory helper. foreach ( $subdirs as $subdir ) { File::delete_directory( $subdir ); } // Clean up legacy avatar URL meta from previous versions. if ( 'avatar' === $type ) { \delete_metadata( 'post', 0, '_activitypub_avatar_url', '', true ); } return $count; } /** * Get statistics for a cache directory. * * @param string $dir The directory path. * * @return array { * Directory statistics. * * @type int $files Total number of files. * @type int $size Total size in bytes. * } */ private function get_directory_stats( $dir ) { $stats = array( 'files' => 0, 'size' => 0, ); if ( ! \is_dir( $dir ) ) { return $stats; } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $dir, \RecursiveDirectoryIterator::SKIP_DOTS ), \RecursiveIteratorIterator::LEAVES_ONLY ); foreach ( $iterator as $file ) { if ( $file->isFile() ) { ++$stats['files']; $stats['size'] += $file->getSize(); } } return $stats; } /** * Check if a cache type is enabled. * * @param string $type The cache type. * * @return bool True if enabled. */ private function is_cache_enabled( $type ) { // Check global cache enablement (includes constant and activitypub_remote_cache_enabled filter). if ( ! Cache::is_enabled() ) { return false; } // Check type-specific filter. return (bool) \apply_filters( "activitypub_cache_{$type}_enabled", true ); } }