core/lib/Drupal/Core/Form/FormBuilder.php | 12 ++++++++++ .../Drupal/Core/Render/Element/RenderElement.php | 2 +- .../system/src/Controller/FormAjaxController.php | 27 +++++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 4b995c1..fa411a2 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -17,6 +17,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Render\Element; use Drupal\Core\Render\ElementInfoManagerInterface; +use Drupal\Core\Site\Settings; use Drupal\Core\Theme\ThemeManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -180,6 +181,10 @@ public function getForm($form_arg) { */ public function buildForm($form_id, FormStateInterface &$form_state) { // Ensure the form ID is prepared. + $private_key = \Drupal::service('private_key'); + $keyvalue = \Drupal::service('keyvalue.expirable'); + $form_build_key = 'form_build_key:' . Crypt::hmacBase64(serialize($form_id) . serialize($form_state), 'form_key' . $private_key->get() . Settings::getHashSalt()); + $keyvalue->get('form_build_key')->setWithExpire($form_build_key, [$form_id, $form_state], 21600); $form_id = $this->getFormId($form_id, $form_state); $input = $form_state->getUserInput(); @@ -221,6 +226,13 @@ public function buildForm($form_id, FormStateInterface &$form_state) { $form = $this->retrieveForm($form_id, $form_state); $this->prepareForm($form_id, $form, $form_state); + $form['form_build_key'] = array( + '#type' => 'hidden', + '#value' => $form_build_key, + '#name' => 'form_build_key', + '#parents' => array('form_build_key'), + ); + // self::setCache() removes uncacheable $form_state keys (see properties // in \Drupal\Core\Form\FormState) in order for multi-step forms to work // properly. This means that form processing logic for single-step forms diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php index 0283333..b064782 100644 --- a/core/lib/Drupal/Core/Render/Element/RenderElement.php +++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php @@ -128,7 +128,7 @@ public static function preRenderGroup($element) { */ public static function processAjaxForm(&$element, FormStateInterface $form_state, &$complete_form) { $element = static::preRenderAjaxForm($element); - if (!empty($element['#ajax_processed'])) { + if (!empty($element['#ajax_processed']) && isset($element['#ajax']['url'])) { $form_state->setCached(); } return $element; diff --git a/core/modules/system/src/Controller/FormAjaxController.php b/core/modules/system/src/Controller/FormAjaxController.php index 0c29322..87210fe 100644 --- a/core/modules/system/src/Controller/FormAjaxController.php +++ b/core/modules/system/src/Controller/FormAjaxController.php @@ -114,13 +114,11 @@ public static function create(ContainerInterface $container) { * @throws \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface */ public function content(Request $request) { - $ajax_form = $this->getForm($request); + $ajax_form = $this->buildForm($request); $form = $ajax_form->getForm(); $form_state = $ajax_form->getFormState(); $commands = $ajax_form->getCommands(); - $this->formBuilder->processForm($form['#form_id'], $form, $form_state); - // We need to return the part of the form (or some other content) that needs // to be re-rendered so the browser can update the page with changed content. // Since this is the generic menu callback used by many Ajax elements, it is @@ -216,4 +214,27 @@ protected function getForm(Request $request) { return new FileAjaxForm($form, $form_state, $form_id, $form_build_id, $commands); } + protected function buildForm(Request $request) { + $form_build_id = $request->request->get('form_build_id'); + $form_build_key = $request->request->get('form_build_key'); + + $keyvalue = \Drupal::service('keyvalue.expirable'); + $result = $keyvalue->get('form_build_key')->get($form_build_key); + if ($result === NULL) { + $this->logger->warning('Expired form.'); + throw new BadRequestHttpException(); + } + else { + list($form_id, $form_state) = $result; + $form = $this->formBuilder->buildForm($form_id, $form_state); + + $commands = []; + if ($form_build_id != $form['#build_id']) { + // If the form build ID has changed, issue an Ajax command to update it. + $commands[] = new UpdateBuildIdCommand($form_build_id, $form['#build_id']); + } + return new FileAjaxForm($form, $form_state, $form_id, $form['#build_id'], $commands); + } + } + }