OpenXE/classes/Modules/GoogleCalendar/Service/GoogleCalendarSynchronizer.php
2021-05-21 08:49:41 +02:00

418 lines
14 KiB
PHP

<?php
declare(strict_types=1);
namespace Xentral\Modules\GoogleCalendar\Service;
use DateInterval;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use Throwable;
use Xentral\Components\Logger\LoggerAwareTrait;
use Xentral\Modules\Calendar\CalendarService;
use Xentral\Modules\Calendar\Data\CalendarEvent;
use Xentral\Modules\GoogleCalendar\Client\GoogleCalendarClientInterface;
use Xentral\Modules\GoogleCalendar\Data\GoogleCalendarColorCollection;
use Xentral\Modules\GoogleCalendar\Data\GoogleCalendarEventData;
use Xentral\Modules\GoogleCalendar\Data\GoogleCalenderSyncValue;
use Xentral\Modules\GoogleCalendar\Exception\GoogleCalendarApiException;
use Xentral\Modules\GoogleCalendar\Exception\GoogleCalendarSyncException;
use Xentral\Modules\GoogleCalendar\Wrapper\UserAddressGatewayWrapper;
use Xentral\Modules\User\Service\UserConfigService;
final class GoogleCalendarSynchronizer
{
use LoggerAwareTrait;
/** @var string CONFIG_KEY_LAST_SYNC */
public const CONFIG_KEY_LAST_SYNC = 'last_google_calendar_sync';
/** @var GoogleSyncGateway $gateway */
private $gateway;
/** @var GoogleSyncService $service */
private $service;
/** @var CalendarService $calendarService */
private $calendarService;
/** @var UserAddressGatewayWrapper $addressService */
private $addressService;
/** @var UserConfigService $userConfigService */
private $userConfigService;
/** @var GoogleEventConverter $converter */
private $converter;
/**
* @param GoogleSyncGateway $gateway
* @param GoogleSyncService $service
* @param CalendarService $calendarService
* @param GoogleEventConverter $converter
* @param UserAddressGatewayWrapper $addressService
* @param UserConfigService $userConfigService
*/
public function __construct(
GoogleSyncGateway $gateway,
GoogleSyncService $service,
CalendarService $calendarService,
GoogleEventConverter $converter,
UserAddressGatewayWrapper $addressService,
UserConfigService $userConfigService
) {
$this->calendarService = $calendarService;
$this->gateway = $gateway;
$this->service = $service;
$this->userConfigService = $userConfigService;
$this->converter = $converter;
$this->addressService = $addressService;
}
/**
* @param int $addressId
* @param int $calendarEventId
*
* @return bool
*/
public function canAddressEditEvent(int $addressId, int $calendarEventId): bool
{
$eventId = $calendarEventId;
$event = $this->calendarService->tryGetEventWithoutUsers($eventId);
if ($event === null) {
return false;
}
$canEdit = false;
$creator = $event->getCreator();
if ($creator !== null && $creator->getAddressId() === $addressId) {
$canEdit = true;
}
$organizer = $event->getOrganizer();
if ($organizer !== null && $organizer->getAddressId() === $addressId) {
$canEdit = true;
}
return $canEdit;
}
/**
* @param GoogleCalendarClientInterface $client
* @param mixed $eventId
* @param string $action 'added', 'modified' or 'deleted'
*
* @return void
*/
public function calendarEventHook(GoogleCalendarClientInterface $client, $eventId, $action): void
{
try {
$eventId = (int)$eventId;
switch ($action) {
case 'added':
//NO BREAK
case 'modified':
$event = $this->calendarService->tryGetEvent($eventId);
if ($event === null) {
return;
}
$this->exportCalendarEvent($client, $event, false);
break;
case 'deleted':
$this->exportDeleteEvent($client, $eventId);
break;
}
} catch (Throwable $e) {
$this->logger->error(
'Exception in calendarEventHook: {message}',
['message' => $e->getMessage(), 'exception' => $e]
);
}
}
/**
* @param GoogleCalendarClientInterface $client
* @param CalendarEvent $event
* @param bool $sendUpdates
*
* @throws GoogleCalendarApiException
*
* @return void
*/
public function exportCalendarEvent(
GoogleCalendarClientInterface $client,
CalendarEvent $event,
bool $sendUpdates = false
): void {
$sync = $this->gateway->tryGetSyncEntryByEvent($event->getId());
if ($sync === null || $sync->getGoogleId() === '') {
$googleEvent = $this->converter->convertToGoogleEvent($event);
$sendUpdateMethod = GoogleCalendarClientInterface::SENDUPDATES_DEFAULT;
if ($sendUpdates === true) {
$sendUpdateMethod = GoogleCalendarClientInterface::SENDUPDATES_ALL;
}
$insertedEvent = $client->insertEvent($googleEvent, $sendUpdateMethod);
$newOrganizer = '';
if ($googleEvent->getOrganizer() !== null) {
$newOrganizer = $googleEvent->getOrganizer()->getEmail();
}
if ($newOrganizer !== '' && $newOrganizer !== $insertedEvent->getOrganizer()->getEmail()) {
try {
$insertedEvent = $client->moveEvent($insertedEvent, $newOrganizer);
} catch (GoogleCalendarApiException $e) {
$this->logger->error('Failed to export calendar event', ['event' => $event->toArray()]);
throw new GoogleCalendarApiException($e->getMessage(), $e->getCode(), $e);
}
}
$address = $this->addressService->getAddressByUser($client->getAccount()->getUserId());
$sync = new GoogleCalenderSyncValue(
0,
$event->getId(),
$insertedEvent->getId(),
$address,
false,
$event->getStart(),
$insertedEvent->getHtmlLink()
);
} else {
$existingEvent = $client->getEvent($sync->getGoogleId());
$googleEvent = $this->converter->convertToGoogleEvent($event, $existingEvent);
$updated = $client->updateEvent($googleEvent);
$sync->setEventDate($event->getStart());
$sync->setHtmlLink($updated->getHtmlLink());
}
$this->service->saveSyncEntry($sync);
}
/**
* @param GoogleCalendarClientInterface $client
* @param int $eventId
*
* @return void
*/
public function exportDeleteEvent(GoogleCalendarClientInterface $client, int $eventId): void
{
$sync = $this->gateway->tryGetSyncEntryByEvent($eventId);
if ($sync === null || $sync->getGoogleId() === '') {
return;
}
$client->deleteEvent($sync->getGoogleId());
$this->service->deleteSyncEntry($sync->getGoogleId(), $sync->getEventId());
}
/**
* @param GoogleCalendarClientInterface $client
*
* @return void
*/
public function importChangedEvents(GoogleCalendarClientInterface $client): void
{
$lastSync = $this->userConfigService->tryGet(
self::CONFIG_KEY_LAST_SYNC,
$client->getAccount()->getUserId()
);
if ($lastSync === null) {
$now = new DateTime('now');
$lastSyncDate = $now->sub(new DateInterval('P1D'));
} else {
$lastSyncDate = DateTime::createFromFormat('Y-m-d H:i:s', $lastSync);
}
$importEvents = $client->getModifiedEvents('primary', $lastSyncDate);
$userAddress = $this->addressService->getAddressByUser($client->getAccount()->getUserId());
try {
$colors = $client->getAvailableColors();
} catch (Exception $e) {
$colors = null;
}
try {
$this->importGoogleEvents($importEvents, $userAddress, $colors);
} catch (Exception $e) {
throw new GoogleCalendarSyncException('Error during Google Calender Sync.', $e->getCode(), $e);
}
$now = new DateTime('now');
$this->userConfigService->set(
self::CONFIG_KEY_LAST_SYNC,
$now->format('Y-m-d H:i:s'),
$client->getAccount()->getUserId()
);
}
/**
* @param GoogleCalendarClientInterface $client
* @param DateTimeInterface|null $from
* @param DateTimeInterface|null $to
*
* @throws GoogleCalendarSyncException
*
* @return void
*/
public function importAbsoluteEvents(
GoogleCalendarClientInterface $client,
DateTimeInterface $from = null,
DateTimeInterface $to = null
): void {
$now = new DateTimeImmutable('now');
if ($from === null) {
/** @noinspection CallableParameterUseCaseInTypeContextInspection */
$from = $now->sub(new DateInterval('P1W'));
}
if ($to === null) {
$to = $now->add(new DateInterval('P3W'));
}
$importEvents = $client->getAbsoluteEvents('primary', $from, $to);
$this->logger->debug(
'Google responded with {count} events to import for "user_id={user}"',
['count' => count($importEvents), 'user' => $client->getAccount()->getUserId()]
);
try {
$colors = $client->getAvailableColors();
} catch (Exception $e) {
$colors = null;
}
$userAddress = $this->addressService->getAddressByUser($client->getAccount()->getUserId());
try {
$this->importGoogleEvents($importEvents, $userAddress, $colors);
} catch (Exception $e) {
$this->logger->error('Exception ' . $e->getMessage(), ['exception' => $e]);
throw new GoogleCalendarSyncException('Error during Google Calender Sync.', $e->getCode(), $e);
}
}
/**
* @param GoogleCalendarEventData $googleEvent
* @param int $addressId
* @param GoogleCalendarColorCollection|null $colors
*
* @return void
*/
public function importGoogleEvent(
GoogleCalendarEventData $googleEvent,
int $addressId,
GoogleCalendarColorCollection $colors = null
): void {
if (strtolower($googleEvent->getStatus()) === 'cancelled') {
$this->importDeletedGoogleEvent($googleEvent, $addressId);
return;
}
$creator = $googleEvent->getCreator();
$owner = 0;
if ($creator !== null) {
$owner = $this->addressService->findAddressByEmail($creator->getEmail());
}
$sync = $this->gateway->tryGetSyncEntryByGoogleEvent($googleEvent->getId());
if ($sync === null) {
$sync = new GoogleCalenderSyncValue(
0,
0,
$googleEvent->getId(),
$owner,
true,
$googleEvent->getTime()->getBeginning(),
$googleEvent->getHtmlLink()
);
}
$existingEvent = null;
if ($sync->getEventId() > 0) {
$existingEvent = $this->calendarService->tryGetEventWithoutUsers($sync->getEventId());
}
$event = $this->converter->convertToEvent($googleEvent, $existingEvent);
if ($colors !== null) {
$color = $colors->getEventColorById($googleEvent->getColorId());
if ($color === null) {
$color = $colors->getDefaultColor();
}
if ($color !== null) {
$event->setColor($color->getBackground());
}
}
$id = $this->calendarService->saveEvent($event);
$sync->setEventId($id);
$sync->setEventDate($event->getStart());
$sync->setOwner($owner);
$this->service->saveSyncEntry($sync);
}
/**
* @param GoogleCalendarEventData $googleEvent
* @param int $addressId
*
* @return void
*/
public function importDeletedGoogleEvent(
GoogleCalendarEventData $googleEvent,
int $addressId = 0
): void {
$sync = $this->gateway->tryGetSyncEntryByGoogleEvent($googleEvent->getId());
if ($sync === null) {
return; //event has never been synced
}
$event = $this->calendarService->tryGetEventWithoutUsers($sync->getEventId());
if ($event === null) {
return; //event was already deleted
}
$owner = 0;
if ($event->getCreator() !== null) {
$owner = $event->getCreator()->getAddressId();
}
$editor = 0;
if ($event->getOrganizer() !== null) {
$editor = $event->getOrganizer()->getAddressId();
}
if ($owner === $addressId || $editor === $addressId) {
$this->calendarService->deleteEvent($sync->getEventId());
$this->service->deleteSyncEntry($sync->getGoogleId(), $sync->getEventId());
} else {
$userId = $this->addressService->getUserByAddress($addressId);
$this->calendarService->removeUserFromEvent($sync->getEventId(), $userId);
}
}
/**
*
* @param array $googleEvents
* @param int $addressId
* @param GoogleCalendarColorCollection|null $colors
*
* @return void
*/
public function importGoogleEvents(
array $googleEvents,
int $addressId,
GoogleCalendarColorCollection $colors = null
): void {
$this->logger->debug(
'import {count} events on addressId {address}',
['count' => count($googleEvents), 'address' => $addressId,]
);
foreach ($googleEvents as $googleEvent) {
try {
$this->importGoogleEvent($googleEvent, $addressId, $colors);
} catch (Exception $e) {
$this->logger->error(
'failed to import Google event {event}',
['event' => $googleEvent->getHtmlLink(), 'exception' => $e]
);
continue;
}
}
}
}