generated from Nodarx/template
refactor: use custom imap client
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
243
lib/Client/Command/MessageTransferCommand.php
Normal file
243
lib/Client/Command/MessageTransferCommand.php
Normal file
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXM\ProviderImap\Client\Command;
|
||||
|
||||
use KTXM\ProviderImap\Client\Command\Result\MessageTransferResult;
|
||||
use KTXM\ProviderImap\Client\FetchTarget;
|
||||
use KTXM\ProviderImap\Client\IdentifierMode;
|
||||
use KTXM\ProviderImap\Client\ImapException;
|
||||
use KTXM\ProviderImap\Client\Protocol\RequestFrame;
|
||||
use KTXM\ProviderImap\Client\Protocol\Response\TaggedResponse;
|
||||
use KTXM\ProviderImap\Client\Protocol\Response\UntaggedResponse;
|
||||
use KTXM\ProviderImap\Client\Protocol\ResponseStream;
|
||||
use KTXM\ProviderImap\Client\SequenceSet;
|
||||
use KTXM\ProviderImap\Client\SessionContext;
|
||||
use KTXM\ProviderImap\Client\SessionState;
|
||||
|
||||
/**
|
||||
* @implements CommandInterface<MessageTransferResult>
|
||||
*/
|
||||
final class MessageTransferCommand implements CommandInterface
|
||||
{
|
||||
private readonly string $operation;
|
||||
private readonly SequenceSet $sequenceSet;
|
||||
private readonly IdentifierMode $identifierMode;
|
||||
|
||||
public function __construct(
|
||||
string $operation,
|
||||
FetchTarget|string|SequenceSet|null $target = null,
|
||||
private readonly string $destinationMailbox = '',
|
||||
) {
|
||||
$resolvedTarget = match (true) {
|
||||
$target instanceof FetchTarget => $target,
|
||||
$target instanceof SequenceSet => FetchTarget::sequence($target),
|
||||
is_string($target) => FetchTarget::sequence($target),
|
||||
default => FetchTarget::all(),
|
||||
};
|
||||
|
||||
$this->operation = strtoupper(trim($operation));
|
||||
|
||||
if (!in_array($this->operation, ['COPY', 'MOVE'], true)) {
|
||||
throw new ImapException('Unsupported transfer operation: ' . $this->operation);
|
||||
}
|
||||
|
||||
$this->sequenceSet = $resolvedTarget->sequenceSet();
|
||||
$this->identifierMode = $resolvedTarget->identifierMode();
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return $this->operation;
|
||||
}
|
||||
|
||||
public function allowedStates(): array
|
||||
{
|
||||
return [SessionState::Selected];
|
||||
}
|
||||
|
||||
public function encode(string $tag, SessionContext $context): RequestFrame
|
||||
{
|
||||
unset($tag, $context);
|
||||
|
||||
return new RequestFrame(sprintf(
|
||||
'%s%s %s %s',
|
||||
$this->identifierMode === IdentifierMode::Uid ? 'UID ' : '',
|
||||
$this->operation,
|
||||
$this->sequenceSet->toCommand(),
|
||||
$this->quote($this->destinationMailbox),
|
||||
));
|
||||
}
|
||||
|
||||
public function handle(ResponseStream $responses, SessionContext $context): MessageTransferResult
|
||||
{
|
||||
if ($context->selectedMailbox() === null) {
|
||||
throw new ImapException($this->operation . ' requires a selected mailbox.');
|
||||
}
|
||||
|
||||
$responseCodes = [];
|
||||
$copyUid = null;
|
||||
$tryCreate = false;
|
||||
$highestModSeq = null;
|
||||
$expunged = [];
|
||||
$vanished = [];
|
||||
|
||||
foreach ($responses as $response) {
|
||||
if ($response instanceof UntaggedResponse) {
|
||||
$this->collectUntaggedData(
|
||||
$response,
|
||||
$responseCodes,
|
||||
$copyUid,
|
||||
$tryCreate,
|
||||
$highestModSeq,
|
||||
$expunged,
|
||||
$vanished,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($response instanceof TaggedResponse) {
|
||||
$this->collectResponseCode(
|
||||
'tagged',
|
||||
$response->text(),
|
||||
$responseCodes,
|
||||
$copyUid,
|
||||
$tryCreate,
|
||||
$highestModSeq,
|
||||
);
|
||||
|
||||
$result = new MessageTransferResult(
|
||||
$response->status(),
|
||||
$response->text(),
|
||||
$responseCodes,
|
||||
$copyUid,
|
||||
$tryCreate,
|
||||
$highestModSeq,
|
||||
$expunged,
|
||||
$vanished,
|
||||
);
|
||||
|
||||
if (!$response->isOk()) {
|
||||
throw new ImapException($this->operation . ' failed: ' . $response->text());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ImapException($this->operation . ' did not receive a tagged completion response.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{source:string, name:string, arguments:list<string>, text:string}> $responseCodes
|
||||
* @param ?array{uidValidity:string, sourceUids:string, destinationUids:string} $copyUid
|
||||
* @param list<int> $expunged
|
||||
* @param list<array{earlier:bool, knownUids:string}> $vanished
|
||||
*/
|
||||
private function collectUntaggedData(
|
||||
UntaggedResponse $response,
|
||||
array &$responseCodes,
|
||||
?array &$copyUid,
|
||||
bool &$tryCreate,
|
||||
?string &$highestModSeq,
|
||||
array &$expunged,
|
||||
array &$vanished,
|
||||
): void {
|
||||
$label = strtoupper($response->label());
|
||||
|
||||
if (in_array($label, ['OK', 'NO', 'BAD', 'BYE', 'PREAUTH'], true)) {
|
||||
$this->collectResponseCode(
|
||||
'untagged',
|
||||
$response->payload(),
|
||||
$responseCodes,
|
||||
$copyUid,
|
||||
$tryCreate,
|
||||
$highestModSeq,
|
||||
);
|
||||
}
|
||||
|
||||
if (preg_match('/^\*\s+(\d+)\s+EXPUNGE$/i', $response->raw(), $matches) === 1) {
|
||||
$expunged[] = (int) $matches[1];
|
||||
return;
|
||||
}
|
||||
|
||||
if (preg_match('/^\*\s+VANISHED(?:\s+\((EARLIER)\))?\s+(.+)$/i', $response->raw(), $matches) === 1) {
|
||||
$vanished[] = [
|
||||
'earlier' => isset($matches[1]) && strtoupper($matches[1]) === 'EARLIER',
|
||||
'knownUids' => trim($matches[2]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{source:string, name:string, arguments:list<string>, text:string}> $responseCodes
|
||||
* @param ?array{uidValidity:string, sourceUids:string, destinationUids:string} $copyUid
|
||||
*/
|
||||
private function collectResponseCode(
|
||||
string $source,
|
||||
string $text,
|
||||
array &$responseCodes,
|
||||
?array &$copyUid,
|
||||
bool &$tryCreate,
|
||||
?string &$highestModSeq,
|
||||
): void {
|
||||
$responseCode = $this->parseResponseCode($text);
|
||||
if ($responseCode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$responseCodes[] = [
|
||||
'source' => $source,
|
||||
'name' => $responseCode['name'],
|
||||
'arguments' => $responseCode['arguments'],
|
||||
'text' => $responseCode['text'],
|
||||
];
|
||||
|
||||
if ($responseCode['name'] === 'TRYCREATE') {
|
||||
$tryCreate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($responseCode['name'] === 'HIGHESTMODSEQ' && isset($responseCode['arguments'][0])) {
|
||||
$highestModSeq = $responseCode['arguments'][0];
|
||||
return;
|
||||
}
|
||||
|
||||
if ($responseCode['name'] !== 'COPYUID' || count($responseCode['arguments']) < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
$copyUid = [
|
||||
'uidValidity' => $responseCode['arguments'][0],
|
||||
'sourceUids' => $responseCode['arguments'][1],
|
||||
'destinationUids' => $responseCode['arguments'][2],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?array{name:string, arguments:list<string>, text:string}
|
||||
*/
|
||||
private function parseResponseCode(string $text): ?array
|
||||
{
|
||||
$text = trim($text);
|
||||
|
||||
if (preg_match('/^\[([A-Z0-9.-]+)(?:\s+([^\]]+))?\](?:\s*(.*))?$/i', $text, $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$arguments = trim($matches[2] ?? '');
|
||||
|
||||
return [
|
||||
'name' => strtoupper($matches[1]),
|
||||
'arguments' => $arguments === '' ? [] : (preg_split('/\s+/', $arguments) ?: []),
|
||||
'text' => trim($matches[3] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
private function quote(string $value): string
|
||||
{
|
||||
return '"' . addcslashes($value, "\\\"") . '"';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user