mailboxShareMapper->shareExists($ownerUserId, $accountId, $mailboxId, $shareType, $shareWith)) { throw new \InvalidArgumentException('Share already exists'); } $share = new MailboxShare(); $share->setOwnerUserId($ownerUserId); $share->setAccountId($accountId); $share->setMailboxId($mailboxId); $share->setShareType($shareType); $share->setShareWith($shareWith); $share->setPermission($permission); $share->setCreatedAt($this->timeFactory->getTime()); return $this->mailboxShareMapper->insert($share); } /** * Delete a share by id. Caller must ensure the current user is the owner. */ public function deleteShare(int $shareId, string $ownerUserId): void { $share = $this->mailboxShareMapper->find($shareId); if ($share->getOwnerUserId() !== $ownerUserId) { throw new \InvalidArgumentException('Not the share owner'); } $this->mailboxShareMapper->deleteById($shareId); } /** * List shares for a mailbox (owner view: who has access). * * @return MailboxShare[] */ public function getSharesForMailbox(string $ownerUserId, int $accountId, int $mailboxId): array { return $this->mailboxShareMapper->findByMailbox($ownerUserId, $accountId, $mailboxId); } /** * Build list "shared with me" for the given user: each item includes owner info, shared mailbox, and subfolders. * Resolves user + group ids for share_with, then loads owner's mailbox tree and attaches subfolders. * * @return array */ public function getSharedWithMe(string $currentUserId): array { $user = $this->userManager->get($currentUserId); if ($user === null) { return []; } $groupIds = $this->groupManager->getUserGroupIds($user); $shareWithIds = array_merge([$currentUserId], $groupIds); $shares = $this->mailboxShareMapper->findSharedWith($shareWithIds); if ($shares === []) { return []; } $result = []; foreach ($shares as $share) { try { $ownerAccount = $this->accountService->find($share->getOwnerUserId(), $share->getAccountId()); } catch (DoesNotExistException $e) { continue; } try { $mailboxes = $this->mailManager->getMailboxes($ownerAccount); } catch (\Throwable $e) { continue; } $sharedMailbox = null; foreach ($mailboxes as $mb) { if ((int)$mb->getId() === $share->getMailboxId()) { $sharedMailbox = $mb; break; } } if ($sharedMailbox === null) { continue; } $delimiter = $sharedMailbox->getDelimiter() ?? '.'; $prefix = $sharedMailbox->getName() . $delimiter; $subMailboxes = []; foreach ($mailboxes as $mb) { if ((int)$mb->getId() === (int)$sharedMailbox->getId()) { continue; } $name = $mb->getName(); if ($name !== $sharedMailbox->getName() && str_starts_with($name, $prefix)) { $subMailboxes[] = $this->mailboxToJson($mb); } } $ownerDisplayName = $currentUserId; $ownerUser = $this->userManager->get($share->getOwnerUserId()); if ($ownerUser !== null) { $ownerDisplayName = $ownerUser->getDisplayName(); } $result[] = [ 'shareId' => $share->getId(), 'ownerUserId' => $share->getOwnerUserId(), 'ownerDisplayName' => $ownerDisplayName, 'accountId' => $share->getAccountId(), 'mailbox' => $this->mailboxToJson($sharedMailbox), 'permission' => $share->getPermission(), 'subMailboxes' => $subMailboxes, ]; } return $result; } /** * Find a share by id and verify the current user has access (as share_with user or group member). * Returns the share entity or null. */ public function getShareForUser(int $shareId, string $currentUserId): ?MailboxShare { try { $share = $this->mailboxShareMapper->find($shareId); } catch (DoesNotExistException $e) { return null; } if ($share->getShareWith() === $currentUserId) { return $share; } if ($share->getShareType() === MailboxShare::TYPE_GROUP) { $user = $this->userManager->get($currentUserId); if ($user !== null && $this->groupManager->isInGroup($currentUserId, $share->getShareWith())) { return $share; } } return null; } /** * Check if the given mailbox is the shared root or a descendant of a shared mailbox for this user. * Returns the MailboxShare that grants access, or null. */ public function getShareForMailboxAccess(string $currentUserId, int $accountId, int $mailboxId): ?MailboxShare { $user = $this->userManager->get($currentUserId); if ($user === null) { return null; } try { $targetMailbox = $this->mailboxMapper->findById($mailboxId); } catch (DoesNotExistException $e) { return null; } if ($targetMailbox->getAccountId() !== $accountId) { return null; } $groupIds = $this->groupManager->getUserGroupIds($user); $shareWithIds = array_merge([$currentUserId], $groupIds); $shares = $this->mailboxShareMapper->findSharedWith($shareWithIds); foreach ($shares as $share) { if ($share->getAccountId() !== $accountId) { continue; } if ($share->getMailboxId() === $mailboxId) { return $share; } try { $rootMailbox = $this->mailboxMapper->findById($share->getMailboxId()); } catch (DoesNotExistException $e) { continue; } if ($rootMailbox->getAccountId() !== $accountId) { continue; } $delimiter = $rootMailbox->getDelimiter() ?? '.'; $prefix = $rootMailbox->getName() . $delimiter; $targetName = $targetMailbox->getName(); if ($targetName === $rootMailbox->getName() || str_starts_with($targetName, $prefix)) { return $share; } } return null; } /** * Resolve mailbox and account for the current user, optionally in shared context. * Returns ['mailbox' => Mailbox, 'account' => Account, 'share' => MailboxShare|null] or null if no access. * * @return array{mailbox: Mailbox, account: Account, share: MailboxShare|null}|null */ public function resolveMailboxForAccess(string $currentUserId, int $mailboxId, ?int $shareId = null): ?array { if ($currentUserId === '') { return null; } if ($shareId !== null) { $share = $this->getShareForUser($shareId, $currentUserId); if ($share === null) { return null; } $shareForMailbox = $this->getShareForMailboxAccess($currentUserId, $share->getAccountId(), $mailboxId); if ($shareForMailbox === null || $shareForMailbox->getId() !== $share->getId()) { return null; } try { $mailbox = $this->mailManager->getMailbox($share->getOwnerUserId(), $mailboxId); $account = $this->accountService->find($share->getOwnerUserId(), $share->getAccountId()); } catch (\Throwable $e) { return null; } return ['mailbox' => $mailbox, 'account' => $account, 'share' => $share]; } try { $mailbox = $this->mailManager->getMailbox($currentUserId, $mailboxId); $account = $this->accountService->find($currentUserId, $mailbox->getAccountId()); } catch (\Throwable $e) { return null; } return ['mailbox' => $mailbox, 'account' => $account, 'share' => null]; } /** * Resolve message, mailbox and account for the current user, optionally in shared context. * Returns ['message' => Message, 'mailbox' => Mailbox, 'account' => Account, 'share' => MailboxShare|null] or null if no access. * * @return array{message: Message, mailbox: Mailbox, account: Account, share: MailboxShare|null}|null */ public function resolveMessageAccess(string $currentUserId, int $messageId, ?int $shareId = null): ?array { if ($currentUserId === '') { return null; } $ownerUserId = $currentUserId; $share = null; if ($shareId !== null) { $share = $this->getShareForUser($shareId, $currentUserId); if ($share === null) { return null; } $ownerUserId = $share->getOwnerUserId(); } try { $message = $this->mailManager->getMessage($ownerUserId, $messageId); } catch (DoesNotExistException $e) { return null; } if ($share !== null) { $shareForMailbox = $this->getShareForMailboxAccess($currentUserId, $share->getAccountId(), $message->getMailboxId()); if ($shareForMailbox === null || $shareForMailbox->getId() !== $share->getId()) { return null; } } try { $mailbox = $this->mailManager->getMailbox($ownerUserId, $message->getMailboxId()); $account = $this->accountService->find($ownerUserId, $mailbox->getAccountId()); } catch (\Throwable $e) { return null; } return ['message' => $message, 'mailbox' => $mailbox, 'account' => $account, 'share' => $share]; } private function mailboxToJson(Mailbox $mailbox): array { $json = $mailbox->jsonSerialize(); return [ 'id' => $json['databaseId'], 'name' => $json['name'], 'displayName' => $json['displayName'], 'delimiter' => $json['delimiter'], 'unread' => $json['unread'], ]; } }