diff --git a/core/includes/Drupal/Context/Context.php b/core/includes/Drupal/Context/Context.php new file mode 100644 index 0000000..519a058 --- /dev/null +++ b/core/includes/Drupal/Context/Context.php @@ -0,0 +1,259 @@ +locked) { + throw new LockedException("Cannot set the stack of a locked context."); + } + $this->stack = $stack; + } + + /** + * Implements ContextInterface::getStack(). + */ + public function getStack() { + return $this->stack; + } + + /** + * Implements ContextInterface::setParent(). + */ + public function setParent(ContextInterface $parent) { + if ($this->locked) { + throw new LockedException("Cannot set the parent of a locked context."); + } + $this->parent = $parent; + // For consistency reasons, this instance must belong to the same stack + // than its parent. + $this->stack = $this->parent->getStack(); + } + + /** + * Checks if this context has a parent. + * + * @return bool + * TRUE if the context has a parent, FALSE otherwise. + */ + public function hasParent() { + return isset($this->parent); + } + + /** + * Implements ContextInterface::getParent(). + */ + public function getParent() { + return $this->parent; + } + + /** + * Returns the handler at the given context key. + * + * @param string $context_key + * The context key for which we want a handler object + * + * @return Drupal\Context\Handler\HandlerInterface + * A valid handler object, or NULL if none found. + */ + protected function getHandlerAt($context_key) { + if (!array_key_exists($context_key, $this->handlers)) { + if (isset($this->handlerClosures[$context_key])) { + $this->handlers[$context_key] = $this->handlerClosures[$context_key](); + } + else { + $this->handlers[$context_key] = NULL; + } + } + + return $this->handlers[$context_key]; + } + + /** + * Implements ContextInterface::getValue(). + */ + public function getValue($context_key) { + if (!$this->locked) { + throw new NotLockedException(t('This context object has not been locked. It must be locked before it can be used.')); + } + + // We do not have data for this offset yet: use array_key_exists() because + // the value can be NULL. We do not want to re-run all handlerClasses for a + // variable with data. + if (!array_key_exists($context_key, $this->contextValues)) { + $this->contextValues[$context_key] = $this->findValue($context_key, $this); + } + + if (!isset($this->usedKeys[$context_key])) { + $this->usedKeys[$context_key] = $context_key; + } + + return $this->contextValues[$context_key]; + } + + /** + * Implements ContextInterface::findValue(). + */ + public function findValue($context_key, ContextInterface $context) { + // Loop over the possible context keys. + $local_key = $context_key; + $key_elements = explode(':', $context_key); + $args = array(); + + while ($key_elements) { + $handler = $this->getHandlerAt($local_key); + + if (isset($handler)) { + $handler_value = $handler->getValue($args, $context); + + // NULL value here means the context pass, and let potential parent + // overrides happen. + if (NULL !== $handler_value) { + // The null object here means it's definitely a NULL and parent + // cannot override it. + if ($handler_value instanceof OffsetIsNull) { + return NULL; + } + else { + return $handler_value; + } + } + } + + array_unshift($args, array_pop($key_elements)); + $local_key = implode(':', $key_elements); + } + + // If we did not found a value using local handlers, check for parents. + if ($this->hasParent()) { + return $this->getParent()->findValue($context_key, $context); + } + + return NULL; + } + + /** + * Implements ContextInterface::setValue(). + */ + public function setValue($context_key, $value) { + if ($this->locked) { + throw new LockedException(t('This context object has been locked. It no longer accepts new explicit context sets.')); + } + // Set an explicit override for a given context value. + $this->contextValues[$context_key] = $value; + } + + /** + * Implements ContextInterface::setHandler(). + */ + public function setHandler($context_key, $closure) { + if ($this->locked) { + throw new LockedException(t('This context object has been locked. It no longer accepts new handler registrations.')); + } + $this->handlerClosures[$context_key] = $closure; + } + + /** + * Implements ContextInterface::usedKeys(). + */ + function usedKeys() { + $key_list = array(); + + foreach ($this->usedKeys as $key) { + $value = $this->contextValues[$key]; + if ($value instanceof ValueInterface) { + $key_list[$key] = $value->contextKey(); + } + else { + $key_list[$key] = $value; + } + } + + return $key_list; + } + + /** + * Implements ContextInterface::lock(). + */ + public function lock() { + $this->locked = TRUE; + return new Tracker($this); + } + + /** + * Implements ContextInterface::addLayer(). + */ + public function addLayer() { + $layer = new self(); + $layer->setParent($this); + return $layer; + } +} diff --git a/core/includes/Drupal/Context/ContextException.php b/core/includes/Drupal/Context/ContextException.php new file mode 100644 index 0000000..002e3db --- /dev/null +++ b/core/includes/Drupal/Context/ContextException.php @@ -0,0 +1,8 @@ +params = $params; + } +} diff --git a/core/includes/Drupal/Context/Handler/HandlerHttp.php b/core/includes/Drupal/Context/Handler/HandlerHttp.php new file mode 100644 index 0000000..42ef85b --- /dev/null +++ b/core/includes/Drupal/Context/Handler/HandlerHttp.php @@ -0,0 +1,126 @@ +request = $request; + } + + /** + * Implements HandlerInterface::getValue(). + */ + public function getValue(array $args = array(), ContextInterface $context = null) { + $property = $args[0]; + + switch ($property) { + case 'method': + $value = $this->request->getMethod(); + break; + case 'uri': + $value = $this->request->getUri(); + break; + case 'base_url': + $value = $this->request->getScheme() . '://' . $this->request->getHost() . $this->request->getBaseUrl(); + break; + case 'base_path': + $value = $this->request->getBasePath() . '/'; + break; + case 'request_uri': + $value = $this->request->getRequestUri(); + break; + case 'script_name': + $value = $this->request->getScriptName(); + break; + case 'php_self': + $value = $this->request->server->get('PHP_SELF'); + break; + case 'accept_types': + $value = $this->request->getAcceptableContentTypes(); + break; + case 'domain': + $value = $this->request->getHost(); + break; + case 'request_args': + $value = $this->request->request->all(); + break; + case 'query': + $value = $this->request->query->all(); + break; + case 'languages': + // Although HttpFoundation has a getLanguages() method already, it + // does some case folding that is incompatible with Drupal's language + // string formats. + // @todo Revisit this after https://github.com/symfony/symfony/issues/2468 + // is resolved + $value = $this->request->splitHttpAcceptHeader($this->request->headers->get('Accept-Language')); + break; + case 'files': + $value = $this->request->files->all(); + break; + case 'cookies': + $value = $this->request->cookies->all(); + break; + case 'headers': + $value = $this->request->headers->all(); + // Cleanup from unnecessary nesting level. + foreach ($value as &$item) { + if (is_array($item)) { + $item = current($item); + } + } + break; + case 'server': + $value = $this->request->server->all(); + break; + case 'request_body': + $value = $this->request->getContent(); + break; + default: + return; + } + + // Many of the properties of the HTTP handler are actually arrays that + // we never want directly, but want a specific element out of. For instance, + // http:query is an array of GET parameters. The following routine lets us + // retrieve third-level properties consistently, so $_GET['foo'] would map + // to http:query:foo. The same code also supports cookies, headers, and so + // forth. + // Only one level of nesting is supported. If a GET parameter is itself an + // array, it will be returned as an array. + if (!is_array($value) || !isset($args[1])) { + return $value; + } + else { + // Return second nesting level value if it exists. + if (!empty($args[1]) && isset($value[$args[1]])) { + return $value[$args[1]]; + } + else { + // We return empty string if there is no + // second argument key in $this->params[$property]. + return ''; + } + } + } +} diff --git a/core/includes/Drupal/Context/Handler/HandlerInterface.php b/core/includes/Drupal/Context/Handler/HandlerInterface.php new file mode 100644 index 0000000..3804d9f --- /dev/null +++ b/core/includes/Drupal/Context/Handler/HandlerInterface.php @@ -0,0 +1,34 @@ +getValue('http:query:q'); + if (!empty($q)) { + // This is a request with a ?q=foo/bar query string. $_GET['q'] is + // overwritten in drupal_path_initialize(), but path:system is called + // very early in the bootstrap process, so the original value is saved in + // $path and returned in later calls. + $raw_path = $q; + } + else { + // This request is either a clean URL, or 'index.php', or nonsense. + // Extract the path from REQUEST_URI. + $request_uri = $context->getValue('http:request_uri'); + $request_path = strtok($request_uri, '?'); + $script_name = $context->getValue('http:script_name'); + $base_path_len = strlen(rtrim(dirname($script_name), '\/')); + // Unescape and strip $base_path prefix, leaving q without a leading slash. + $raw_path = substr(urldecode($request_path), $base_path_len + 1); + // If the path equals the script filename, either because 'index.php' was + // explicitly provided in the URL, or because the server added it to + // $_SERVER['REQUEST_URI'] even when it wasn't provided in the URL (some + // versions of Microsoft IIS do this), the front page should be served. + $php_self = $context->getValue('http:php_self'); + if ($raw_path == basename($php_self)) { + $raw_path = ''; + } + } + + // Under certain conditions Apache's RewriteRule directive prepends the value + // assigned to $_GET['q'] with a slash. Moreover we can always have a trailing + // slash in place, hence we need to normalize $_GET['q']. + $raw_path = trim($raw_path, '/'); + + return $raw_path; + } + +} diff --git a/core/includes/Drupal/Context/Handler/HandlerPathSystem.php b/core/includes/Drupal/Context/Handler/HandlerPathSystem.php new file mode 100644 index 0000000..a27f0dd --- /dev/null +++ b/core/includes/Drupal/Context/Handler/HandlerPathSystem.php @@ -0,0 +1,47 @@ +getValue('path:raw') instead. + * However, be careful when doing that because in the case of Example #3 + * drupal_get_context()->getValue('path:raw') will contain "path/alias". If + * "node/306" is needed, calling drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) + * makes this function available. + + * @todo Clean this up once the path system changes. + */ +class HandlerPathSystem extends HandlerAbstract { + + /** + * Implements HandlerInterface::getValue(). + */ + public function getValue(array $args = array(), ContextInterface $context = null) { + $system_path = ''; + + $path = $context->getValue('path:raw'); + if (!empty($path)) { + $system_path = drupal_get_normal_path($path); + } + else { + $system_path = drupal_get_normal_path(variable_get('site_frontpage', 'node')); + } + + return $system_path; + } +} diff --git a/core/includes/Drupal/Context/LockedException.php b/core/includes/Drupal/Context/LockedException.php new file mode 100644 index 0000000..9cb6e26 --- /dev/null +++ b/core/includes/Drupal/Context/LockedException.php @@ -0,0 +1,9 @@ +stack[spl_object_hash($context)] = $context; + } + + /** + * Removes the given context from the stack, if exists, and all its children. + * + * @param ContextInterface $context + * // TODO comment + * + * @throws ContextException + */ + public function pop(ContextInterface $context) { + $pos = array_search(spl_object_hash($context), array_keys($this->stack)); + if (FALSE !== $pos && count($this->stack) > 1) { + $this->stack = array_slice($this->stack, 0, $pos, TRUE); + } + else { + // throw new ContextException("Attempting to pop a non existing context from the stack."); + } + } + + /** + * Gets the top-most context object. + * + * The top most context object is the only one that any outside systems + * should ever access. + * + * @return Drupal\Context\ContextInterface + */ + public function getActiveContext() { + return end($this->stack); + } +} diff --git a/core/includes/Drupal/Context/Tracker.php b/core/includes/Drupal/Context/Tracker.php new file mode 100644 index 0000000..5f0852a --- /dev/null +++ b/core/includes/Drupal/Context/Tracker.php @@ -0,0 +1,74 @@ +isTracking = FALSE; + } + + /** + * Constructs a Tracker object. + * + * @param ContextInterface $context + * The context object we should be tracking. + */ + public function __construct(ContextInterface $context) { + $this->context = $context; + + // If the context object has not been bound to a stack, it won't work + // properly. Still, we shouldn't fatal. + $stack = $this->context->getStack(); + if (isset($stack)) { + $stack->push($this->context); + $this->stack = $stack; + } + } + + /** + * Destructs a Tracker object. + * + * When destroying this object, pop the associated context off the stack and + * everything above it. + * + * Note that this method does not actively destroy those context objects, it + * just pops them off the stack. PHP will delete them for us unless someone + * has one hanging around somewhere. + */ + public function __destruct() { + if ($this->isTracking && $this->stack) { + $this->stack->pop($this->context); + } + } +} diff --git a/core/includes/Drupal/Context/ValueInterface.php b/core/includes/Drupal/Context/ValueInterface.php new file mode 100644 index 0000000..c75f8d4 --- /dev/null +++ b/core/includes/Drupal/Context/ValueInterface.php @@ -0,0 +1,22 @@ +addLayer(); + $newContext->setValue('http:query:q', $path); + $tracker = $newContext->lock(); + $tracker->disableTracking(); +} + +/** * Finds the appropriate configuration directory for a given host and path. * * @param $http_host @@ -702,13 +731,6 @@ function drupal_environment_initialize() { $_SERVER['HTTP_HOST'] = ''; } - // When clean URLs are enabled, emulate ?q=foo/bar using REQUEST_URI. It is - // not possible to append the query string using mod_rewrite without the B - // flag (this was added in Apache 2.2.8), because mod_rewrite unescapes the - // path before passing it on to PHP. This is a problem when the path contains - // e.g. "&" or "%" that have special meanings in URLs and must be encoded. - $_GET['q'] = request_path(); - // Enforce E_STRICT, but allow users to set levels not part of E_STRICT. error_reporting(E_STRICT | E_ALL | error_reporting()); @@ -1120,9 +1142,10 @@ function bootstrap_invoke_all($hook) { // first time during the bootstrap that module_list() is called, we want to // make sure that its internal cache is primed with the bootstrap modules // only. + $args = func_get_args(); foreach (module_list(FALSE, TRUE) as $module) { drupal_load('module', $module); - module_invoke($module, $hook); + call_user_func_array('module_invoke', array_merge(array($module), $args)); } } @@ -1416,7 +1439,7 @@ function drupal_serve_page_from_cache(stdClass $cache) { * Define the critical hooks that force modules to always be loaded. */ function bootstrap_hooks() { - return array('boot', 'exit', 'watchdog', 'language_init'); + return array('boot', 'exit', 'watchdog', 'language_init', 'context_init'); } /** @@ -2247,12 +2270,6 @@ function _drupal_bootstrap_configuration() { set_error_handler('_drupal_error_handler'); set_exception_handler('_drupal_exception_handler'); - drupal_environment_initialize(); - // Start a page timer: - timer_start('page'); - // Initialize the configuration, including variables from settings.php. - drupal_settings_initialize(); - // Hook up the Symfony ClassLoader for loading PSR-0-compatible classes. require_once(DRUPAL_ROOT . '/core/includes/Symfony/Component/ClassLoader/UniversalClassLoader.php'); @@ -2264,7 +2281,7 @@ function _drupal_bootstrap_configuration() { case 'apc': if (function_exists('apc_store')) { require_once(DRUPAL_ROOT . '/core/includes/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php'); - $loader = new \Symfony\Component\ClassLoader\ApcUniversalClassLoader('drupal.' . $GLOBALS['drupal_hash_salt']); + $loader = new ApcUniversalClassLoader('drupal.' . $GLOBALS['drupal_hash_salt']); break; } // If APC was not loaded, fall through to the default loader so that @@ -2272,7 +2289,7 @@ function _drupal_bootstrap_configuration() { case 'dev': case 'default': default: - $loader = new \Symfony\Component\ClassLoader\UniversalClassLoader(); + $loader = new UniversalClassLoader(); break; } @@ -2286,6 +2303,12 @@ function _drupal_bootstrap_configuration() { // Activate the autoloader. $loader->register(); + + drupal_environment_initialize(); + // Start a page timer: + timer_start('page'); + // Initialize the configuration, including variables from settings.php. + drupal_settings_initialize(); } /** @@ -2321,7 +2344,7 @@ function _drupal_bootstrap_page_cache() { if (is_object($cache)) { header('X-Drupal-Cache: HIT'); // Restore the metadata cached with the page. - $_GET['q'] = $cache->data['path']; + drupal_path_override($cache->data['path']); drupal_set_title($cache->data['title'], PASS_THROUGH); date_default_timezone_set(drupal_get_user_timezone()); // If the skipping of the bootstrap hooks is not enforced, call @@ -2411,6 +2434,66 @@ function _drupal_bootstrap_variables() { // Load bootstrap modules. require_once DRUPAL_ROOT . '/core/includes/module.inc'; module_load_all(TRUE); + + require_once DRUPAL_ROOT . '/core/includes/common.inc'; + + drupal_bootstrap_context(); +} + +/** + * Initialize the context system. + * + * This function should only ever be called by _drupal_bootstrap_variables() + * and the install system. Calling it from anywhere else is a bug. + * + * @todo Refactor the install system so that we don't need an $install argument. + * @param boolean $install + * Set this to TRUE to set up install-system-specific overrides. + * + * @return \Drupal\Context\Context + * The context object just initialized. It is safe to ignore this value + * as it is the same that will be retrieved from drupal_get_context(). + * + * @see drupal_get_context(); + */ +function drupal_bootstrap_context($install = FALSE) { + // Initialize the context system (temporary version). + $stack = new Stack(); + drupal_get_context($stack); + + $context = new Context(); + $context->setStack($stack); + + // Assuming we're answering a web request, there are a number of handlers + // that we know we'll always want available. Don't bother with a hook for + // these. + // @todo Technically this is hard-coding a special case, which is bad. Once + // we have a better low-level "what environment are we running in" concept + // these should be moved over to the HTTP implementation of that. + if (!drupal_is_cli()) { + $context->setHandler('http', function() { + $request = Request::createFromGlobals(); + return new HandlerHttp($request); + }); + $context->setHandler('path:raw', function() { + return new HandlerPathRaw(); + }); + $context->setHandler('path:system', function() { + return new HandlerPathSystem(); + }); + } + + bootstrap_invoke_all('context_init', $context); + + if ($install) { + $context->setValue('path:system', ''); + } + + // We do not need to keep the tracker here, because the DrupalContext + // destructor will never remove the root item from the stack. + $context->lock(); + + return $context; } /** @@ -2426,6 +2509,28 @@ function _drupal_bootstrap_page_header() { } /** + * Returns the currently active context object. + * + * The return value from this funciton should never be statically cached. Doing + * so could lead to strange behavior if it has been removed from the stack, as + * it may no longer be valid and the object may even have been deleted. + * + * @return ContextInterface + */ +function drupal_get_context(Stack $new_stack = NULL) { + // This static property is infamous, but it's better here than in the OOP + // code, at least it does not taints the API. + static $stack; + if ($new_stack) { + $stack = $new_stack; + } + + if (isset($stack)) { + return $stack->getActiveContext(); + } +} + +/** * Returns the current bootstrap phase for this Drupal process. * * The current phase is the one most recently completed by drupal_bootstrap(). @@ -2521,9 +2626,10 @@ function drupal_maintenance_theme() { */ function drupal_fast_404() { $exclude_paths = variable_get('404_fast_paths_exclude', FALSE); - if ($exclude_paths && !preg_match($exclude_paths, $_GET['q'])) { + $system_path = drupal_get_context()->getValue('path:system'); + if ($exclude_paths && !preg_match($exclude_paths, $system_path)) { $fast_paths = variable_get('404_fast_paths', FALSE); - if ($fast_paths && preg_match($fast_paths, $_GET['q'])) { + if ($fast_paths && preg_match($fast_paths, $system_path)) { drupal_add_http_header('Status', '404 Not Found'); $fast_404_html = variable_get('404_fast_html', '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

'); // Replace @path in the variable with the page path. @@ -2708,65 +2814,6 @@ function language_default() { } /** - * Returns the requested URL path of the page being viewed. - * - * Examples: - * - http://example.com/node/306 returns "node/306". - * - http://example.com/drupalfolder/node/306 returns "node/306" while - * base_path() returns "/drupalfolder/". - * - http://example.com/path/alias (which is a path alias for node/306) returns - * "path/alias" as opposed to the internal path. - * - http://example.com/index.php returns an empty string (meaning: front page). - * - http://example.com/index.php?page=1 returns an empty string. - * - * @return - * The requested Drupal URL path. - * - * @see current_path() - */ -function request_path() { - static $path; - - if (isset($path)) { - return $path; - } - - if (isset($_GET['q'])) { - // This is a request with a ?q=foo/bar query string. $_GET['q'] is - // overwritten in drupal_path_initialize(), but request_path() is called - // very early in the bootstrap process, so the original value is saved in - // $path and returned in later calls. - $path = $_GET['q']; - } - elseif (isset($_SERVER['REQUEST_URI'])) { - // This request is either a clean URL, or 'index.php', or nonsense. - // Extract the path from REQUEST_URI. - $request_path = strtok($_SERVER['REQUEST_URI'], '?'); - $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); - // Unescape and strip $base_path prefix, leaving q without a leading slash. - $path = substr(urldecode($request_path), $base_path_len + 1); - // If the path equals the script filename, either because 'index.php' was - // explicitly provided in the URL, or because the server added it to - // $_SERVER['REQUEST_URI'] even when it wasn't provided in the URL (some - // versions of Microsoft IIS do this), the front page should be served. - if ($path == basename($_SERVER['PHP_SELF'])) { - $path = ''; - } - } - else { - // This is the front page. - $path = ''; - } - - // Under certain conditions Apache's RewriteRule directive prepends the value - // assigned to $_GET['q'] with a slash. Moreover we can always have a trailing - // slash in place, hence we need to normalize $_GET['q']. - $path = trim($path, '/'); - - return $path; -} - -/** * Return a component of the current Drupal path. * * When viewing a page at the path "admin/structure/types", for example, arg(0) @@ -2802,7 +2849,7 @@ function arg($index = NULL, $path = NULL) { $arguments = &$drupal_static_fast['arguments']; if (!isset($path)) { - $path = $_GET['q']; + $path = drupal_get_context()->getValue('path:system'); } if (!isset($arguments[$path])) { $arguments[$path] = explode('/', $path); diff --git a/core/includes/common.inc b/core/includes/common.inc index 722c1f6..128e943 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -501,7 +501,7 @@ function drupal_get_destination() { $destination = array('destination' => $_GET['destination']); } else { - $path = $_GET['q']; + $path = drupal_get_context()->getValue('path:system'); $query = drupal_http_build_query(drupal_get_query_parameters()); if ($query != '') { $path .= '?' . $query; @@ -2281,7 +2281,7 @@ function l($text, $path, array $options = array()) { ); // Append active class. - if (($path == $_GET['q'] || ($path == '' && drupal_is_front_page())) && + if (($path == drupal_get_context()->getValue('path:system') || ($path == '' && drupal_is_front_page())) && (empty($options['language']) || $options['language']->language == $language_url->language)) { $options['attributes']['class'][] = 'active'; } @@ -2407,7 +2407,7 @@ function drupal_deliver_page($page_callback_result, $default_delivery_callback = // If a delivery callback is specified, but doesn't exist as a function, // something is wrong, but don't print anything, since it's not known // what format the response needs to be in. - watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR); + watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => drupal_get_context()->getValue('path:system')), WATCHDOG_ERROR); } } @@ -2438,24 +2438,25 @@ function drupal_deliver_html_page($page_callback_result) { // Menu status constants are integers; page content is a string or array. if (is_int($page_callback_result)) { + $system_path = drupal_get_context()->getValue('path:system'); // @todo: Break these up into separate functions? switch ($page_callback_result) { case MENU_NOT_FOUND: // Print a 404 page. drupal_add_http_header('Status', '404 Not Found'); - watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); + watchdog('page not found', check_plain($system_path), NULL, WATCHDOG_WARNING); // Check for and return a fast 404 page if configured. drupal_fast_404(); // Keep old path for reference, and to allow forms to redirect to it. if (!isset($_GET['destination'])) { - $_GET['destination'] = $_GET['q']; + $_GET['destination'] = $system_path; } $path = drupal_get_normal_path(variable_get('site_404', '')); - if ($path && $path != $_GET['q']) { + if ($path && $path != $system_path) { // Custom 404 handler. Set the active item in case there are tabs to // display, or other dependencies on the path. menu_set_active_item($path); @@ -2476,15 +2477,15 @@ function drupal_deliver_html_page($page_callback_result) { case MENU_ACCESS_DENIED: // Print a 403 page. drupal_add_http_header('Status', '403 Forbidden'); - watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); + watchdog('access denied', check_plain($system_path), NULL, WATCHDOG_WARNING); // Keep old path for reference, and to allow forms to redirect to it. if (!isset($_GET['destination'])) { - $_GET['destination'] = $_GET['q']; + $_GET['destination'] = $system_path; } $path = drupal_get_normal_path(variable_get('site_403', '')); - if ($path && $path != $_GET['q']) { + if ($path && $path != $system_path) { // Custom 403 handler. Set the active item in case there are tabs to // display or other dependencies on the path. menu_set_active_item($path); @@ -5030,9 +5031,6 @@ function _drupal_bootstrap_full() { ini_set('error_log', 'public://error.log'); } - // Initialize $_GET['q'] prior to invoking hook_init(). - drupal_path_initialize(); - // Let all modules take action before the menu system handles the request. // We do not want this while running update.php. if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { @@ -5069,7 +5067,7 @@ function drupal_page_set_cache() { $cache = (object) array( 'cid' => $base_root . request_uri(), 'data' => array( - 'path' => $_GET['q'], + 'path' => drupal_get_context()->getValue('path:system'), 'body' => ob_get_clean(), 'title' => drupal_get_title(), 'headers' => array(), diff --git a/core/includes/form.inc b/core/includes/form.inc index a12fb34..586b3b6 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -1102,7 +1102,7 @@ function drupal_validate_form($form_id, &$form, &$form_state) { // matches the current user's session. if (isset($form['#token'])) { if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) { - $path = current_path(); + $path = drupal_get_context()->getValue('path:system'); $query = drupal_get_query_parameters(); $url = url($path, array('query' => $query)); @@ -1236,7 +1236,8 @@ function drupal_redirect_form($form_state) { $function($form_state['redirect']); } } - drupal_goto($_GET['q']); + $system_path = drupal_get_context()->getValue('path:system'); + drupal_goto($system_path); } } @@ -4317,7 +4318,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd 'progressive' => TRUE, 'url' => $url, 'url_options' => array(), - 'source_url' => $_GET['q'], + 'source_url' => drupal_get_context()->getValue('path:system'), 'redirect' => $redirect, 'theme' => $GLOBALS['theme_key'], 'redirect_callback' => $redirect_callback, diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index f9d8062..f3eb78f 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -267,6 +267,11 @@ function install_begin_request(&$install_state) { drupal_load('module', 'entity'); drupal_load('module', 'user'); + // Initialize the context system. + // @todo Refactor the context system such that we don't need to pass a boolean + // here and can rely on the normal context stack. + $context = drupal_bootstrap_context(TRUE); + // Load the cache infrastructure using a "fake" cache implementation that // does not attempt to write to the database. We need this during the initial // part of the installer because the database is not available yet. We diff --git a/core/includes/menu.inc b/core/includes/menu.inc index f23eb0d..700c6bc 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -425,7 +425,7 @@ function menu_set_item($path, $router_item) { function menu_get_item($path = NULL, $router_item = NULL) { $router_items = &drupal_static(__FUNCTION__); if (!isset($path)) { - $path = $_GET['q']; + $path = drupal_get_context()->getValue('path:system'); } if (isset($router_item)) { $router_items[$path] = $router_item; @@ -490,7 +490,7 @@ function menu_execute_active_handler($path = NULL, $deliver = TRUE) { // Allow other modules to change the site status but not the path because that // would not change the global variable. hook_url_inbound_alter() can be used // to change the path. Code later will not use the $read_only_path variable. - $read_only_path = !empty($path) ? $path : $_GET['q']; + $read_only_path = !empty($path) ? $path : drupal_get_context()->getValue('path:system'); drupal_alter('menu_site_status', $page_callback_result, $read_only_path); // Only continue if the site status is not set. @@ -1035,11 +1035,11 @@ function menu_tree_output($tree) { $class[] = 'active-trail'; $data['link']['localized_options']['attributes']['class'][] = 'active-trail'; } - // Normally, l() compares the href of every link with $_GET['q'] and sets - // the active class accordingly. But local tasks do not appear in menu + // Normally, l() compares the href of every link with the system path and + // sets the active class accordingly. But local tasks do not appear in menu // trees, so if the current path is a local task, and this link is its // tab root, then we have to set the class manually. - if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) { + if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != drupal_get_context()->getValue('path:system')) { $data['link']['localized_options']['attributes']['class'][] = 'active'; } @@ -1824,11 +1824,11 @@ function menu_navigation_links($menu_name, $level = 0) { $class = ' active-trail'; $l['attributes']['class'][] = 'active-trail'; } - // Normally, l() compares the href of every link with $_GET['q'] and sets - // the active class accordingly. But local tasks do not appear in menu + // Normally, l() compares the href of every link with the system path and + // sets the active class accordingly. But local tasks do not appear in menu // trees, so if the current path is a local task, and this link is its // tab root, then we have to set the class manually. - if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != $_GET['q']) { + if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != drupal_get_context()->getValue('path:system')) { $l['attributes']['class'][] = 'active'; } // Keyed with the unique mlid to generate classes in theme_links(). @@ -1942,8 +1942,8 @@ function menu_local_tasks($level = 0) { // local tasks link to their parent, but the path of default local // tasks can still be accessed directly, in which case this link // would not be marked as active, since l() only compares the href - // with $_GET['q']. - if ($link['href'] != $_GET['q']) { + // with the system path. + if ($link['href'] != drupal_get_context()->getValue('path:system')) { $link['localized_options']['attributes']['class'][] = 'active'; } $tabs_current[] = array( @@ -2018,8 +2018,8 @@ function menu_local_tasks($level = 0) { // Mark the link as active, if the current path is a (second-level) // local task of a default local task. Since this default local task // links to its parent, l() will not mark it as active, as it only - // compares the link's href to $_GET['q']. - if ($link['href'] != $_GET['q']) { + // compares the link's href to the system path. + if ($link['href'] != drupal_get_context()->getValue('path:system')) { $link['localized_options']['attributes']['class'][] = 'active'; } $tabs_current[] = array( @@ -2275,7 +2275,7 @@ function menu_get_active_menu_names() { * A Drupal path - not a path alias. */ function menu_set_active_item($path) { - $_GET['q'] = $path; + drupal_path_override($path); } /** @@ -2385,7 +2385,7 @@ function menu_link_get_preferred($path = NULL) { $preferred_links = &drupal_static(__FUNCTION__); if (!isset($path)) { - $path = $_GET['q']; + $path = drupal_get_context()->getValue('path:system'); } if (!isset($preferred_links[$path])) { @@ -3777,7 +3777,7 @@ function _menu_site_is_offline($check_only = FALSE) { // Ensure that the maintenance mode message is displayed only once // (allowing for page redirects) and specifically suppress its display on // the maintenance mode settings page. - if (!$check_only && $_GET['q'] != 'admin/config/development/maintenance') { + if (!$check_only && drupal_get_context()->getValue('path:system') != 'admin/config/development/maintenance') { if (user_access('administer site configuration')) { drupal_set_message(t('Operating in maintenance mode. Go online.', array('@url' => url('admin/config/development/maintenance'))), 'status', FALSE); } diff --git a/core/includes/pager.inc b/core/includes/pager.inc index a5d3e6b..2168242 100644 --- a/core/includes/pager.inc +++ b/core/includes/pager.inc @@ -630,7 +630,7 @@ function theme_pager_link($variables) { } } - return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => $query)); + return l($text, drupal_get_context()->getValue('path:system'), array('attributes' => $attributes, 'query' => $query)); } /** diff --git a/core/includes/path.inc b/core/includes/path.inc index 1fac235..bc0b472 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -10,18 +10,6 @@ */ /** - * Initialize the $_GET['q'] variable to the proper normal path. - */ -function drupal_path_initialize() { - // Ensure $_GET['q'] is set before calling drupal_normal_path(), to support - // path caching with hook_url_inbound_alter(). - if (empty($_GET['q'])) { - $_GET['q'] = variable_get('site_frontpage', 'node'); - } - $_GET['q'] = drupal_get_normal_path($_GET['q']); -} - -/** * Given an alias, return its Drupal system URL if one exists. Given a Drupal * system URL return one of its aliases if such a one exists. Otherwise, * return FALSE. @@ -89,7 +77,7 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) { $cache['map'][$path_language] = array(); // Load system paths from cache. - $cid = current_path(); + $cid = drupal_get_context()->getValue('path:system'); if ($cached = cache('path')->get($cid)) { $cache['system_paths'] = $cached->data; // Now fetch the aliases corresponding to these system paths. @@ -206,7 +194,7 @@ function drupal_cache_system_paths() { $cache = &drupal_static('drupal_lookup_path', array()); if (empty($cache['system_paths']) && !empty($cache['map'])) { // Generate a cache ID (cid) specifically for this page. - $cid = current_path(); + $cid = drupal_get_context()->getValue('path:system'); // The static $map array used by drupal_lookup_path() includes all // system paths for the page request. if ($paths = current($cache['map'])) { @@ -235,7 +223,7 @@ function drupal_cache_system_paths() { function drupal_get_path_alias($path = NULL, $path_language = NULL) { // If no path is specified, use the current page's path. if ($path == NULL) { - $path = $_GET['q']; + $path = drupal_get_context()->getValue('path:system'); } $result = $path; if ($alias = drupal_lookup_path('alias', $path, $path_language)) { @@ -290,9 +278,9 @@ function drupal_is_front_page() { $is_front_page = &$drupal_static_fast['is_front_page']; if (!isset($is_front_page)) { - // As drupal_path_initialize updates $_GET['q'] with the 'site_frontpage' path, + // As system path handler updates 'path:system' with the 'site_frontpage' path, // we can check it against the 'site_frontpage' variable. - $is_front_page = ($_GET['q'] == variable_get('site_frontpage', 'node')); + $is_front_page = (drupal_get_context()->getValue('path:system') == variable_get('site_frontpage', 'node')); } return $is_front_page; @@ -332,30 +320,6 @@ function drupal_match_path($path, $patterns) { } /** - * Return the current URL path of the page being viewed. - * - * Examples: - * - http://example.com/node/306 returns "node/306". - * - http://example.com/drupalfolder/node/306 returns "node/306" while - * base_path() returns "/drupalfolder/". - * - http://example.com/path/alias (which is a path alias for node/306) returns - * "node/306" as opposed to the path alias. - * - * This function is not available in hook_boot() so use $_GET['q'] instead. - * However, be careful when doing that because in the case of Example #3 - * $_GET['q'] will contain "path/alias". If "node/306" is needed, calling - * drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available. - * - * @return - * The current Drupal URL path. - * - * @see request_path() - */ -function current_path() { - return $_GET['q']; -} - -/** * Rebuild the path alias white list. * * @param $source diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc index 121a1b9..bdf2546 100644 --- a/core/includes/tablesort.inc +++ b/core/includes/tablesort.inc @@ -143,7 +143,7 @@ function tablesort_header($cell, $header, $ts) { $ts['sort'] = 'asc'; $image = ''; } - $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => array_merge($ts['query'], array('sort' => $ts['sort'], 'order' => $cell['data'])), 'html' => TRUE)); + $cell['data'] = l($cell['data'] . $image, drupal_get_context()->getValue('path:system'), array('attributes' => array('title' => $title), 'query' => array_merge($ts['query'], array('sort' => $ts['sort'], 'order' => $cell['data'])), 'html' => TRUE)); unset($cell['field'], $cell['sort']); } diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 06c4b79..5756685 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1604,7 +1604,7 @@ function theme_links($variables) { if ($i == $num_links) { $class[] = 'last'; } - if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '' && drupal_is_front_page())) + if (isset($link['href']) && ($link['href'] == drupal_get_context()->getValue('path:system') || ($link['href'] == '' && drupal_is_front_page())) && (empty($link['language']) || $link['language']->language == $language_url->language)) { $class[] = 'active'; } diff --git a/core/modules/block/block.module b/core/modules/block/block.module index b6ea373..ea6637d 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -833,11 +833,12 @@ function block_block_list_alter(&$blocks) { $pages = drupal_strtolower($block->pages); if ($block->visibility < BLOCK_VISIBILITY_PHP) { // Convert the Drupal path to lowercase - $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); + $system_path = drupal_get_context()->getValue('path:system'); + $path = drupal_strtolower(drupal_get_path_alias($system_path)); // Compare the lowercase internal and lowercase path alias (if any). $page_match = drupal_match_path($path, $pages); - if ($path != $_GET['q']) { - $page_match = $page_match || drupal_match_path($_GET['q'], $pages); + if ($path != $system_path) { + $page_match = $page_match || drupal_match_path($system_path, $pages); } // When $block->visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED), // the block is displayed on all pages except those listed in $block->pages. diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module index 9388877..ec04bfa 100644 --- a/core/modules/contact/contact.module +++ b/core/modules/contact/contact.module @@ -173,7 +173,7 @@ function contact_mail($key, &$message, $params) { '!site-name' => variable_get('site_name', 'Drupal'), '!subject' => $params['subject'], '!category' => isset($params['category']['category']) ? $params['category']['category'] : '', - '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)), + '!form-url' => url(drupal_get_context()->getValue('path:system'), array('absolute' => TRUE, 'language' => $language)), '!sender-name' => format_username($params['sender']), '!sender-url' => $params['sender']->uid ? url('user/' . $params['sender']->uid, array('absolute' => TRUE, 'language' => $language)) : $params['sender']->mail, ); diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 74a7434..a9b7509 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -230,7 +230,7 @@ function locale_init() { // in $conf. This should happen on all pages except the date and time formats // settings page, where we want to display the site default and not the // localized version. - if (strpos($_GET['q'], 'admin/config/regional/date-time/formats') !== 0) { + if (strpos(drupal_get_context()->getValue('path:system'), 'admin/config/regional/date-time/formats') !== 0) { $languages = array($language->language); // Setup appropriate date formats for this locale. @@ -1008,7 +1008,7 @@ function locale_block_info() { */ function locale_block_view($type) { if (drupal_multilingual()) { - $path = drupal_is_front_page() ? '' : $_GET['q']; + $path = drupal_is_front_page() ? '' : drupal_get_context()->getValue('path:system'); $links = language_negotiation_get_switch_links($type, $path); if (isset($links->links)) { diff --git a/core/modules/overlay/overlay.install b/core/modules/overlay/overlay.install index 2fa7c84..89e5e4b 100644 --- a/core/modules/overlay/overlay.install +++ b/core/modules/overlay/overlay.install @@ -12,7 +12,7 @@ * install profile, reopen the modules page in an overlay. */ function overlay_enable() { - if (strpos(current_path(), 'admin/modules') === 0) { + if (strpos(drupal_get_context()->getValue('path:system'), 'admin/modules') === 0) { // Flag for a redirect to #overlay=admin/modules on hook_init(). $_SESSION['overlay_enable_redirect'] = 1; } diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module index 5433d3e..9215259 100644 --- a/core/modules/overlay/overlay.module +++ b/core/modules/overlay/overlay.module @@ -125,7 +125,7 @@ function overlay_init() { // set. Other modules can also enable the overlay directly for other uses. $use_overlay = !isset($user->data['overlay']) || $user->data['overlay']; if (empty($mode) && user_access('access overlay') && $use_overlay) { - $current_path = current_path(); + $current_path = drupal_get_context()->getValue('path:system'); // After overlay is enabled on the modules page, redirect to // #overlay=admin/modules to actually enable the overlay. if (isset($_SESSION['overlay_enable_redirect']) && $_SESSION['overlay_enable_redirect']) { @@ -241,7 +241,7 @@ function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) { // overlay_init(). if ($path == system_authorized_get_url() || $path == system_authorized_batch_processing_url()) { $_SESSION['overlay_close_dialog'] = array($path, $options); - $path = current_path(); + $path = drupal_get_context()->getValue('path:system'); $options = drupal_get_query_parameters(); } diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc index 75c12b4..93e95b4 100644 --- a/core/modules/shortcut/shortcut.admin.inc +++ b/core/modules/shortcut/shortcut.admin.inc @@ -137,7 +137,7 @@ function shortcut_set_switch_submit($form, &$form_state) { $replacements = array( '%user' => $account->name, '%set_name' => $set->title, - '@switch-url' => url(current_path()), + '@switch-url' => url(drupal_get_context()->getValue('path:system')), ); if ($account->uid == $user->uid) { // Only administrators can create new shortcut sets, so we know they have diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index f8ddcc2..a3109cd 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -648,7 +648,7 @@ function shortcut_preprocess_page(&$variables) { // we do not want to display it on "access denied" or "page not found" // pages). if (shortcut_set_edit_access() && ($item = menu_get_item()) && $item['access']) { - $link = $_GET['q']; + $link = drupal_get_context()->getValue('path:system'); $query_parameters = drupal_get_query_parameters(); if (!empty($query_parameters)) { $link .= '?' . drupal_http_build_query($query_parameters); diff --git a/core/modules/simpletest/simpletest.info b/core/modules/simpletest/simpletest.info index fab7b5e..07835f9 100644 --- a/core/modules/simpletest/simpletest.info +++ b/core/modules/simpletest/simpletest.info @@ -14,6 +14,7 @@ files[] = tests/batch.test files[] = tests/bootstrap.test files[] = tests/cache.test files[] = tests/common.test +files[] = tests/context.test files[] = tests/database_test.test files[] = tests/error.test files[] = tests/file.test diff --git a/core/modules/simpletest/tests/common.test b/core/modules/simpletest/tests/common.test index 6cc159b..cc7f2a7 100644 --- a/core/modules/simpletest/tests/common.test +++ b/core/modules/simpletest/tests/common.test @@ -97,7 +97,7 @@ class CommonURLUnitTest extends DrupalWebTestCase { * Tests for active class in l() function. */ function testLActiveClass() { - $link = l($this->randomName(), $_GET['q']); + $link = l($this->randomName(), drupal_get_context()->getValue('path:system')); $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); } @@ -106,7 +106,7 @@ class CommonURLUnitTest extends DrupalWebTestCase { */ function testLCustomClass() { $class = $this->randomName(); - $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class)))); + $link = l($this->randomName(), drupal_get_context()->getValue('path:system'), array('attributes' => array('class' => array($class)))); $this->assertTrue($this->hasClass($link, $class), t('Custom class @class is present on link when requested', array('@class' => $class))); $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); } diff --git a/core/modules/simpletest/tests/context.test b/core/modules/simpletest/tests/context.test new file mode 100644 index 0000000..347ef90 --- /dev/null +++ b/core/modules/simpletest/tests/context.test @@ -0,0 +1,874 @@ + 'Context functionality', + 'description' => 'Test the Context context object.', + 'group' => 'Context', + ); + } + + /** + * Test simple handler registration. + */ + function testSimpleRegistration() { + $butler = new Context(); + + $butler->setHandler('http:get', function() { + $handler = new ContextMockHandler(); + $handler->setValue('foo', 'bar'); + return $handler; + }); + $butler->lock(); + + $param = $butler->getValue('http:get:foo'); + + $this->assertEqual($param, 'bar', t('Correct Http GET fragment found.')); + } + + /** + * Test explicit specification of a context value. + */ + function testExplicitContext() { + $butler = new Context(); + + $butler->setValue('foo:bar', 'baz'); + + $butler->lock(); + + $this->assertEqual($butler->getValue('foo:bar'), 'baz', t('Explicit context set correctly.')); + } + + /** + * Test explicit overriding of a context value. + */ + function testExplicitContextOverride() { + $butler = new Context(); + + // This handler would return "one". + $butler->setHandler('foo:bar', function() { + $handler = new ContextMockHandler(); + $handler->setValue('', 'one'); + return $handler; + }); + + // But we override it to "two". + $butler->setValue('foo:bar', 'two'); + + $butler->lock(); + + $this->assertEqual($butler->getValue('foo:bar'), 'two', t('Explicit context overridden correctly.')); + } + + /** + * Test that we can lock a context object against modification. + */ + function testLockStatusExplcit() { + $butler = new Context(); + + try { + $butler->lock(); + // This should throw an exception. + $butler->setValue('foo:bar', 'baz'); + + $this->fail(t('Exception not thrown when setting context on a locked object.')); + } + catch (LockedException $e) { + $this->pass(t('Proper exception thrown when setting context on a locked object.')); + } + catch (Exception $e) { + $this->fail(t('Incorrect exception thrown when modifying a locked object.')); + } + } + + /** + * Test that we can lock a context object against modification. + */ + function testLockStatusDerived() { + $butler = new Context(); + + try { + $butler->lock(); + // This should throw an exception. + $butler->setHandler('foo:bar', function() { + $handler = new ContextMockHandler(); + $handler->setValue('', 'one'); + return $handler; + }); + $this->fail(t('Exception not thrown when setting context on a locked object.')); + } + catch (LockedException $e) { + $this->pass(t('Proper exception thrown when setting context handler on a locked object.')); + } + catch (Exception $e) { + $this->fail(t('Incorrect exception thrown when modifying a locked object.')); + } + } + + /** + * Test the result caching logic. + */ + function testResultCaching() { + $butler = new Context(); + + // This handler should return 1, then 2, then 3, each time it's called. + $butler->setHandler('foo:bar', function() { + $handler = new ContextMockHandler(); + $counter = 1; + $handler->setValueClosure(function(array $args = array(), \Drupal\Context\ContextInterface $context) use (&$counter) { + // This increases every time it's called. If we ever get back something + // other than 1, it means the result caching is broken because we were + // called a second time. + return $counter++; + }); + return $handler; + }); + $butler->lock(); + + // Calling the same context variable should always give the same value + // due to immutable caching. + $this->assertEqual($butler->getValue('foo:bar'), $butler->getValue('foo:bar'), t('Identical context keys always return the same value.')); + // Handlers should be cached. That is, the same handler object should be + // used for foo:bar and foo:bar:baz, but the value of foo:bar:baz has not + // been cached. Therefore it should get called again and return an + // incremented value. + $this->assertNotEqual($butler->getValue('foo:bar'), $butler->getValue('foo:bar:baz'), t('Handler classes are cached.')); + } + + /** + * Test that the priority order of keys at different levels is stable. + */ + function testTraversingLogic() { + $butler = new \Drupal\Context\Context; + // This handler will be more specific than the child one, but should not be + // found because the less specialized child one will hide it. + $butler->setHandler('foo:bar', function() { + $handler = new ContextMockHandler(); + $handler->setValue('baz', 1); + return $handler; + }); + + $t1 = $butler->lock(); + $this->assertEqual(1, $butler->getValue('foo:bar:baz'), t('Asking for specialized value in root context.')); + + $b2 = $butler->addLayer(); + // This handler is less specialized than the parent one, but working on the + // same context key path tree than its parent. It should hide the parent + // more specialized one. + $b2->setHandler('foo', function() { + $handler = new ContextMockHandler(); + $handler->setValue('bar:baz', 2); + return $handler; + }); + $t2 = $b2->lock(); + + $this->assertEqual(2, $b2->getValue('foo:bar:baz'), t('Less specialized handler in the overriding context hides the root more specialized one.')); + + $b3 = $b2->addLayer(); + // Test the other way arround, local context handlers should always hide + // the parent one if on the same context key path. + $b3->setHandler('foo:bar', function() { + $handler = new ContextMockHandler(); + $handler->setValue('baz', 3); + return $handler; + }); + $t3 = $b3->lock(); + + $this->assertEqual(3, $b3->getValue('foo:bar:baz'), t('More specialized handler in the overriding context hides the root less specialized one.')); + + $b4 = $b2->addLayer(); + + // Set two handlers on the same context key tree, one less specialized + // and one more specialized, and ensures that everything is ok. + $b4->setHandler('foo', function() { + $handler = new ContextMockHandler(); + $handler->setValue('bar:baz', 5); + return $handler; + }); + $b4->setHandler('foo:bar', function() { + $handler = new ContextMockHandler(); + $handler->setValue('baz', 7); + return $handler; + }); + $t4 = $b4->lock(); + + $this->assertEqual(7, $b4->getValue('foo:bar:baz'), t('Generic handler is overridden by the more specialized one in the fourth layer.')); + + $b5 = $b2->addLayer(); + + // Set one handler and one value on the same context key tree, handler will + // be more generic than the value. + $b5->setHandler('foo', function() { + $handler = new ContextMockHandler(); + $handler->setValue('bar:baz', 11); + return $handler; + }); + $b5->setValue('foo:bar:baz', 13); + $t5 = $b5->lock(); + + $this->assertEqual(13, $b5->getValue('foo:bar:baz'), t('Value overrides the generic handler in fifth layer.')); + } +} + +class ContextValueObjectTestCase extends ContextTestCases { + public static function getInfo() { + return array( + 'name' => 'Context Value Object functionality', + 'description' => 'Test the Context system\'s handling of objects.', + 'group' => 'Context', + ); + } + + /** + * Test the retrieval of context values of objects. + */ + function testContextValues() { + $butler = new Context(); + + // Add a handler for this test to mimic to create an example (E.g. from url arguments) + $butler->setHandler('example', function() { + return new ContextHandlerExample(array('id' => 1)); + }); + + $t1 = $butler->lock(); + + // Catch the id from the url. + $this->assertEqual($butler->getValue('example')->id, 1, t('ContextValueNode property id in context retrieved from request correctly.')); + $this->assertEqual($butler->getValue('example')->foo, 10, t('ContextValueNode property foo in context retrieved from request correctly.')); + + // Add a context layer to the butler context. + $b2 = $butler->addLayer(); + $b2->setValue('test', 'test'); + + // Override with another instance of ContextValueInterface. + $n1 = $this->createExampleObject(2); + $b2->setValue('example', $n1); + // This will cause the error, which needs to be fixed. + //$b2['example:id'] = 2; + $t2 = $b2->lock(); + + $this->assertEqual($b2->getValue('test'), 'test', t('Explicit context assigned correctly in layer 2.')); + $this->assertEqual($b2->getValue('example')->foo, 20, t('ContextValueNode property in in layer 2 overridden correctly.')); + //$this->assertEqual($b2->getValue('example:id'), 2, t('Explicit context (example:id) overridden correctly in layer 2.')); + + // Add a context layer to the butler context. + $b3 = $butler->addLayer(); + $b3->setValue('test', 'testischanged'); + $b3->setValue('test:foo', 'newtest'); + $n2 = $this->createExampleObject(3); + $b3->setValue('example', $n2); + // Setting the example:id as contextKey:offset will add that property to the context. + $b3->setValue('example:id', 3); + $t3 = $b3->lock(); + + $this->assertEqual($b3->getValue('test'), 'testischanged', t('Explicit context overridden correctly in layer 3.')); + $this->assertEqual($b3->getValue('test:foo'), 'newtest', t('Explicit context retrieved correctly in layer 3.')); + $this->assertEqual($b3->getValue('example')->foo, 30, t('ContextValueNode property in in layer overridden correctly.')); + $this->assertEqual($b3->getValue('example:id'), 3, t('Explicit context (example:id) overridden correctly in layer 3.')); + } + + /** + * Test that we can track the keys we've used. + */ + public function testUsedKeys() { + $butler = new Context(); + + // Add a handler for this test to mimic to create an example (E.g. from url arguments) + $butler->setHandler('example', function() { + $handler = new ContextMockHandler(); + $handler->setValue('id', 1); + return $handler; + }); + + $butler->setValue('test', 'test'); + + $t1 = $butler->lock(); + + // Add a context layer to the butler context. + $b2 = $butler->addLayer(); + $b2->setValue('test', 'test2'); + + // Override with another instance of ContextValueInterface. + $n1 = $this->createExampleObject(2); + $b2->setValue('example', $n1); + $t2 = $b2->lock(); + + // Now try using the context values. + $b2->getValue('example'); + $b2->getValue('test'); + + $used = $b2->usedKeys(); + + $this->assertEqual(count($used), 2, t('Correct number of keys in used keys array.')); + $this->assertEqual($used['test'], 'test2', t('Used keys includes primitive key correctly.')); + $this->assertEqual($used['example'], 2, t('Used keys includes object key correctly.')); + } + + /** + * Utility method for an example value object. + * + * The property values are predictable and can be used for testing. + */ + protected function createExampleObject($id) { + $c = new ContextValueExample($id); + $c->foo = $id * 10; + $c->bar = 'Baz ' . $id; + + return $c; + } +} + +/** + * Test class for the mocking/overriding capability of the context system. + */ +class ContextMockTestCase extends ContextTestCases { + public static function getInfo() { + return array( + 'name' => 'Context Mocking functionality', + 'description' => 'Test the Context object\'s override capability.', + 'group' => 'Context', + ); + } + + /** + * Test basic context overrides. + */ + function testOverrides() { + $butler = new Context(); + + $butler->setHandler('http:get', function() { + $handler = new ContextMockHandler(); + $handler->setValue('foo', 'bar'); + return $handler; + }); + + $t1 = $butler->lock(); + + $foo1 = $butler->getValue('http:get:foo'); + + $this->assertEqual($foo1, 'bar', t('Correct Http GET fragment found.')); + + // Now check cloning. + $b2 = $butler->addLayer(); + $b2->setValue('test', 'test'); + $t2 = $b2->lock(); + + $this->assertNotIdentical($butler, $b2, t('New context object created properly.')); + + $this->assertEqual($b2->getValue('http:get:foo'), 'bar', t('Inherited property works.')); + $this->assertEqual($b2->getValue('test'), 'test', t('Explicit context property in mocked object is correct.')); + + $b3 = $b2->addLayer(); + $b3->setValue('test', 'test_again'); + $t3 = $b3->lock(); + + $this->assertEqual($b3->getValue('test'), 'test_again', t('Explicitly overriden property works.')); + } + + /** + * Test access to the "active" context. + */ + public function testActiveContext() { + $stack = new Stack(); + $butler = new Context(); + $butler->setStack($stack); + + $t1 = $butler->lock(); + $b2 = $butler->addLayer(); + $t2 = $b2->lock(); + + $this->assertEqual($b2, $stack->getActiveContext(), t('Active context is correct when adding context objects.')); + + unset($t2); + + $this->assertEqual($butler, $stack->getActiveContext(), t('Active context is correct when removing context objects.')); + } + + /** + * Test access to the "active" context at the global level. + */ + public function testGlobalActiveContext() { + $butler = drupal_get_context(); + + $t1 = $butler->lock(); + $b2 = $butler->addLayer(); + $t2 = $b2->lock(); + + $this->assertEqual($b2, drupal_get_context(), t('Active global context is correct when adding context objects.')); + + unset($t2); + + $this->assertEqual($butler, drupal_get_context(), t('Active global context is correct when removing context objects.')); + } + + /** + * Test that cotext values are removed from the stack properly. + */ + public function testGarbageCollection() { + // Create a simple butler object. + $butler = new Context(); + $butler->setHandler('foo', function() { + return new ContextTestCaseHelperThree(array('bar' => 'butler')); + }); + $t1 = $butler->lock(); + + // Create a new layer on top of that, which overrides some information. + $b2 = $butler->addLayer(); + $b2->setHandler('foo', function() { + return new ContextTestCaseHelperThree(array('bar' => 'b2')); + }); + $t2 = $b2->lock(); + + // Create a new layer on top of that, which actually does nothing at all. + $b3 = $b2->addLayer(); + $t3 = $b3->lock(); + + // Get rid of the tracker for b3. + unset($t3); + + // Get rid of b2 entirely. + unset($t2); + unset($b2); + + /* + * FIXME: This test is not needed anymore. Contextes will remain in memory + * even if removed from the stack as long as references remain. + * + * This makes sense, contextes could be referenced (even if it sounds bad) + * in render array's and result built at the very end of page build, for + * exemple. + * + try { + // This should throw an exception. + $b3->getValue('foo:bar'); + + $this->fail(t('Exception not thrown when getting data from a context that should have been destroyed.')); + } + catch (\Drupal\Context\ParentContextNotExistsException $e) { + $this->pass(t('Proper exception thrown when getting data from a context that should have been destroyed.')); + } + catch (Exception $e) { + $this->fail(t('Incorrect exception thrown when getting data from a context that should have been destroyed.')); + } + */ + } + + /** + * Test that later parts of the context key are passed to the handler. + */ + public function testContextArguments() { + $butler = new Context(); + $butler->setHandler('foo', function() { + $handler = new ContextMockHandler(); + $handler->setValueClosure(function(array $args = array(), ContextInterface $context) { + return $args; + }); + return $handler; + }); + $butler->lock(); + + $this->assertEqual($butler->getValue('foo:bar:baz'), array('bar', 'baz'), t('ContextHandler gets the correct arguments.')); + } + + public function testInheritanceContexts() { + $butler = new Context(); + $butler->setHandler('foo:bar', function() { + return new ContextTestCaseHelperThree(array('baz' => 1)); + }); + + $t1 = $butler->lock(); + + $b2 = $butler->addLayer(); + $b2->setHandler('foo', function() { + return new ContextTestCaseHelperThree(array('bar' => 3)); + }); + $t2 = $b2->lock(); + + $this->assertEqual($b2->getValue('foo:bar:baz'), 3, t('Context overrides even when more generic.')); + + unset($t2, $b2); + + $b3 = $butler->addLayer(); + $b3->setHandler('foo', function() { + return new ContextTestCaseHelperThree(array('baz' => 5)); + }); + $t3 = $b3->lock(); + $this->assertEqual($b3->getValue('foo:bar:baz'), 1, t('If a context handler does not return data, the parent handler gets a chance to return data')); + + unset($t3, $b3); + + $b4 = $butler->addLayer(); + $b4->setHandler('foo:bar:baz', function() { + return new ContextTestCaseHelperThree(array('bax' => 7)); + }); + $b4->setHandler('foo:bar', function() { + return new ContextTestCaseHelperThree(array('baz' => 9)); + }); + $t4 = $b4->lock(); + + $this->assertEqual($b4->getValue('foo:bar:baz:boo'), 9, t('A more generic handler in the same context should be called if the less generic handler does not return data.')); + } + + /** + * Test the offsetExists() method behavior. + */ + public function testOffsetExists() { + $butler = new Context(); + $butler->setHandler('foo', function() { + return new ContextTestCaseHelperThree(array('bar' => 3)); + }); + // B1: T1, B2: T2, Butler: Tutler :) + $tutler = $butler->lock(); + + // Normal behavior, with no derivation. + $this->assertNotNull($butler->getValue('foo:bar'), t("foo:bar exists")); + $this->assertNull($butler->getValue('I:Do:Not:Exist'), t("I:Do:Not:Exist does not exists")); + $this->assertNotNull($butler->getValue('foo:bar'), t("foo:bar exists (caching does not break anything)")); + $this->assertNull($butler->getValue('I:Do:Not:Exist'), t("I:Do:Not:Exist does not exists (caching does not break anything)")); + } + + /** + * Test the offsetExists() method behavior after multiple context derivation + * (through multiple layers over the same root context). + */ + public function testOffsetExistsWithDerivation() { + $butler = new Context(); + $butler->setHandler('foo', function() { + return new ContextTestCaseHelperThree(array('bar' => 3)); + }); + // B1: T1, B2: T2, Butler: Tutler :) + $tutler = $butler->lock(); + + // Derivate and test over the new layer. Results should be the exact same. + $b1 = $butler->addLayer(); + $t1 = $b1->lock(); + $this->assertNotNull($b1->getValue('foo:bar'), t("foo:bar exists in new layer")); + $this->assertNull($b1->getValue('I:Do:Not:Exist'), t("I:Do:Not:Exist does not exists in new layer")); + $this->assertNotNull($b1->getValue('foo:bar'), t("Value exists in new layer (caching does not break anything)")); + $this->assertNull($b1->getValue('I:Do:Not:Exist'), t("I:Do:Not:Exist does not exists in new layer (caching does not break anything)")); + + unset($t1, $b1); + + // Derivate again, add a new value, and test for all other existence. + $b2 = $butler->addLayer(); + $b2->setHandler('May:I', function() { + return new ContextTestCaseHelperThree(array('Exist' => 5)); + }); + $t2 = $b2->lock(); + $this->assertNotNull($b2->getValue('foo:bar'), t("foo:bar exists in new layer")); + $this->assertNull($b2->getValue('I:Do:Not:Exist'), t("I:Do:Not:Exist does not exists in new layer")); + $this->assertNotNull($b2->getValue('May:I:Exist'), t("May:I:Exist value exists in new layer")); + + unset($t2, $tutler); + } +} + +/** + * Test class for the HTTP Handler. + */ +class ContextHTTPTestCase extends ContextTestCases { + public static function getInfo() { + return array( + 'name' => 'Context HTTP Handler', + 'description' => 'Test the HTTP Handler.', + 'group' => 'Context', + ); + } + + /** + * Mock HTTP property. + */ + function testMockHttpProperty() { + $butler = new Context(); + + $butler->setHandler('http', function() { + $request = Request::create('', 'POST'); + return new HandlerHttp($request); + }); + $butler->lock(); + + $this->assertEqual($butler->getValue('http:method'), 'POST', t('Http method property mocked successfully.')); + } + + /** + * Access array property. + */ + function testArrayHttpProperty() { + $butler = new Context(); + + $params = array('foo' => array('bar' => 'baz')); + $butler->setHandler('http', function() use ($params) { + $request = Request::create('', 'GET', $params); + return new HandlerHttp($request); + }); + $butler->lock(); + + // Different nesting level values. + $this->assertEqual($butler->getValue('http:query'), $params, t('Query args fetched successfully.')); + $this->assertEqual($butler->getValue('http:query:foo'), $params['foo'], t('First level value from query_args fetched successfully.')); + + // Non existing value. + $this->assertEqual($butler->getValue('http:query:baz'), '', t('Non existent value fetched as empty string')); + } +} + +/** + * Test class for the HTTP Handler. + */ +class ContextPathTestCase extends ContextTestCases { + public static function getInfo() { + return array( + 'name' => 'Context Path Handlers', + 'description' => 'Test the Path Handlers.', + 'group' => 'Context', + ); + } + + /** + * Test path:raw from http:query:q + */ + function testPathRawFromGetQ() { + $butler = new Context(); + $path_alias = 'alias'; + + $butler->setHandler('path:raw', function() { + return new HandlerPathRaw(); + }); + + $butler->setValue('http:query:q', $path_alias); + $butler->lock(); + + // Comment needed to tell the raw path is based on http:query:q + $this->assertEqual($butler->getValue('path:raw'), $path_alias, t('Raw path is fetched successfully from GET query parameters.')); + } + + /** + * Test path:raw from http:request_uri + */ + function testPathRawPropertyFromRequestUri() { + $butler = new Context(); + $path_alias = 'alias'; + + $butler->setHandler('path:raw', function() { + return new HandlerPathRaw(); + }); + + $butler->setValue('http:request_uri', '/alias?page=1'); + $butler->setValue('http:script_name', '/index.php'); + $butler->setValue('http:php_self', '/index.php'); + $butler->lock(); + + // Comment needed to tell the raw path is based on the request_uri + $this->assertEqual($butler->getValue('path:raw'), $path_alias, t('Raw path is fetched successfully from request uri.')); + } + + /** + * Test path:raw from http:request_uri with index.php as request uri + */ + function testPathRawPropertyFromRequestUriIndexPhp() { + $butler = new Context(); + $path_alias = ''; + + $butler->setHandler('path:raw', function() { + return new HandlerPathRaw(); + }); + + $butler->setValue('http:request_uri', '/index.php'); + $butler->setValue('http:script_name', '/index.php'); + $butler->setValue('http:php_self', '/index.php'); + $butler->lock(); + + // Comment needed to tell the raw path is based on the request_uri with index.php as request uri + $this->assertEqual($butler->getValue('path:raw'), $path_alias, t('Raw path is fetched successfully from request uri.')); + } + + /** + * Test that a derived context value will clear when its dependant values do. + */ + public function testDerivedContextCaching() { + $butler = new Context(); + $butler->setHandler('base_value', function() { + $handler = new ContextMockHandler(); + $handler->setValue('val', 5); + return $handler; + }); + $butler->setHandler('derived_value', function() { + $handler = new ContextMockHandler(); + $handler->setValueClosure(function(array $args = array(), ContextInterface $context) { + return 2 * $context->getValue('base_value:val'); + }); + return $handler; + }); + $t1 = $butler->lock(); + + $this->derivedContextCachingInner($butler); + + // Because we are out of the scope where base_value was overridden, we + // should get back 10 here, that is, the base value of 5 times 2. + $value = $butler->getValue('derived_value'); + $this->assertEqual($value, 10, t('Correct value derived from original base value.')); + } + + protected function derivedContextCachingInner(ContextInterface $context) { + $c2 = $context->addLayer(); + $c2->setValue('base_value:val', 6); + $t2 = $c2->lock(); + + // This should implicitly request base_value:val, which should have a + // value of 6. + $value = $c2->getValue('derived_value'); + $this->assertEqual($value, 12, t('Correct value derived from overridden base value.')); + + // $t2 goes out of scope here, so $c2 gets popped off the stack. + } +} + +/** + * Mock handler for testing purposes. + * + * Return an instance of this object from the handler closure in a unit test, + * in order to tightly control what should be returned for testing purposes. + */ +class ContextMockHandler implements HandlerInterface { + + /** + * Simple list of values to return for a given key. + * + * @var array + */ + protected $values = array(); + + /** + * Set the closure that will be used for the getValue() method. + * + * @var Closure + */ + protected $valueClosure = NULL; + + /** + * Sets a specific value to a specific key for testing purposes. + * + * @param string $key + * A colon-delimited string specifying the context key to which to set a value. + * @param mixed $value + * The value to bind to the $key. + */ + public function setValue($key, $value) { + $this->values[$key] = $value; + } + + /** + * Sets a custom method body for the getValue() method for testing purposes. + * + * @param Closure $closure + * The closure object that will get called as part of the getValue() method. + * It should match the signature of getValue(). + */ + public function setValueClosure($closure) { + $this->valueClosure = $closure; + } + + /** + * Implements HandlerInterface::getValue(). + */ + public function getValue(array $args = array(), ContextInterface $context = NULL) { + $key = implode(':', $args); + if (isset($this->values[$key])) { + return $this->values[$key]; + } + $closure = $this->valueClosure; + return $closure($args, $context); + } +} + +class ContextTestCaseHelperThree extends \Drupal\Context\Handler\HandlerAbstract { + public function getValue(array $args = array(), ContextInterface $context = null) { + $param = $args[0]; + return isset($this->params[$param]) ? $this->params[$param] : NULL; + } +} + +/** + * Basic implementation of a ContextValueInterface. + */ +class ContextValueExample implements \Drupal\Context\ValueInterface { + + public $id = 0; + public $foo = 0; + public $bar = ''; + + public function __construct($id) { + $this->id = $id; + } + + public function contextKey() { + return $this->id(); + } + + public function id() { + return $this->id; + } +} + +/** + * ContextHandlerExample + */ +class ContextHandlerExample extends \Drupal\Context\Handler\HandlerAbstract { + + public function getValue(array $args = array(), ContextInterface $context = null) { + if (isset($this->params['id'])) { + $contextValueExample = $this->createExampleObject($this->params['id']); + $param = !empty($args) ? $args[0] : NULL; + return isset($contextValueExample->$param) ? $contextValueExample->$param : $contextValueExample; + } + } + + /** + * Utility method for an example value object. + * + * The property values are predictable and can be used for testing. + */ + protected function createExampleObject($id) { + $c = new ContextValueExample($id); + $c->foo = $id * 10; + $c->bar = 'Baz ' . $id; + + return $c; + } +} diff --git a/core/modules/simpletest/tests/path.test b/core/modules/simpletest/tests/path.test index 9406a65..bfa6ce0 100644 --- a/core/modules/simpletest/tests/path.test +++ b/core/modules/simpletest/tests/path.test @@ -192,20 +192,11 @@ class UrlAlterFunctionalTest extends DrupalWebTestCase { } /** - * Test current_path() and request_path(). - */ - function testCurrentUrlRequestedPath() { - $this->drupalGet('url-alter-test/bar'); - $this->assertRaw('request_path=url-alter-test/bar', t('request_path() returns the requested path.')); - $this->assertRaw('current_path=url-alter-test/foo', t('current_path() returns the internal path.')); - } - - /** - * Tests that $_GET['q'] is initialized when the request path is empty. + * Tests that system path is initialized when raw path is empty. */ function testGetQInitialized() { $this->drupalGet(''); - $this->assertText("\$_GET['q'] is non-empty with an empty request path.", "\$_GET['q'] is initialized with an empty request path."); + $this->assertText("System path is non-empty with an empty raw path.", "System path is initialized with an empty raw path."); } /** diff --git a/core/modules/simpletest/tests/theme.test b/core/modules/simpletest/tests/theme.test index 7968cf7..11aae38 100644 --- a/core/modules/simpletest/tests/theme.test +++ b/core/modules/simpletest/tests/theme.test @@ -61,13 +61,15 @@ class ThemeUnitTest extends DrupalWebTestCase { * Ensure page-front template suggestion is added when on front page. */ function testFrontPageThemeSuggestion() { - $q = $_GET['q']; - // Set $_GET['q'] to node because theme_get_suggestions() will query it to + $context = drupal_get_context(); + $c2 = $context->addLayer(); + // Set path:system to node because theme_get_suggestions() will query it to // see if we are on the front page. - $_GET['q'] = variable_get('site_frontpage', 'node'); - $suggestions = theme_get_suggestions(explode('/', $_GET['q']), 'page'); - // Set it back to not annoy the batch runner. - $_GET['q'] = $q; + $c2->setValue('path:system', variable_get('site_frontpage', 'node')); + $t2 = $c2->lock(); + + $suggestions = theme_get_suggestions(explode('/', $c2->getValue('path:system')), 'page'); + $this->assertTrue(in_array('page__front', $suggestions), t('Front page template was suggested.')); } diff --git a/core/modules/simpletest/tests/url_alter_test.module b/core/modules/simpletest/tests/url_alter_test.module index 9287ff5..0635bdb 100644 --- a/core/modules/simpletest/tests/url_alter_test.module +++ b/core/modules/simpletest/tests/url_alter_test.module @@ -22,7 +22,7 @@ function url_alter_test_menu() { * Menu callback. */ function url_alter_test_foo() { - print 'current_path=' . current_path() . ' request_path=' . request_path(); + print 'current_path=' . drupal_get_context()->getValue('path:system') . ' request_path=' . drupal_get_context()->getValue('path:raw'); exit; } @@ -30,8 +30,10 @@ function url_alter_test_foo() { * Implements hook_url_inbound_alter(). */ function url_alter_test_url_inbound_alter(&$path, $original_path, $path_language) { - if (!request_path() && !empty($_GET['q'])) { - drupal_set_message("\$_GET['q'] is non-empty with an empty request path."); + + $request_path = drupal_get_context()->getValue('path:raw'); + if (empty($request_path) && !empty($path)) { + drupal_set_message("System path is non-empty with an empty raw path."); } // Rewrite user/username to user/uid. diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 4f72553..b14dc4e 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -82,7 +82,7 @@ function statistics_exit() { db_insert('accesslog') ->fields(array( 'title' => truncate_utf8(strip_tags(drupal_get_title()), 255), - 'path' => truncate_utf8($_GET['q'], 255), + 'path' => truncate_utf8(drupal_get_context()->getValue('path:system'), 255), 'url' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '', 'hostname' => ip_address(), 'uid' => $user->uid, diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 51f0ecb..53ec757 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -486,7 +486,7 @@ function hook_page_build(&$page) { */ function hook_menu_get_item_alter(&$router_item, $path, $original_map) { // When retrieving the router item for the current path... - if ($path == $_GET['q']) { + if ($path == drupal_get_context()->getValue('path:system')) { // ...call a function that prepares something for this request. mymodule_prepare_something(); } @@ -4141,5 +4141,24 @@ function hook_filetransfer_info_alter(&$filetransfer_info) { } /** + * Register universal context handlers. + * + * This bootstrap-level hook allows modules to register handlers that will + * respond to various context key requests. Handlers should be registered + * using the setHandler() method. + * + * @see \Drupal\Context\ContextInterface + * + * @param ContextInterface $context + * The root context object for the system. + */ +function hook_context_init(\Drupal\Context\ContextInterface $context) { + + $context->setHandler('http', function() { + return new \Drupal\Context\Handler\HandlerHttp(); + }); +} + +/** * @} End of "addtogroup hooks". */ diff --git a/core/modules/system/system.module b/core/modules/system/system.module index f16b73f..921424b 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1898,7 +1898,7 @@ function system_init() { // Add the CSS for this module. These aren't in system.info, because they // need to be in the CSS_SYSTEM group rather than the CSS_DEFAULT group. drupal_add_css($path . '/system.base.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE)); - if (path_is_admin(current_path())) { + if (path_is_admin(drupal_get_context()->getValue('path:system'))) { drupal_add_css($path . '/system.admin.css', array('group' => CSS_SYSTEM)); } drupal_add_css($path . '/system.theme.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE)); @@ -1953,7 +1953,7 @@ function system_add_module_assets() { * Implements hook_custom_theme(). */ function system_custom_theme() { - if (user_access('view the administration theme') && path_is_admin(current_path())) { + if (user_access('view the administration theme') && path_is_admin(drupal_get_context()->getValue('path:system'))) { return variable_get('admin_theme'); } } diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc index 99114a6..2041c5c 100644 --- a/core/modules/taxonomy/taxonomy.admin.inc +++ b/core/modules/taxonomy/taxonomy.admin.inc @@ -412,7 +412,7 @@ function taxonomy_overview_terms($form, &$form_state, $vocabulary) { '#type' => 'submit', '#value' => t('Reset to alphabetical') ); - $form_state['redirect'] = array($_GET['q'], (isset($_GET['page']) ? array('query' => array('page' => $_GET['page'])) : array())); + $form_state['redirect'] = array(drupal_get_context()->getValue('path:system'), (isset($_GET['page']) ? array('query' => array('page' => $_GET['page'])) : array())); } return $form; @@ -772,7 +772,7 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = ); } else { - $form_state['redirect'] = $_GET['q']; + $form_state['redirect'] = drupal_get_context()->getValue('path:system'); } return $form; diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc index 2ccd97c..69e075c 100644 --- a/core/modules/update/update.compare.inc +++ b/core/modules/update/update.compare.inc @@ -738,8 +738,7 @@ function update_project_cache($cid) { $projects = array(); // On certain paths, we should clear the cache and recompute the projects for - // update status of the site to avoid presenting stale information. - $q = $_GET['q']; + // update status of the site to avoid presenting stale informatio $paths = array( 'admin/modules', 'admin/modules/update', @@ -751,7 +750,7 @@ function update_project_cache($cid) { 'admin/reports/status', 'admin/reports/updates/check', ); - if (in_array($q, $paths)) { + if (in_array(drupal_get_context()->getValue('path:system'), $paths)) { _update_cache_clear($cid); } else { diff --git a/core/modules/update/update.module b/core/modules/update/update.module index 6da47c0..43baebb 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -107,7 +107,7 @@ function update_help($path, $arg) { */ function update_init() { if (arg(0) == 'admin' && user_access('administer site configuration')) { - switch ($_GET['q']) { + switch (drupal_get_context()->getValue('path:system')) { // These pages don't need additional nagging. case 'admin/appearance/update': case 'admin/appearance/install': diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 6f1bc3c..2546c10 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1206,7 +1206,7 @@ function user_user_presave(&$edit, $account) { } function user_login_block($form) { - $form['#action'] = url($_GET['q'], array('query' => drupal_get_destination())); + $form['#action'] = url(drupal_get_context()->getValue('path:system'), array('query' => drupal_get_destination())); $form['#id'] = 'user-login-form'; $form['#validate'] = user_login_default_validators(); $form['#submit'][] = 'user_login_submit'; @@ -3698,7 +3698,7 @@ function user_register_form($form, &$form_state) { if ($admin) { // Redirect back to page which initiated the create request; // usually admin/people/create. - $form_state['redirect'] = $_GET['q']; + $form_state['redirect'] = drupal_get_context()->getValue('path:system'); } $form['actions'] = array('#type' => 'actions'); diff --git a/core/scripts/drupal.sh b/core/scripts/drupal.sh old mode 100755 new mode 100644 diff --git a/core/scripts/password-hash.sh b/core/scripts/password-hash.sh old mode 100755 new mode 100644 diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh old mode 100755 new mode 100644