mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			761 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			761 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /**
 | |
|  * Chill is a software for social workers.
 | |
|  *
 | |
|  * For the full copyright and license information, please view
 | |
|  * the LICENSE file that was distributed with this source code.
 | |
|  */
 | |
| 
 | |
| declare(strict_types=1);
 | |
| 
 | |
| /*
 | |
|  * Chill is a software for social workers
 | |
|  *
 | |
|  * For the full copyright and license information, please view
 | |
|  * the LICENSE file that was distributed with this source code.
 | |
|  */
 | |
| 
 | |
| namespace Chill\CalendarBundle\RemoteCalendar\Connector;
 | |
| 
 | |
| use Chill\CalendarBundle\Entity\Calendar;
 | |
| use Chill\CalendarBundle\Entity\CalendarRange;
 | |
| use Chill\CalendarBundle\Entity\Invite;
 | |
| use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser;
 | |
| use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\OnBehalfOfUserHttpClient;
 | |
| use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\OnBehalfOfUserTokenStorage;
 | |
| use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteEventConverter;
 | |
| use Chill\CalendarBundle\Repository\CalendarRangeRepository;
 | |
| use Chill\CalendarBundle\Repository\CalendarRepository;
 | |
| use Chill\MainBundle\Entity\User;
 | |
| use DateTimeImmutable;
 | |
| use Exception;
 | |
| use Psr\Log\LoggerInterface;
 | |
| use Symfony\Component\HttpFoundation\RedirectResponse;
 | |
| use Symfony\Component\HttpFoundation\Response;
 | |
| use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
 | |
| use Symfony\Component\Security\Core\Security;
 | |
| use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
 | |
| use Symfony\Contracts\HttpClient\HttpClientInterface;
 | |
| use Symfony\Contracts\Translation\TranslatorInterface;
 | |
| use function array_key_exists;
 | |
| use function count;
 | |
| 
 | |
| class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface
 | |
| {
 | |
|     private array $cacheScheduleTimeForUser = [];
 | |
| 
 | |
|     private CalendarRangeRepository $calendarRangeRepository;
 | |
| 
 | |
|     private CalendarRepository $calendarRepository;
 | |
| 
 | |
|     private LoggerInterface $logger;
 | |
| 
 | |
|     private HttpClientInterface $machineHttpClient;
 | |
| 
 | |
|     private MapCalendarToUser $mapCalendarToUser;
 | |
| 
 | |
|     private RemoteEventConverter $remoteEventConverter;
 | |
| 
 | |
|     private OnBehalfOfUserTokenStorage $tokenStorage;
 | |
| 
 | |
|     private TranslatorInterface $translator;
 | |
| 
 | |
|     private UrlGeneratorInterface $urlGenerator;
 | |
| 
 | |
|     private OnBehalfOfUserHttpClient $userHttpClient;
 | |
| 
 | |
|     private Security $security;
 | |
| 
 | |
|     public function __construct(
 | |
|         CalendarRepository $calendarRepository,
 | |
|         CalendarRangeRepository $calendarRangeRepository,
 | |
|         HttpClientInterface $machineHttpClient,
 | |
|         MapCalendarToUser $mapCalendarToUser,
 | |
|         LoggerInterface $logger,
 | |
|         OnBehalfOfUserTokenStorage $tokenStorage,
 | |
|         OnBehalfOfUserHttpClient $userHttpClient,
 | |
|         RemoteEventConverter $remoteEventConverter,
 | |
|         TranslatorInterface $translator,
 | |
|         UrlGeneratorInterface $urlGenerator,
 | |
|         Security $security
 | |
|     ) {
 | |
|         $this->calendarRepository = $calendarRepository;
 | |
|         $this->calendarRangeRepository = $calendarRangeRepository;
 | |
|         $this->machineHttpClient = $machineHttpClient;
 | |
|         $this->mapCalendarToUser = $mapCalendarToUser;
 | |
|         $this->logger = $logger;
 | |
|         $this->remoteEventConverter = $remoteEventConverter;
 | |
|         $this->tokenStorage = $tokenStorage;
 | |
|         $this->translator = $translator;
 | |
|         $this->urlGenerator = $urlGenerator;
 | |
|         $this->userHttpClient = $userHttpClient;
 | |
|         $this->security = $security;
 | |
|     }
 | |
| 
 | |
|     public function countEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): int
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         if (null === $userId) {
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $data = $this->userHttpClient->request(
 | |
|                 'GET',
 | |
|                 'users/' . $userId . '/calendarView',
 | |
|                 [
 | |
|                     'query' => [
 | |
|                         'startDateTime' => $startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()),
 | |
|                         'endDateTime' => $endDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()),
 | |
|                         '$count' => 'true',
 | |
|                         '$top' => 0,
 | |
|                     ],
 | |
|                 ]
 | |
|             )->toArray();
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             if (403 === $e->getResponse()->getStatusCode()) {
 | |
|                 return count($this->getScheduleTimesForUser($user, $startDate, $endDate));
 | |
|             }
 | |
| 
 | |
|             $this->logger->error('Could not get list of event on MSGraph', [
 | |
|                 'error_code' => $e->getResponse()->getStatusCode(),
 | |
|                 'error' => $e->getResponse()->getInfo(),
 | |
|             ]);
 | |
| 
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         return $data['@odata.count'];
 | |
|     }
 | |
| 
 | |
|     public function getMakeReadyResponse(string $returnPath): Response
 | |
|     {
 | |
|         return new RedirectResponse($this->urlGenerator
 | |
|             ->generate('chill_calendar_remote_connect_azure', ['returnPath' => $returnPath]));
 | |
|     }
 | |
| 
 | |
|     public function isReady(): bool
 | |
|     {
 | |
|         $user = $this->security->getUser();
 | |
| 
 | |
|         if (!$user instanceof User) {
 | |
|             // this is not a user from chill. This is not the role of this class to
 | |
|             // restrict access, so we will just say that we do not have to do anything more
 | |
|             // here...
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         if (null === $this->mapCalendarToUser->getUserId($user)) {
 | |
|             // this user is not mapped with remote calendar. The user will have to wait for
 | |
|             // the next calendar subscription iteration
 | |
|             $this->logger->debug('mark user ready for msgraph calendar as he does not have any mapping', [
 | |
|                 'userId' => $user->getId(),
 | |
|             ]);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         return $this->tokenStorage->hasToken();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface
 | |
|      * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
 | |
|      * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
 | |
|      * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
 | |
|      *
 | |
|      * @return array|\Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent[]
 | |
|      */
 | |
|     public function listEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate, ?int $offset = 0, ?int $limit = 50): array
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         if (null === $userId) {
 | |
|             return [];
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $bareEvents = $this->userHttpClient->request(
 | |
|                 'GET',
 | |
|                 'users/' . $userId . '/calendarView',
 | |
|                 [
 | |
|                     'query' => [
 | |
|                         'startDateTime' => $startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()),
 | |
|                         'endDateTime' => $endDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat()),
 | |
|                         '$select' => 'id,subject,start,end,isAllDay',
 | |
|                         '$top' => $limit,
 | |
|                         '$skip' => $offset,
 | |
|                     ],
 | |
|                 ]
 | |
|             )->toArray();
 | |
| 
 | |
|             $ids = array_map(static fn ($item) => $item['id'], $bareEvents['value']);
 | |
|             $existingIdsInRange = $this->calendarRangeRepository->findRemoteIdsPresent($ids);
 | |
|             $existingIdsInCalendar = $this->calendarRepository->findRemoteIdsPresent($ids);
 | |
| 
 | |
|             return array_values(
 | |
|                 array_map(
 | |
|                     fn ($item) => $this->remoteEventConverter->convertToRemote($item),
 | |
|                     // filter all event to keep only the one not in range
 | |
|                     array_filter(
 | |
|                         $bareEvents['value'],
 | |
|                         static fn ($item) => ((!$existingIdsInRange[$item['id']]) ?? true) && ((!$existingIdsInCalendar[$item['id']]) ?? true)
 | |
|                     )
 | |
|                 )
 | |
|             );
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             if (403 === $e->getResponse()->getStatusCode()) {
 | |
|                 return $this->getScheduleTimesForUser($user, $startDate, $endDate);
 | |
|             }
 | |
| 
 | |
|             $this->logger->error('Could not get list of event on MSGraph', [
 | |
|                 'error_code' => $e->getResponse()->getStatusCode(),
 | |
|                 'error' => $e->getResponse()->getInfo(),
 | |
|             ]);
 | |
| 
 | |
|             return [];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, ?CalendarRange $associatedCalendarRange = null): void
 | |
|     {
 | |
|         if ('' === $remoteId) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $this->removeEvent($remoteId, $user);
 | |
| 
 | |
|         if (null !== $associatedCalendarRange) {
 | |
|             $this->syncCalendarRange($associatedCalendarRange);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function removeCalendarRange(string $remoteId, array $remoteAttributes, User $user): void
 | |
|     {
 | |
|         if ('' === $remoteId) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $this->removeEvent($remoteId, $user);
 | |
|     }
 | |
| 
 | |
|     public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void
 | |
|     {
 | |
|         /*
 | |
|          * cases to support:
 | |
|          *
 | |
|          * * a calendar range is created:
 | |
|          *     * create on remote
 | |
|          *     * if calendar range is associated: remove the range
 | |
|          * * a Calendar change the CalendarRange:
 | |
|          *     * re-create the previous calendar range;
 | |
|          *     * remove the current calendar range
 | |
|          * * a calendar change the mainUser
 | |
|          *     * cancel the calendar in the previous mainUser
 | |
|          *     * recreate the previous calendar range in the previousMainUser, if any
 | |
|          *     * delete the current calendar range in the current mainUser, if any
 | |
|          *     * create the calendar in the current mainUser
 | |
|          *
 | |
|          */
 | |
| 
 | |
|         if (!$calendar->hasRemoteId()) {
 | |
|             $this->createCalendarOnRemote($calendar);
 | |
|         } else {
 | |
|             if (null !== $previousMainUser) {
 | |
|                 // cancel event in previousMainUserCalendar
 | |
|                 $this->cancelOnRemote(
 | |
|                     $calendar->getRemoteId(),
 | |
|                     $this->translator->trans('remote_ms_graph.cancel_event_because_main_user_is_%label%', ['%label%' => $calendar->getMainUser()]),
 | |
|                     $previousMainUser,
 | |
|                     'calendar_' . $calendar->getRemoteId()
 | |
|                 );
 | |
|                 $this->createCalendarOnRemote($calendar);
 | |
|             } else {
 | |
|                 $this->patchCalendarOnRemote($calendar, $newInvites);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if ($calendar->hasCalendarRange() && $calendar->getCalendarRange()->hasRemoteId()) {
 | |
|             $this->removeEvent(
 | |
|                 $calendar->getCalendarRange()->getRemoteId(),
 | |
|                 $calendar->getCalendarRange()->getUser()
 | |
|             );
 | |
| 
 | |
|             $calendar->getCalendarRange()
 | |
|                 ->addRemoteAttributes([
 | |
|                     'lastModifiedDateTime' => null,
 | |
|                     'changeKey' => null,
 | |
|                     'previousId' => $calendar->getCalendarRange()->getRemoteId(),
 | |
|                 ])
 | |
|                 ->setRemoteId('');
 | |
|         }
 | |
| 
 | |
|         if (null !== $previousCalendarRange) {
 | |
|             $this->createRemoteCalendarRange($previousCalendarRange);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function syncCalendarRange(CalendarRange $calendarRange): void
 | |
|     {
 | |
|         if ($calendarRange->hasRemoteId()) {
 | |
|             $this->updateRemoteCalendarRange($calendarRange);
 | |
|         } else {
 | |
|             $this->createRemoteCalendarRange($calendarRange);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function syncInvite(Invite $invite): void
 | |
|     {
 | |
|         if ('' === $remoteId = $invite->getCalendar()->getRemoteId()) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (null === $invite->getUser()) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (null === $userId = $this->mapCalendarToUser->getUserId($invite->getUser())) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if ($invite->hasRemoteId()) {
 | |
|             $remoteIdAttendeeCalendar = $invite->getRemoteId();
 | |
|         } else {
 | |
|             $remoteIdAttendeeCalendar = $this->findRemoteIdOnUserCalendar($invite->getCalendar(), $invite->getUser());
 | |
|             $invite->setRemoteId($remoteIdAttendeeCalendar);
 | |
|         }
 | |
| 
 | |
|         switch ($invite->getStatus()) {
 | |
|             case Invite::PENDING:
 | |
|                 return;
 | |
| 
 | |
|             case Invite::ACCEPTED:
 | |
|                 $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/accept";
 | |
| 
 | |
|                 break;
 | |
| 
 | |
|             case Invite::TENTATIVELY_ACCEPTED:
 | |
|                 $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/tentativelyAccept";
 | |
| 
 | |
|                 break;
 | |
| 
 | |
|             case Invite::DECLINED:
 | |
|                 $url = "/v1.0/users/{$userId}/calendar/events/{$remoteIdAttendeeCalendar}/decline";
 | |
| 
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 throw new Exception('not supported');
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $this->machineHttpClient->request(
 | |
|                 'POST',
 | |
|                 $url,
 | |
|                 ['json' => ['sendResponse' => true]]
 | |
|             );
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->warning('could not update calendar range to remote', [
 | |
|                 'exception' => $e->getTraceAsString(),
 | |
|                 'content' => $e->getResponse()->getContent(),
 | |
|                 'calendarRangeId' => 'invite_' . $invite->getId(),
 | |
|             ]);
 | |
| 
 | |
|             throw $e;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function cancelOnRemote(string $remoteId, string $comment, User $user, string $identifier): void
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         if (null === $userId) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $this->machineHttpClient->request(
 | |
|                 'POST',
 | |
|                 "users/{$userId}/calendar/events/{$remoteId}/cancel",
 | |
|                 [
 | |
|                     'json' => ['Comment' => $comment],
 | |
|                 ]
 | |
|             );
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->warning('could not update calendar range to remote', [
 | |
|                 'exception' => $e->getTraceAsString(),
 | |
|                 'content' => $e->getResponse()->getContent(),
 | |
|                 'calendarRangeId' => $identifier,
 | |
|             ]);
 | |
| 
 | |
|             throw $e;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function createCalendarOnRemote(Calendar $calendar): void
 | |
|     {
 | |
|         $eventData = $this->remoteEventConverter->calendarToEvent($calendar);
 | |
| 
 | |
|         [
 | |
|             'id' => $id,
 | |
|             'lastModifiedDateTime' => $lastModified,
 | |
|             'changeKey' => $changeKey
 | |
|         ] = $this->createOnRemote($eventData, $calendar->getMainUser(), 'calendar_' . $calendar->getId());
 | |
| 
 | |
|         if (null === $id) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $calendar
 | |
|             ->setRemoteId($id)
 | |
|             ->addRemoteAttributes([
 | |
|                 'lastModifiedDateTime' => $lastModified,
 | |
|                 'changeKey' => $changeKey,
 | |
|             ]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $identifier an identifier for logging in case of something does not work
 | |
|      *
 | |
|      * @return array{?id: string, ?lastModifiedDateTime: int, ?changeKey: string}
 | |
|      */
 | |
|     private function createOnRemote(array $eventData, User $user, string $identifier): array
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         if (null === $userId) {
 | |
|             $this->logger->warning('user does not have userId nor calendarId', [
 | |
|                 'user_id' => $user->getId(),
 | |
|                 'calendar_identifier' => $identifier,
 | |
|             ]);
 | |
| 
 | |
|             return ['id' => null, 'lastModifiedDateTime' => null, 'changeKey' => null];
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $event = $this->machineHttpClient->request(
 | |
|                 'POST',
 | |
|                 'users/' . $userId . '/calendar/events',
 | |
|                 [
 | |
|                     'json' => $eventData,
 | |
|                 ]
 | |
|             )->toArray();
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->warning('could not save calendar range to remote', [
 | |
|                 'exception' => $e->getTraceAsString(),
 | |
|                 'content' => $e->getResponse()->getContent(),
 | |
|                 'calendar_identifier' => $identifier,
 | |
|             ]);
 | |
| 
 | |
|             throw $e;
 | |
|         }
 | |
| 
 | |
|         return [
 | |
|             'id' => $event['id'],
 | |
|             'lastModifiedDateTime' => $this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp(),
 | |
|             'changeKey' => $event['changeKey'],
 | |
|         ];
 | |
|     }
 | |
| 
 | |
|     private function createRemoteCalendarRange(CalendarRange $calendarRange): void
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($calendarRange->getUser());
 | |
| 
 | |
|         if (null === $userId) {
 | |
|             $this->logger->warning('user does not have userId nor calendarId', [
 | |
|                 'user_id' => $calendarRange->getUser()->getId(),
 | |
|                 'calendar_range_id' => $calendarRange->getId(),
 | |
|             ]);
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $eventData = $this->remoteEventConverter->calendarRangeToEvent($calendarRange);
 | |
| 
 | |
|         [
 | |
|             'id' => $id,
 | |
|             'lastModifiedDateTime' => $lastModified,
 | |
|             'changeKey' => $changeKey
 | |
|         ] = $this->createOnRemote(
 | |
|             $eventData,
 | |
|             $calendarRange->getUser(),
 | |
|             'calendar_range_' . $calendarRange->getId()
 | |
|         );
 | |
| 
 | |
|         $calendarRange->setRemoteId($id)
 | |
|             ->addRemoteAttributes([
 | |
|                 'lastModifiedDateTime' => $lastModified,
 | |
|                 'changeKey' => $changeKey,
 | |
|             ]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * the remoteId is not the same across different user calendars. This method allow to find
 | |
|      * the correct remoteId in another calendar.
 | |
|      *
 | |
|      * For achieving this, the iCalUid is used.
 | |
|      */
 | |
|     private function findRemoteIdOnUserCalendar(Calendar $calendar, User $user): ?string
 | |
|     {
 | |
|         // find the icalUid on original user
 | |
|         $event = $this->getOnRemote($calendar->getMainUser(), $calendar->getRemoteId());
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         if ('' === $iCalUid = ($event['iCalUId'] ?? '')) {
 | |
|             throw new Exception('no iCalUid for this event');
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $events = $this->machineHttpClient->request(
 | |
|                 'GET',
 | |
|                 "/v1.0/users/{$userId}/calendar/events",
 | |
|                 [
 | |
|                     'query' => [
 | |
|                         '$select' => 'id',
 | |
|                         '$filter' => "iCalUId eq '{$iCalUid}'",
 | |
|                     ],
 | |
|                 ]
 | |
|             )->toArray();
 | |
|         } catch (ClientExceptionInterface $clientException) {
 | |
|             throw $clientException;
 | |
|         }
 | |
| 
 | |
|         if (1 !== count($events['value'])) {
 | |
|             throw new Exception('multiple events found with same iCalUid');
 | |
|         }
 | |
| 
 | |
|         return $events['value'][0]['id'];
 | |
|     }
 | |
| 
 | |
|     private function getOnRemote(User $user, string $remoteId): array
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         if (null === $userId) {
 | |
|             throw new Exception(
 | |
|                 sprintf(
 | |
|                     'no remote calendar for this user: %s, remoteid: %s',
 | |
|                     $user->getId(),
 | |
|                     $remoteId
 | |
|                 )
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             return $this->machineHttpClient->request(
 | |
|                 'GET',
 | |
|                 'users/' . $userId . '/calendar/events/' . $remoteId
 | |
|             )->toArray();
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->warning('Could not get event from calendar', [
 | |
|                 'remoteId' => $remoteId,
 | |
|             ]);
 | |
| 
 | |
|             throw $e;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function getScheduleTimesForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): array
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         if (array_key_exists($userId, $this->cacheScheduleTimeForUser)) {
 | |
|             return $this->cacheScheduleTimeForUser[$userId];
 | |
|         }
 | |
| 
 | |
|         if (null === $userId) {
 | |
|             return [];
 | |
|         }
 | |
| 
 | |
|         if (null === $user->getEmailCanonical() || '' === $user->getEmailCanonical()) {
 | |
|             return [];
 | |
|         }
 | |
| 
 | |
|         $body = [
 | |
|             'schedules' => [$user->getEmailCanonical()],
 | |
|             'startTime' => [
 | |
|                 'dateTime' => ($startDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat())),
 | |
|                 'timeZone' => 'UTC',
 | |
|             ],
 | |
|             'endTime' => [
 | |
|                 'dateTime' => ($endDate->setTimezone(RemoteEventConverter::getRemoteTimeZone())->format(RemoteEventConverter::getRemoteDateTimeSimpleFormat())),
 | |
|                 'timeZone' => 'UTC',
 | |
|             ],
 | |
|         ];
 | |
| 
 | |
|         try {
 | |
|             $response = $this->userHttpClient->request('POST', 'users/' . $userId . '/calendar/getSchedule', [
 | |
|                 'json' => $body,
 | |
|             ])->toArray();
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->debug('Could not get schedule on MSGraph', [
 | |
|                 'error_code' => $e->getResponse()->getStatusCode(),
 | |
|                 'error' => $e->getResponse()->getInfo(),
 | |
|             ]);
 | |
| 
 | |
|             return [];
 | |
|         }
 | |
| 
 | |
|         $this->cacheScheduleTimeForUser[$userId] = array_map(
 | |
|             fn ($item) => $this->remoteEventConverter->convertAvailabilityToRemoteEvent($item),
 | |
|             $response['value'][0]['scheduleItems']
 | |
|         );
 | |
| 
 | |
|         return $this->cacheScheduleTimeForUser[$userId];
 | |
|     }
 | |
| 
 | |
|     private function patchCalendarOnRemote(Calendar $calendar, array $newInvites): void
 | |
|     {
 | |
|         $eventDatas = [];
 | |
|         $eventDatas[] = $this->remoteEventConverter->calendarToEvent($calendar);
 | |
| 
 | |
|         if (0 < count($newInvites)) {
 | |
|             // it seems that invitaiton are always send, even if attendee changes are mixed with other datas
 | |
|             // $eventDatas[] = $this->remoteEventConverter->calendarToEventAttendeesOnly($calendar);
 | |
|         }
 | |
| 
 | |
|         foreach ($eventDatas as $eventData) {
 | |
|             [
 | |
|                 'id' => $id,
 | |
|                 'lastModifiedDateTime' => $lastModified,
 | |
|                 'changeKey' => $changeKey
 | |
|             ] = $this->patchOnRemote(
 | |
|                 $calendar->getRemoteId(),
 | |
|                 $eventData,
 | |
|                 $calendar->getMainUser(),
 | |
|                 'calendar_' . $calendar->getId()
 | |
|             );
 | |
| 
 | |
|             $calendar->addRemoteAttributes([
 | |
|                 'lastModifiedDateTime' => $lastModified,
 | |
|                 'changeKey' => $changeKey,
 | |
|             ]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param string $identifier an identifier for logging in case of something does not work
 | |
|      *
 | |
|      * @return array{?id: string, ?lastModifiedDateTime: int, ?changeKey: string}
 | |
|      */
 | |
|     private function patchOnRemote(string $remoteId, array $eventData, User $user, string $identifier): array
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         if (null === $userId) {
 | |
|             $this->logger->warning('user does not have userId nor calendarId', [
 | |
|                 'user_id' => $user->getId(),
 | |
|                 'calendar_identifier' => $identifier,
 | |
|             ]);
 | |
| 
 | |
|             return ['id' => null, 'lastModifiedDateTime' => null, 'changeKey' => null];
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $event = $this->machineHttpClient->request(
 | |
|                 'PATCH',
 | |
|                 'users/' . $userId . '/calendar/events/' . $remoteId,
 | |
|                 [
 | |
|                     'json' => $eventData,
 | |
|                 ]
 | |
|             )->toArray();
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->warning('could not update calendar range to remote', [
 | |
|                 'exception' => $e->getTraceAsString(),
 | |
|                 'calendarRangeId' => $identifier,
 | |
|             ]);
 | |
| 
 | |
|             throw $e;
 | |
|         }
 | |
| 
 | |
|         return [
 | |
|             'id' => $event['id'],
 | |
|             'lastModifiedDateTime' => $this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp(),
 | |
|             'changeKey' => $event['changeKey'],
 | |
|         ];
 | |
|     }
 | |
| 
 | |
|     private function removeEvent($remoteId, User $user): void
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($user);
 | |
| 
 | |
|         try {
 | |
|             $this->machineHttpClient->request(
 | |
|                 'DELETE',
 | |
|                 'users/' . $userId . '/calendar/events/' . $remoteId
 | |
|             );
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->warning('could not remove event from calendar', [
 | |
|                 'event_remote_id' => $remoteId,
 | |
|                 'user_id' => $user->getId(),
 | |
|             ]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function updateRemoteCalendarRange(CalendarRange $calendarRange): void
 | |
|     {
 | |
|         $userId = $this->mapCalendarToUser->getUserId($calendarRange->getUser());
 | |
|         $calendarId = $this->mapCalendarToUser->getCalendarId($calendarRange->getUser());
 | |
| 
 | |
|         if (null === $userId || null === $calendarId) {
 | |
|             $this->logger->warning('user does not have userId nor calendarId', [
 | |
|                 'user_id' => $calendarRange->getUser()->getId(),
 | |
|                 'calendar_range_id' => $calendarRange->getId(),
 | |
|             ]);
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         try {
 | |
|             $event = $this->machineHttpClient->request(
 | |
|                 'GET',
 | |
|                 'users/' . $userId . '/calendar/events/' . $calendarRange->getRemoteId()
 | |
|             )->toArray();
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->warning('Could not get event from calendar', [
 | |
|                 'calendar_range_id' => $calendarRange->getId(),
 | |
|                 'calendar_range_remote_id' => $calendarRange->getRemoteId(),
 | |
|             ]);
 | |
| 
 | |
|             throw $e;
 | |
|         }
 | |
| 
 | |
|         if ($this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp() > $calendarRange->getUpdatedAt()->getTimestamp()) {
 | |
|             $this->logger->info('Skip updating as the lastModified date seems more fresh than the database one', [
 | |
|                 'calendar_range_id' => $calendarRange->getId(),
 | |
|                 'calendar_range_remote_id' => $calendarRange->getRemoteId(),
 | |
|                 'db_last_updated' => $calendarRange->getUpdatedAt()->getTimestamp(),
 | |
|                 'remote_last_updated' => $this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp(),
 | |
|             ]);
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $eventData = $this->remoteEventConverter->calendarRangeToEvent($calendarRange);
 | |
| 
 | |
|         try {
 | |
|             $event = $this->machineHttpClient->request(
 | |
|                 'PATCH',
 | |
|                 'users/' . $userId . '/calendar/events/' . $calendarRange->getRemoteId(),
 | |
|                 [
 | |
|                     'json' => $eventData,
 | |
|                 ]
 | |
|             )->toArray();
 | |
|         } catch (ClientExceptionInterface $e) {
 | |
|             $this->logger->warning('could not update calendar range to remote', [
 | |
|                 'exception' => $e->getTraceAsString(),
 | |
|                 'calendarRangeId' => $calendarRange->getId(),
 | |
|             ]);
 | |
| 
 | |
|             throw $e;
 | |
|         }
 | |
| 
 | |
|         $calendarRange
 | |
|             ->addRemoteAttributes([
 | |
|                 'lastModifiedDateTime' => $this->remoteEventConverter->getLastModifiedDate($event)->getTimestamp(),
 | |
|                 'changeKey' => $event['changeKey'],
 | |
|             ]);
 | |
|     }
 | |
| }
 |