TextareaWidget::errorElement() does not consult violation property path.

Created on 23 August 2021, almost 3 years ago
Updated 15 May 2024, about 1 month ago

Problem/Motivation

When attempting to add a validation constraint on a text_long field (without summary), I ran into an issue where the violation path (string) $delta . '.value' did not actually attach the error message to the value property, but rather to the whole widget.

Looking at Drupal\text\Plugin\Field\FieldWidget\TextareaWithSummaryWidget, we see ::errorElement() like so:

  /**
   * {@inheritdoc}
   */
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
    $element = parent::errorElement($element, $violation, $form, $form_state);
    return ($element === FALSE) ? FALSE : $element[$violation->arrayPropertyPath[0]];
  }

which is fine and good. Provided a $violation->arrayPropertyPath of 'value', a violation targeting field_wysiwyg.value will attach appropriately.

TextareaWidget is somewhat different:

  /**
   * {@inheritdoc}
   */
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
    if ($violation->arrayPropertyPath == ['format'] && isset($element['format']['#access']) && !$element['format']['#access']) {
      // Ignore validation errors for formats if formats may not be changed,
      // such as when existing formats become invalid.
      // See \Drupal\filter\Element\TextFormat::processFormat().
      return FALSE;
    }
    return $element;
  }

If called directly, it won't consult the arrayPropertyPath and thus will target both the value and the format, leading to unfortunate issues like this.

Steps to reproduce

Create a constraint and validator targeting a text_long field using a textarea widget, and attempt to target the `value` property of the field in the validator like so:

        $this->context->buildViolation($constraint->someMessage)
          ->atPath((string) $delta . '.value')
          ->setInvalidValue($text)
          ->addViolation();

Notice that both the format and the value fields are highlighted, that an error message appears under each, and that the link to the field is constructed inaccurately (e.g. it will link to /node/11463/edit#edit-field-content-block-0-subform-field-wysiwyg-0 when it should link to /node/11463/edit#edit-field-content-block-0-subform-field-wysiwyg-0-value.

Proposed resolution

My proposed fix is to consult arrayPropertyPath in TextareaWidget and then remove the override in TextareaWithSummaryWidget.

Like so:

TextareaWidget:


  /**
   * {@inheritdoc}
   */
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
    if ($violation->arrayPropertyPath == ['format'] && isset($element['format']['#access']) && !$element['format']['#access']) {
      // Ignore validation errors for formats if formats may not be changed,
      // such as when existing formats become invalid.
      // See \Drupal\filter\Element\TextFormat::processFormat().
      return FALSE;
    }
    if (($property = $violation->arrayPropertyPath[0]) && !empty($element[$property])) {
      return $element[$property];
    }
    return $element;
  }

Remaining tasks

I'll upload a patch here as soon as I remember how.

πŸ› Bug report
Status

Active

Version

11.0 πŸ”₯

Component
TextΒ  β†’

Last updated 9 days ago

Created by

πŸ‡ΊπŸ‡ΈUnited States natedouglas

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡¦πŸ‡ΊAustralia gordon Melbourne

    I have just ran across this issue and I do not think this is a good solution. It fixes the TextAreaWidget and descendants but not any other type of field will be similar (Single widget which has multiple fields).

    From the example above.

    $this->context->buildViolation($constraint->someMessage)
              ->atPath((string) $delta . '.value')
              ->setInvalidValue($text)
              ->addViolation();
    

    When creating the violation as above using the `::AtPath('value')`, the WidgetBase::flagErrors() is then splitting all the violations into 2 groups. A delta list and an item list. The delta list is basically, the violation has the path of `body.0.value` and everything else goes into the item list.

    Since this is a delta list it uses the delta and anything after the delta is discarded (In this case the `value` is not used). In reality it should use the rest of the path after the delta to find the element that the error should be added to.

Production build 0.69.0 2024