Problem/Motivation
Starting in Webform 6.3.1, saving a webform as a draft fails with a LogicException whenever the form contains a managed file upload. Submitting the same form normally (without saving as a draft) works without problems. This works correctly in Webform 6.3.0.
The full error message is:
LogicException: Form errors cannot be set after form validation has finished. in Drupal\Core\Form\FormState->setErrorByName() (line 1119 of /web/core/lib/Drupal/Core/Form/FormState.php).
#0 -/web/core/lib/Drupal/Core/Form/FormState.php(1156): Drupal\Core\Form\FormState->setErrorByName()
#1 -/web/modules/contrib/webform/src/Plugin/WebformElement/WebformManagedFileBase.php(915): Drupal\Core\Form\FormState->setError()
#2 [internal function]: Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::valueCallback()
#3 -/web/core/lib/Drupal/Core/Form/FormBuilder.php(1264): call_user_func_array()
#4 -/web/core/lib/Drupal/Core/Form/FormBuilder.php(1004): Drupal\Core\Form\FormBuilder->handleInputElement()
#5 -/web/core/lib/Drupal/Core/Form/FormBuilder.php(1074): Drupal\Core\Form\FormBuilder->doBuildForm()
#6 -/web/core/lib/Drupal/Core/Form/FormBuilder.php(1074): Drupal\Core\Form\FormBuilder->doBuildForm()
#7 -/web/core/lib/Drupal/Core/Form/FormBuilder.php(449): Drupal\Core\Form\FormBuilder->doBuildForm()
#8 -/web/core/lib/Drupal/Core/Form/FormBuilder.php(633): Drupal\Core\Form\FormBuilder->rebuildForm()
#9 -/web/core/lib/Drupal/Core/Form/FormBuilder.php(326): Drupal\Core\Form\FormBuilder->processForm()
#10 -/web/core/lib/Drupal/Core/Entity/EntityFormBuilder.php(48): Drupal\Core\Form\FormBuilder->buildForm()
#11 -/web/modules/contrib/webform/src/Entity/Webform.php(1243): Drupal\Core\Entity\EntityFormBuilder->getForm()
#12 -/web/modules/contrib/webform/src/Controller/WebformEntityController.php(97): Drupal\webform\Entity\Webform->getSubmissionForm()
#13 [internal function]: Drupal\webform\Controller\WebformEntityController->addForm()
#14 -/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()
#15 -/web/core/lib/Drupal/Core/Render/Renderer.php(637): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()
#16 -/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\Core\Render\Renderer->executeInRenderContext()
#17 -/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()
#18 -/vendor/symfony/http-kernel/HttpKernel.php(181): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()
#19 -/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\Component\HttpKernel\HttpKernel->handleRaw()
#20 -/web/core/lib/Drupal/Core/StackMiddleware/Session.php(53): Symfony\Component\HttpKernel\HttpKernel->handle()
#21 -/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle()
#22 -/web/core/lib/Drupal/Core/StackMiddleware/ContentLength.php(28): Drupal\Core\StackMiddleware\KernelPreHandle->handle()
#23 -/web/core/modules/big_pipe/src/StackMiddleware/ContentLength.php(32): Drupal\Core\StackMiddleware\ContentLength->handle()
#24 -/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(116): Drupal\big_pipe\StackMiddleware\ContentLength->handle()
#25 -/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(90): Drupal\page_cache\StackMiddleware\PageCache->pass()
#26 -/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\page_cache\StackMiddleware\PageCache->handle()
#27 -/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle()
#28 -/web/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php(36): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle()
#29 -/web/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\Core\StackMiddleware\AjaxPageState->handle()
#30 -/web/core/lib/Drupal/Core/DrupalKernel.php(741): Drupal\Core\StackMiddleware\StackedHttpKernel->handle()
#31 -/web/index.php(19): Drupal\Core\DrupalKernel->handle()
#32 {main}
Steps to reproduce
Install Drupal 10.6.18 with Webform 6.3.1, with a private file system configured.
Create a webform with a file element and enable "Allow users to save a draft" under the form's Draft settings.
Log in as an authenticated user, upload a file, click Save Draft.
Result: the LogicException.
My yml source coude for the form is:
subject:
'#type': textfield
'#title': Subject
'#required': true
test_pdf:
'#type': document_file
'#title': 'Test pdf'
'#max_filesize': '1'
'#required': true
I already have a clean Drupal 10.6.18 / Webform 6.3.1 installation with a ready form to test if needed.
Issue fork webform-3626841
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
liam morlandWhat version did you upgrade from? Can you use
git bisectto figure out which commit caused this?Comment #4
initsos commentedThanks for the suggestion.
I tested this on a staging copy:
- Webform 6.3.0: Save Draft with an uploaded file works.
- Webform 6.3.1: Save Draft with an uploaded file throws "LogicException: Form errors cannot be set after form validation has finished."
So this is a regression in 6.3.1. I can't run git bisect directly because I don't have Webform in a git checkout.
What I found from the stack trace:
- The exception comes from WebformManagedFileBase::valueCallback() (line 915), which calls FormState::setError() while FormBuilder::rebuildForm() runs after validation has completed. The full trace is in the issue description.
- My understanding is that the new file-reference check treats a file as invalid when !$file->isTemporary(). Saving a draft makes the file permanent, so the check fails when the form is rebuilt with the same input. This is my reading of the code and I haven't confirmed it in a debugger.
- I also saw "The uploaded file is invalid" appear when removing or adding files on a resumed draft. This may be the same cause, since those files are already saved.
I have a temporary local workaround in valueCallback(). It skips the check once validation is complete, and it trusts file IDs that are already saved on the submission (including rows of a multiple-value composite). On my site Save Draft works again, including custom_composite rows with webform_document_file elements. It skips a check in one code path, so it as a local stopgap and not a proposed fix. I can attach the diff if useful.
I used AI assistance to help analyze the stack trace and draft the workaround. I reviewed the code and tested it on my own site (the 6.3.0 versus 6.3.1 behavior and the workaround on my forms). I have not run the Webform test suite or done a security review of the workaround.
Comment #5
liam morlandIt would be very helpful to figure out which commit caused the problem.
Comment #6
mingsongIt seems to me that it is a same issue reported at #3625496: Managed file valueCallback() rejects a draft's own previously uploaded files, forcing re-upload on resume
Comment #7
liam morland