Chris@0: storage = $storage; Chris@0: $this->languageManager = $language_manager; Chris@0: $this->whitelist = $whitelist; Chris@0: $this->cache = $cache; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setCacheKey($key) { Chris@0: // Prefix the cache key to avoid clashes with other caches. Chris@0: $this->cacheKey = 'preload-paths:' . $key; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * Cache an array of the paths available on each page. We assume that aliases Chris@0: * will be needed for the majority of these paths during subsequent requests, Chris@0: * and load them in a single query during path alias lookup. Chris@0: */ Chris@0: public function writeCache() { Chris@0: // Check if the paths for this page were loaded from cache in this request Chris@0: // to avoid writing to cache on every request. Chris@0: if ($this->cacheNeedsWriting && !empty($this->cacheKey)) { Chris@0: // Start with the preloaded path lookups, so that cached entries for other Chris@0: // languages will not be lost. Chris@0: $path_lookups = $this->preloadedPathLookups ?: []; Chris@0: foreach ($this->lookupMap as $langcode => $lookups) { Chris@0: $path_lookups[$langcode] = array_keys($lookups); Chris@0: if (!empty($this->noAlias[$langcode])) { Chris@0: $path_lookups[$langcode] = array_merge($path_lookups[$langcode], array_keys($this->noAlias[$langcode])); Chris@0: } Chris@0: } Chris@0: Chris@0: $twenty_four_hours = 60 * 60 * 24; Chris@0: $this->cache->set($this->cacheKey, $path_lookups, $this->getRequestTime() + $twenty_four_hours); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getPathByAlias($alias, $langcode = NULL) { Chris@0: // If no language is explicitly specified we default to the current URL Chris@0: // language. If we used a language different from the one conveyed by the Chris@0: // requested URL, we might end up being unable to check if there is a path Chris@0: // alias matching the URL path. Chris@0: $langcode = $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(); Chris@0: Chris@0: // If we already know that there are no paths for this alias simply return. Chris@0: if (empty($alias) || !empty($this->noPath[$langcode][$alias])) { Chris@0: return $alias; Chris@0: } Chris@0: Chris@0: // Look for the alias within the cached map. Chris@0: if (isset($this->lookupMap[$langcode]) && ($path = array_search($alias, $this->lookupMap[$langcode]))) { Chris@0: return $path; Chris@0: } Chris@0: Chris@0: // Look for path in storage. Chris@0: if ($path = $this->storage->lookupPathSource($alias, $langcode)) { Chris@0: $this->lookupMap[$langcode][$path] = $alias; Chris@0: return $path; Chris@0: } Chris@0: Chris@0: // We can't record anything into $this->lookupMap because we didn't find any Chris@0: // paths for this alias. Thus cache to $this->noPath. Chris@0: $this->noPath[$langcode][$alias] = TRUE; Chris@0: Chris@0: return $alias; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getAliasByPath($path, $langcode = NULL) { Chris@0: if ($path[0] !== '/') { Chris@0: throw new \InvalidArgumentException(sprintf('Source path %s has to start with a slash.', $path)); Chris@0: } Chris@0: // If no language is explicitly specified we default to the current URL Chris@0: // language. If we used a language different from the one conveyed by the Chris@0: // requested URL, we might end up being unable to check if there is a path Chris@0: // alias matching the URL path. Chris@0: $langcode = $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(); Chris@0: Chris@0: // Check the path whitelist, if the top-level part before the first / Chris@0: // is not in the list, then there is no need to do anything further, Chris@0: // it is not in the database. Chris@0: if ($path === '/' || !$this->whitelist->get(strtok(trim($path, '/'), '/'))) { Chris@0: return $path; Chris@0: } Chris@0: Chris@0: // During the first call to this method per language, load the expected Chris@0: // paths for the page from cache. Chris@0: if (empty($this->langcodePreloaded[$langcode])) { Chris@0: $this->langcodePreloaded[$langcode] = TRUE; Chris@0: $this->lookupMap[$langcode] = []; Chris@0: Chris@0: // Load the cached paths that should be used for preloading. This only Chris@0: // happens if a cache key has been set. Chris@0: if ($this->preloadedPathLookups === FALSE) { Chris@0: $this->preloadedPathLookups = []; Chris@0: if ($this->cacheKey) { Chris@0: if ($cached = $this->cache->get($this->cacheKey)) { Chris@0: $this->preloadedPathLookups = $cached->data; Chris@0: } Chris@0: else { Chris@0: $this->cacheNeedsWriting = TRUE; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Load paths from cache. Chris@0: if (!empty($this->preloadedPathLookups[$langcode])) { Chris@0: $this->lookupMap[$langcode] = $this->storage->preloadPathAlias($this->preloadedPathLookups[$langcode], $langcode); Chris@0: // Keep a record of paths with no alias to avoid querying twice. Chris@0: $this->noAlias[$langcode] = array_flip(array_diff_key($this->preloadedPathLookups[$langcode], array_keys($this->lookupMap[$langcode]))); Chris@0: } Chris@0: } Chris@0: Chris@0: // If we already know that there are no aliases for this path simply return. Chris@0: if (!empty($this->noAlias[$langcode][$path])) { Chris@0: return $path; Chris@0: } Chris@0: Chris@0: // If the alias has already been loaded, return it from static cache. Chris@0: if (isset($this->lookupMap[$langcode][$path])) { Chris@0: return $this->lookupMap[$langcode][$path]; Chris@0: } Chris@0: Chris@0: // Try to load alias from storage. Chris@0: if ($alias = $this->storage->lookupPathAlias($path, $langcode)) { Chris@0: $this->lookupMap[$langcode][$path] = $alias; Chris@0: return $alias; Chris@0: } Chris@0: Chris@0: // We can't record anything into $this->lookupMap because we didn't find any Chris@0: // aliases for this path. Thus cache to $this->noAlias. Chris@0: $this->noAlias[$langcode][$path] = TRUE; Chris@0: return $path; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function cacheClear($source = NULL) { Chris@0: if ($source) { Chris@0: foreach (array_keys($this->lookupMap) as $lang) { Chris@0: unset($this->lookupMap[$lang][$source]); Chris@0: } Chris@0: } Chris@0: else { Chris@0: $this->lookupMap = []; Chris@0: } Chris@0: $this->noPath = []; Chris@0: $this->noAlias = []; Chris@0: $this->langcodePreloaded = []; Chris@0: $this->preloadedPathLookups = []; Chris@0: $this->cache->delete($this->cacheKey); Chris@0: $this->pathAliasWhitelistRebuild($source); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Rebuild the path alias white list. Chris@0: * Chris@0: * @param string $path Chris@0: * An optional path for which an alias is being inserted. Chris@0: * Chris@0: * @return Chris@0: * An array containing a white list of path aliases. Chris@0: */ Chris@0: protected function pathAliasWhitelistRebuild($path = NULL) { Chris@0: // When paths are inserted, only rebuild the whitelist if the path has a top Chris@0: // level component which is not already in the whitelist. Chris@0: if (!empty($path)) { Chris@0: if ($this->whitelist->get(strtok($path, '/'))) { Chris@0: return; Chris@0: } Chris@0: } Chris@0: $this->whitelist->clear(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Wrapper method for REQUEST_TIME constant. Chris@0: * Chris@0: * @return int Chris@0: */ Chris@0: protected function getRequestTime() { Chris@0: return defined('REQUEST_TIME') ? REQUEST_TIME : (int) $_SERVER['REQUEST_TIME']; Chris@0: } Chris@0: Chris@0: }