diff --git a/src/Entity/Redirect.php b/src/Entity/Redirect.php index c1796c1..e6ad36c 100644 --- a/src/Entity/Redirect.php +++ b/src/Entity/Redirect.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Language\LanguageInterface; use Drupal\link\LinkItemInterface; /** @@ -85,9 +86,17 @@ class Redirect extends ContentEntityBase { * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage_controller) { - // Get the language code directly from the field as language() might not - // be up to date if the language was just changed. - $this->set('hash', Redirect::generateHash($this->redirect_source->path, (array) $this->redirect_source->query, $this->get('language')->value)); + $path = $this->redirect_source->path; + + // Get the language code directly from the field as language() might not be + // up to date if the language was just changed. + $redirect_language = $this->get('language')->value; + + // Pass the path through the alias manager to convert it to the the alias's + // source. If it is not an alias the original value will be returned. + $path = ltrim(\Drupal::service('path.alias_manager')->getPathByAlias('/' . $path, $redirect_language), '/'); + + $this->set('hash', Redirect::generateHash($path, (array) $this->redirect_source->query, $redirect_language)); } /** diff --git a/src/EventSubscriber/RedirectRequestSubscriber.php b/src/EventSubscriber/RedirectRequestSubscriber.php index cca4ac0..e83f9ea 100644 --- a/src/EventSubscriber/RedirectRequestSubscriber.php +++ b/src/EventSubscriber/RedirectRequestSubscriber.php @@ -162,6 +162,17 @@ class RedirectRequestSubscriber implements EventSubscriberInterface { if ($this->config->get('passthrough_querystring')) { $url->setOption('query', (array) $url->getOption('query') + $request_query); } + + // Redirects to path aliases are stored and searched for using the alias's + // source (/node/123 vs /foo-bar-path). This can cause redirect loops + // because the path processor above may have converted a path alias to a + // path source (/foo-bar-path => /node/123) which will trigger a redirect + // on the path's valid alias. + $redirect_url = $url->setAbsolute()->toString(); + if ($redirect_url === ($request->getSchemeAndHttpHost() . $request->getRequestUri())) { + return; + } + $headers = [ 'X-Redirect-ID' => $redirect->id(), ]; diff --git a/src/Form/RedirectForm.php b/src/Form/RedirectForm.php index 0c9eafd..05195f1 100644 --- a/src/Form/RedirectForm.php +++ b/src/Form/RedirectForm.php @@ -120,18 +120,14 @@ class RedirectForm extends ContentEntityForm { // Do nothing, we want to only compare the resulting URLs. } - $parsed_url = UrlHelper::parse(trim($source['path'])); - $path = isset($parsed_url['path']) ? $parsed_url['path'] : NULL; - $query = isset($parsed_url['query']) ? $parsed_url['query'] : NULL; - $hash = Redirect::generateHash($path, $query, $form_state->getValue('language')[0]['value']); - // Search for duplicate. - $redirects = \Drupal::entityManager() - ->getStorage('redirect') - ->loadByProperties(['hash' => $hash]); + $parsed_url = UrlHelper::parse(trim($source['path'])); + $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; + $query = isset($parsed_url['query']) ? $parsed_url['query'] : []; + $language = $form_state->getValue('language')[0]['value']; + $redirect = \Drupal::service('redirect.repository')->findMatchingRedirect($path, $query, $language); - if (!empty($redirects)) { - $redirect = array_shift($redirects); + if (!empty($redirect)) { if ($this->entity->isNew() || $redirect->id() != $this->entity->id()) { $form_state->setErrorByName('redirect_source', $this->t('The source path %source is already being redirected. Do you want to edit the existing redirect?', [ diff --git a/src/RedirectRepository.php b/src/RedirectRepository.php index 236c927..42311a4 100644 --- a/src/RedirectRepository.php +++ b/src/RedirectRepository.php @@ -6,6 +6,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageInterface; use Drupal\redirect\Entity\Redirect; use Drupal\redirect\Exception\RedirectLoopException; @@ -69,6 +70,15 @@ class RedirectRepository { $hashes[] = Redirect::generateHash($source_path, $query, Language::LANGCODE_NOT_SPECIFIED); } + // The source path might be an alias, so add the source for the alias if it + // is. The alias manager will return the source path as is if it's not an + // alias, which will lead to a duplicate it in the hashes array, deal with + // that too. + $alias_language = ($language == Language::LANGCODE_NOT_SPECIFIED) ? NULL : $language; + $alias_source = ltrim(\Drupal::service('path.alias_manager')->getPathByAlias('/' . $source_path, $alias_language), '/'); + $hashes[] = Redirect::generateHash($alias_source, $query, Language::LANGCODE_NOT_SPECIFIED); + $hashes = array_unique($hashes); + // Add a hash without the query string if using passthrough querystrings. if (!empty($query) && $this->config->get('passthrough_querystring')) { $hashes[] = Redirect::generateHash($source_path, [], $language);