In contrast to other performAction() implementations the one in RabbitHoleBehaviorPlugin::PageRedirect is very large and combines several aims. It mixes the determination of the $response_code, the $target AND the response creation.

On the one hand this leads to a quite long and monolithic function, on the other hand it prevents extending modules from getting the resulting $target URL object and $status_code independently from the finale response.

So what I'd suggest is to split the function into:

  • performAction(EntityInterface $entity, Response $current_response = NULL) which calls and orchestrates the submethods
  • getActionTarget(EntityInterface $entity) which returns the target with parent entity fallback, language handling, token replacement etc.
  • getActionResponseCode(EntityInterface $entity) which returns the response code with parent entity fallback, etc.

One might discuss if only getActionTarget() is required, but getActionTarget() makes a lot of sense because of the additional logic for token replancement and language handling.

If the idea is OK for you, I'll create a patch.

Concretely I need this method in rabbit_hole_href and had to copy half of the function code so far, which is dirty, but the only possible way so far.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Anybody created an issue. See original summary.

Anybody’s picture

This is the code I'm talking about:

/**
   * {@inheritdoc}
   */
  public function performAction(EntityInterface $entity, Response $current_response = NULL) {
    $target = $entity->get('rh_redirect')->value;
    $response_code = NULL;

    $bundle_entity_type = $entity->getEntityType()->getBundleEntityType();
    $bundle_settings = $this->rhBehaviorSettingsManager
      ->loadBehaviorSettingsAsConfig(
        $bundle_entity_type ?: $entity->getEntityType()->id(),
        $bundle_entity_type ? $entity->bundle() : NULL);

    if (empty($target)) {
      $target = $bundle_settings->get('redirect');
      $response_code = $bundle_settings->get('redirect_code');
    }
    else {
      $response_code = $entity->get('rh_redirect_response')->value;
    }

    // Replace any tokens if applicable.
    $langcode = $entity->language()->getId();

    if ($langcode == LanguageInterface::LANGCODE_NOT_APPLICABLE) {
      $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
    }

    if ($this->moduleHandler->moduleExists('token')) {
      $target = $this->token->replace($target,
        [
          $entity->getEntityTypeId() => $entity,
        ],
        [
          'clear' => TRUE,
          'langcode' => $langcode,
        ], new BubbleableMetadata()
      );
    }

    if ($target === '<front>' || $target === '/<front>') {
      // Special case for redirecting to the front page.
      $target = \Drupal::service('url_generator')->generateFromRoute('<front>', [], []);
    }

    // If non-absolute URI, pass URL through Drupal's URL generator to
    // handle languages etc.
    if (!UrlHelper::isExternal($target)) {
      $target = Url::fromUserInput($target)->toString();
    }

    switch ($response_code) {
      case self::REDIRECT_MOVED_PERMANENTLY:
      case self::REDIRECT_FOUND:
      case self::REDIRECT_SEE_OTHER:
      case self::REDIRECT_TEMPORARY_REDIRECT:
        if ($current_response === NULL) {
          return new TrustedRedirectResponse($target, $response_code);
        }
        else {
          // If a response already exists we don't need to do anything with it.
          return $current_response;
        }
        // TODO: I don't think this is the correct way to handle a 304 response.
      case self::REDIRECT_NOT_MODIFIED:
        if ($current_response === NULL) {
          $not_modified_response = new Response();
          $not_modified_response->setStatusCode(self::REDIRECT_NOT_MODIFIED);
          $not_modified_response->headers->set('Location', $target);
          return $not_modified_response;
        }
        else {
          // If a response already exists we don't need to do anything with it.
          return $current_response;
        }
        // TODO: I have no idea if this is actually the correct way to handle a
        // 305 response in Symfony/D8. Documentation on it seems a bit sparse.
      case self::REDIRECT_USE_PROXY:
        if ($current_response === NULL) {
          $use_proxy_response = new Response();
          $use_proxy_response->setStatusCode(self::REDIRECT_USE_PROXY);
          $use_proxy_response->headers->set('Location', $target);
          return $use_proxy_response;
        }
        else {
          // If a response already exists we don't need to do anything with it.
          return $current_response;
        }
      default:
        throw new InvalidRedirectResponseException();
    }
  }

Anybody’s picture

Status: Active » Needs review
FileSize
5.71 KB

Patch attached, please have a look.

Anybody’s picture

FileSize
5.89 KB

There was a ->toString() that had to be removed in

    if (!UrlHelper::isExternal($target)) {
      $target = Url::fromUserInput($target)->toString();
    }

to always return an URL object consistently.

Anybody’s picture

The ->toString() had to be readded on the call to make things work like before:

$target = $this->getActionTarget($entity)->toString();

Everything seems fine to me now. Please review!

Anybody’s picture

Any chance for a maintainer review here?

thomas.frobieter’s picture

Status: Needs review » Reviewed & tested by the community

We're using this patch #5 successfully since months now. +1 RTBC

Anybody’s picture

@Dylan Donkersgoed: Could you perhaps have a look? It would be very helpful to have this large function more atomic for extending contrib modules. This prevents us from copying a lot of code.

Thanks a lot in advance.

Anybody’s picture

I can once more confirm this works well and without any problems. Any chance for a maintainer to have a look? This would really help to allow better reuse and additional modules built on this like described above. Thanks a lot!

Matroskeen’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
6.42 KB

We have a bunch of new changes in dev branch, so here is a re-roll.
Let's see tests results.

  • Matroskeen committed e96fbdd on 8.x-1.x
    Issue #3095653 by Anybody, Matroskeen: Split performAction() into...
Matroskeen’s picture

Status: Needs review » Fixed

Tests are OK with those changes... so do I :)
Committed to 8.x-1.x, will be available in the next release.
Thanks!

Anybody’s picture

Thank you very much! I'm very much looking forward to the next release which will make https://www.drupal.org/project/rabbit_hole_href compatible without patches!

Is there an estimation already when it will be released?

Matroskeen’s picture

The plan is the following:

  1. cut a new beta release in 1-2 weeks (when we sort out the existing bugs);
  2. create the first RC in ~1 month after the next beta;
Anybody’s picture

@Matroskeen thank you very much, that sounds great! :)

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.