mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
258 lines
9.0 KiB
PHP
258 lines
9.0 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\MSGraph;
|
|
|
|
use Chill\CalendarBundle\Entity\Calendar;
|
|
use Chill\CalendarBundle\Entity\CalendarRange;
|
|
use Chill\CalendarBundle\Entity\Invite;
|
|
use Chill\CalendarBundle\RemoteCalendar\Model\RemoteEvent;
|
|
use Chill\PersonBundle\Entity\Person;
|
|
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
|
|
/**
|
|
* Convert Chill Calendar event to Remote MS Graph event, and MS Graph
|
|
* event to RemoteEvent.
|
|
*/
|
|
class RemoteEventConverter
|
|
{
|
|
/**
|
|
* valid when the remote string contains also a timezone, like in
|
|
* lastModifiedDate.
|
|
*/
|
|
final public const REMOTE_DATETIMEZONE_FORMAT = 'Y-m-d\TH:i:s.u?P';
|
|
|
|
/**
|
|
* Same as above, but sometimes the date is expressed with only 6 milliseconds.
|
|
*/
|
|
final public const REMOTE_DATETIMEZONE_FORMAT_ALT = 'Y-m-d\TH:i:s.uP';
|
|
|
|
private const REMOTE_DATE_FORMAT = 'Y-m-d\TH:i:s.u0';
|
|
|
|
private const REMOTE_DATETIME_WITHOUT_TZ_FORMAT = 'Y-m-d\TH:i:s.u?';
|
|
|
|
private readonly \DateTimeZone $defaultDateTimeZone;
|
|
|
|
private readonly \DateTimeZone $remoteDateTimeZone;
|
|
|
|
public function __construct(
|
|
private readonly \Twig\Environment $engine,
|
|
private readonly LocationConverter $locationConverter,
|
|
private readonly LoggerInterface $logger,
|
|
private readonly PersonRenderInterface $personRender,
|
|
private readonly TranslatorInterface $translator,
|
|
) {
|
|
$this->defaultDateTimeZone = (new \DateTimeImmutable())->getTimezone();
|
|
$this->remoteDateTimeZone = self::getRemoteTimeZone();
|
|
}
|
|
|
|
/**
|
|
* Transform a CalendarRange into a representation suitable for storing into MSGraph.
|
|
*
|
|
* @return array an array representation for event in MS Graph
|
|
*/
|
|
public function calendarRangeToEvent(CalendarRange $calendarRange): array
|
|
{
|
|
return [
|
|
'subject' => $this->translator->trans('remote_calendar.calendar_range_title'),
|
|
'start' => [
|
|
'dateTime' => $calendarRange->getStartDate()->setTimezone($this->remoteDateTimeZone)
|
|
->format(self::REMOTE_DATE_FORMAT),
|
|
'timeZone' => 'UTC',
|
|
],
|
|
'end' => [
|
|
'dateTime' => $calendarRange->getEndDate()->setTimezone($this->remoteDateTimeZone)
|
|
->format(self::REMOTE_DATE_FORMAT),
|
|
'timeZone' => 'UTC',
|
|
],
|
|
'attendees' => [
|
|
[
|
|
'emailAddress' => [
|
|
'address' => $calendarRange->getUser()->getEmailCanonical(),
|
|
'name' => $calendarRange->getUser()->getLabel(),
|
|
],
|
|
],
|
|
],
|
|
'isReminderOn' => false,
|
|
'location' => $this->locationConverter->locationToRemote($calendarRange->getLocation()),
|
|
];
|
|
}
|
|
|
|
public function calendarToEvent(Calendar $calendar): array
|
|
{
|
|
$result = array_merge(
|
|
[
|
|
'subject' => '[Chill] '.
|
|
implode(
|
|
', ',
|
|
$calendar->getPersons()->map(fn (Person $p) => $this->personRender->renderString($p, []))->toArray()
|
|
),
|
|
'start' => [
|
|
'dateTime' => $calendar->getStartDate()->setTimezone($this->remoteDateTimeZone)
|
|
->format(self::REMOTE_DATE_FORMAT),
|
|
'timeZone' => 'UTC',
|
|
],
|
|
'end' => [
|
|
'dateTime' => $calendar->getEndDate()->setTimezone($this->remoteDateTimeZone)
|
|
->format(self::REMOTE_DATE_FORMAT),
|
|
'timeZone' => 'UTC',
|
|
],
|
|
'allowNewTimeProposals' => false,
|
|
'transactionId' => 'calendar_'.$calendar->getId(),
|
|
'body' => [
|
|
'contentType' => 'text',
|
|
'content' => $this->engine->render(
|
|
'@ChillCalendar/MSGraph/calendar_event_body.html.twig',
|
|
['calendar' => $calendar]
|
|
),
|
|
],
|
|
'responseRequested' => true,
|
|
'isReminderOn' => false,
|
|
],
|
|
$this->calendarToEventAttendeesOnly($calendar)
|
|
);
|
|
|
|
if ($calendar->hasLocation()) {
|
|
$result['location'] = $this->locationConverter->locationToRemote($calendar->getLocation());
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function calendarToEventAttendeesOnly(Calendar $calendar): array
|
|
{
|
|
return [
|
|
'attendees' => $calendar->getInvites()->map(
|
|
fn (Invite $i) => $this->buildInviteToAttendee($i)
|
|
)->toArray(),
|
|
];
|
|
}
|
|
|
|
public function convertAvailabilityToRemoteEvent(array $event): RemoteEvent
|
|
{
|
|
$startDate =
|
|
\DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['start']['dateTime'], $this->remoteDateTimeZone)
|
|
->setTimezone($this->defaultDateTimeZone);
|
|
$endDate =
|
|
\DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['end']['dateTime'], $this->remoteDateTimeZone)
|
|
->setTimezone($this->defaultDateTimeZone);
|
|
|
|
return new RemoteEvent(
|
|
uniqid('generated_'),
|
|
$this->translator->trans('remote_ms_graph.freebusy_statuses.'.$event['status']),
|
|
'',
|
|
$startDate,
|
|
$endDate
|
|
);
|
|
}
|
|
|
|
public static function convertStringDateWithoutTimezone(string $date): \DateTimeImmutable
|
|
{
|
|
$d = \DateTimeImmutable::createFromFormat(
|
|
self::REMOTE_DATETIME_WITHOUT_TZ_FORMAT,
|
|
$date,
|
|
self::getRemoteTimeZone()
|
|
);
|
|
|
|
if (false === $d) {
|
|
throw new \RuntimeException("could not convert string date to datetime: {$date}");
|
|
}
|
|
|
|
return $d->setTimezone((new \DateTimeImmutable())->getTimezone());
|
|
}
|
|
|
|
public static function convertStringDateWithTimezone(string $date): \DateTimeImmutable
|
|
{
|
|
$d = \DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $date);
|
|
|
|
if (false === $d) {
|
|
throw new \RuntimeException("could not convert string date to datetime: {$date}");
|
|
}
|
|
|
|
$d->setTimezone((new \DateTimeImmutable())->getTimezone());
|
|
|
|
return $d;
|
|
}
|
|
|
|
public function convertToRemote(array $event): RemoteEvent
|
|
{
|
|
$startDate =
|
|
\DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['start']['dateTime'], $this->remoteDateTimeZone)
|
|
->setTimezone($this->defaultDateTimeZone);
|
|
$endDate =
|
|
\DateTimeImmutable::createFromFormat(self::REMOTE_DATE_FORMAT, $event['end']['dateTime'], $this->remoteDateTimeZone)
|
|
->setTimezone($this->defaultDateTimeZone);
|
|
|
|
return new RemoteEvent(
|
|
$event['id'],
|
|
$event['subject'],
|
|
'',
|
|
$startDate,
|
|
$endDate,
|
|
$event['isAllDay']
|
|
);
|
|
}
|
|
|
|
public function getLastModifiedDate(array $event): \DateTimeImmutable
|
|
{
|
|
$date = \DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT, $event['lastModifiedDateTime']);
|
|
|
|
if (false === $date) {
|
|
$date = \DateTimeImmutable::createFromFormat(self::REMOTE_DATETIMEZONE_FORMAT_ALT, $event['lastModifiedDateTime']);
|
|
}
|
|
|
|
if (false === $date) {
|
|
$this->logger->error(self::class.' Could not convert lastModifiedDate', [
|
|
'actual' => $event['lastModifiedDateTime'],
|
|
'format' => self::REMOTE_DATETIMEZONE_FORMAT,
|
|
'format_alt' => self::REMOTE_DATETIMEZONE_FORMAT_ALT,
|
|
]);
|
|
|
|
throw new \RuntimeException(sprintf('could not convert lastModifiedDate: %s, expected format: %s', $event['lastModifiedDateTime'], self::REMOTE_DATETIMEZONE_FORMAT.' and '.self::REMOTE_DATETIMEZONE_FORMAT_ALT));
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
/**
|
|
* Return a string which format a DateTime to string. To be used in POST requests,.
|
|
*/
|
|
public static function getRemoteDateTimeSimpleFormat(): string
|
|
{
|
|
return 'Y-m-d\TH:i:s';
|
|
}
|
|
|
|
public static function getRemoteTimeZone(): \DateTimeZone
|
|
{
|
|
return new \DateTimeZone('UTC');
|
|
}
|
|
|
|
private function buildInviteToAttendee(Invite $invite): array
|
|
{
|
|
return [
|
|
'emailAddress' => [
|
|
'address' => $invite->getUser()->getEmail(),
|
|
'name' => $invite->getUser()->getLabel(),
|
|
],
|
|
'type' => 'Required',
|
|
];
|
|
}
|
|
}
|