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_display are copied onto the mail_1 sub-element and then unset on the parent.
  • #required is copied onto the sub-elements and then forced to FALSE on the parent.
  • #required_error is copied onto the sub-elements but is not cleared on the parent, which is why requiredError still 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_confirm fallback to WebformElementProperty::resolve(), handling the flat and flexbox-nested mail_1 positions.
  • Rewrite EmailConfirmTest::testMetadataDescriptionHelpRequiredAbsorbed() to assert the restored required / description / help / helpTitle (alongside the already-correct requiredError) for the fully-configured fixture element.
  • Correct the now-inaccurate prose: the test docblock and the graphql_webform_test_emailconf fixture description that claim the values are read from the confirmation input fields.
  • Run phpunit, phpcs and phpstan before commit.
Command icon 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

pfrenssen created an issue. See original summary.

  • pfrenssen committed f304476f on 3.x
    fix: #3604017 Email confirm metadata empty
    
    By: pfrenssen
    
pfrenssen’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.