3 namespace Drupal\redirect;
5 use Drupal\Core\Config\ConfigFactoryInterface;
6 use Drupal\Core\Database\Connection;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Language\Language;
9 use Drupal\redirect\Entity\Redirect;
10 use Drupal\redirect\Exception\RedirectLoopException;
12 class RedirectRepository {
15 * @var \Drupal\Core\Entity\EntityManagerInterface
20 * @var \Drupal\Core\Database\Connection
22 protected $connection;
25 * @var \Drupal\Core\Config\ImmutableConfig
30 * An array of found redirect IDs to avoid recursion.
34 protected $foundRedirects = [];
37 * Constructs a \Drupal\redirect\EventSubscriber\RedirectRequestSubscriber object.
39 * @param \Drupal\Core\Entity\EntityManagerInterface $manager
40 * The entity manager service.
41 * @param \Drupal\Core\Database\Connection $connection
42 * The database connection.
44 public function __construct(EntityManagerInterface $manager, Connection $connection, ConfigFactoryInterface $config_factory) {
45 $this->manager = $manager;
46 $this->connection = $connection;
47 $this->config = $config_factory->get('redirect.settings');
51 * Gets a redirect for given path, query and language.
53 * @param string $source_path
54 * The redirect source path.
56 * The redirect source path query.
58 * The language for which is the redirect.
60 * @return \Drupal\redirect\Entity\Redirect
61 * The matched redirect entity.
63 * @throws \Drupal\redirect\Exception\RedirectLoopException
65 public function findMatchingRedirect($source_path, array $query = [], $language = Language::LANGCODE_NOT_SPECIFIED) {
66 $source_path = ltrim($source_path, '/');
67 $hashes = [Redirect::generateHash($source_path, $query, $language)];
68 if ($language != Language::LANGCODE_NOT_SPECIFIED) {
69 $hashes[] = Redirect::generateHash($source_path, $query, Language::LANGCODE_NOT_SPECIFIED);
72 // Add a hash without the query string if using passthrough querystrings.
73 if (!empty($query) && $this->config->get('passthrough_querystring')) {
74 $hashes[] = Redirect::generateHash($source_path, [], $language);
75 if ($language != Language::LANGCODE_NOT_SPECIFIED) {
76 $hashes[] = Redirect::generateHash($source_path, [], Language::LANGCODE_NOT_SPECIFIED);
80 // Load redirects by hash. A direct query is used to improve performance.
81 $rid = $this->connection->query('SELECT rid FROM {redirect} WHERE hash IN (:hashes[]) ORDER BY LENGTH(redirect_source__query) DESC', [':hashes[]' => $hashes])->fetchField();
84 // Check if this is a loop.
85 if (in_array($rid, $this->foundRedirects)) {
86 throw new RedirectLoopException('/' . $source_path, $rid);
88 $this->foundRedirects[] = $rid;
90 $redirect = $this->load($rid);
92 // Find chained redirects.
93 if ($recursive = $this->findByRedirect($redirect, $language)) {
94 // Reset found redirects.
95 $this->foundRedirects = [];
106 * Helper function to find recursive redirects.
108 * @param \Drupal\redirect\Entity\Redirect
109 * The redirect object.
110 * @param string $language
111 * The language to use.
113 protected function findByRedirect(Redirect $redirect, $language) {
114 $uri = $redirect->getRedirectUrl();
115 $base_url = \Drupal::request()->getBaseUrl();
116 $generated_url = $uri->toString(TRUE);
117 $path = ltrim(substr($generated_url->getGeneratedUrl(), strlen($base_url)), '/');
118 $query = $uri->getOption('query') ?: [];
119 $return_value = $this->findMatchingRedirect($path, $query, $language);
120 return $return_value ? $return_value->addCacheableDependency($generated_url) : $return_value;
124 * Finds redirects based on the source path.
126 * @param string $source_path
127 * The redirect source path (without the query).
129 * @return \Drupal\redirect\Entity\Redirect[]
130 * Array of redirect entities.
132 public function findBySourcePath($source_path) {
133 $ids = $this->manager->getStorage('redirect')->getQuery()
134 ->condition('redirect_source.path', $source_path, 'LIKE')
136 return $this->manager->getStorage('redirect')->loadMultiple($ids);
140 * Finds redirects based on the destination URI.
142 * @param string[] $destination_uri
143 * List of destination URIs, for example ['internal:/node/123'].
145 * @return \Drupal\redirect\Entity\Redirect[]
146 * Array of redirect entities.
148 public function findByDestinationUri(array $destination_uri) {
149 $storage = $this->manager->getStorage('redirect');
150 $ids = $storage->getQuery()
151 ->condition('redirect_redirect.uri', $destination_uri, 'IN')
153 return $storage->loadMultiple($ids);
157 * Load redirect entity by id.
159 * @param int $redirect_id
162 * @return \Drupal\redirect\Entity\Redirect
164 public function load($redirect_id) {
165 return $this->manager->getStorage('redirect')->load($redirect_id);
169 * Loads multiple redirect entities.
171 * @param array $redirect_ids
172 * Redirect ids to load.
174 * @return \Drupal\redirect\Entity\Redirect[]
175 * List of redirect entities.
177 public function loadMultiple(array $redirect_ids = NULL) {
178 return $this->manager->getStorage('redirect')->loadMultiple($redirect_ids);