*/ final class SortCommand implements CommandInterface { /** * @param SearchCriteriaBuilder|list $criteria * @param list $sortCriteria */ public function __construct( private readonly array $sortCriteria, private readonly SearchCriteriaBuilder|array $criteria = ['ALL'], private readonly IdentifierMode $identifierMode = IdentifierMode::Sequence, private readonly string $charset = 'UTF-8', ) {} public function name(): string { return 'SORT'; } public function allowedStates(): array { return [SessionState::Selected]; } public function encode(string $tag, SessionContext $context): RequestFrame { unset($tag, $context); $sortCriteria = $this->normalizeSortCriteria($this->sortCriteria); if ($sortCriteria === []) { throw new ImapException('SORT requires at least one sort criterion.'); } $criteria = $this->normalizeCriteria( $this->criteria instanceof SearchCriteriaBuilder ? $this->criteria->toArray() : $this->criteria, ); $command = $this->identifierMode === IdentifierMode::Uid ? 'UID SORT' : 'SORT'; $command .= sprintf( ' (%s) %s %s', implode(' ', $sortCriteria), strtoupper(trim($this->charset)), implode(' ', $criteria), ); return new RequestFrame($command); } public function handle(ResponseStream $responses, SessionContext $context): SortResult { if ($context->selectedMailbox() === null) { throw new ImapException('SORT requires a selected mailbox.'); } $matches = []; foreach ($responses as $response) { if ($response instanceof UntaggedResponse && $response->label() === 'SORT') { $matches = $this->parseMatches($response->payloadTokens()); continue; } if ($response instanceof TaggedResponse) { if (!$response->isOk()) { throw new ImapException('SORT failed: ' . $response->text()); } return new SortResult($matches, $this->identifierMode); } } throw new ImapException('SORT did not receive a tagged completion response.'); } /** * @param list $criteria * @return list */ private function normalizeCriteria(array $criteria): array { $normalized = []; foreach ($criteria as $criterion) { $criterion = trim($criterion); if ($criterion === '') { continue; } $normalized[] = $criterion; } return $normalized === [] ? ['ALL'] : $normalized; } /** * @param list $criteria * @return list */ private function normalizeSortCriteria(array $criteria): array { $normalized = []; foreach ($criteria as $criterion) { $criterion = strtoupper(trim($criterion)); if ($criterion === '') { continue; } $normalized[] = $criterion; } return $normalized; } /** * @param list $tokens * @return list */ private function parseMatches(array $tokens): array { $matches = []; foreach ($tokens as $token) { if (preg_match('/^[1-9]\d*$/', $token) !== 1) { continue; } $matches[] = (int) $token; } return $matches; } }