diff --git a/.htaccess b/.htaccess index a69bdd4..725897e 100644 --- a/.htaccess +++ b/.htaccess @@ -109,7 +109,7 @@ DirectoryIndex index.php index.html index.htm RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} !=/favicon.ico - RewriteRule ^ index.php [L] + RewriteRule ^(.*)$ index.php [L] # Rules to correctly serve gzip compressed CSS and JS files. # Requires both mod_rewrite and mod_headers to be enabled. diff --git a/core/authorize.php b/core/authorize.php index c6ba51d..d703b33 100644 --- a/core/authorize.php +++ b/core/authorize.php @@ -103,17 +103,6 @@ if (authorize_access_allowed()) { // Load the code that drives the authorize process. require_once DRUPAL_ROOT . '/core/includes/authorize.inc'; - // For the sake of Batch API and a few other low-level functions, we need to - // initialize the URL path into $_GET['q']. However, we do not want to raise - // our bootstrap level, nor do we want to call drupal_initialize_path(), - // since that is assuming that modules are loaded and invoking hooks. - // However, all we really care is if we're in the middle of a batch, in which - // case $_GET['q'] will already be set, we just initialize it to an empty - // string if it's not already defined. - if (!isset($_GET['q'])) { - $_GET['q'] = ''; - } - if (isset($_SESSION['authorize_operation']['page_title'])) { drupal_set_title($_SESSION['authorize_operation']['page_title']); } diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 83ddd30..0b07d8e 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -14,6 +14,8 @@ * @see batch_get() */ +use \Symfony\Component\HttpFoundation\JsonResponse; + /** * Loads a batch from the database. * @@ -77,7 +79,7 @@ function _batch_page() { case 'do': // JavaScript-based progress page callback. - _batch_do(); + $output = _batch_do(); break; case 'do_nojs': @@ -160,7 +162,7 @@ function _batch_do() { // Perform actual processing. list($percentage, $message) = _batch_process(); - drupal_json_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); + return new JsonResponse(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); } /** diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 6abe08a..f02edcb 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -4,6 +4,7 @@ use Drupal\Core\Database\Database; use Symfony\Component\ClassLoader\UniversalClassLoader; use Symfony\Component\ClassLoader\ApcUniversalClassLoader; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpFoundation\Request; /** * @file @@ -551,12 +552,8 @@ 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(); + // @todo Refactor with the Symfony Request object. + _current_path(request_path()); // Enforce E_STRICT, but allow users to set levels not part of E_STRICT. error_reporting(E_STRICT | E_ALL | error_reporting()); @@ -597,7 +594,7 @@ function drupal_valid_http_host($host) { * Sets the base URL, cookie domain, and session name from configuration. */ function drupal_settings_initialize() { - global $base_url, $base_path, $base_root; + global $base_url, $base_path, $base_root, $script_path; // Export the following settings.php variables to the global namespace global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url, $config_directory_name, $config_signature_key; @@ -626,8 +623,8 @@ function drupal_settings_initialize() { $base_url = $base_root; - // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not - // be modified by a visitor. + // For a request URI of '/index.php/foo', $_SERVER['SCRIPT_NAME'] is + // '/index.php', whereas $_SERVER['PHP_SELF'] is '/index.php/foo'. if ($dir = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')) { // Remove "core" directory if present, allowing install.php, update.php, // cron.php and others to auto-detect a base path. @@ -648,6 +645,32 @@ function drupal_settings_initialize() { $base_secure_url = str_replace('http://', 'https://', $base_url); $base_insecure_url = str_replace('https://', 'http://', $base_url); + // Determine the path of the script relative to the base path, and add a + // trailing slash. This is needed for creating URLs to Drupal pages. + if (!isset($script_path)) { + $script_path = ''; + // We don't expect scripts outside of the base path, but sanity check + // anyway. + if (strpos($_SERVER['SCRIPT_NAME'], $base_path) === 0) { + $script_path = substr($_SERVER['SCRIPT_NAME'], strlen($base_path)) . '/'; + // If the request URI does not contain the script name, then clean URLs + // are in effect and the script path can be similarly dropped from URL + // generation. For servers that don't provide $_SERVER['REQUEST_URI'], we + // do not know the actual URI requested by the client, and request_uri() + // returns a URI with the script name, resulting in non-clean URLs unless + // there's other code that intervenes. + if (strpos(request_uri(TRUE) . '/', $base_path . $script_path) !== 0) { + $script_path = ''; + } + // @todo Temporary BC for install.php, update.php, and other scripts. + // - http://drupal.org/node/1547184 + // - http://drupal.org/node/1546082 + if ($script_path !== 'index.php/') { + $script_path = ''; + } + } + } + if ($cookie_domain) { // If the user specifies the cookie domain, also use it for session name. $session_name = $cookie_domain; @@ -1488,13 +1511,15 @@ function drupal_validate_utf8($text) { * * Because $_SERVER['REQUEST_URI'] is only available on Apache, we generate an * equivalent using other environment variables. + * + * @todo The above comment is incorrect: http://drupal.org/node/1547294. */ -function request_uri() { +function request_uri($omit_query_string = FALSE) { if (isset($_SERVER['REQUEST_URI'])) { $uri = $_SERVER['REQUEST_URI']; } else { - if (isset($_SERVER['argv'])) { + if (isset($_SERVER['argv'][0])) { $uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['argv'][0]; } elseif (isset($_SERVER['QUERY_STRING'])) { @@ -1507,7 +1532,28 @@ function request_uri() { // Prevent multiple slashes to avoid cross site requests via the Form API. $uri = '/' . ltrim($uri, '/'); - return $uri; + return $omit_query_string ? strtok($uri, '?') : $uri; +} + +/** + * Returns the current global reuqest object. + * + * @todo Replace this function with a proper dependency injection container. + * + * @staticvar Request $request + * @param Request $new_request + * The new request object to store. If you are not index.php, you probably + * should not be using this parameter. + * @return Request + * The current request object. + */ +function request(Request $new_request = NULL) { + static $request; + + if ($new_request) { + $request = $new_request; + } + return $request; } /** @@ -2181,7 +2227,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']; + _current_path($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 @@ -2418,9 +2464,9 @@ 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'])) { + if ($exclude_paths && !preg_match($exclude_paths, request_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, request_path())) { drupal_add_http_header('Status', '404 Not Found'); $fast_404_html = variable_get('404_fast_html', '404 Not Found

Not Found

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

'); // Replace @path in the variable with the page path. @@ -2655,42 +2701,45 @@ function request_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. + // Get the part of the URI between the base path of the Drupal installation + // and the query string, and unescape it. + $request_path = request_uri(TRUE); + $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); + $path = substr(urldecode($request_path), $base_path_len + 1); + + // Depending on server configuration, the URI might or might not include the + // script name. For example, the front page might be accessed as + // http://example.com or as http://example.com/index.php, and the "user" + // page might be accessed as http://example.com/user or as + // http://example.com/index.php/user. Strip the script name from $path. + $script = basename($_SERVER['SCRIPT_NAME']); + if ($path == $script) { $path = ''; } + elseif (strpos($path, $script . '/') === 0) { + $path = substr($path, strlen($script) + 1); + } - // 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']. + // Extra slashes can appear in URLs or under some conditions, added by Apache, + // so normalize. $path = trim($path, '/'); return $path; } /** + * @todo This is a temporary function pending refactoring Drupal to use + * Symfony's Request object exclusively. + */ +function _current_path($path = NULL) { + static $current_path = ''; + if (isset($path)) { + $current_path = $path; + } + return $current_path; +} + +/** * Returns a component of the current Drupal path. * * When viewing a page at the path "admin/structure/types", for example, arg(0) @@ -2726,7 +2775,9 @@ function arg($index = NULL, $path = NULL) { $arguments = &$drupal_static_fast['arguments']; if (!isset($path)) { - $path = $_GET['q']; + // @todo The public function current_path() is not available during early + // bootstrap. + $path = _current_path(); } if (!isset($arguments[$path])) { $arguments[$path] = explode('/', $path); diff --git a/core/includes/common.inc b/core/includes/common.inc index 353a9b5..ae256c6 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1,5 +1,7 @@ $base_root . request_uri(), 'data' => array( 'path' => $_GET['q'], - 'body' => ob_get_clean(), + 'body' => $response_body, 'title' => drupal_get_title(), 'headers' => array(), ), diff --git a/core/includes/file.inc b/core/includes/file.inc index c4356a9..7edd4be 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -5,6 +5,9 @@ * API for handling file uploads and server file management. */ +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpFoundation\StreamedResponse; use Drupal\Core\StreamWrapper\LocalStream; /** @@ -2031,7 +2034,7 @@ function file_transfer($uri, $headers) { * @see system_menu() */ function file_download() { - // Merge remainder of arguments from GET['q'], into relative file path. + // Merge remaining path arguments into relative file path. $args = func_get_args(); $scheme = array_shift($args); $target = implode('/', $args); @@ -2046,18 +2049,27 @@ function file_download() { $function = $module . '_file_download'; $result = $function($uri); if ($result == -1) { - return drupal_access_denied(); + throw new AccessDeniedHttpException(); } if (isset($result) && is_array($result)) { $headers = array_merge($headers, $result); } } if (count($headers)) { - file_transfer($uri, $headers); + return new StreamedResponse(function() use ($uri) { + $scheme = file_uri_scheme($uri); + // Transfer file in 1024 byte chunks to save memory usage. + if ($scheme && file_stream_wrapper_valid_scheme($scheme) && $fd = fopen($uri, 'rb')) { + while (!feof($fd)) { + print fread($fd, 1024); + } + fclose($fd); + } + }, 200, $headers); } - return drupal_access_denied(); + throw new AccessDeniedHttpException(); } - return drupal_not_found(); + throw new NotFoundHttpException(); } diff --git a/core/includes/form.inc b/core/includes/form.inc index 6d93420..bc71b9e 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -1248,7 +1248,7 @@ function drupal_redirect_form($form_state) { $function($form_state['redirect']); } } - drupal_goto($_GET['q']); + drupal_goto(current_path()); } } @@ -4682,7 +4682,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd 'progressive' => TRUE, 'url' => $url, 'url_options' => array(), - 'source_url' => $_GET['q'], + 'source_url' => current_path(), '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 125852a..5cb3399 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1522,11 +1522,8 @@ function install_configure_form($form, &$form_state, &$install_state) { // We add these strings as settings because JavaScript translation does not // work on install time. drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))), 'setting'); - drupal_add_js('jQuery(function () { Drupal.cleanURLsInstallCheck(); });', 'inline'); // Add JS to show / hide the 'Email administrator about site updates' elements drupal_add_js('jQuery(function () { Drupal.hideEmailAdministratorCheckbox() });', 'inline'); - // Build menu to allow clean URL check. - menu_rebuild(); // Cache a fully-built schema. This is necessary for any invocation of // index.php because: (1) setting cache table entries requires schema @@ -1534,8 +1531,7 @@ function install_configure_form($form, &$form_state, &$install_state) { // loaded, so (3) if there is no cached schema, drupal_get_schema() will // try to generate one but with no loaded modules will return nothing. // - // This logically could be done during the 'install_finished' task, but the - // clean URL check requires it now. + // @todo Move this to the 'install_finished' task? drupal_get_schema(NULL, TRUE); // Return the form. @@ -1827,12 +1823,6 @@ function _install_configure_form($form, &$form_state, &$install_state) { '#attributes' => array('class' => array('timezone-detect')), ); - $form['server_settings']['clean_url'] = array( - '#type' => 'hidden', - '#default_value' => 0, - '#attributes' => array('id' => 'edit-clean-url', 'class' => array('install')), - ); - $form['update_notifications'] = array( '#type' => 'fieldset', '#title' => st('Update notifications'), @@ -1903,10 +1893,6 @@ function install_configure_form_submit($form, &$form_state) { $user = user_load(1); user_login_finalize(); - if (isset($form_state['values']['clean_url'])) { - variable_set('clean_url', $form_state['values']['clean_url']); - } - // Record when this install ran. variable_set('install_time', $_SERVER['REQUEST_TIME']); } diff --git a/core/includes/install.inc b/core/includes/install.inc index 72adf1c..283e31c 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -121,8 +121,7 @@ function drupal_detect_baseurl($file = 'core/install.php') { $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://'; $host = $_SERVER['SERVER_NAME']; $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':' . $_SERVER['SERVER_PORT']); - $uri = preg_replace("/\?.*/", '', $_SERVER['REQUEST_URI']); - $dir = str_replace("/$file", '', $uri); + $dir = str_replace("/$file", '', $_SERVER['SCRIPT_NAME']); return "$proto$host$port$dir"; } diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 96791e3..cccc217 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -3813,7 +3813,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 && request()->attributes->get('system_path') != '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 c579e7e..201c5ad 100644 --- a/core/includes/pager.inc +++ b/core/includes/pager.inc @@ -297,7 +297,7 @@ function pager_default_initialize($total, $limit, $element = 0) { function pager_get_query_parameters() { $query = &drupal_static(__FUNCTION__); if (!isset($query)) { - $query = drupal_get_query_parameters($_GET, array('q', 'page')); + $query = drupal_get_query_parameters($_GET, array('page')); } return $query; } @@ -638,7 +638,7 @@ function theme_pager_link($variables) { // none of the pager links is active at any time - but it should still be // possible to use l() here. // @see http://drupal.org/node/1410574 - $attributes['href'] = url($_GET['q'], array('query' => $query)); + $attributes['href'] = url(current_path(), array('query' => $query)); return '' . check_plain($text) . ''; } diff --git a/core/includes/path.inc b/core/includes/path.inc index d6a0201..0c7e4d4 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -10,15 +10,24 @@ */ /** - * Initialize the $_GET['q'] variable to the proper normal path. + * Initializes the current path 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'); + // At this point, the current path is either the request path (due to + // drupal_environment_initialize()) or some modified version of it due to + // other bootstrap code (e.g., language negotiation), but it has not yet been + // normalized by drupal_get_normal_path(). + $path = _current_path(); + + // If on the front page, resolve to the front page path, including for calls + // to current_path() while drupal_get_normal_path() is in progress. + if (empty($path)) { + $path = variable_get('site_frontpage', 'user'); + _current_path($path); } - $_GET['q'] = drupal_get_normal_path($_GET['q']); + + // Normalize the path. + _current_path(drupal_get_normal_path($path)); } /** @@ -234,7 +243,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 = current_path(); } $result = $path; if ($alias = drupal_lookup_path('alias', $path, $langcode)) { @@ -289,9 +298,7 @@ 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, - // we can check it against the 'site_frontpage' variable. - $is_front_page = ($_GET['q'] == variable_get('site_frontpage', 'user')); + $is_front_page = (current_path() == variable_get('site_frontpage', 'user')); } return $is_front_page; @@ -340,9 +347,9 @@ function drupal_match_path($path, $patterns) { * - 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. + * This function is not available in hook_boot() so use request_path() 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 + * request_path() will contain "path/alias". If "node/306" is needed, calling * drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available. * * @return @@ -351,7 +358,7 @@ function drupal_match_path($path, $patterns) { * @see request_path() */ function current_path() { - return $_GET['q']; + return _current_path(); } /** diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc index 3c70b96..a900a61 100644 --- a/core/includes/tablesort.inc +++ b/core/includes/tablesort.inc @@ -150,7 +150,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, current_path(), array('attributes' => array('title' => $title), 'query' => array_merge($ts['query'], array('sort' => $ts['sort'], 'order' => $cell['data'])), 'html' => TRUE)); unset($cell['field'], $cell['sort']); } @@ -193,7 +193,7 @@ function tablesort_cell($cell, $header, $ts, $i) { * page request except for those pertaining to table sorting. */ function tablesort_get_query_parameters() { - return drupal_get_query_parameters($_GET, array('q', 'sort', 'order')); + return drupal_get_query_parameters($_GET, array('sort', 'order')); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 99e872e..aadafd8 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1743,7 +1743,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'] == current_path() || ($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'; diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php new file mode 100644 index 0000000..b87b33c --- /dev/null +++ b/core/lib/Drupal/Core/ContentNegotiation.php @@ -0,0 +1,53 @@ +. + if ($request->get('ajax_iframe_upload', FALSE)) { + return 'iframeupload'; + } + + // AJAX calls need to be run through ajax rendering functions + elseif ($request->isXmlHttpRequest()) { + return 'ajax'; + } + + foreach ($request->getAcceptableContentTypes() as $mime_type) { + $format = $request->getFormat($mime_type); + if (!is_null($format)) { + return $format; + } + } + + // Do HTML last so that it always wins. + return 'html'; + } + +} + diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php new file mode 100644 index 0000000..26ff906 --- /dev/null +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -0,0 +1,88 @@ +dispatcher = $dispatcher; + $this->resolver = $resolver; + + $context = new RequestContext(); + $this->matcher = new UrlMatcher($context); + $this->dispatcher->addSubscriber(new RouterListener($this->matcher)); + + $negotiation = new ContentNegotiation(); + + // @todo Make this extensible rather than just hard coding some. + // @todo Add a subscriber to handle other things, too, like our Ajax + // replacement system. + $this->dispatcher->addSubscriber(new ViewSubscriber($negotiation)); + $this->dispatcher->addSubscriber(new AccessSubscriber()); + $this->dispatcher->addSubscriber(new MaintenanceModeSubscriber()); + $this->dispatcher->addSubscriber(new PathSubscriber()); + $this->dispatcher->addSubscriber(new LegacyControllerSubscriber()); + $this->dispatcher->addSubscriber(new RequestCloseSubscriber()); + + // Some other form of error occured that wasn't handled by another kernel + // listener. That could mean that it's a method/mime-type/error + // combination that is not accounted for, or some other type of error. + // Either way, treat it as a server-level error and return an HTTP 500. + // By default, this will be an HTML-type response because that's a decent + // best guess if we don't know otherwise. + $this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController($this, $negotiation), 'execute'))); + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php new file mode 100644 index 0000000..04eac0a --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php @@ -0,0 +1,54 @@ +getRequest()->attributes->get('drupal_menu_item'); + + if (isset($router_item['access']) && !$router_item['access']) { + throw new AccessDeniedHttpException(); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php new file mode 100644 index 0000000..cc4da15 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php @@ -0,0 +1,64 @@ +getRequest()->attributes->get('drupal_menu_item'); + $controller = $event->getController(); + + // This BC logic applies only to functions. Otherwise, skip it. + if (is_string($controller) && function_exists($controller)) { + $new_controller = function() use ($router_item) { + return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']); + }; + $event->setController($new_controller); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::CONTROLLER][] = array('onKernelControllerLegacy', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php new file mode 100644 index 0000000..2befe92 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -0,0 +1,59 @@ +getRequest()->attributes->get('system_path'); + drupal_alter('menu_site_status', $status, $read_only_path); + + // Only continue if the site is online. + if ($status != MENU_SITE_ONLINE) { + // Deliver the 503 page. + drupal_maintenance_theme(); + drupal_set_title(t('Site under maintenance')); + $content = theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))))); + $response = new Response('Service unavailable', 503); + $response->setContent($content); + $event->setResponse($response); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestMaintenanceModeCheck', 40); + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php new file mode 100644 index 0000000..dc5059b --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -0,0 +1,68 @@ +getRequest(); + + $path = ltrim($request->getPathInfo(), '/'); + + if (empty($path)) { + // @todo Temporary hack. Fix when configuration is injectable. + $path = variable_get('site_frontpage', 'user'); + } + $system_path = drupal_get_normal_path($path); + + $request->attributes->set('system_path', $system_path); + + // @todo Remove this line. + // 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'] = $system_path; + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php new file mode 100644 index 0000000..a6f9ad8 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php @@ -0,0 +1,66 @@ +getResponse(); + $config = config('system.performance'); + + if ($config->get('cache') && ($cache = drupal_page_set_cache($response->getContent()))) { + drupal_serve_page_from_cache($cache); + } + else { + ob_flush(); + } + + _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); + drupal_cache_system_paths(); + module_implements_write_cache(); + system_run_automated_cron(); + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::TERMINATE][] = array('onTerminate'); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php new file mode 100644 index 0000000..647b641 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php @@ -0,0 +1,131 @@ +negotiation = $negotiation; + } + + /** + * Processes a successful controller into an HTTP 200 response. + * + * Some controllers may not return a response object but simply the body of + * one. The VIEW event is called in that case, to allow us to mutate that + * body into a Response object. In particular we assume that the return + * from an JSON-type response is a JSON string, so just wrap it into a + * Response object. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onView(GetResponseEvent $event) { + + $request = $event->getRequest(); + + $method = 'on' . $this->negotiation->getContentType($request); + + if (method_exists($this, $method)) { + $event->setResponse($this->$method($event)); + } + else { + $event->setResponse(new Response('Unsupported Media Type', 415)); + } + } + + public function onJson(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + + //print_r($page_callback_result); + + $response = new JsonResponse(); + $response->setContent($page_callback_result); + + return $response; + } + + public function onAjax(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + + // Construct the response content from the page callback result. + $commands = ajax_prepare_response($page_callback_result); + $json = ajax_render($commands); + + // Build the actual response object. + $response = new JsonResponse(); + $response->setContent($json); + + return $response; + } + + public function onIframeUpload(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + + // Construct the response content from the page callback result. + $commands = ajax_prepare_response($page_callback_result); + $json = ajax_render($commands); + + // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification + // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into + // links. This corrupts the JSON response. Protect the integrity of the + // JSON data by making it the value of a textarea. + // @see http://malsup.com/jquery/form/#file-upload + // @see http://drupal.org/node/1009382 + $html = ''; + + return new Response($html); + } + + /** + * Processes a successful controller into an HTTP 200 response. + * + * Some controllers may not return a response object but simply the body of + * one. The VIEW event is called in that case, to allow us to mutate that + * body into a Response object. In particular we assume that the return + * from an HTML-type response is a render array from a legacy page callback + * and render it. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function onHtml(GetResponseEvent $event) { + $page_callback_result = $event->getControllerResult(); + return new Response(drupal_render_page($page_callback_result)); + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::VIEW][] = array('onView'); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php new file mode 100644 index 0000000..3e92e83 --- /dev/null +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -0,0 +1,211 @@ +kernel = $kernel; + $this->negotiation = $negotiation; + } + + /** + * Handles an exception on a request. + * + * @param FlattenException $exception + * The flattened exception. + * @param Request $request + * The request that generated the exception. + * @return \Symfony\Component\HttpFoundation\Response + * A response object to be sent to the server. + */ + public function execute(FlattenException $exception, Request $request) { + + $method = 'on' . $exception->getStatusCode() . $this->negotiation->getContentType($request); + + if (method_exists($this, $method)) { + return $this->$method($exception, $request); + } + + return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode()); + + } + + /** + * Processes a MethodNotAllowed exception into an HTTP 405 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on405Html(FlattenException $exception, Request $request) { + $event->setResponse(new Response('Method Not Allowed', 405)); + } + + /** + * Processes an AccessDenied exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on403Html(FlattenException $exception, Request $request) { + $system_path = $request->attributes->get('system_path'); + watchdog('access denied', $system_path, NULL, WATCHDOG_WARNING); + + $path = drupal_get_normal_path(variable_get('site_403', '')); + if ($path && $path != $system_path) { + // Keep old path for reference, and to allow forms to redirect to it. + if (!isset($_GET['destination'])) { + $_GET['destination'] = $system_path; + } + + $subrequest = Request::create('/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all()); + + $response = $this->kernel->handle($subrequest, DrupalKernel::SUB_REQUEST); + $response->setStatusCode(403, 'Access denied'); + } + else { + $response = new Response('Access Denied', 403); + + // @todo Replace this block with something cleaner. + $return = t('You are not authorized to access this page.'); + drupal_set_title(t('Access denied')); + drupal_set_page_content($return); + $page = element_info('page'); + $content = drupal_render_page($page); + + $response->setContent($content); + } + + return $response; + } + + /** + * Processes a NotFound exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on404Html(FlattenException $exception, Request $request) { + watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); + + // Check for and return a fast 404 page if configured. + // @todo Inline this rather than using a function. + drupal_fast_404(); + + $system_path = $request->attributes->get('system_path'); + + // Keep old path for reference, and to allow forms to redirect to it. + if (!isset($_GET['destination'])) { + $_GET['destination'] = $system_path; + } + + $path = drupal_get_normal_path(variable_get('site_404', '')); + if ($path && $path != $system_path) { + // @todo: Um, how do I specify an override URL again? Totally not clear. + // Do that and sub-call the kernel rather than using meah(). + // @todo: The create() method expects a slash-prefixed path, but we + // store a normal system path in the site_404 variable. + $subrequest = Request::create('/' . $path, 'get', array(), $request->cookies->all(), array(), $request->server->all()); + + $response = $this->kernel->handle($subrequest, HttpKernelInterface::SUB_REQUEST); + $response->setStatusCode(404, 'Not Found'); + } + else { + $response = new Response('Not Found', 404); + + // @todo Replace this block with something cleaner. + $return = t('The requested page "@path" could not be found.', array('@path' => $request->getPathInfo())); + drupal_set_title(t('Page not found')); + drupal_set_page_content($return); + $page = element_info('page'); + $content = drupal_render_page($page); + + $response->setContent($content); + } + + return $response; + } + + /** + * Processes an AccessDenied exception into an HTTP 403 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on403Json(FlattenException $exception, Request $request) { + $response = new JsonResponse(); + $response->setStatusCode(403, 'Access Denied'); + return $response; + } + + /** + * Processes a NotFound exception into an HTTP 404 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on404Json(FlattenException $exception, Request $request) { + $response = new JsonResponse(); + $response->setStatusCode(404, 'Not Found'); + return $response; + } + + /** + * Processes a MethodNotAllowed exception into an HTTP 405 response. + * + * @param GetResponseEvent $event + * The Event to process. + */ + public function on405Json(FlattenException $exception, Request $request) { + $response = new JsonResponse(); + $response->setStatusCode(405, 'Method Not Allowed'); + return $response; + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php index 0cd76e0..1b4abc9 100644 --- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php +++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php @@ -44,7 +44,7 @@ abstract class LocalStream implements StreamWrapperInterface { /** * Gets the path that the wrapper is responsible for. - * @TODO: Review this method name in D8 per http://drupal.org/node/701358 + * @todo: Review this method name in D8 per http://drupal.org/node/701358 * * @return string * String specifying the path. diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php index 2dca5ba..ad2213a 100644 --- a/core/lib/Drupal/Core/Updater/Updater.php +++ b/core/lib/Drupal/Core/Updater/Updater.php @@ -213,7 +213,7 @@ class Updater { $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); // Run the updates. - // @TODO: decide if we want to implement this. + // @todo: decide if we want to implement this. $this->postUpdate(); // For now, just return a list of links of things to do. @@ -252,7 +252,7 @@ class Updater { $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); // Potentially enable something? - // @TODO: decide if we want to implement this. + // @todo: decide if we want to implement this. $this->postInstall(); // For now, just return a list of links of things to do. return $this->postInstallTasks(); diff --git a/core/lib/Drupal/Core/UrlMatcher.php b/core/lib/Drupal/Core/UrlMatcher.php new file mode 100644 index 0000000..585fc45 --- /dev/null +++ b/core/lib/Drupal/Core/UrlMatcher.php @@ -0,0 +1,111 @@ +context = $context; + } + + /** + * {@inheritDoc} + * + * @api + */ + public function match($pathinfo) { + + $this->allow = array(); + + // Symfony uses a prefixing / but we don't yet. + $dpathinfo = ltrim($pathinfo, '/'); + + // Do our fancy frontpage logic. + if (empty($dpathinfo)) { + $dpathinfo = variable_get('site_frontpage', 'user'); + $pathinfo = '/' . $dpathinfo; + } + + if ($router_item = $this->matchDrupalItem($dpathinfo)) { + $ret = $this->convertDrupalItem($router_item); + // Stash the router item in the attributes while we're transitioning. + $ret['drupal_menu_item'] = $router_item; + + // Most legacy controllers (aka page callbacks) are in a separate file, + // so we have to include that. + if ($router_item['include_file']) { + require_once DRUPAL_ROOT . '/' . $router_item['include_file']; + } + + return $ret; + } + + // This matcher doesn't differentiate by method, so don't bother with those + // exceptions. + throw new ResourceNotFoundException(); + } + + /** + * Get a drupal menu item. + * + * @todo Make this return multiple possible candidates for the resolver to + * consider. + * + * @param string $path + * The path being looked up by + */ + protected function matchDrupalItem($path) { + // For now we can just proxy our procedural method. At some point this will + // become more complicated because we'll need to get back candidates for a + // path and them resolve them based on things like method and scheme which + // we currently can't do. + return menu_get_item($path); + } + + protected function convertDrupalItem($router_item) { + $route = array( + '_controller' => $router_item['page_callback'] + ); + + // Place argument defaults on the route. + // @todo: For some reason drush test runs have a serialized page_arguments + // but HTTP requests are unserialized. Hack to get around this for now. + // This might be because page arguments aren't unserialized in + // menu_get_item() when the access is denied. + !is_array($router_item['page_arguments']) ? $page_arguments = unserialize($router_item['page_arguments']) : $page_arguments = $router_item['page_arguments']; + foreach ($page_arguments as $k => $v) { + $route[$k] = $v; + } + return $route; + return new Route($router_item['href'], $route); + } +} diff --git a/core/misc/ajax.js b/core/misc/ajax.js index c35241c..072cd2b 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -115,16 +115,12 @@ Drupal.ajax = function (base, element, element_settings) { // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let // the server detect when it needs to degrade gracefully. - // There are five scenarios to check for: + // There are four scenarios to check for: // 1. /nojs/ // 2. /nojs$ - The end of a URL string. - // 3. /nojs? - Followed by a query (with clean URLs enabled). - // E.g.: path/nojs?destination=foobar - // 4. /nojs& - Followed by a query (without clean URLs enabled). - // E.g.: ?q=path/nojs&destination=foobar - // 5. /nojs# - Followed by a fragment. - // E.g.: path/nojs#myfragment - this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1'); + // 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar). + // 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment). + this.url = element_settings.url.replace(/\/nojs(\/|$|\?|#)/g, '/ajax$1'); this.wrapper = '#' + element_settings.wrapper; // If there isn't a form, jQuery.ajax() will be used instead, allowing us to diff --git a/core/misc/drupal.js b/core/misc/drupal.js index c7917b0..5bbbf51 100644 --- a/core/misc/drupal.js +++ b/core/misc/drupal.js @@ -202,6 +202,13 @@ Drupal.t = function (str, args, options) { }; /** + * Returns the URL to a Drupal page. + */ +Drupal.url = function (path) { + return Drupal.settings.basePath + Drupal.settings.scriptPath + path; +} + +/** * Format a string containing a count of items. * * This function ensures that the string is pluralized correctly. Since Drupal.t() is diff --git a/core/misc/timezone.js b/core/misc/timezone.js index 62b7d4b..6f82bc1 100644 --- a/core/misc/timezone.js +++ b/core/misc/timezone.js @@ -50,8 +50,8 @@ Drupal.behaviors.setTimezone = { var element = this; $.ajax({ async: false, - url: settings.basePath, - data: { q: path, date: dateString }, + url: Drupal.url(path), + data: { date: dateString }, dataType: 'json', success: function (data) { if (data) { diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc index 09da1cf..ac868b4 100644 --- a/core/modules/aggregator/aggregator.admin.inc +++ b/core/modules/aggregator/aggregator.admin.inc @@ -401,11 +401,9 @@ function _aggregator_parse_opml($opml) { * An object describing the feed to be refreshed. * * @see aggregator_menu() + * @see aggregator_admin_refresh_feed_access() */ function aggregator_admin_refresh_feed($feed) { - if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'aggregator/update/' . $feed->fid)) { - return MENU_ACCESS_DENIED; - } aggregator_refresh($feed); drupal_goto('admin/config/services/aggregator'); } diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index caac279..5efc773 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -138,7 +138,8 @@ function aggregator_menu() { 'title' => 'Update items', 'page callback' => 'aggregator_admin_refresh_feed', 'page arguments' => array(5), - 'access arguments' => array('administer news feeds'), + 'access callback' => 'aggregator_admin_refresh_feed_access', + 'access arguments' => array(5), 'file' => 'aggregator.admin.inc', ); $items['admin/config/services/aggregator/list'] = array( @@ -796,3 +797,23 @@ function aggregator_preprocess_block(&$variables) { $variables['attributes_array']['role'] = 'complementary'; } } + +/** + * Access callback: Determines if feed refresh is accessible. + * + * @param $feed + * An object describing the feed to be refreshed. + * + * @see aggregator_admin_refresh_feed() + * @see aggregator_menu() + */ +function aggregator_admin_refresh_feed_access($feed) { + if (!user_access('administer news feeds')) { + return FALSE; + } + $token = request()->query->get('token'); + if (!isset($token) || !drupal_valid_token($token, 'aggregator/update/' . $feed->fid)) { + return FALSE; + } + return TRUE; +} diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 069e879..5ce7b2a 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -836,13 +836,10 @@ function block_block_list_alter(&$blocks) { // with different case. Ex: /Page, /page, /PAGE. $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'])); - // 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); - } + // Compare the lowercase path alias (if any) and internal path. + $path = current_path(); + $path_alias = drupal_strtolower(drupal_get_path_alias($path)); + $page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($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. // When set to 1 (BLOCK_VISIBILITY_LISTED), it is displayed only on those diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc index d84b785..9758075 100644 --- a/core/modules/comment/comment.admin.inc +++ b/core/modules/comment/comment.admin.inc @@ -258,7 +258,7 @@ function comment_confirm_delete_page($cid) { if ($comment = comment_load($cid)) { return drupal_get_form('comment_confirm_delete', $comment); } - return MENU_NOT_FOUND; + drupal_not_found(); } /** diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 0302913..75942fc 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -277,7 +277,8 @@ function comment_menu() { 'title' => 'Approve', 'page callback' => 'comment_approve', 'page arguments' => array(1), - 'access arguments' => array('administer comments'), + 'access callback' => 'comment_approve_access', + 'access arguments' => array(1), 'file' => 'comment.pages.inc', 'weight' => 1, ); @@ -500,12 +501,10 @@ function comment_permalink($cid) { // Find the current display page for this comment. $page = comment_get_display_page($comment->cid, $node->type); - // Set $_GET['q'] and $_GET['page'] ourselves so that the node callback - // behaves as it would when visiting the page directly. - $_GET['q'] = 'node/' . $node->nid; - $_GET['page'] = $page; - // Return the node view, this will show the correct comment in context. + // @todo Refactor to use Symfony's Request object. + _current_path('node/' . $node->nid); + $_GET['page'] = $page; return menu_execute_active_handler('node/' . $node->nid, FALSE); } drupal_not_found(); @@ -2516,3 +2515,23 @@ function comment_file_download_access($field, $entity_type, $entity) { return FALSE; } } + +/** + * Access callback: Determines if comment approval is accessible. + * + * @param $cid + * A comment identifier. + * + * @see comment_approve() + * @see comment_menu() + */ +function comment_approve_access($cid) { + if (!user_access('administer comments')) { + return FALSE; + } + $token = request()->query->get('token'); + if (!isset($token) || !drupal_valid_token($token, "comment/$cid/approve")) { + return FALSE; + } + return TRUE; +} diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc index bac078b..59423ec 100644 --- a/core/modules/comment/comment.pages.inc +++ b/core/modules/comment/comment.pages.inc @@ -105,11 +105,9 @@ function comment_reply(Node $node, $pid = NULL) { * A comment identifier. * * @see comment_menu() + * @see comment_approve_access() */ function comment_approve($cid) { - if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], "comment/$cid/approve")) { - return MENU_ACCESS_DENIED; - } if ($comment = comment_load($cid)) { $comment->status = COMMENT_PUBLISHED; comment_save($comment); @@ -117,5 +115,5 @@ function comment_approve($cid) { drupal_set_message(t('Comment approved.')); drupal_goto('node/' . $comment->nid); } - return MENU_NOT_FOUND; + drupal_not_found(); } diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module index c695c7e..48c12d9 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(current_path(), array('absolute' => TRUE, 'language' => $language)), '!sender-name' => user_format_name($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/image/image.module b/core/modules/image/image.module index 9e13c85..8fd8cee 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -869,10 +869,10 @@ function image_style_url($style_name, $path) { $uri = image_style_path($style_name, $path); // If not using clean URLs, the image derivative callback is only available - // with the query string. If the file does not exist, use url() to ensure + // with the script path. If the file does not exist, use url() to ensure // that it is included. Once the file exists it's fine to fall back to the // actual file path, this avoids bootstrapping PHP once the files are built. - if (!variable_get('clean_url') && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { + if ($GLOBALS['script_path'] && file_uri_scheme($uri) == 'public' && !file_exists($uri)) { $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath(); return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE)); } diff --git a/core/modules/image/image.test b/core/modules/image/image.test index d3b3374..9e0bf6e 100644 --- a/core/modules/image/image.test +++ b/core/modules/image/image.test @@ -210,7 +210,7 @@ class ImageStylesPathAndUrlUnitTest extends DrupalWebTestCase { $generate_url = image_style_url($this->style_name, $original_uri); if (!$clean_url) { - $this->assertTrue(strpos($generate_url, '?q=') !== FALSE, 'When using non-clean URLS, the system path contains the query string.'); + $this->assertTrue(strpos($generate_url, 'index.php') !== FALSE, 'When using non-clean URLS, the system path contains the query string.'); } // Fetch the URL that generates the file. @@ -651,7 +651,7 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase { // sent by Drupal. $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png; name="' . $test_image->filename . '"', t('Content-Type header was sent.')); $this->assertEqual($this->drupalGetHeader('Content-Disposition'), 'inline; filename="' . $test_image->filename . '"', t('Content-Disposition header was sent.')); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'private', t('Cache-Control header was sent.')); + $this->assertTrue(strstr($this->drupalGetHeader('Cache-Control'),'private') !== FALSE, t('Cache-Control header was sent.')); // Log out and try to access the file. $this->drupalLogout(); diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc index b05a130..c124b9e 100644 --- a/core/modules/language/language.admin.inc +++ b/core/modules/language/language.admin.inc @@ -679,7 +679,7 @@ function language_negotiation_configure_url_form($form, &$form_state) { '#title' => t('%language (%langcode) path prefix', array('%language' => $language->name, '%langcode' => $language->langcode)), '#maxlength' => 64, '#default_value' => isset($prefixes[$langcode]) ? $prefixes[$langcode] : '', - '#field_prefix' => $base_url . '/' . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => $base_url . '/', ); $form['domain'][$langcode] = array( '#type' => 'textfield', diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 63b3743..c7d6f18 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -463,7 +463,7 @@ function language_block_info() { */ function language_block_view($type) { if (language_multilingual()) { - $path = drupal_is_front_page() ? '' : $_GET['q']; + $path = drupal_is_front_page() ? '' : current_path(); $links = language_negotiation_get_switch_links($type, $path); if (isset($links->links)) { diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc index 6269e8b..4d560e7 100644 --- a/core/modules/language/language.negotiation.inc +++ b/core/modules/language/language.negotiation.inc @@ -209,9 +209,11 @@ function language_from_url($languages) { switch (variable_get('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX)) { case 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); + // Language negotiation happens before the public function current_path() + // is available. + // @todo Refactor with Symfony's Request object. + list($language, $path) = language_url_split_prefix(_current_path(), $languages); + _current_path($path); if ($language !== FALSE) { $language_url = $language->langcode; } @@ -319,7 +321,6 @@ function language_switcher_session($type, $path) { $links = array(); $query = $_GET; - unset($query['q']); foreach ($languages as $language) { $langcode = $language->langcode; diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 4351342..3571660 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -197,7 +197,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(current_path(), 'admin/config/regional/date-time/formats') !== 0) { $languages = array($language_interface->langcode); // Setup appropriate date formats for this locale. diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test index fa78e14..ded11ef 100644 --- a/core/modules/locale/locale.test +++ b/core/modules/locale/locale.test @@ -2181,7 +2181,7 @@ class LocalePathFunctionalTest extends DrupalWebTestCase { // Test that both node titles link to our path alias. $this->drupalGet(''); - $custom_path_url = base_path() . (variable_get('clean_url', 0) ? $custom_path : '?q=' . $custom_path); + $custom_path_url = base_path() . $GLOBALS['script_path'] . $custom_path; $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title)); $this->assertTrue(!empty($elements), t('First node links to the path alias.')); $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title)); @@ -2651,7 +2651,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Check that the language switcher active link matches the given browser // language. - $args = array(':url' => base_path() . (!empty($GLOBALS['conf']['clean_url']) ? $langcode_browser_fallback : "?q=$langcode_browser_fallback")); + $args = array(':url' => base_path() . $GLOBALS['script_path'] . $langcode_browser_fallback); $fields = $this->xpath('//div[@id="block-language-language-interface"]//a[@class="language-link active" and starts-with(@href, :url)]', $args); $this->assertTrue($fields[0] == $languages[$langcode_browser_fallback]->name, t('The browser language is the URL active language')); @@ -2686,21 +2686,21 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { ); $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration')); - // Build the link we're going to test based on the clean url setting. - $link = (!empty($GLOBALS['conf']['clean_url'])) ? 'it.example.com/admin' : 'it.example.com/?q=admin'; + // Build the link we're going to test. + $link = 'it.example.com/admin'; global $is_https; - // Test URL in another language: http://it.example.com/?q=admin. + // Test URL in another language: http://it.example.com/admin. // Base path gives problems on the testbot, so $correct_link is hard-coded. // @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test). - $italian_url = url('admin', array('language' => $languages['it'])); + $italian_url = url('admin', array('language' => $languages['it'], 'script' => '')); $url_scheme = ($is_https) ? 'https://' : 'http://'; $correct_link = $url_scheme . $link; $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right url (@url) in accordance with the chosen language', array('@url' => $italian_url))); // Test https via options. variable_set('https', TRUE); - $italian_url = url('admin', array('https' => TRUE, 'language' => $languages['it'])); + $italian_url = url('admin', array('https' => TRUE, 'language' => $languages['it'], 'script' => '')); $correct_link = 'https://' . $link; $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right https url (via options) (@url) in accordance with the chosen language', array('@url' => $italian_url))); variable_set('https', FALSE); @@ -2708,7 +2708,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Test https via current url scheme. $temp_https = $is_https; $is_https = TRUE; - $italian_url = url('admin', array('language' => $languages['it'])); + $italian_url = url('admin', array('language' => $languages['it'], 'script' => '')); $correct_link = 'https://' . $link; $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right url (via current url scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url))); $is_https = $temp_https; @@ -2767,9 +2767,9 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase { * is actually not working. */ private function checkUrl($language, $message1, $message2) { - $options = array('language' => $language); + $options = array('language' => $language, 'script' => ''); $base_path = trim(base_path(), '/'); - $rewritten_path = trim(str_replace(array('?q=', $base_path), '', url('node', $options)), '/'); + $rewritten_path = trim(str_replace($base_path, '', url('node', $options)), '/'); $segments = explode('/', $rewritten_path, 2); $prefix = $segments[0]; $path = isset($segments[1]) ? $segments[1] : $prefix; diff --git a/core/modules/menu/menu.admin.js b/core/modules/menu/menu.admin.js index 4e5bf07..42d69d5 100644 --- a/core/modules/menu/menu.admin.js +++ b/core/modules/menu/menu.admin.js @@ -22,9 +22,8 @@ Drupal.menu_update_parent_list = function () { values.push(Drupal.checkPlain($.trim($(this).val()))); }); - var url = Drupal.settings.basePath + 'admin/structure/menu/parents'; $.ajax({ - url: location.protocol + '//' + location.host + url, + url: location.protocol + '//' + location.host + Drupal.url('admin/structure/menu/parents'), type: 'POST', data: {'menus[]' : values}, dataType: 'json', diff --git a/core/modules/openid/tests/openid_test.module b/core/modules/openid/tests/openid_test.module index 5bd2f4d..ac49dbd 100644 --- a/core/modules/openid/tests/openid_test.module +++ b/core/modules/openid/tests/openid_test.module @@ -94,19 +94,8 @@ function openid_test_yadis_xrds() { // Only respond to XRI requests for one specific XRI. The is used to verify // that the XRI has been properly encoded. The "+" sign in the _xrd_r query // parameter is decoded to a space by PHP. - if (arg(3) == 'xri') { - if (variable_get('clean_url', 0)) { - if (arg(4) != '@example*résumé;%25' || $_GET['_xrd_r'] != 'application/xrds xml') { - drupal_not_found(); - } - } - else { - // Drupal cannot properly emulate an XRI proxy resolver using unclean - // URLs, so the arguments gets messed up. - if (arg(4) . '/' . arg(5) != '@example*résumé;%25?_xrd_r=application/xrds xml') { - drupal_not_found(); - } - } + if (arg(3) == 'xri' && (arg(4) != '@example*résumé;%25' || $_GET['_xrd_r'] != 'application/xrds xml')) { + drupal_not_found(); } drupal_add_http_header('Content-Type', 'application/xrds+xml'); print ' diff --git a/core/modules/overlay/overlay-parent.js b/core/modules/overlay/overlay-parent.js index 19d2d64..aa90d9b 100644 --- a/core/modules/overlay/overlay-parent.js +++ b/core/modules/overlay/overlay-parent.js @@ -569,7 +569,7 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) { // If the link contains the overlay-restore class and the overlay-context // state is set, also update the parent window's location. var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string') - ? Drupal.settings.basePath + $.bbq.getState('overlay-context') + ? Drupal.url($.bbq.getState('overlay-context')) : null; href = this.fragmentizeLink($target.get(0), parentLocation); // Only override default behavior when left-clicking and user is not @@ -653,10 +653,10 @@ Drupal.overlay.eventhandlerOperateByURLFragment = function (event) { if (state) { // Append render variable, so the server side can choose the right // rendering and add child frame code to the page if needed. - var url = $.param.querystring(Drupal.settings.basePath + state, { render: 'overlay' }); + var url = $.param.querystring(Drupal.url(state), { render: 'overlay' }); this.open(url); - this.resetActiveClass(this.getPath(Drupal.settings.basePath + state)); + this.resetActiveClass(this.getPath(url)); } // If there is no overlay URL in the fragment and the overlay is (still) // open, close the overlay. @@ -688,7 +688,7 @@ Drupal.overlay.eventhandlerSyncURLFragment = function (event) { if (this.isOpen) { var expected = $.bbq.getState('overlay'); // This is just a sanity check, so we're comparing paths, not query strings. - if (this.getPath(Drupal.settings.basePath + expected) != this.getPath(this.iframeWindow.document.location)) { + if (this.getPath(Drupal.url(expected)) != this.getPath(this.iframeWindow.document.location)) { // There may have been a redirect inside the child overlay window that the // parent wasn't aware of. Update the parent URL fragment appropriately. var newLocation = Drupal.overlay.fragmentizeLink(this.iframeWindow.document.location); @@ -752,10 +752,8 @@ Drupal.overlay.fragmentizeLink = function (link, parentLocation) { return link.href; } - // Determine the link's original destination. Set ignorePathFromQueryString to - // true to prevent transforming this link into a clean URL while clean URLs - // may be disabled. - var path = this.getPath(link, true); + // Determine the link's original destination. + var path = this.getPath(link); // Preserve existing query and fragment parameters in the URL, except for // "render=overlay" which is re-added in Drupal.overlay.eventhandlerOperateByURLFragment. var destination = path + link.search.replace(/&?render=overlay/, '').replace(/\?$/, '') + link.hash; @@ -783,7 +781,7 @@ Drupal.overlay.refreshRegions = function (data) { (function (regionName, regionSelector) { var $region = $(regionSelector); Drupal.detachBehaviors($region); - $.get(Drupal.settings.basePath + Drupal.settings.overlay.ajaxCallback + '/' + regionName, function (newElement) { + $.get(Drupal.url(Drupal.settings.overlay.ajaxCallback + '/' + regionName), function (newElement) { $region.replaceWith($(newElement)); Drupal.attachBehaviors($region, Drupal.settings); }); @@ -827,13 +825,11 @@ Drupal.overlay.resetActiveClass = function(activePath) { * * @param link * Link object or string to get the Drupal path from. - * @param ignorePathFromQueryString - * Boolean whether to ignore path from query string if path appears empty. * * @return * The Drupal path. */ -Drupal.overlay.getPath = function (link, ignorePathFromQueryString) { +Drupal.overlay.getPath = function (link) { if (typeof link == 'string') { // Create a native Link object, so we can use its object methods. link = $(link.link(link)).get(0); @@ -844,15 +840,7 @@ Drupal.overlay.getPath = function (link, ignorePathFromQueryString) { if (path.charAt(0) != '/') { path = '/' + path; } - path = path.replace(new RegExp(Drupal.settings.basePath + '(?:index.php)?'), ''); - if (path == '' && !ignorePathFromQueryString) { - // If the path appears empty, it might mean the path is represented in the - // query string (clean URLs are not used). - var match = new RegExp('([?&])q=(.+)([&#]|$)').exec(link.search); - if (match && match.length == 4) { - path = match[2]; - } - } + path = path.replace(new RegExp(Drupal.settings.basePath + Drupal.settings.scriptPath), ''); return path; }; diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module index e2ecd19..271f15d 100644 --- a/core/modules/overlay/overlay.module +++ b/core/modules/overlay/overlay.module @@ -143,7 +143,7 @@ function overlay_init() { // If this page shouldn't be rendered inside the overlay, redirect to the // parent. elseif (!path_is_admin($current_path)) { - overlay_close_dialog($current_path, array('query' => drupal_get_query_parameters(NULL, array('q', 'render')))); + overlay_close_dialog($current_path, array('query' => drupal_get_query_parameters(NULL, array('render')))); } // Indicate that we are viewing an overlay child page. diff --git a/core/modules/path/path.admin.inc b/core/modules/path/path.admin.inc index 9e22cff..d45ba59 100644 --- a/core/modules/path/path.admin.inc +++ b/core/modules/path/path.admin.inc @@ -129,7 +129,7 @@ function path_admin_form($form, &$form_state, $path = array('source' => '', 'ali '#maxlength' => 255, '#size' => 45, '#description' => t('Specify the existing path you wish to alias. For example: node/28, forum/1, taxonomy/term/1.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => url(NULL, array('absolute' => TRUE)), '#required' => TRUE, ); $form['alias'] = array( @@ -139,7 +139,7 @@ function path_admin_form($form, &$form_state, $path = array('source' => '', 'ali '#maxlength' => 255, '#size' => 45, '#description' => t('Specify an alternative path by which this data can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => url(NULL, array('absolute' => TRUE)), '#required' => TRUE, ); diff --git a/core/modules/search/search.module b/core/modules/search/search.module index 9a6a11a..4e76150 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -239,7 +239,7 @@ function search_menu() { } /** - * Determines access for the ?q=search path. + * Determines access for the 'search' path. */ function search_is_active() { // This path cannot be accessed if there are no active modules. diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc index 75c12b4..9010f90 100644 --- a/core/modules/shortcut/shortcut.admin.inc +++ b/core/modules/shortcut/shortcut.admin.inc @@ -489,7 +489,7 @@ function _shortcut_link_form_elements($shortcut_link = NULL) { '#title' => t('Path'), '#size' => 40, '#maxlength' => 255, - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => url(NULL, array('absolute' => TRUE)), '#default_value' => $shortcut_link['link_path'], ); diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index aa43503..73b6385 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -657,7 +657,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 = current_path(); $query_parameters = drupal_get_query_parameters(); if (!empty($query_parameters)) { $link .= '?' . drupal_http_build_query($query_parameters); diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php index 74b13fb..99a3b73 100644 --- a/core/modules/simpletest/drupal_web_test_case.php +++ b/core/modules/simpletest/drupal_web_test_case.php @@ -1891,6 +1891,7 @@ class DrupalWebTestCase extends DrupalTestCase { * Retrieve a Drupal path or an absolute path and JSON decode the result. */ protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) { + $headers[] = 'X-Requested-With: XMLHttpRequest'; return drupal_json_decode($this->drupalGet($path, $options, $headers)); } @@ -2098,6 +2099,7 @@ class DrupalWebTestCase extends DrupalTestCase { } $content = $this->content; $drupal_settings = $this->drupalSettings; + $headers[] = 'X-Requested-With: XMLHttpRequest'; // Get the Ajax settings bound to the triggering element. if (!isset($ajax_settings)) { diff --git a/core/modules/simpletest/simpletest.test b/core/modules/simpletest/simpletest.test index 4cecbe6..185a71a 100644 --- a/core/modules/simpletest/simpletest.test +++ b/core/modules/simpletest/simpletest.test @@ -341,8 +341,6 @@ class SimpleTestBrowserTestCase extends DrupalWebTestCase { * Test DrupalWebTestCase::getAbsoluteUrl(). */ function testGetAbsoluteUrl() { - // Testbed runs with Clean URLs disabled, so disable it here. - variable_set('clean_url', 0); $url = 'user/login'; $this->drupalGet($url); diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 69d5f5b..654621d 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -67,7 +67,9 @@ function statistics_exit() { db_insert('accesslog') ->fields(array( 'title' => truncate_utf8(strip_tags(drupal_get_title()), 255), - 'path' => truncate_utf8($_GET['q'], 255), + // @todo The public function current_path() is not available on a cache + // hit. + 'path' => truncate_utf8(_current_path(), 255), 'url' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '', 'hostname' => ip_address(), 'uid' => $user->uid, diff --git a/core/modules/statistics/statistics.test b/core/modules/statistics/statistics.test index 6f19e37..3314761 100644 --- a/core/modules/statistics/statistics.test +++ b/core/modules/statistics/statistics.test @@ -152,8 +152,13 @@ class StatisticsLoggingTestCase extends DrupalWebTestCase { $this->assertTrue(is_array($log) && count($log) == 7, t('Page request was logged.')); $this->assertEqual(array_intersect_key($log[6], $expected), $expected); - // Create a path longer than 255 characters. - $long_path = $this->randomName(256); + // Create a path longer than 255 characters. Drupal's .htaccess file + // instructs Apache to test paths against the file system before routing to + // index.php. Many file systems restrict file names to 255 characters + // (http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits), and + // Apache returns a 403 when testing longer file names, but the total path + // length is not restricted. + $long_path = $this->randomName(127) . '/' . $this->randomName(128); // Test that the long path is properly truncated when logged. $this->drupalGet($long_path); diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index aac0c3d..3bd3b89 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -5,6 +5,8 @@ * Admin page callbacks for the system module. */ +use Symfony\Component\HttpFoundation\Response; + /** * Menu callback; Provide the administration overview page. */ @@ -1494,7 +1496,7 @@ function system_site_information_settings() { '#default_value' => (variable_get('site_frontpage') != 'user' ? drupal_get_path_alias(variable_get('site_frontpage', 'user')) : ''), '#size' => 40, '#description' => t('Optionally, specify a relative URL to display as the front page. Leave blank to display the default content feed.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='), + '#field_prefix' => url(NULL, array('absolute' => TRUE)), ); $form['front_page']['default_nodes_main'] = array( '#type' => 'select', '#title' => t('Number of posts on front page'), @@ -1513,7 +1515,7 @@ function system_site_information_settings() { '#default_value' => variable_get('site_403', ''), '#size' => 40, '#description' => t('This page is displayed when the requested document is denied to the current user. Leave blank to display a generic "access denied" page.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=') + '#field_prefix' => url(NULL, array('absolute' => TRUE)), ); $form['error_page']['site_404'] = array( '#type' => 'textfield', @@ -1521,7 +1523,7 @@ function system_site_information_settings() { '#default_value' => variable_get('site_404', ''), '#size' => 40, '#description' => t('This page is displayed when no other content matches the requested document. Leave blank to display a generic "page not found" page.'), - '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=') + '#field_prefix' => url(NULL, array('absolute' => TRUE)), ); $form['#validate'][] = 'system_site_information_settings_validate'; @@ -2206,96 +2208,6 @@ function system_site_maintenance_mode() { } /** - * Form builder; Configure clean URL settings. - * - * @ingroup forms - * @see system_settings_form() - */ -function system_clean_url_settings($form, &$form_state) { - $available = FALSE; - $conflict = FALSE; - - // If the request URI is a clean URL, clean URLs must be available. - // Otherwise, run a test. - if (strpos(request_uri(), '?q=') === FALSE && strpos(request_uri(), '&q=') === FALSE) { - $available = TRUE; - } - else { - $request = drupal_http_request($GLOBALS['base_url'] . '/admin/config/search/clean-urls/check'); - // If the request returns HTTP 200, clean URLs are available. - if (isset($request->code) && $request->code == 200) { - $available = TRUE; - // If the user started the clean URL test, provide explicit feedback. - if (isset($form_state['input']['clean_url_test_execute'])) { - drupal_set_message(t('The clean URL test passed.')); - } - } - else { - // If the test failed while clean URLs are enabled, make sure clean URLs - // can be disabled. - if (variable_get('clean_url', 0)) { - $conflict = TRUE; - // Warn the user of a conflicting situation, unless after processing - // a submitted form. - if (!isset($form_state['input']['op'])) { - drupal_set_message(t('Clean URLs are enabled, but the clean URL test failed. Uncheck the box below to disable clean URLs.'), 'warning'); - } - } - // If the user started the clean URL test, provide explicit feedback. - elseif (isset($form_state['input']['clean_url_test_execute'])) { - drupal_set_message(t('The clean URL test failed.'), 'warning'); - } - } - } - - // Show the enable/disable form if clean URLs are available or if the user - // must be able to resolve a conflicting setting. - if ($available || $conflict) { - $form['clean_url'] = array( - '#type' => 'checkbox', - '#title' => t('Enable clean URLs'), - '#default_value' => variable_get('clean_url', 0), - '#description' => t('Use URLs like example.com/user instead of example.com/?q=user.'), - ); - $form = system_settings_form($form); - if ($conflict) { - // $form_state['redirect'] needs to be set to the non-clean URL, - // otherwise the setting is not saved. - $form_state['redirect'] = url('', array('query' => array('q' => '/admin/config/search/clean-urls'))); - } - } - // Show the clean URLs test form. - else { - drupal_add_js(drupal_get_path('module', 'system') . '/system.js'); - - $form_state['redirect'] = url('admin/config/search/clean-urls'); - $form['clean_url_description'] = array( - '#type' => 'markup', - '#markup' => '

' . t('Use URLs like example.com/user instead of example.com/?q=user.'), - ); - // Explain why the user is seeing this page and what to expect after - // clicking the 'Run the clean URL test' button. - $form['clean_url_test_result'] = array( - '#type' => 'markup', - '#markup' => '

' . t('Clean URLs cannot be enabled. If you are directed to this page or to a Page not found (404) error after testing for clean URLs, see the online handbook.', array('@handbook' => 'http://drupal.org/node/15365')) . '

', - ); - $form['actions'] = array( - '#type' => 'actions', - 'clean_url_test' => array( - '#type' => 'submit', - '#value' => t('Run the clean URL test'), - ), - ); - $form['clean_url_test_execute'] = array( - '#type' => 'hidden', - '#value' => 1, - ); - } - - return $form; -} - -/** * Menu callback: displays the site status report. Can also be used as a pure check. * * @param $check @@ -2357,6 +2269,9 @@ function system_batch_page() { if ($output === FALSE) { drupal_access_denied(); } + elseif ($output instanceof Response) { + return $output; + } elseif (isset($output)) { // Force a page without blocks or messages to // display a list of collected messages later. diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index c6e1288..15ffa21 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -497,7 +497,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 == current_path()) { // ...call a function that prepares something for this request. mymodule_prepare_something(); } diff --git a/core/modules/system/system.cron.js b/core/modules/system/system.cron.js index af17dab..aa5b351 100644 --- a/core/modules/system/system.cron.js +++ b/core/modules/system/system.cron.js @@ -9,7 +9,7 @@ Drupal.behaviors.cronCheck = { $('body').once('cron-check', function() { // Only execute the cron check if its the right time. if (Math.round(new Date().getTime() / 1000.0) > settings.cronCheck) { - $.get(settings.basePath + 'system/run-cron-check'); + $.get(Drupal.url('system/run-cron-check')); } }); } diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 51603ff..f0e36cc 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1849,6 +1849,13 @@ function system_update_8007() { } /** + * Remove the 'clean_url' configuration variable. + */ +function system_update_8008() { + variable_del('clean_url'); +} + +/** * @} End of "defgroup updates-7.x-to-8.x" * The next series of updates should start at 9000. */ diff --git a/core/modules/system/system.js b/core/modules/system/system.js index 98da1a6..ab00426 100644 --- a/core/modules/system/system.js +++ b/core/modules/system/system.js @@ -20,55 +20,6 @@ Drupal.hideEmailAdministratorCheckbox = function () { }; /** - * Internal function to check using Ajax if clean URLs can be enabled on the - * settings page. - * - * This function is not used to verify whether or not clean URLs - * are currently enabled. - */ -Drupal.behaviors.cleanURLsSettingsCheck = { - attach: function (context, settings) { - // This behavior attaches by ID, so is only valid once on a page. - // Also skip if we are on an install page, as Drupal.cleanURLsInstallCheck will handle - // the processing. - if (!($('#edit-clean-url').length) || $('#edit-clean-url.install').once('clean-url').length) { - return; - } - var url = settings.basePath + 'admin/config/search/clean-urls/check'; - $.ajax({ - url: location.protocol + '//' + location.host + url, - dataType: 'json', - success: function () { - // Check was successful. Redirect using a "clean URL". This will force the form that allows enabling clean URLs. - location = settings.basePath +"admin/config/search/clean-urls"; - } - }); - } -}; - -/** - * Internal function to check using Ajax if clean URLs can be enabled on the - * install page. - * - * This function is not used to verify whether or not clean URLs - * are currently enabled. - */ -Drupal.cleanURLsInstallCheck = function () { - var url = location.protocol + '//' + location.host + Drupal.settings.basePath + 'admin/config/search/clean-urls/check'; - // Submit a synchronous request to avoid database errors associated with - // concurrent requests during install. - $.ajax({ - async: false, - url: url, - dataType: 'json', - success: function () { - // Check was successful. - $('#edit-clean-url').attr('value', 1); - } - }); -}; - -/** * When a field is filled out, apply its value to other fields that will likely * use the same value. In the installer this is used to populate the * administrator e-mail address with the same value as the site e-mail address. diff --git a/core/modules/system/system.maintenance.css b/core/modules/system/system.maintenance.css index 5543c2d..16a2f35 100644 --- a/core/modules/system/system.maintenance.css +++ b/core/modules/system/system.maintenance.css @@ -1,4 +1,3 @@ - /** * Update styles */ @@ -46,10 +45,3 @@ ol.task-list li.active { font-weight: bold; } - -/** - * Installation clean URLs - */ -#clean-url.install { - display: none; -} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index dfb8936..2b23d38 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -88,7 +88,7 @@ function system_help($path, $arg) { $output .= '
' . t('Performing system maintenance') . '
'; $output .= '
' . t('In order for the site and its modules to continue to operate well, a set of routine administrative operations must run on a regular basis. The System module manages this task by making use of a system cron job. You can verify the status of cron tasks by visiting the Status report page. For more information, see the online handbook entry for configuring cron jobs. You can set up cron job by visiting Cron configuration page', array('@status' => url('admin/reports/status'), '@handbook' => 'http://drupal.org/cron', '@cron' => url('admin/config/system/cron'))) . '
'; $output .= '
' . t('Configuring basic site settings') . '
'; - $output .= '
' . t('The System module also handles basic configuration options for your site, including Date and time settings, File system settings, Clean URL support, Site name and other information, and a Maintenance mode for taking your site temporarily offline.', array('@date-time-settings' => url('admin/config/regional/date-time'), '@file-system' => url('admin/config/media/file-system'), '@clean-url' => url('admin/config/search/clean-urls'), '@site-info' => url('admin/config/system/site-information'), '@maintenance-mode' => url('admin/config/development/maintenance'))) . '
'; + $output .= '
' . t('The System module also handles basic configuration options for your site, including Date and time settings, File system settings, Site name and other information, and a Maintenance mode for taking your site temporarily offline.', array('@date-time-settings' => url('admin/config/regional/date-time'), '@file-system' => url('admin/config/media/file-system'), '@site-info' => url('admin/config/system/site-information'), '@maintenance-mode' => url('admin/config/development/maintenance'))) . '
'; $output .= '
' . t('Configuring actions') . '
'; $output .= '
' . t('Actions are individual tasks that the system can do, such as unpublishing a piece of content or banning a user. Other modules can fire these actions when certain system events happen; for example, when a new post is added or when a user logs in. Modules may also provide additional actions. Visit the Actions page to configure actions.', array('@actions' => url('admin/config/system/actions'))) . '
'; $output .= ''; @@ -971,23 +971,6 @@ function system_menu() { 'access arguments' => array('access administration pages'), 'file' => 'system.admin.inc', ); - $items['admin/config/search/clean-urls'] = array( - 'title' => 'Clean URLs', - 'description' => 'Enable or disable clean URLs for your site.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('system_clean_url_settings'), - 'access arguments' => array('administer site configuration'), - 'file' => 'system.admin.inc', - 'weight' => 5, - ); - $items['admin/config/search/clean-urls/check'] = array( - 'title' => 'Clean URL check', - 'page callback' => 'drupal_json_output', - 'page arguments' => array(array('status' => TRUE)), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, - 'file' => 'system.admin.inc', - ); // System settings. $items['admin/config/system'] = array( @@ -1850,8 +1833,7 @@ function system_authorized_get_url(array $options = array()) { global $base_url; // Force https if available, regardless of what the caller specifies. $options['https'] = TRUE; - // We prefix with $base_url so we get a full path even if clean URLs are - // disabled. + // Prefix with $base_url so url() treats it as an external link. return url($base_url . '/core/authorize.php', $options); } @@ -1995,8 +1977,11 @@ 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())) { - return variable_get('admin_theme'); + if ($request = request()) { + $path = ltrim($request->getPathInfo(), '/'); + if (user_access('view the administration theme') && path_is_admin($path)) { + return variable_get('admin_theme'); + } } } diff --git a/core/modules/system/system.test b/core/modules/system/system.test index 16348cc..4fdad71 100644 --- a/core/modules/system/system.test +++ b/core/modules/system/system.test @@ -2896,10 +2896,7 @@ class SystemIndexPhpTest extends DrupalWebTestCase { $this->drupalGet($index_php, array('external' => TRUE)); $this->assertResponse(200, t('Make sure index.php returns a valid page.')); - $this->drupalGet($index_php, array('external' => TRUE, 'query' => array('q' => 'user'))); - $this->assertResponse(200, t('Make sure index.php?q=user returns a valid page.')); - $this->drupalGet($index_php .'/user', array('external' => TRUE)); - $this->assertResponse(404, t("Make sure index.php/user returns a 'page not found'.")); + $this->assertResponse(200, t("Make sure index.php/user returns a valid page.")); } } diff --git a/core/modules/system/tests/ajax.test b/core/modules/system/tests/ajax.test index 4f254f8..49f1632 100644 --- a/core/modules/system/tests/ajax.test +++ b/core/modules/system/tests/ajax.test @@ -80,7 +80,7 @@ class AJAXFrameworkTestCase extends AJAXTestCase { // drupal_add_js(). $expected = array( 'command' => 'settings', - 'settings' => array('basePath' => base_path(), 'ajax' => 'test'), + 'settings' => array('ajax' => 'test'), ); $this->assertCommand($commands, $expected, t('ajax_render() loads settings added with drupal_add_js().')); diff --git a/core/modules/system/tests/common.test b/core/modules/system/tests/common.test index f0ac957..46b379e 100644 --- a/core/modules/system/tests/common.test +++ b/core/modules/system/tests/common.test @@ -97,7 +97,7 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { * Tests for active class in l() function. */ function testLActiveClass() { - $link = l($this->randomName(), $_GET['q']); + $link = l($this->randomName(), current_path()); $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 CommonURLUnitTestCase extends DrupalWebTestCase { */ function testLCustomClass() { $class = $this->randomName(); - $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class)))); + $link = l($this->randomName(), current_path(), 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'))); } @@ -128,19 +128,8 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { ), ), 'c' => 3, - 'q' => 'foo/bar', ); - // Default arguments. - $result = $_GET; - unset($result['q']); - $this->assertEqual(drupal_get_query_parameters(), $result, t("\$_GET['q'] was removed.")); - - // Default exclusion. - $result = $original; - unset($result['q']); - $this->assertEqual(drupal_get_query_parameters($original), $result, t("'q' was removed.")); - // First-level exclusion. $result = $original; unset($result['b']); @@ -176,14 +165,21 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { * Test drupal_parse_url(). */ function testDrupalParseUrl() { - // Relative URL. - $url = 'foo/bar?foo=bar&bar=baz&baz#foo'; - $result = array( - 'path' => 'foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.')); + // Relative, absolute, and external URLs, without/with explicit script path, + // without/with Drupal path. + foreach (array('', '/', 'http://drupal.org/') as $absolute) { + foreach (array('', 'index.php/') as $script) { + foreach (array('', 'foo/bar') as $path) { + $url = $absolute . $script . $path . '?foo=bar&bar=baz&baz#foo'; + $expected = array( + 'path' => $absolute . $script . $path, + 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), + 'fragment' => 'foo', + ); + $this->assertEqual(drupal_parse_url($url), $expected, t('URL parsed correctly.')); + } + } + } // Relative URL that is known to confuse parse_url(). $url = 'foo/bar:1'; @@ -194,47 +190,10 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { ); $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.')); - // Absolute URL. - $url = '/foo/bar?foo=bar&bar=baz&baz#foo'; - $result = array( - 'path' => '/foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL parsed correctly.')); - - // External URL testing. - $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; - // Test that drupal can recognize an absolute URL. Used to prevent attack vectors. + $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; $this->assertTrue(url_is_external($url), t('Correctly identified an external URL.')); - // Test the parsing of absolute URLs. - $result = array( - 'path' => 'http://drupal.org/foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('External URL parsed correctly.')); - - // Verify proper parsing of URLs when clean URLs are disabled. - $result = array( - 'path' => 'foo/bar', - 'query' => array('bar' => 'baz'), - 'fragment' => 'foo', - ); - // Non-clean URLs #1: Absolute URL generated by url(). - $url = $GLOBALS['base_url'] . '/?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL with clean URLs disabled parsed correctly.')); - - // Non-clean URLs #2: Relative URL generated by url(). - $url = '?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL with clean URLs disabled parsed correctly.')); - - // Non-clean URLs #3: URL generated by url() on non-Apache webserver. - $url = 'index.php?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.')); - // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect. $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html'); $this->assertFalse(valid_url($parts['path'], TRUE), t('drupal_parse_url() correctly parsed a forged URL.')); @@ -245,75 +204,41 @@ class CommonURLUnitTestCase extends DrupalWebTestCase { * assert all that works when clean URLs are on and off. */ function testUrl() { - global $base_url; - - foreach (array(FALSE, TRUE) as $absolute) { - // Get the expected start of the path string. - $base = $absolute ? $base_url . '/' : base_path(); - $absolute_string = $absolute ? 'absolute' : NULL; - - // Disable Clean URLs. - $GLOBALS['conf']['clean_url'] = 0; - - $url = $base . '?q=node/123'; - $result = url('node/123', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123#foo'; - $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo'; - $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo=bar&bar=baz'; - $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo#bar'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo#0'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '0', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base; - $result = url('', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - // Enable Clean URLs. - $GLOBALS['conf']['clean_url'] = 1; - - $url = $base . 'node/123'; - $result = url('node/123', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123#foo'; - $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo'; - $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo=bar&bar=baz'; - $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo#bar'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base; - $result = url('', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); + global $base_url, $script_path; + + $script_path_original = $script_path; + foreach (array('', 'index.php/') as $script_path) { + foreach (array(FALSE, TRUE) as $absolute) { + // Get the expected start of the path string. + $base = ($absolute ? $base_url . '/' : base_path()) . $script_path; + $absolute_string = $absolute ? 'absolute' : NULL; + + $url = $base . 'node/123'; + $result = url('node/123', array('absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123#foo'; + $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo'; + $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo=bar&bar=baz'; + $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo#bar'; + $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base; + $result = url('', array('absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + } } + $script_path = $script_path_original; } /** @@ -1230,8 +1155,8 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { $this->assertTrue(array_key_exists('core/misc/html5.js', $javascript), t('html5.js is added when file is added.')); $this->assertTrue(array_key_exists('core/misc/collapse.js', $javascript), t('JavaScript files are correctly added.')); $this->assertEqual(base_path(), $javascript['settings']['data'][0]['basePath'], t('Base path JavaScript setting is correctly set.')); - url('', array('prefix' => &$prefix)); - $this->assertEqual(empty($prefix) ? '' : $prefix, $javascript['settings']['data'][1]['pathPrefix'], t('Path prefix JavaScript setting is correctly set.')); + $this->assertIdentical($GLOBALS['script_path'], $javascript['settings']['data'][1]['scriptPath'], t('Script path JavaScript setting is correctly set.')); + $this->assertIdentical('', $javascript['settings']['data'][2]['pathPrefix'], t('Path prefix JavaScript setting is correctly set.')); } /** @@ -1239,8 +1164,8 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { */ function testAddSetting() { $javascript = drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting'); - $this->assertEqual(280342800, $javascript['settings']['data'][2]['dries'], t('JavaScript setting is set correctly.')); - $this->assertEqual('rocks', $javascript['settings']['data'][2]['drupal'], t('The other JavaScript setting is set correctly.')); + $this->assertEqual(280342800, $javascript['settings']['data'][3]['dries'], t('JavaScript setting is set correctly.')); + $this->assertEqual('rocks', $javascript['settings']['data'][3]['drupal'], t('The other JavaScript setting is set correctly.')); } /** @@ -1269,8 +1194,9 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { $javascript = drupal_get_js('header'); $this->assertTrue(strpos($javascript, 'basePath') > 0, t('Rendered JavaScript header returns basePath setting.')); - $this->assertTrue(strpos($javascript, 'core/misc/jquery.js') > 0, t('Rendered JavaScript header includes jQuery.')); + $this->assertTrue(strpos($javascript, 'scriptPath') > 0, t('Rendered JavaScript header returns scriptPath setting.')); $this->assertTrue(strpos($javascript, 'pathPrefix') > 0, t('Rendered JavaScript header returns pathPrefix setting.')); + $this->assertTrue(strpos($javascript, 'core/misc/jquery.js') > 0, t('Rendered JavaScript header includes jQuery.')); // Test whether drupal_add_js can be used to override a previous setting. $this->assertTrue(strpos($javascript, 'commonTestShouldAppear') > 0, t('Rendered JavaScript header returns custom setting.')); @@ -1974,8 +1900,8 @@ class CommonValidUrlUnitTestCase extends DrupalUnitTestCase { 'example.com/index.html#pagetop', 'example.com:8080', 'subdomain.example.com', - 'example.com/index.php?q=node', - 'example.com/index.php?q=node¶m=false', + 'example.com/index.php/node', + 'example.com/index.php/node?param=false', 'user@www.example.com', 'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop', '127.0.0.1', @@ -2022,8 +1948,8 @@ class CommonValidUrlUnitTestCase extends DrupalUnitTestCase { $valid_relative_urls = array( 'paren(the)sis', 'index.html#pagetop', - 'index.php?q=node', - 'index.php?q=node¶m=false', + 'index.php/node', + 'index.php/node?param=false', 'login.php?do=login&style=%23#pagetop', ); diff --git a/core/modules/system/tests/file.test b/core/modules/system/tests/file.test index 173d05a..ac0418c 100644 --- a/core/modules/system/tests/file.test +++ b/core/modules/system/tests/file.test @@ -2436,7 +2436,7 @@ class FileDownloadTest extends FileTestCase { $this->checkUrl('public', '', $basename, $base_url . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . $basename_encoded); $this->checkUrl('private', '', $basename, $base_url . '/system/files/' . $basename_encoded); - $this->checkUrl('private', '', $basename, $base_url . '/?q=system/files/' . $basename_encoded, '0'); + $this->checkUrl('private', '', $basename, $base_url . '/index.php/system/files/' . $basename_encoded, '0'); } /** diff --git a/core/modules/system/tests/menu.test b/core/modules/system/tests/menu.test index 0a10ec8..c17adb5 100644 --- a/core/modules/system/tests/menu.test +++ b/core/modules/system/tests/menu.test @@ -262,11 +262,11 @@ class MenuRouterTestCase extends DrupalWebTestCase { $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")); + $this->assertTrue($this->url == url('user', array('absolute' => TRUE)), t("Logged-in user redirected to user on accessing user/login")); // user/register should redirect to user/UID/edit. $this->drupalGet('user/register'); - $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid . '/edit', array('absolute' => TRUE)), t("Logged-in user redirected to q=user/UID/edit on accessing q=user/register")); + $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid . '/edit', array('absolute' => TRUE)), t("Logged-in user redirected to user/UID/edit on accessing user/register")); } /** diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module index 0b954ae..6e6bb4d 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.module +++ b/core/modules/system/tests/modules/menu_test/menu_test.module @@ -537,7 +537,7 @@ function menu_test_static_variable($value = NULL) { * Implements hook_menu_site_status_alter(). */ function menu_test_menu_site_status_alter(&$menu_site_status, $path) { - // Allow access to ?q=menu_login_callback even if in maintenance mode. + // Allow access to menu_login_callback even if in maintenance mode. if ($menu_site_status == MENU_SITE_OFFLINE && $path == 'menu_login_callback') { $menu_site_status = MENU_SITE_ONLINE; } diff --git a/core/modules/system/tests/modules/url_alter_test/url_alter_test.module b/core/modules/system/tests/modules/url_alter_test/url_alter_test.module index 9287ff5..a5ca13d 100644 --- a/core/modules/system/tests/modules/url_alter_test/url_alter_test.module +++ b/core/modules/system/tests/modules/url_alter_test/url_alter_test.module @@ -30,8 +30,8 @@ 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."); + if (!request_path() && current_path()) { + drupal_set_message("current_path() is non-empty with an empty request path."); } // Rewrite user/username to user/uid. diff --git a/core/modules/system/tests/path.test b/core/modules/system/tests/path.test index fe6f734..f02a9b4 100644 --- a/core/modules/system/tests/path.test +++ b/core/modules/system/tests/path.test @@ -201,11 +201,11 @@ class UrlAlterFunctionalTest extends DrupalWebTestCase { } /** - * Tests that $_GET['q'] is initialized when the request path is empty. + * Tests that current_path() is initialized when the request 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("current_path() is non-empty with an empty request path.", "current_path() is initialized with an empty request path."); } /** @@ -221,7 +221,7 @@ class UrlAlterFunctionalTest extends DrupalWebTestCase { protected function assertUrlOutboundAlter($original, $final) { // Test outbound altering. $result = url($original); - $base_path = base_path() . (variable_get('clean_url', '0') ? '' : '?q='); + $base_path = base_path() . $GLOBALS['script_path']; $result = substr($result, strlen($base_path)); $this->assertIdentical($result, $final, t('Altered outbound URL %original, expected %final, and got %result.', array('%original' => $original, '%final' => $final, '%result' => $result))); } diff --git a/core/modules/system/tests/session.test b/core/modules/system/tests/session.test index 5cc8fe9..ff0afb8 100644 --- a/core/modules/system/tests/session.test +++ b/core/modules/system/tests/session.test @@ -513,7 +513,7 @@ class SessionHttpsTestCase extends DrupalWebTestCase { */ protected function httpsUrl($url) { global $base_url; - return $base_url . '/core/modules/system/tests/https.php?q=' . $url; + return $base_url . '/core/modules/system/tests/https.php/' . $url; } /** @@ -527,7 +527,7 @@ class SessionHttpsTestCase extends DrupalWebTestCase { */ protected function httpUrl($url) { global $base_url; - return $base_url . '/core/modules/system/tests/http.php?q=' . $url; + return $base_url . '/core/modules/system/tests/http.php/' . $url; } } diff --git a/core/modules/system/tests/tablesort.test b/core/modules/system/tests/tablesort.test index 9c068f8..aaea505 100644 --- a/core/modules/system/tests/tablesort.test +++ b/core/modules/system/tests/tablesort.test @@ -49,7 +49,7 @@ class TableSortTest extends DrupalUnitTestCase { $headers = array('foo', 'bar', 'baz'); // Reset $_GET to prevent parameters from Simpletest and Batch API ending // up in $ts['query']. - $_GET = array('q' => 'jahwohl'); + $_GET = array(); $expected_ts = array( 'name' => 'foo', 'sql' => '', @@ -64,7 +64,6 @@ class TableSortTest extends DrupalUnitTestCase { // override the default. $_GET = array( - 'q' => 'jahwohl', // This should not override the table order because only complex // headers are overridable. 'order' => 'bar', @@ -77,7 +76,6 @@ class TableSortTest extends DrupalUnitTestCase { // override the default. $_GET = array( - 'q' => 'jahwohl', 'sort' => 'DESC', // Add an unrelated parameter to ensure that tablesort will include // it in the links that it creates. @@ -107,7 +105,6 @@ class TableSortTest extends DrupalUnitTestCase { ); // Reset $_GET from previous assertion. $_GET = array( - 'q' => 'jahwohl', 'order' => '2', ); $ts = tablesort_init($headers); @@ -124,7 +121,6 @@ class TableSortTest extends DrupalUnitTestCase { // override the default. $_GET = array( - 'q' => 'jahwohl', // This should not override the table order because this header does not // exist. 'order' => 'bar', @@ -144,7 +140,6 @@ class TableSortTest extends DrupalUnitTestCase { // override the default. $_GET = array( - 'q' => 'jahwohl', 'order' => '1', 'sort' => 'ASC', // Add an unrelated parameter to ensure that tablesort will include diff --git a/core/modules/system/tests/theme.test b/core/modules/system/tests/theme.test index c1ba08f..2a1959c 100644 --- a/core/modules/system/tests/theme.test +++ b/core/modules/system/tests/theme.test @@ -69,14 +69,14 @@ 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 - // see if we are on the front page. + $original_path = _current_path(); + // Set the current path to node because theme_get_suggestions() will query + // it to see if we are on the front page. variable_set('site_frontpage', 'node'); - $_GET['q'] = 'node'; - $suggestions = theme_get_suggestions(explode('/', $_GET['q']), 'page'); + _current_path('node'); + $suggestions = theme_get_suggestions(array('node'), 'page'); // Set it back to not annoy the batch runner. - $_GET['q'] = $q; + _current_path($original_path); $this->assertTrue(in_array('page__front', $suggestions), t('Front page template was suggested.')); } @@ -317,7 +317,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'); + _current_path(variable_get('site_frontpage', 'user')); // Verify that a list of links is properly rendered. $variables = array(); diff --git a/core/modules/system/tests/upgrade/upgrade.test b/core/modules/system/tests/upgrade/upgrade.test index e4045be..52d7800 100644 --- a/core/modules/system/tests/upgrade/upgrade.test +++ b/core/modules/system/tests/upgrade/upgrade.test @@ -78,7 +78,6 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { $this->originalLanguageDefault = variable_get('language_default'); $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files'); $this->originalProfile = drupal_get_profile(); - $clean_url_original = variable_get('clean_url', 0); // Unregister the registry. // This is required to make sure that the database layer works properly. @@ -138,7 +137,6 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase { drupal_save_session(FALSE); // Restore necessary variables. - $this->variable_set('clean_url', $clean_url_original); $this->variable_set('site_mail', 'simpletest@example.com'); drupal_set_time_limit($this->timeLimit); diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc index a95b856..9b23005 100644 --- a/core/modules/taxonomy/taxonomy.admin.inc +++ b/core/modules/taxonomy/taxonomy.admin.inc @@ -422,7 +422,7 @@ function taxonomy_overview_terms($form, &$form_state, TaxonomyVocabulary $vocabu '#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(current_path(), (isset($_GET['page']) ? array('query' => array('page' => $_GET['page'])) : array())); } return $form; @@ -784,7 +784,7 @@ function taxonomy_form_term($form, &$form_state, TaxonomyTerm $term = NULL, Taxo ); } else { - $form_state['redirect'] = $_GET['q']; + $form_state['redirect'] = current_path(); } return $form; diff --git a/core/modules/taxonomy/taxonomy.test b/core/modules/taxonomy/taxonomy.test index 5c16884..7303ecd 100644 --- a/core/modules/taxonomy/taxonomy.test +++ b/core/modules/taxonomy/taxonomy.test @@ -765,9 +765,8 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { $path = 'taxonomy/autocomplete/taxonomy_'; $path .= $this->vocabulary->machine_name . '/' . $input; // The result order is not guaranteed, so check each term separately. - $url = url($path, array('absolute' => TRUE)); - $result = drupal_http_request($url); - $data = drupal_json_decode($result->data); + $result = $this->drupalGet($path); + $data = drupal_json_decode($result); $this->assertEqual($data[$first_term->name], check_plain($first_term->name), 'Autocomplete returned the first matching term'); $this->assertEqual($data[$second_term->name], check_plain($second_term->name), 'Autocomplete returned the second matching term'); diff --git a/core/modules/update/update.compare.inc b/core/modules/update/update.compare.inc index 3b1de48..46e98d4 100644 --- a/core/modules/update/update.compare.inc +++ b/core/modules/update/update.compare.inc @@ -741,7 +741,6 @@ function update_project_cache($cid) { // 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']; $paths = array( 'admin/modules', 'admin/modules/update', @@ -753,7 +752,7 @@ function update_project_cache($cid) { 'admin/reports/status', 'admin/reports/updates/check', ); - if (in_array($q, $paths)) { + if (in_array(current_path(), $paths)) { _update_cache_clear($cid); } else { diff --git a/core/modules/update/update.module b/core/modules/update/update.module index 6142a20..44d86d6 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 (current_path()) { // 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 e6fa2dd..809cd8a 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1025,7 +1025,7 @@ function user_account_form_validate($form, &$form_state) { } function user_login_block($form) { - $form['#action'] = url($_GET['q'], array('query' => drupal_get_destination())); + $form['#action'] = url(current_path(), array('query' => drupal_get_destination())); $form['#id'] = 'user-login-form'; $form['#validate'] = user_login_default_validators(); $form['#submit'][] = 'user_login_submit'; @@ -2325,9 +2325,12 @@ function user_delete_multiple(array $uids) { * Page callback wrapper for user_view(). */ function user_view_page($account) { + if (is_object($account)) { + return user_view($account); + } // An administrator may try to view a non-existent account, // so we give them a 404 (versus a 403 for non-admins). - return is_object($account) ? user_view($account) : MENU_NOT_FOUND; + drupal_not_found(); } /** @@ -3557,7 +3560,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'] = current_path(); } $form['actions'] = array('#type' => 'actions'); diff --git a/core/scripts/drupal.sh b/core/scripts/drupal.sh index cf17e68..fb5e82c 100755 --- a/core/scripts/drupal.sh +++ b/core/scripts/drupal.sh @@ -119,16 +119,15 @@ while ($param = array_shift($_SERVER['argv'])) { $cmd = substr($path['path'], 1); } elseif (isset($path['path'])) { - if (!isset($_GET['q'])) { - $_REQUEST['q'] = $_GET['q'] = $path['path']; - } + $_SERVER['SCRIPT_NAME'] = '/' . $cmd; + $_SERVER['REQUEST_URI'] = $path['path']; } // display setup in verbose mode if ($_verbose_mode) { echo "Hostname set to: {$_SERVER['HTTP_HOST']}\n"; echo "Script name set to: {$cmd}\n"; - echo "Path set to: {$_GET['q']}\n"; + echo "Path set to: {$path['path']}\n"; } } break; diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index 47bfd48..644ff47 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -155,7 +155,7 @@ All arguments are long options. One or more tests to be run. By default, these are interpreted as the names of test groups as shown at - ?q=admin/config/development/testing. + admin/config/development/testing. These group names typically correspond to module names like "User" or "Profile" or "System", but there is also a group "XML-RPC". If --class is specified then these are interpreted as the names of diff --git a/core/update.php b/core/update.php index 9797833..3b05dd0 100644 --- a/core/update.php +++ b/core/update.php @@ -461,13 +461,15 @@ if (update_access_allowed()) { // update.php ops. case 'selection': - if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) { + $token = request()->query->get('token'); + if (isset($token) && drupal_valid_token($token, 'update')) { $output = update_selection_page(); break; } case 'Apply pending updates': - if (isset($_GET['token']) && $_GET['token'] == drupal_get_token('update')) { + $token = request()->query->get('token'); + if (isset($token) && drupal_valid_token($token, 'update')) { // Generate absolute URLs for the batch processing (using $base_root), // since the batch API will pass them to url() which does not handle // update.php correctly by default. diff --git a/index.php b/index.php index b91fb1e..6ebb47f 100644 --- a/index.php +++ b/index.php @@ -11,11 +11,34 @@ * See COPYRIGHT.txt and LICENSE.txt files in the "core" directory. */ +use Drupal\Core\DrupalKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; + /** * Root directory of Drupal installation. */ define('DRUPAL_ROOT', getcwd()); - +// Bootstrap the lowest level of what we need. require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc'; +drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); + +// A request object from the HTTPFoundation to tell us about the request. +$request = Request::createFromGlobals(); + +// Set the global $request object. This is a temporary measure to +// keep legacy utility functions working. It should be moved to a dependency +// injection container at some point. +request($request); + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); -menu_execute_active_handler(); + +$dispatcher = new EventDispatcher(); +$resolver = new ControllerResolver(); + +$kernel = new DrupalKernel($dispatcher, $resolver); +$response = $kernel->handle($request); +$response->prepare($request); +$response->send(); +$kernel->terminate($request, $response); diff --git a/robots.txt b/robots.txt index 90bec1e..d590fbf 100644 --- a/robots.txt +++ b/robots.txt @@ -35,12 +35,12 @@ Disallow: /user/password/ Disallow: /user/login/ Disallow: /user/logout/ # Paths (no clean URLs) -Disallow: /?q=admin/ -Disallow: /?q=comment/reply/ -Disallow: /?q=filter/tips/ -Disallow: /?q=node/add/ -Disallow: /?q=search/ -Disallow: /?q=user/password/ -Disallow: /?q=user/register/ -Disallow: /?q=user/login/ -Disallow: /?q=user/logout/ +Disallow: /index.php/admin/ +Disallow: /index.php/comment/reply/ +Disallow: /index.php/filter/tips/ +Disallow: /index.php/node/add/ +Disallow: /index.php/search/ +Disallow: /index.php/user/password/ +Disallow: /index.php/user/register/ +Disallow: /index.php/user/login/ +Disallow: /index.php/user/logout/ diff --git a/web.config b/web.config index e990e03..06e4cbe 100644 --- a/web.config +++ b/web.config @@ -57,7 +57,9 @@ --> - + @@ -65,9 +67,6 @@ -