]> Raphaël G. Git Repositories - airbundle/commitdiff
Rename rapsysair:calendar2 command to rapsysair:calendar master 0.5.0
authorRaphaël Gertz <git@rapsys.eu>
Tue, 9 Apr 2024 13:44:41 +0000 (15:44 +0200)
committerRaphaël Gertz <git@rapsys.eu>
Tue, 9 Apr 2024 13:44:41 +0000 (15:44 +0200)
14 files changed:
Command/Calendar2Command.php [deleted file]
Command/CalendarCommand.php
Controller/DefaultController.php
Form/ContactType.php
RapsysAirBundle.php
Repository/GoogleTokenRepository.php
Repository/LocationRepository.php
Repository/SessionRepository.php
Repository/UserRepository.php
Resources/config/doctrine/User.orm.yml
Resources/config/packages/rapsysair.yaml
Resources/translations/messages.en_gb.yaml
Resources/translations/messages.fr_fr.yaml
Resources/views/form/register.html.twig

diff --git a/Command/Calendar2Command.php b/Command/Calendar2Command.php
deleted file mode 100644 (file)
index 0ce4da8..0000000
+++ /dev/null
@@ -1,110 +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\Oauth2;
-
-use Symfony\Component\Cache\Adapter\FilesystemAdapter;
-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\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';
-
-       /**
-        * {@inheritdoc}
-        */
-       public function __construct(protected ManagerRegistry $doctrine, protected string $locale, protected RouterInterface $router, protected SluggerUtil $slugger, protected TranslatorInterface $translator, 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 {
-               //Set 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')
-               );
-
-               //Iterate on google tokens
-               foreach($tokens = $this->doctrine->getRepository(GoogleToken::class)->findAllIndexed() as $tid => $token) {
-                       //Iterate on google calendars
-                       foreach($calendars = $token['calendars'] as $cid => $calendar) {
-                               #$calendar['synchronized']
-                               var_dump($token);
-
-                               //TODO: see if we may be smarter here ?
-
-                               //TODO: load all calendar events here ?
-
-                               //Iterate on sessions to update
-                               foreach($sessions = $this->doctrine->getRepository(Session::class)->findAllByUserIdSynchronized($token['uid'], $calendar['synchronized']) as $session) {
-                                       //TODO: insert/update/delete events here ?
-                               }
-
-                               //TODO: delete remaining events here ?
-                       }
-               }
-
-               //TODO: get user filter ? (users_subscriptions+users_dances)
-
-               //TODO: XXX: or fetch directly the events updated since synchronized + matching rubscriptions and/or dances
-
-               exit;
-
-               //Return success
-               return self::SUCCESS;
-       }
-}
index dca096bf368d6b286ef01e6c2117ee3855f099cb..f722aaed4cfa842eeb2bf74cedd6ee93a68ea99f 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
+                               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
-                               $googleCalendar = new \Google\Service\Calendar($googleClient);
+                       //Get google calendar service
+                       $this->service = new Calendar($this->google);
 
-                               //Retrieve calendar
+                       //Iterate on google calendars
+                       foreach($calendars = $token['calendars'] as $cid => $calendar) {
+                               //Set start
+                               $synchronized = null;
+
+                               //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()?:'');
 
-                                       //Return failure
-                                       return self::FAILURE;
+                                               //TODO: warn user by mail ?
+
+                                               //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();
+                               //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 647baf844bb85af78275f564262555467072e5dd..14590963806e098561cc9da8ab44c9f23fb37847 100644 (file)
@@ -105,6 +105,7 @@ class DefaultController extends AbstractController {
                //And give the proper parameters
                $form = $this->factory->create('Rapsys\AirBundle\Form\ContactType', $data, [
                        'action' => $this->generateUrl('rapsysair_contact'),
+                       'captcha' => true,
                        'method' => 'POST'
                ]);
 
index 93951e968a8c42c724a90a974b2b683a70363a23..4142c996d4c0bc3dcdcf8a3d13b8c7ffba8f8a7a 100644 (file)
@@ -46,6 +46,9 @@ class ContactType extends CaptchaType {
         * {@inheritdoc}
         */
        public function configureOptions(OptionsResolver $resolver): void {
+               //Call parent configure options
+               parent::configureOptions($resolver);
+
                //Set defaults
                $resolver->setDefaults(['error_bubbling' => true]);
        }
index 6096959ffc6f6a6db95097f9a0df859f8ab0cf66..e1bb77b65664c6049e6a47d79cf7239e581cd0bd 100644 (file)
@@ -62,6 +62,6 @@ class RapsysAirBundle extends Bundle {
         */
        public static function getVersion(): string {
                //Return version
-               return '0.4.0';
+               return '0.5.0';
        }
 }
index fa2a8545d404feec28e892096218ca95d38b89ef..29b96d28ca8381647f7b16ad04a8f70513f1054e 100644 (file)
@@ -29,20 +29,54 @@ class GoogleTokenRepository extends Repository {
                //Set the request
                $req = <<<SQL
 SELECT
-       t.id AS tid,
-       t.mail AS gmail,
-       t.user_id AS uid,
-       t.access,
-       t.refresh,
-       t.expired,
-       GROUP_CONCAT(c.id ORDER BY c.id SEPARATOR "\\n") AS cids,
-       GROUP_CONCAT(c.mail ORDER BY c.id SEPARATOR "\\n") AS cmails,
-       GROUP_CONCAT(c.summary ORDER BY c.id SEPARATOR "\\n") AS csummaries,
-       GROUP_CONCAT(IFNULL(c.synchronized, 'NULL') ORDER BY c.id SEPARATOR "\\n") AS csynchronizeds
-FROM Rapsys\AirBundle\Entity\GoogleToken AS t
-JOIN Rapsys\AirBundle\Entity\GoogleCalendar AS c ON (c.google_token_id = t.id)
-GROUP BY t.id
-ORDER BY NULL
+       b.tid,
+       b.gmail,
+       b.uid,
+       b.access,
+       b.refresh,
+       b.created,
+       b.expired,
+       b.cids,
+       b.cmails,
+       b.csummaries,
+       b.csynchronizeds,
+       b.dids,
+       GROUP_CONCAT(us.subscribed_id ORDER BY us.subscribed_id SEPARATOR "\\n") AS sids
+FROM (
+       SELECT
+               a.tid,
+               a.gmail,
+               a.uid,
+               a.access,
+               a.refresh,
+               a.created,
+               a.expired,
+               a.cids,
+               a.cmails,
+               a.csummaries,
+               a.csynchronizeds,
+               GROUP_CONCAT(ud.dance_id ORDER BY ud.dance_id SEPARATOR "\\n") AS dids
+       FROM (
+               SELECT
+                       t.id AS tid,
+                       t.mail AS gmail,
+                       t.user_id AS uid,
+                       t.access,
+                       t.refresh,
+                       t.created,
+                       t.expired,
+                       GROUP_CONCAT(c.id ORDER BY c.id SEPARATOR "\\n") AS cids,
+                       GROUP_CONCAT(c.mail ORDER BY c.id SEPARATOR "\\n") AS cmails,
+                       GROUP_CONCAT(c.summary ORDER BY c.id SEPARATOR "\\n") AS csummaries,
+                       GROUP_CONCAT(IFNULL(c.synchronized, 'NULL') ORDER BY c.id SEPARATOR "\\n") AS csynchronizeds
+               FROM Rapsys\AirBundle\Entity\GoogleToken AS t
+               JOIN Rapsys\AirBundle\Entity\GoogleCalendar AS c ON (c.google_token_id = t.id)
+               GROUP BY t.id
+               ORDER BY NULL
+       ) AS a
+       LEFT JOIN Rapsys\AirBundle\Entity\UserDance AS ud ON (ud.user_id = a.uid)
+) AS b
+LEFT JOIN Rapsys\AirBundle\Entity\UserSubscription AS us ON (us.user_id = b.uid)
 SQL;
 
                //Replace bundle entity name by table name
@@ -61,11 +95,14 @@ SQL;
                        ->addScalarResult('uid', 'uid', 'integer')
                        ->addScalarResult('access', 'access', 'string')
                        ->addScalarResult('refresh', 'refresh', 'string')
+                       ->addScalarResult('created', 'created', 'datetime')
                        ->addScalarResult('expired', 'expired', 'datetime')
                        ->addScalarResult('cids', 'cids', 'string')
                        ->addScalarResult('cmails', 'cmails', 'string')
                        ->addScalarResult('csummaries', 'csummaries', 'string')
                        ->addScalarResult('csynchronizeds', 'csynchronizeds', 'string')
+                       ->addScalarResult('dids', 'dids', 'string')
+                       ->addScalarResult('sids', 'sids', 'string')
                        ->addIndexByScalar('tid');
 
                //Set result array
@@ -97,11 +134,14 @@ SQL;
                                'uid' => $token['uid'],
                                'access' => $token['access'],
                                'refresh' => $token['refresh'],
+                               'created' => $token['created'],
                                'expired' => $token['expired'],
-                               'calendars' => []
+                               'calendars' => [],
+                               'dances' => [],
+                               'subscriptions' => []
                        ];
 
-                       //Iterate on 
+                       //Iterate on calendars
                        foreach($cids as $k => $cid) {
                                $result[$tid]['calendars'][$cid] = [
                                        'id' => $cid,
@@ -110,6 +150,26 @@ SQL;
                                        'synchronized' => $csynchronizeds[$k]
                                ];
                        }
+
+                       //Set dids
+                       $dids = explode("\n", $token['dids']);
+
+                       //Iterate on dances
+                       foreach($dids as $k => $did) {
+                               $result[$tid]['dances'][$did] = [
+                                       'id' => $did
+                               ];
+                       }
+
+                       //Set sids
+                       $sids = explode("\n", $token['sids']);
+
+                       //Iterate on subscriptions
+                       foreach($sids as $k => $sid) {
+                               $result[$tid]['subscriptions'][$sid] = [
+                                       'id' => $sid
+                               ];
+                       }
                }
 
                //Return result
index 963f2c81e1492b60a1c09cd21e40e20b3557e8df..9b621ef3cafe2b073c511ee42867c2bd665bf8e4 100644 (file)
@@ -134,7 +134,7 @@ FROM (
                l.latitude,
                l.longitude,
                l.indoor,
-               GREATEST(l.created, l.updated, COALESCE(s.created, 0), COALESCE(s.updated, 0)) AS modified,
+               GREATEST(l.created, l.updated, COALESCE(s.created, '1970-01-01'), COALESCE(s.updated, '1970-01-01')) AS modified,
                l.zipcode,
                COUNT(s.id) AS count,
                COUNT(IF(s.date BETWEEN :begin AND :end, s.id, NULL)) AS pcount
@@ -362,7 +362,7 @@ SELECT
        a.longitude,
        a.created,
        a.updated,
-       MAX(GREATEST(a.modified, COALESCE(s.created, 0), COALESCE(s.updated, 0))) AS modified,
+       MAX(GREATEST(a.modified, COALESCE(s.created, '1970-01-01'), COALESCE(s.updated, '1970-01-01'))) AS modified,
        COUNT(s.id) AS count
 FROM (
        SELECT
@@ -461,7 +461,7 @@ SELECT
        a.longitude,
        a.created,
        a.updated,
-       MAX(GREATEST(a.modified, COALESCE(s3.created, 0), COALESCE(s3.updated, 0))) AS modified,
+       MAX(GREATEST(a.modified, COALESCE(s3.created, '1970-01-01'), COALESCE(s3.updated, '1970-01-01'))) AS modified,
        a.pcount,
        COUNT(s3.id) AS tcount
 FROM (
@@ -473,7 +473,7 @@ FROM (
                b.longitude,
                b.created,
                b.updated,
-               MAX(GREATEST(b.modified, COALESCE(s2.created, 0), COALESCE(s2.updated, 0))) AS modified,
+               MAX(GREATEST(b.modified, COALESCE(s2.created, '1970-01-01'), COALESCE(s2.updated, '1970-01-01'))) AS modified,
                COUNT(s2.id) AS pcount
        FROM (
                SELECT
@@ -716,18 +716,15 @@ SQL;
         */
        public function findComplementBySessionId(int $id): array {
                //Fetch complement locations
-               $ret = $this->getEntityManager()
-                         ->createQuery('SELECT l.id, l.title FROM Rapsys\AirBundle\Entity\Session s LEFT JOIN Rapsys\AirBundle\Entity\Session s2 WITH s2.id != s.id AND s2.slot = s.slot AND s2.date = s.date LEFT JOIN Rapsys\AirBundle\Entity\Location l WITH l.id != s.location AND (l.id != s2.location OR s2.location IS NULL) WHERE s.id = :sid GROUP BY l.id ORDER BY l.id')
-                       ->setParameter('sid', $id)
-                       ->getArrayResult();
-
-               //TODO: try to improve with:
-               #->addIndexByScalar('city');
-
-               //Rekey array
-               $ret = array_column($ret, 'id', 'title');
-
-               return $ret;
+               return array_column(
+                       $this->getEntityManager()
+                               #->createQuery('SELECT l.id, l.title FROM Rapsys\AirBundle\Entity\Location l JOIN Rapsys\AirBundle\Entity\Session s WITH s.id = :sid LEFT JOIN Rapsys\AirBundle\Entity\Session s2 WITH s2.id != s.id AND s2.slot = s.slot AND s2.date = s.date WHERE l.id != s.location AND s2.location IS NULL GROUP BY l.id ORDER BY l.id')
+                               ->createQuery('SELECT l.id, l.title FROM Rapsys\AirBundle\Entity\Session s LEFT JOIN Rapsys\AirBundle\Entity\Session s2 WITH s2.id != s.id AND s2.slot = s.slot AND s2.date = s.date LEFT JOIN Rapsys\AirBundle\Entity\Location l WITH l.id != s.location AND (l.id != s2.location OR s2.location IS NULL) WHERE s.id = :sid GROUP BY l.id ORDER BY l.id')
+                               ->setParameter('sid', $id)
+                               ->getArrayResult(),
+                       'id',
+                       'title'
+               );
        }
 
        /**
index d44db5570bbdeed24ab3d14fbc300e59ab333ce8..c310b5406458295f720cb0755a2bd66c4e865477 100644 (file)
@@ -106,7 +106,7 @@ SELECT
        p.profile AS p_profile,
        p.rate AS p_rate,
        p.hat AS p_hat,
-       GREATEST(COALESCE(s.updated, 0), COALESCE(l.updated, 0), COALESCE(t.updated, 0), COALESCE(p.updated, 0), COALESCE(MAX(sa.updated), 0), COALESCE(MAX(sau.updated), 0), COALESCE(MAX(sad.updated), 0)) AS modified,
+       GREATEST(s.created, s.updated, l.created, l.updated, t.created, t.updated, COALESCE(a.created, '1970-01-01'), COALESCE(a.updated, '1970-01-01'), COALESCE(ad.created, '1970-01-01'), COALESCE(ad.updated, '1970-01-01'), COALESCE(au.created, '1970-01-01'), COALESCE(au.updated, '1970-01-01'), COALESCE(p.created, '1970-01-01'), COALESCE(p.updated, '1970-01-01'), MAX(GREATEST(COALESCE(sa.created, '1970-01-01'), COALESCE(sa.updated, '1970-01-01'), COALESCE(sad.created, '1970-01-01'), COALESCE(sad.updated, '1970-01-01'), COALESCE(sau.created, '1970-01-01'), COALESCE(sau.updated, '1970-01-01')))) AS modified,
        GROUP_CONCAT(sa.id ORDER BY sa.user_id SEPARATOR "\\n") AS sa_id,
        GROUP_CONCAT(IFNULL(sa.score, 'NULL') ORDER BY sa.user_id SEPARATOR "\\n") AS sa_score,
        GROUP_CONCAT(sa.created ORDER BY sa.user_id SEPARATOR "\\n") AS sa_created,
@@ -122,8 +122,8 @@ LEFT JOIN Rapsys\AirBundle\Entity\Dance AS ad ON (ad.id = a.dance_id)
 LEFT JOIN Rapsys\AirBundle\Entity\User AS au ON (au.id = a.user_id)
 LEFT JOIN Rapsys\AirBundle\Entity\Snippet AS p ON (p.locale = :locale AND p.location_id = s.location_id AND p.user_id = a.user_id)
 LEFT JOIN Rapsys\AirBundle\Entity\Application AS sa ON (sa.session_id = s.id)
-LEFT JOIN Rapsys\AirBundle\Entity\User AS sau ON (sau.id = sa.user_id)
 LEFT JOIN Rapsys\AirBundle\Entity\Dance AS sad ON (sad.id = sa.dance_id)
+LEFT JOIN Rapsys\AirBundle\Entity\User AS sau ON (sau.id = sa.user_id)
 WHERE s.id = :id
 GROUP BY s.id
 ORDER BY NULL
@@ -553,12 +553,12 @@ SELECT
        p.hat AS p_hat,
        p.rate AS p_rate,
        p.short AS p_short,
-       GROUP_CONCAT(sa.user_id ORDER BY sa.user_id SEPARATOR "\\n") AS sau_id,
-       GROUP_CONCAT(sau.pseudonym ORDER BY sa.user_id SEPARATOR "\\n") AS sau_pseudonym,
+       GREATEST(s.created, s.updated, l.created, l.updated, t.created, t.updated, COALESCE(a.created, '1970-01-01'), COALESCE(a.updated, '1970-01-01'), COALESCE(ad.created, '1970-01-01'), COALESCE(ad.updated, '1970-01-01'), COALESCE(au.created, '1970-01-01'), COALESCE(au.updated, '1970-01-01'), COALESCE(p.created, '1970-01-01'), COALESCE(p.updated, '1970-01-01'), MAX(GREATEST(COALESCE(sa.created, '1970-01-01'), COALESCE(sa.updated, '1970-01-01'), COALESCE(sad.created, '1970-01-01'), COALESCE(sad.updated, '1970-01-01'), COALESCE(sau.created, '1970-01-01'), COALESCE(sau.updated, '1970-01-01')))) AS modified,
        GROUP_CONCAT(sa.dance_id ORDER BY sa.user_id SEPARATOR "\\n") AS sad_id,
        GROUP_CONCAT(sad.name ORDER BY sa.user_id SEPARATOR "\\n") AS sad_name,
        GROUP_CONCAT(sad.type ORDER BY sa.user_id SEPARATOR "\\n") AS sad_type,
-       GREATEST(COALESCE(s.updated, 0), COALESCE(l.updated, 0), COALESCE(p.updated, 0), COALESCE(MAX(sa.updated), 0), COALESCE(MAX(sau.updated), 0), COALESCE(MAX(sad.updated), 0)) AS modified
+       GROUP_CONCAT(sa.user_id ORDER BY sa.user_id SEPARATOR "\\n") AS sau_id,
+       GROUP_CONCAT(sau.pseudonym ORDER BY sa.user_id SEPARATOR "\\n") AS sau_pseudonym
 FROM Rapsys\AirBundle\Entity\Session AS s
 JOIN Rapsys\AirBundle\Entity\Location AS l ON (l.id = s.location_id)
 JOIN Rapsys\AirBundle\Entity\Slot AS t ON (t.id = s.slot_id)
@@ -615,15 +615,15 @@ SQL;
                        ->addScalarResult('p_rate', 'p_rate', 'integer')
                        ->addScalarResult('p_short', 'p_short', 'string')
                        //XXX: is a string because of \n separator
-                       ->addScalarResult('sau_id', 'sau_id', 'string')
-                       //XXX: is a string because of \n separator
-                       ->addScalarResult('sau_pseudonym', 'sau_pseudonym', 'string')
-                       //XXX: is a string because of \n separator
                        ->addScalarResult('sad_id', 'sad_id', 'string')
                        //XXX: is a string because of \n separator
                        ->addScalarResult('sad_name', 'sad_name', 'string')
                        //XXX: is a string because of \n separator
                        ->addScalarResult('sad_type', 'sad_type', 'string')
+                       //XXX: is a string because of \n separator
+                       ->addScalarResult('sau_id', 'sau_id', 'string')
+                       //XXX: is a string because of \n separator
+                       ->addScalarResult('sau_pseudonym', 'sau_pseudonym', 'string')
                        ->addIndexByScalar('id');
 
                //Fetch result
@@ -943,7 +943,7 @@ SQL;
         * @param DateTime $synchronized The synchronized datetime
         * @return array The session data
         */
-       public function findAllByUserIdSynchronized(int $userId, \DateTime $synchronized): array {
+       public function findAllByUserIdSynchronized(int $userId, \DateTime $synchronized = new \DateTime('1970-01-01')): array {
                //Set the request
                $req = <<<SQL
 SELECT ud.dance_id
@@ -976,9 +976,9 @@ SQL;
 
                //Set the request
                $req = <<<SQL
-SELECT us.user_id
+SELECT us.subscribed_id
 FROM Rapsys\AirBundle\Entity\UserSubscription AS us
-WHERE us.subscriber_id = :uid
+WHERE us.user_id = :uid
 SQL;
 
                //Replace bundle entity name by table name
@@ -988,7 +988,7 @@ SQL;
                $rsm = new ResultSetMapping();
 
                //Declare user id field
-               $rsm->addScalarResult('user_id', 'user_id', 'integer');
+               $rsm->addScalarResult('subscribed_id', 'subscribed_id', 'integer');
 
                //Set subscription sql part
                $subscriptionSql = '';
@@ -1010,7 +1010,7 @@ SELECT
        s.id,
        s.date,
        s.locked,
-       s.updated,
+       GREATEST(s.created, s.updated, l.created, l.updated, a.created, a.updated, ad.created, ad.updated, au.created, au.updated, COALESCE(p.created, '1970-01-01'), COALESCE(p.updated, '1970-01-01')) AS modified,
        ADDDATE(ADDTIME(s.date, s.begin), INTERVAL IF(s.slot_id = :afterid, 1, 0) DAY) AS start,
        ADDDATE(ADDTIME(ADDTIME(s.date, s.begin), s.length), INTERVAL IF(s.slot_id = :afterid, 1, 0) DAY) AS stop,
        s.location_id AS l_id,
@@ -1044,7 +1044,7 @@ JOIN Rapsys\AirBundle\Entity\Application AS a ON (a.id = s.application_id{$dance
 JOIN Rapsys\AirBundle\Entity\Dance AS ad ON (ad.id = a.dance_id)
 JOIN Rapsys\AirBundle\Entity\User AS au ON (au.id = a.user_id)
 LEFT JOIN Rapsys\AirBundle\Entity\Snippet AS p ON (p.locale = :locale AND p.location_id = s.location_id AND p.user_id = a.user_id)
-WHERE GREATEST(s.created, s.updated, a.created, a.updated, ADDTIME(ADDTIME(s.date, s.begin), s.length)) >= :synchronized
+WHERE GREATEST(GREATEST(s.created, s.updated, l.created, l.updated, a.created, a.updated, ad.created, ad.updated, au.created, au.updated, COALESCE(p.created, '1970-01-01'), COALESCE(p.updated, '1970-01-01')), ADDDATE(ADDTIME(ADDTIME(s.date, s.begin), s.length), INTERVAL IF(s.slot_id = :afterid, 1, 0) DAY)) >= :synchronized
 SQL;
 
                //Replace bundle entity name by table name
@@ -1060,7 +1060,7 @@ SQL;
                        ->addScalarResult('id', 'id', 'integer')
                        ->addScalarResult('date', 'date', 'date')
                        ->addScalarResult('locked', 'locked', 'datetime')
-                       ->addScalarResult('updated', 'updated', 'datetime')
+                       ->addScalarResult('modified', 'modified', 'datetime')
                        ->addScalarResult('start', 'start', 'datetime')
                        ->addScalarResult('stop', 'stop', 'datetime')
                        ->addScalarResult('l_id', 'l_id', 'integer')
@@ -1099,7 +1099,7 @@ SQL;
                        ->setParameter('dids', $userDances)
                        ->setParameter('uids', $userSubscriptions)
                        ->setParameter('synchronized', $synchronized)
-                       ->getArrayResult();
+                       ->getResult(AbstractQuery::HYDRATE_ARRAY);
        }
 
        /**
index 4e7dae86cd8c3043418ac2972a89bf4980639c20..725e3ed3f482e3a08001d9f523549bb7ce4cfacc 100644 (file)
@@ -218,9 +218,9 @@ SELECT
        c.title AS c_title,
        u.country_id AS o_id,
        o.title AS o_title,
+       GREATEST(u.created, u.updated, COALESCE(c.created, '1970-01-01'), COALESCE(c.updated, '1970-01-01'), COALESCE(o.created, '1970-01-01'), COALESCE(o.updated, '1970-01-01'), COALESCE(g.created, '1970-01-01'), COALESCE(g.updated, '1970-01-01')) AS modified,
        GROUP_CONCAT(g.id ORDER BY g.id SEPARATOR "\\n") AS ids,
-       GROUP_CONCAT(g.title ORDER BY g.id SEPARATOR "\\n") AS titles,
-       GREATEST(COALESCE(u.updated, 0), COALESCE(c.updated, 0), COALESCE(o.updated, 0)) AS modified
+       GROUP_CONCAT(g.title ORDER BY g.id SEPARATOR "\\n") AS titles
 FROM Rapsys\AirBundle\Entity\User AS u
 LEFT JOIN Rapsys\AirBundle\Entity\Civility AS c ON (c.id = u.civility_id)
 LEFT JOIN Rapsys\AirBundle\Entity\Country AS o ON (o.id = u.country_id)
@@ -252,11 +252,11 @@ SQL;
                        ->addScalarResult('c_title', 'c_title', 'string')
                        ->addScalarResult('o_id', 'o_id', 'integer')
                        ->addScalarResult('o_title', 'o_title', 'string')
+                       ->addScalarResult('modified', 'modified', 'datetime')
                        //XXX: is a string because of \n separator
                        ->addScalarResult('ids', 'ids', 'string')
                        //XXX: is a string because of \n separator
                        ->addScalarResult('titles', 'titles', 'string')
-                       ->addScalarResult('modified', 'modified', 'datetime')
                        ->addIndexByScalar('id');
 
                //Get result
index 600265143341e2a5fcc2e992288a715081cc4781..fcc5712ab64019680e735b7b42974b1d4a6528ae 100644 (file)
@@ -54,7 +54,7 @@ Rapsys\AirBundle\Entity\User:
                         name: user_id
                 inverseJoinColumns:
                     id:
-                        name: subscriber_id
+                        name: subscribed_id
         locations:
             targetEntity: Rapsys\AirBundle\Entity\Location
             inversedBy: users
index 02197a85ce237bb02a75a35576588eabe7736b31..151ec25263b853b816075b4f4e55bf29e0bcf8d8 100644 (file)
@@ -357,11 +357,7 @@ 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', '@google.client', '@twig.markdown.default' ]
+        arguments: [ '@doctrine', '%kernel.default_locale%', '@router', '@rapsyspack.slugger_util', '@translator', '@user.cache', '@google.client', '@twig.markdown.default' ]
         tags: [ 'console.command' ]
     # Register rekey command
     Rapsys\AirBundle\Command\RekeyCommand:
index 5bb0f5d1651fcc95dc24d89a062ccd3c77a4b326..e6c2f81533bb55a0248709f8013a719bc5de0566 100644 (file)
 'Class': 'Class'
 'Colette': 'Colette'
 'Colette place': 'Colette place'
+'Colette place access map': 'Colette place access map'
 'Colette place miniature': 'Colette place miniature'
 'Colette place sector map': 'Colette place sector map'
 'Completely offline (neither deezer nor spotify)': 'Completely offline (neither deezer nor spotify)'
index 628909a6bb724fd2987e6dad28fb3612b11f1875..a82368f09897d2ad5e309084c8388cdfe956232d 100644 (file)
 'Civility': 'Civilité'
 'Class': 'Cours'
 'Colette': 'Colette'
+'Colette place access map': 'Carte d''accès de la place Colette'
 'Colette place miniature': 'Miniature de la place Colette'
 'Colette place': 'Place Colette'
 'Colette place sector map': 'Carte du secteur de la place Colette'
index 74e3aa232a685cdeb56bb611a8e2f351f6dcfaf9..cd71a78dc88a7b416ec0b6f85ecc280a139ea517 100644 (file)
                                        {{ form_row(register.phone) }}
                                {% endif %}
 
+                               {% if register.captcha is defined %}
+                                       {{ form_row(register.captcha) }}
+                               {% endif %}
+
                                {{ form_row(register.submit) }}
 
                                {# Render CSRF token etc .#}