]> Raphaël G. Git Repositories - airbundle/commitdiff
List dance sessions master
authorRaphaël Gertz <git@rapsys.eu>
Sat, 26 Apr 2025 03:32:52 +0000 (05:32 +0200)
committerRaphaël Gertz <git@rapsys.eu>
Sat, 26 Apr 2025 03:32:52 +0000 (05:32 +0200)
Command/Calendar2Command.php [deleted file]
Command/CalendarCommand.php
Command/WeatherCommand.php
Controller/ApplicationController.php
Controller/DanceController.php
Repository/DanceRepository.php
Resources/config/packages/rapsysair.yaml

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'];
-       }
-}
index dca096bf368d6b286ef01e6c2117ee3855f099cb..9a7edb3f28c1fab07e32d4d5cd7caaba7bac3dfd 100644 (file)
 namespace Rapsys\AirBundle\Command;
 
 use Doctrine\Persistence\ManagerRegistry;
-use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+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\DependencyInjection\ContainerInterface;
 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' google calendar
+ */
 class CalendarCommand extends Command {
        /**
-        * Creates new calendar command
+        * Set description
         *
-        * @param ManagerRegistry $doctrine The doctrine instance
-        * @param string $locale The default locale
-        * @param RouterInterface $router The router instance
-        * @param SluggerUtil $slugger The slugger instance
-        * @param TranslatorInterface $translator The translator instance
-        * @param string $namespace The cache namespace
-        * @param int $lifetime The cache lifetime
-        * @param string $path The cache path
+        * Shown with bin/console list
         */
-       public function __construct(protected ManagerRegistry $doctrine, protected string $locale, protected RouterInterface $router, protected SluggerUtil $slugger, protected TranslatorInterface $translator, protected string $namespace, protected int $lifetime, protected string $path) {
-               //Call parent constructor
-               parent::__construct($this->doctrine, $this->locale, $this->router, $this->slugger, $this->translator);
-       }
+       protected string $description = 'Synchronize sessions in users\' calendar';
 
        /**
-        * Configure attribute command
+        * Set help
+        *
+        * Shown with bin/console --help rapsysair:calendar
         */
-       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');
-       }
+       protected string $help = 'This command synchronize sessions in users\' google calendar';
 
        /**
-        * Process the attribution
+        * Set domain
         */
-       protected function execute(InputInterface $input, OutputInterface $output): int {
-               //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')
-               );
+       protected string $domain;
 
-               //Retrieve events to update
-               $sessions = $this->doctrine->getRepository(Session::class)->fetchAllByDatePeriod($period, $this->locale);
+       /**
+        * Set item
+        *
+        * Cache item instance
+        */
+       protected ItemInterface $item;
 
-               //Markdown converted instance
-               $markdown = new DefaultMarkdown;
+       /**
+        * Set prefix
+        */
+       protected string $prefix;
 
-               //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->namespace, $this->lifetime, $this->path);
+       /**
+        * Set service
+        *
+        * Google calendar instance
+        */
+       protected Calendar $service;
 
-               //Retrieve calendars
-               $cacheCalendars = $cache->getItem('calendars');
+       /**
+        * {@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);
 
-               //Without calendars
-               if (!$cacheCalendars->isHit()) {
-                       //Return failure
-                       return self::FAILURE;
-               }
+               //Replace google client redirect uri
+               $this->google->setRedirectUri($this->router->generate($this->google->getRedirectUri(), [], UrlGeneratorInterface::ABSOLUTE_URL));
+       }
 
-               //Retrieve calendars
-               $calendars = $cacheCalendars->get();
-
-               //XXX: calendars content
-               #var_export($calendars);
-
-               //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 (($refreshToken = $googleClient->getRefreshToken()) && ($googleToken = $googleClient->fetchAccessTokenWithRefreshToken($refreshToken)) && empty($googleToken['error'])) {
-                                               //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]);
-                                               }
+       /**
+        * Process the attribution
+        */
+       protected function execute(InputInterface $input, OutputInterface $output): int {
+               //Get domain
+               $this->domain = $this->router->getContext()->getHost();
 
-                                               //Save calendars
-                                               $cacheCalendars->set($calendars);
+               //Get manager
+               $manager = $this->doctrine->getManager();
 
-                                               //Save calendar
-                                               $cache->save($cacheCalendars);
+               //Convert from any to latin, then to ascii and lowercase
+               $trans = \Transliterator::create('Any-Latin; Latin-ASCII; Lower()');
 
-                                               //Drop token and report
-                                               //XXX: submit app to avoid expiration
-                                               //XXX: see https://console.cloud.google.com/apis/credentials/consent?project=calendar-317315
-                                               echo 'Token '.$tokenId.' for calendar '.$token['calendar'].' has expired and is not refreshable'."\n";
+               //Replace every non alphanumeric character by dash then trim dash
+               $this->prefix = preg_replace(['/(\.[a-z0-9]+)+$/', '/[^a-v0-9]+/'], '', $trans->transliterate($this->domain));
 
-                                               //Return failure
-                                               //XXX: we want that mail and stop here
-                                               return self::FAILURE;
-                                       }
-                               }
-                       }
+               //With too short prefix
+               if ($this->prefix === null || strlen($this->prefix) < 4) {
+                       //Throw domain exception
+                       throw new \DomainException('Prefix too short: '.$this->prefix);
                }
 
-               //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
+               //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
+                               //TODO: better handle internal_failure
+                               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
+                                       //TODO: remove that and simply log internal failure ?
+                                       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);
 
-                               //Get google calendar
-                               $googleCalendar = new \Google\Service\Calendar($googleClient);
+                       //Iterate on google calendars
+                       foreach($calendars = $token['calendars'] as $cid => $calendar) {
+                               //Set start
+                               $synchronized = null;
 
-                               //Retrieve calendar
+                               //Set cache key
+                               $cacheKey = 'command.calendar.'.$this->slugger->short($calendar['mail']);
+
+                               //XXX: TODO: remove DEBUG
+                               #$this->cache->delete($cacheKey);
+
+                               //Retrieve calendar events
                                try {
-                                       $calendar = $googleCalendar->calendars->get($token['calendar']);
+                                       //Get events
+                                       $events = $this->cache->get(
+                                               //Cache key
+                                               //XXX: set to command.calendar.$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) {
-                                       //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";
+                                       //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 ?
 
-                                       //Return failure
-                                       return self::FAILURE;
+                                               //Skip to next token
+                                               continue;
+                                       }
+
+                                       //Throw error
+                                       throw new \LogicException('Calendar event list failed', 0, $e);
                                }
 
-                               //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";*/
+                               //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 page token
-                                       $pageToken = $googleEvents->getNextPageToken();
+                                       //Sleep
+                                       usleep(300000);
+                               }
+
+                               //Get all sessions
+                               $sessions = $this->doctrine->getRepository(Session::class)->findAllByUserIdSynchronized($token['uid']);
 
-                                       //Handle next page
-                                       if ($pageToken) {
-                                               //Replace collection with next one
-                                               $googleEvents = $service->events->listEvents($token['calendar'], $filters+['pageToken' => $pageToken]);
-                                       } else {
-                                               break;
+                               //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]);
+                                               }
                                        }
                                }
 
-                               //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
-                                       //TODO: drop shared as unused ???
-                                       $shared = [
-                                               'gps' => $session['l_latitude'].','.$session['l_longitude']
-                                       ];
-
-                                       //Init source
-                                       $source = [
-                                               'title' => $this->translator->trans('%dance% %id% by %pseudonym%', ['%id%' => $sessionId, '%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' => $sessionId, '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'] = $markdown->convert(strip_tags($session['l_description']));
-
-                                       //Add description
-                                       $description .= "\n\n".'Description :'."\n".strip_tags(preg_replace('!<a href="([^"]+)"(?: title="[^"]+")?'.'>([^<]+)</a>!', '\1', $markdown->convert(strip_tags($session['p_description']))));
-                                       $shared['description'] = $markdown->convert(strip_tags($session['p_description']));
-
-                                       //Add class when available
-                                       if (!empty($session['p_class'])) {
-                                               $shared['class'] = $session['p_class'];
-                                               $description .= "\n\n".'Classe :'."\n".$session['p_class'];
-                                       }
+                               //Persist cache item
+                               $this->cache->commit();
 
-                                       //Add contact when available
-                                       if (!empty($session['p_contact'])) {
-                                               $shared['contact'] = $session['p_contact'];
-                                               $description .= "\n\n".'Contact :'."\n".$session['p_contact'];
-                                       }
+                               //With synchronized
+                               //XXX: only store synchronized on run without caching
+                               if ($synchronized) {
+                                       //Get google calendar
+                                       $googleCalendar = $this->doctrine->getRepository(GoogleCalendar::class)->findOneById($calendar['id']);
 
-                                       //Add donate when available
-                                       if (!empty($session['p_donate'])) {
-                                               $shared['donate'] = $session['p_donate'];
-                                               $description .= "\n\n".'Contribuer :'."\n".$session['p_donate'];
-                                       }
+                                       //Set synchronized
+                                       $googleCalendar->setSynchronized($synchronized);
 
-                                       //Add link when available
-                                       if (!empty($session['p_link'])) {
-                                               $shared['link'] = $session['p_link'];
-                                               $description .= "\n\n".'Site :'."\n".$session['p_link'];
-                                       }
+                                       //Queue google calendar save
+                                       $manager->persist($googleCalendar);
 
-                                       //Add profile when available
-                                       if (!empty($session['p_profile'])) {
-                                               $shared['profile'] = $session['p_profile'];
-                                               $description .= "\n\n".'Réseau social :'."\n".$session['p_profile'];
-                                       }
+                                       //Flush to get the ids
+                                       $manager->flush();
+                               }
+                       }
+               }
 
-                                       //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_title']),
-                                                               'summary' => $source['title'],
-                                                               #'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];
+               //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);
 
-                                               //With updated event
-                                               if ($session['updated'] >= (new \DateTime($event->getUpdated()))) {
-                                                       //Set summary
-                                                       #$event->setSummary($session['au_pseudonym'].' '.$this->translator->trans('at '.$session['l_title']));
-                                                       $event->setSummary($source['title']);
+               //Set sid
+               $sid = intval(substr($event->getId(), strlen($this->prefix)));
 
-                                                       //Set description
-                                                       $event->setDescription($description);
+               //Remove from events and cache events
+               unset($cacheEvents[$sid]);
 
-                                                       //Set status
-                                                       $event->setStatus(empty($session['a_canceled'])?'confirmed':'cancelled');
+               //Set cache events
+               $this->item->set($cacheEvents);
 
-                                                       //Set location
-                                                       $event->setLocation(implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']]));
+               //Save cache item
+               $this->cache->saveDeferred($this->item);
 
-                                                       //Get source
-                                                       $eventSource = $event->getSource();
+               //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)
+                       ]
+               );
 
-                                                       //Update source title
-                                                       $eventSource->setTitle($source['title']);
+               //Init location
+               $description = 'Emplacement :'."\n".$this->translator->trans($session['l_description']);
+               $shared['location'] = strip_tags($this->translator->trans($session['l_description']));
 
-                                                       //Update source url
-                                                       $eventSource->setUrl($source['url']);
+               //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']));
+               }
 
-                                                       //Set source
-                                                       #$event->setSource($source);
+               //Add class when available
+               if (!empty($session['p_class'])) {
+                       $description .= "\n\n".'Classe :'."\n".$session['p_class'];
+                       $shared['class'] = $session['p_class'];
+               }
 
-                                                       //Get extended properties
-                                                       $extendedProperties = $event->getExtendedProperties();
+               //Add contact when available
+               if (!empty($session['p_contact'])) {
+                       $description .= "\n\n".'Contact :'."\n".$session['p_contact'];
+                       $shared['contact'] = $session['p_contact'];
+               }
 
-                                                       //Update shared
-                                                       $extendedProperties->setShared($shared);
+               //Add donate when available
+               if (!empty($session['p_donate'])) {
+                       $description .= "\n\n".'Contribuer :'."\n".$session['p_donate'];
+                       $shared['donate'] = $session['p_donate'];
+               }
 
-                                                       //TODO: colorId ?
-                                                       //TODO: attendees[] ?
+               //Add link when available
+               if (!empty($session['p_link'])) {
+                       $description .= "\n\n".'Site :'."\n".$session['p_link'];
+                       $shared['link'] = $session['p_link'];
+               }
 
-                                                       //Set start
-                                                       $start = $event->getStart();
+               //Add profile when available
+               if (!empty($session['p_profile'])) {
+                       $description .= "\n\n".'Réseau social :'."\n".$session['p_profile'];
+                       $shared['profile'] = $session['p_profile'];
+               }
 
-                                                       //Update start datetime
-                                                       $start->setDateTime($session['start']->format(\DateTime::ISO8601));
+               //Set properties
+               $properties = new EventExtendedProperties(
+                       [
+                               //Set private property
+                               'private' => $private,
+                               //Set shared property
+                               'shared' => $shared
+                       ]
+               );
 
-                                                       //Set end
-                                                       $end = $event->getEnd();
+               //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());
 
-                                                       //Update stop datetime
-                                                       $end->setDateTime($session['stop']->format(\DateTime::ISO8601));
+                       //Set description
+                       $event->setDescription($description);
 
-                                                       try {
-                                                               //Update 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";
+                       //Set status
+                       $event->setStatus(empty($session['a_canceled'])?'confirmed':'cancelled');
 
-                                                               //Return failure
-                                                               return self::FAILURE;
-                                                       }
-                                               }
+                       //Set location
+                       $event->setLocation(implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']]));
 
-                                               //Drop from events array
-                                               unset($events[$sessionId]);
-                                       }
-                               }
+                       //Get source
+                       #$eventSource = $event->getSource();
 
-                               //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;
-                                               }
-                                       }
-                               }
-                       }
+                       //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 success
-               return self::SUCCESS;
+               //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'];
        }
 }
index b499390e0912b2f135a08acdf07530b1bb75f588..0318383671451ebc6ad93e40dc6d19fc7937947d 100644 (file)
@@ -177,6 +177,12 @@ class WeatherCommand extends DoctrineCommand {
                                //Set stop day
                                $day = $stop->diff((new \DateTime('now'))->setTime(0, 0, 0))->d + 1;
 
+                               //Skip 4th day
+                               //XXX: accuweather only allow until 3rd day
+                               if ($day >= 4) {
+                                       continue;
+                               }
+
                                //Check if zipcode date is set
                                if (!isset($zipcodes[$zipcode][$day])) {
                                        $zipcodes[$zipcode][$day] = [ $sessionId => $sessionId ];
index f818dbe03991915155273c2d9a7a3249b08482f0..3ad656caf1a44b8c69f4f5f4d3b034c63b581a39 100644 (file)
@@ -163,29 +163,35 @@ class ApplicationController extends AbstractController {
                                //Check if morning
                                if ($slot == 'Morning') {
                                        //Set begin at 9h
-                                       $session->setBegin(new \DateTime('09:00:00'));
+                                       $session->setBegin(new \DateTime('10:00:00'));
 
                                        //Set length at 5h
                                        $session->setLength(new \DateTime('05:00:00'));
                                //Check if afternoon
                                } elseif ($slot == 'Afternoon') {
                                        //Set begin at 18h
-                                       $session->setBegin(new \DateTime('15:30:00'));
+                                       $session->setBegin(new \DateTime('15:00:00'));
 
                                        //Set length at 5h
-                                       $session->setLength(new \DateTime('05:30:00'));
+                                       $session->setLength(new \DateTime('04:00:00'));
+
+                                       //Check if next day is premium
+                                       if ($premium) {
+                                               //Set length at 11h
+                                               $session->setLength(new \DateTime('11:00:00'));
+                                       }
                                //Check if evening
                                } elseif ($slot == 'Evening') {
                                        //Set begin at 19h00
-                                       $session->setBegin(new \DateTime('19:30:00'));
+                                       $session->setBegin(new \DateTime('19:00:00'));
 
                                        //Set length at 5h
-                                       $session->setLength(new \DateTime('05:30:00'));
+                                       $session->setLength(new \DateTime('06:00:00'));
 
                                        //Check if next day is premium
                                        if ($premium) {
                                                //Set length at 7h
-                                               $session->setLength(new \DateTime('06:30:00'));
+                                               $session->setLength(new \DateTime('07:00:00'));
                                        }
                                //Check if after
                                } else {
index 34ad4929e28bb810db2c1363793ec9350fd590b4..96b090027ac10c09d49af13746a06c706b7585c6 100644 (file)
@@ -164,7 +164,39 @@ class DanceController extends AbstractController {
                return $this->render('@RapsysAir/dance/name.html.twig', $this->context, $response);
        }
 
-       public function view(Request $request, $id, $name, $type): Response {
+       /**
+        * List all sessions for the dance
+        *
+        * Display all sessions for the dance with an application or login form
+        *
+        * @TODO: add dance edit form ???
+        *
+        * @param Request $request The request instance
+        * @param int $id The dance id
+        * @param ?string $name The dance name
+        * @param ?string $type The dance type
+        *
+        * @return Response The rendered view
+        */
+       public function view(Request $request, int $id, string $name, string $type): Response {
+               //Without dance
+               if (empty($this->context['dance'] = $this->doctrine->getRepository(Dance::class)->findOneByIdAsArray($id))) {
+                       //Throw 404
+                       throw $this->createNotFoundException($this->translator->trans('Unable to find dance: %id%', ['%id%' => $id]));
+               }
+
+               //With invalid name slug
+               if ($name !== $this->context['dance']['slug']['name']) {
+                       //Redirect on correctly spelled location
+                       return $this->redirect($this->context['dance']['link'], Response::HTTP_MOVED_PERMANENTLY);
+               }
+
+               //With invalid type slug
+               if ($type !== $this->context['dance']['slug']['type']) {
+                       //Redirect on correctly spelled location
+                       return $this->redirect($this->context['dance']['link'], Response::HTTP_MOVED_PERMANENTLY);
+               }
+
                throw new \RuntimeException('TODO', 503);
                header('Content-Type: text/plain');
                var_dump('TODO');
@@ -172,5 +204,111 @@ class DanceController extends AbstractController {
                #var_dump($type);
                #var_dump($slug);
                exit;
+
+               //Fetch calendar
+               $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsCalendarArray($this->period, !$this->checker->isGranted('IS_AUTHENTICATED_REMEMBERED'), $this->context['location']['latitude'], $this->context['location']['longitude']);
+
+               //Set dances
+               $this->context['dances'] = [];
+
+               //Iterate on each calendar
+               foreach($this->context['calendar'] as $date => $calendar) {
+                       //Iterate on each session
+                       foreach($calendar['sessions'] as $sessionId => $session) {
+                               //Session with application dance
+                               if (!empty($session['application']['dance'])) {
+                                       //Add dance
+                                       $this->context['dances'][$session['application']['dance']['id']] = $session['application']['dance'];
+                               }
+                       }
+               }
+
+               //Get locations at less than 2 km
+               $this->context['locations'] = $this->doctrine->getRepository(Location::class)->findAllByLatitudeLongitudeAsArray($this->context['location']['latitude'], $this->context['location']['longitude'], $this->period, 2);
+
+               //Set modified
+               //XXX: dance modified is already computed inside calendar modified
+               $this->modified = max(array_merge([$this->context['location']['modified']], array_map(function ($v) { return $v['modified']; }, array_merge($this->context['calendar'], $this->context['locations']))));
+
+               //Create response
+               $response = new Response();
+
+               //With logged user
+               if ($this->checker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
+                       //Set last modified
+                       $response->setLastModified(new \DateTime('-1 year'));
+
+                       //Set as private
+                       $response->setPrivate();
+               //Without logged user
+               } else {
+                       //Set etag
+                       //XXX: only for public to force revalidation by last modified
+                       $response->setEtag(md5(serialize(array_merge($this->context['location'], $this->context['calendar'], $this->context['locations']))));
+
+                       //Set last modified
+                       $response->setLastModified($this->modified);
+
+                       //Set as public
+                       $response->setPublic();
+
+                       //Without role and modification
+                       if ($response->isNotModified($request)) {
+                               //Return 304 response
+                               return $response;
+                       }
+               }
+
+               //Add multi map
+               $this->context['multimap'] = $this->map->getMultiMap($this->context['location']['multimap'], $this->modified->getTimestamp(), $this->context['locations']);
+
+               //Set keywords
+               $this->context['keywords'] = [
+                       $this->context['location']['title'],
+                       $this->context['location']['city']['title'],
+                       $this->translator->trans($this->context['location']['indoor']?'Indoor':'Outdoor'),
+                       $this->translator->trans('Calendar'),
+                       $this->translator->trans('Libre Air')
+               ];
+
+               //With dances
+               if (!empty($this->context['dances'])) {
+                       //Set dances
+                       $dances = array_map(function ($v) { return $v['name']; }, $this->context['dances']);
+
+                       //Insert dances in keywords
+                       array_splice($this->context['keywords'], 2, 0, $dances);
+
+                       //Get textual dances
+                       $dances = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen'));
+
+                       //Set title
+                       $this->context['title']['page'] = $this->translator->trans('%dances% %location%', ['%dances%' => $dances, '%location%' => $this->context['location']['atin']]);
+
+                       //Set description
+                       $this->context['description'] = $this->translator->trans('%dances% indoor and outdoor calendar %location%', ['%dances%' => $dances, '%location%' => $this->context['location']['at']]);
+               //Without dances
+               } else {
+                       //Set title
+                       $this->context['title']['page'] = $this->translator->trans('Dance %location%', ['%location%' => $this->context['location']['atin']]);
+
+                       //Set description
+                       $this->context['description'] = $this->translator->trans('Indoor and outdoor dance calendar %location%', ['%location%' => $this->context['location']['at']]);
+               }
+
+               //Set locations description
+               $this->context['locations_description'] = $this->translator->trans('Libre Air location list %location% %city%', ['%location%' => $this->context['location']['around'], '%city%' => $this->context['location']['city']['in']]);
+
+               //Set locations link
+               $this->context['locations_link'] = $this->context['location']['city']['link'];
+
+               //Set locations title
+               $this->context['locations_title'] = $this->context['location']['city']['title'].' ('.$this->context['location']['city']['id'].')';
+
+               //Set alternates
+               $this->context['alternates'] += $this->context['location']['alternates'];
+
+               //Render the view
+               return $this->render('@RapsysAir/location/view.html.twig', $this->context, $response);
        }
 }
index 251a1baa21913829941c783703a620248b93ab5b..f7986d17c49f667fda12f4089f76085d760598cc 100644 (file)
@@ -14,6 +14,8 @@ namespace Rapsys\AirBundle\Repository;
 use Doctrine\ORM\AbstractQuery;
 use Doctrine\ORM\Query\ResultSetMapping;
 
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
 use Rapsys\AirBundle\Repository;
 
 /**
@@ -158,6 +160,112 @@ SQL;
                        ->getResult(AbstractQuery::HYDRATE_SCALAR_COLUMN);
        }
 
+       /**
+        * Find dance as array by id
+        *
+        * @param int $id The dance id
+        * @return array The dance data
+        */
+       public function findOneByIdAsArray(int $id): ?array {
+               //Set the request
+               $req = <<<SQL
+SELECT
+       d.id,
+       d.name,
+       d.type,
+       GREATEST(d.created, d.updated) AS modified
+FROM Rapsys\AirBundle\Entity\Dance AS d
+WHERE d.id = :id
+SQL;
+
+               //Replace bundle entity name by table name
+               $req = str_replace($this->tableKeys, $this->tableValues, $req);
+
+               //Get result set mapping instance
+               //XXX: DEBUG: see ../blog.orig/src/Rapsys/BlogBundle/Repository/ArticleRepository.php
+               $rsm = new ResultSetMapping();
+
+               //Declare all fields
+               //XXX: see vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Types.php
+               //addScalarResult($sqlColName, $resColName, $type = 'string');
+               $rsm->addScalarResult('id', 'id', 'integer')
+                       ->addScalarResult('name', 'name', 'string')
+                       ->addScalarResult('type', 'type', 'string')
+                       ->addScalarResult('modified', 'modified', 'datetime')
+                       ->addIndexByScalar('id');
+
+               //Get result
+               $result = $this->_em
+                       ->createNativeQuery($req, $rsm)
+                       ->setParameter('id', $id)
+                       ->getOneOrNullResult();
+
+               //Without result
+               if ($result === null) {
+                       //Return result
+                       return $result;
+               }
+
+               //Set alternates
+               $result['alternates'] = [];
+
+               //Set route
+               $route = 'rapsysair_dance_view';
+
+               //Set route params
+               $routeParams = ['id' => $id];
+
+               //Iterate on each languages
+               foreach($this->languages as $languageId => $language) {
+                       //Without current locale
+                       if ($languageId !== $this->locale) {
+                               //Set titles
+                               $titles = [];
+
+                               //Set route params locale
+                               $routeParams['_locale'] = $languageId;
+
+                               //Set route params name
+                               $routeParams['name'] = $this->slugger->slug($this->translator->trans($result['name'], [], null, $languageId));
+
+                               //Set route params type
+                               $routeParams['type'] = $this->slugger->slug($this->translator->trans($result['type'], [], null, $languageId));
+
+                               //Iterate on each locales
+                               foreach(array_keys($this->languages) as $other) {
+                                       //Without other locale
+                                       if ($other !== $languageId) {
+                                               //Set other locale title
+                                               $titles[$other] = $this->translator->trans($language, [], null, $other);
+                                       }
+                               }
+
+                               //Add alternates locale
+                               $result['alternates'][substr($languageId, 0, 2)] = $result['alternates'][str_replace('_', '-', $languageId)] = [
+                                       'absolute' => $this->router->generate($route, $routeParams, UrlGeneratorInterface::ABSOLUTE_URL),
+                                       'relative' => $this->router->generate($route, $routeParams),
+                                       'title' => implode('/', $titles),
+                                       'translated' => $this->translator->trans($language, [], null, $languageId)
+                               ];
+                       }
+               }
+
+               //Return result
+               return [
+                       'id' => $result['id'],
+                       'name' => $name = $this->translator->trans($result['name']),
+                       'type' => $type = $this->translator->trans($result['type']),
+                       'slug' => [
+                               'name' => $sname = $this->slugger->slug($name),
+                               'type' => $stype = $this->slugger->slug($type)
+                       ],
+                       'modified' => $result['modified'],
+                       //XXX: Useless ???
+                       'link' => $this->router->generate($route, ['_locale' => $this->locale, 'name' => $sname, 'type' => $stype]+$routeParams),
+                       'alternates' => $result['alternates']
+               ];
+       }
+
        /**
         * Find dance names as array
         *
index 9def36c27b67525ded7cb40d504170e1208dead7..151ec25263b853b816075b4f4e55bf29e0bcf8d8 100644 (file)
@@ -357,10 +357,6 @@ services:
         tags: [ 'console.command' ]
     # Register calendar command
     Rapsys\AirBundle\Command\CalendarCommand:
-        arguments: [ '@doctrine', '%kernel.default_locale%', '@router', '@rapsyspack.slugger_util', '@translator', 'airlibre', 0, '%kernel.project_dir%/var/cache' ]
-        tags: [ 'console.command' ]
-    # Register calendar2 command
-    Rapsys\AirBundle\Command\Calendar2Command:
         arguments: [ '@doctrine', '%kernel.default_locale%', '@router', '@rapsyspack.slugger_util', '@translator', '@user.cache', '@google.client', '@twig.markdown.default' ]
         tags: [ 'console.command' ]
     # Register rekey command