diff --git a/CHANGELOG.md b/CHANGELOG.md index 0985cd3a0..2b6678693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [5.3.0] - tbd ### New - Add label to public shares + - Send all unsent invitations to a poll with one click (resolves contact groups and circles too) ### Fixes - Fix API calls ### Changes diff --git a/appinfo/routes.php b/appinfo/routes.php index 8c6ead5a2..86cfe5acd 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -89,6 +89,7 @@ ['name' => 'share#list', 'url' => '/poll/{pollId}/shares', 'verb' => 'GET'], ['name' => 'share#add', 'url' => '/poll/{pollId}/share', 'verb' => 'POST'], + ['name' => 'share#send_all_invitations', 'url' => '/poll/{pollId}/inviteAll', 'verb' => 'PUT'], ['name' => 'share#delete', 'url' => '/share/{token}', 'verb' => 'DELETE'], ['name' => 'share#personal', 'url' => '/share/personal', 'verb' => 'POST'], ['name' => 'share#sendInvitation', 'url' => '/share/{token}/invite', 'verb' => 'POST'], diff --git a/lib/Command/Share/Add.php b/lib/Command/Share/Add.php index ab770dbbf..990e5ec46 100644 --- a/lib/Command/Share/Add.php +++ b/lib/Command/Share/Add.php @@ -95,7 +95,7 @@ private function inviteUsers(Poll $poll, array $userIds): void { foreach ($userIds as $userId) { try { $share = $this->shareService->add($poll->getId(), User::TYPE, $userId); - $this->shareService->sendInvitation($share->getToken()); + $this->shareService->sendInvitation($share); } catch (ShareAlreadyExistsException $e) { // silently ignore already existing shares } @@ -110,7 +110,7 @@ private function inviteGroups(Poll $poll, array $groupIds): void { foreach ($groupIds as $groupId) { try { $share = $this->shareService->add($poll->getId(), Group::TYPE, $groupId); - $this->shareService->sendInvitation($share->getToken()); + $this->shareService->sendInvitation($share); } catch (ShareAlreadyExistsException $e) { // silently ignore already existing shares } @@ -125,7 +125,7 @@ private function inviteEmails(Poll $poll, array $emails): void { foreach ($emails as $email) { try { $share = $this->shareService->add($poll->getId(), Email::TYPE, $email); - $this->shareService->sendInvitation($share->getToken()); + $this->shareService->sendInvitation($share); } catch (ShareAlreadyExistsException $e) { // silently ignore already existing shares } diff --git a/lib/Command/Share/Remove.php b/lib/Command/Share/Remove.php index 519ed0a2a..adf574e5f 100644 --- a/lib/Command/Share/Remove.php +++ b/lib/Command/Share/Remove.php @@ -95,7 +95,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function removeUsers(Poll $poll, array $userIds): void { foreach ($this->getUserShares($poll) as $share) { if (in_array($share->getUserId(), $userIds, true)) { - $this->shareService->delete($share->getToken()); + $this->shareService->delete($share); } } } @@ -107,7 +107,7 @@ private function removeUsers(Poll $poll, array $userIds): void { private function removeGroups(Poll $poll, array $groupIds): void { foreach ($this->getGroupShares($poll) as $share) { if (in_array($share->getUserId(), $groupIds, true)) { - $this->shareService->delete($share->getToken()); + $this->shareService->delete($share); } } } @@ -119,7 +119,7 @@ private function removeGroups(Poll $poll, array $groupIds): void { private function removeEmails(Poll $poll, array $emails): void { foreach ($this->getEmailShares($poll) as $share) { if (in_array($share->getEmailAddress(), $emails, true)) { - $this->shareService->delete($share->getToken()); + $this->shareService->delete($share); } } } diff --git a/lib/Controller/PublicController.php b/lib/Controller/PublicController.php index 57d389251..95256db70 100644 --- a/lib/Controller/PublicController.php +++ b/lib/Controller/PublicController.php @@ -299,8 +299,10 @@ public function deleteEmailAddress(string $token): JSONResponse { * @PublicPage */ public function register(string $token, string $userName, string $emailAddress = '', string $timeZone = ''): JSONResponse { + $publicShare = $this->shareService->get($token); + $personalShare = $this->shareService->register($publicShare, $userName, $emailAddress, $timeZone); return $this->responseCreate(fn () => [ - 'share' => $this->shareService->register($this->shareService->get($token), $userName, $emailAddress, $timeZone) + 'share' => $personalShare, ], $token); } @@ -310,8 +312,10 @@ public function register(string $token, string $userName, string $emailAddress = * @PublicPage */ public function resendInvitation(string $token): JSONResponse { + $share = $this->shareService->get($token); return $this->response(fn () => [ - 'share' => $this->mailService->resendInvitation($token) + 'share' => $share, + 'sentResult' => $this->mailService->sendInvitation($share) ], $token); } } diff --git a/lib/Controller/ShareApiController.php b/lib/Controller/ShareApiController.php index 458e39720..4ed73a8e5 100644 --- a/lib/Controller/ShareApiController.php +++ b/lib/Controller/ShareApiController.php @@ -75,7 +75,7 @@ public function add(int $pollId, string $type, string $userId = ''): JSONRespons * @NoCSRFRequired */ public function delete(string $token): JSONResponse { - return $this->responseDeleteTolerant(fn () => ['share' => $this->shareService->delete($token)]); + return $this->responseDeleteTolerant(fn () => ['share' => $this->shareService->delete(token: $token)]); } /** @@ -85,11 +85,10 @@ public function delete(string $token): JSONResponse { * @NoCSRFRequired */ public function sendInvitation(string $token): JSONResponse { - $sentResult = $this->mailService->sendInvitation($token); $share = $this->shareService->get($token); return $this->response(fn () => [ 'share' => $share, - 'sentResult' => $sentResult + 'sentResult' => $this->mailService->sendInvitation($share), ]); } } diff --git a/lib/Controller/ShareController.php b/lib/Controller/ShareController.php index 15bba8650..1f5d62649 100644 --- a/lib/Controller/ShareController.php +++ b/lib/Controller/ShareController.php @@ -24,8 +24,6 @@ namespace OCA\Polls\Controller; use OCA\Polls\Db\Share; -use OCA\Polls\Exceptions\InvalidShareTypeException; -use OCA\Polls\Exceptions\ShareAlreadyExistsException; use OCA\Polls\Service\ShareService; use OCA\Polls\Service\UserService; use OCP\AppFramework\Http\JSONResponse; @@ -112,18 +110,31 @@ public function setEmailAddress(string $token, string $emailAddress = ''): JSONR */ public function delete(string $token): JSONResponse { - return $this->responseDeleteTolerant(fn () => ['share' => $this->shareService->delete($token)]); + return $this->responseDeleteTolerant(fn () => ['share' => $this->shareService->delete(token: $token)]); } /** - * Sent invitation mails for a share + * Send invitation mails for a share * Additionally send notification via notifications * @NoAdminRequired */ public function sendInvitation(string $token): JSONResponse { + $share = $this->shareService->get($token); return $this->response(fn () => [ - 'share' => $this->shareService->get($token), - 'sentResult' => $this->shareService->sendInvitation($token), + 'share' => $share, + 'sentResult' => $this->shareService->sendInvitation($share), + ]); + } + + /** + * Send all invitation mails for a share and resolve groups + * Additionally send notification via notifications + * @NoAdminRequired + */ + public function sendAllInvitations(int $pollId): JSONResponse { + return $this->response(fn () => [ + 'poll' => $pollId, + 'sentResult' => $this->shareService->sendAllInvitations($pollId), ]); } @@ -132,29 +143,8 @@ public function sendInvitation(string $token): JSONResponse { * @NoAdminRequired */ public function resolveGroup(string $token): JSONResponse { - return $this->response(function () use ($token) { - $shares = []; - $share = $this->shareService->get($token); - $resolvableShares = [ - Share::TYPE_CIRCLE, - Share::TYPE_CONTACTGROUP - ]; - - if (!in_array($share->getType(), $resolvableShares)) { - throw new InvalidShareTypeException('Cannot resolve members from share type ' . $share->getType()); - } - - foreach ($this->userService->getUser($share->getType(), $share->getUserId())->getMembers() as $member) { - try { - $newShare = $this->shareService->add($share->getPollId(), $member->getType(), $member->getId()); - $shares[] = $newShare; - } catch (ShareAlreadyExistsException $e) { - continue; - } - } - - $this->shareService->delete($token); - return ['shares' => $shares]; - }); + return $this->response(fn () => [ + 'shares' => $this->shareService->resolveGroup($token) + ]); } } diff --git a/lib/Db/Share.php b/lib/Db/Share.php index ee11b7fd7..a6a1c836d 100644 --- a/lib/Db/Share.php +++ b/lib/Db/Share.php @@ -87,6 +87,10 @@ class Share extends Entity implements JsonSerializable { self::TYPE_CIRCLE, self::TYPE_CONTACTGROUP, ]; + public const RESOLVABLE_SHARES = [ + self::TYPE_CIRCLE, + self::TYPE_CONTACTGROUP + ]; public $id = null; protected IURLGenerator $urlGenerator; diff --git a/lib/Db/ShareMapper.php b/lib/Db/ShareMapper.php index 7447a06ae..96c279979 100644 --- a/lib/Db/ShareMapper.php +++ b/lib/Db/ShareMapper.php @@ -57,6 +57,23 @@ public function findByPoll(int $pollId): array { return $this->findEntities($qb); } + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @return Share[] + * @psalm-return array + */ + public function findByPollNotInvited(int $pollId): array { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('poll_id', $qb->createNamedParameter($pollId, IQueryBuilder::PARAM_INT)) + ) + ->andWhere($qb->expr()->eq('invitation_sent', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + + return $this->findEntities($qb); + } /** * @throws \OCP\AppFramework\Db\DoesNotExistException if not found * @return Share[] diff --git a/lib/Model/SentResult.php b/lib/Model/SentResult.php new file mode 100644 index 000000000..28d831f8b --- /dev/null +++ b/lib/Model/SentResult.php @@ -0,0 +1,56 @@ + + * + * @author René Gieling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Polls\Model; + +class SentResult implements \JsonSerializable { + public const INVALID_EMAIL_ADDRESS = 'InvalidMail'; + public const UNHANDELED_REASON = 'UnknownError'; + + private array $sentMails = []; + private array $abortedMails = []; + + public function AddSentMail(UserBase $user): void { + array_push($this->sentMails, [ + 'emailAddress' => $user->getEmailAddress(), + 'displayName' => $user->getDisplayName(), + ]); + } + + public function AddAbortedMail(UserBase $user, string $reason = self::UNHANDELED_REASON): void { + array_push($this->abortedMails, [ + 'emailAddress' => $user->getEmailAddress(), + 'displayName' => $user->getDisplayName(), + 'reason' => $reason, + ]); + } + + public function jsonSerialize(): array { + return [ + 'sentMails' => $this->sentMails, + 'abortedMails' => $this->abortedMails, + 'countSentMails' => count($this->sentMails), + 'countAbortedMails' => count($this->abortedMails), + ]; + } +} diff --git a/lib/Service/MailService.php b/lib/Service/MailService.php index 53bb968e2..0b266fdde 100644 --- a/lib/Service/MailService.php +++ b/lib/Service/MailService.php @@ -36,6 +36,7 @@ use OCA\Polls\Model\Mail\InvitationMail; use OCA\Polls\Model\Mail\NotificationMail; use OCA\Polls\Model\Mail\ReminderMail; +use OCA\Polls\Model\SentResult; use OCA\Polls\Model\UserBase; use Psr\Log\LoggerInterface; @@ -93,34 +94,40 @@ public function sendNotifications(): void { } } - public function resendInvitation(string $token): Share { - $this->sendInvitation($token); - return $this->shareMapper->findByToken($token); - } - - public function sendInvitation(string $token): array { - $share = $this->shareMapper->findByToken($token); - $sentMails = []; - $abortedMails = []; + public function sendInvitation( + Share $share, + SentResult &$sentResult = null, + string $token = null, + ): SentResult|null { + if ($token) { + $share = $this->shareMapper->findByToken($token); + } foreach ($this->userService->getUserFromShare($share)->getMembers() as $recipient) { $invitation = new InvitationMail($recipient->getId(), $share); try { $invitation->send(); - $sentMails[] = $recipient; + if ($sentResult) { + $sentResult->AddSentMail($recipient); + } } catch (InvalidEmailAddress $e) { - $abortedMails[] = $recipient; + if ($sentResult) { + $sentResult->AddAbortedMail($recipient, SentResult::INVALID_EMAIL_ADDRESS); + } $this->logger->warning('Invalid or no email address for invitation: ' . json_encode($recipient)); } catch (\Exception $e) { - $abortedMails[] = $recipient; + if ($sentResult) { + $sentResult->AddAbortedMail($recipient); + } $this->logger->error('Error sending Invitation to ' . json_encode($recipient)); } } $share->setInvitationSent(time()); $this->shareMapper->update($share); - return ['sentMails' => $sentMails, 'abortedMails' => $abortedMails]; + + return $sentResult; } public function sendAutoReminder(): void { @@ -138,23 +145,25 @@ public function sendAutoReminder(): void { /** * Send a confirmation mail for the poll to all participants */ - public function sendConfirmations(int $pollId): array { - $sentMails = []; - $abortedMails = []; - + public function sendConfirmations(int $pollId): SentResult { + $sentResult = new SentResult(); + /** @var UserBase[] */ $participants = $this->userService->getParticipants($pollId); + foreach ($participants as $participant) { - if ($this->sendConfirmationMail($participant, $pollId)) { - $sentMails[] = $participant->getDisplayName(); - } else { - $abortedMails[] = $participant->getDisplayName(); + try { + $this->sendConfirmationMail($sentResult, $participant, $pollId); + $sentResult->AddSentMail($participant); + } catch (InvalidEmailAddress $e) { + $sentResult->AddAbortedMail($participant, SentResult::INVALID_EMAIL_ADDRESS); + $this->logger->warning('Invalid or no email address for confirmation: ' . json_encode($participant)); + } catch (\Exception $e) { + $sentResult->AddAbortedMail($participant); + $this->logger->error('Error sending confirmation to ' . json_encode($participant)); } } - return [ - 'sent' => $sentMails, - 'error' => $abortedMails - ]; + return $sentResult; } private function processSharesForAutoReminder(Poll $poll): void { @@ -170,19 +179,21 @@ private function processSharesForAutoReminder(Poll $poll): void { } } - private function sendConfirmationMail(UserBase $participant, int $pollId) : bool { + private function sendConfirmationMail(SentResult &$sentResult, UserBase $participant, int $pollId) : SentResult { $confirmation = new ConfirmationMail($participant->getId(), $pollId); try { $confirmation->send(); - return true; + $sentResult->AddSentMail($participant); } catch (InvalidEmailAddress $e) { + $sentResult->AddAbortedMail($participant, SentResult::INVALID_EMAIL_ADDRESS); $this->logger->warning('Invalid or no email address for confirmation: ' . json_encode($participant)); } catch (\Exception $e) { + $sentResult->AddAbortedMail($participant); $this->logger->error('Error sending confirmation to ' . json_encode($participant)); } - return false; + return $sentResult; } private function sendAutoReminderToRecipients(Share $share, Poll $poll): void { diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index fe7f5f1c7..64c028346 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -23,6 +23,7 @@ namespace OCA\Polls\Service; +use OCA\Polls\Db\Poll; use OCA\Polls\Db\Share; use OCA\Polls\Db\ShareMapper; use OCA\Polls\Event\ShareChangedDisplayNameEvent; @@ -39,9 +40,10 @@ use OCA\Polls\Exceptions\ShareAlreadyExistsException; use OCA\Polls\Exceptions\ShareNotFoundException; use OCA\Polls\Model\Acl; +use OCA\Polls\Model\SentResult; use OCA\Polls\Model\UserBase; use OCP\AppFramework\Db\DoesNotExistException; -use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\DB\Exception; use OCP\EventDispatcher\IEventDispatcher; use OCP\IGroupManager; use OCP\IUserSession; @@ -91,11 +93,32 @@ public function list(int $pollId): array { return $this->shares; } + /** + * Read all univited shares of a poll based on the poll id and return list as array + * + * @return Share[] + * + * @psalm-return array + */ + public function listNotInvited(int $pollId): array { + try { + $this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT); + $this->shares = $this->shareMapper->findByPollNotInvited($pollId); + } catch (ForbiddenException $e) { + return []; + } catch (DoesNotExistException $e) { + return []; + } + $this->sortByCategory(); + return $this->shares; + } + /** * Get share by token for accessing the poll */ public function get(string $token, bool $validateShareType = false): Share { $this->share = $this->shareMapper->findByToken($token); + if ($validateShareType) { $this->validateShareType(); } @@ -218,12 +241,12 @@ public function deleteEmailAddress(Share $share): Share { * or update an email share with the username */ public function register( - Share $share, + Share $publicShare, string $userName, string $emailAddress = '', string $timeZone = '' ): Share { - $this->share = $share; + $this->share = $publicShare; $this->systemService->validatePublicUsername($userName, $this->share); if ($this->share->getPublicPollEmail() !== Share::EMAIL_DISABLED) { @@ -265,7 +288,7 @@ public function register( // send invitation mail, if invitationSent has no timestamp try { if (!$this->share->getInvitationSent()) { - $this->mailService->resendInvitation($this->share->getToken()); + $this->mailService->sendInvitation($this->share); } } catch (\Exception $e) { $this->logger->error('Error sending Mail to ' . $this->share->getEmailAddress()); @@ -277,39 +300,83 @@ public function register( /** * Delete share */ - public function delete(string $token): string { + public function delete(Share $share = null, string $token = null): string { try { - $this->share = $this->shareMapper->findByToken($token); - $this->acl->setPollId($this->share->getPollId(), Acl::PERMISSION_POLL_EDIT); - $this->shareMapper->delete($this->share); + if ($token) { + $share = $this->shareMapper->findByToken($token); + } + $this->acl->setPollId($share->getPollId(), Acl::PERMISSION_POLL_EDIT); + $this->shareMapper->delete($share); } catch (ShareNotFoundException $e) { // silently catch } - $this->eventDispatcher->dispatchTyped(new ShareDeletedEvent($this->share)); + $this->eventDispatcher->dispatchTyped(new ShareDeletedEvent($share)); - return $token; + return $share->getToken(); + } + + public function sendAllInvitations(int $pollId): SentResult|null { + $sentResult = new SentResult(); + + // first resolve group shares + $shares = $this->listNotInvited($pollId); + foreach ($shares as $share) { + if (in_array($share->getType(), Share::RESOLVABLE_SHARES)) { + $this->resolveGroup(share: $share); + } + } + + // finally send invitation for all not already invited sharees + $shares = $this->listNotInvited($pollId); + foreach ($shares as $share) { + if (!in_array($share->getType(), Share::RESOLVABLE_SHARES)) { + $this->sendInvitation($share, $sentResult); + } + } + return $sentResult; + } + + public function resolveGroup(string $token = null, Share $share = null): array { + if ($token) { + $share = $this->get($token); + } + + if (!in_array($share->getType(), Share::RESOLVABLE_SHARES)) { + throw new InvalidShareTypeException('Cannot resolve members from share type ' . $share->getType()); + } + + foreach ($this->userService->getUser($share->getType(), $share->getUserId())->getMembers() as $member) { + try { + $newShare = $this->add($share->getPollId(), $member->getType(), $member->getId()); + $shares[] = $newShare; + } catch (ShareAlreadyExistsException $e) { + continue; + } + } + + $this->delete($share); + return $shares; } /** * Sent invitation mails for a share * Additionally send notification via notifications */ - public function sendInvitation(string $token): array { - $share = $this->get($token); + public function sendInvitation(Share $share = null, SentResult &$sentResult = null, string $token = null): SentResult|null { + if ($token) { + $share = $this->get($token); + } + if (in_array($share->getType(), [Share::TYPE_USER, Share::TYPE_ADMIN], true)) { $this->notificationService->sendInvitation($share->getPollId(), $share->getUserId()); - - // TODO: skip this atm, to send invitations as mail too, if user is a site user - // $sentResult = ['sentMails' => [new User($share->getuserId())]]; - // $this->shareService->setInvitationSent($token); } elseif ($share->getType() === Share::TYPE_GROUP) { foreach ($this->userService->getUserFromShare($share)->getMembers() as $member) { $this->notificationService->sendInvitation($share->getPollId(), $member->getId()); } } - return $this->mailService->sendInvitation($token); + return $this->mailService->sendInvitation($share, $sentResult); } private function generatePublicUserId(string $prefix = 'ex_'): string { @@ -363,7 +430,7 @@ public function add( string $type, string $userId = '', string $displayName = '', - string $emailAddress = '', + string $emailAddress = '' ): Share { $this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT); @@ -371,20 +438,17 @@ public function add( $this->acl->request(Acl::PERMISSION_PUBLIC_SHARES); } else { try { - $this->shareMapper->findByPollAndUser($pollId, $userId); - throw new ShareAlreadyExistsException; - } catch (MultipleObjectsReturnedException $e) { - throw new ShareAlreadyExistsException; - } catch (ShareNotFoundException $e) { - // continue + $share = $this->createNewShare($pollId, $this->userService->getUser($type, $userId, $displayName, $emailAddress)); + $this->eventDispatcher->dispatchTyped(new ShareCreateEvent($share)); + } catch (Exception $e) { + if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + throw new ShareAlreadyExistsException; + } + + throw $e; } } - - $this->createNewShare($pollId, $this->userService->getUser($type, $userId, $displayName, $emailAddress)); - - $this->eventDispatcher->dispatchTyped(new ShareCreateEvent($this->share)); - - return $this->share; + return $share; } private function sortByCategory(): void { diff --git a/lib/Service/UserService.php b/lib/Service/UserService.php index d535b3418..0f7a78dab 100644 --- a/lib/Service/UserService.php +++ b/lib/Service/UserService.php @@ -115,7 +115,7 @@ public function evaluateUser(string $userId, int $pollId = 0): UserBase { /** * Get participans of a poll as array of user objects - * @return array + * @return UserBase[] */ public function getParticipants(int $pollId) : array { $users = []; diff --git a/src/js/Api/shares.js b/src/js/Api/shares.js index d14853428..598bbd81d 100644 --- a/src/js/Api/shares.js +++ b/src/js/Api/shares.js @@ -94,6 +94,14 @@ const shares = { cancelToken: cancelTokenHandlerObject[this.deleteShare.name].handleRequestCancellation().token, }) }, + + inviteAll(pollId) { + return httpInstance.request({ + method: 'PUT', + url: `poll/${pollId}/inviteAll`, + cancelToken: cancelTokenHandlerObject[this.inviteAll.name].handleRequestCancellation().token, + }) + }, } const cancelTokenHandlerObject = createCancelTokenHandler(shares) diff --git a/src/js/components/Actions/ActionSendConfirmedOptions.vue b/src/js/components/Actions/ActionSendConfirmedOptions.vue index 0afb7f976..990328d44 100644 --- a/src/js/components/Actions/ActionSendConfirmedOptions.vue +++ b/src/js/components/Actions/ActionSendConfirmedOptions.vue @@ -81,18 +81,20 @@ export default { methods: { async clickAction() { try { - this.confirmations = await PollsAPI.sendConfirmation(this.$route.params.id) + const result = await PollsAPI.sendConfirmation(this.$route.params.id) + this.confirmations = result.data.confirmations + + this.headerCaption = n('polls', 'Confirmations send to {count} recipient', 'Confirmations send to {count} recipients', { count: this.confirmations.countSentMails }) + this.confirmations.sentMails.forEach((confirmation) => { + showSuccess(t('polls', 'Confirmation sent to {participant} ({emailAddress})', { participant: confirmation.displayName, emailAddress: confirmation.emailAddress })) + }) + + this.confirmations.abortedMails.forEach((confirmation) => { + showError(t('polls', 'Confirmation could not be sent to {participant} ({emailAddress})', { participant: confirmation.displayName, emailAddress: confirmation.emailAddress })) + }) } catch (e) { - if (e?.code === 'ERR_CANCELED') return + // ignore } - - this.headerCaption = t('polls', 'Confirmations processed') - this.confirmations.sent.forEach((confirmation) => { - showSuccess(t('polls', `Confirmation sent to ${confirmation}`)) - }) - this.confirmations.error.forEach((confirmation) => { - showError(t('polls', `Confirmation could not be sent to ${confirmation}`)) - }) }, }, } diff --git a/src/js/components/Shares/ShareItem.vue b/src/js/components/Shares/ShareItem.vue index cb26ffd34..e6fcb905c 100644 --- a/src/js/components/Shares/ShareItem.vue +++ b/src/js/components/Shares/ShareItem.vue @@ -21,7 +21,10 @@ -->