vendor/tattali/mobile-detect-bundle/src/EventListener/RequestResponseListener.php line 153

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the MobileDetectBundle.
  4.  *
  5.  * (c) Nikolay Ivlev <nikolay.kotovsky@gmail.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. declare(strict_types=1);
  11. namespace MobileDetectBundle\EventListener;
  12. use MobileDetectBundle\DeviceDetector\MobileDetectorInterface;
  13. use MobileDetectBundle\Helper\DeviceView;
  14. use MobileDetectBundle\Helper\RedirectResponseWithCookie;
  15. use Symfony\Component\HttpFoundation\RedirectResponse;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\HttpKernel\Event\RequestEvent;
  18. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  19. use Symfony\Component\Routing\Route;
  20. use Symfony\Component\Routing\RouterInterface;
  21. /**
  22.  * @author suncat2000 <nikolay.kotovsky@gmail.com>
  23.  * @author HenriVesala <henri.vesala@gmail.com>
  24.  */
  25. class RequestResponseListener
  26. {
  27.     public const REDIRECT 'redirect';
  28.     public const NO_REDIRECT 'no_redirect';
  29.     public const REDIRECT_WITHOUT_PATH 'redirect_without_path';
  30.     public const MOBILE 'mobile';
  31.     public const TABLET 'tablet';
  32.     public const FULL 'full';
  33.     /**
  34.      * @var RouterInterface
  35.      */
  36.     protected $router;
  37.     /**
  38.      * @var MobileDetectorInterface
  39.      */
  40.     protected $mobileDetector;
  41.     /**
  42.      * @var DeviceView
  43.      */
  44.     protected $deviceView;
  45.     /**
  46.      * @var array
  47.      */
  48.     protected $redirectConf;
  49.     /**
  50.      * @var bool
  51.      */
  52.     protected $isFullPath;
  53.     /**
  54.      * @var bool
  55.      */
  56.     protected $needModifyResponse false;
  57.     /**
  58.      * @var \Closure
  59.      */
  60.     protected $modifyResponseClosure;
  61.     public function __construct(
  62.         MobileDetectorInterface $mobileDetector,
  63.         DeviceView $deviceView,
  64.         RouterInterface $router,
  65.         array $redirectConf,
  66.         bool $fullPath true
  67.     ) {
  68.         $this->mobileDetector $mobileDetector;
  69.         $this->deviceView $deviceView;
  70.         $this->router $router;
  71.         // Configs mobile & tablet
  72.         $this->redirectConf $redirectConf;
  73.         $this->isFullPath $fullPath;
  74.     }
  75.     public function handleRequest(RequestEvent $event): void
  76.     {
  77.         // only handle master request, do not handle sub request like esi includes
  78.         // If the device view is "not the mobile view" (e.g. we're not in the request context)
  79.         if ((\defined('Symfony\Component\HttpKernel\HttpKernelInterface::MAIN_REQUEST') ? \constant('Symfony\Component\HttpKernel\HttpKernelInterface::MAIN_REQUEST') : \constant('Symfony\Component\HttpKernel\HttpKernelInterface::MASTER_REQUEST')) !== $event->getRequestType() || $this->deviceView->isNotMobileView()) {
  80.             return;
  81.         }
  82.         $request $event->getRequest();
  83.         $this->mobileDetector->setUserAgent($request->headers->get('user-agent'));
  84.         // Sets the flag for the response handled by the GET switch param and the type of the view.
  85.         if ($this->deviceView->hasSwitchParam()) {
  86.             $event->setResponse($this->getRedirectResponseBySwitchParam($request));
  87.             return;
  88.         }
  89.         // If neither the SwitchParam nor the cookie are set, detect the view...
  90.         $cookieIsSet null !== $this->deviceView->getRequestedViewType();
  91.         if (!$cookieIsSet) {
  92.             if (false === $this->redirectConf['detect_tablet_as_mobile'] && $this->mobileDetector->isTablet()) {
  93.                 $this->deviceView->setTabletView();
  94.             } elseif ($this->mobileDetector->isMobile()) {
  95.                 $this->deviceView->setMobileView();
  96.             } else {
  97.                 $this->deviceView->setFullView();
  98.             }
  99.         }
  100.         $viewType $this->deviceView->getViewType();
  101.         // Check if we must redirect to the target view and do so if needed
  102.         if ($viewType && $this->mustRedirect($request$viewType)) {
  103.             if ($response $this->getRedirectResponse($request$viewType)) {
  104.                 $event->setResponse($response);
  105.             }
  106.             return;
  107.         }
  108.         // No need to redirect
  109.         // We don't need to modify _every_ response: once the cookie is set,
  110.         // save bandwith and CPU cycles by just letting it expire someday.
  111.         if ($cookieIsSet) {
  112.             return;
  113.         }
  114.         // Sets the flag for the response handler and prepares the modification closure
  115.         $this->needModifyResponse true;
  116.         $this->prepareResponseModification($this->deviceView->getViewType());
  117.     }
  118.     /**
  119.      * Will this request listener modify the response? This flag will be set during the "handleRequest" phase.
  120.      * Made public for testability.
  121.      */
  122.     public function needsResponseModification(): bool
  123.     {
  124.         return $this->needModifyResponse;
  125.     }
  126.     public function handleResponse(ResponseEvent $event): void
  127.     {
  128.         if ($this->needModifyResponse && $this->modifyResponseClosure instanceof \Closure) {
  129.             $modifyClosure $this->modifyResponseClosure;
  130.             $event->setResponse($modifyClosure($this->deviceView$event));
  131.             return;
  132.         }
  133.     }
  134.     protected function getRedirectResponseBySwitchParam(Request $request): RedirectResponseWithCookie
  135.     {
  136.         if ($this->mustRedirect($request$this->deviceView->getViewType())) {
  137.             // Avoid unnecessary redirects: if we need to redirect to another view,
  138.             // do it in one response while setting the cookie.
  139.             $redirectUrl $this->getRedirectUrl($request$this->deviceView->getViewType());
  140.         } else {
  141.             if (true === $this->isFullPath) {
  142.                 $redirectUrl $request->getUriForPath($request->getPathInfo());
  143.                 // $redirectUrl = ($request->getPathInfo()) ? $request->getUriForPath($request->getPathInfo()) : $this->getCurrentHost($request);
  144.                 $queryParams $request->query->all();
  145.                 if (\array_key_exists($this->deviceView->getSwitchParam(), $queryParams)) {
  146.                     unset($queryParams[$this->deviceView->getSwitchParam()]);
  147.                 }
  148.                 if (\count($queryParams) > 0) {
  149.                     $redirectUrl .= '?'.Request::normalizeQueryString(http_build_query($queryParams'''&'));
  150.                 }
  151.             } else {
  152.                 $redirectUrl $request->getSchemeAndHttpHost();
  153.             }
  154.         }
  155.         return $this->deviceView->getRedirectResponseBySwitchParam($redirectUrl);
  156.     }
  157.     /**
  158.      * Do we have to redirect?
  159.      *
  160.      * @param string $viewType The view we want to redirect to
  161.      */
  162.     protected function mustRedirect(Request $requeststring $viewType): bool
  163.     {
  164.         if (!isset($this->redirectConf[$viewType])
  165.             || !$this->redirectConf[$viewType]['is_enabled']
  166.             || (self::NO_REDIRECT === $this->getRoutingOption($request->get('_route'), $viewType))
  167.         ) {
  168.             return false;
  169.         }
  170.         return $request->getSchemeAndHttpHost() !== $this->redirectConf[$viewType]['host'];
  171.     }
  172.     protected function getRoutingOption(string $routeNamestring $optionName): ?string
  173.     {
  174.         $option null;
  175.         $route $this->router->getRouteCollection()->get($routeName);
  176.         if ($route instanceof Route) {
  177.             $option $route->getOption($optionName);
  178.         }
  179.         if (!$option && isset($this->redirectConf[$optionName])) {
  180.             $option $this->redirectConf[$optionName]['action'];
  181.         }
  182.         if (\in_array($option, [self::REDIRECTself::REDIRECT_WITHOUT_PATHself::NO_REDIRECT], true)) {
  183.             return $option;
  184.         }
  185.         return null;
  186.     }
  187.     protected function getRedirectUrl(Request $requeststring $view): ?string
  188.     {
  189.         if ($routingOption $this->getRoutingOption($request->get('_route'), $view)) {
  190.             if (self::REDIRECT === $routingOption) {
  191.                 // Make sure to hint at the device override, otherwise infinite loop
  192.                 // redirection may occur if different device views are hosted on
  193.                 // different domains (since the cookie can't be shared across domains)
  194.                 $queryParams $request->query->all();
  195.                 $queryParams[$this->deviceView->getSwitchParam()] = $view;
  196.                 return rtrim($this->redirectConf[$view]['host'], '/').$request->getPathInfo().'?'.Request::normalizeQueryString(http_build_query($queryParams));
  197.             }
  198.             if (self::REDIRECT_WITHOUT_PATH === $routingOption) {
  199.                 // Make sure to hint at the device override, otherwise infinite loop
  200.                 // redirections may occur if different device views are hosted on
  201.                 // different domains (since the cookie can't be shared across domains)
  202.                 return $this->redirectConf[$view]['host'].'?'.$this->deviceView->getSwitchParam().'='.$view;
  203.             }
  204.             return null;
  205.         }
  206.         return null;
  207.     }
  208.     /**
  209.      * Gets the RedirectResponse for the specified view.
  210.      *
  211.      * @param string $view the view for which we want the RedirectResponse
  212.      */
  213.     protected function getRedirectResponse(Request $requeststring $view): ?RedirectResponse
  214.     {
  215.         if ($host $this->getRedirectUrl($request$view)) {
  216.             return $this->deviceView->getRedirectResponse(
  217.                 $view,
  218.                 $host,
  219.                 $this->redirectConf[$view]['status_code']
  220.             );
  221.         }
  222.         return null;
  223.     }
  224.     /**
  225.      * Prepares the response modification which will take place after the controller logic has been executed.
  226.      *
  227.      * @param string $view the view for which to prepare the response modification
  228.      */
  229.     protected function prepareResponseModification(string $view): void
  230.     {
  231.         $this->modifyResponseClosure = function (DeviceView $deviceViewResponseEvent $event) use ($view) {
  232.             return $deviceView->modifyResponse($view$event->getResponse());
  233.         };
  234.     }
  235. }