generated from Nodarx/template
377 lines
9.1 KiB
PHP
377 lines
9.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace KTXM\ProviderImap\Client;
|
|
|
|
use DateTimeInterface;
|
|
use InvalidArgumentException;
|
|
|
|
final class SearchCriteriaBuilder
|
|
{
|
|
/**
|
|
* @var list<string>
|
|
*/
|
|
private array $criteria = [];
|
|
|
|
public static function create(): self
|
|
{
|
|
return new self();
|
|
}
|
|
|
|
public function all(): self
|
|
{
|
|
return $this->pushExpression('ALL');
|
|
}
|
|
|
|
public function answered(): self
|
|
{
|
|
return $this->pushExpression('ANSWERED');
|
|
}
|
|
|
|
public function unanswered(): self
|
|
{
|
|
return $this->pushExpression('UNANSWERED');
|
|
}
|
|
|
|
public function deleted(): self
|
|
{
|
|
return $this->pushExpression('DELETED');
|
|
}
|
|
|
|
public function undeleted(): self
|
|
{
|
|
return $this->pushExpression('UNDELETED');
|
|
}
|
|
|
|
public function draft(): self
|
|
{
|
|
return $this->pushExpression('DRAFT');
|
|
}
|
|
|
|
public function undraft(): self
|
|
{
|
|
return $this->pushExpression('UNDRAFT');
|
|
}
|
|
|
|
public function flagged(): self
|
|
{
|
|
return $this->pushExpression('FLAGGED');
|
|
}
|
|
|
|
public function unflagged(): self
|
|
{
|
|
return $this->pushExpression('UNFLAGGED');
|
|
}
|
|
|
|
public function seen(): self
|
|
{
|
|
return $this->pushExpression('SEEN');
|
|
}
|
|
|
|
public function unseen(): self
|
|
{
|
|
return $this->pushExpression('UNSEEN');
|
|
}
|
|
|
|
public function recent(): self
|
|
{
|
|
return $this->pushExpression('RECENT');
|
|
}
|
|
|
|
public function old(): self
|
|
{
|
|
return $this->pushExpression('OLD');
|
|
}
|
|
|
|
public function new(): self
|
|
{
|
|
return $this->pushExpression('NEW');
|
|
}
|
|
|
|
public function from(string $value): self
|
|
{
|
|
return $this->pushKeyValue('FROM', $value);
|
|
}
|
|
|
|
public function to(string $value): self
|
|
{
|
|
return $this->pushKeyValue('TO', $value);
|
|
}
|
|
|
|
public function cc(string $value): self
|
|
{
|
|
return $this->pushKeyValue('CC', $value);
|
|
}
|
|
|
|
public function bcc(string $value): self
|
|
{
|
|
return $this->pushKeyValue('BCC', $value);
|
|
}
|
|
|
|
public function subject(string $value): self
|
|
{
|
|
return $this->pushKeyValue('SUBJECT', $value);
|
|
}
|
|
|
|
public function body(string $value): self
|
|
{
|
|
return $this->pushKeyValue('BODY', $value);
|
|
}
|
|
|
|
public function text(string $value): self
|
|
{
|
|
return $this->pushKeyValue('TEXT', $value);
|
|
}
|
|
|
|
public function keyword(string $value): self
|
|
{
|
|
return $this->pushKeyValue('KEYWORD', $value, false);
|
|
}
|
|
|
|
public function unkeyword(string $value): self
|
|
{
|
|
return $this->pushKeyValue('UNKEYWORD', $value, false);
|
|
}
|
|
|
|
public function before(DateTimeInterface|string $value): self
|
|
{
|
|
return $this->pushDate('BEFORE', $value);
|
|
}
|
|
|
|
public function on(DateTimeInterface|string $value): self
|
|
{
|
|
return $this->pushDate('ON', $value);
|
|
}
|
|
|
|
public function since(DateTimeInterface|string $value): self
|
|
{
|
|
return $this->pushDate('SINCE', $value);
|
|
}
|
|
|
|
public function sentBefore(DateTimeInterface|string $value): self
|
|
{
|
|
return $this->pushDate('SENTBEFORE', $value);
|
|
}
|
|
|
|
public function sentOn(DateTimeInterface|string $value): self
|
|
{
|
|
return $this->pushDate('SENTON', $value);
|
|
}
|
|
|
|
public function sentSince(DateTimeInterface|string $value): self
|
|
{
|
|
return $this->pushDate('SENTSINCE', $value);
|
|
}
|
|
|
|
public function larger(int $value): self
|
|
{
|
|
return $this->pushNumber('LARGER', $value);
|
|
}
|
|
|
|
public function smaller(int $value): self
|
|
{
|
|
return $this->pushNumber('SMALLER', $value);
|
|
}
|
|
|
|
public function uid(int|string|SequenceSet $value): self
|
|
{
|
|
return $this->pushExpression('UID ' . $this->formatSequenceSet($value));
|
|
}
|
|
|
|
public function sequence(int|string|SequenceSet $value): self
|
|
{
|
|
return $this->pushExpression($this->formatSequenceSet($value));
|
|
}
|
|
|
|
public function header(string $name, string $value): self
|
|
{
|
|
return $this->pushExpression(sprintf(
|
|
'HEADER %s %s',
|
|
$this->formatString($name),
|
|
$this->formatString($value),
|
|
));
|
|
}
|
|
|
|
public function group(SearchCriteriaBuilder|array|string $criteria): self
|
|
{
|
|
$expression = $this->normalizeOperand($criteria, true);
|
|
if ($expression === '') {
|
|
return $this;
|
|
}
|
|
|
|
return $this->pushExpression($expression);
|
|
}
|
|
|
|
public function not(SearchCriteriaBuilder|array|string $criteria): self
|
|
{
|
|
$expression = $this->normalizeOperand($criteria, true);
|
|
if ($expression === '') {
|
|
throw new InvalidArgumentException('NOT search criteria cannot be empty.');
|
|
}
|
|
|
|
return $this->pushExpression('NOT ' . $expression);
|
|
}
|
|
|
|
public function or(SearchCriteriaBuilder|array|string $left, SearchCriteriaBuilder|array|string $right): self
|
|
{
|
|
$leftExpression = $this->normalizeOperand($left, true);
|
|
$rightExpression = $this->normalizeOperand($right, true);
|
|
|
|
if ($leftExpression === '' || $rightExpression === '') {
|
|
throw new InvalidArgumentException('OR search criteria requires two non-empty operands.');
|
|
}
|
|
|
|
return $this->pushExpression(sprintf('OR %s %s', $leftExpression, $rightExpression));
|
|
}
|
|
|
|
public function raw(string ...$criteria): self
|
|
{
|
|
$normalized = [];
|
|
|
|
foreach ($criteria as $criterion) {
|
|
$criterion = trim($criterion);
|
|
if ($criterion === '') {
|
|
continue;
|
|
}
|
|
|
|
$normalized[] = $criterion;
|
|
}
|
|
|
|
if ($normalized === []) {
|
|
return $this;
|
|
}
|
|
|
|
foreach ($normalized as $expression) {
|
|
$this->pushExpression($expression);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
public function build(): array
|
|
{
|
|
return $this->criteria === [] ? ['ALL'] : $this->criteria;
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
public function toArray(): array
|
|
{
|
|
return $this->build();
|
|
}
|
|
|
|
private function pushExpression(string $expression): self
|
|
{
|
|
$expression = trim($expression);
|
|
if ($expression !== '') {
|
|
$this->criteria[] = $expression;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
private function pushKeyValue(string $key, string $value, bool $quote = true): self
|
|
{
|
|
$value = trim($value);
|
|
if ($value === '') {
|
|
throw new InvalidArgumentException(sprintf('%s search criteria cannot be empty.', $key));
|
|
}
|
|
|
|
return $this->pushExpression(sprintf(
|
|
'%s %s',
|
|
$key,
|
|
$quote ? $this->formatString($value) : $value,
|
|
));
|
|
}
|
|
|
|
private function pushDate(string $key, DateTimeInterface|string $value): self
|
|
{
|
|
return $this->pushExpression(sprintf('%s %s', $key, $this->formatDate($value)));
|
|
}
|
|
|
|
private function pushNumber(string $key, int $value): self
|
|
{
|
|
if ($value < 0) {
|
|
throw new InvalidArgumentException(sprintf('%s search criteria must not be negative.', $key));
|
|
}
|
|
|
|
return $this->pushExpression(sprintf('%s %d', $key, $value));
|
|
}
|
|
|
|
private function normalizeOperand(SearchCriteriaBuilder|array|string $criteria, bool $wrapComposite): string
|
|
{
|
|
if ($criteria instanceof self) {
|
|
$expressions = $criteria->toArray();
|
|
return $this->combineExpressions($expressions, $wrapComposite);
|
|
}
|
|
|
|
if (is_string($criteria)) {
|
|
$criteria = trim($criteria);
|
|
return $criteria;
|
|
}
|
|
|
|
$normalized = [];
|
|
|
|
foreach ($criteria as $criterion) {
|
|
$criterion = trim((string) $criterion);
|
|
if ($criterion === '') {
|
|
continue;
|
|
}
|
|
|
|
$normalized[] = $criterion;
|
|
}
|
|
|
|
return $this->combineExpressions($normalized, $wrapComposite);
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $expressions
|
|
*/
|
|
private function combineExpressions(array $expressions, bool $wrapComposite): string
|
|
{
|
|
$expression = implode(' ', $expressions);
|
|
|
|
if ($expression === '') {
|
|
return '';
|
|
}
|
|
|
|
if (!$wrapComposite || count($expressions) === 1) {
|
|
return $expression;
|
|
}
|
|
|
|
return '(' . $expression . ')';
|
|
}
|
|
|
|
private function formatDate(DateTimeInterface|string $value): string
|
|
{
|
|
if ($value instanceof DateTimeInterface) {
|
|
return $value->format('d-M-Y');
|
|
}
|
|
|
|
$value = trim($value);
|
|
if ($value === '') {
|
|
throw new InvalidArgumentException('Search dates cannot be empty.');
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
private function formatSequenceSet(int|string|SequenceSet $value): string
|
|
{
|
|
return match (true) {
|
|
$value instanceof SequenceSet => $value->toCommand(),
|
|
is_int($value) => SequenceSet::single($value)->toCommand(),
|
|
default => SequenceSet::parse($value)->toCommand(),
|
|
};
|
|
}
|
|
|
|
private function formatString(string $value): string
|
|
{
|
|
return '"' . addcslashes($value, "\\\"") . '"';
|
|
}
|
|
} |