diff --git a/lib/Service/Proposal/ProposalService.php b/lib/Service/Proposal/ProposalService.php index 11908f5f2f..5fc46449d0 100644 --- a/lib/Service/Proposal/ProposalService.php +++ b/lib/Service/Proposal/ProposalService.php @@ -18,6 +18,7 @@ use OCA\Calendar\Objects\Proposal\ProposalObject; use OCA\Calendar\Objects\Proposal\ProposalParticipantCollection; use OCA\Calendar\Objects\Proposal\ProposalParticipantObject; +use OCA\Calendar\Objects\Proposal\ProposalParticipantRealm; use OCA\Calendar\Objects\Proposal\ProposalParticipantStatus; use OCA\Calendar\Objects\Proposal\ProposalResponseObject; use OCA\Calendar\Objects\Proposal\ProposalVoteCollection; @@ -35,6 +36,7 @@ use OCP\Mail\Provider\IMessageSend; use Psr\Log\LoggerInterface; use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VEvent; use Symfony\Component\Uid\Uuid; class ProposalService { @@ -193,6 +195,9 @@ public function createProposal(IUser $user, ProposalObject $proposal): ProposalO // generate notifications for internal and external participants $this->generateNotifications($user, $proposal, 'C'); + // generate iTip for internal participants + $this->generateIMip($user, $proposal, 'C'); + return $proposal; } @@ -257,6 +262,9 @@ public function modifyProposal(IUser $user, ProposalObject $mutatedProposal): Pr // generate notifications for internal and external participants $this->generateNotifications($user, $proposal, 'M'); + // generate iTip for internal participants + $this->generateIMip($user, $proposal, 'M'); + return $proposal; } @@ -277,6 +285,9 @@ public function destroyProposal(IUser $user, int $identifier): void { // generate notifications for internal and external participants $this->generateNotifications($user, $proposal, 'D'); + + // generate iTip for internal participants + $this->generateIMip($user, $proposal, 'D'); } /** @@ -548,4 +559,70 @@ private function sendEmailNotifications(IUser $user, ProposalObject $proposal, P } } + + private function generateIMip(IUser $user, ProposalObject $proposal, string $reason): void { + // if the calendar manager does not have a handleIMip method, we cannot generate iTip messages + if (!method_exists($this->calendarManager, 'handleIMip')) { + return; + } + // if the proposal has no dates or participants, we cannot generate any iTip messages + if ($proposal->getDates()->count() === 0 || $proposal->getParticipants()->count() === 0) { + return; + } + // construct calendar object with events + $template = new VCalendar(); + // TODO: change REQUEST to PUBLISH + $template->add('METHOD', $reason !== 'D' ? 'REQUEST' : 'CANCEL'); + // create a event for each date in the proposal + // TODO: should we create a new instance for each date or use a recurrence rule? Like RDATE:19970714T083000Z,19970715T083000Z + foreach ($proposal->getDates()->sortByDate() as $proposalDate) { + /** @var VEvent $vEvent */ + $vEvent = $template->add('VEVENT', []); + if (isset($baseDate)) { + $vEvent->add('RECURRENCE-ID', $baseDate->getDate()->format('Ymd\THis\Z')); + } else { + $baseDate = $proposalDate; + } + $vEvent->UID->setValue($proposal->getUuid()); + $vEvent->add('STATUS', 'TENTATIVE'); + $vEvent->add('SEQUENCE', 1); + $vEvent->add('DTSTART', $proposalDate->getDate()); + $vEvent->add('DURATION', "PT{$proposal->getDuration()}M"); + $vEvent->add('SUMMARY', $proposal->getTitle()); + $vEvent->add('DESCRIPTION', $proposal->getDescription()); + $vEvent->add('ORGANIZER', 'mailto:' . $user->getEMailAddress(), ['CN' => $user->getDisplayName()]); + // add the participant to the event + foreach ($proposal->getParticipants() as $participant) { + $vEvent->add('ATTENDEE', 'mailto:' . $participant->getAddress(), [ + 'CN' => $participant->getName(), + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT' + ]); + } + } + + foreach ($proposal->getParticipants()->filterByRealm(ProposalParticipantRealm::Internal) as $participant) { + // TODO: this is stupid, we send the internal users email address from the UI then convert it back to a user name + // should probably be sent from the UI as a user name, or send and store both the user name and email address + // maybe send the address as a special schema "local:{user name}/{email address}", this would allow us to later extend this to federated users + // with a different special schema like "federated:{user name}@{server}/{email address}" + $participantUsers = $this->userManager->getByEmail($participant->getAddress()); + if ($participantUsers === []) { + continue; + } + $participantUser = $participantUsers[0]; + try { + $this->calendarManager->handleIMip( + $participantUser->getUID(), + $template->serialize(), + $reason !== 'D' ? ['absent' => 'create'] : [] + ); + } catch (Exception $e) { + $this->logger->error($e->getMessage(), ['app' => 'calendar', 'exception' => $e]); + } + } + + } + }