Upgraded drupal core with security updates
[yaffs-website] / web / core / lib / Drupal / Core / Path / AliasManager.php
1 <?php
2
3 namespace Drupal\Core\Path;
4
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\CacheDecorator\CacheDecoratorInterface;
7 use Drupal\Core\Language\LanguageInterface;
8 use Drupal\Core\Language\LanguageManagerInterface;
9
10 /**
11  * The default alias manager implementation.
12  */
13 class AliasManager implements AliasManagerInterface, CacheDecoratorInterface {
14
15   /**
16    * The alias storage service.
17    *
18    * @var \Drupal\Core\Path\AliasStorageInterface
19    */
20   protected $storage;
21
22   /**
23    * Cache backend service.
24    *
25    * @var \Drupal\Core\Cache\CacheBackendInterface;
26    */
27   protected $cache;
28
29   /**
30    * The cache key to use when caching paths.
31    *
32    * @var string
33    */
34   protected $cacheKey;
35
36   /**
37    * Whether the cache needs to be written.
38    *
39    * @var bool
40    */
41   protected $cacheNeedsWriting = FALSE;
42
43   /**
44    * Language manager for retrieving the default langcode when none is specified.
45    *
46    * @var \Drupal\Core\Language\LanguageManagerInterface
47    */
48   protected $languageManager;
49
50   /**
51    * Holds the map of path lookups per language.
52    *
53    * @var array
54    */
55   protected $lookupMap = [];
56
57   /**
58    * Holds an array of aliases for which no path was found.
59    *
60    * @var array
61    */
62   protected $noPath = [];
63
64   /**
65    * Holds the array of whitelisted path aliases.
66    *
67    * @var \Drupal\Core\Path\AliasWhitelistInterface
68    */
69   protected $whitelist;
70
71   /**
72    * Holds an array of paths that have no alias.
73    *
74    * @var array
75    */
76   protected $noAlias = [];
77
78   /**
79    * Whether preloaded path lookups has already been loaded.
80    *
81    * @var array
82    */
83   protected $langcodePreloaded = [];
84
85   /**
86    * Holds an array of previously looked up paths for the current request path.
87    *
88    * This will only get populated if a cache key has been set, which for example
89    * happens if the alias manager is used in the context of a request.
90    *
91    * @var array
92    */
93   protected $preloadedPathLookups = FALSE;
94
95   /**
96    * Constructs an AliasManager.
97    *
98    * @param \Drupal\Core\Path\AliasStorageInterface $storage
99    *   The alias storage service.
100    * @param \Drupal\Core\Path\AliasWhitelistInterface $whitelist
101    *   The whitelist implementation to use.
102    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
103    *   The language manager.
104    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
105    *   Cache backend.
106    */
107   public function __construct(AliasStorageInterface $storage, AliasWhitelistInterface $whitelist, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) {
108     $this->storage = $storage;
109     $this->languageManager = $language_manager;
110     $this->whitelist = $whitelist;
111     $this->cache = $cache;
112   }
113
114   /**
115    * {@inheritdoc}
116    */
117   public function setCacheKey($key) {
118     // Prefix the cache key to avoid clashes with other caches.
119     $this->cacheKey = 'preload-paths:' . $key;
120   }
121
122   /**
123    * {@inheritdoc}
124    *
125    * Cache an array of the paths available on each page. We assume that aliases
126    * will be needed for the majority of these paths during subsequent requests,
127    * and load them in a single query during path alias lookup.
128    */
129   public function writeCache() {
130     // Check if the paths for this page were loaded from cache in this request
131     // to avoid writing to cache on every request.
132     if ($this->cacheNeedsWriting && !empty($this->cacheKey)) {
133       // Start with the preloaded path lookups, so that cached entries for other
134       // languages will not be lost.
135       $path_lookups = $this->preloadedPathLookups ?: [];
136       foreach ($this->lookupMap as $langcode => $lookups) {
137         $path_lookups[$langcode] = array_keys($lookups);
138         if (!empty($this->noAlias[$langcode])) {
139           $path_lookups[$langcode] = array_merge($path_lookups[$langcode], array_keys($this->noAlias[$langcode]));
140         }
141       }
142
143       $twenty_four_hours = 60 * 60 * 24;
144       $this->cache->set($this->cacheKey, $path_lookups, $this->getRequestTime() + $twenty_four_hours);
145     }
146   }
147
148   /**
149    * {@inheritdoc}
150    */
151   public function getPathByAlias($alias, $langcode = NULL) {
152     // If no language is explicitly specified we default to the current URL
153     // language. If we used a language different from the one conveyed by the
154     // requested URL, we might end up being unable to check if there is a path
155     // alias matching the URL path.
156     $langcode = $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId();
157
158     // If we already know that there are no paths for this alias simply return.
159     if (empty($alias) || !empty($this->noPath[$langcode][$alias])) {
160       return $alias;
161     }
162
163     // Look for the alias within the cached map.
164     if (isset($this->lookupMap[$langcode]) && ($path = array_search($alias, $this->lookupMap[$langcode]))) {
165       return $path;
166     }
167
168     // Look for path in storage.
169     if ($path = $this->storage->lookupPathSource($alias, $langcode)) {
170       $this->lookupMap[$langcode][$path] = $alias;
171       return $path;
172     }
173
174     // We can't record anything into $this->lookupMap because we didn't find any
175     // paths for this alias. Thus cache to $this->noPath.
176     $this->noPath[$langcode][$alias] = TRUE;
177
178     return $alias;
179   }
180
181   /**
182    * {@inheritdoc}
183    */
184   public function getAliasByPath($path, $langcode = NULL) {
185     if ($path[0] !== '/') {
186       throw new \InvalidArgumentException(sprintf('Source path %s has to start with a slash.', $path));
187     }
188     // If no language is explicitly specified we default to the current URL
189     // language. If we used a language different from the one conveyed by the
190     // requested URL, we might end up being unable to check if there is a path
191     // alias matching the URL path.
192     $langcode = $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId();
193
194     // Check the path whitelist, if the top-level part before the first /
195     // is not in the list, then there is no need to do anything further,
196     // it is not in the database.
197     if ($path === '/' || !$this->whitelist->get(strtok(trim($path, '/'), '/'))) {
198       return $path;
199     }
200
201     // During the first call to this method per language, load the expected
202     // paths for the page from cache.
203     if (empty($this->langcodePreloaded[$langcode])) {
204       $this->langcodePreloaded[$langcode] = TRUE;
205       $this->lookupMap[$langcode] = [];
206
207       // Load the cached paths that should be used for preloading. This only
208       // happens if a cache key has been set.
209       if ($this->preloadedPathLookups === FALSE) {
210         $this->preloadedPathLookups = [];
211         if ($this->cacheKey) {
212           if ($cached = $this->cache->get($this->cacheKey)) {
213             $this->preloadedPathLookups = $cached->data;
214           }
215           else {
216             $this->cacheNeedsWriting = TRUE;
217           }
218         }
219       }
220
221       // Load paths from cache.
222       if (!empty($this->preloadedPathLookups[$langcode])) {
223         $this->lookupMap[$langcode] = $this->storage->preloadPathAlias($this->preloadedPathLookups[$langcode], $langcode);
224         // Keep a record of paths with no alias to avoid querying twice.
225         $this->noAlias[$langcode] = array_flip(array_diff_key($this->preloadedPathLookups[$langcode], array_keys($this->lookupMap[$langcode])));
226       }
227     }
228
229     // If we already know that there are no aliases for this path simply return.
230     if (!empty($this->noAlias[$langcode][$path])) {
231       return $path;
232     }
233
234     // If the alias has already been loaded, return it from static cache.
235     if (isset($this->lookupMap[$langcode][$path])) {
236       return $this->lookupMap[$langcode][$path];
237     }
238
239     // Try to load alias from storage.
240     if ($alias = $this->storage->lookupPathAlias($path, $langcode)) {
241       $this->lookupMap[$langcode][$path] = $alias;
242       return $alias;
243     }
244
245     // We can't record anything into $this->lookupMap because we didn't find any
246     // aliases for this path. Thus cache to $this->noAlias.
247     $this->noAlias[$langcode][$path] = TRUE;
248     return $path;
249   }
250
251   /**
252    * {@inheritdoc}
253    */
254   public function cacheClear($source = NULL) {
255     if ($source) {
256       foreach (array_keys($this->lookupMap) as $lang) {
257         unset($this->lookupMap[$lang][$source]);
258       }
259     }
260     else {
261       $this->lookupMap = [];
262     }
263     $this->noPath = [];
264     $this->noAlias = [];
265     $this->langcodePreloaded = [];
266     $this->preloadedPathLookups = [];
267     $this->cache->delete($this->cacheKey);
268     $this->pathAliasWhitelistRebuild($source);
269   }
270
271   /**
272    * Rebuild the path alias white list.
273    *
274    * @param string $path
275    *   An optional path for which an alias is being inserted.
276    *
277    * @return
278    *   An array containing a white list of path aliases.
279    */
280   protected function pathAliasWhitelistRebuild($path = NULL) {
281     // When paths are inserted, only rebuild the whitelist if the path has a top
282     // level component which is not already in the whitelist.
283     if (!empty($path)) {
284       if ($this->whitelist->get(strtok($path, '/'))) {
285         return;
286       }
287     }
288     $this->whitelist->clear();
289   }
290
291   /**
292    * Wrapper method for REQUEST_TIME constant.
293    *
294    * @return int
295    */
296   protected function getRequestTime() {
297     return defined('REQUEST_TIME') ? REQUEST_TIME : (int) $_SERVER['REQUEST_TIME'];
298   }
299
300 }