6

There's this module Splash Redirect in which src/EventSubscriber/SplashRedirectEventSubscriber.php is supposed to check if the cookie $config_cookie exists or not, if it already exists then the user continues onto the requested node ($config_source), but if it does not exist then the user is sent to $config_destination and it creates the cookie ($config_cookie).

It succeeds in creating the $config_cookie and the redirect to $config_destination but it never seems to acknowledge the existence of the cookie once created and continues to redirect the user to $config_destination.

Any clues? On Drupal 8.6

<?php

namespace Drupal\splash_redirect\EventSubscriber;

use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Drupal\Core\Routing\TrustedRedirectResponse;

/**
 * Splash redirect Event Subscriber.
 */
class SplashRedirectEventSubscriber implements EventSubscriberInterface {

  /**
   * Triggered when system sends response.
   */
  public function modifyIntercept(GetResponseEvent $event) {
    $config = \Drupal::config('splash_redirect.settings');
    $config_enabled = $config->get('splash_redirect.is_enabled');
    $config_source = $config->get('splash_redirect.source');
    $config_destination = $config->get('splash_redirect.destination');
    $config_cookie = $config->get('splash_redirect.cookie_name');
    $config_duration = $config->get('splash_redirect.duration');

    // If splash config is not enabled then we don't need to do any of this.
    if ($config_enabled == 1) {
      // Current request from client.
      $request = \Drupal::request();
      $current_uri = $request->getRequestUri();
      $http_host = $request->getHost();
      // Current response from system.
      $response = $event->getResponse();
      $route = (\Drupal::routeMatch()->getParameter('node')) ? \Drupal::routeMatch()->getParameter('node')->id() : null;
      $has_cookie = $request->cookies->has($config_cookie);

      // If splash-cookie has not been set, and user requesting 'source' page,
      // set cookie and redirect to splash page.
      if ($config_source == $route) {
        // Kill cache on this route or else cookie might not be read with VCL.
        \Drupal::service('page_cache_kill_switch')->trigger();

        if (!$has_cookie) {
          // Set redirect response with cookie and redirect location.
          $redir = new TrustedRedirectResponse($config_destination, '302');
          $cookie = new Cookie($config_cookie, 'true', strtotime('now + ' . $config_duration . 'days'), '/', '.' . $http_host, false, true);
          $redir->headers->setCookie($cookie);
          $redir->headers->set('Cache-Control', 'public, max-age=0');
          $redir->addCacheableDependency($config_destination);
          $event->setResponse($redir);
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    // Listen for response event from system and intercept.
    $events[KernelEvents::REQUEST][] = ['modifyIntercept'];
    return $events;
  }

}
8
  • 1
    Did you check that the cookie exists in your browser's "inspect element/inspect" window? If it's not there you might wanna check if your browser settings has allow sites to set & read cookie data.
    – No Sssweat
    Commented Dec 31, 2018 at 6:51
  • 1
    @NoSssweat Yes the cookie is created and exists (via the inspect window). I have a suspicion now that this is due to the fact that the cookie that is successfully set is not being successfully read - perhaps due to this answer: stackoverflow.com/questions/8432281/symfony2-read-cookie that says reading a cookie needs to be done in a Request... but I don't know how to implement that here to test. Commented Dec 31, 2018 at 6:56
  • 1
    Yeah, cookie is the way to go here. If these are nodes, you could try doing the redirect in hook_node_view
    – No Sssweat
    Commented Dec 31, 2018 at 7:43
  • 1
    This is really close to working - everything works up to the point where I need to verify the cookie that was successfully set. I don't think I need to run down another path just yet. Commented Dec 31, 2018 at 7:51
  • 1
    ok, try retrieving the cookie like this and see if that makes any diff.
    – No Sssweat
    Commented Dec 31, 2018 at 7:52

2 Answers 2

7

Some ideas:

Like for the config you have to add a cache dependency for the cookie, too:

  $response->getCacheableMetadata()->addCacheContexts(['cookies:' . $config_cookie]);

Or use an uncacheable RedirectResponse to simplify things.

And try to set a priority for the event subscriber, for example 31, because your code depends only on routing, which has 32:

  $events[KernelEvents::REQUEST][] = ['modifyIntercept', 31];

You don't need to get a response in an request subscriber, because there isn't one yet. Get the request instead and make sure that you are in a master request. So this would be how the subscriber starts most times:

  public function onKernelRequest(GetResponseEvent $event) {
    if (!$event->isMasterRequest()) {
      return;
    }
    $request = $event->getRequest();
    ...

Simplifying a bit more and using dummy values for testing:

  public function onKernelRequest(GetResponseEvent $event) {
    if (!$event->isMasterRequest()) {
      return;
    }
    $route_match = \Drupal::routeMatch();
    if (!$route_match->getRouteName() == 'entity.node.canonical') {
      return;
    }
    $request = $event->getRequest();
    if ($route_match->getRawParameter('node') == '1') {
      \Drupal::service('page_cache_kill_switch')->trigger();
      if (!$request->cookies->has('foo')) {
        $redirect = new RedirectResponse($request->getBasePath() . '/node/2', 302);
        $redirect->headers->setCookie(new Cookie('foo', '123', '+30 seconds'));
        $event->setResponse($redirect);
      }
    }
  }

This is working on my local dev environment.


Complete code (including dependency injection and code updated to D9/10):

/src/EventSubscriber/MymoduleSubscriber.php

<?php

namespace Drupal\mymodule\EventSubscriber;

use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Cookie;

/**
 * mymodule event subscriber.
 */
class MymoduleSubscriber implements EventSubscriberInterface {

  /**
   * The route match.
   *
   * @var \Drupal\Core\Routing\RouteMatchInterface
   */
  protected $routeMatch;

  /**
   * Constructs event subscriber.
   *
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
   *   The route match.
   */
  public function __construct(RouteMatchInterface $route_match) {
    $this->routeMatch = $route_match;
  }

  /**
   * Kernel request event handler.
   *
   * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
   *   The request event.
   */
  public function onKernelRequest(RequestEvent $event) {
    if (!$event->isMainRequest()) {
      return;
    }
    if (!$this->routeMatch->getRouteName() == 'entity.node.canonical') {
      return;
    }
    $request = $event->getRequest();
    if ($this->routeMatch->getRawParameter('node') == '1') {
      \Drupal::service('page_cache_kill_switch')->trigger();
      if (!$request->cookies->has('foo')) {
        $redirect = new RedirectResponse($request->getBasePath() .'/node/2', 302);
        $redirect->headers->setCookie(new Cookie('foo', '123', '+30 seconds'));
        $event->setResponse($redirect);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      KernelEvents::REQUEST => ['onKernelRequest', 31],
    ];
  }

}
   

mymodule.services.yml

services:
  mymodule.event_subscriber:
    class: Drupal\mymodule\EventSubscriber\MymoduleSubscriber
    arguments: ['@current_route_match']
    tags:
      - { name: event_subscriber }
4
  • Dumped in your code above - thank you for this. I'm still getting a redirect on every request for node/1. Is it possible to get your complete working code from the top so I can test that? Just to get all the moving parts - eg: are you using Symfony\Component\HttpFoundation\RedirectResponse;? Commented Jan 1, 2019 at 9:16
  • I've transferred the code to another Drupal site in a new module and it is working there as well, see the complete code sample.
    – 4uk4
    Commented Jan 1, 2019 at 10:20
  • 2
    Amazing, thanks for the answer. I'm marking this as answered since you have it working on 2 environments. However my environment is still having issues and forwarding me persistently to the redirect (node/2 in your modules case) despite the cookie successfully showing up in my dev tools. I'm on a pantheon.io environment on a fresh install. Maybe it is a pantheon.io / lando problem? PHP 7.2? I can't work it out.. I've tried on 3 browsers across 2 machines. I'll have to try debug the environment I guess. Commented Jan 1, 2019 at 18:38
  • 1
    Confirming this now works in my environment as long as cookie name is prefixed with SESS on Pantheon/Lando environment - eg SESSfoo. This code is tight. Thanks again. Commented Jan 1, 2019 at 22:14
4

Original splash_redirect module dev here. Thanks for joining me in the caching rabbit hole! (Sorry, can't make 'comments' yet so I have to put this in an answer)

Thanks to @4k4 for the great suggestions, I will likely implement some of those in the next release, but OP's issue looks to be specific to Pantheon's global CDN, which sets the Vary: cookie header, and caches the source redirect.

*So, if you're using splash_redirect on Pantheon, please prefix your Splash Cookie name with "SESS", e.g. SESSsplash *

1
  • 2
    Wow. That you @AdamB. - Can also confirm you cannot have underscores in the cookie on Pantheon. It should be SESScookiename not SESScookie_name. Both this module and splash_redirect module now work as expected. THANK YOU BOTH! Glad also that module maintainer for splash_redirect can pull some ideas from stellar work by @4k4. Commented Jan 1, 2019 at 19:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.