]> Raphaël G. Git Repositories - airbundle/blobdiff - Command/Calendar2Command.php
Rename rapsysair:calendar2 command to rapsysair:calendar
[airbundle] / Command / Calendar2Command.php
diff --git a/Command/Calendar2Command.php b/Command/Calendar2Command.php
deleted file mode 100644 (file)
index 52eafb9..0000000
+++ /dev/null
@@ -1,613 +0,0 @@
-<?php declare(strict_types=1);
-
-/*
- * This file is part of the Rapsys AirBundle package.
- *
- * (c) Raphaël Gertz <symfony@rapsys.eu>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Rapsys\AirBundle\Command;
-
-use Doctrine\Persistence\ManagerRegistry;
-
-use Google\Client;
-use Google\Service\Calendar;
-use Google\Service\Calendar\Event;
-use Google\Service\Calendar\EventExtendedProperties;
-use Google\Service\Calendar\EventSource;
-
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
-use Symfony\Component\Routing\RouterInterface;
-use Symfony\Contracts\Cache\CacheInterface;
-use Symfony\Contracts\Cache\ItemInterface;
-use Symfony\Contracts\Translation\TranslatorInterface;
-
-use Twig\Extra\Markdown\DefaultMarkdown;
-
-use Rapsys\AirBundle\Command;
-use Rapsys\AirBundle\Entity\GoogleCalendar;
-use Rapsys\AirBundle\Entity\GoogleToken;
-use Rapsys\AirBundle\Entity\Session;
-
-use Rapsys\PackBundle\Util\SluggerUtil;
-
-/**
- * {@inheritdoc}
- *
- * Synchronize sessions in users' calendar
- */
-class Calendar2Command extends Command {
-       /**
-        * Set description
-        *
-        * Shown with bin/console list
-        */
-       protected string $description = 'Synchronize sessions in users\' calendar';
-
-       /**
-        * Set help
-        *
-        * Shown with bin/console --help rapsysair:calendar2
-        */
-       protected string $help = 'This command synchronize sessions in users\' google calendar';
-
-       /**
-        * Set domain
-        */
-       protected string $domain;
-
-       /**
-        * Set item
-        *
-        * Cache item instance
-        */
-       protected ItemInterface $item;
-
-       /**
-        * Set prefix
-        */
-       protected string $prefix;
-
-       /**
-        * Set service
-        *
-        * Google calendar instance
-        */
-       protected Calendar $service;
-
-       /**
-        * {@inheritdoc}
-        *
-        * @param CacheInterface $cache The cache instance
-        * @param Client $google The google client instance
-        * @param DefaultMarkdown $markdown The markdown instance
-        */
-       public function __construct(protected ManagerRegistry $doctrine, protected string $locale, protected RouterInterface $router, protected SluggerUtil $slugger, protected TranslatorInterface $translator, protected CacheInterface $cache, protected Client $google, protected DefaultMarkdown $markdown) {
-               //Call parent constructor
-               parent::__construct($this->doctrine, $this->locale, $this->router, $this->slugger, $this->translator);
-
-               //Replace google client redirect uri
-               $this->google->setRedirectUri($this->router->generate($this->google->getRedirectUri(), [], UrlGeneratorInterface::ABSOLUTE_URL));
-       }
-
-       /**
-        * Process the attribution
-        */
-       protected function execute(InputInterface $input, OutputInterface $output): int {
-               //Get domain
-               $this->domain = $this->router->getContext()->getHost();
-
-               //Get manager
-               $manager = $this->doctrine->getManager();
-
-               //Convert from any to latin, then to ascii and lowercase
-               $trans = \Transliterator::create('Any-Latin; Latin-ASCII; Lower()');
-
-               //Replace every non alphanumeric character by dash then trim dash
-               $this->prefix = preg_replace(['/(\.[a-z0-9]+)+$/', '/[^a-v0-9]+/'], '', $trans->transliterate($this->domain));
-
-               //With too short prefix
-               if ($this->prefix === null || strlen($this->prefix) < 4) {
-                       //Throw domain exception
-                       throw new \DomainException('Prefix too short: '.$this->prefix);
-               }
-
-               //Iterate on google tokens
-               foreach($tokens = $this->doctrine->getRepository(GoogleToken::class)->findAllIndexed() as $tid => $token) {
-                       //Clear client cache before changing access token
-                       //TODO: set a per token cache ?
-                       $this->google->getCache()->clear();
-
-                       //Set access token
-                       $this->google->setAccessToken(
-                               [
-                                       'access_token' => $token['access'],
-                                       'refresh_token' => $token['refresh'],
-                                       'created' => $token['created']->getTimestamp(),
-                                       'expires_in' => $token['expired']->getTimestamp() - (new \DateTime('now'))->getTimestamp()
-                               ]
-                       );
-
-                       //With expired token
-                       if ($this->google->isAccessTokenExpired()) {
-                               //Refresh token
-                               if (($gRefresh = $this->google->getRefreshToken()) && ($gToken = $this->google->fetchAccessTokenWithRefreshToken($gRefresh)) && empty($gToken['error'])) {
-                                       //Get google token
-                                       $googleToken = $this->doctrine->getRepository(GoogleToken::class)->findOneById($token['id']);
-
-                                       //Set access token
-                                       $googleToken->setAccess($gToken['access_token']);
-
-                                       //Set expires
-                                       $googleToken->setExpired(new \DateTime('+'.$gToken['expires_in'].' second'));
-
-                                       //Set refresh
-                                       $googleToken->setRefresh($gToken['refresh_token']);
-
-                                       //Queue google token save
-                                       $manager->persist($googleToken);
-
-                                       //Flush to get the ids
-                                       $manager->flush();
-                               //Refresh failed
-                               } else {
-                                       //Show error
-                                       fprintf(STDERR, 'Unable to refresh token %d: %s', $token['id'], $gToken['error']?:'');
-
-                                       //TODO: warn user by mail ?
-
-                                       //Skip to next token
-                                       continue;
-                               }
-                       }
-
-                       //Get google calendar service
-                       $this->service = new Calendar($this->google);
-
-                       //Iterate on google calendars
-                       foreach($calendars = $token['calendars'] as $cid => $calendar) {
-                               //Set start
-                               $synchronized = null;
-
-                               //Set cache key
-                               $cacheKey = 'command.calendar2.'.$this->slugger->short($calendar['mail']);
-
-                               //XXX: TODO: remove DEBUG
-                               #$this->cache->delete($cacheKey);
-
-                               //Retrieve calendar events
-                               try {
-                                       //Get events
-                                       $events = $this->cache->get(
-                                               //Cache key
-                                               //XXX: set to command.calendar2.$mail
-                                               $cacheKey,
-                                               //Fetch mail calendar event list
-                                               function (ItemInterface $item) use ($calendar, &$synchronized): array {
-                                                       //Expire after 1h
-                                                       $item->expiresAfter(3600);
-
-                                                       //Set synchronized
-                                                       $synchronized = new \DateTime('now');
-
-                                                       //Init events
-                                                       $events = [];
-
-                                                       //Set filters
-                                                       //TODO: add a filter to only retrieve
-                                                       $filters = [
-                                                               //XXX: every event even deleted one to be able to update them
-                                                               'showDeleted' => true,
-                                                               //XXX: every instances
-                                                               'singleEvents' => false,
-                                                               //XXX: select only domain events
-                                                               'privateExtendedProperty' => 'domain='.$this->domain
-                                                               #TODO: restrict events even more by time or updated datetime ? (-1 week to +2 week)
-                                                               //TODO: fetch events one day before and one day after to avoid triggering double insert duplicate key 409 errors :=) on google
-                                                               #'timeMin' => $period->getStartDate()->format(\DateTime::ISO8601),
-                                                               #'timeMax' => $period->getEndDate()->format(\DateTime::ISO8601)
-                                                               /*, 'iCalUID' => 'airlibre/?????'*//*'orderBy' => 'startTime', */
-                                                               //updatedMin => new \DateTime('-1 week') ?
-                                                       ];
-
-                                                       //Set page token
-                                                       $pageToken = null;
-
-                                                       //Iterate until next page token is null
-                                                       do {
-                                                               //Get calendar events list
-                                                               //XXX: see vendor/google/apiclient-services/src/Calendar/Resource/Events.php +289
-                                                               $eventList = $this->service->events->listEvents($calendar['mail'], ['pageToken' => $pageToken]+$filters);
-
-                                                               //Iterate on items
-                                                               foreach($eventList->getItems() as $event) {
-                                                                       //With extended properties
-                                                                       if (($properties = $event->getExtendedProperties()) && ($private = $properties->getPrivate()) && isset($private['id']) && ($id = $private['id']) && isset($private['domain']) && $private['domain'] == $this->domain) {
-                                                                               //Add event
-                                                                               $events[$id] = $event;
-                                                                       //XXX: 3rd party events without matching prefix and id are skipped
-                                                                       #} else {
-                                                                       #       #echo 'Skipping '.$event->getId().':'.$event->getSummary()."\n";
-                                                                       #       echo 'Skipping '.$id.':'.$event->getSummary()."\n";
-                                                                       }
-                                                               }
-                                                       } while ($pageToken = $eventList->getNextPageToken());
-
-                                                       //Return events
-                                                       return $events;
-                                               }
-                                       );
-                               //Catch exception
-                               } catch(\Google\Service\Exception $e) {
-                                       //With 401 or code
-                                       //XXX: see https://cloud.google.com/apis/design/errors
-                                       if ($e->getCode() == 401 || $e->getCode() == 403) {
-                                               //Show error
-                                               fprintf(STDERR, 'Unable to list calendar %d events: %s', $calendar['id'], $e->getMessage()?:'');
-
-                                               //TODO: warn user by mail ?
-
-                                               //Skip to next token
-                                               continue;
-                                       }
-
-                                       //Throw error
-                                       throw new \LogicException('Calendar event list failed', 0, $e);
-                               }
-
-                               //Store cache item
-                               $this->item = $this->cache->getItem($cacheKey);
-
-                               //Iterate on sessions to update
-                               foreach($this->doctrine->getRepository(Session::class)->findAllByUserIdSynchronized($token['uid'], $calendar['synchronized']) as $session) {
-                                       //Start exception catching
-                                       try {
-                                               //Without event
-                                               if (!isset($events[$session['id']])) {
-                                                       //Insert event
-                                                       $this->insert($calendar['mail'], $session);
-                                               //With locked session
-                                               } elseif (($event = $events[$session['id']]) && !empty($session['locked'])) {
-                                                       //Delete event
-                                                       $sid = $this->delete($calendar['mail'], $event);
-
-                                                       //Drop from events array
-                                                       unset($events[$sid]);
-                                               //With event to update
-                                               } elseif ($session['modified'] > (new \DateTime($event->getUpdated()))) {
-                                                       //Update event
-                                                       $sid = $this->update($calendar['mail'], $event, $session);
-
-                                                       //Drop from events array
-                                                       unset($events[$sid]);
-                                               }
-                                       //Catch exception
-                                       } catch(\Google\Service\Exception $e) {
-                                               //Identifier already exists
-                                               if ($e->getCode() == 409) {
-                                                       //Get calendar event
-                                                       //XXX: see vendor/google/apiclient-services/src/Calendar/Resource/Events.php +81
-                                                       $event = $this->service->events->get($calendar['mail'], $this->prefix.$session['id']);
-
-                                                       //Update required
-                                                       if ($session['modified'] > (new \DateTime($event->getUpdated()))) {
-                                                               //Update event
-                                                               $sid = $this->update($calendar['mail'], $event, $session);
-
-                                                               //Drop from events array
-                                                               unset($events[$sid]);
-                                                       }
-                                               //TODO: handle other codes gracefully ? (503 & co)
-                                               //Other errors
-                                               } else {
-                                                       //Throw error
-                                                       throw new \LogicException(sprintf('Calendar %s event %s operation failed', $calendar, $this->prefix.$session['id']), 0, $e);
-                                               }
-                                       }
-                               }
-
-                               //Get all sessions
-                               $sessions = $this->doctrine->getRepository(Session::class)->findAllByUserIdSynchronized($token['uid']);
-
-                               //Remaining events to drop
-                               foreach($events as $eid => $event) {
-                                       //With events updated since last synchronized
-                                       if ($event->getStatus() == 'confirmed' && (new \DateTime($event->getUpdated())) > $calendar['synchronized']) {
-                                               //TODO: Add a try/catch here to handle error codes gracefully (503 & co) ?
-                                               //With event to update
-                                               if (isset($sessions[$eid]) && ($session = $sessions[$eid]) && empty($session['locked'])) {
-                                                       //Update event
-                                                       $sid = $this->update($calendar['mail'], $event, $session);
-
-                                                       //Drop from events array
-                                                       unset($events[$sid]);
-                                               //With locked or unknown session
-                                               } else {
-                                                       //Delete event
-                                                       $sid = $this->delete($calendar['mail'], $event);
-
-                                                       //Drop from events array
-                                                       unset($events[$sid]);
-                                               }
-                                       }
-                               }
-
-                               //Persist cache item
-                               $this->cache->commit();
-
-                               //With synchronized
-                               //XXX: only store synchronized on run without caching
-                               if ($synchronized) {
-                                       //Get google calendar
-                                       $googleCalendar = $this->doctrine->getRepository(GoogleCalendar::class)->findOneById($calendar['id']);
-
-                                       //Set synchronized
-                                       $googleCalendar->setSynchronized($synchronized);
-
-                                       //Queue google calendar save
-                                       $manager->persist($googleCalendar);
-
-                                       //Flush to get the ids
-                                       $manager->flush();
-                               }
-                       }
-               }
-
-               //Return success
-               return self::SUCCESS;
-       }
-
-       /**
-        * Delete event
-        *
-        * @param string $calendar The calendar mail
-        * @param Event $event The google event instance
-        * @return void
-        */
-       function delete(string $calendar, Event $event): int {
-               //Get cache events
-               $cacheEvents = $this->item->get();
-
-               //Get event id
-               $eid = $event->getId();
-
-               //Delete the event
-               $this->service->events->delete($calendar, $eid);
-
-               //Set sid
-               $sid = intval(substr($event->getId(), strlen($this->prefix)));
-
-               //Remove from events and cache events
-               unset($cacheEvents[$sid]);
-
-               //Set cache events
-               $this->item->set($cacheEvents);
-
-               //Save cache item
-               $this->cache->saveDeferred($this->item);
-
-               //Return session id
-               return $sid;
-       }
-
-       /**
-        * Fill event
-        *
-        * TODO: add domain based/calendar mail specific templates ?
-        *
-        * @param array $session The session instance
-        * @param ?Event $event The event instance
-        * @return Event The filled event
-        */
-       function fill(array $session, ?Event $event = null): Event {
-               //Init private properties
-               $private = [
-                       'id' => $session['id'],
-                       'domain' => $this->domain,
-                       'updated' => $session['modified']->format(\DateTime::ISO8601)
-               ];
-
-               //Init shared properties
-               //TODO: validate for constraints here ??? https://developers.google.com/calendar/api/guides/extended-properties
-               //TODO: drop shared as unused ???
-               $shared = [
-                       'gps' => $session['l_latitude'].','.$session['l_longitude']
-               ];
-
-               //Init source
-               $source = new EventSource(
-                       [
-                               'title' => $this->translator->trans('%dance% %id% by %pseudonym%', ['%id%' => $session['id'], '%dance%' => $this->translator->trans($session['ad_name'].' '.lcfirst($session['ad_type'])), '%pseudonym%' => $session['au_pseudonym']]).' '.$this->translator->trans('at '.$session['l_title']),
-                               'url' => $this->router->generate('rapsysair_session_view', ['id' => $session['id'], 'location' => $this->slugger->slug($this->translator->trans($session['l_title'])), 'dance' => $this->slugger->slug($this->translator->trans($session['ad_name'].' '.lcfirst($session['ad_type']))), 'user' => $this->slugger->slug($session['au_pseudonym'])], UrlGeneratorInterface::ABSOLUTE_URL)
-                       ]
-               );
-
-               //Init location
-               $description = 'Emplacement :'."\n".$this->translator->trans($session['l_description']);
-               $shared['location'] = strip_tags($this->translator->trans($session['l_description']));
-
-               //Add description when available
-               if(!empty($session['p_description'])) {
-                       $description .= "\n\n".'Description :'."\n".strip_tags(preg_replace('!<a href="([^"]+)"(?: title="[^"]+")?'.'>([^<]+)</a>!', '\1', $this->markdown->convert(strip_tags($session['p_description']))));
-                       $shared['description'] = $this->markdown->convert(strip_tags($session['p_description']));
-               }
-
-               //Add class when available
-               if (!empty($session['p_class'])) {
-                       $description .= "\n\n".'Classe :'."\n".$session['p_class'];
-                       $shared['class'] = $session['p_class'];
-               }
-
-               //Add contact when available
-               if (!empty($session['p_contact'])) {
-                       $description .= "\n\n".'Contact :'."\n".$session['p_contact'];
-                       $shared['contact'] = $session['p_contact'];
-               }
-
-               //Add donate when available
-               if (!empty($session['p_donate'])) {
-                       $description .= "\n\n".'Contribuer :'."\n".$session['p_donate'];
-                       $shared['donate'] = $session['p_donate'];
-               }
-
-               //Add link when available
-               if (!empty($session['p_link'])) {
-                       $description .= "\n\n".'Site :'."\n".$session['p_link'];
-                       $shared['link'] = $session['p_link'];
-               }
-
-               //Add profile when available
-               if (!empty($session['p_profile'])) {
-                       $description .= "\n\n".'Réseau social :'."\n".$session['p_profile'];
-                       $shared['profile'] = $session['p_profile'];
-               }
-
-               //Set properties
-               $properties = new EventExtendedProperties(
-                       [
-                               //Set private property
-                               'private' => $private,
-                               //Set shared property
-                               'shared' => $shared
-                       ]
-               );
-
-               //Without event
-               if ($event === null) {
-                       //Init event
-                       $event = new Event(
-                               [
-                                       //Id must match /^[a-v0-9]{5,}$/
-                                       //XXX: see https://developers.google.com/calendar/api/v3/reference/events/insert#id
-                                       'id' => $this->prefix.$session['id'],
-                                       'summary' => $source->getTitle(),
-                                       'description' => $description,
-                                       'status' => empty($session['a_canceled'])?'confirmed':'cancelled',
-                                       'location' => implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']]),
-                                       'source' => $source,
-                                       'extendedProperties' => $properties,
-                                       //TODO: colorId ?
-                                       //TODO: attendees[] ?
-                                       'start' => [
-                                               'dateTime' => $session['start']->format(\DateTime::ISO8601)
-                                       ],
-                                       'end' => [
-                                               'dateTime' => $session['stop']->format(\DateTime::ISO8601)
-                                       ]
-                               ]
-                       );
-               //With event
-               } else {
-                       //Set summary
-                       $event->setSummary($source->getTitle());
-
-                       //Set description
-                       $event->setDescription($description);
-
-                       //Set status
-                       $event->setStatus(empty($session['a_canceled'])?'confirmed':'cancelled');
-
-                       //Set location
-                       $event->setLocation(implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']]));
-
-                       //Get source
-                       #$eventSource = $event->getSource();
-
-                       //Update source title
-                       #$eventSource->setTitle($source->getTitle());
-
-                       //Update source url
-                       #$eventSource->setUrl($source->getUrl());
-
-                       //Set source
-                       $event->setSource($source);
-
-                       //Get extended properties
-                       #$extendedProperties = $event->getExtendedProperties();
-
-                       //Update private
-                       #$extendedProperties->setPrivate($properties->getPrivate());
-
-                       //Update shared
-                       #$extendedProperties->setShared($properties->getShared());
-
-                       //Set properties
-                       $event->setExtendedProperties($properties);
-
-                       //TODO: colorId ?
-                       //TODO: attendees[] ?
-
-                       //Set start
-                       $start = $event->getStart();
-
-                       //Update start datetime
-                       $start->setDateTime($session['start']->format(\DateTime::ISO8601));
-
-                       //Set end
-                       $end = $event->getEnd();
-
-                       //Update stop datetime
-                       $end->setDateTime($session['stop']->format(\DateTime::ISO8601));
-               }
-
-               //Return event
-               return $event;
-       }
-
-       /**
-        * Insert event
-        *
-        * @param string $calendar The calendar mail
-        * @param array $session The session instance
-        * @return void
-        */
-       function insert(string $calendar, array $session): void {
-               //Get event
-               $event = $this->fill($session);
-
-               //Get cache events
-               $cacheEvents = $this->item->get();
-
-               //Insert in cache event
-               $cacheEvents[$session['id']] = $this->service->events->insert($calendar, $event);
-
-               //Set cache events
-               $this->item->set($cacheEvents);
-
-               //Save cache item
-               $this->cache->saveDeferred($this->item);
-       }
-
-       /**
-        * Update event
-        *
-        * @param string $calendar The calendar mail
-        * @param Event $event The google event instance
-        * @param array $session The session instance
-        * @return int The session id
-        */
-       function update(string $calendar, Event $event, array $session): int {
-               //Get event
-               $event = $this->fill($session, $event);
-
-               //Get cache events
-               $cacheEvents = $this->item->get();
-
-               //Update in cache events
-               $cacheEvents[$session['id']] = $this->service->events->update($calendar, $event->getId(), $event);
-
-               //Set cache events
-               $this->item->set($cacheEvents);
-
-               //Save cache item
-               $this->cache->saveDeferred($this->item);
-
-               //Return session id
-               return $session['id'];
-       }
-}