3 namespace Drupal\redirect\EventSubscriber;
5 use Drupal\Core\Cache\CacheableMetadata;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Extension\ModuleHandlerInterface;
9 use Drupal\Core\Language\LanguageManagerInterface;
10 use Drupal\Core\Logger\RfcLogLevel;
11 use Drupal\Core\Path\AliasManagerInterface;
12 use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
13 use Drupal\Core\Routing\TrustedRedirectResponse;
15 use Drupal\redirect\Exception\RedirectLoopException;
16 use Drupal\redirect\RedirectChecker;
17 use Drupal\redirect\RedirectRepository;
18 use Symfony\Component\HttpFoundation\Response;
19 use Symfony\Component\HttpKernel\HttpKernelInterface;
20 use Symfony\Component\HttpKernel\KernelEvents;
21 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
22 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
23 use Symfony\Component\Routing\RequestContext;
26 * Redirect subscriber for controller requests.
28 class RedirectRequestSubscriber implements EventSubscriberInterface {
30 /** @var \Drupal\redirect\RedirectRepository */
31 protected $redirectRepository;
34 * @var \Drupal\Core\Language\LanguageManagerInterface
36 protected $languageManager;
39 * @var \Drupal\Core\Config\Config
44 * @var \Drupal\Core\Path\AliasManager
46 protected $aliasManager;
49 * @var \Drupal\Core\Extension\ModuleHandlerInterface
51 protected $moduleHandler;
54 * @var \Drupal\Core\Entity\EntityManagerInterface
56 protected $entityManager;
59 * @var \Drupal\redirect\RedirectChecker
64 * @var \Symfony\Component\Routing\RequestContext
69 * A path processor manager for resolving the system path.
71 * @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
73 protected $pathProcessor;
76 * Constructs a \Drupal\redirect\EventSubscriber\RedirectRequestSubscriber object.
78 * @param \Drupal\redirect\RedirectRepository $redirect_repository
79 * The redirect entity repository.
80 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
81 * The language manager service.
82 * @param \Drupal\Core\Config\ConfigFactoryInterface $config
84 * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
85 * The alias manager service.
86 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
87 * The module handler service.
88 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
89 * The entity manager service.
90 * @param \Drupal\redirect\RedirectChecker $checker
91 * The redirect checker service.
92 * @param \Symfony\Component\Routing\RequestContext
95 public function __construct(RedirectRepository $redirect_repository, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config, AliasManagerInterface $alias_manager, ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager, RedirectChecker $checker, RequestContext $context, InboundPathProcessorInterface $path_processor) {
96 $this->redirectRepository = $redirect_repository;
97 $this->languageManager = $language_manager;
98 $this->config = $config->get('redirect.settings');
99 $this->aliasManager = $alias_manager;
100 $this->moduleHandler = $module_handler;
101 $this->entityManager = $entity_manager;
102 $this->checker = $checker;
103 $this->context = $context;
104 $this->pathProcessor = $path_processor;
108 * Handles the redirect if any found.
110 * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
111 * The event to process.
113 public function onKernelRequestCheckRedirect(GetResponseEvent $event) {
114 // Get a clone of the request. During inbound processing the request
115 // can be altered. Allowing this here can lead to unexpected behavior.
116 // For example the path_processor.files inbound processor provided by
117 // the system module alters both the path and the request; only the
118 // changes to the request will be propagated, while the change to the
119 // path will be lost.
120 $request = clone $event->getRequest();
122 if (!$this->checker->canRedirect($request)) {
126 // Get URL info and process it to be used for hash generation.
127 parse_str($request->getQueryString(), $request_query);
129 if (strpos($request->getPathInfo(), '/system/files/') === 0 && !$request->query->has('file')) {
130 // Private files paths are split by the inbound path processor and the
131 // relative file path is moved to the 'file' query string parameter. This
132 // is because the route system does not allow an arbitrary amount of
133 // parameters. We preserve the path as is returned by the request object.
134 // @see \Drupal\system\PathProcessor\PathProcessorFiles::processInbound()
135 $path = $request->getPathInfo();
138 // Do the inbound processing so that for example language prefixes are
140 $path = $this->pathProcessor->processInbound($request->getPathInfo(), $request);
142 $path = trim($path, '/');
144 $this->context->fromRequest($request);
147 $redirect = $this->redirectRepository->findMatchingRedirect($path, $request_query, $this->languageManager->getCurrentLanguage()->getId());
149 catch (RedirectLoopException $e) {
150 \Drupal::logger('redirect')->warning($e->getMessage());
151 $response = new Response();
152 $response->setStatusCode(503);
153 $response->setContent('Service unavailable');
154 $event->setResponse($response);
158 if (!empty($redirect)) {
160 // Handle internal path.
161 $url = $redirect->getRedirectUrl();
162 if ($this->config->get('passthrough_querystring')) {
163 $url->setOption('query', (array) $url->getOption('query') + $request_query);
166 'X-Redirect-ID' => $redirect->id(),
168 $response = new TrustedRedirectResponse($url->setAbsolute()->toString(), $redirect->getStatusCode(), $headers);
169 $response->addCacheableDependency($redirect);
170 $event->setResponse($response);
175 * Prior to set the response it check if we can redirect.
177 * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
179 * @param \Drupal\Core\Url $url
180 * The Url where we want to redirect.
182 protected function setResponse(GetResponseEvent $event, Url $url) {
183 $request = $event->getRequest();
184 $this->context->fromRequest($request);
186 parse_str($request->getQueryString(), $query);
187 $url->setOption('query', $query);
188 $url->setAbsolute(TRUE);
190 // We can only check access for routed URLs.
191 if (!$url->isRouted() || $this->checker->canRedirect($request, $url->getRouteName())) {
192 // Add the 'rendered' cache tag, so that we can invalidate all responses
193 // when settings are changed.
194 $response = new TrustedRedirectResponse($url->toString(), 301);
195 $response->addCacheableDependency(CacheableMetadata::createFromRenderArray([])->addCacheTags(['rendered']));
196 $event->setResponse($response);
203 public static function getSubscribedEvents() {
204 // This needs to run before RouterListener::onKernelRequest(), which has
205 // a priority of 32. Otherwise, that aborts the request if no matching
207 $events[KernelEvents::REQUEST][] = array('onKernelRequestCheckRedirect', 33);