<?php
/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Enterprise License (PEL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PEL
*/
namespace Pimcore\Bundle\CoreBundle\EventListener\Frontend;
use Pimcore\Bundle\CoreBundle\EventListener\Traits\PimcoreContextAwareTrait;
use Pimcore\Config;
use Pimcore\Http\Request\Resolver\PimcoreContextResolver;
use Pimcore\Http\Request\Resolver\SiteResolver;
use Pimcore\Http\RequestHelper;
use Pimcore\Model\Site;
use Pimcore\Routing\RedirectHandler;
use Pimcore\Tool;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Runs before dynamic routing kicks in and resolves site + handles redirects
*
* TODO as this also handles the admin domain, a name without "Frontend" would be more suitable
*/
class FrontendRoutingListener implements EventSubscriberInterface
{
use PimcoreContextAwareTrait;
/**
* @var RequestHelper
*/
protected $requestHelper;
/**
* @var RedirectHandler
*/
protected $redirectHandler;
/**
* @var SiteResolver
*/
protected $siteResolver;
/**
* @param RequestHelper $requestHelper
* @param RedirectHandler $redirectHandler
* @param SiteResolver $siteResolver
*/
public function __construct(
RequestHelper $requestHelper,
RedirectHandler $redirectHandler,
SiteResolver $siteResolver
) {
$this->requestHelper = $requestHelper;
$this->redirectHandler = $redirectHandler;
$this->siteResolver = $siteResolver;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
// run with high priority as we need to set the site early
KernelEvents::REQUEST => ['onKernelRequest', 512],
// run with high priority before handling real errors
KernelEvents::EXCEPTION => ['onKernelException', 64]
];
}
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
// handle main domain redirect in admin context
if ($this->matchesPimcoreContext($request, PimcoreContextResolver::CONTEXT_ADMIN)) {
$this->handleMainDomainRedirect($event, true);
return;
}
if (!$this->matchesPimcoreContext($request, PimcoreContextResolver::CONTEXT_DEFAULT)) {
return;
}
$path = urldecode($request->getPathInfo());
// resolve current site from request
$this->resolveSite($request, $path);
// check for override redirects
$response = $this->redirectHandler->checkForRedirect($request, true);
if ($response) {
$event->setResponse($response);
return;
}
// check for app.php in URL and remove it for SEO puroposes
$this->handleFrontControllerRedirect($event, $path);
if ($event->hasResponse()) {
return;
}
// redirect to the main domain if specified
$this->handleMainDomainRedirect($event);
if ($event->hasResponse()) {
return;
}
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
// in case routing didn't find a matching route, check for redirects without override
$exception = $event->getException();
if ($exception instanceof NotFoundHttpException) {
$response = $this->redirectHandler->checkForRedirect($event->getRequest(), false);
if ($response) {
$event->setResponse($response);
}
}
}
/**
* Initialize Site
*
* @param Request $request
* @param $path
*
* @return string
*/
protected function resolveSite(Request $request, $path)
{
// check for a registered site
// do not initialize a site if it is a "special" admin request
if (!$this->requestHelper->isFrontendRequestByAdmin($request)) {
// host name without port incl. X-Forwarded-For handling for trusted proxies
$host = $request->getHost();
try {
$site = Site::getByDomain($host);
$path = $site->getRootPath() . $path;
Site::setCurrentSite($site);
$this->siteResolver->setSite($request, $site);
$this->siteResolver->setSitePath($request, $path);
} catch (\Exception $e) {
// noop - execption is logged in getByDomain
}
}
return $path;
}
/**
* @param GetResponseEvent $event
* @param string $path
*/
protected function handleFrontControllerRedirect(GetResponseEvent $event, $path)
{
$request = $event->getRequest();
// do not allow requests including /app.php/ => SEO
// this is after the first redirect check, to allow redirects in app.php?xxx
if (preg_match('@^/app.php(.*)@', $path, $matches) && $request->getMethod() === 'GET') {
$redirectUrl = $matches[1];
$redirectUrl = ltrim($redirectUrl, '/');
$redirectUrl = '/' . $redirectUrl;
$event->setResponse(new RedirectResponse($redirectUrl, Response::HTTP_MOVED_PERMANENTLY));
}
}
/**
* Redirect to the main domain if specified
*
* @param GetResponseEvent $event
* @param bool $adminContext
*/
protected function handleMainDomainRedirect(GetResponseEvent $event, bool $adminContext = false)
{
$request = $event->getRequest();
$config = Config::getSystemConfig();
$hostRedirect = null;
if ($adminContext) {
$hostRedirect = $this->resolveConfigDomainRedirectHost($config, $request);
} else {
if (Site::isSiteRequest()) {
$site = Site::getCurrentSite();
if ($site->getRedirectToMainDomain() && $site->getMainDomain() != $request->getHost()) {
$hostRedirect = $site->getMainDomain();
}
} else {
if (!$this->requestHelper->isFrontendRequestByAdmin()) {
$hostRedirect = $this->resolveConfigDomainRedirectHost($config, $request);
}
}
}
if ($hostRedirect && !$request->query->has('pimcore_disable_host_redirect')) {
$qs = '';
if (null !== $qs = $request->getQueryString()) {
$qs = '?' . $qs;
}
$url = $request->getScheme() . '://' . $hostRedirect . $request->getBaseUrl() . $request->getPathInfo() . $qs;
// TODO use symfony logger service
// log all redirects to the redirect log
\Pimcore\Log\Simple::log('redirect', Tool::getAnonymizedClientIp() . " \t Host-Redirect Source: " . $request->getRequestUri() . ' -> ' . $url);
$event->setResponse(new RedirectResponse($url, Response::HTTP_MOVED_PERMANENTLY));
}
}
private function resolveConfigDomainRedirectHost(Config\Config $config, Request $request)
{
$hostRedirect = null;
$gc = $config->general;
if ($gc->redirect_to_maindomain && $gc->domain && $gc->domain !== $request->getHost()) {
$hostRedirect = $config->general->domain;
}
return $hostRedirect;
}
}