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

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\MobileDetector;
  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\HttpKernel\HttpKernelInterface;
  20. use Symfony\Component\Routing\Route;
  21. use Symfony\Component\Routing\RouterInterface;
  22. /**
  23.  * @author suncat2000 <nikolay.kotovsky@gmail.com>
  24.  * @author HenriVesala <henri.vesala@gmail.com>
  25.  */
  26. class RequestResponseListener
  27. {
  28.     const REDIRECT 'redirect';
  29.     const NO_REDIRECT 'no_redirect';
  30.     const REDIRECT_WITHOUT_PATH 'redirect_without_path';
  31.     const MOBILE 'mobile';
  32.     const TABLET 'tablet';
  33.     const FULL 'full';
  34.     /**
  35.      * @var RouterInterface
  36.      */
  37.     protected $router;
  38.     /**
  39.      * @var MobileDetector
  40.      */
  41.     protected $mobileDetector;
  42.     /**
  43.      * @var DeviceView
  44.      */
  45.     protected $deviceView;
  46.     /**
  47.      * @var array
  48.      */
  49.     protected $redirectConf;
  50.     /**
  51.      * @var bool
  52.      */
  53.     protected $isFullPath;
  54.     /**
  55.      * @var bool
  56.      */
  57.     protected $needModifyResponse false;
  58.     /**
  59.      * @var \Closure
  60.      */
  61.     protected $modifyResponseClosure;
  62.     public function __construct(
  63.         MobileDetector $mobileDetector,
  64.         DeviceView $deviceView,
  65.         RouterInterface $router,
  66.         array $redirectConf,
  67.         bool $fullPath true
  68.     ) {
  69.         $this->mobileDetector $mobileDetector;
  70.         $this->deviceView $deviceView;
  71.         $this->router $router;
  72.         // Configs mobile & tablet
  73.         $this->redirectConf $redirectConf;
  74.         $this->isFullPath $fullPath;
  75.     }
  76.     public function handleRequest(RequestEvent $event)
  77.     {
  78.         // only handle master request, do not handle sub request like esi includes
  79.         // If the device view is "not the mobile view" (e.g. we're not in the request context)
  80.         if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType() || $this->deviceView->isNotMobileView()) {
  81.             return;
  82.         }
  83.         $request $event->getRequest();
  84.         $this->mobileDetector->setUserAgent($request->headers->get('user-agent'));
  85.         // Sets the flag for the response handled by the GET switch param and the type of the view.
  86.         if ($this->deviceView->hasSwitchParam()) {
  87.             $event->setResponse($this->getRedirectResponseBySwitchParam($request));
  88.             return;
  89.         }
  90.         // If neither the SwitchParam nor the cookie are set, detect the view...
  91.         $cookieIsSet null !== $this->deviceView->getRequestedViewType();
  92.         if (!$cookieIsSet) {
  93.             if (false === $this->redirectConf['detect_tablet_as_mobile'] && $this->mobileDetector->isTablet()) {
  94.                 $this->deviceView->setTabletView();
  95.             } elseif ($this->mobileDetector->isMobile()) {
  96.                 $this->deviceView->setMobileView();
  97.             } else {
  98.                 $this->deviceView->setFullView();
  99.             }
  100.         }
  101.         // Check if we must redirect to the target view and do so if needed
  102.         if ($this->mustRedirect($request$this->deviceView->getViewType())) {
  103.             if (($response $this->getRedirectResponse($request$this->deviceView->getViewType()))) {
  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.      * @return bool true if the response needs to be modified
  123.      */
  124.     public function needsResponseModification(): bool
  125.     {
  126.         return $this->needModifyResponse;
  127.     }
  128.     public function handleResponse(ResponseEvent $event)
  129.     {
  130.         if ($this->needModifyResponse && $this->modifyResponseClosure instanceof \Closure) {
  131.             $modifyClosure $this->modifyResponseClosure;
  132.             $event->setResponse($modifyClosure($this->deviceView$event));
  133.             return;
  134.         }
  135.     }
  136.     /**
  137.      * Do we have to redirect?
  138.      *
  139.      * @param string $view For which view should be check?
  140.      */
  141.     protected function mustRedirect(Request $requeststring $view): bool
  142.     {
  143.         if (!isset($this->redirectConf[$view]) ||
  144.             !$this->redirectConf[$view]['is_enabled'] ||
  145.             (self::NO_REDIRECT === $this->getRoutingOption($request->get('_route'), $view))
  146.         ) {
  147.             return false;
  148.         }
  149.         $isHost = ($this->getCurrentHost($request) === $this->redirectConf[$view]['host']);
  150.         if (!$isHost) {
  151.             return true;
  152.         }
  153.         return false;
  154.     }
  155.     /**
  156.      * Prepares the response modification which will take place after the controller logic has been executed.
  157.      *
  158.      * @param string $view the view for which to prepare the response modification
  159.      */
  160.     protected function prepareResponseModification(string $view)
  161.     {
  162.         $this->modifyResponseClosure = function (DeviceView $deviceViewResponseEvent $event) use ($view) {
  163.             return $deviceView->modifyResponse($view$event->getResponse());
  164.         };
  165.     }
  166.     protected function getRedirectResponseBySwitchParam(Request $request): RedirectResponseWithCookie
  167.     {
  168.         if ($this->mustRedirect($request$this->deviceView->getViewType())) {
  169.             // Avoid unnecessary redirects: if we need to redirect to another view,
  170.             // do it in one response while setting the cookie.
  171.             $redirectUrl $this->getRedirectUrl($request$this->deviceView->getViewType());
  172.         } else {
  173.             if (true === $this->isFullPath) {
  174.                 $redirectUrl $request->getUriForPath($request->getPathInfo());
  175.                 $queryParams $request->query->all();
  176.                 if (\array_key_exists($this->deviceView->getSwitchParam(), $queryParams)) {
  177.                     unset($queryParams[$this->deviceView->getSwitchParam()]);
  178.                 }
  179.                 if (\count($queryParams) > 0) {
  180.                     $redirectUrl .= '?'.Request::normalizeQueryString(http_build_query($queryParams'''&'));
  181.                 }
  182.             } else {
  183.                 $redirectUrl $this->getCurrentHost($request);
  184.             }
  185.         }
  186.         return $this->deviceView->getRedirectResponseBySwitchParam($redirectUrl);
  187.     }
  188.     /**
  189.      * Gets the RedirectResponse for the specified view.
  190.      *
  191.      * @param string $view the view for which we want the RedirectResponse
  192.      */
  193.     protected function getRedirectResponse(Request $requeststring $view): ?RedirectResponse
  194.     {
  195.         if (($host $this->getRedirectUrl($request$view))) {
  196.             return $this->deviceView->getRedirectResponse(
  197.                 $view,
  198.                 $host,
  199.                 $this->redirectConf[$view]['status_code']
  200.             );
  201.         }
  202.         return null;
  203.     }
  204.     protected function getRedirectUrl(Request $requeststring $platform): ?string
  205.     {
  206.         if (($routingOption $this->getRoutingOption($request->get('_route'), $platform))) {
  207.             if (self::REDIRECT === $routingOption) {
  208.                 // Make sure to hint at the device override, otherwise infinite loop
  209.                 // redirection may occur if different device views are hosted on
  210.                 // different domains (since the cookie can't be shared across domains)
  211.                 $queryParams $request->query->all();
  212.                 $queryParams[$this->deviceView->getSwitchParam()] = $platform;
  213.                 return rtrim($this->redirectConf[$platform]['host'], '/').$request->getPathInfo().'?'.Request::normalizeQueryString(http_build_query($queryParams'''&'));
  214.             }
  215.             if (self::REDIRECT_WITHOUT_PATH === $routingOption) {
  216.                 // Make sure to hint at the device override, otherwise infinite loop
  217.                 // redirections may occur if different device views are hosted on
  218.                 // different domains (since the cookie can't be shared across domains)
  219.                 return $this->redirectConf[$platform]['host'].'?'.$this->deviceView->getSwitchParam().'='.$platform;
  220.             }
  221.             return null;
  222.         }
  223.         return null;
  224.     }
  225.     protected function getRoutingOption(string $routeNamestring $optionName): ?string
  226.     {
  227.         $option null;
  228.         $route $this->router->getRouteCollection()->get($routeName);
  229.         if ($route instanceof Route) {
  230.             $option $route->getOption($optionName);
  231.         }
  232.         if (!$option && isset($this->redirectConf[$optionName])) {
  233.             $option $this->redirectConf[$optionName]['action'];
  234.         }
  235.         if (\in_array($option, [self::REDIRECTself::REDIRECT_WITHOUT_PATHself::NO_REDIRECT], true)) {
  236.             return $option;
  237.         }
  238.         return null;
  239.     }
  240.     protected function getCurrentHost(Request $request): string
  241.     {
  242.         return $request->getScheme().'://'.$request->getHost();
  243.     }
  244. }