diff --git a/lib/Client/MessagePart.php b/lib/Client/MessagePart.php
index ab1f608..1ea05c1 100644
--- a/lib/Client/MessagePart.php
+++ b/lib/Client/MessagePart.php
@@ -168,7 +168,7 @@ final class MessagePart
$data = [
'partId' => $this->partId,
'blobId' => $this->partId,
- 'cId' => $this->contentId,
+ 'cid' => $this->contentId,
'type' => $this->mimeType,
'charset' => $this->parameters['charset'] ?? null,
'name' => $this->parameters['name'] ?? $this->dispositionParameters['filename'] ?? null,
diff --git a/lib/Providers/MessageAttachment.php b/lib/Providers/MessageAttachment.php
index f504973..7db5050 100644
--- a/lib/Providers/MessageAttachment.php
+++ b/lib/Providers/MessageAttachment.php
@@ -121,10 +121,20 @@ class MessageAttachment implements MessagePartInterface {
public function getBlobId(): ?string { return $this->_meta->getBlobId(); }
public function getId(): ?string { return $this->_meta->getId(); }
+ public function getSize(): ?int { return $this->_meta->getSize(); }
public function getDisposition(): ?string { return $this->_meta->getDisposition(); }
+ public function getContentId(): ?string { return $this->_meta->getContentId(); }
public function getCharset(): ?string { return $this->_meta->getCharset(); }
public function getLanguage(): ?string { return $this->_meta->getLanguage(); }
public function getLocation(): ?string { return $this->_meta->getLocation(); }
+ public function getContent(): ?string { return $this->_contents; }
public function getParts(): array { return $this->_meta->getParts(); }
- public function jsonSerialize(): array { return $this->_meta->jsonSerialize(); }
+ public function jsonSerialize(): array {
+ $data = $this->_meta->jsonSerialize();
+ if ($this->_contents !== null) {
+ $data['content'] = $this->_contents;
+ }
+
+ return $data;
+ }
}
diff --git a/lib/Providers/MessagePart.php b/lib/Providers/MessagePart.php
index 616e6e6..8ba4192 100644
--- a/lib/Providers/MessagePart.php
+++ b/lib/Providers/MessagePart.php
@@ -17,6 +17,24 @@ use KTXM\ProviderImap\Client\MessagePart as ImapMessagePart;
*/
class MessagePart extends MessagePartMutableAbstract {
+ /**
+ * @param array $data
+ */
+ private function hydrateArray(array $data): static {
+ $this->data = $data;
+
+ if (isset($this->data['subParts']) && is_array($this->data['subParts'])) {
+ foreach ($this->data['subParts'] as $entry) {
+ if (is_array($entry)) {
+ $this->parts[] = (new self())->hydrateArray($entry);
+ }
+ }
+ unset($this->data['subParts']);
+ }
+
+ return $this;
+ }
+
/**
* Convert gricob BodyStructure part to message part object
*
@@ -24,82 +42,11 @@ class MessagePart extends MessagePartMutableAbstract {
* @param string $partId numeric part identifier (e.g. "1", "1.1", "2")
*/
public function fromImap(ImapMessagePart $part, string $partId = '1'): static {
+ $data = $part->toArray();
+ $data['partId'] = $partId;
+ $data['blobId'] = $data['blobId'] ?? $partId;
- $this->data['partId'] = $partId;
-
- if ($part instanceof SinglePart) {
- $mimeType = strtolower($part->type) . '/' . strtolower($part->subtype);
- $this->data['type'] = $mimeType;
-
- if ($part->id !== null) {
- $this->data['blobId'] = trim($part->id, '<>');
- }
-
- // Content-Type parameters (name, charset, etc.)
- if (!empty($part->attributes)) {
- foreach ($part->attributes as $key => $value) {
- $keyLower = strtolower($key);
- if ($keyLower === 'name') {
- $this->data['name'] = $value;
- } elseif ($keyLower === 'charset') {
- $this->data['charset'] = $value;
- }
- }
- }
-
- if ($part->encoding !== null) {
- $this->data['encoding'] = strtolower($part->encoding);
- }
-
- if ($part->size !== null) {
- $this->data['size'] = $part->size;
- }
-
- if ($part->disposition !== null) {
- $this->data['disposition'] = strtolower($part->disposition->type);
- // disposition filename attribute
- if (!empty($part->disposition->attributes)) {
- foreach ($part->disposition->attributes as $key => $value) {
- if (strtolower($key) === 'filename') {
- $this->data['name'] = $this->data['name'] ?? $value;
- }
- }
- }
- }
-
- if (!empty($part->language)) {
- $this->data['language'] = implode(',', $part->language);
- }
-
- if ($part->location !== null) {
- $this->data['location'] = $part->location;
- }
-
- } elseif ($part instanceof MultiPart) {
- $this->data['type'] = 'multipart/' . strtolower($part->subtype);
-
- if ($part->disposition !== null) {
- $this->data['disposition'] = strtolower($part->disposition->type);
- }
-
- if (!empty($part->language)) {
- $this->data['language'] = implode(',', $part->language);
- }
-
- if ($part->location !== null) {
- $this->data['location'] = $part->location;
- }
-
- // Recursively process sub-parts
- // When this part has no section ID (root multipart) children are
- // numbered "1", "2", … to match IMAP section numbering.
- foreach ($part->parts as $index => $subPart) {
- $subPartId = ($partId === '') ? (string)($index + 1) : $partId . '.' . ($index + 1);
- $this->parts[] = (new MessagePart())->fromImap($subPart, $subPartId);
- }
- }
-
- return $this;
+ return $this->hydrateArray($data);
}
}
diff --git a/lib/Providers/MessageProperties.php b/lib/Providers/MessageProperties.php
index b784da6..42093b0 100644
--- a/lib/Providers/MessageProperties.php
+++ b/lib/Providers/MessageProperties.php
@@ -82,36 +82,16 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
}
if ($message->bodyStructure() !== null) {
- $this->data[static::PROPERTY_BODY] = $message->bodyStructure()->toArray();
+ $body = $message->bodyStructure()->withInjectedSections($message->bodySections() ?? [])->toArray();
+ $this->data[static::PROPERTY_BODY] = $body;
$attachments = [];
- $this->collectAttachments($message->bodyStructure(), $attachments);
+ $this->collectAttachments($body, $attachments);
if ($attachments !== []) {
$this->data[static::PROPERTY_ATTACHMENTS] = $attachments;
}
}
- if ($message->bodyStructure() !== null) {
- $this->data[static::PROPERTY_BODY] = $message->bodyStructure()->toArray();
- // Recursively add content from bodyValues to matching parts
- if (is_array($message->bodySections())) {
- $addContentToParts = function(&$structure, $bodyValues) use (&$addContentToParts) {
- // If this part has a partId and matching bodyValue, add content
- if (isset($structure['partId']) && isset($bodyValues[$structure['partId']])) {
- $structure['content'] = $bodyValues[$structure['partId']] ?? null;
- }
- // Recursively process subParts
- if (isset($structure['subParts']) && is_array($structure['subParts'])) {
- foreach ($structure['subParts'] as &$subPart) {
- $addContentToParts($subPart, $bodyValues);
- }
- }
- };
-
- $addContentToParts($this->data[static::PROPERTY_BODY], $message->bodySections());
- }
- }
-
$this->data[static::PROPERTY_FLAGS] = [];
foreach ($message->flags() as $flag) {
$flag = ltrim($flag, '\\');
@@ -132,9 +112,9 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
/**
* Recursively collect attachment parts from body structure
*/
- private function collectAttachments(ClientMessagePart $part, array &$attachments): void
+ private function collectAttachments(array $part, array &$attachments): void
{
- $children = $part->parts();
+ $children = $part['subParts'] ?? [];
if ($children !== []) {
foreach ($children as $childPart) {
$this->collectAttachments($childPart, $attachments);
@@ -142,9 +122,9 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
return;
}
- $mimeType = strtolower($part->mimeType());
- $disposition = strtolower($part->disposition() ?? '');
- $name = $part->parameters()['name'] ?? $part->dispositionParameters()['filename'] ?? null;
+ $mimeType = strtolower((string)($part['type'] ?? ''));
+ $disposition = strtolower((string)($part['disposition'] ?? ''));
+ $name = $part['name'] ?? null;
$isInlineText = str_starts_with($mimeType, 'text/')
&& in_array($mimeType, ['text/plain', 'text/html'], true)
&& $disposition !== 'attachment';
@@ -153,7 +133,7 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
return;
}
- $attachments[] = $part->toArray();
+ $attachments[] = $part;
}
}
diff --git a/lib/Providers/Provider.php b/lib/Providers/Provider.php
index 27312d7..e57c175 100644
--- a/lib/Providers/Provider.php
+++ b/lib/Providers/Provider.php
@@ -49,10 +49,10 @@ class Provider implements ProviderBaseInterface, ProviderServiceMutateInterface,
public function jsonSerialize(): array
{
return [
- self::JSON_PROPERTY_TYPE => self::JSON_TYPE,
- self::JSON_PROPERTY_IDENTIFIER => self::PROVIDER_IDENTIFIER,
- self::JSON_PROPERTY_LABEL => self::PROVIDER_LABEL,
- self::JSON_PROPERTY_CAPABILITIES => $this->providerAbilities,
+ self::PROPERTY_TYPE => self::JSON_TYPE,
+ self::PROPERTY_IDENTIFIER => self::PROVIDER_IDENTIFIER,
+ self::PROPERTY_LABEL => self::PROVIDER_LABEL,
+ self::PROPERTY_CAPABILITIES => $this->providerAbilities,
];
}
diff --git a/lib/Providers/Service.php b/lib/Providers/Service.php
index e7cd46d..f8c387b 100644
--- a/lib/Providers/Service.php
+++ b/lib/Providers/Service.php
@@ -44,9 +44,6 @@ use KTXF\Resource\Identifier\EntityIdentifierInterface;
use KTXM\ProviderImap\Providers\EntityResource;
use KTXM\ProviderImap\Client\Mailbox;
-/**
- * IMAP Mail Service
- */
class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceConfigurableInterface, ServiceCollectionMutableInterface, ServiceEntityMutableInterface
{
private const PROVIDER_IDENTIFIER = 'imap';
@@ -56,7 +53,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
private ?string $serviceIdentifier = null;
private ?string $serviceLabel = null;
private bool $serviceEnabled = false;
- private string $primaryAddress = '';
+ private array $primaryAddress = [];
private array $secondaryAddresses = [];
private ?ServiceLocation $location = null;
private ?ServiceIdentityBasic $identity = null;
@@ -109,6 +106,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
self::CAPABILITY_ENTITY_DELETE => true,
self::CAPABILITY_ENTITY_MOVE => true,
self::CAPABILITY_ENTITY_COPY => false,
+ 'EntityTransmit' => true,
];
private RemoteMailService $remoteService;
@@ -169,17 +167,17 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
public function jsonSerialize(): array
{
return array_filter([
- self::JSON_PROPERTY_TYPE => self::JSON_TYPE,
- self::JSON_PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER,
- self::JSON_PROPERTY_IDENTIFIER => $this->serviceIdentifier,
- self::JSON_PROPERTY_LABEL => $this->serviceLabel,
- self::JSON_PROPERTY_ENABLED => $this->serviceEnabled,
- self::JSON_PROPERTY_CAPABILITIES => $this->serviceAbilities,
- self::JSON_PROPERTY_PRIMARY_ADDRESS => $this->primaryAddress,
- self::JSON_PROPERTY_SECONDARY_ADDRESSES => $this->secondaryAddresses,
- self::JSON_PROPERTY_LOCATION => $this->location?->jsonSerialize(),
- self::JSON_PROPERTY_IDENTITY => $this->identity?->jsonSerialize(),
- self::JSON_PROPERTY_AUXILIARY => $this->auxiliary,
+ self::PROPERTY_TYPE => self::JSON_TYPE,
+ self::PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER,
+ self::PROPERTY_IDENTIFIER => $this->serviceIdentifier,
+ self::PROPERTY_LABEL => $this->serviceLabel,
+ self::PROPERTY_ENABLED => $this->serviceEnabled,
+ self::PROPERTY_CAPABILITIES => $this->serviceAbilities,
+ self::PROPERTY_PRIMARY_ADDRESS => $this->primaryAddress,
+ self::PROPERTY_SECONDARY_ADDRESSES => $this->secondaryAddresses,
+ self::PROPERTY_LOCATION => $this->location?->jsonSerialize(),
+ self::PROPERTY_IDENTITY => $this->identity?->jsonSerialize(),
+ self::PROPERTY_AUXILIARY => $this->auxiliary,
], fn($v) => $v !== null);
}
@@ -189,29 +187,29 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
}
- if (isset($data[self::JSON_PROPERTY_ENABLED])) {
- $this->setEnabled($data[self::JSON_PROPERTY_ENABLED]);
+ if (isset($data[self::PROPERTY_ENABLED])) {
+ $this->setEnabled($data[self::PROPERTY_ENABLED]);
}
- if (isset($data[self::JSON_PROPERTY_LABEL])) {
- $this->setLabel($data[self::JSON_PROPERTY_LABEL]);
+ if (isset($data[self::PROPERTY_LABEL])) {
+ $this->setLabel($data[self::PROPERTY_LABEL]);
}
- if (isset($data[self::JSON_PROPERTY_LOCATION])) {
- $this->setLocation($this->freshLocation(null, $data[self::JSON_PROPERTY_LOCATION]));
+ if (isset($data[self::PROPERTY_LOCATION])) {
+ $this->setLocation($this->freshLocation(null, $data[self::PROPERTY_LOCATION]));
}
- if (isset($data[self::JSON_PROPERTY_IDENTITY])) {
- $this->setIdentity($this->freshIdentity(null, $data[self::JSON_PROPERTY_IDENTITY]));
+ if (isset($data[self::PROPERTY_IDENTITY])) {
+ $this->setIdentity($this->freshIdentity(null, $data[self::PROPERTY_IDENTITY]));
}
- if (isset($data[self::JSON_PROPERTY_PRIMARY_ADDRESS]) && is_string($data[self::JSON_PROPERTY_PRIMARY_ADDRESS])) {
- $this->setPrimaryAddress(new Address($data[self::JSON_PROPERTY_PRIMARY_ADDRESS]));
+ if (isset($data[self::PROPERTY_PRIMARY_ADDRESS]) && is_string($data[self::PROPERTY_PRIMARY_ADDRESS])) {
+ $this->setPrimaryAddress(new Address($data[self::PROPERTY_PRIMARY_ADDRESS]));
}
- if (isset($data[self::JSON_PROPERTY_SECONDARY_ADDRESSES]) && is_array($data[self::JSON_PROPERTY_SECONDARY_ADDRESSES])) {
+ if (isset($data[self::PROPERTY_SECONDARY_ADDRESSES]) && is_array($data[self::PROPERTY_SECONDARY_ADDRESSES])) {
$this->setSecondaryAddresses(array_map(
fn($addr) => new Address(is_array($addr) ? ($addr['address'] ?? $addr) : $addr),
- $data[self::JSON_PROPERTY_SECONDARY_ADDRESSES]
+ $data[self::PROPERTY_SECONDARY_ADDRESSES]
));
}
- if (isset($data[self::JSON_PROPERTY_AUXILIARY]) && is_array($data[self::JSON_PROPERTY_AUXILIARY])) {
- $this->setAuxiliary($data[self::JSON_PROPERTY_AUXILIARY]);
+ if (isset($data[self::PROPERTY_AUXILIARY]) && is_array($data[self::PROPERTY_AUXILIARY])) {
+ $this->setAuxiliary($data[self::PROPERTY_AUXILIARY]);
}
return $this;
@@ -261,23 +259,28 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
public function getPrimaryAddress(): AddressInterface
{
- return new Address($this->primaryAddress);
+ return Address::fromArray($this->primaryAddress);
}
public function setPrimaryAddress(AddressInterface $value): static
{
- $this->primaryAddress = $value->getAddress();
+ $this->primaryAddress = $value->toArray();
return $this;
}
public function getSecondaryAddresses(): array
{
- return $this->secondaryAddresses;
+ return array_map(
+ fn($addr) => $addr instanceof AddressInterface ? $addr : Address::fromArray(is_array($addr) ? $addr : ['address' => (string) $addr])
+ , $this->secondaryAddresses);
}
public function setSecondaryAddresses(array $addresses): static
{
- $this->secondaryAddresses = $addresses;
+ $this->secondaryAddresses = array_map(
+ fn($addr) => $addr instanceof AddressInterface ? $addr : Address::fromArray(is_array($addr) ? $addr : ['address' => (string) $addr]),
+ $addresses
+ );
return $this;
}
@@ -285,7 +288,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
{
$address = strtolower(trim($address));
- if ($this->primaryAddress && strtolower($this->primaryAddress) === $address) {
+ if ($this->primaryAddress && strtolower($this->primaryAddress['address'] ?? '') === $address) {
return true;
}
foreach ($this->secondaryAddresses as $secondary) {
@@ -580,7 +583,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
foreach ($identifiers as $collection => $entities) {
$uids = array_keys($entities);
- foreach ($this->remoteService->entityFetch((string) $collection, ...$uids) as $uid => $message) {
+ foreach ($this->remoteService->entityFetch((string) $collection, null, ...$uids) as $uid => $message) {
$resource = $this->entityFresh();
$resource->fromImap($message, $collection);
yield $resource->urn() => $resource;
diff --git a/src/components/ImapAuxiliaryPanel.vue b/src/components/ImapAuxiliaryPanel.vue
index be0b76e..555f5a7 100644
--- a/src/components/ImapAuxiliaryPanel.vue
+++ b/src/components/ImapAuxiliaryPanel.vue
@@ -1,6 +1,7 @@
@@ -165,23 +179,66 @@ function sameAddresses(service: ServiceObject, nextPrimaryAddress: string, nextS
Configure the primary mailbox identity and any additional sender aliases exposed by this service.
-
+ Primary Address
+
+
+
+
-
+ Secondary Addresses
+
+
+
+
+ mdi-delete-outline
+ Remove Alias
+
+
+
+
+ Add Alias
+