Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user