/** * Gets a redirect for given path, query and language. * * @param string $source_path * The redirect source path. * @param array $query * The redirect source path query. * @param $language * The language for which is the redirect. * * @return \Drupal\redirect\Entity\Redirect * The matched redirect entity. * * @throws \Drupal\redirect\Exception\RedirectLoopException */ public function findMatchingRedirect($source_path, array $query = [], $language = Language::LANGCODE_NOT_SPECIFIED) { $hashes = [Redirect::generateHash($source_path, $query, $language)]; if ($language != Language::LANGCODE_NOT_SPECIFIED) { $hashes[] = Redirect::generateHash($source_path, $query, Language::LANGCODE_NOT_SPECIFIED); } // 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); if ($language != Language::LANGCODE_NOT_SPECIFIED) { $hashes[] = Redirect::generateHash($source_path, [], Language::LANGCODE_NOT_SPECIFIED); } } // Load redirects by hash. A direct query is used to improve performance. $rid = $this->connection->query('SELECT rid FROM {redirect} WHERE hash IN (:hashes[]) ORDER BY LENGTH(redirect_source__query) DESC', [':hashes[]' => $hashes])->fetchField(); if (!empty($rid)) { // Check if this is a loop. if (in_array($rid, $this->foundRedirects)) { throw new RedirectLoopException('/' . $source_path, $rid); } $this->foundRedirects[] = $rid; $redirect = $this->load($rid); // Find chained redirects. if ($recursive = $this->findByRedirect($redirect, $language)) { // Reset found redirects. $this->foundRedirects = []; return $recursive; } return $redirect; } return NULL; }
/** * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage_controller) { $this->set('hash', Redirect::generateHash($this->redirect_source->path, (array) $this->redirect_source->query, $this->language()->getId())); }
/** * Test redirect entity logic. */ public function testRedirectEntity() { // Create a redirect and test if hash has been generated correctly. /** @var \Drupal\redirect\Entity\Redirect $redirect */ $redirect = $this->controller->create(); $redirect->setSource('some-url', array('key' => 'val')); $redirect->setRedirect('node'); $redirect->save(); $this->assertEqual(Redirect::generateHash('some-url', array('key' => 'val'), Language::LANGCODE_NOT_SPECIFIED), $redirect->getHash()); // Update the redirect source query and check if hash has been updated as // expected. $redirect->setSource('some-url', array('key1' => 'val1')); $redirect->save(); $this->assertEqual(Redirect::generateHash('some-url', array('key1' => 'val1'), Language::LANGCODE_NOT_SPECIFIED), $redirect->getHash()); // Update the redirect source path and check if hash has been updated as // expected. $redirect->setSource('another-url', array('key1' => 'val1')); $redirect->save(); $this->assertEqual(Redirect::generateHash('another-url', array('key1' => 'val1'), Language::LANGCODE_NOT_SPECIFIED), $redirect->getHash()); // Update the redirect language and check if hash has been updated as // expected. $redirect->setLanguage('de'); $redirect->save(); $this->assertEqual(Redirect::generateHash('another-url', array('key1' => 'val1'), 'de'), $redirect->getHash()); // Create a few more redirects to test the select. for ($i = 0; $i < 5; $i++) { $redirect = $this->controller->create(); $redirect->setSource($this->randomMachineName()); $redirect->save(); } /** @var \Drupal\redirect\RedirectRepository $repository */ $repository = \Drupal::service('redirect.repository'); $redirect = $repository->findMatchingRedirect('another-url', array('key1' => 'val1'), 'de'); if (!empty($redirect)) { $this->assertEqual($redirect->getSourceUrl(), '/another-url?key1=val1'); } else { $this->fail(t('Failed to find matching redirect.')); } // Load the redirect based on url. $redirects = $repository->findBySourcePath('another-url'); $redirect = array_shift($redirects); if (!empty($redirect)) { $this->assertEqual($redirect->getSourceUrl(), '/another-url?key1=val1'); } else { $this->fail(t('Failed to find redirect by source path.')); } // Test passthrough_querystring. $redirect = $this->controller->create(); $redirect->setSource('a-different-url'); $redirect->setRedirect('node'); $redirect->save(); $redirect = $repository->findMatchingRedirect('a-different-url', ['key1' => 'val1'], 'de'); if (!empty($redirect)) { $this->assertEqual($redirect->getSourceUrl(), '/a-different-url'); } else { $this->fail('Failed to find redirect by source path with query string.'); } // Add another redirect to the same path, with a query. This should always // be found before the source without a query set. /** @var \Drupal\redirect\Entity\Redirect $new_redirect */ $new_redirect = $this->controller->create(); $new_redirect->setSource('a-different-url', ['foo' => 'bar']); $new_redirect->setRedirect('node'); $new_redirect->save(); $found = $repository->findMatchingRedirect('a-different-url', ['foo' => 'bar'], 'de'); if (!empty($found)) { $this->assertEqual($found->getSourceUrl(), '/a-different-url?foo=bar'); } else { $this->fail('Failed to find a redirect by source path with query string.'); } // Hashes should be case-insensitive since the source paths are. /** @var \Drupal\redirect\Entity\Redirect $redirect */ $redirect = $this->controller->create(); $redirect->setSource('Case-Sensitive-Path'); $redirect->setRedirect('node'); $redirect->save(); $found = $repository->findBySourcePath('case-sensitive-path'); if (!empty($found)) { $found = reset($found); $this->assertEqual($found->getSourceUrl(), '/Case-Sensitive-Path'); } else { $this->fail('findBySourcePath is case sensitive'); } $found = $repository->findMatchingRedirect('case-sensitive-path'); if (!empty($found)) { $this->assertEqual($found->getSourceUrl(), '/Case-Sensitive-Path'); } else { $this->fail('findMatchingRedirect is case sensitive.'); } }
/** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); $source = $form_state->getValue(array('redirect_source', 0)); $redirect = $form_state->getValue(array('redirect_redirect', 0)); if ($source['path'] == '<front>') { $form_state->setErrorByName('redirect_source', t('It is not allowed to create a redirect from the front page.')); } if (strpos($source['path'], '#') !== FALSE) { $form_state->setErrorByName('redirect_source', t('The anchor fragments are not allowed.')); } if (strpos($source['path'], '/') === 0) { $form_state->setErrorByName('redirect_source', t('The url to redirect from should not start with a forward slash (/).')); } try { $source_url = Url::fromUri('internal:/' . $source['path']); $redirect_url = Url::fromUri($redirect['uri']); // It is relevant to do this comparison only in case the source path has // a valid route. Otherwise the validation will fail on the redirect path // being an invalid route. if ($source_url->toString() == $redirect_url->toString()) { $form_state->setErrorByName('redirect_redirect', t('You are attempting to redirect the page to itself. This will result in an infinite loop.')); } } catch (\InvalidArgumentException $e) { // 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(array('hash' => $hash)); if (!empty($redirects)) { $redirect = array_shift($redirects); if ($this->entity->isNew() || $redirect->id() != $this->entity->id()) { $form_state->setErrorByName('redirect_source', t('The source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => $source['path'], '@edit-page' => $redirect->url('edit-form')))); } } }