diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 952b4ea..bfaaaa8 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -3,6 +3,8 @@ use Symfony\Component\ClassLoader\UniversalClassLoader; use Symfony\Component\ClassLoader\ApcUniversalClassLoader; +use Drupal\Core\Request; + /** * @file * Functions that need to be loaded on every Drupal request. @@ -718,13 +720,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()); @@ -2725,124 +2720,56 @@ function language_default() { } /** + * Returns the request object for the current request. + * + * @return Drupal\Core\Request + * The request object for this request, as populated from the PHP superglobals. + */ +function request() { + $request = &drupal_static(__FUNCTION__); + if (empty($request)) { + $request = Request::createFromGlobals(); + } + return $request; +} + +/** * 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. + * @deprecated * * @return * The requested Drupal URL path. * - * @see current_path() + * @see Drupal\Core\Request::requestPath() */ 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 request()->requestPath(); } /** * Returns a component of the current Drupal path. * - * When viewing a page at the path "admin/structure/types", for example, arg(0) - * returns "admin", arg(1) returns "structure", and arg(2) returns "types". - * - * Avoid use of this function where possible, as resulting code is hard to - * read. In menu callback functions, attempt to use named arguments. See the - * explanation in menu.inc for how to construct callbacks that take arguments. - * When attempting to use this function to load an element from the current - * path, e.g. loading the node on a node page, use menu_get_object() instead. - * - * @param $index - * The index of the component, where each component is separated by a '/' - * (forward-slash), and where the first component has an index of 0 (zero). - * @param $path - * A path to break into components. Defaults to the path of the current page. + * @deprecated * * @return * The component specified by $index, or NULL if the specified component was - * not found. If called without arguments, it returns an array containing all - * the components of the current path. + * not found. */ function arg($index = NULL, $path = NULL) { - // Even though $arguments doesn't need to be resettable for any functional - // reasons (the result of explode() does not depend on any run-time - // information), it should be resettable anyway in case a module needs to - // free up the memory used by it. - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['arguments'] = &drupal_static(__FUNCTION__); - } - $arguments = &$drupal_static_fast['arguments']; - - if (!isset($path)) { - $path = $_GET['q']; - } - if (!isset($arguments[$path])) { - $arguments[$path] = explode('/', $path); - } - if (!isset($index)) { - return $arguments[$path]; - } - if (isset($arguments[$path][$index])) { - return $arguments[$path][$index]; - } + return request()->pathElement($index); } /** * Returns the IP address of the client machine. * - * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header - * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of - * the proxy server, and not the client's. The actual header name can be - * configured by the reverse_proxy_header variable. + * @deprecated * * @return * IP address of client machine, adjusted for reverse proxy and/or cluster * environments. + * + * @see Symfony\Component\HttpFoundation\Request::getClientIp() */ function ip_address() { $ip_address = &drupal_static(__FUNCTION__); @@ -2875,8 +2802,7 @@ function ip_address() { } } - return $ip_address; -} + return $ip_address;} /** * @ingroup schemaapi diff --git a/core/includes/common.inc b/core/includes/common.inc index f840e5c..1108386 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2343,7 +2343,7 @@ function l($text, $path, array $options = array()) { ); // Append active class. - if (($path == $_GET['q'] || ($path == '' && drupal_is_front_page())) && + if (($path == request()->systemPath() || ($path == '' && drupal_is_front_page())) && (empty($options['language']) || $options['language']->langcode == $language_url->langcode)) { $options['attributes']['class'][] = 'active'; } diff --git a/core/includes/form.inc b/core/includes/form.inc index 85bffc6..6dea16c 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -1238,7 +1238,7 @@ function drupal_redirect_form($form_state) { $function($form_state['redirect']); } } - drupal_goto($_GET['q']); + drupal_goto(request()->systemPath()); } } diff --git a/core/includes/locale.inc b/core/includes/locale.inc index e2a79b6..6195b0d 100644 --- a/core/includes/locale.inc +++ b/core/includes/locale.inc @@ -272,7 +272,12 @@ function locale_language_from_url($languages) { case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: // $_GET['q'] might not be available at this time, because // path initialization runs after the language bootstrap phase. - list($language, $_GET['q']) = language_url_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL, $languages); + $path = request()->systemPath(); + list($language, $path) = language_url_split_prefix($path, $languages); + if (empty($path)) { + $path = variable_get('site_frontpage', 'user'); + } + request()->setSystemPath($path); if ($language !== FALSE) { $language_url = $language->langcode; } diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 84bd0d1..9df9a94 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -434,7 +434,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 = request()->systemPath(); } if (isset($router_item)) { $router_items[$path] = $router_item; @@ -445,7 +445,7 @@ function menu_get_item($path = NULL, $router_item = NULL) { if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) { menu_rebuild(); } - $original_map = arg(NULL, $path); + $original_map = explode('/', $path); $parts = array_slice($original_map, 0, MENU_MAX_PARTS); $ancestors = menu_get_ancestors($parts); @@ -491,7 +491,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 : request()->systemPath(); drupal_alter('menu_site_status', $page_callback_result, $read_only_path); // Only continue if the site status is not set. @@ -1697,7 +1697,7 @@ function menu_get_active_help() { return ''; } - $arg = drupal_help_arg(arg(NULL)); + $arg = drupal_help_arg(explode('/', request()->systemPath())); foreach (module_implements('help') as $module) { $function = $module . '_help'; @@ -2291,7 +2291,7 @@ function menu_get_active_menu_names() { * A Drupal path - not a path alias. */ function menu_set_active_item($path) { - $_GET['q'] = $path; + request()->setSystemPath($path); } /** diff --git a/core/includes/path.inc b/core/includes/path.inc index 44bf3fe..166e0ac 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -13,12 +13,7 @@ * 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', 'user'); - } - $_GET['q'] = drupal_get_normal_path($_GET['q']); + request()->setSystemPath(drupal_get_normal_path(request()->systemPath())); } /** @@ -235,7 +230,7 @@ function drupal_cache_system_paths() { function drupal_get_path_alias($path = NULL, $langcode = NULL) { // If no path is specified, use the current page's path. if ($path == NULL) { - $path = $_GET['q']; + $path = request()->systemPath(); } $result = $path; if ($alias = drupal_lookup_path('alias', $path, $langcode)) { @@ -292,7 +287,7 @@ function drupal_is_front_page() { if (!isset($is_front_page)) { // As drupal_path_initialize updates $_GET['q'] with the 'site_frontpage' path, // we can check it against the 'site_frontpage' variable. - $is_front_page = ($_GET['q'] == variable_get('site_frontpage', 'user')); + $is_front_page = (request()->systemPath() == variable_get('site_frontpage', 'user')); } return $is_front_page; @@ -352,7 +347,7 @@ function drupal_match_path($path, $patterns) { * @see request_path() */ function current_path() { - return $_GET['q']; + return request()->systemPath(); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 5088c41..9d01c1c 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1683,7 +1683,7 @@ function theme_links($variables) { // Handle links. if (isset($link['href'])) { - $is_current_path = ($link['href'] == $_GET['q'] || ($link['href'] == '' && drupal_is_front_page())); + $is_current_path = ($link['href'] == request()->systemPath() || ($link['href'] == '' && drupal_is_front_page())); $is_current_language = (empty($link['language']) || $link['language']->langcode == $language_url->langcode); if ($is_current_path && $is_current_language) { $class[] = 'active'; @@ -2435,7 +2435,7 @@ function template_preprocess_html(&$variables) { } // Populate the body classes. - if ($suggestions = theme_get_suggestions(arg(), 'page', '-')) { + if ($suggestions = theme_get_suggestions(request()->pathElements(), 'page', '-')) { foreach ($suggestions as $suggestion) { if ($suggestion != 'page-front') { // Add current suggestion to page classes to make it possible to theme @@ -2483,7 +2483,7 @@ function template_preprocess_html(&$variables) { $variables['head_title'] = implode(' | ', $head_title); // Populate the page template suggestions. - if ($suggestions = theme_get_suggestions(arg(), 'html')) { + if ($suggestions = theme_get_suggestions(request()->pathElements(), 'html')) { $variables['theme_hook_suggestions'] = $suggestions; } } @@ -2495,9 +2495,6 @@ function template_preprocess_html(&$variables) { * inside "modules/system/page.tpl.php". Look in there for the full list of * variables. * - * Uses the arg() function to generate a series of page template suggestions - * based on the current path. - * * Any changes to variables in this preprocessor should also be changed inside * template_preprocess_maintenance_page() to keep all of them consistent. * @@ -2542,7 +2539,7 @@ function template_preprocess_page(&$variables) { } // Populate the page template suggestions. - if ($suggestions = theme_get_suggestions(arg(), 'page')) { + if ($suggestions = theme_get_suggestions(request()->pathElements(), 'page')) { $variables['theme_hook_suggestions'] = $suggestions; } } @@ -2611,7 +2608,7 @@ function template_process_html(&$variables) { * base the additional suggestions on the path of the current page. * * @param $args - * An array of path arguments, such as from function arg(). + * An array of path arguments. * @param $base * A string identifying the base 'thing' from which more specific suggestions * are derived. For example, 'page' or 'html'. diff --git a/core/lib/Drupal/Core/Request.php b/core/lib/Drupal/Core/Request.php new file mode 100644 index 0000000..6b891a3 --- /dev/null +++ b/core/lib/Drupal/Core/Request.php @@ -0,0 +1,175 @@ +pathElements(); + return isset($elements[$index]) ? $elements[$index] : NULL; + } + + /** + * Returns the current path broken up into an array. + * + * @return array + * An array representing the elements of the path. + */ + public function pathElements() { + if (empty($this->pathElements)) { + $this->pathElements = explode('/', $this->systemPath()); + } + return $this->pathElements; + } + + /** + * 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 URL path, as it came from the user agent. + */ + public function requestPath() { + if (empty($this->requestPath)) { + $raw_path = ''; + + $q = $this->query->get('q'); + + if (!empty($q)) { + // This is a request with a ?q=foo/bar query string. That trumps all other + // path locations. + $raw_path = $q; + } + else { + // This request is either a clean URL, or 'index.php', or nonsense. + // Extract the path from REQUEST_URI. + $request_uri = $this->getRequestUri(); + $request_path = strtok($request_uri, '?'); + $script_name = $this->getScriptName(); + $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 = $this->server->get('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']. + $this->requestPath = trim($raw_path, '/'); + } + + return $this->requestPath; + } + + /** + * 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 Request::requestPath() + * instead. Be aware that requestPath() does not have aliases resolved. + * + * @return + * The current URL path, with aliases resolved. + */ + public function systemPath() { + if (empty($this->systemPath)) { + // @todo Temporary hack. Fix when path is an object. + require_once DRUPAL_ROOT . '/core/includes/path.inc'; + + $path = $this->requestPath(); + + if (empty($path)) { + // @todo Temporary hack. Fix when configuration is injectable. + $path = variable_get('site_frontpage', 'user'); + } + $this->systemPath = drupal_get_normal_path($path); + + // @todo Remove this unholy blight upon the world. + // Drupal uses $_GET['q'] directly in over 100 places at present, + // including writing back to it at times. Those are all critical bugs, + // even by Drupal 7 standards, but as many of the places that it does so + // are slated to be rewritten anyway we will save time and include this + // temporary hack. Removal of this line is a critical, Drupal-release + // blocking bug. + $_GET['q'] = $this->systemPath; + } + + return $this->systemPath; + } + + /** + * Set the systemPath(). + * + * @deprecated + * + * Never ever use this or a 1000 kittens will + * hide under your bed, scream all night and haunt your dreams. + */ + function setSystemPath($path) { + $this->systemPath = $path; + } +} diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index a712c49..7d3552f 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -931,7 +931,7 @@ function locale_block_info() { */ function locale_block_view($type) { if (language_multilingual()) { - $path = drupal_is_front_page() ? '' : $_GET['q']; + $path = drupal_is_front_page() ? '' : request()->systemPath(); $links = language_negotiation_get_switch_links($type, $path); if (isset($links->links)) { diff --git a/core/modules/simpletest/simpletest.info b/core/modules/simpletest/simpletest.info index bbe65f0..8faf836 100644 --- a/core/modules/simpletest/simpletest.info +++ b/core/modules/simpletest/simpletest.info @@ -29,6 +29,7 @@ files[] = tests/module.test files[] = tests/password.test files[] = tests/path.test files[] = tests/registry.test +files[] = tests/request.test files[] = tests/schema.test files[] = tests/session.test files[] = tests/symfony.test diff --git a/core/modules/simpletest/tests/form_test.module b/core/modules/simpletest/tests/form_test.module index e1e2435..28ba566 100644 --- a/core/modules/simpletest/tests/form_test.module +++ b/core/modules/simpletest/tests/form_test.module @@ -1526,7 +1526,7 @@ function form_test_clicked_button($form, &$form_state) { // 'image_button', and a 'button' with #access=FALSE. This enables form.test // to test a variety of combinations. $i=0; - $args = array_slice(arg(), 2); + $args = array_slice(request()->pathElements(), 2); foreach ($args as $arg) { $name = 'button' . ++$i; // 's', 'b', or 'i' in the argument define the button type wanted. diff --git a/core/modules/simpletest/tests/menu.test b/core/modules/simpletest/tests/menu.test index 5a173b1..5630ff4 100644 --- a/core/modules/simpletest/tests/menu.test +++ b/core/modules/simpletest/tests/menu.test @@ -236,15 +236,15 @@ class MenuRouterTestCase extends DrupalWebTestCase { /** * Test that an authenticated user hitting 'user/login' gets redirected to - * 'user' and 'user/register' gets redirected to the user edit page. + * 'user/uid' and 'user/register' gets redirected to the user edit page. */ function testAuthUserUserLogin() { $loggedInUser = $this->drupalCreateUser(array()); $this->drupalLogin($loggedInUser); $this->DrupalGet('user/login'); - // Check that we got to 'user'. - $this->assertTrue($this->url == url('user', array('absolute' => TRUE)), t("Logged-in user redirected to q=user on accessing q=user/login")); + // Check that we got to 'user/uid'. + $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid, array('absolute' => TRUE)), t("Logged-in user redirected to q=user/" . $this->loggedInUser->uid . " on accessing q=user/login")); // user/register should redirect to user/UID/edit. $this->DrupalGet('user/register'); diff --git a/core/modules/simpletest/tests/request.test b/core/modules/simpletest/tests/request.test new file mode 100644 index 0000000..93e215d --- /dev/null +++ b/core/modules/simpletest/tests/request.test @@ -0,0 +1,92 @@ + 'Request', + 'description' => 'Tests for request information', + 'group' => 'Request', + ); + } + + public function setUp() { + parent::setUp(); + + require_once DRUPAL_ROOT . '/core/lib/Drupal/Core/Request.php'; + + // This unfortunately necessary because Drupal's registry throws a database + // exception when testing class_exist on non-existent classes in unit tests. + // This is the point of one of our tests so we have to remove the registry + // prior to running our tests. + spl_autoload_unregister('drupal_autoload_class'); + spl_autoload_unregister('drupal_autoload_interface'); + } + + public function tearDown() { + // Re-register drupal's autoload classes. See setUp for the reasoning. + spl_autoload_register('drupal_autoload_class'); + spl_autoload_register('drupal_autoload_interface'); + + parent::tearDown(); + } + + /** + * Test the request path logic. + */ + function testRequestPathFromPath() { + $request = Request::create('/' . $this->testPath); + + $this->assertEqual($request->requestPath(), $this->testPath, t('Correct request path derived from path.')); + } + + /** + * Test the request path logic. + */ + function testRequestPathFromQuery() { + $request = Request::create('index.php', 'GET', array('q' => $this->testPath)); + + $this->assertEqual($request->requestPath(), $this->testPath, t('Correct request path derived from arguments.')); + } + + /** + * Test the path fragment logic. + */ + function testPathFragment() { + $request = Request::create('/' . $this->testPath); + + $this->assertEqual($request->pathElement(0), 'foo', t('Correct first path element returned.')); + $this->assertEqual($request->pathElement(1), 'bar', t('Correct second path element returned.')); + $this->assertNull($request->pathElement(2), t('Null returned for non-existent path element.')); + } + + /** + * Test the path fragment logic. + */ + function testPathFragments() { + $request = Request::create('/' . $this->testPath); + + $elements = $request->pathElements(); + + $this->assertEqual($elements[0], 'foo', t('Correct first path element returned.')); + $this->assertEqual($elements[1], 'bar', t('Correct second path element returned.')); + $this->assertTrue(empty($elements[2]), t('Null returned for non-existent path element.')); + } + + + // System path cannot be unit tested, because it relies on the database. + // @TODO: Add a unit test here once the path logic becomes an object and can + // be mocked. + +} diff --git a/core/modules/simpletest/tests/theme.test b/core/modules/simpletest/tests/theme.test index 21a69bd..1e0b1be 100644 --- a/core/modules/simpletest/tests/theme.test +++ b/core/modules/simpletest/tests/theme.test @@ -265,7 +265,7 @@ class ThemeFunctionsTestCase extends DrupalWebTestCase { // Required to verify the "active" class in expected links below, and // because the current path is different when running tests manually via // simpletest.module ('batch') and via the testing framework (''). - $_GET['q'] = variable_get('site_frontpage', 'user'); + request()->setSystemPath(variable_get('site_frontpage', 'user')); // Verify that a list of links is properly rendered. $variables = array(); diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc index 8239c53..f020d75 100644 --- a/core/modules/user/user.pages.inc +++ b/core/modules/user/user.pages.inc @@ -504,8 +504,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') { function user_page() { global $user; if ($user->uid) { - menu_set_active_item('user/' . $user->uid); - return menu_execute_active_handler(NULL, FALSE); + drupal_goto('user/' . $user->uid); } else { return drupal_get_form('user_login');