Issue #3632737: Final submit validation rebuilds the entire form once per wizard page
Closes #3632737
Fixes #3632737.
Problem
With forward navigation, the final submit calls WebformNavigationHelper::validateAllPages(), which validates each accessible page in its own programmatic submission of the form. Webform hides every page except the current one by setting #access to FALSE on it (WebformWizardPage::hidePage()), so each page needs a separate build. On a webform with N pages, the final submit builds the complete form N + 1 times, running every form alter and webform handler each time. On large webforms this makes the final submit very slow.
Changes
- Opt-in single pass validation. A new third-party setting, "Validate all pages in a single pass on final submit" (
single_pass_validation, off by default), validates every accessible page in one build of the form. The handler shows the hidden pages in that build, restoring each element's access with the same checksWebformElementBase::prepare()applies (disabled elements,#private, access rules and handleraccessElement()), so elements the user has no access to are still not validated. - Validation build flag. Every form built to validate the submission, in either mode, has the
webformnavigation_validatingform state value set (WebformNavigationHelper::VALIDATING). The navigation handler uses it to skip its page visit logging and error messages, which are only needed for display, and other modules can use it to skip display-only work in their form alters. - Pages for page-scoped handlers. In single pass mode, the
webformnavigation_validate_pagesform state value (WebformNavigationHelper::VALIDATE_PAGES) lists the pages being validated. Handlers that only validate the current page can validate each listed page instead; the README shows the pattern. - Error logging for every validated page.
validateAllPages()now resets and logs the errors of every validated page once, instead of relying on each inner build to log its own page. This is required for single pass mode, and it means an error fixed from another page (e.g. a field made required by a#statescondition on a later page) is cleared in both modes.logPageErrors()takes an optional list of pages to reset. - The accessible pages are computed once instead of once per page.
Without the new setting, each page is still validated in its own build, and the same errors are reported as before.
API changes
WebformNavigationHelper::__construct()takes the webform element manager (@plugin.manager.webform.element) as a new last argument.- New
WebformNavigationHelper::showPages(), and theVALIDATINGandVALIDATE_PAGESconstants. WebformNavigationHelper::logPageErrors()has a new optional$pagesargument.
Behavior change when the setting is enabled
Webform handlers' validateForm() runs once, with every page shown, instead of once per page. Handlers that only validate $webform_submission->getCurrentPage() need to use webformnavigation_validate_pages before the setting is enabled. This is described in the setting's description and the README.
Tests
Adds ValidateAllPagesTest and a small webformnavigation_test module that records each validation build and the pages it lists. Each test runs with and without single pass validation.
| Test | Checks |
|---|---|
testValidateAllPages |
Skipping to the last page and submitting reports the errors of each skipped page, does not validate a #private element the user has no access to, and completes once the errors are fixed. One build per page makes 3 validation builds; single pass makes 1, listing all 3 pages. |
testErrorFixedFromAnotherPageIsCleared |
A page two field required by a checkbox on page three: unchecking it on page three clears the page two error without revisiting page two. |
ThirdPartySettingsBooleanTest also covers the new checkbox being stored as a boolean.