wintercms / winter

Free, open-source, self-hosted CMS platform based on the Laravel PHP Framework.
https://wintercms.com
MIT License
1.38k stars 193 forks source link

The "responseError" variable in Snowboard is null when a ValidationException is thrown #1211

Open diegoflorez opened 1 month ago

diegoflorez commented 1 month ago

Winter CMS Build

1.2

PHP Version

8.1

Database engine

MySQL/MariaDB

Plugins installed

No response

Issue description

When submitting a form using Snowboard.request in WinterCMS, validation errors that should be caught in the responseError of the error handler are instead returned in the responseData of the error handler.

The expected behavior is that validation errors, when thrown as a ValidationException, should be returned in responseError. However, they are currently being returned in responseData, which is inconsistent with the expected Snowboard behavior for error handling.

Steps to replicate

  1. Create a simple form in WinterCMS and trigger a validation error using the ValidationException class.
  2. Use the following form partial and backend handler code to replicate the issue:

Form Partial (HTML + JS)

<form id="subscription-form" class="row mb-0">
    <!-- Name Input -->
    <div class="form-group">
        <label for="name">Name</label>
        <input type="text"
               name="name"
               id="name"
               class="required"
               placeholder="John Doe"
               required>
    </div>

    <!-- Email Input -->
    <div class="form-group">
        <label for="email">Email</label>
        <input type="email"
               name="email"
               id="email"
               class="required"
               placeholder="user@company.com"
               required>
    </div>

    <!-- Message Input -->
    <div class="form-group">
        <label for="message">Message</label>
        <textarea name="message"
                  id="message"
                  class="required"
                  cols="30"
                  rows="5"
                  placeholder="Please let us know how we can help you..."
                  required></textarea>
    </div>

    <!-- Submit Button -->
    <div class="col-12">
        <button type="submit">Subscribe</button>
    </div>
</form>

<div class="form-result"></div>

<script>
  document.addEventListener('DOMContentLoaded', function () {
  const form = document.getElementById('subscription-form');
  const formResult = document.querySelector('.form-result');

  form.addEventListener('submit', function (e) {
    e.preventDefault();

    // Trigger a Snowboard request
    Snowboard.request(form, 'onSubscribe', {
      loading: form.querySelector('button'),
      success: function (response) {
        // Handle success
        formResult.innerHTML = `<div class="alert alert-success">${response.message}</div>`;
        form.reset();
      },
      error: function (response, request) {
        // Expected: responseError contains the error details
        // Actual: validation errors are returned in request.responseData

        console.log('Error: ', request.responseError); // This is null
        console.log('Data: ', request.responseData); // This contains the validation errors

        // Display error messages
        if (request.responseData && request.responseData.X_WINTER_ERROR_FIELDS) {
          let errorMessage = '<div class="alert alert-danger"><ul>';
          Object.keys(request.responseData.X_WINTER_ERROR_FIELDS).forEach(function (field) {
            request.responseData.X_WINTER_ERROR_FIELDS[field].forEach(function (error) {
              errorMessage += `<li>${error}</li>`;
            });
          });
          errorMessage += '</ul></div>';
          formResult.innerHTML = errorMessage;
        } else {
          formResult.innerHTML = `<div class="alert alert-danger">${response.message}</div>`;
        }
      }
    });
  });
});

</script>

Backend Handler (PHP)

use Winter\Storm\Exception\ValidationException;

public function onSubscribe()
{
    $data = post();

    // Validation rules
    $rules = [
        'name' => 'required|string|max:255',
        'email' => 'required|email',
        'message' => 'required|string|max:500',
    ];

    // Validate input data
    $validator = Validator::make($data, $rules);

    if ($validator->fails()) {
        throw new ValidationException($validator); // Should return in responseError
    }

    // Simulate a successful save to the database
    return [
        'message' => 'Thank you for subscribing!',
    ];
}

Expected Behavior:

When a ValidationException is thrown, the error response should be captured in responseError in the error handler of the Snowboard.request.

Actual Behavior:

Possible Fix:

Ensure that validation errors, when thrown as ValidationException, are returned in the responseError instead of responseData.

Environment:

Thank you for looking into this issue!

Workaround

No response

LukeTowers commented 1 month ago

@bennothommo any thoughts?

bennothommo commented 1 month ago

@diegoflorez this is partially intended, based on the behavior of the previous AJAX framework.

Validation errors in the PHP side are returned with a response code of HTTP 406 Not Acceptable, which in the old framework was interpreted as a "successful" response, because it may include partial updates (such as displaying said validation errors in a specific way in a component, for example). Thus, the validation errors are simply returned as response data, and we have kept that behavior intact with Snowboard.

However, the responseError variable is reserved for the main exception message and should be passed through, but it appears it may not be. So that particular part of the functionality may have a bug.