<?php
namespace App\Controller\Widget;
use App\Event\ModeEvent;
use App\Manager\FavoritesManager;
use App\Normalizer\DisruptionNormalizer;
use App\Normalizer\LineNormalizer;
use App\Normalizer\UserPreferencesNormalizer;
use App\Service\Internal\DisruptionService;
use App\Service\Internal\HomePageService;
use App\Service\Internal\NetworkService;
use App\Service\Internal\User\PreferenceService;
use App\Service\Internal\WidgetService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class AbstractWidgetController extends AbstractController
{
public const WIDGET_CODE_JOURNEY_INTEGRATED = 'ITINERAIRE_INTEGRE';
public const WIDGET_CODE_JOURNEY_REDIRECTION_SIM = 'ITINERAIRE_REDIRECTION_SIM';
public const WIDGET_CODE_JOURNEY_REDIRECTION_SITE_SITE = 'ITINERAIRE_REDIRECTION_SITE_SITE';
public const WIDGET_CODE_JOURNEY_REDIRECTION_PAGE_PAGE = 'ITINERAIRE_REDIRECTION_PAGE_PAGE';
public const WIDGET_CODE_SCHEDULE_INTEGRATED = 'HORAIRE_INTEGRE';
public const WIDGET_CODE_SCHEDULE_REDIRECTION_SIM = 'HORAIRE_REDIRECTION_SIM';
public const WIDGET_CODE_SCHEDULE_REDIRECTION_SITE_SITE = 'HORAIRE_REDIRECTION_SITE_SITE';
public const WIDGET_CODE_SCHEDULE_REDIRECTION_PAGE_PAGE = 'HORAIRE_REDIRECTION_PAGE_PAGE';
public const WIDGET_CODE_TRAFFIC_INTEGRATED = 'TRAFIC_INTEGRE';
public const WIDGET_CODE_FULL = 'FULL';
public const WIDGET_CODE_FULL_EXCEPT_TRAFFIC = 'FULL_EXCEPT_TRAFFIC';
public const WIDGET_CODE_FULL_EXCEPT_TRAFFIC_REDIRECTION_SIM = 'FULL_EXCEPT_TRAFFIC_REDIRECTION_SIM';
public const WIDGET_CODE_LOGIN = 'LOGIN';
public const WIDGET_CODE_JOURNEY_BOOKING = 'RESERVATION_TRAJET';
public const WIDGET_CODE_BANNERS = 'BANNERS';
public const WIDGET = 'WIDGET';
public const WIDGET_BOOKING_SECUREBIKEPARK = 'RESERVATION_ABRI_VELO';
public const WIDGET_WITHOUT_AUTHORIZATION = array(
self::WIDGET_CODE_BANNERS,
self::WIDGET_CODE_LOGIN,
self::WIDGET_CODE_JOURNEY_BOOKING
);
protected NetworkService $networkService;
protected PreferenceService $preferenceService;
protected UserPreferencesNormalizer $userPreferencesNormalizer;
protected string $baseUrl;
protected string $locale;
protected string$networkId;
protected WidgetService $widgetService;
protected EventDispatcherInterface $eventDispatcher;
protected FavoritesManager $favoritesManager;
protected DisruptionNormalizer $disruptionNormalizer;
protected DisruptionService $disruptionService;
protected HomePageService $homepageService;
protected LineNormalizer$lineNormalizer;
/**
* WidgetController constructor.
*/
public function __construct(
NetworkService $networkService,
PreferenceService $preferenceService,
UserPreferencesNormalizer $userPreferencesNormalizer,
WidgetService $widgetService,
EventDispatcherInterface $eventDispatcher,
FavoritesManager $favoritesManager,
DisruptionNormalizer $disruptionNormalizer,
DisruptionService $disruptionService,
HomePageService $homepageService,
LineNormalizer $lineNormalizer
) {
$this->baseUrl = 'https://' . $_SERVER['SERVER_NAME'];
$this->networkService = $networkService;
$this->preferenceService = $preferenceService;
$this->userPreferencesNormalizer = $userPreferencesNormalizer;
$this->widgetService = $widgetService;
$this->eventDispatcher = $eventDispatcher;
$this->favoritesManager = $favoritesManager;
$this->disruptionNormalizer = $disruptionNormalizer;
$this->disruptionService = $disruptionService;
$this->homepageService = $homepageService;
$this->lineNormalizer = $lineNormalizer;
}
protected function initServices(Request $request)
{
$this->networkService->getNetwork();
$this->networkId = $this->getParameter('network_id');
$this->locale = $request->attributes->get('_locale');
$layoutScheduleLine = $this->networkService->getLayoutScheduleLine();
$session = $request->getSession();
$session->set('layoutScheduleLine', $layoutScheduleLine);
$isRedirection = $request->get('redirection');
$isInternal = $request->get('internal');
$target = $request->get('target');
$isPartner = $request->get('partner');
$widgetCode = $this->getWidgetCode($request, $isRedirection, $isInternal, $isPartner, $target);
$domain = parse_url($request->headers->get('referer'), PHP_URL_HOST);
if($domain &&
$widgetCode &&
!in_array($widgetCode,self::WIDGET_WITHOUT_AUTHORIZATION) &&
$_SERVER['SERVER_NAME'] != $domain &&
!$this->widgetService->isAuthorizedDomain($domain, $widgetCode)) {
die('Unauthorized domain');
}
}
/**
* @param Request $request
* @param bool $split
*
* @return array
* @throws \Exception
*/
protected function getFullWidgetsParameters(Request $request, bool $split = false)
{
$this->initServices($request);
$isRedirection = $request->get('redirection');
$isInternal = $request->get('internal');
$target = $request->get('target');
$isPartner = $request->get('partner');
$islid = $request->get('islid');
$isdir = $request->get('isdir');
$issaid = $request->get('issaid');
$issav = $request->get('issav');
$islocid = $request->get('islocid');
$except = $request->get('except');
$isSubNetworks = $request->get('issubnetworks');
$widgetCode = $this->getWidgetCode($request, $isRedirection, $isInternal, $isPartner, $target);
$this->saveXHeaders($request, $widgetCode);
if (empty($request->get('token'))) {
throw new InvalidArgumentException("The token is missing");
}
$modeEvent = new ModeEvent();
$this->eventDispatcher->dispatch($modeEvent, ModeEvent::NAME);
$modes = $modeEvent->getDefaultModes();
$modesLayout = $modeEvent->getLayoutModes();
$nbDayFuture = $this->networkService->getNbDayFuture();
$maxDate = new \DateTime('now');
$maxDate = $maxDate->add(new \DateInterval('P'.$nbDayFuture.'D'));
$itineraryOptions = $this->networkService->getItineraryOptions();
$userPreferences = [];
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$userPreferences = $this->preferenceService->getUserPreferences();
}
$preferences = $this->userPreferencesNormalizer->normalize($userPreferences, ['itineraryOptions' => $itineraryOptions]);
if ('traffic' !== $except) {
$parameters = [];
$parameters['issubnetworks'] = $isSubNetworks;
$this->homepageService->getDisruption($parameters);
}
$scheduleFavorites = [];
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$scheduleFavorites = $this->favoritesManager->getSchedules([], true);
}
$parameters['scheduleFavorites'] = $scheduleFavorites ?? [];
$parameters['lineFavorites'] = [];
if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$parameters['lineFavorites'] = $this->favoritesManager->getLines([], true);
}
$parameters = array_merge($parameters, [
'isWidget' => true,
'requestReferer' => $request->headers->get('referer'),
'isSplit' => $split,
'default_latlon' => $this->networkService->getLatLon(),
'network_lat' => $this->networkService->getLat(),
'network_lon' => $this->networkService->getLon(),
'external' => $this->networkService->isExternalNetwork(),
'maxDate' => $maxDate->format('Y-d-m'),
'modes' => $modes,
'baseUrl' => $this->baseUrl,
'isRedirection' => !empty($isRedirection) && $isRedirection === 'true' ? true : false,
'isInternal' => !empty($isInternal) && $isInternal === 'true' ? true : false,
'widgetTarget' => !empty($target) ? $target : '',
'isPartner' => !empty($isPartner) && $isPartner === 'true' ? true : false,
'islid' => !empty($islid) ? $islid : '',
'isdir' => !empty($isdir) ? $isdir : '',
'issaid' => !empty($issaid) ? $issaid : '',
'issav' => !empty($issav) ? $issav : '',
'islocid' => !empty($islocid) ? $islocid : '',
'issubnetworks' => !empty($isSubNetworks) ? $isSubNetworks : '',
'preferences' => $preferences,
]);
$parameters = array_merge($parameters, $this->getFullCustomMapParameters($request));
$configTemplate = $this->render("includes/common/js-config.html.twig", $parameters )->getContent();
$parameters = array_merge($parameters, [
'isWidget' => true,
'widgetCode' => $widgetCode,
'default_latlon' => $this->networkService->getLatLon(),
'isRedirection' => !empty($isRedirection) && $isRedirection === 'true' ? true : false,
'isInternal' => !empty($isInternal) && $isInternal === 'true' ? true : false,
'modes' => $modes,
'modesLayout' => $modesLayout,
'preferences' => $preferences,
]);
$widgetScript = $this->getWidgetScript($request);
$widgetCss = $this->getWidgetCss($request);
$hasMap = $this->hasMap($request);
return [
'baseUrl' => $this->baseUrl,
'newtorkId' => $this->networkId,
'config' => $configTemplate,
'widgetScript' => $widgetScript,
'widgetCss' => $widgetCss,
'hasMap' => $hasMap,
'parameters' => $parameters,
];
}
/**
* Get name for the widget minified script
*/
protected function getWidgetScript(Request $request): string
{
switch ($request->getPathInfo()) {
case '/'. $this->locale .'/widget/place-journey':
$widgetScript = 'place-journey-widget.min.js';
break;
case '/'. $this->locale .'/widget/place':
$widgetScript = 'place-widget.min.js';
break;
case '/'. $this->locale .'/widget/schedule':
$widgetScript = 'schedule-widget.min.js';
break;
case '/'. $this->locale .'/widget/place-journey-map':
$widgetScript = 'place-journey-map-widget.min.js';
break;
case '/'. $this->locale .'/widget/schedule-map':
$widgetScript = 'schedule-map-widget.min.js';
break;
case '/'. $this->locale .'/widget/traffic':
$widgetScript = 'traffic-widget.min.js';
break;
case '/'.$this->locale . '/widget/full':
case '/'.$this->locale . '/widget/full-split':
case '/'.$this->locale . '/widget/banners':
$widgetScript = 'search-widget.min.js';
break;
case '/'.$this->locale . '/widget/login':
$widgetScript = 'login-widget.min.js';
break;
case '/'.$this->locale . '/widget/journey-booking':
$widgetScript = 'journey-booking-widget.min.js';
break;
default:
throw new NotFoundHttpException('Wrong widget');
break;
}
return $widgetScript;
}
/**
* Get name for the widget minified css
*/
protected function getWidgetCss(Request $request): string
{
switch ($request->getPathInfo()) {
case '/'. $this->locale .'/widget/place-journey':
$widgetCss = 'widget-journey.min.css';
break;
case '/'. $this->locale .'/widget/place':
$widgetCss = 'widget-journey.min.css';
break;
case '/'. $this->locale .'/widget/schedule':
$widgetCss = 'widget-schedule.min.css';
break;
case '/'. $this->locale .'/widget/place-journey-map':
$widgetCss = 'widget-journey.min.css';
break;
case '/'. $this->locale .'/widget/schedule-map':
$widgetCss = 'widget-schedule.min.css';
break;
case '/'. $this->locale .'/widget/traffic':
$widgetCss = 'widget-traffic.min.css';
break;
case '/'.$this->locale . '/widget/full':
case '/'.$this->locale . '/widget/full-split':
case '/'.$this->locale . '/widget/banners':
case '/'.$this->locale . '/widget/login':
case '/'.$this->locale . '/widget/journey-booking':
$widgetCss = 'widget-full.min.css';
break;
default:
throw new NotFoundHttpException('Wrong widget');
break;
}
return $widgetCss;
}
/**
* Get rtl css for the widget
*/
protected function getRtlCss(): ?string
{
switch ($this->locale) {
case 'ar':
$rtlCss = 'sim-rtl.min.css';
break;
default:
$rtlCss = null;
break;
}
return $rtlCss;
}
/**
* Check if the widget require the map
*/
protected function hasMap(Request $request): bool
{
switch ($request->getPathInfo()) {
case '/'. $this->locale .'/widget/place-journey':
case '/'. $this->locale .'/widget/place':
case '/'. $this->locale .'/widget/schedule':
case '/'. $this->locale .'/widget/traffic':
case '/'. $this->locale .'/widget/banners':
case '/'. $this->locale .'/widget/login':
case '/'. $this->locale .'/widget/journey-booking':
return false;
break;
case '/'. $this->locale .'/widget/place-journey-map':
case '/'. $this->locale .'/widget/schedule-map':
case '/'. $this->locale .'/widget/full':
return true;
break;
case '/'. $this->locale .'/widget/full-split':
return true;
break;
default:
throw new NotFoundHttpException('Wrong widget');
break;
}
}
/**
* Build headers for tracking and security
*/
protected function saveXHeaders(Request $request, string $widgetCode)
{
$token = $request->get('token');
$session = $request->getSession();
$request->attributes->set('widgetContext', 'true');
$session->set('context', self::WIDGET);
$session->set('xHeaders', []);
$xHeaders = $session->get('xHeaders');
if (!empty($request) && !empty($request->get('token') && get_class($request) === Request::class)
) {
$token = 'IS_WIDGET:' . $this->networkId . ':'. $widgetCode . ':' . $token;
$session->set('token', $token);
if (!is_null($request->query->get('token'))) $xHeaders['x-widget-id'] = $token;
if (!is_null($request->headers->get('referer'))) $xHeaders['x-referer'] = $request->headers->get('referer');
if (!is_null($request->headers->get('referer'))) $xHeaders['x-origin'] = $request->headers->get('referer');
$session->set('xHeaders', $xHeaders);
}
}
/**
* Get widget code for tracking and security
*/
protected function getWidgetCode(Request $request, $isRedirection, $isInternal, $isPartner, $target): string
{
$isRedirection = $isRedirection === 'true'? true: false;
$isInternal = $isInternal === 'true'? true: false;
$locale = $request->getLocale();
$params = $request->query->all();
switch ($request->getPathInfo()) {
case '/'. $locale .'/widget/place-journey':
case '/'. $locale .'/widget/place-journey-map':
if (!$isRedirection && $isInternal) {
$widgetCode = self::WIDGET_CODE_JOURNEY_REDIRECTION_PAGE_PAGE;
} else {
$widgetCode = self::WIDGET_CODE_JOURNEY_INTEGRATED;
}
break;
case '/'. $locale .'/widget/place':
if ($isRedirection && !$isInternal && !$isPartner && !$target) {
$widgetCode = self::WIDGET_CODE_JOURNEY_REDIRECTION_SIM;
} elseif ($isRedirection && !$isInternal && $isPartner && $target ) {
$widgetCode = self::WIDGET_CODE_JOURNEY_REDIRECTION_SITE_SITE;
} elseif ($isRedirection && $isInternal && $target ) {
$widgetCode = self::WIDGET_CODE_JOURNEY_REDIRECTION_PAGE_PAGE;
} else {
$widgetCode = self::WIDGET_CODE_JOURNEY_INTEGRATED;
}
break;
case '/'. $locale .'/widget/schedule':
case '/'. $locale .'/widget/schedule-map':
if ($isRedirection && !$isInternal && !$isPartner && !$target) {
$widgetCode = self::WIDGET_CODE_SCHEDULE_REDIRECTION_SIM;
} elseif ($isRedirection && !$isInternal && $isPartner && $target ) {
$widgetCode = self::WIDGET_CODE_SCHEDULE_REDIRECTION_SITE_SITE;
} elseif ($isRedirection && $isInternal && $target ) {
$widgetCode = self::WIDGET_CODE_SCHEDULE_REDIRECTION_PAGE_PAGE;
} else {
$widgetCode = self::WIDGET_CODE_SCHEDULE_INTEGRATED;
}
break;
case '/'. $locale .'/widget/traffic':
$widgetCode = self::WIDGET_CODE_TRAFFIC_INTEGRATED;
break;
case '/'. $locale .'/widget/full':
$widgetCode = self::WIDGET_CODE_FULL;
if (isset($params['except']) && 'traffic' === $params['except']) {
$widgetCode = self::WIDGET_CODE_FULL_EXCEPT_TRAFFIC;
} elseif (isset($params['except']) && 'traffic' === $params['except'] && $isRedirection && $isInternal) {
$widgetCode = self::WIDGET_CODE_FULL_EXCEPT_TRAFFIC_REDIRECTION_SIM;
}
break;
case '/'. $locale .'/widget/full-split':
$widgetCode = self::WIDGET_CODE_FULL;
break;
case '/'. $locale .'/widget/banners':
$widgetCode = self::WIDGET_CODE_BANNERS;
break;
case '/'. $locale .'/widget/login':
$widgetCode = self::WIDGET_CODE_LOGIN;
break;
case '/'. $locale .'/widget/journey-booking':
$widgetCode = self::WIDGET_CODE_JOURNEY_BOOKING;
break;
case '/' . $locale . '/widget/booking/securebikepark/create':
$widgetCode = self::WIDGET_BOOKING_SECUREBIKEPARK;
break;
default:
$widgetCode = '';
break;
}
return $widgetCode;
}
/**
* Get disruptions
*/
protected function getDisruptions(Request $request)
{
$disruptionLayout = $this->networkService->getDisruptionLayout();
$currentSubNetworksDisruptions = 0;
$futureSubNetworksDisruptions = 0;
$isSubNetworks = $request->get('issubnetworks');
$linesDisruptions = $this->disruptionService->getLinesDisruptions($isSubNetworks);
$parameters['linesDisruptions'] = isset($linesDisruptions) ? $this->disruptionNormalizer->normalize($linesDisruptions) : [];
$disruptions = $this->disruptionService->getDisruptions($isSubNetworks);
$parameters['disruptions'] = isset($disruptions) ? $this->disruptionNormalizer->normalize($disruptions) : [];
$withDisruptionOnly = null;
if (in_array("FAV_WITH_DISRUPTIONS_BOARDS_LINES", $disruptionLayout)) {
$withDisruptionOnly = 0;
} elseif (in_array("FAV_WITH_DISRUPTIONS_BOARDS_LINES_WITHDISRUPTIONSONLY", $disruptionLayout)) {
$withDisruptionOnly = 1;
}
$this->homepageService->getDisruption($parameters);
$parameters['disruptions'] = isset($boardsLinesDisruptions['disruptions']) ? $this->disruptionNormalizer->normalize($boardsLinesDisruptions['disruptions']) : [];
$boardsLinesDisruptions['lines'] = isset($boardsLinesDisruptions['lines']) ? $this->lineNormalizer->normalize($boardsLinesDisruptions['lines'], ['lineDisruptions' => $boardsLinesDisruptions['disruptions'] ?? []]) : [];
$parameters['boardsLinesDisruptions'] = $boardsLinesDisruptions;
$parameters['trafficMini'] = $request->get('trafficMini');
$parameters['currentSubNetworksDisruptions'] = $currentSubNetworksDisruptions;
$parameters['futureSubNetworksDisruptions'] = $futureSubNetworksDisruptions;
return $parameters;
}
/**
* Entry point for some widget
*/
public function init(Request $request): Response
{
$remove = $request->get('remove');
$handledKeys = ['css'];
$widgetParams = [];
if($remove !== null){
if(is_array($remove)){
foreach ($remove as $key){
if(in_array($key, $handledKeys)){
$widgetParams[$key] = false;
}
}
}
else {
if(in_array($remove, $handledKeys)){
$widgetParams[$remove] = false;
}
}
}
$configTemplate = $this
->renderView('includes/common/js-config.html.twig', [
'baseUrl' => $this->baseUrl,
'widgetParams' => $widgetParams
]);
$configTemplate = preg_replace("/[\n\r\t]+ [\s]{2,}/", '', $configTemplate);
$jsContent = file_get_contents($this->getParameter('kernel.project_dir') . '/public/js/widget.min.js');
return new Response($configTemplate . $jsContent, 200, ['Content-Type' => 'text/javascript']);
}
protected function getFullCustomMapParameters(Request $request): array
{
$customMapParameters = [];
$iszoom = $request->get('iszoom');
if(!empty($iszoom) && is_numeric($iszoom)) {
$customMapParameters['customZoom'] = $iszoom;
}
$islat = $request->get('islat');
$islon = $request->get('islon');
if(!empty($islat) && is_numeric($islat) && !empty($islon) && is_numeric($islon)) {
$customMapParameters['islat'] = $islat;
$customMapParameters['islon'] = $islon;
$customMapParameters['customLatLon'] = '[' . $islat . ', ' . $islon . ']';
}
return $customMapParameters;
}
}