Problem/Motivation
A webform_email_confirm element configured with #required: true, #description and #help exposes empty metadata over GraphQL: metadata.required is false, metadata.description and metadata.help are "", while metadata.requiredError on the same element resolves correctly. The authored values are silently lost: there is currently no field a consumer can read them from.
Root cause is the build-form switch from #3595276: Expose the webform elements on the built form instead of on the configured form (part of #3595219: 3.0 Exploration). These fields resolve through the generic WebformElementProperty producer, which reads the built element render array. For webform_email_confirm, the render element's #process callback (WebformEmailConfirm::processWebformEmailConfirm()) redistributes parent properties before the schema walks the element:
#description,#help,#help_title,#help_displayare copied onto themail_1sub-element and then unset on the parent.#requiredis copied onto the sub-elements and then forced toFALSEon the parent.#required_erroris copied onto the sub-elements but is not cleared on the parent, which is whyrequiredErrorstill resolves. That is the asymmetry in the report.
So on the built parent element the schema walks, these properties are gone, and the producer reads empty. The mail_1 / mail_2 sub-elements that now hold them are not exposed as separate GraphQL elements, so the values are unreachable.
The module currently codifies this data loss as intended, on an incorrect premise. EmailConfirmTest::testMetadataDescriptionHelpRequiredAbsorbed() asserts the empty values, and its docblock (and the graphql_webform_test_emailconf fixture description) state that a frontend reads the authored values from the confirmation input fields (confirmDescription, ...). That is wrong: confirmDescription resolves from the distinct #confirm__description property of the second input, not from the parent #description; and the parent values are moved onto mail_1 (the first input), not the confirmation input. The authored #description / #help / #required have no exposed read path. This is a regression, not intended behavior.
Steps to reproduce
Define a webform with a webform_email_confirm element setting #required: true, #required_error, #description and #help, then query:
{ webformById(id: "ID") { form { elements { ... on WebformElement { metadata { key required requiredError description help } } } } } }
Observe required: false, description: "", help: "", but a populated requiredError.
Proposed resolution
Restore parent-level exposure in WebformElementProperty::resolve(). When the element is webform_email_confirm and the requested parent property is empty, fall back to the value carried on the mail_1 sub-element. This mirrors the existing webform_multiple wrapper fallback already in that producer, which recovers a property the wrapper redistributes to its per-item template.
The fallback must locate mail_1 in both positions the #process callback can leave it: directly at element['mail_1'], or nested at element['flexbox']['mail_1'] when the element uses the side-by-side flexbox layout. This recovers required, description, help and helpTitle on the parent, matching the pre-build-form contract and consumer expectations. requiredError still reads from the parent and is unaffected.
The alternative, exposing mail_1 / mail_2 as their own GraphQL elements and telling consumers to read metadata there, is rejected: it is a larger surface change, and the parent-level contract is what consumers already expect and what every other element type provides.
Other element types that redistribute properties were checked: webform_multiple is already handled by its wrapper fallback, and the composite form-element trait and webform_terms_of_service keep their parent #description / #required. The gap is specific to webform_email_confirm.
Remaining tasks
- Add the
webform_email_confirmfallback toWebformElementProperty::resolve(), handling the flat and flexbox-nestedmail_1positions. - Rewrite
EmailConfirmTest::testMetadataDescriptionHelpRequiredAbsorbed()to assert the restoredrequired/description/help/helpTitle(alongside the already-correctrequiredError) for the fully-configured fixture element. - Correct the now-inaccurate prose: the test docblock and the
graphql_webform_test_emailconffixture description that claim the values are read from the confirmation input fields. - Run phpunit, phpcs and phpstan before commit.
Issue fork graphql_webform-3604017
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 #4
pfrenssen