] * : Max attachments to migrate. 0 = unlimited. * * ## EXAMPLES * # See how many Smush-processed images exist * wp h4b-img migrate-from-smush --dry-run * * # Trust Smush's prior work — just mark them processed * wp h4b-img migrate-from-smush * * # Re-optimise everything from scratch through our pipeline * wp h4b-img migrate-from-smush --force-rescan * * # Also strip the Smush postmeta after migration * wp h4b-img migrate-from-smush --remove-smush-meta */ public function __invoke( $args, $assoc ): void { $dry_run = ! empty( $assoc['dry-run'] ); $force_rescan = ! empty( $assoc['force-rescan'] ); $remove_smush = ! empty( $assoc['remove-smush-meta'] ); $limit = (int) ( $assoc['limit'] ?? 0 ); global $wpdb; $base_sql = "SELECT pm.post_id, pm.meta_value, p.post_mime_type FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = %s ORDER BY pm.post_id ASC"; if ( $limit > 0 ) { $base_sql .= ' LIMIT ' . (int) $limit; } $rows = $wpdb->get_results( $wpdb->prepare( $base_sql, self::SMUSH_META_KEY ) ); $total = count( $rows ); \WP_CLI::log( "Found $total attachments with Smush optimisation history." ); if ( $total === 0 ) { \WP_CLI::success( 'Nothing to migrate.' ); return; } if ( $dry_run ) { $action = $force_rescan ? 'mark for re-optimisation' : 'mark as already processed'; $remove = $remove_smush ? ' + remove Smush meta' : ''; \WP_CLI::success( "Dry-run only. Would $action $total attachments$remove." ); return; } $progress = \WP_CLI\Utils\make_progress_bar( 'Migrating', $total ); $migrated = 0; $skipped = 0; $errored = 0; $removed_meta = 0; foreach ( $rows as $row ) { $id = (int) $row->post_id; try { if ( $force_rescan ) { // Wipe our marker so bulk treats this as unprocessed delete_post_meta( $id, Attachment_Meta::META_KEY ); } else { $existing = Attachment_Meta::get( $id ); if ( ! empty( $existing['processed_at'] ) ) { $skipped++; $progress->tick(); continue; } $stats = self::translate_smush_stats( (string) $row->meta_value ); $meta = [ 'version' => H4B_IMG_OPTIM_VERSION, 'processed_at' => current_time( 'c' ), 'migrated_from' => 'smush', 'tool_chain' => [ 'smush_legacy' ], 'sizes' => $stats['sizes'], 'totals' => $stats['totals'], 'note' => 'Migrated from Smush — not re-processed by h4b-image-optim.', ]; Attachment_Meta::set( $id, $meta ); } if ( $remove_smush ) { if ( delete_post_meta( $id, self::SMUSH_META_KEY ) ) { $removed_meta++; } } $migrated++; } catch ( \Throwable $e ) { $errored++; } $progress->tick(); } $progress->finish(); \WP_CLI::success( sprintf( "Migration done.\n Migrated: %d\n Already-marked (skipped): %d\n Errors: %d\n Smush meta removed: %d", $migrated, $skipped, $errored, $removed_meta ) ); } /** * Translate Smush's serialized stats into our schema. * * Smush stores: * a:2:{ * s:"stats" => [ time, bytes, percent, size_before, size_after, lossy, keep_exif, api_version ], * s:"sizes" => [ => stdClass{ time, bytes, percent, size_before, size_after }, ... ] * } */ private static function translate_smush_stats( string $raw ): array { $result = [ 'sizes' => [], 'totals' => [ 'bytes_saved' => 0, 'bytes_before' => 0, 'bytes_after' => 0, 'percent' => 0, ], ]; // Smush uses PHP serialize. Allow stdClass. $data = @unserialize( $raw, [ 'allowed_classes' => [ \stdClass::class ] ] ); if ( ! is_array( $data ) ) { return $result; } if ( isset( $data['stats'] ) && is_array( $data['stats'] ) ) { $result['totals']['bytes_saved'] = (int) ( $data['stats']['bytes'] ?? 0 ); $result['totals']['bytes_before'] = (int) ( $data['stats']['size_before'] ?? 0 ); $result['totals']['bytes_after'] = (int) ( $data['stats']['size_after'] ?? 0 ); $result['totals']['percent'] = (float) ( $data['stats']['percent'] ?? 0 ); } if ( isset( $data['sizes'] ) && is_array( $data['sizes'] ) ) { foreach ( $data['sizes'] as $size_key => $info ) { // $info can be stdClass or array $arr = is_object( $info ) ? get_object_vars( $info ) : (array) $info; $result['sizes'][ (string) $size_key ] = [ 'status' => 'migrated_from_smush', 'before' => (int) ( $arr['size_before'] ?? 0 ), 'after' => (int) ( $arr['size_after'] ?? 0 ), 'percent' => (float) ( $arr['percent'] ?? 0 ), 'icc_preserved' => false, // unknown — Smush stripped it 'tool_chain' => [ 'smush_legacy' ], 'webp' => null, 'avif' => null, 'avif_status' => 'never_generated', 'backup' => null, 'error' => null, ]; } } return $result; } }