From 9334d6f100cc5cf7e687f80dca122efb7438273b Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Mon, 18 Mar 2024 09:44:54 +0000 Subject: [PATCH] Issue #3109970 by znerol, andypost, Prashant.c, daffie, Wim Leers, dww, catch: Convert uses of $_SESSION in forms and batch --- core/includes/batch.inc | 16 ++++++++++------ core/includes/form.inc | 14 +++++++++----- core/lib/Drupal/Core/Form/FormBuilder.php | 8 +++++--- .../tests/src/Functional/BigPipeTest.php | 15 +++++++++++++++ .../LanguageNegotiationSession.php | 4 +--- .../EventSubscriber/SessionTestSubscriber.php | 2 +- .../tests/src/Functional/Session/SessionTest.php | 2 +- .../Kernel/UserAccountFormPasswordResetTest.php | 9 +-------- .../Drupal/Tests/Core/Form/FormBuilderTest.php | 4 ++++ .../Drupal/Tests/Core/Form/FormTestBase.php | 3 +++ 10 files changed, 50 insertions(+), 27 deletions(-) diff --git a/core/includes/batch.inc b/core/includes/batch.inc index b187dcd5170a..3c80ee1d1db8 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -474,11 +474,14 @@ function _batch_finished() { } } // Clean-up the session. Not needed for CLI updates. - if (isset($_SESSION)) { - unset($_SESSION['batches'][$batch['id']]); - if (empty($_SESSION['batches'])) { - unset($_SESSION['batches']); - } + $session = \Drupal::request()->getSession(); + $batches = $session->get('batches', []); + unset($batches[$batch['id']]); + if (empty($batches)) { + $session->remove('batches'); + } + else { + $session->set('batches', $batches); } } $_batch = $batch; @@ -531,7 +534,8 @@ function _batch_finished() { // form needs to be rebuilt, save the final $form_state for // \Drupal\Core\Form\FormBuilderInterface::buildForm(). if ($_batch['form_state']->isRebuilding()) { - $_SESSION['batch_form_state'] = $_batch['form_state']; + $session = \Drupal::request()->getSession(); + $session->set('batch_form_state', $_batch['form_state']); } $callback = $_batch['redirect_callback']; $_batch['source_url']->mergeOptions(['query' => ['op' => 'finish', 'id' => $_batch['id']]]); diff --git a/core/includes/form.inc b/core/includes/form.inc index 80a04c2cbcec..6f1025e61f9e 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -612,7 +612,7 @@ function template_preprocess_form_element_label(&$variables) { * // The following keys allow for multi-step operations : * // 'sandbox' (read / write): An array that can be freely used to * // store persistent data between iterations. It is recommended to - * // use this instead of $_SESSION, which is unsafe if the user + * // use this instead of the session, which is unsafe if the user * // continues browsing in a separate window while the batch is processing. * // 'finished' (write): A float number between 0 and 1 informing * // the processing engine of the completion level for the operation. @@ -669,11 +669,12 @@ function template_preprocess_form_element_label(&$variables) { * $message = t('Finished with an error.'); * } * \Drupal::messenger()->addMessage($message); - * // Providing data for the redirected page is done through $_SESSION. + * // Providing data for the redirected page is done through the session. * foreach ($results as $result) { * $items[] = t('Loaded node %title.', array('%title' => $result)); * } - * $_SESSION['my_batch_results'] = $items; + * $session = \Drupal::getRequest()->getSession(); + * $session->set('my_batch_results', $items); * } * @endcode */ @@ -718,7 +719,7 @@ function template_preprocess_form_element_label(&$variables) { * - finished: Name of an implementation of callback_batch_finished(). This is * executed after the batch has completed. This should be used to perform * any result massaging that may be needed, and possibly save data in - * $_SESSION for display after final page redirection. + * the session for display after final page redirection. * - file: Path to the file containing the definitions of the 'operations' and * 'finished' functions, for instance if they don't reside in the main * .module file. The path should be relative to base_path(), and thus should @@ -952,7 +953,10 @@ function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = N \Drupal::service(BatchStorageInterface::class)->create($batch); // Set the batch number in the session to guarantee that it will stay alive. - $_SESSION['batches'][$batch['id']] = TRUE; + $session = \Drupal::request()->getSession(); + $batches = $session->get('batches', []); + $batches[$batch['id']] = TRUE; + $session->set('batches', $batches); // Redirect for processing. $query_options = $error_url->getOption('query'); diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index a379036cb4f3..2331a59e1f40 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -251,11 +251,13 @@ public function buildForm($form_arg, FormStateInterface &$form_state) { $form_state->setUserInput($input); } - if (isset($_SESSION['batch_form_state'])) { + // @todo Remove hasSession() condition in https://www.drupal.org/i/3413153 + if ($request->hasSession() && $request->getSession()->has('batch_form_state')) { // We've been redirected here after a batch processing. The form has // already been processed, but needs to be rebuilt. See _batch_finished(). - $form_state = $_SESSION['batch_form_state']; - unset($_SESSION['batch_form_state']); + $session = $request->getSession(); + $form_state = $session->get('batch_form_state'); + $session->remove('batch_form_state'); return $this->rebuildForm($form_id, $form_state); } diff --git a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php index 59681f05c063..0c55b3884864 100644 --- a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php +++ b/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php @@ -431,8 +431,23 @@ protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholde * Ensures CSRF tokens can be generated for the current user's session. */ protected function setCsrfTokenSeedInTestEnvironment() { + // Retrieve the CSRF token from the child site from its serialized session + // record in the database. $session_data = $this->container->get('session_handler.write_safe')->read($this->getSession()->getCookie($this->getSessionName())); $csrf_token_seed = unserialize(explode('_sf2_meta|', $session_data)[1])['s']; + + // Ensure that the session is started before accessing a session bag. + // Otherwise the value stored in the bag is lost when subsequent session + // access triggers a session start automatically. + + /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */ + $request_stack = $this->container->get('request_stack'); + $session = $request_stack->getSession(); + if (!$session->isStarted()) { + $session->start(); + } + + // Store the CSRF token in the test runners session metadata bag. $this->container->get('session_manager.metadata_bag')->setCsrfTokenSeed($csrf_token_seed); } diff --git a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSession.php b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSession.php index b17dd9dfa11e..365cfced6f03 100644 --- a/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSession.php +++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationSession.php @@ -88,9 +88,7 @@ public function getLangcode(Request $request = NULL) { if ($request->query->has($param)) { return $request->query->get($param); } - // @todo Remove hasSession() from condition in - // https://www.drupal.org/node/2484991 - if ($request->hasSession() && $request->getSession()->has($param)) { + if ($request->getSession()->has($param)) { return $request->getSession()->get($param); } } diff --git a/core/modules/system/tests/modules/session_test/src/EventSubscriber/SessionTestSubscriber.php b/core/modules/system/tests/modules/session_test/src/EventSubscriber/SessionTestSubscriber.php index 38b797a8a27d..35e491cfef1a 100644 --- a/core/modules/system/tests/modules/session_test/src/EventSubscriber/SessionTestSubscriber.php +++ b/core/modules/system/tests/modules/session_test/src/EventSubscriber/SessionTestSubscriber.php @@ -13,7 +13,7 @@ class SessionTestSubscriber implements EventSubscriberInterface { /** - * Stores whether $_SESSION is empty at the beginning of the request. + * Stores whether the session is empty at the beginning of the request. * * @var bool */ diff --git a/core/modules/system/tests/src/Functional/Session/SessionTest.php b/core/modules/system/tests/src/Functional/Session/SessionTest.php index 27e36f770d41..fa3ad0419851 100644 --- a/core/modules/system/tests/src/Functional/Session/SessionTest.php +++ b/core/modules/system/tests/src/Functional/Session/SessionTest.php @@ -403,7 +403,7 @@ public function assertSessionCookie(bool $sent): void { } /** - * Assert whether $_SESSION is empty at the beginning of the request. + * Assert whether the session is empty at the beginning of the request. * * @internal */ diff --git a/core/modules/user/tests/src/Kernel/UserAccountFormPasswordResetTest.php b/core/modules/user/tests/src/Kernel/UserAccountFormPasswordResetTest.php index 95bf84056da7..d122ebfa37e6 100644 --- a/core/modules/user/tests/src/Kernel/UserAccountFormPasswordResetTest.php +++ b/core/modules/user/tests/src/Kernel/UserAccountFormPasswordResetTest.php @@ -4,7 +4,6 @@ use Drupal\KernelTests\KernelTestBase; use Drupal\user\Entity\User; -use Symfony\Component\HttpFoundation\Session\Session; /** * Verifies that the password reset behaves as expected with form elements. @@ -53,14 +52,8 @@ public function testPasswordResetToken() { /** @var \Symfony\Component\HttpFoundation\Request $request */ $request = $this->container->get('request_stack')->getCurrentRequest(); - // @todo: Replace with $request->getSession() as soon as the session is - // present in KernelTestBase. - // see: https://www.drupal.org/node/2484991 - $session = new Session(); - $request->setSession($session); - $token = 'VALID_TOKEN'; - $session->set('pass_reset_1', $token); + $request->getSession()->set('pass_reset_1', $token); // Set token in query string. $request->query->set('pass-reset-token', $token); diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index f0b3425f5b86..e3bd606f94a9 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -22,6 +22,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; /** * @coversDefaultClass \Drupal\Core\Form\FormBuilder @@ -575,6 +577,7 @@ public function testFormCacheDeletionUncached() { */ public function testExceededFileSize() { $request = new Request([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]); + $request->setSession(new Session(new MockArraySessionStorage())); $request_stack = new RequestStack(); $request_stack->push($request); $this->formBuilder = $this->getMockBuilder('\Drupal\Core\Form\FormBuilder') @@ -598,6 +601,7 @@ public function testExceededFileSize() { public function testPostAjaxRequest(): void { $request = new Request([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE], ['form_id' => 'different_form_id']); $request->setMethod('POST'); + $request->setSession(new Session(new MockArraySessionStorage())); $this->requestStack->push($request); $form_state = (new FormState()) diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php index 9de2c5e1ac99..53c96350e836 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php +++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php @@ -14,6 +14,8 @@ use Drupal\Tests\UnitTestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; /** * Provides a base class for testing form functionality. @@ -184,6 +186,7 @@ protected function setUp(): void { $this->account = $this->createMock('Drupal\Core\Session\AccountInterface'); $this->themeManager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface'); $this->request = Request::createFromGlobals(); + $this->request->setSession(new Session(new MockArraySessionStorage())); $this->eventDispatcher = $this->createMock('Symfony\Contracts\EventDispatcher\EventDispatcherInterface'); $this->requestStack = new RequestStack(); $this->requestStack->push($this->request); -- GitLab