1 <?php 
declare(strict_types
=1); 
   4  * This file is part of the Rapsys AirBundle package. 
   6  * (c) Raphaël Gertz <symfony@rapsys.eu> 
   8  * For the full copyright and license information, please view the LICENSE 
   9  * file that was distributed with this source code. 
  12 namespace Rapsys\AirBundle\Controller
; 
  14 use Doctrine\ORM\EntityManagerInterface
; 
  15 use Doctrine\Persistence\ManagerRegistry
; 
  19 use Psr\Container\ContainerInterface
; 
  20 use Psr\Log\LoggerInterface
; 
  22 use Symfony\Bundle\SecurityBundle\Security
; 
  23 use Symfony\Component\Form\FormFactoryInterface
; 
  24 use Symfony\Component\HttpFoundation\Request
; 
  25 use Symfony\Component\HttpFoundation\RequestStack
; 
  26 use Symfony\Component\HttpFoundation\Response
; 
  27 use Symfony\Component\Mailer\MailerInterface
; 
  28 use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface
; 
  29 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
; 
  30 use Symfony\Component\Routing\RouterInterface
; 
  31 use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface
; 
  32 use Symfony\Contracts\Cache\CacheInterface
; 
  33 use Symfony\Contracts\Cache\ItemInterface
; 
  34 use Symfony\Contracts\Translation\TranslatorInterface
; 
  36 use Rapsys\UserBundle\Controller\UserController 
as BaseUserController
; 
  38 use Rapsys\AirBundle\Entity\Dance
; 
  39 use Rapsys\AirBundle\Entity\GoogleCalendar
; 
  40 use Rapsys\AirBundle\Entity\GoogleToken
; 
  41 use Rapsys\AirBundle\Entity\User
; 
  43 use Rapsys\PackBundle\Util\SluggerUtil
; 
  50 class UserController 
extends BaseUserController 
{ 
  54          * @param CacheInterface $cache The cache instance 
  55          * @param AuthorizationCheckerInterface $checker The checker instance 
  56          * @param ContainerInterface $container The container instance 
  57          * @param ManagerRegistry $doctrine The doctrine instance 
  58          * @param FormFactoryInterface $factory The factory instance 
  59          * @param UserPasswordHasherInterface $hasher The password hasher instance 
  60          * @param LoggerInterface $logger The logger instance 
  61          * @param MailerInterface $mailer The mailer instance 
  62          * @param EntityManagerInterface $manager The manager instance 
  63          * @param RouterInterface $router The router instance 
  64          * @param Security $security The security instance 
  65          * @param SluggerUtil $slugger The slugger instance 
  66          * @param RequestStack $stack The stack instance 
  67          * @param TranslatorInterface $translator The translator instance 
  68          * @param Environment $twig The twig environment instance 
  69          * @param Client $google The google client instance 
  70          * @param integer $limit The page limit 
  72         public function __construct(protected CacheInterface 
$cache, protected AuthorizationCheckerInterface 
$checker, protected ContainerInterface 
$container, protected ManagerRegistry 
$doctrine, protected FormFactoryInterface 
$factory, protected UserPasswordHasherInterface 
$hasher, protected LoggerInterface 
$logger, protected MailerInterface 
$mailer, protected EntityManagerInterface 
$manager, protected RouterInterface 
$router, protected Security 
$security, protected SluggerUtil 
$slugger, protected RequestStack 
$stack, protected TranslatorInterface 
$translator, protected Environment 
$twig, protected Client 
$google, protected int $limit = 5) { 
  73                 //Call parent constructor 
  74                 parent
::__construct($this->cache
, $this->checker
, $this->container
, $this->doctrine
, $this->factory
, $this->hasher
, $this->logger
, $this->mailer
, $this->manager
, $this->router
, $this->security
, $this->slugger
, $this->stack
, $this->translator
, $this->twig
, $this->limit
); 
  76                 //Replace google client redirect uri 
  77                 $this->google
->setRedirectUri($this->router
->generate($this->google
->getRedirectUri(), [], UrlGeneratorInterface
::ABSOLUTE_URL
)); 
  83         public function edit(Request 
$request, string $hash, string $mail): Response 
{ 
  85                 if ($hash != $this->slugger
->hash($mail)) { 
  87                         throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash])); 
  91                 $mail = $this->slugger
->unshort($smail = $mail); 
  93                 //With existing subscriber 
  94                 if (empty($user = $this->doctrine
->getRepository($this->config
['class']['user'])->findOneByMail($mail))) { 
  96                         //XXX: prevent slugger reverse engineering by not displaying decoded mail 
  97                         throw $this->createNotFoundException($this->translator
->trans('Unable to find account %mail%', ['%mail%' => $smail])); 
 100                 //Prevent access when not admin, user is not guest and not currently logged user 
 101                 if (!$this->checker
->isGranted('ROLE_ADMIN') && $user != $this->security
->getUser() || !$this->checker
->isGranted('IS_AUTHENTICATED_FULLY')) { 
 102                         //Throw access denied 
 103                         //XXX: prevent slugger reverse engineering by not displaying decoded mail 
 104                         throw $this->createAccessDeniedException($this->translator
->trans('Unable to access user: %mail%', ['%mail%' => $smail])); 
 107                 //Create the RegisterType form and give the proper parameters 
 108                 $edit = $this->factory
->create($this->config
['edit']['view']['edit'], $user, [ 
 109                         //Set action to register route name and context 
 110                         'action' => $this->generateUrl($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $hash]+
$this->config
['route']['edit']['context']), 
 112                         'civility_class' => $this->config
['class']['civility'], 
 113                         //Set civility default 
 114                         'civility_default' => $this->doctrine
->getRepository($this->config
['class']['civility'])->findOneByTitle($this->config
['default']['civility']), 
 116                         'country_class' => $this->config
['class']['country'], 
 117                         //Set country default 
 118                         'country_default' => $this->doctrine
->getRepository($this->config
['class']['country'])->findOneByTitle($this->config
['default']['country']), 
 119                         //Set country favorites 
 120                         'country_favorites' => $this->doctrine
->getRepository($this->config
['class']['country'])->findByTitle($this->config
['default']['country_favorites']), 
 122                         'dance' => $this->checker
->isGranted('ROLE_ADMIN'), 
 124                         'dance_choices' => $danceChoices = $this->doctrine
->getRepository($this->config
['class']['dance'])->findChoicesAsArray(), 
 126                         #'dance_default' => /*$this->doctrine->getRepository($this->config['class']['dance'])->findOneByNameType($this->config['default']['dance'])*/null, 
 127                         //Set dance favorites 
 128                         'dance_favorites' => $this->doctrine
->getRepository($this->config
['class']['dance'])->findIdByNameTypeAsArray($this->config
['default']['dance_favorites']), 
 130                         'subscription' => $this->checker
->isGranted('ROLE_ADMIN'), 
 131                         //Set subscription choices 
 132                         'subscription_choices' => $subscriptionChoices = $this->doctrine
->getRepository($this->config
['class']['user'])->findChoicesAsArray(), 
 133                         //Set subscription default 
 134                         #'subscription_default' => /*$this->doctrine->getRepository($this->config['class']['user'])->findOneByPseudonym($this->config['default']['subscription'])*/null, 
 135                         //Set subscription favorites 
 136                         'subscription_favorites' => $this->doctrine
->getRepository($this->config
['class']['user'])->findIdByPseudonymAsArray($this->config
['default']['subscription_favorites']), 
 138                         'mail' => $this->checker
->isGranted('ROLE_ADMIN'), 
 140                         'pseudonym' => $this->checker
->isGranted('ROLE_GUEST'), 
 145                 ]+
$this->config
['edit']['field']); 
 148                 if ($this->checker
->isGranted('ROLE_ADMIN')) { 
 149                         //Create the ResetType form and give the proper parameters 
 150                         $reset = $this->factory
->create($this->config
['edit']['view']['reset'], $user, [ 
 151                                 //Set action to register route name and context 
 152                                 'action' => $this->generateUrl($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $hash]+
$this->config
['route']['edit']['context']), 
 160                         if ($request->isMethod('POST')) { 
 161                                 //Refill the fields in case the form is not valid. 
 162                                 $reset->handleRequest($request); 
 164                                 //With reset submitted and valid 
 165                                 if ($reset->isSubmitted() && $reset->isValid()) { 
 167                                         $data = $reset->getData(); 
 170                                         $data->setPassword($this->hasher
->hashPassword($data, $data->getPassword())); 
 172                                         //Queue user password save 
 173                                         $this->manager
->persist($data); 
 175                                         //Flush to get the ids 
 176                                         $this->manager
->flush(); 
 179                                         $this->addFlash('notice', $this->translator
->trans('Account %mail% password updated', ['%mail%' => $mail])); 
 181                                         //Redirect to cleanup the form 
 182                                         return $this->redirectToRoute($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $hash]+
$this->config
['route']['edit']['context']); 
 187                         $this->config
['edit']['view']['context']['reset'] = $reset->createView(); 
 189                         //Add google calendar array 
 190                         $this->config
['edit']['view']['context']['calendar'] = [ 
 193                                 //Uri to link account 
 197                                         'png' => '@RapsysAir/png/calendar.png', 
 198                                         'svg' => '@RapsysAir/svg/calendar.svg' 
 203                         $this->google
->setLoginHint($user->getMail()); 
 206                         if (!($googleTokens = $user->getGoogleTokens())->isEmpty()) { 
 207                                 //Iterate on each google token 
 208                                 //XXX: either we finish with a valid token set or a logic exception after token removal 
 209                                 foreach($googleTokens as $googleToken) { 
 210                                         //Clear client cache before changing access token 
 211                                         //TODO: set a per token cache ? 
 212                                         $this->google
->getCache()->clear(); 
 215                                         $this->google
->setAccessToken( 
 217                                                         'access_token' => $googleToken->getAccess(), 
 218                                                         'refresh_token' => $googleToken->getRefresh(), 
 219                                                         'created' => $googleToken->getCreated()->getTimestamp(), 
 220                                                         'expires_in' => $googleToken->getExpired()->getTimestamp() - (new \
DateTime('now'))->getTimestamp(), 
 225                                         if ($this->google
->isAccessTokenExpired()) { 
 227                                                 if (($refresh = $this->google
->getRefreshToken()) && ($token = $this->google
->fetchAccessTokenWithRefreshToken($refresh)) && empty($token['error'])) { 
 229                                                         $googleToken->setAccess($token['access_token']); 
 232                                                         $googleToken->setExpired(new \
DateTime('+'.$token['expires_in'].' second')); 
 235                                                         $googleToken->setRefresh($token['refresh_token']); 
 237                                                         //Queue google token save 
 238                                                         $this->manager
->persist($googleToken); 
 240                                                         //Flush to get the ids 
 241                                                         $this->manager
->flush(); 
 244                                                         //Add error in flash message 
 247                                                                 $this->translator
->trans( 
 248                                                                         empty($token['error'])?'Unable to refresh token':'Unable to refresh token: %error%', 
 249                                                                         empty($token['error'])?[]:['%error%' => str_replace('_', ' ', $token['error'])] 
 256                                                         //Iterate on each google token calendars 
 257                                                         foreach($googleToken->getGoogleCalendars() as $googleCalendar) { 
 259                                                                 $cmails[] = $googleCalendar->getMail(); 
 261                                                                 //Remove google token calendar 
 262                                                                 $this->manager
->remove($googleCalendar); 
 265                                                         //Log unlinked google token infos 
 266                                                         $this->logger
->emergency( 
 267                                                                 $this->translator
->trans( 
 268                                                                         'expired: mail=%mail% gmail=%gmail% cmails=%cmails% locale=%locale%', 
 270                                                                                 '%mail%' => $googleToken->getUser()->getMail(), 
 271                                                                                 '%gmail%' => $googleToken->getMail(), 
 272                                                                                 '%cmails' => implode(',', $cmails), 
 273                                                                                 '%locale%' => $request->getLocale() 
 278                                                         //Remove google token 
 279                                                         $this->manager
->remove($googleToken); 
 282                                                         $this->manager
->flush(); 
 284                                                         //TODO: warn user by mail ? 
 291                                         //XXX: TODO: remove DEBUG 
 292                                         #$this->cache->delete('user.edit.calendar.'.$this->slugger->short($googleToken->getMail())); 
 297                                                 $calendars = $this->cache
->get( 
 298                                                         //Set key to user.edit.$mail 
 299                                                         ($calendarKey = 'user.edit.calendar.'.($googleShortMail = $this->slugger
->short($googleMail = $googleToken->getMail()))), 
 300                                                         //Fetch mail calendar list 
 301                                                         function (ItemInterface 
$item): array { 
 303                                                                 $item->expiresAfter(3600); 
 305                                                                 //Get google calendar service 
 306                                                                 $service = new \Google\Service\
Calendar($this->google
); 
 317                                                                 //Iterate until next page token is null 
 319                                                                         //Get token calendar list 
 320                                                                         //XXX: require permission to read and write events 
 321                                                                         $calendarList = $service->calendarList
->listCalendarList(['pageToken' => $pageToken, 'minAccessRole' => 'writer', 'showHidden' => true]); 
 324                                                                         foreach($calendarList->getItems() as $calendarItem) { 
 325                                                                                 //With primary calendar 
 326                                                                                 if ($calendarItem->getPrimary()) { 
 327                                                                                         //Add primary calendar 
 328                                                                                         //XXX: use primary as key as described in google api documentation 
 329                                                                                         $calendars = ['primary' => $this->translator
->trans('Primary') /*$calendarItem->getSummary()*/] + 
$calendars; 
 330                                                                                 //With secondary calendar 
 332                                                                                         //Add secondary calendar 
 333                                                                                         //XXX: Append counter to make sure summary is unique for later array_flip call 
 334                                                                                         $calendars +
= [$calendarItem->getId() => $calendarItem->getSummary().' ('.++
$count.')']; 
 337                                                                 } while ($pageToken = $calendarList->getNextPageToken()); 
 344                                         } catch(\Google\Service\Exception 
$e) { 
 346                                                 //XXX: see https://cloud.google.com/apis/design/errors 
 347                                                 if ($e->getCode() == 401 || $e->getCode() == 403) { 
 348                                                         //Add error in flash message 
 351                                                                 $this->translator
->trans( 
 352                                                                         'Unable to list calendars: %error%', 
 353                                                                         ['%error%' => $e->getMessage()] 
 360                                                         //Iterate on each google token calendars 
 361                                                         foreach($googleToken->getGoogleCalendars() as $googleCalendar) { 
 363                                                                 $cmails[] = $googleCalendar->getMail(); 
 365                                                                 //Remove google token calendar 
 366                                                                 $this->manager
->remove($googleCalendar); 
 369                                                         //Log unlinked google token infos 
 370                                                         $this->logger
->emergency( 
 371                                                                 $this->translator
->trans( 
 372                                                                         'denied: mail=%mail% gmail=%gmail% cmails=%cmails% locale=%locale%', 
 374                                                                                 '%mail%' => $googleToken->getUser()->getMail(), 
 375                                                                                 '%gmail%' => $googleToken->getMail(), 
 376                                                                                 '%cmails' => implode(',', $cmails), 
 377                                                                                 '%locale%' => $request->getLocale() 
 382                                                         //Remove google token 
 383                                                         $this->manager
->remove($googleToken); 
 386                                                         $this->manager
->flush(); 
 388                                                         //TODO: warn user by mail ? 
 395                                                 throw new \
LogicException('Calendar list failed', 0, $e); 
 399                                         $formData = ['calendar' => []]; 
 401                                         //With google calendars 
 402                                         if (!($googleCalendars = $googleToken->getGoogleCalendars())->isEmpty()) { 
 403                                                 //Iterate on each google calendars 
 404                                                 foreach($googleCalendars as $googleCalendar) { 
 405                                                         //With existing google calendar 
 406                                                         if (isset($calendars[$googleCalendar->getMail()])) { 
 407                                                                 //Add google calendar to form data 
 408                                                                 $formData['calendar'][] = $googleCalendar->getMail(); 
 410                                                                 //Remove google calendar from database 
 411                                                                 $this->manager
->remove($googleCalendar); 
 413                                                                 //Flush to persist ids 
 414                                                                 $this->manager
->flush(); 
 419                                         //TODO: add feature for alerts (-30min/-1h) ? 
 420                                         //TODO: [Direct link to calendar ?][Direct link to calendar settings ?][Alerts][Remove] 
 422                                         //Create the CalendarType form and give the proper parameters 
 423                                         $form = $this->factory
->createNamed('calendar_'.$googleShortMail, 'Rapsys\AirBundle\Form\CalendarType', $formData, [ 
 424                                                 //Set action to register route name and context 
 425                                                 'action' => $this->generateUrl($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $hash]+
$this->config
['route']['edit']['context']), 
 426                                                 //Set calendar choices 
 427                                                 //XXX: unique calendar summary required by choice widget is guaranteed by appending ' (x)' to secondary calendars earlier 
 428                                                 'calendar_choices' => array_flip($calendars), 
 434                                         if ($request->isMethod('POST')) { 
 435                                                 //Refill the fields in case the form is not valid. 
 436                                                 $form->handleRequest($request); 
 438                                                 //With reset submitted and valid 
 439                                                 if ($form->isSubmitted() && $form->isValid()) { 
 441                                                         $data = $form->getData(); 
 444                                                         if (($clicked = $form->getClickedButton()->getName()) == 'refresh') { 
 445                                                                 //Remove calendar key 
 446                                                                 $this->cache
->delete($calendarKey); 
 449                                                                 $this->addFlash('notice', $this->translator
->trans('Account %mail% calendars updated', ['%mail%' => $googleMail])); 
 451                                                         } elseif ($clicked == 'add') { 
 452                                                                 //Get google calendar service 
 453                                                                 $service = new \Google\Service\
Calendar($this->google
); 
 457                                                                         //Instantiate calendar 
 458                                                                         $calendar = new \Google\Service\Calendar\
Calendar( 
 460                                                                                         'summary' => $this->translator
->trans($this->config
['context']['site']['title']), 
 461                                                                                         'timeZone' => date_default_timezone_get() 
 466                                                                         $service->calendars
->insert($calendar); 
 468                                                                 } catch(\Google\Service\Exception 
$e) { 
 470                                                                         throw new \
LogicException('Calendar insert failed', 0, $e); 
 473                                                                 //Remove calendar key 
 474                                                                 $this->cache
->delete($calendarKey); 
 477                                                                 $this->addFlash('notice', $this->translator
->trans('Account %mail% calendar added', ['%mail%' => $googleMail])); 
 479                                                         } elseif ($clicked == 'delete') { 
 480                                                                 //Get google calendar service 
 481                                                                 $service = new \Google\Service\
Calendar($this->google
); 
 486                                                                         $siteTitle = $this->translator
->trans($this->config
['context']['site']['title']); 
 488                                                                         //Iterate on calendars 
 489                                                                         foreach($calendars as $calendarId => $calendarSummary) { 
 490                                                                                 //With calendar matching site title 
 491                                                                                 if (substr($calendarSummary, 0, strlen($siteTitle)) == $siteTitle) { 
 492                                                                                         //Delete the calendar 
 493                                                                                         $service->calendars
->delete($calendarId); 
 497                                                                 } catch(\Google\Service\Exception 
$e) { 
 499                                                                         throw new \
LogicException('Calendar delete failed', 0, $e); 
 502                                                                 //Remove calendar key 
 503                                                                 $this->cache
->delete($calendarKey); 
 506                                                                 $this->addFlash('notice', $this->translator
->trans('Account %mail% calendars deleted', ['%mail%' => $googleMail])); 
 508                                                         } elseif ($clicked == 'unlink') { 
 509                                                                 //Iterate on each google calendars 
 510                                                                 foreach($googleCalendars as $googleCalendar) { 
 511                                                                         //Remove google calendar from database 
 512                                                                         $this->manager
->remove($googleCalendar); 
 515                                                                 //Remove google token from database 
 516                                                                 $this->manager
->remove($googleToken); 
 519                                                                 $this->manager
->flush(); 
 521                                                                 //Revoke access token 
 522                                                                 $this->google
->revokeToken($googleToken->getAccess()); 
 525                                                                 if ($refresh = $googleToken->getRefresh()) { 
 526                                                                         //Revoke refresh token 
 527                                                                         $this->google
->revokeToken($googleToken->getRefresh()); 
 530                                                                 //Remove calendar key 
 531                                                                 $this->cache
->delete($calendarKey); 
 534                                                                 $this->addFlash('notice', $this->translator
->trans('Account %mail% calendars unlinked', ['%mail%' => $googleMail])); 
 537                                                                 //Flipped calendar data 
 538                                                                 $dataCalendarFlip = array_flip($data['calendar']); 
 540                                                                 //Iterate on each google calendars 
 541                                                                 foreach($googleCalendars as $googleCalendar) { 
 542                                                                         //Without calendar in flipped data 
 543                                                                         if (!isset($dataCalendarFlip[$googleCalendarMail = $googleCalendar->getMail()])) { 
 544                                                                                 //Remove google calendar from database 
 545                                                                                 $this->manager
->remove($googleCalendar); 
 546                                                                         //With calendar in flipped data 
 548                                                                                 //Remove google calendar from calendar data 
 549                                                                                 unset($data['calendar'][$dataCalendarFlip[$googleCalendarMail]]); 
 553                                                                 //Iterate on remaining calendar data 
 554                                                                 foreach($data['calendar'] as $googleCalendarMail) { 
 555                                                                         //Create new google calendar 
 556                                                                         //XXX: remove trailing ' (x)' from summary 
 557                                                                         $googleCalendar = new GoogleCalendar($googleToken, $googleCalendarMail, preg_replace('/ \([0-9]\)$/', '', $calendars[$googleCalendarMail])); 
 559                                                                         //Queue google calendar save 
 560                                                                         $this->manager
->persist($googleCalendar); 
 563                                                                 //Flush to persist ids 
 564                                                                 $this->manager
->flush(); 
 567                                                                 $this->addFlash('notice', $this->translator
->trans('Account %mail% calendars updated', ['%mail%' => $googleMail])); 
 570                                                         //Redirect to cleanup the form 
 571                                                         return $this->redirectToRoute($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $hash]+
$this->config
['route']['edit']['context']); 
 576                                         $this->config
['edit']['view']['context']['calendar']['form'][$googleToken->getMail()] = $form->createView(); 
 580                         //Add google calendar auth url 
 581                         $this->config
['edit']['view']['context']['calendar']['link'] = $this->google
->createAuthUrl(); 
 585                 if ($request->isMethod('POST')) { 
 586                         //Refill the fields in case the form is not valid. 
 587                         $edit->handleRequest($request); 
 589                         //With edit submitted and valid 
 590                         if ($edit->isSubmitted() && $edit->isValid()) { 
 592                                 $data = $edit->getData(); 
 595                                 $this->manager
->persist($data); 
 597                                 //Try saving in database 
 599                                         //Flush to get the ids 
 600                                         $this->manager
->flush(); 
 603                                         //XXX: get mail from data as it may change 
 604                                         $this->addFlash('notice', $this->translator
->trans('Account %mail% updated', ['%mail%' => $mail = $data->getMail()])); 
 606                                         //Redirect to cleanup the form 
 607                                         return $this->redirectToRoute($this->config
['route']['edit']['name'], ['mail' => $smail = $this->slugger
->short($mail), 'hash' => $this->slugger
->hash($smail)]+
$this->config
['route']['edit']['context']); 
 608                                 //Catch double slug or mail 
 609                                 } catch (UniqueConstraintViolationException 
$e) { 
 610                                         //Add error message mail already exists 
 611                                         $this->addFlash('error', $this->translator
->trans('Account %mail% already exists', ['%mail%' => $data->getMail()])); 
 615                 //XXX: prefer a reset on login to force user unspam action 
 616                 } elseif (!$this->checker
->isGranted('ROLE_ADMIN')) { 
 618                         $this->addFlash('notice', $this->translator
->trans('To change your password login with your mail and any password then follow the procedure')); 
 622                 return $this->render( 
 624                         $this->config
['edit']['view']['name'], 
 626                         ['edit' => $edit->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['edit']['view']['context'] 
 631          * Handle google callback 
 633          * @param Request $request The request 
 634          * @return Response The response 
 636         public function googleCallback(Request 
$request): Response 
{ 
 638                 if (empty($code = $request->query
->get('code', ''))) { 
 639                         throw new \
InvalidArgumentException('Query parameter code is empty'); 
 643                 if (empty($user = $this->security
->getUser())) { 
 644                         throw new \
LogicException('User is empty'); 
 647                 //Set google client login hint 
 648                 $this->google
->setLoginHint($user->getMail()); 
 650                 //Set google client scopes 
 651                 $googleScopes = [\Google\Service\Calendar
::CALENDAR_EVENTS
, \Google\Service\Calendar
::CALENDAR
, \Google\Service\Oauth2
::USERINFO_EMAIL
]; 
 653                 //Protect to extract failure 
 655                         //Authenticate with code 
 656                         if (!empty($token = $this->google
->authenticate($code))) { 
 658                                 if (!empty($token['error'])) { 
 659                                         throw new \
LogicException('Client authenticate failed: '.str_replace('_', ' ', $token['error'])); 
 660                                 //Without refresh token 
 661                                 } elseif (empty($token['refresh_token'])) { 
 662                                         throw new \
LogicException('Refresh token is empty'); 
 664                                 } elseif (empty($token['expires_in'])) { 
 665                                         throw new \
LogicException('Expires in is empty'); 
 667                                 } elseif (empty($token['scope'])) { 
 668                                         throw new \
LogicException('Scope in is empty'); 
 669                                 //Without valid scope 
 670                                 } elseif (array_intersect($googleScopes, explode(' ', $token['scope'])) != $googleScopes) { 
 671                                         throw new \
LogicException('Scope in is not valid'); 
 675                                 $oauth2 = new \Google\Service\
Oauth2($this->google
); 
 677                                 //Protect user info get call 
 680                                         $userInfo = $oauth2->userinfo
->get(); 
 682                                 } catch(\Google\Service\Exception 
$e) { 
 684                                         throw new \
LogicException('Userinfo get failed', 0, $e); 
 687                                 //With existing token 
 689                                         //If available retrieve google token with matching mail 
 690                                         $googleToken = array_reduce( 
 691                                                 $user->getGoogleTokens()->getValues(), 
 692                                                 function ($c, $i) use ($userInfo) { 
 693                                                         if ($i->getMail() == $userInfo['email']) { 
 700                                         //XXX: TODO: should already be set and not change, remove ? 
 701                                         //XXX: TODO: store picture as well ? 
 702                                         $googleToken->setMail($userInfo['email']); 
 705                                         $googleToken->setAccess($token['access_token']); 
 708                                         $googleToken->setExpired(new \
DateTime('+'.$token['expires_in'].' second')); 
 711                                         $googleToken->setRefresh($token['refresh_token']); 
 714                                         //XXX: TODO: store picture as well ? 
 715                                         $googleToken = new GoogleToken($user, $userInfo['email'], $token['access_token'], new \
DateTime('+'.$token['expires_in'].' second'), $token['refresh_token']); 
 718                                 //Queue google token save 
 719                                 $this->manager
->persist($googleToken); 
 721                                 //Flush to get the ids 
 722                                 $this->manager
->flush(); 
 725                                 $this->addFlash('notice', $this->translator
->trans('Account %mail% google token updated', ['%mail%' => $user->getMail()])); 
 726                         //With failed authenticate 
 728                                 throw new \
LogicException('Client authenticate failed'); 
 731                 } catch(\Exception 
$e) { 
 733                         $this->addFlash('error', $this->translator
->trans('Account %mail% google token rejected: %error%', ['%mail%' => $user->getMail(), '%error%' => $e->getMessage()])); 
 737                 return $this->redirectToRoute('rapsysuser_edit', ['mail' => $short = $this->slugger
->short($user->getMail()), 'hash' => $this->slugger
->hash($short)]);