]> Raphaël G. Git Repositories - airbundle/commitdiff
Add calendar command
authorRaphaël Gertz <git@rapsys.eu>
Wed, 7 Jul 2021 15:23:48 +0000 (17:23 +0200)
committerRaphaël Gertz <git@rapsys.eu>
Wed, 7 Jul 2021 15:23:48 +0000 (17:23 +0200)
Command/CalendarCommand.php [new file with mode: 0644]

diff --git a/Command/CalendarCommand.php b/Command/CalendarCommand.php
new file mode 100644 (file)
index 0000000..84e096f
--- /dev/null
@@ -0,0 +1,508 @@
+<?php
+
+namespace Rapsys\AirBundle\Command;
+
+use Doctrine\Persistence\ManagerRegistry;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\RouterInterface;
+use Symfony\Component\Translation\TranslatorInterface;
+use Twig\Extra\Markdown\DefaultMarkdown;
+
+use Rapsys\AirBundle\Entity\Session;
+
+class CalendarCommand extends Command {
+       //Set failure constant
+       const FAILURE = 1;
+
+       ///Set success constant
+       const SUCCESS = 0;
+
+       ///Config array
+       protected $config;
+
+       /**
+        * Doctrine instance
+        *
+        * @var ManagerRegistry
+        */
+       protected $doctrine;
+
+       ///Locale
+       protected $locale;
+
+       ///Translator instance
+       protected $translator;
+
+       /**
+        * Inject doctrine, container and translator interface
+        *
+        * @param ContainerInterface $container The container instance
+        * @param ManagerRegistry $doctrine The doctrine instance
+        * @param RouterInterface $router The router instance
+        * @param TranslatorInterface $translator The translator instance
+        */
+    public function __construct(ContainerInterface $container, ManagerRegistry $doctrine, RouterInterface $router, TranslatorInterface $translator) {
+               //Call parent constructor
+               parent::__construct();
+
+               //Retrieve config
+               $this->config = $container->getParameter($this->getAlias());
+
+               //Retrieve locale
+               $this->locale = $container->getParameter('kernel.default_locale');
+
+               //Store doctrine
+        $this->doctrine = $doctrine;
+
+               //Store router
+        $this->router = $router;
+
+               //Get router context
+               $context = $this->router->getContext();
+
+               //Set host
+               $context->setHost('airlibre.eu');
+
+               //Set scheme
+               $context->setScheme('https');
+
+               //Set the translator
+               $this->translator = $translator;
+       }
+
+       ///Configure attribute command
+       protected function configure() {
+               //Configure the class
+               $this
+                       //Set name
+                       ->setName('rapsysair:calendar')
+                       //Set description shown with bin/console list
+                       ->setDescription('Synchronize sessions in calendar')
+                       //Set description shown with bin/console --help airlibre:attribute
+                       ->setHelp('This command synchronize sessions in google calendar');
+       }
+
+       ///Process the attribution
+       protected function execute(InputInterface $input, OutputInterface $output) {
+               //Compute period
+               $period = new \DatePeriod(
+                       //Start from last week
+                       new \DateTime('-1 week'),
+                       //Iterate on each day
+                       new \DateInterval('P1D'),
+                       //End with next 2 week
+                       new \DateTime('+2 week')
+               );
+
+               //Retrieve events to update
+               $sessions = $this->doctrine->getRepository(Session::class)->fetchAllByDatePeriod($period, $this->locale);
+
+               //Markdown converted instance
+               $markdown = new DefaultMarkdown;
+
+               //Retrieve cache object
+               //XXX: by default stored in /tmp/symfony-cache/@/W/3/6SEhFfeIW4UMDlAII+Dg
+               //XXX: stored in %kernel.project_dir%/var/cache/airlibre/0/P/IA20X0K4dkMd9-+Ohp9Q
+               $cache = new FilesystemAdapter($this->config['cache']['namespace'], $this->config['cache']['lifetime'], $this->config['cache']['directory']);
+
+               //Retrieve calendars
+               $cacheCalendars = $cache->getItem('calendars');
+
+               //Without calendars
+               if (!$cacheCalendars->isHit()) {
+                       //Return failure
+                       return self::FAILURE;
+               }
+
+               //Retrieve calendars
+               $calendars = $cacheCalendars->get();
+
+               //XXX: calendars content
+               #var_export($calendars);
+               #$debug = [
+               #       '635317121880-usqucmne71jnmprl8br9khh2om4n8cmh.apps.googleusercontent.com' => [
+               #               'project' => 'calendar-317315',
+               #               'secret' => 'HRsKd4FIc9gxQHM4IoBWnlbD',
+               #               'redirect' => 'https://airlibre.eu/calendar/callback',
+               #               'tokens' => [
+               #                       'ya29.a0ARrdaM_cNpedJ-B3irC76_0-C7cfF-WmMh0smAs4m7cSvBChnniWr-e79q0IfAbh5DSG4FlHbCMvmaYb7xX4V45PujT2U4InZmpHfspiPv-QeR4XeZJp7bLXwnw7A4M0imeeYyQcwCW7GJ8O7dGLBQlBZAvt_Q' => [
+               #                               'calendar' => 'airlibre',
+               #                               'expire' => 3599,
+               #                               'scope' => 'https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events',
+               #                               'type' => 'Bearer',
+               #                               'created' => 1625417137,
+               #                       ],
+               #               ],
+               #       ],
+               #];
+
+               //Check expired token
+               foreach($calendars as $clientId => $client) {
+                       //Get google client
+                       $googleClient = new \Google\Client(['application_name' => $client['project'], 'client_id' => $clientId, 'client_secret' => $client['secret'], 'redirect_uri' => $client['redirect']]);
+
+                       //Iterate on each tokens
+                       foreach($client['tokens'] as $tokenId => $token) {
+                               //Set token
+                               $googleClient->setAccessToken(
+                                       [
+                                               'access_token' => $tokenId,
+                                               'refresh_token' => $token['refresh'],
+                                               'expires_in' => $token['expire'],
+                                               'scope' => $token['scope'],
+                                               'token_type' => $token['type'],
+                                               'created' => $token['created']
+                                       ]
+                               );
+
+                               //With expired token
+                               if ($exp = $googleClient->isAccessTokenExpired()) {
+                                       //Refresh token
+                                       if ($googleClient->getRefreshToken()) {
+                                               //Retrieve refreshed token
+                                               $googleToken = $googleClient->fetchAccessTokenWithRefreshToken($googleClient->getRefreshToken());
+
+                                               //Add refreshed token
+                                               $calendars[$clientId]['tokens'][$googleToken['access_token']] = [
+                                                       'calendar' => $token['calendar'],
+                                                       'prefix' => $token['prefix'],
+                                                       'refresh' => $googleToken['refresh_token'],
+                                                       'expire' => $googleToken['expires_in'],
+                                                       'scope' => $googleToken['scope'],
+                                                       'type' => $googleToken['token_type'],
+                                                       'created' => $googleToken['created']
+                                               ];
+
+                                               //Remove old token
+                                               unset($calendars[$clientId]['tokens'][$tokenId]);
+                                       } else {
+                                               //Drop token
+                                               unset($calendars[$clientId]['tokens'][$tokenId]);
+
+                                               //Without tokens
+                                               if (empty($calendars[$clientId]['tokens'])) {
+                                                       //Drop client
+                                                       unset($calendars[$clientId]);
+                                               }
+
+                                               //Drop token and report
+                                               echo 'Token '.$tokenId.' for calendar '.$token['calendar'].' has expired and is not refreshable'."\n";
+
+                                               //Return failure
+                                               //XXX: we want that mail and stop here
+                                               return self::FAILURE;
+                                       }
+                               }
+                       }
+               }
+
+               //Save calendars
+               $cacheCalendars->set($calendars);
+
+               //Save calendar
+               $cache->save($cacheCalendars);
+
+               //Iterate on each calendar client
+               foreach($calendars as $clientId => $client) {
+                       //Get google client
+                       $googleClient = new \Google\Client(['application_name' => $client['project'], 'client_id' => $clientId, 'client_secret' => $client['secret'], 'redirect_uri' => $client['redirect']]);
+
+                       //Iterate on each tokens
+                       foreach($client['tokens'] as $tokenId => $token) {
+                               //Set token
+                               $googleClient->setAccessToken(
+                                       [
+                                               'access_token' => $tokenId,
+                                               'refresh_token' => $token['refresh'],
+                                               'expires_in' => $token['expire'],
+                                               'scope' => $token['scope'],
+                                               'token_type' => $token['type'],
+                                               'created' => $token['created']
+                                       ]
+                               );
+
+                               //With expired token
+                               if ($exp = $googleClient->isAccessTokenExpired()) {
+                                       //Last chance to skip this run
+                                       continue;
+                               }
+
+                               //Get google calendar
+                               $googleCalendar = new \Google\Service\Calendar($googleClient);
+
+                               //Retrieve calendar
+                               try {
+                                       $calendar = $googleCalendar->calendars->get($token['calendar']);
+                               //Catch exception
+                               } catch(\Google\Service\Exception $e) {
+                                       //Display exception
+                                       //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
+                                       echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
+                                       echo $e->getTraceAsString()."\n";
+
+                                       //Return failure
+                                       return self::FAILURE;
+                               }
+
+                               //Init events
+                               $events = [];
+
+                               //Set filters
+                               $filters = [
+                                       //XXX: show even deleted event to be able to update them
+                                       'showDeleted' => true,
+                                       //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', */
+                               ];
+
+                               //Retrieve event collection
+                               $googleEvents = $googleCalendar->events->listEvents($token['calendar'], $filters);
+
+                               //Iterate until reached end
+                               while (true) {
+                                       //Iterate on each event
+                                       foreach ($googleEvents->getItems() as $event) {
+                                               //Store event by id
+                                               if (preg_match('/^'.$token['prefix'].'([0-9]+)$/', $id = $event->getId(), $matches)) {
+                                                       $events[$matches[1]] = $event;
+                                               //XXX: 3rd party events with id not matching prefix are skipped
+                                               #} else {
+                                               #       echo 'Skipping '.$event->getId().':'.$event->getSummary()."\n";*/
+                                               }
+                                       }
+
+                                       //Get page token
+                                       $pageToken = $googleEvents->getNextPageToken();
+
+                                       //Handle next page
+                                       if ($pageToken) {
+                                               //Replace collection with next one
+                                               $googleEvents = $service->events->listEvents($token['calendar'], $filters+['pageToken' => $pageToken]);
+                                       } else {
+                                               break;
+                                       }
+                               }
+
+                               //Iterate on each session to sync
+                               foreach($sessions as $sessionId => $session) {
+                                       //Init shared properties
+                                       //TODO: validate for constraints here ??? https://developers.google.com/calendar/api/guides/extended-properties
+                                       $shared = [
+                                               'gps' => $session['l_latitude'].','.$session['l_longitude']
+                                       ];
+
+                                       //Init source
+                                       $source = [
+                                               'title' => $this->translator->trans('Session %id% by %pseudonym%', ['%id%' => $sessionId, '%pseudonym%' => $session['au_pseudonym']]).' '.$this->translator->trans('at '.$session['l_title']),
+                                               'url' => $this->router->generate('rapsys_air_session_view', ['id' => $sessionId], UrlGeneratorInterface::ABSOLUTE_URL)
+                                       ];
+
+                                       //Init description
+                                       #$description = '<dl><dt>Description</dt><dd>'.$markdown->convert(strip_tags(str_replace(["\r", "\n\n"], ['', "\n"], $session['p_description']))).'</dd></dl>';
+                                       $description = '<dl><dt>Description</dt><dd>'.$markdown->convert(strip_tags($session['p_description'])).'</dd></dl>';
+
+                                       //Add class when available
+                                       if (!empty($session['p_class'])) {
+                                               $shared['class'] = $session['p_class'];
+                                               #$description .= '<dl><dt>Classe</dt><dd>'.$markdown->convert(strip_tags(str_replace(["\r", "\n\n"], ['', "\n"], $session['p_class']))).'</dd></dl>';
+                                               $description .= '<dl><dt>Classe</dt><dd><p>'.$session['p_class'].'</p></dd></dl>';
+                                       }
+
+                                       //Add contact when available
+                                       if (!empty($session['p_contact'])) {
+                                               $shared['contact'] = $session['p_contact'];
+                                               $description .= '<dl><dt>Contacter</dt><dd><p>'.$session['p_contact'].'</p></dd></dl>';
+                                       }
+
+                                       //Add donate when available
+                                       if (!empty($session['p_donate'])) {
+                                               $shared['donate'] = $session['p_donate'];
+                                               $description .= '<dl><dt>Contribuer</dt><dd><p>'.$session['p_donate'].'</p></dd></dl>';
+                                       }
+
+                                       //Add link when available
+                                       if (!empty($session['p_link'])) {
+                                               $shared['link'] = $session['p_link'];
+                                               $description .= '<dl><dt>Site</dt><dd><p>'.$session['p_link'].'</p></dd></dl>';
+                                       }
+
+                                       //Add profile when available
+                                       if (!empty($session['p_profile'])) {
+                                               $shared['profile'] = $session['p_profile'];
+                                               $description .= '<dl><dt>Réseau social</dt><dd><p>'.$session['p_profile'].'</p></dd></dl>';
+                                       }
+
+                                       //Locked session
+                                       if (!empty($session['locked']) && $events[$sessionId]) {
+                                               //With events
+                                               if (!empty($event = $events[$sessionId])) {
+                                                       try {
+                                                               //Delete the event
+                                                               $googleCalendar->events->delete($token['calendar'], $event->getId());
+                                                       //Catch exception
+                                                       } catch(\Google\Service\Exception $e) {
+                                                               //Display exception
+                                                               //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
+                                                               echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
+                                                               echo $e->getTraceAsString()."\n";
+
+                                                               //Return failure
+                                                               return self::FAILURE;
+                                                       }
+                                               }
+                                       //Without event
+                                       } elseif (empty($events[$sessionId])) {
+                                               //Init event
+                                               $event = new \Google\Service\Calendar\Event(
+                                                       [
+                                                               //TODO: replace 'airlibre' with $this->config['calendar']['prefix'] when possible with prefix validating [a-v0-9]{5,}
+                                                               //XXX: see https://developers.google.com/calendar/api/v3/reference/events/insert#id
+                                                               'id' => $token['prefix'].$sessionId,
+                                                               'summary' => $session['au_pseudonym'].' '.$this->translator->trans('at '.$session['l_short']),
+                                                               #'description' => $markdown->convert(strip_tags($session['p_description'])),
+                                                               'description' => $description,
+                                                               'status' => empty($session['a_canceled'])?'confirmed':'cancelled',
+                                                               'location' => implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']]),
+                                                               'source' => $source,
+                                                               'extendedProperties' => [
+                                                                       'shared' => $shared
+                                                               ],
+                                                               //TODO: colorId ?
+                                                               //TODO: attendees[] ?
+                                                               'start' => [
+                                                                       'dateTime' => $session['start']->format(\DateTime::ISO8601)
+                                                               ],
+                                                               'end' => [
+                                                                       'dateTime' => $session['stop']->format(\DateTime::ISO8601)
+                                                               ]
+                                                       ]
+                                               );
+
+                                               try {
+                                                       //Insert the event
+                                                       $googleCalendar->events->insert($token['calendar'], $event);
+                                               //Catch exception
+                                               } catch(\Google\Service\Exception $e) {
+                                                       //Display exception
+                                                       //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
+                                                       echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
+                                                       echo $e->getTraceAsString()."\n";
+
+                                                       //Return failure
+                                                       return self::FAILURE;
+                                               }
+                                       // With event
+                                       } else {
+                                               //Set event
+                                               $event = $events[$sessionId];
+
+                                               //With updated event
+                                               #if ($session['updated'] >= (new \DateTime($event->getUpdated()))) {
+                                               {
+                                                       //Set summary
+                                                       $event->setSummary($session['au_pseudonym'].' '.$this->translator->trans('at '.$session['l_short']));
+
+                                                       //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['title']);
+
+                                                       //Update source url
+                                                       $eventSource->setUrl($source['url']);
+
+                                                       //Set source
+                                                       #$event->setSource($source);
+
+                                                       //Get extended properties
+                                                       $extendedProperties = $event->getExtendedProperties();
+
+                                                       //Update shared
+                                                       $extendedProperties->setShared($shared);
+
+                                                       //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));
+
+                                                       try {
+                                                               //Insert the event
+                                                               $updatedEvent = $googleCalendar->events->update($token['calendar'], $event->getId(), $event);
+                                                       //Catch exception
+                                                       } catch(\Google\Service\Exception $e) {
+                                                               //Display exception
+                                                               //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
+                                                               echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
+                                                               echo $e->getTraceAsString()."\n";
+
+                                                               //Return failure
+                                                               return self::FAILURE;
+                                                       }
+                                               }
+
+                                               //Drop from events array
+                                               unset($events[$sessionId]);
+                                       }
+                               }
+
+                               //Remaining events to drop
+                               foreach($events as $eventId => $event) {
+                                       //Non canceled events
+                                       if ($event->getStatus() == 'confirmed') {
+                                               try {
+                                                       //Delete the event
+                                                       $googleCalendar->events->delete($token['calendar'], $event->getId());
+                                               //Catch exception
+                                               } catch(\Google\Service\Exception $e) {
+                                                       //Display exception
+                                                       //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors
+                                                       echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n";
+                                                       echo $e->getTraceAsString()."\n";
+
+                                                       //Return failure
+                                                       return self::FAILURE;
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               //Return success
+               return self::SUCCESS;
+       }
+
+       /**
+        * Return the bundle alias
+        *
+        * {@inheritdoc}
+        */
+       public function getAlias(): string {
+               return 'rapsys_air';
+       }
+}