feat: implement meta and content cache

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-09-29 19:45:00 -04:00
parent e5e76351a5
commit d28800a04e
8 changed files with 715 additions and 22 deletions
+104 -3
View File
@@ -35,9 +35,9 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
$this->data[static::PROPERTY_IN_REPLY_TO] = $message->inReplyTo();
}
//if ($message->references() !== []) {
// $this->data[static::PROPERTY_REFERENCES] = $message->references();
//}
if ($message->references() !== []) {
$this->data[static::PROPERTY_REFERENCES] = $message->references();
}
$receivedAt = $message->receivedAt() ?? $message->internalDate();
if ($receivedAt !== null) {
@@ -109,6 +109,107 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
return $this;
}
// ── Cache (meta store / content store) ───────────────────────────────────
/**
* Serialise the fields the meta store needs for list, filter and sort.
*
* Dates are normalised to UTC so they sort correctly as strings; the original
* values (with their offsets) are kept in the content store.
*/
public function toCacheMeta(): array
{
return [
'received' => self::cacheDate($this->data[static::PROPERTY_RECEIVED] ?? null),
'sent' => self::cacheDate($this->data[static::PROPERTY_SENT] ?? null),
'size' => $this->data[static::PROPERTY_SIZE] ?? 0,
'subject' => $this->data[static::PROPERTY_SUBJECT] ?? '',
'from' => $this->data[static::PROPERTY_FROM] ?? null,
'to' => $this->data[static::PROPERTY_TO] ?? [],
'cc' => $this->data[static::PROPERTY_CC] ?? [],
'urid' => $this->data[static::PROPERTY_URID] ?? null,
'inReplyTo' => $this->data[static::PROPERTY_IN_REPLY_TO] ?? null,
'references' => $this->data[static::PROPERTY_REFERENCES] ?? [],
// list of set flags, not a map: keywords such as $Forwarded are not valid field names
'flags' => array_keys(array_filter($this->data[static::PROPERTY_FLAGS] ?? [])),
'hasAttachments' => ($this->data[static::PROPERTY_ATTACHMENTS] ?? []) !== [],
'preview' => $this->cachePreview(),
];
}
/**
* Restore the mutable fields from a meta store document.
*
* Only flags are taken from the meta store; everything else comes from the
* content store, which holds the original values.
*/
public function fromCacheMeta(array $document): static
{
$this->data[static::PROPERTY_FLAGS] = array_fill_keys(
array_map('strval', (array) ($document['flags'] ?? [])),
true,
);
return $this;
}
/**
* Serialise the immutable message content for the content store.
*/
public function toCacheContent(): array
{
$content = $this->data;
unset($content[static::PROPERTY_FLAGS]);
return $content;
}
/**
* Restore the immutable message content from the content store, keeping any flags already set.
*/
public function fromCacheContent(array $content): static
{
unset($content[static::PROPERTY_FLAGS]);
$flags = $this->data[static::PROPERTY_FLAGS] ?? null;
$this->data = $content;
if ($flags !== null) {
$this->data[static::PROPERTY_FLAGS] = $flags;
}
return $this;
}
private function cachePreview(int $length = 200): string
{
$text = $this->getBodyTextPlain();
if ($text === null) {
$html = $this->getBodyTextHtml();
$text = $html !== null
? html_entity_decode(strip_tags((string) preg_replace('/<(style|script)\b[^>]*>.*?<\/\1>/is', ' ', $html)), ENT_QUOTES | ENT_HTML5, 'UTF-8')
: '';
}
$text = trim((string) preg_replace('/\s+/u', ' ', $text));
return mb_substr($text, 0, $length);
}
private static function cacheDate(?string $value): ?string
{
if ($value === null || $value === '') {
return null;
}
try {
return (new DateTimeImmutable($value))
->setTimezone(new \DateTimeZone('UTC'))
->format('Y-m-d\TH:i:s\Z');
} catch (\Exception) {
return null;
}
}
/**
* Recursively collect attachment parts from body structure
*/