* * @package H4B\ImageOptim */ namespace H4B\ImageOptim; if ( ! defined( 'ABSPATH' ) ) { exit; } final class CLI { public static function register(): void { \WP_CLI::add_command( 'h4b-img', __CLASS__ ); \WP_CLI::add_command( 'h4b-img bulk', CLI_Bulk::class ); \WP_CLI::add_command( 'h4b-img rescue', CLI_Rescue::class ); } /** * Show tool detection + plugin status. * * ## EXAMPLES * wp h4b-img status */ public function status( $args, $assoc ): void { $tools = Tools::detect(); \WP_CLI::log( 'h4b-image-optim v' . H4B_IMG_OPTIM_VERSION ); \WP_CLI::log( '' ); \WP_CLI::log( 'External tools:' ); foreach ( $tools as $name => $path ) { $mark = $path ? '✓' : '✗'; \WP_CLI::log( sprintf( ' %s %-12s %s', $mark, $name, $path ?: '(not found)' ) ); } $missing = Tools::missing_required(); if ( ! empty( $missing ) ) { \WP_CLI::warning( 'Missing required tools: ' . implode( ', ', $missing ) ); } \WP_CLI::log( '' ); \WP_CLI::log( 'Settings:' ); foreach ( Settings::all() as $k => $v ) { if ( is_array( $v ) ) { $v = '[' . implode( ',', $v ) . ']'; } elseif ( is_bool( $v ) ) { $v = $v ? 'true' : 'false'; } \WP_CLI::log( sprintf( ' %-28s %s', $k, $v ) ); } \WP_CLI::log( '' ); \WP_CLI::log( 'Originals dir: ' . Optimizer::originals_root() ); \WP_CLI::log( ' exists: ' . ( is_dir( Optimizer::originals_root() ) ? 'yes' : 'no' ) ); } /** * Optimise a single attachment by ID. * * ## OPTIONS * * --id= * : Attachment post ID. * * [--dry-run] * : Report what would happen without writing. * * ## EXAMPLES * wp h4b-img optimise --id=12345 * wp h4b-img optimise --id=12345 --dry-run */ public function optimise( $args, $assoc ): void { $id = (int) ( $assoc['id'] ?? 0 ); if ( $id <= 0 ) { \WP_CLI::error( '--id required' ); } $dry_run = ! empty( $assoc['dry-run'] ); $metadata = wp_get_attachment_metadata( $id ); if ( ! $metadata ) { \WP_CLI::error( "No metadata for attachment $id" ); } $uploads = wp_get_upload_dir(); $basedir = trailingslashit( $uploads['basedir'] ); $relative = get_post_meta( $id, '_wp_attached_file', true ); if ( ! $relative ) { \WP_CLI::error( "Attachment $id has no _wp_attached_file" ); } $full_path = $basedir . $relative; if ( ! is_readable( $full_path ) ) { \WP_CLI::error( "Not readable: $full_path" ); } \WP_CLI::log( "Attachment $id: $relative" ); \WP_CLI::log( ' full path: ' . $full_path ); $summary = [ 'full' => self::run_one( $id, 'full', $full_path, $dry_run ), ]; $dir = dirname( $full_path ); foreach ( ( $metadata['sizes'] ?? [] ) as $size_key => $size_data ) { if ( empty( $size_data['file'] ) ) { continue; } $path = trailingslashit( $dir ) . $size_data['file']; if ( ! is_readable( $path ) ) { continue; } $summary[ $size_key ] = self::run_one( $id, (string) $size_key, $path, $dry_run ); } // Tabular summary $rows = []; foreach ( $summary as $size => $r ) { $rows[] = [ 'size' => $size, 'status' => $r['status'] ?? '', 'before' => $r['before'] ?? 0, 'after' => $r['after'] ?? 0, 'pct' => $r['percent'] ?? 0, 'icc' => ! empty( $r['icc_preserved'] ) ? '✓' : '', 'webp' => $r['webp_size'] ?? '', 'avif' => $r['avif_status'] ?? '', 'error' => $r['error'] ?? '', ]; } \WP_CLI\Utils\format_items( 'table', $rows, [ 'size', 'status', 'before', 'after', 'pct', 'icc', 'webp', 'avif', 'error' ] ); } private static function run_one( int $id, string $size_key, string $path, bool $dry_run ): array { if ( $dry_run ) { return [ 'status' => 'dry-run', 'before' => filesize( $path ), 'after' => filesize( $path ), 'percent' => 0, ]; } $opt = Optimizer::optimise( $path ); $webp_stats = [ 'status' => 'skipped' ]; $avif_stats = [ 'status' => 'skipped' ]; $generate_siblings = in_array( $opt['status'], [ 'done', 'skipped' ], true ); if ( $generate_siblings && Settings::get( 'generate_webp', true ) ) { $webp_stats = Format_Generator::make_webp( $path ); } if ( $generate_siblings && Settings::get( 'generate_avif', true ) ) { $avif_stats = Format_Generator::make_avif( $path ); } $record = [ 'status' => $opt['status'], 'before' => $opt['before'], 'after' => $opt['after'], 'percent' => $opt['percent'], 'icc_preserved' => $opt['icc_preserved'] ?? false, 'tool_chain' => $opt['tool_chain'] ?? [], 'webp' => $webp_stats['status'] === 'done' ? $webp_stats['size'] : null, 'avif' => $avif_stats['status'] === 'done' ? $avif_stats['size'] : null, 'avif_status' => $avif_stats['status'], 'backup' => $opt['backup_path'] ?? null, 'error' => $opt['error'] ?? null, ]; Attachment_Meta::record_size( $id, $size_key, $record ); return [ 'status' => $opt['status'], 'before' => $opt['before'], 'after' => $opt['after'], 'percent' => $opt['percent'], 'icc_preserved' => $opt['icc_preserved'] ?? false, 'webp_size' => $webp_stats['status'] === 'done' ? $webp_stats['size'] : '', 'avif_status' => $avif_stats['status'], 'error' => $opt['error'] ?? null, ]; } }