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 FoundNot 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