Skip to content

CaptchaManager doesn't set per-field messages on failure, breaking has-errors highlighting #627

@Sogl

Description

@Sogl

Plugin version

grav-plugin-form 8.2.1 (and likely all versions since CaptchaManager was introduced)

Description

When captcha validation fails (e.g. basic-captcha, turnstile), CaptchaManager::validateCaptcha() fires the onFormValidationError event with only a top-level message string, but does not include a per-field messages array.

As a result:

  • form.messages[field.name] is always empty for captcha fields in Twig templates
  • The field wrapper never receives the has-errors CSS class (which is driven by form.messages[field.name])
  • The inline_errors: true form option has no visual effect on captcha fields — no error text appears under the field, and no red border is applied

All other field types validated via the standard Grav pipeline do populate form.messages correctly, so the problem is specific to the new CaptchaManager.

Steps to reproduce

  1. Add a basic-captcha (or turnstile) field to a Grav form
  2. Enable inline_errors: true on the form
  3. Submit the form with an incorrect captcha answer
  4. Observe: the top-level error message appears, but the captcha input field does not get the has-errors class and shows no inline error

Root cause

In classes/Captcha/CaptchaManager.php, the onFormValidationError event is fired without a messages array:

// Current (broken) code — lines ~96–100
Grav::instance()->fireEvent('onFormValidationError', new Event([
    'form'     => $form,
    'message'  => $errorMessage,
    // 'messages' key is missing!
    'provider' => $providerName
]));

In form.php, onFormValidationError assigns $event['messages'] to $form->messages:

// form.php, onFormValidationError()
$form->status   = 'error';
$form->message  = $event['message'];
$form->messages = $event['messages']; // will be null because CaptchaManager didn't set it

In forms/default/field.html.twig, has-errors depends on form.messages[field.name]:

{% set errors = attribute(form.messages, field.name) %}
{%- if errors %}{% set form_field_outer_core = form_field_outer_core ~ ' has-errors' %}{% endif -%}

Since form.messages is null, errors is always falsy for captcha fields → no has-errors class.

For comparison, standard field validation in Form.php (line 950–951) correctly populates messages:

$this->messages = array_merge($this->messages, $e->getMessages());
$event = new Event(['form' => $this, 'message' => $this->message, 'messages' => $this->messages]);

Expected behaviour

CaptchaManager should pass a messages array in the event, mapping the captcha field name to the error message — exactly the same convention used by regular field validation:

Grav::instance()->fireEvent('onFormValidationError', new Event([
    'form'     => $form,
    'message'  => $errorMessage,
    'messages' => [$captchaField['name'] => [$errorMessage]], // <-- fix
    'provider' => $providerName
]));

This should be applied to both the !$result['success'] branch and the catch (\Exception $e) branch in validateCaptcha().

Workaround

Until this is fixed, the has-errors class can be forced in a theme-level Twig override of forms/default/field.html.twig:

{% set errors = attribute(form.messages, field.name) %}
{% if not errors and field.type == 'basic-captcha' and form.status == 'error' %}
    {% set errors = [form.message] %}
{% endif %}

This is a hack and should not be necessary once CaptchaManager is corrected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions