3 namespace Rapsys\AirBundle\Command
; 
   5 use Doctrine\Persistence\ManagerRegistry
; 
   6 use Symfony\Component\Cache\Adapter\FilesystemAdapter
; 
   7 use Symfony\Component\Console\Command\Command
; 
   8 use Symfony\Component\Console\Input\InputInterface
; 
   9 use Symfony\Component\Console\Output\OutputInterface
; 
  10 use Symfony\Component\DependencyInjection\ContainerInterface
; 
  11 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
; 
  12 use Symfony\Component\Routing\RouterInterface
; 
  13 use Symfony\Contracts\Translation\TranslatorInterface
; 
  14 use Twig\Extra\Markdown\DefaultMarkdown
; 
  16 use Rapsys\AirBundle\Entity\Session
; 
  18 use Rapsys\PackBundle\Util\SluggerUtil
; 
  20 class CalendarCommand 
extends Command 
{ 
  21         //Set failure constant 
  24         ///Set success constant 
  30          * @var ManagerRegistry 
  32         protected ManagerRegistry 
$doctrine; 
  35         protected RouterInterface 
$router; 
  38         protected SluggerUtil 
$slugger; 
  40         ///Translator instance 
  41         protected TranslatorInterface 
$translator; 
  56          * Creates new calendar command 
  58          * @param ManagerRegistry $doctrine The doctrine instance 
  59          * @param RouterInterface $router The router instance 
  60          * @param SluggerUtil $slugger The slugger instance 
  61          * @param TranslatorInterface $translator The translator instance 
  62          * @param string $namespace The cache namespace 
  63          * @param int $lifetime The cache lifetime 
  64          * @param string $path The cache path 
  65          * @param string $locale The default locale 
  67         public function __construct(ManagerRegistry 
$doctrine, RouterInterface 
$router, SluggerUtil 
$slugger, TranslatorInterface 
$translator, string $namespace, int $lifetime, string $path, string $locale) { 
  68                 //Call parent constructor 
  69                 parent
::__construct(); 
  72                 $this->doctrine 
= $doctrine; 
  75                 $this->lifetime 
= $lifetime; 
  78                 $this->namespace = $namespace; 
  84                 $this->router 
= $router; 
  87                 $this->slugger 
= $slugger; 
  90                 $context = $this->router
->getContext(); 
  93                 $context->setHost('airlibre.eu'); 
  96                 $context->setScheme('https'); 
  99                 $this->translator 
= $translator; 
 102         ///Configure attribute command 
 103         protected function configure() { 
 104                 //Configure the class 
 107                         ->setName('rapsysair:calendar') 
 108                         //Set description shown with bin/console list 
 109                         ->setDescription('Synchronize sessions in calendar') 
 110                         //Set description shown with bin/console --help airlibre:attribute 
 111                         ->setHelp('This command synchronize sessions in google calendar'); 
 114         ///Process the attribution 
 115         protected function execute(InputInterface 
$input, OutputInterface 
$output) { 
 117                 $period = new \
DatePeriod( 
 118                         //Start from last week 
 119                         new \
DateTime('-1 week'), 
 120                         //Iterate on each day 
 121                         new \
DateInterval('P1D'), 
 122                         //End with next 2 week 
 123                         new \
DateTime('+2 week') 
 126                 //Retrieve events to update 
 127                 $sessions = $this->doctrine
->getRepository(Session
::class)->fetchAllByDatePeriod($period, $this->locale
); 
 129                 //Markdown converted instance 
 130                 $markdown = new DefaultMarkdown
; 
 132                 //Retrieve cache object 
 133                 //XXX: by default stored in /tmp/symfony-cache/@/W/3/6SEhFfeIW4UMDlAII+Dg 
 134                 //XXX: stored in %kernel.project_dir%/var/cache/airlibre/0/P/IA20X0K4dkMd9-+Ohp9Q 
 135                 $cache = new FilesystemAdapter($this->namespace, $this->lifetime
, $this->path
); 
 138                 $cacheCalendars = $cache->getItem('calendars'); 
 141                 if (!$cacheCalendars->isHit()) { 
 143                         return self
::FAILURE
; 
 147                 $calendars = $cacheCalendars->get(); 
 149                 //XXX: calendars content 
 150                 #var_export($calendars); 
 152                 //Check expired token 
 153                 foreach($calendars as $clientId => $client) { 
 155                         $googleClient = new \Google\
Client(['application_name' => $client['project'], 'client_id' => $clientId, 'client_secret' => $client['secret'], 'redirect_uri' => $client['redirect']]); 
 157                         //Iterate on each tokens 
 158                         foreach($client['tokens'] as $tokenId => $token) { 
 160                                 $googleClient->setAccessToken( 
 162                                                 'access_token' => $tokenId, 
 163                                                 'refresh_token' => $token['refresh'], 
 164                                                 'expires_in' => $token['expire'], 
 165                                                 'scope' => $token['scope'], 
 166                                                 'token_type' => $token['type'], 
 167                                                 'created' => $token['created'] 
 172                                 if ($exp = $googleClient->isAccessTokenExpired()) { 
 174                                         if (($refreshToken = $googleClient->getRefreshToken()) && ($googleToken = $googleClient->fetchAccessTokenWithRefreshToken($refreshToken)) && empty($googleToken['error'])) { 
 175                                                 //Add refreshed token 
 176                                                 $calendars[$clientId]['tokens'][$googleToken['access_token']] = [ 
 177                                                         'calendar' => $token['calendar'], 
 178                                                         'prefix' => $token['prefix'], 
 179                                                         'refresh' => $googleToken['refresh_token'], 
 180                                                         'expire' => $googleToken['expires_in'], 
 181                                                         'scope' => $googleToken['scope'], 
 182                                                         'type' => $googleToken['token_type'], 
 183                                                         'created' => $googleToken['created'] 
 187                                                 unset($calendars[$clientId]['tokens'][$tokenId]); 
 190                                                 unset($calendars[$clientId]['tokens'][$tokenId]); 
 193                                                 if (empty($calendars[$clientId]['tokens'])) { 
 195                                                         unset($calendars[$clientId]); 
 199                                                 $cacheCalendars->set($calendars); 
 202                                                 $cache->save($cacheCalendars); 
 204                                                 //Drop token and report 
 205                                                 //XXX: submit app to avoid expiration 
 206                                                 //XXX: see https://console.cloud.google.com/apis/credentials/consent?project=calendar-317315 
 207                                                 echo 'Token '.$tokenId.' for calendar '.$token['calendar'].' has expired and is not refreshable'."\n"; 
 210                                                 //XXX: we want that mail and stop here 
 211                                                 return self
::FAILURE
; 
 218                 $cacheCalendars->set($calendars); 
 221                 $cache->save($cacheCalendars); 
 223                 //Iterate on each calendar client 
 224                 foreach($calendars as $clientId => $client) { 
 226                         $googleClient = new \Google\
Client(['application_name' => $client['project'], 'client_id' => $clientId, 'client_secret' => $client['secret'], 'redirect_uri' => $client['redirect']]); 
 228                         //Iterate on each tokens 
 229                         foreach($client['tokens'] as $tokenId => $token) { 
 231                                 $googleClient->setAccessToken( 
 233                                                 'access_token' => $tokenId, 
 234                                                 'refresh_token' => $token['refresh'], 
 235                                                 'expires_in' => $token['expire'], 
 236                                                 'scope' => $token['scope'], 
 237                                                 'token_type' => $token['type'], 
 238                                                 'created' => $token['created'] 
 243                                 if ($exp = $googleClient->isAccessTokenExpired()) { 
 244                                         //Last chance to skip this run 
 248                                 //Get google calendar 
 249                                 $googleCalendar = new \Google\Service\
Calendar($googleClient); 
 253                                         $calendar = $googleCalendar->calendars
->get($token['calendar']); 
 255                                 } catch(\Google\Service\Exception 
$e) { 
 257                                         //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors 
 258                                         echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n"; 
 259                                         echo $e->getTraceAsString()."\n"; 
 262                                         return self
::FAILURE
; 
 270                                         //XXX: show even deleted event to be able to update them 
 271                                         'showDeleted' => true, 
 272                                         //TODO: fetch events one day before and one day after to avoid triggering double insert duplicate key 409 errors :=) on google 
 273                                         'timeMin' => $period->getStartDate()->format(\DateTime
::ISO8601
), 
 274                                         'timeMax' => $period->getEndDate()->format(\DateTime
::ISO8601
) 
 275                                         /*, 'iCalUID' => 'airlibre/?????'*//*'orderBy' => 'startTime', */ 
 278                                 //Retrieve event collection 
 279                                 $googleEvents = $googleCalendar->events
->listEvents($token['calendar'], $filters); 
 281                                 //Iterate until reached end 
 283                                         //Iterate on each event 
 284                                         foreach ($googleEvents->getItems() as $event) { 
 286                                                 if (preg_match('/^'.$token['prefix'].'([0-9]+)$/', $id = $event->getId(), $matches)) { 
 287                                                         $events[$matches[1]] = $event; 
 288                                                 //XXX: 3rd party events with id not matching prefix are skipped 
 290                                                 #       echo 'Skipping '.$event->getId().':'.$event->getSummary()."\n";*/ 
 295                                         $pageToken = $googleEvents->getNextPageToken(); 
 299                                                 //Replace collection with next one 
 300                                                 $googleEvents = $service->events
->listEvents($token['calendar'], $filters+
['pageToken' => $pageToken]); 
 306                                 //Iterate on each session to sync 
 307                                 foreach($sessions as $sessionId => $session) { 
 308                                         //Init shared properties 
 309                                         //TODO: validate for constraints here ??? https://developers.google.com/calendar/api/guides/extended-properties 
 310                                         //TODO: drop shared as unused ??? 
 312                                                 'gps' => $session['l_latitude'].','.$session['l_longitude'] 
 317                                                 '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']), 
 318                                                 'url' => $this->router
->generate('rapsys_air_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
) 
 322                                         $description = 'Emplacement :'."\n".$this->translator
->trans($session['l_description']); 
 323                                         $shared['location'] = $markdown->convert(strip_tags($session['l_description'])); 
 326                                         $description .= "\n\n".'Description :'."\n".strip_tags(preg_replace('!<a href="([^"]+)"(?: title="[^"]+")?'.'>([^<]+)</a>!', '\1', $markdown->convert(strip_tags($session['p_description'])))); 
 327                                         $shared['description'] = $markdown->convert(strip_tags($session['p_description'])); 
 329                                         //Add class when available 
 330                                         if (!empty($session['p_class'])) { 
 331                                                 $shared['class'] = $session['p_class']; 
 332                                                 $description .= "\n\n".'Classe :'."\n".$session['p_class']; 
 335                                         //Add contact when available 
 336                                         if (!empty($session['p_contact'])) { 
 337                                                 $shared['contact'] = $session['p_contact']; 
 338                                                 $description .= "\n\n".'Contact :'."\n".$session['p_contact']; 
 341                                         //Add donate when available 
 342                                         if (!empty($session['p_donate'])) { 
 343                                                 $shared['donate'] = $session['p_donate']; 
 344                                                 $description .= "\n\n".'Contribuer :'."\n".$session['p_donate']; 
 347                                         //Add link when available 
 348                                         if (!empty($session['p_link'])) { 
 349                                                 $shared['link'] = $session['p_link']; 
 350                                                 $description .= "\n\n".'Site :'."\n".$session['p_link']; 
 353                                         //Add profile when available 
 354                                         if (!empty($session['p_profile'])) { 
 355                                                 $shared['profile'] = $session['p_profile']; 
 356                                                 $description .= "\n\n".'Réseau social :'."\n".$session['p_profile']; 
 360                                         if (!empty($session['locked']) && $events[$sessionId]) { 
 362                                                 if (!empty($event = $events[$sessionId])) { 
 365                                                                 $googleCalendar->events
->delete($token['calendar'], $event->getId()); 
 367                                                         } catch(\Google\Service\Exception 
$e) { 
 369                                                                 //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors 
 370                                                                 echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n"; 
 371                                                                 echo $e->getTraceAsString()."\n"; 
 374                                                                 return self
::FAILURE
; 
 378                                         } elseif (empty($events[$sessionId])) { 
 380                                                 $event = new \Google\Service\Calendar\
Event( 
 382                                                                 //TODO: replace 'airlibre' with $this->config['calendar']['prefix'] when possible with prefix validating [a-v0-9]{5,} 
 383                                                                 //XXX: see https://developers.google.com/calendar/api/v3/reference/events/insert#id 
 384                                                                 'id' => $token['prefix'].$sessionId, 
 385                                                                 #'summary' => $session['au_pseudonym'].' '.$this->translator->trans('at '.$session['l_title']), 
 386                                                                 'summary' => $source['title'], 
 387                                                                 #'description' => $markdown->convert(strip_tags($session['p_description'])), 
 388                                                                 'description' => $description, 
 389                                                                 'status' => empty($session['a_canceled'])?'confirmed':'cancelled', 
 390                                                                 'location' => implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']]), 
 392                                                                 'extendedProperties' => [ 
 396                                                                 //TODO: attendees[] ? 
 398                                                                         'dateTime' => $session['start']->format(\DateTime
::ISO8601
) 
 401                                                                         'dateTime' => $session['stop']->format(\DateTime
::ISO8601
) 
 408                                                         $googleCalendar->events
->insert($token['calendar'], $event); 
 410                                                 } catch(\Google\Service\Exception 
$e) { 
 412                                                         //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors 
 413                                                         echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n"; 
 414                                                         echo $e->getTraceAsString()."\n"; 
 417                                                         return self
::FAILURE
; 
 422                                                 $event = $events[$sessionId]; 
 425                                                 if ($session['updated'] >= (new \
DateTime($event->getUpdated()))) { 
 427                                                         #$event->setSummary($session['au_pseudonym'].' '.$this->translator->trans('at '.$session['l_title'])); 
 428                                                         $event->setSummary($source['title']); 
 431                                                         $event->setDescription($description); 
 434                                                         $event->setStatus(empty($session['a_canceled'])?'confirmed':'cancelled'); 
 437                                                         $event->setLocation(implode(' ', [$session['l_address'], $session['l_zipcode'], $session['l_city']])); 
 440                                                         $eventSource = $event->getSource(); 
 442                                                         //Update source title 
 443                                                         $eventSource->setTitle($source['title']); 
 446                                                         $eventSource->setUrl($source['url']); 
 449                                                         #$event->setSource($source); 
 451                                                         //Get extended properties 
 452                                                         $extendedProperties = $event->getExtendedProperties(); 
 455                                                         $extendedProperties->setShared($shared); 
 458                                                         //TODO: attendees[] ? 
 461                                                         $start = $event->getStart(); 
 463                                                         //Update start datetime 
 464                                                         $start->setDateTime($session['start']->format(\DateTime
::ISO8601
)); 
 467                                                         $end = $event->getEnd(); 
 469                                                         //Update stop datetime 
 470                                                         $end->setDateTime($session['stop']->format(\DateTime
::ISO8601
)); 
 474                                                                 $updatedEvent = $googleCalendar->events
->update($token['calendar'], $event->getId(), $event); 
 476                                                         } catch(\Google\Service\Exception 
$e) { 
 478                                                                 //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors 
 479                                                                 echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n"; 
 480                                                                 echo $e->getTraceAsString()."\n"; 
 483                                                                 return self
::FAILURE
; 
 487                                                 //Drop from events array 
 488                                                 unset($events[$sessionId]); 
 492                                 //Remaining events to drop 
 493                                 foreach($events as $eventId => $event) { 
 494                                         //Non canceled events 
 495                                         if ($event->getStatus() == 'confirmed') { 
 498                                                         $googleCalendar->events
->delete($token['calendar'], $event->getId()); 
 500                                                 } catch(\Google\Service\Exception 
$e) { 
 502                                                         //TODO: handle codes here https://developers.google.com/calendar/api/guides/errors 
 503                                                         echo 'Exception '.$e->getCode().':'.$e->getMessage().' in '.$e->getFile().' +'.$e->getLine()."\n"; 
 504                                                         echo $e->getTraceAsString()."\n"; 
 507                                                         return self
::FAILURE
; 
 515                 return self
::SUCCESS
; 
 519          * Return the bundle alias 
 523         public function getAlias(): string {