diff --git a/includes/path.inc b/includes/path.inc index 630b34c..1bf912a 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -55,21 +55,13 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) { $cache = array( 'map' => array(), 'no_source' => array(), - 'whitelist' => NULL, + 'whitelist' => new PathAliasWhitelist('path_alias_whitelist', 'cache'), 'system_paths' => array(), 'no_aliases' => array(), 'first_call' => TRUE, ); } - // Retrieve the path alias whitelist. - if (!isset($cache['whitelist'])) { - $cache['whitelist'] = variable_get('path_alias_whitelist', NULL); - if (!isset($cache['whitelist'])) { - $cache['whitelist'] = drupal_path_alias_whitelist_rebuild(); - } - } - // If no language is explicitly specified we default to the current URL // language. If we used a language different from the one conveyed by the // requested URL, we might end up being unable to check if there is a path @@ -355,6 +347,70 @@ function current_path() { return $_GET['q']; } +/* + * Extends DrupalCacheArray to build the path alias whitelist over time. + */ +class PathAliasWhitelist extends DrupalCacheArray { + + public function __construct($cid, $bin) { + parent::__construct($cid, $bin); + + if (empty($this->storage)) { + // The path whitelist is based on the first part of each menu router item + // prepare the storage array by processing the router. + $router = menu_get_router(); + foreach ($router as $item) { + $root = strtok($item['path'], '/'); + $this->storage[$root] = NULL; + $this->persist($root); + } + } + } + + public function offsetGet($offset) { + // url() may be called with paths that are not represented by menu router + // items such as paths that will be rewritten by hook_url_outbound_alter(). + // Therefore internally TRUE is used to indicate whitelisted paths. FALSE is + // used to indicate paths that have already been checked but are not + // whitelisted, and NULL indicates paths that have not been checked yet. + if (isset($this->storage[$offset])) { + if ($this->storage[$offset]) { + return TRUE; + } + } + elseif (array_key_exists($offset, $this->storage)) { + return $this->resolveCacheMiss($offset); + } + } + + function resolveCacheMiss($root) { + $query = db_select('url_alias', 'u'); + $query->addExpression(1); + $query->condition('u.source', db_like($root) . '%', 'LIKE') ->range(0, 1); + $exists = (bool) $query->execute()->fetchField(); + $this->storage[$root] = $exists; + $this->persist($root); + if ($exists) { + return TRUE; + } + } + + public function set($cid, $data, $bin, $lock = TRUE) { + $lock_name = $cid . ':' . $bin; + if (!$lock || lock_acquire($lock_name)) { + if ($cached = cache($bin)->get($cid)) { + // Use array merge instead of union so that filled in values in $data + // overwrite empty values in the current cache. + $data = array_merge($cached->data, $data); + } + cache($bin)->set($cid, $data); + if ($lock) { + lock_release($lock_name); + } + } + } +} + /** * Rebuild the path alias white list. * @@ -362,27 +418,19 @@ function current_path() { * An optional system path for which an alias is being inserted. * * @return - * An array containing a white list of path aliases. + * An instance of the PathAliasWhitelist class. */ function drupal_path_alias_whitelist_rebuild($source = NULL) { // When paths are inserted, only rebuild the whitelist if the system path // has a top level component which is not already in the whitelist. if (!empty($source)) { - $whitelist = variable_get('path_alias_whitelist', NULL); + $whitelist = new PathAliasWhitelist('path_alias_whitelist', 'cache'); if (isset($whitelist[strtok($source, '/')])) { return $whitelist; } } - // For each alias in the database, get the top level component of the system - // path it corresponds to. This is the portion of the path before the first - // '/', if present, otherwise the whole path itself. - $whitelist = array(); - $result = db_query("SELECT DISTINCT SUBSTRING_INDEX(source, '/', 1) AS path FROM {url_alias}"); - foreach ($result as $row) { - $whitelist[$row->path] = TRUE; - } - variable_set('path_alias_whitelist', $whitelist); - return $whitelist; + cache_clear_all('path_alias_whitelist', 'cache'); + return new PathAliasWhitelist('path_alias_whitelist', 'cache'); } /**