diff --git a/core/core.services.yml b/core/core.services.yml index 9af8ff1..dc43c35 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -341,6 +341,11 @@ services: - { name: event_subscriber } arguments: ['@language_manager'] scope: request + redirect_response_subscriber: + class: Drupal\Core\EventSubscriber\RedirectResponseSubscriber + tags: + - { name: event_subscriber } + scope: request request_close_subscriber: class: Drupal\Core\EventSubscriber\RequestCloseSubscriber tags: diff --git a/core/includes/batch.inc b/core/includes/batch.inc index ff0b4cb..c759c48 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -14,7 +14,8 @@ * @see batch_get() */ -use \Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Loads a batch from the database. @@ -54,7 +55,7 @@ function _batch_page() { $batch = batch_load($_REQUEST['id']); if (!$batch) { drupal_set_message(t('No active batch.'), 'error'); - drupal_goto(); + return new RedirectResponse(url('')); } } @@ -469,6 +470,9 @@ function _batch_finished() { // Use drupal_redirect_form() to handle the redirection logic. drupal_redirect_form($_batch['form_state']); + if ($redirect = drupal_set_redirect()) { + return $redirect; + } // If no redirection happened, redirect to the originating page. In case the // form needs to be rebuilt, save the final $form_state for diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index c7094af..889a35f 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Exception\RuntimeException as DependencyInjectionRuntimeException; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RedirectResponse; use Drupal\Core\Language\Language; use Drupal\Core\Lock\DatabaseLockBackend; use Drupal\Core\Lock\LockBackendInterface; @@ -3409,6 +3410,22 @@ function drupal_check_memory_limit($required, $memory_limit = NULL) { } /** + * Set a redirect response. + * + * This should only be used if there is no other way to return the response + * as the page callback. + * + * @param \Symfony\Component\HttpFoundation\RedirectResponse $response + */ +function drupal_set_redirect(RedirectResponse $response = NULL) { + $stored_response = &drupal_static(__FUNCTION__); + if ($response) { + $stored_response = $response; + } + return $stored_response; +} + +/** * @defgroup lock Locking mechanisms * @{ * Functions to coordinate long-running operations across requests. diff --git a/core/includes/common.inc b/core/includes/common.inc index 654d849..bd37e9f 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -534,7 +534,7 @@ function drupal_http_build_query(array $query, $parent = '') { } /** - * Prepares a 'destination' URL query parameter for use with drupal_goto(). + * Prepares a 'destination' URL query parameter for use with RedirectResponse. * * Used to direct the user back to the referring page after completing a form. * By default the current URL is returned. If a destination exists in the @@ -547,7 +547,7 @@ function drupal_http_build_query(array $query, $parent = '') { * not available, the current path. * * @see current_path() - * @see drupal_goto() + * @see \Symfony\Component\HttpFoundation\RedirectResponse */ function drupal_get_destination() { $destination = &drupal_static(__FUNCTION__); @@ -601,7 +601,6 @@ function drupal_get_destination() { * - 'fragment': The fragment of $url, if existent. * * @see url() - * @see drupal_goto() * @ingroup php_wrappers */ function drupal_parse_url($url) { @@ -661,78 +660,6 @@ function drupal_encode_path($path) { } /** - * Sends the user to a different Drupal page. - * - * This issues an on-site HTTP redirect. The function makes sure the redirected - * URL is formatted correctly. - * - * If a destination was specified in the current request's URI (i.e., - * $_GET['destination']) then it will override the $path and $options values - * passed to this function. This provides the flexibility to build a link to - * user/login and override the default redirection so that the user is - * redirected to a specific path after logging in: - * @code - * $query = array('destination' => "node/$node->nid"); - * $link = l(t('Log in'), 'user/login', array('query' => $query)); - * @endcode - * - * Drupal will ensure that messages set by drupal_set_message() and other - * session data are written to the database before the user is redirected. - * - * This function ends the request; use it instead of a return in your menu - * callback. - * - * @param $path - * (optional) A Drupal path or a full URL, which will be passed to url() to - * compute the redirect for the URL. - * @param $options - * (optional) An associative array of additional URL options to pass to url(). - * @param $http_response_code - * (optional) The HTTP status code to use for the redirection, defaults to - * 302. The valid values for 3xx redirection status codes are defined in - * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3 RFC 2616 @endlink - * and the - * @link http://tools.ietf.org/html/draft-reschke-http-status-308-07 draft for the new HTTP status codes: @endlink - * - 301: Moved Permanently (the recommended value for most redirects). - * - 302: Found (default in Drupal and PHP, sometimes used for spamming search - * engines). - * - 303: See Other. - * - 304: Not Modified. - * - 305: Use Proxy. - * - 307: Temporary Redirect. - * - * @see drupal_get_destination() - * @see url() - */ -function drupal_goto($path = '', array $options = array(), $http_response_code = 302) { - // A destination in $_GET always overrides the function arguments. - // We do not allow absolute URLs to be passed via $_GET, as this can be an - // attack vector, with the following exception: - // - Absolute URLs that point to this site (i.e. same base URL and - // base path) are allowed. - if (isset($_GET['destination']) && (!url_is_external($_GET['destination']) || _external_url_is_local($_GET['destination']))) { - $destination = drupal_parse_url($_GET['destination']); - $path = $destination['path']; - $options['query'] = $destination['query']; - $options['fragment'] = $destination['fragment']; - } - - drupal_alter('drupal_goto', $path, $options, $http_response_code); - - // The 'Location' HTTP header must be absolute. - $options['absolute'] = TRUE; - - $url = url($path, $options); - - header('Location: ' . $url, TRUE, $http_response_code); - - // The "Location" header sends a redirect status code to the HTTP daemon. In - // some cases this can be wrong, so we make sure none of the code below the - // drupal_goto() call gets executed upon redirection. - drupal_exit($url); -} - -/** * Determines if an external URL points to this Drupal installation. * * @param $url @@ -1985,16 +1912,11 @@ function l($text, $path, array $options = array()) { * Performs end-of-request tasks. * * There should rarely be a reason to call exit instead of drupal_exit(); - * - * @param $destination - * If this function is called from drupal_goto(), then this argument - * will be a fully-qualified URL that is the destination of the redirect. - * This should be passed along to hook_exit() implementations. */ -function drupal_exit($destination = NULL) { +function drupal_exit() { if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) { if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { - module_invoke_all('exit', $destination); + module_invoke_all('exit'); } drupal_session_commit(); } diff --git a/core/includes/form.inc b/core/includes/form.inc index 4e67aac..7414ee1 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -5,6 +5,7 @@ * Functions for form and batch generation and processing. */ +use Symfony\Component\HttpFoundation\RedirectResponse; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormInterface; use Drupal\Core\Form\BaseFormIdInterface; @@ -226,9 +227,9 @@ function drupal_get_form($form_arg) { * errors. * - redirect: Used to redirect the form on submission. It may either be a * string containing the destination URL, or an array of arguments - * compatible with drupal_goto(). See drupal_redirect_form() for complete + * compatible with url(). See drupal_redirect_form() for complete * information. - * - no_redirect: If set to TRUE the form will NOT perform a drupal_goto(), + * - no_redirect: If set to TRUE the form will NOT perform a RedirectResponse, * even if 'redirect' is set. * - method: The HTTP form method to use for finding the input for this form. * May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method @@ -412,7 +413,10 @@ function drupal_build_form($form_id, &$form_state) { // appropriate information persists to the next page request. // All of the handlers in the pipeline receive $form_state by reference and // can use it to know or update information about the state of the form. - drupal_process_form($form_id, $form, $form_state); + $response = drupal_process_form($form_id, $form, $form_state); + if (is_object($response)) { + return $response; + } // If this was a successful submission of a single-step form or the last step // of a multi-step form, then drupal_process_form() issued a redirect to @@ -939,7 +943,7 @@ function drupal_process_form($form_id, &$form, &$form_state) { } $batch['progressive'] = !$form_state['programmed']; - batch_process(); + return batch_process(); // Execution continues only for programmatic forms. // For 'regular' forms, we get redirected to the batch processing @@ -952,6 +956,7 @@ function drupal_process_form($form_id, &$form, &$form_state) { // Redirect the form based on values in $form_state. drupal_redirect_form($form_state); + return; } // Don't rebuild or cache form submissions invoked via drupal_form_submit(). @@ -1243,10 +1248,10 @@ function drupal_validate_form($form_id, &$form, &$form_state) { * * Usually (for exceptions, see below) $form_state['redirect'] determines where * to redirect the user. This can be set either to a string (the path to - * redirect to), or an array of arguments for drupal_goto(). If - * $form_state['redirect'] is missing, the user is usually (again, see below for - * exceptions) redirected back to the page they came from, where they should see - * a fresh, unpopulated copy of the form. + * redirect to), or an array of arguments for url(). If $form_state['redirect'] + * is missing, the user is usually (again, see below for exceptions) redirected + * back to the page they came from, where they should see a fresh, unpopulated + * copy of the form. * * Here is an example of how to set up a form to redirect to the path 'node': * @code @@ -1277,12 +1282,12 @@ function drupal_validate_form($form_id, &$form, &$form_state) { * form builder functions or form validation/submit handlers. * - If $form_state['redirect'] is set to FALSE, redirection is disabled. * - If none of the above conditions has prevented redirection, then the - * redirect is accomplished by calling drupal_goto(), passing in the value of - * $form_state['redirect'] if it is set, or the current path if it is - * not. drupal_goto() preferentially uses the value of $_GET['destination'] + * redirect is accomplished by returning RedirectResponse, passing in the + * value of $form_state['redirect'] if it is set, or the current path if it is + * not. RedirectResponse preferentially uses the value of $_GET['destination'] * (the 'destination' URL query string) if it is present, so this will * override any values set by $form_state['redirect']. Note that during - * installation, install_goto() is called in place of drupal_goto(). + * installation, install_goto() is called in place of RedirectResponse. * * @param $form_state * An associative array containing the current state of the form. @@ -1303,21 +1308,31 @@ function drupal_redirect_form($form_state) { if (!empty($form_state['no_redirect'])) { return; } - // Only invoke drupal_goto() if redirect value was not set to FALSE. + // Only return a RedirectResponse if redirect value was not set to FALSE. if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) { if (isset($form_state['redirect'])) { if (is_array($form_state['redirect'])) { - call_user_func_array('drupal_goto', $form_state['redirect']); + drupal_set_redirect(new RedirectResponse(url($form_state['redirect'][0], $form_state['redirect'][1]), isset($form_state['redirect'][2]) ? $form_state['redirect'][2] : 302)); + return; } else { // This function can be called from the installer, which guarantees // that $redirect will always be a string, so catch that case here // and use the appropriate redirect function. - $function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto'; - $function($form_state['redirect']); + if (drupal_installation_attempted()) { + install_goto($form_state['redirect']); + } + else { + drupal_set_redirect(new RedirectResponse(url($form_state['redirect']))); + return; + } } } - drupal_goto(current_path(), array('query' => drupal_container()->get('request')->query->all())); + $url = url(Drupal::request()->attributes->get('system_path'), array( + 'query' => Drupal::request()->query->all() + )); + drupal_set_redirect(new RedirectResponse($url)); + return; } } @@ -5071,7 +5086,7 @@ function batch_set($batch_definition) { * Processes the batch. * * Unless the batch has been marked with 'progressive' = FALSE, the function - * issues a drupal_goto and thus ends page execution. + * returns a RedirectResponse and thus ends page execution. * * This function is generally not needed in form submit handlers; * Form API takes care of batches that were set during form submission. @@ -5083,11 +5098,11 @@ function batch_set($batch_definition) { * URL of the batch processing page. * @param $redirect_callback * (optional) Specify a function to be called to redirect to the progressive - * processing page. By default drupal_goto() will be used to redirect to a + * processing page. By default RedirectResponse will be used to redirect to a * page which will do the progressive page. Specifying another function will * allow the progressive processing to be processed differently. */ -function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') { +function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NULL) { $batch =& batch_get(); drupal_theme_initialize(); @@ -5128,8 +5143,8 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd $t = get_t(); $batch['error_message'] = $t('Please continue to the error page', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished'))))); - // Clear the way for the drupal_goto() redirection to the batch processing - // page, by saving and unsetting the 'destination', if there is any. + // Clear the way for the redirection to the batch processing page, by + // saving and unsetting the 'destination', if there is any. if (isset($_GET['destination'])) { $batch['destination'] = $_GET['destination']; unset($_GET['destination']); @@ -5149,8 +5164,14 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd $_SESSION['batches'][$batch['id']] = TRUE; // Redirect for processing. - $function = $batch['redirect_callback']; - $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id']))); + $options = array('query' => array('op' => 'start', 'id' => $batch['id'])); + if (($function = $batch['redirect_callback']) && function_exists($function)) { + $function($batch['url'], $options); + } + else { + $options['absolute'] = TRUE; + return new RedirectResponse(url($batch['url'], $options), 302); + } } else { // Non-progressive execution: bypass the whole progressbar workflow diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 6617366..0e9d2e0 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -11,6 +11,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; use Guzzle\Http\Exception\RequestException; @@ -588,7 +589,10 @@ function install_run_task($task, &$install_state) { } // Process the batch. For progressive batches, this will redirect. // Otherwise, the batch will complete. - batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state)); + $return = batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state)); + if ($return instanceof RedirectResponse) { + install_goto($return->getTargetUrl()); + } } // If we are in the middle of processing this batch, keep sending back // any output from the batch process, until the task is complete. diff --git a/core/includes/install.inc b/core/includes/install.inc index 391750f..eef4095 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -861,7 +861,17 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) { function install_goto($path) { global $base_url; include_once DRUPAL_ROOT . '/core/includes/common.inc'; - header('Location: ' . $base_url . '/' . $path); + + // TODO this is hacking around symfony's redirection giving us a fully + // qualified url. It's a quick hack to get things working but warrants a + // security review or a better implementation. + if (url_is_external($path)) { + $real_path = $path; + } + else { + $real_path = $base_url . '/' . $path; + } + header('Location: ' . $real_path); header('Cache-Control: no-cache'); // Not a permanent redirect. drupal_exit(); } diff --git a/core/includes/update.inc b/core/includes/update.inc index 2719634..e0cd007 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -862,7 +862,7 @@ function update_do_one($module, $number, $dependency_map, &$context) { * * @see update_resolve_dependencies() */ -function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = 'drupal_goto') { +function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = NULL) { // During the update, bring the site offline so that schema changes do not // affect visiting users. $maintenance_mode = config('system.maintenance')->get('enabled'); diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php index 885db53..5554905 100644 --- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php @@ -8,6 +8,7 @@ namespace Drupal\Core\EventSubscriber; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -30,10 +31,14 @@ public function onKernelRequestMaintenanceModeCheck(GetResponseEvent $event) { // Allow other modules to change the site status but not the path. The path // can be changed using a request listener. $read_only_path = $event->getRequest()->attributes->get('system_path'); - drupal_alter('menu_site_status', $status, $read_only_path); + $redirect_path = $read_only_path; + drupal_alter('menu_site_status', $status, $read_only_path, $redirect_path); + if ($redirect_path != $read_only_path) { + $event->setResponse(new RedirectResponse($redirect_path)); + } // Only continue if the site is online. - if ($status != MENU_SITE_ONLINE) { + elseif ($status != MENU_SITE_ONLINE) { // Deliver the 503 page. drupal_maintenance_theme(); drupal_set_title(t('Site under maintenance')); diff --git a/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php new file mode 100644 index 0000000..688dcb6 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php @@ -0,0 +1,85 @@ +getResponse(); + if ($response instanceOf RedirectResponse) { + $options = array(); + + $redirect_path = $destination = $response->getTargetUrl(); + $destination = $event->getRequest()->query->get('destination'); + // A destination in $_GET always overrides the function arguments. + // We do not allow absolute URLs to be passed via $_GET, as this can be an + // attack vector, with the following exception: + // - Absolute URLs that point to this site (i.e. same base URL and + // base path) are allowed.rget + if ($destination && (!url_is_external($destination) || _external_url_is_local($destination))) { + $redirect_path = $destination; + } + elseif (url_is_external($redirect_path)) { + // Do not override redirect responses with external URL. + return; + } + + $destination = drupal_parse_url($redirect_path); + $path = $destination['path']; + // Remove $base_path if path has already go through url(). This can + // happen for RedirectResponse that are set in Form API. + // @see drupal_redirect_form(). + $path = ltrim($path, base_path()); + $options['query'] = $destination['query']; + $options['fragment'] = $destination['fragment']; + // The 'Location' HTTP header must always be absolute. + $options['absolute'] = TRUE; + + $response->setTargetUrl(url($path, $options)); + } + } + + /** + * Checks for redirects coming from legacy callback functions and/or forms. + * + * @param Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event + * The Event to process. + */ + public function checkLegacyRedirect(GetResponseForControllerResultEvent $event) { + if ($redirect = drupal_set_redirect()) { + $event->setResponse($redirect); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::RESPONSE][] = array('checkRedirectUrl', 10); + $events[KernelEvents::VIEW][] = array('checkLegacyRedirect', 10); + return $events; + } +} diff --git a/core/lib/Drupal/Core/HtmlFormController.php b/core/lib/Drupal/Core/HtmlFormController.php index e19d8ea..a303977 100644 --- a/core/lib/Drupal/Core/HtmlFormController.php +++ b/core/lib/Drupal/Core/HtmlFormController.php @@ -63,7 +63,14 @@ public function content(Request $request, $_form) { $form_id = _drupal_form_id($form_object, $form_state); $form = drupal_build_form($form_id, $form_state); - return new Response(drupal_render_page($form)); + + if ($redirect = drupal_set_redirect()) { + // $form_state['redirect'] has a RedirectResponse stored. + return $redirect; + } + else { + return new Response(drupal_render_page($form)); + } } /** diff --git a/core/modules/action/action.module b/core/modules/action/action.module index 7ff568b..a25a99c 100644 --- a/core/modules/action/action.module +++ b/core/modules/action/action.module @@ -23,6 +23,8 @@ * @} End of "defgroup actions". */ +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * Implements hook_help(). */ @@ -688,8 +690,11 @@ function action_goto_action_submit($form, $form_state) { * \Drupal\Core\Utility\Token::replace(). * - Other elements will be used as the data for token replacement. * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * A redirection response object + * * @ingroup actions. */ function action_goto_action($entity, $context) { - drupal_goto(Drupal::token()->replace($context['url'], $context)); + return new RedirectResponse(Drupal::token()->replace($context['url'], 302, $context)); } diff --git a/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php b/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php index 8c83491..c93581b 100644 --- a/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php +++ b/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php @@ -7,6 +7,7 @@ namespace Drupal\action\Form; use Drupal\Core\Form\FormInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Provides a form for configuring an action. @@ -25,7 +26,7 @@ public function getFormID() { */ public function buildForm(array $form, array &$form_state, $action = NULL) { if ($action === NULL) { - drupal_goto('admin/config/system/actions'); + return new RedirectResponse('admin/config/system/actions'); } $actions_map = action_actions_map(action_list()); diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc index 9b8d4ff..8e7553e 100644 --- a/core/modules/aggregator/aggregator.admin.inc +++ b/core/modules/aggregator/aggregator.admin.inc @@ -6,6 +6,7 @@ */ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpFoundation\RedirectResponse; use Drupal\aggregator\Plugin\Core\Entity\Feed; /** @@ -113,7 +114,7 @@ function aggregator_admin_refresh_feed(Feed $feed) { } aggregator_refresh($feed); - drupal_goto('admin/config/services/aggregator'); + return new RedirectResponse('admin/config/services/aggregator'); } /** diff --git a/core/modules/aggregator/tests/aggregator_test.module b/core/modules/aggregator/tests/aggregator_test.module index 09190fa..b5d5b9f 100644 --- a/core/modules/aggregator/tests/aggregator_test.module +++ b/core/modules/aggregator/tests/aggregator_test.module @@ -1,5 +1,7 @@ nid"); + return new RedirectResponse("node/{$node->id()}"); } } else { @@ -62,19 +63,19 @@ function comment_reply(EntityInterface $node, $pid = NULL) { if ($comment->nid->target_id != $node->nid) { // Attempting to reply to a comment not belonging to the current nid. drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); - drupal_goto("node/$node->nid"); + return new RedirectResponse("node/{$node->id()}"); } // Display the parent comment $build['comment_parent'] = comment_view($comment); } else { drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); - drupal_goto("node/$node->nid"); + return new RedirectResponse("node/{$node->id()}"); } } else { drupal_set_message(t('You are not authorized to view comments.'), 'error'); - drupal_goto("node/$node->nid"); + return new RedirectResponse("node/{$node->id()}"); } } // This is the case where the comment is in response to a node. Display the node. @@ -85,14 +86,14 @@ function comment_reply(EntityInterface $node, $pid = NULL) { // Should we show the reply box? if ($node->comment != COMMENT_NODE_OPEN) { drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error'); - drupal_goto("node/$node->nid"); + return new RedirectResponse("node/{$node->id()}"); } elseif (user_access('post comments')) { $build['comment_form'] = comment_add($node, $pid); } else { drupal_set_message(t('You are not authorized to post comments.'), 'error'); - drupal_goto("node/$node->nid"); + return new RedirectResponse("node/{$node->id()}"); } } @@ -120,5 +121,5 @@ function comment_approve(Comment $comment) { $comment->save(); drupal_set_message(t('Comment approved.')); - drupal_goto('node/' . $comment->nid->target_id); + return new RedirectResponse('node/' . $comment->nid->target_id); } diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc index 9ed3aa9..12ccfd4 100644 --- a/core/modules/image/image.admin.inc +++ b/core/modules/image/image.admin.inc @@ -5,6 +5,8 @@ * Administration pages for image settings. */ +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * Menu callback; Listing of all current image styles. */ @@ -284,7 +286,7 @@ function image_effect_form($form, &$form_state, $style, $effect) { // If there's no configuration for this image effect, return to // the image style page. if (!isset($effect['form callback'])) { - drupal_goto('admin/config/media/image-styles/manage/' . $style->id()); + return new RedirectResponse('admin/config/media/image-styles/manage/' . $style->id()); } $form_state['image_style'] = $style; $form_state['image_effect'] = $effect; diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc index f59fb38..5947fcf 100644 --- a/core/modules/language/language.admin.inc +++ b/core/modules/language/language.admin.inc @@ -7,6 +7,7 @@ use Drupal\Core\Language\Language; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * User interface for the language overview screen. @@ -297,7 +298,7 @@ function language_admin_delete_form($form, &$form_state, $language) { if (language_default()->langcode == $langcode) { drupal_set_message(t('The default language cannot be deleted.')); - drupal_goto('admin/config/regional/language'); + return new RedirectResponse('admin/config/regional/language'); } // For other languages, warn the user that data loss is ahead. diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index a109f5f..3c2435a 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -8,6 +8,7 @@ use Drupal\locale\SourceString; use Drupal\locale\TranslationString; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Page callback: Shows the string search screen. @@ -480,7 +481,7 @@ function locale_translation_manual_status() { if (batch_get()) { batch_process('admin/reports/translations'); } - drupal_goto('admin/reports/translations'); + return new RedirectResponse('admin/reports/translations'); } /** diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index 863babd..96c6cb5 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -10,6 +10,7 @@ */ use Drupal\Core\Entity\EntityInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Page callback: Presents the node editing form. @@ -50,7 +51,7 @@ function node_add_page() { // Bypass the node/add listing if only one content type is available. if (count($content) == 1) { $type = array_shift($content); - drupal_goto('node/add/' . $type->type); + return new RedirectResponse('node/add/' . $type->type); } return array('#theme' => 'node_add_list', '#content' => $content); } diff --git a/core/modules/openid/openid.module b/core/modules/openid/openid.module index ad4da7b..f37e0d5 100644 --- a/core/modules/openid/openid.module +++ b/core/modules/openid/openid.module @@ -6,6 +6,7 @@ */ use Guzzle\Http\Exception\RequestException; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Implements hook_menu(). @@ -777,7 +778,7 @@ function openid_authentication($response) { } else { module_invoke_all('openid_response', $response, $form_state['user']); - drupal_goto(); + return new RedirectResponse(url('')); } $messages = drupal_get_messages('error'); @@ -799,13 +800,13 @@ function openid_authentication($response) { // registration page and prefill with the values we received. $destination = drupal_get_destination(); unset($_GET['destination']); - drupal_goto('user/register', array('query' => $destination)); + return new RedirectResponse(url('user/register', array('query' => $destination))); } else { drupal_set_message(t('Only site administrators can create new user accounts.'), 'error'); module_invoke_all('openid_response', $response, NULL); } - drupal_goto(); + return new RedirectResponse(url('')); } function openid_association_request($public) { diff --git a/core/modules/openid/openid.pages.inc b/core/modules/openid/openid.pages.inc index 81a5905..97a24a9 100644 --- a/core/modules/openid/openid.pages.inc +++ b/core/modules/openid/openid.pages.inc @@ -5,6 +5,8 @@ * User page callbacks for the openid module. */ +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * Menu callback; Process an OpenID authentication. */ @@ -20,7 +22,7 @@ function openid_authentication_page() { drupal_set_message(t('OpenID login cancelled.')); break; } - drupal_goto(); + return new RedirectResponse(url('')); } /** diff --git a/core/modules/overlay/lib/Drupal/overlay/EventSubscriber/OverlaySubscriber.php b/core/modules/overlay/lib/Drupal/overlay/EventSubscriber/OverlaySubscriber.php new file mode 100644 index 0000000..ad734f9 --- /dev/null +++ b/core/modules/overlay/lib/Drupal/overlay/EventSubscriber/OverlaySubscriber.php @@ -0,0 +1,68 @@ +getTargetUrl()); + $options = $destination['options']; + $path = $destination['path']; + // The authorize.php script bootstraps Drupal to a very low level, where + // the PHP code that is necessary to close the overlay properly will not be + // loaded. Therefore, if we are redirecting to authorize.php inside the + // overlay, instead redirect back to the current page with instructions to + // close the overlay there before redirecting to the final destination; see + // overlay_init(). + if ($path == system_authorized_get_url() || $path == system_authorized_batch_processing_url()) { + $_SESSION['overlay_close_dialog'] = array($path, $options); + $path = $event->getRequest()->attributes->get('system_path'); + $options = drupal_get_query_parameters(); + } + + // If the current page request is inside the overlay, add ?render=overlay + // to the new path, so that it appears correctly inside the overlay. + if (isset($options['query'])) { + $options['query'] += array('render' => 'overlay'); + } + else { + $options['query'] = array('render' => 'overlay'); + } + $options['absolute'] = TRUE; + $event->setTargetUrl(url($path, $options)); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::RESPONSE][] = array('overlayRedirectAlter'); + return $events; + } + +} diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module index 1cedd2e..1f1f51f 100644 --- a/core/modules/overlay/overlay.module +++ b/core/modules/overlay/overlay.module @@ -7,6 +7,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Implements hook_help(). @@ -146,7 +147,7 @@ function overlay_init() { // #overlay=admin/modules to actually enable the overlay. if (isset($_SESSION['overlay_enable_redirect']) && $_SESSION['overlay_enable_redirect']) { unset($_SESSION['overlay_enable_redirect']); - drupal_goto('', array('fragment' => 'overlay=' . $current_path)); + return new RedirectResponse(url('', array('fragment' => 'overlay=' . $current_path))); } if (isset($_GET['render']) && $_GET['render'] == 'overlay') { @@ -255,34 +256,6 @@ function overlay_library_info() { } /** - * Implements hook_drupal_goto_alter(). - */ -function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) { - if (overlay_get_mode() == 'child') { - // The authorize.php script bootstraps Drupal to a very low level, where - // the PHP code that is necessary to close the overlay properly will not be - // loaded. Therefore, if we are redirecting to authorize.php inside the - // overlay, instead redirect back to the current page with instructions to - // close the overlay there before redirecting to the final destination; see - // overlay_init(). - if ($path == system_authorized_get_url() || $path == system_authorized_batch_processing_url()) { - $_SESSION['overlay_close_dialog'] = array($path, $options); - $path = current_path(); - $options = drupal_get_query_parameters(); - } - - // If the current page request is inside the overlay, add ?render=overlay - // to the new path, so that it appears correctly inside the overlay. - if (isset($options['query'])) { - $options['query'] += array('render' => 'overlay'); - } - else { - $options['query'] = array('render' => 'overlay'); - } - } -} - -/** * Implements hook_batch_alter(). * * If the current page request is inside the overlay, add ?render=overlay to @@ -371,7 +344,7 @@ function overlay_user_dismiss_message() { drupal_container()->get('user.data')->set('overlay', $user->uid, 'message_dismissed', 1); drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.')); // Destination is normally given. Go to the user profile as a fallback. - drupal_goto('user/' . $user->uid . '/edit'); + return new RedirectResponse('user/' . $user->uid . '/edit'); } /** diff --git a/core/modules/overlay/overlay.services.yml b/core/modules/overlay/overlay.services.yml new file mode 100644 index 0000000..c64df81 --- /dev/null +++ b/core/modules/overlay/overlay.services.yml @@ -0,0 +1,5 @@ +services: + overlay.subscriber: + class: Drupal\overlay\EventSubscriber\OverlaySubscriber + tags: + - { name: event_subscriber } diff --git a/core/modules/search/search.pages.inc b/core/modules/search/search.pages.inc index eaae71a..5c09c05 100644 --- a/core/modules/search/search.pages.inc +++ b/core/modules/search/search.pages.inc @@ -5,6 +5,8 @@ * User page callbacks for the Search module. */ +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * Page callback: Presents the search form and/or search results. * @@ -39,7 +41,7 @@ function search_view($module = NULL, $keys = '') { if ($keys) { $path .= '/' . $keys; } - drupal_goto($path); + return new RedirectResponse($path); } // Default results output is an empty string. diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc index dec4dbd..cdb75c9 100644 --- a/core/modules/shortcut/shortcut.admin.inc +++ b/core/modules/shortcut/shortcut.admin.inc @@ -6,6 +6,7 @@ */ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Form callback: builds the form for switching shortcut sets. @@ -494,7 +495,7 @@ function shortcut_link_add_inline($shortcut_set) { else { drupal_set_message(t('Unable to add a shortcut for %title.', array('%title' => $link['link_title']))); } - drupal_goto(); + return new RedirectResponse(url('')); } throw new AccessDeniedHttpException(); diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 0a46873..fcc72f2 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -54,8 +54,8 @@ function simpletest_menu() { ); $items['admin/config/development/testing/results/%'] = array( 'title' => 'Test result', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('simpletest_result_form', 5), + 'page callback' => 'simpletest_test_result', + 'page arguments' => array(5), 'description' => 'View result of tests.', 'access arguments' => array('administer unit tests'), 'file' => 'simpletest.pages.inc', diff --git a/core/modules/simpletest/simpletest.pages.inc b/core/modules/simpletest/simpletest.pages.inc index 4fb908d..a4c31a3 100644 --- a/core/modules/simpletest/simpletest.pages.inc +++ b/core/modules/simpletest/simpletest.pages.inc @@ -5,6 +5,8 @@ * Page callbacks for simpletest module. */ +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * List tests arranged in groups that can be selected and run. */ @@ -204,16 +206,25 @@ function simpletest_test_form_submit($form, &$form_state) { } /** - * Test results form for $test_id. + * Page callback for the rest results. + * + * @param string $test_id + * The ID of the executed test. */ -function simpletest_result_form($form, &$form_state, $test_id) { +function simpletest_test_result($test_id) { // Make sure there are test results to display and a re-run is not being performed. - $results = array(); if (is_numeric($test_id) && !$results = simpletest_result_get($test_id)) { drupal_set_message(t('No test results to display.'), 'error'); - drupal_goto('admin/config/development/testing'); - return $form; + return new RedirectResponse('admin/config/development/testing'); } + return drupal_get_form('simpletest_result_form', $test_id); +} + +/** + * Test results form for $test_id. + */ +function simpletest_result_form($form, &$form_state, $test_id) { + $results = simpletest_result_get($test_id); // Load all classes and include CSS. drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css'); diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/GotoTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/GotoTest.php index c9e3cfc..8855c6a 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/GotoTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/GotoTest.php @@ -8,9 +8,10 @@ namespace Drupal\system\Tests\Common; use Drupal\simpletest\WebTestBase; +use Symfony\Component\HttpFoundation\RedirectResponse; /** - * Tests drupal_goto() and hook_drupal_goto_alter(). + * Tests redirection functionality. */ class GotoTest extends WebTestBase { @@ -24,59 +25,49 @@ class GotoTest extends WebTestBase { public static function getInfo() { return array( 'name' => 'Redirect functionality', - 'description' => 'Tests the drupal_goto() and hook_drupal_goto_alter() functionality.', + 'description' => 'Tests redirection functionality.', 'group' => 'Common', ); } /** - * Tests drupal_goto(). + * Tests RedirectResponse. */ - function testDrupalGoto() { - $this->drupalGet('common-test/drupal_goto/redirect'); + function testRedirectResponse() { + $this->drupalGet('common-test/redirect-response/redirect'); $headers = $this->drupalGetHeaders(TRUE); list(, $status) = explode(' ', $headers[0][':status'], 3); $this->assertEqual($status, 302, 'Expected response code was sent.'); - $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.'); - $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('absolute' => TRUE)), 'Drupal goto redirected to expected URL.'); + $this->assertText('RedirectResponse', 'Drupal RedirectResponse redirect succeeded.'); + $this->assertEqual($this->getUrl(), url('common-test/redirect-response', array('absolute' => TRUE)), 'Drupal RedirectResponse redirected to expected URL.'); - $this->drupalGet('common-test/drupal_goto/redirect_advanced'); + $this->drupalGet('common-test/redirect-response/redirect_advanced'); $headers = $this->drupalGetHeaders(TRUE); list(, $status) = explode(' ', $headers[0][':status'], 3); $this->assertEqual($status, 301, 'Expected response code was sent.'); - $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.'); - $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to expected URL.'); + $this->assertText('RedirectResponse', 'Drupal RedirectResponse redirect succeeded.'); + $this->assertEqual($this->getUrl(), url('common-test/redirect-response', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal RedirectResponse redirected to expected URL.'); - // Test that drupal_goto() respects ?destination=xxx. Use a complicated URL + // Test that RedirectResponse respects ?destination=xxx. Use a complicated URL // to test that the path is encoded and decoded properly. - $destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123'; - $this->drupalGet('common-test/drupal_goto/redirect', array('query' => array('destination' => $destination))); - $this->assertText('drupal_goto', 'Drupal goto redirect with destination succeeded.'); - $this->assertEqual($this->getUrl(), url('common-test/drupal_goto/destination', array('query' => array('foo' => '%25', 'bar' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to given query string destination.'); + $destination = 'common-test/redirect-response/destination?foo=%2525&bar=123'; + $this->drupalGet('common-test/redirect-response/redirect', array('query' => array('destination' => $destination))); + $this->assertText('RedirectResponse', 'Drupal RedirectResponse redirect with destination succeeded.'); + $this->assertEqual($this->getUrl(), url('common-test/redirect-response/destination', array('query' => array('foo' => '%25', 'bar' => '123'), 'absolute' => TRUE)), 'Drupal RedirectResponse redirected to given query string destination.'); - // Test that drupal_goto() respects ?destination=xxx with an absolute URL + // Test that RedirectResponse respects ?destination=xxx with an absolute URL // that points to this Drupal installation. - $destination = url('common-test/drupal_goto/alternative', array('absolute' => TRUE)); - $this->drupalGet('common-test/drupal_goto/redirect', array('query' => array('destination' => $destination))); - $this->assertText('drupal_goto_alternative', 'Drupal goto redirect with absolute URL destination that points to this Drupal installation succeeded.'); - $this->assertEqual($this->getUrl(), url('common-test/drupal_goto/alternative', array('absolute' => TRUE)), 'Drupal goto redirected to given query string destination with absolute URL that points to this Drupal installation.'); + $destination = url('common-test/redirect-response/alternative', array('absolute' => TRUE)); + $this->drupalGet('common-test/redirect-response/redirect', array('query' => array('destination' => $destination))); + $this->assertText('RedirectResponse_alternative', 'Drupal RedirectResponse redirect with absolute URL destination that points to this Drupal installation succeeded.'); + $this->assertEqual($this->getUrl(), url('common-test/redirect-response/alternative', array('absolute' => TRUE)), 'Drupal RedirectResponse redirected to given query string destination with absolute URL that points to this Drupal installation.'); - // Test that drupal_goto() fails to respect ?destination=xxx with an absolute URL + // Test that RedirectResponse fails to respect ?destination=xxx with an absolute URL // that does not point to this Drupal installation. $destination = 'http://example.com'; - $this->drupalGet('common-test/drupal_goto/redirect', array('query' => array('destination' => $destination))); - $this->assertText('drupal_goto', 'Drupal goto fails to redirect with absolute URL destination that does not point to this Drupal installation.'); - $this->assertNotEqual($this->getUrl(), $destination, 'Drupal goto failed to redirect to given query string destination with absolute URL that does not point to this Drupal installation.'); - } - - /** - * Tests hook_drupal_goto_alter(). - */ - function testDrupalGotoAlter() { - $this->drupalGet('common-test/drupal_goto/redirect_fail'); - - $this->assertNoText(t("Drupal goto failed to stop program"), 'Drupal goto stopped program.'); - $this->assertNoText('drupal_goto_fail', 'Drupal goto redirect failed.'); + $this->drupalGet('common-test/redirect-response/redirect', array('query' => array('destination' => $destination))); + $this->assertText('RedirectResponse', 'Drupal RedirectResponse fails to redirect with absolute URL destination that does not point to this Drupal installation.'); + $this->assertNotEqual($this->getUrl(), $destination, 'Drupal RedirectResponse failed to redirect to given query string destination with absolute URL that does not point to this Drupal installation.'); } /** diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 9d6d4b6..31ad14a 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -9,6 +9,7 @@ use Drupal\Core\Ajax\ReplaceCommand; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Drupal\Core\Datetime\DrupalDateTime; @@ -300,7 +301,7 @@ function system_theme_enable() { else { drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error'); } - drupal_goto('admin/appearance'); + return new RedirectResponse('admin/appearance'); } throw new AccessDeniedHttpException(); } @@ -328,7 +329,7 @@ function system_theme_disable() { else { drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error'); } - drupal_goto('admin/appearance'); + return new RedirectResponse('admin/appearance'); } throw new AccessDeniedHttpException(); } @@ -377,7 +378,7 @@ function system_theme_default() { else { drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error'); } - drupal_goto('admin/appearance'); + return new RedirectResponse('admin/appearance'); } throw new AccessDeniedHttpException(); } @@ -1377,7 +1378,7 @@ function system_modules_uninstall_validate($form, &$form_state) { // Form submitted, but no modules selected. if (!count(array_filter($form_state['values']['uninstall']))) { drupal_set_message(t('No modules selected.'), 'error'); - drupal_goto('admin/modules/uninstall'); + return new RedirectResponse('admin/modules/uninstall'); } } @@ -1591,7 +1592,7 @@ function system_run_cron_submit($form, &$form_state) { drupal_set_message(t('Cron run failed.'), 'error'); } - drupal_goto('admin/config/system/cron'); + return new RedirectResponse('admin/config/system/cron'); } /** @@ -2123,7 +2124,7 @@ function system_run_cron() { drupal_set_message(t('Cron run failed.'), 'error'); } - drupal_goto('admin/reports/status'); + return new RedirectResponse('admin/reports/status'); } /** diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 9d6922c..4aaeb41 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -327,8 +327,9 @@ function hook_element_info_alter(&$type) { * This hook is not run on cached pages. * * @param $destination - * If this hook is invoked as part of a drupal_goto() call, then this argument - * will be a fully-qualified URL that is the destination of the redirect. + * If this hook is invoked as part of a redirection response, then this + * argument will be a fully-qualified URL that is the destination of the + * redirect. */ function hook_exit($destination = NULL) { watchdog('mymodule', 'Page was built for user %name.', array('%name' => user_format_name($GLOBALS['user'])), WATCHDOG_INFO); @@ -3020,22 +3021,6 @@ function hook_install_tasks(&$install_state) { } /** - * Change the page the user is sent to by drupal_goto(). - * - * @param $path - * A Drupal path or a full URL. - * @param $options - * An associative array of additional URL options to pass to url(). - * @param $http_response_code - * The HTTP status code to use for the redirection. See drupal_goto() for more - * information. - */ -function hook_drupal_goto_alter(&$path, &$options, &$http_response_code) { - // A good addition to misery module. - $http_response_code = 500; -} - -/** * Alter XHTML HEAD tags before they are rendered by drupal_get_html_head(). * * Elements available to be altered are only those added using diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 925709c..586a9e8 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Response; use Guzzle\Http\Exception\BadResponseException; use Guzzle\Http\Exception\RequestException; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Maximum age of temporary files in seconds. @@ -2410,8 +2411,8 @@ function _system_themes_access($theme) { * or to call system_authorized_init() and then redirect to authorize.php, * using the URL from system_authorized_get_url(). Redirecting yourself is * necessary when your authorized operation is being triggered by a form - * submit handler, since calling drupal_goto() in a submit handler is a bad - * idea, and you should instead set $form_state['redirect']. + * submit handler, since calling new RedirectResponse in a submit handler is a + * bad idea, and you should instead set $form_state['redirect']. * * Once the SESSION is setup for the operation and the user is redirected to * authorize.php, they will be prompted for their connection credentials (core @@ -2438,7 +2439,7 @@ function _system_themes_access($theme) { * not to assume any code exists. Example (system_authorized_run()): * @code * system_authorized_init($callback, $file, $arguments, $page_title); - * drupal_goto(system_authorized_get_url()); + * return new RedirectResponse(system_authorized_get_url()); * @endcode * Example (update_manager_install_form_submit()): * @code @@ -2511,7 +2512,7 @@ function system_authorized_batch_processing_url() { */ function system_authorized_run($callback, $file, $arguments = array(), $page_title = NULL) { system_authorized_init($callback, $file, $arguments, $page_title); - drupal_goto(system_authorized_get_url()); + return new RedirectResponse(system_authorized_get_url()); } /** @@ -3517,7 +3518,7 @@ function system_admin_compact_mode() { */ function system_admin_compact_page($mode = 'off') { user_cookie_save(array('admin_compact_mode' => ($mode == 'on'))); - drupal_goto(); + return new RedirectResponse(url('')); } /** diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module index 3670d41..28bbae3 100644 --- a/core/modules/system/tests/modules/common_test/common_test.module +++ b/core/modules/system/tests/modules/common_test/common_test.module @@ -5,44 +5,47 @@ * Helper module for the Common tests. */ +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * Implements hook_menu(). */ function common_test_menu() { - $items['common-test/drupal_goto'] = array( + $items['common-test/redirect-response'] = array( 'title' => 'Drupal Goto', - 'page callback' => 'common_test_drupal_goto_land', + 'page callback' => 'common_test_redirect_response_land', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - $items['common-test/drupal_goto/alternative'] = array( + $items['common-test/redirect-response/alternative'] = array( 'title' => 'Drupal Goto', - 'page callback' => 'common_test_drupal_goto_land_alternative', + 'page callback' => 'common_test_redirect_response_land_alternative', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - $items['common-test/drupal_goto/fail'] = array( + $items['common-test/redirect-response/fail'] = array( 'title' => 'Drupal Goto', - 'page callback' => 'common_test_drupal_goto_land_fail', + 'page callback' => 'common_test_redirect_response_land_fail', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - $items['common-test/drupal_goto/redirect'] = array( + $items['common-test/redirect-response/redirect'] = array( 'title' => 'Drupal Goto', - 'page callback' => 'common_test_drupal_goto_redirect', + 'page callback' => 'common_test_redirect_response_redirect', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - $items['common-test/drupal_goto/redirect_advanced'] = array( + $items['common-test/redirect-response/redirect_advanced'] = array( 'title' => 'Drupal Goto', - 'page callback' => 'common_test_drupal_goto_redirect_advanced', + 'page callback' => 'common_test_redirect_response_redirect_advanced', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - $items['common-test/drupal_goto/redirect_fail'] = array( + // @todo build a callback function for use here. + $items['common-test/redirect-response/redirect_fail'] = array( 'title' => 'Drupal Goto Failure', - 'page callback' => 'drupal_goto', - 'page arguments' => array('common-test/drupal_goto/fail'), + 'page callback' => 'common_test_redirect_response_redirect', + 'page arguments' => array('common-test/redirect-response/fail'), 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); @@ -74,53 +77,44 @@ function common_test_menu() { } /** - * Redirects using drupal_goto(). + * Redirects using RedirectResponse. */ -function common_test_drupal_goto_redirect() { - drupal_goto('common-test/drupal_goto'); +function common_test_redirect_response_redirect() { + return new RedirectResponse('common-test/redirect-response'); } /** - * Redirects using drupal_goto(). + * Redirects using RedirectResponse. */ -function common_test_drupal_goto_redirect_advanced() { - drupal_goto('common-test/drupal_goto', array('query' => array('foo' => '123')), 301); +function common_test_redirect_response_redirect_advanced() { + return new RedirectResponse(url('common-test/redirect-response', array('query' => array('foo' => '123'))), 301); } /** - * Page callback: Provides a landing page for drupal_goto(). + * Page callback: Provides a landing page for RedirectResponse. * * @see common_test_menu() */ -function common_test_drupal_goto_land() { - print "drupal_goto"; +function common_test_redirect_response_land() { + print "RedirectResponse"; } /** - * Page callback: Provides a landing page for drupal_goto(). + * Page callback: Provides a landing page for RedirectResponse. * * @see common_test_menu() */ -function common_test_drupal_goto_land_alternative() { - print "drupal_goto_alternative"; +function common_test_redirect_response_land_alternative() { + print "RedirectResponse_alternative"; } /** - * Page callback: Provides a failure landing page for drupal_goto(). + * Page callback: Provides a failure landing page for RedirectResponse. * * @see common_test_menu() */ -function common_test_drupal_goto_land_fail() { - print "drupal_goto_fail"; -} - -/** - * Implements hook_drupal_goto_alter(). - */ -function common_test_drupal_goto_alter(&$path, &$options, &$http_response_code) { - if ($path == 'common-test/drupal_goto/fail') { - $path = 'common-test/drupal_goto/redirect'; - } +function common_test_redirect_response_land_fail() { + print "RedirectResponse_fail"; } /** diff --git a/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php b/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php index c9f1b0a..44ad465 100644 --- a/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php +++ b/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php @@ -7,6 +7,7 @@ namespace Drupal\session_test\EventSubscriber; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -40,6 +41,15 @@ public function onKernelRequestSessionTest(GetResponseEvent $event) { */ public function onKernelResponseSessionTest(FilterResponseEvent $event) { $event->getResponse()->headers->set('X-Session-Empty', $this->emptySession); + if ($event instanceof RedirectResponse) { + global $base_insecure_url, $is_https_mock; + // Alter the redirect to use HTTP when using a mock HTTPS request through + // https.php because form submissions would otherwise redirect to a + // non-existent HTTPS site. + if (!empty($is_https_mock)) { + $event->setTargetUrl($base_insecure_url . '/' . $path); + } + } } /** diff --git a/core/modules/system/tests/modules/session_test/session_test.module b/core/modules/system/tests/modules/session_test/session_test.module index b3e82fd..73bf3ff 100644 --- a/core/modules/system/tests/modules/session_test/session_test.module +++ b/core/modules/system/tests/modules/session_test/session_test.module @@ -162,22 +162,6 @@ function session_test_form_user_login_form_alter(&$form) { } /** - * Implements hook_drupal_goto_alter(). - * - * Force the redirection to go to a non-secure page after being on a secure - * page through https.php. - */ -function session_test_drupal_goto_alter(&$path, &$options, &$http_response_code) { - global $base_insecure_url, $is_https_mock; - // Alter the redirect to use HTTP when using a mock HTTPS request through - // https.php because form submissions would otherwise redirect to a - // non-existent HTTPS site. - if (!empty($is_https_mock)) { - $path = $base_insecure_url . '/' . $path; - } -} - -/** * Menu callback, only available if current user is logged in. */ function _session_test_is_logged_in() { diff --git a/core/modules/system/tests/modules/system_test/system_test.module b/core/modules/system/tests/modules/system_test/system_test.module index 97c6f03..26f7071 100644 --- a/core/modules/system/tests/modules/system_test/system_test.module +++ b/core/modules/system/tests/modules/system_test/system_test.module @@ -289,5 +289,5 @@ function system_test_filetransfer_info() { function system_test_authorize_init_page($page_title) { $authorize_url = $GLOBALS['base_url'] . '/core/authorize.php'; system_authorized_init('system_test_authorize_run', drupal_get_path('module', 'system_test') . '/system_test.module', array(), $page_title); - drupal_goto($authorize_url); + return new RedirectResponse($authorize_url); } diff --git a/core/modules/update/update.manager.inc b/core/modules/update/update.manager.inc index bc84058..64b24be 100644 --- a/core/modules/update/update.manager.inc +++ b/core/modules/update/update.manager.inc @@ -38,6 +38,7 @@ use Drupal\Core\Updater\Updater; use Drupal\Core\FileTransfer\Local; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * @defgroup update_manager_update Update Manager module: update @@ -356,7 +357,7 @@ function update_manager_download_batch_finished($success, $results) { elseif ($success) { drupal_set_message(t('Updates downloaded successfully.')); $_SESSION['update_manager_update_projects'] = $results['projects']; - drupal_goto('admin/update/ready'); + return new RedirectResponse('admin/update/ready'); } else { // Ideally we're catching all Exceptions, so they should never see this, diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php index ceecd65..a7b2bf2 100644 --- a/core/modules/user/lib/Drupal/user/RegisterFormController.php +++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php @@ -7,6 +7,8 @@ namespace Drupal\user; +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * Form controller for the user register forms. */ @@ -31,7 +33,7 @@ public function form(array $form, array &$form_state) { // If we aren't admin but already logged on, go to the user page instead. if (!$admin && $user->uid) { - drupal_goto('user/' . $user->uid); + return new RedirectResponse('user/' . $user->uid); } $form['#attached']['library'][] = array('system', 'jquery.cookie'); diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 9a1b32c..15b227a 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1060,7 +1060,7 @@ function user_menu() { /** * Implements hook_menu_site_status_alter(). */ -function user_menu_site_status_alter(&$menu_site_status, $path) { +function user_menu_site_status_alter(&$menu_site_status, $path, &$redirect_path) { if ($menu_site_status == MENU_SITE_OFFLINE) { // If the site is offline, log out unprivileged users. if (user_is_logged_in() && !user_access('access site in maintenance mode')) { @@ -1072,7 +1072,8 @@ function user_menu_site_status_alter(&$menu_site_status, $path) { switch ($path) { case 'user': // Forward anonymous user to login page. - drupal_goto('user/login'); + $redirect_path = 'user/login'; + break; case 'user/login': case 'user/password': // Disable offline mode. @@ -1090,11 +1091,11 @@ function user_menu_site_status_alter(&$menu_site_status, $path) { if (user_is_logged_in()) { if ($path == 'user/login') { // If user is logged in, redirect to 'user' instead of giving 403. - drupal_goto('user'); + $redirect_path = 'user'; } if ($path == 'user/register') { // Authenticated user should be redirected to user edit page. - drupal_goto('user/' . $GLOBALS['user']->uid . '/edit'); + $redirect_path = 'user/' . $GLOBALS['user']->uid . '/edit'; } } } @@ -2169,7 +2170,7 @@ function user_multiple_cancel_confirm($form, &$form_state) { drupal_set_message($message, $redirect ? 'error' : 'warning'); // If only user 1 was selected, redirect to the overview. if ($redirect) { - drupal_goto('admin/people'); + return new RedirectResponse('admin/people'); } } diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc index 0a86e67..ec79ae3 100644 --- a/core/modules/user/user.pages.inc +++ b/core/modules/user/user.pages.inc @@ -104,7 +104,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a drupal_set_message(t('The one-time login link you clicked is invalid.')); } } - drupal_goto(); + return new RedirectResponse(url('')); } else { // Time out, in seconds, until login URL expires. @@ -116,7 +116,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a // No time out for first time login. if ($account->login && $current - $timestamp > $timeout) { drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.')); - drupal_goto('user/password'); + return new RedirectResponse('user/password'); } elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { // First stage is a confirmation form, then login @@ -131,7 +131,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a // Let the user's password be changed without the current password check. $token = drupal_hash_base64(drupal_random_bytes(55)); $_SESSION['pass_reset_' . $user->uid] = $token; - drupal_goto('user/' . $user->uid . '/edit', array('query' => array('pass-reset-token' => $token))); + return new RedirectResponse('user/' . $user->uid . '/edit', array('query' => array('pass-reset-token' => $token))); } else { if (!$account->login) { @@ -150,7 +150,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a } else { drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.')); - drupal_goto('user/password'); + return new RedirectResponse('user/password'); } } else { @@ -174,7 +174,7 @@ function user_logout() { // Destroy the current session, and reset $user to the anonymous user. session_destroy(); - drupal_goto(); + return new RedirectResponse(url('')); } /** @@ -397,7 +397,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') { } else { drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.')); - drupal_goto("user/$account->uid/cancel"); + return new RedirectResponse("user/{$account->id()}/cancel"); } } throw new AccessDeniedHttpException(); diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php index 94a3eb2..250470a 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewEditFormController.php @@ -240,7 +240,7 @@ public function submit(array $form, array &$form_state) { if (($display->getPluginId() == 'page') && ($old_path == $destination) && ($old_path != $view->get('executable')->displayHandlers->get($id)->getOption('path'))) { $destination = $view->get('executable')->displayHandlers->get($id)->getOption('path'); $query->remove('destination'); - // @todo For whatever reason drupal_goto is still using $_GET. + // @todo For whatever reason RedirectResponse uses query parameters. // @see http://drupal.org/node/1668866 unset($_GET['destination']); }