1

I have a site with Pixel & Tonic's contact form. This site is built using Nitro. The contact form has a 'post' submit, and a page redirect to a thank you page if the form is sent properly. There's not Ajax involved.

The form was working well. I'm not sure if this is just a mere coincidence, but I upgraded today to the latest CraftCMS version (3.17.19) and, my default browser, Firefox, was updated too. Afterwards I noticed the problem. The form's version is 2.2.7.

I have inline JS validation before sending. This part is working. And when the inputs are valid, there's an attempt to submit the form.

Examining the network traffic, I see that the Request parameters in the post call, are empty (Firefox reports it as No payload for this request). I was expecting to see the submitted data.

Also I noticed something a bit puzzling. The form has this code:

{{ csrfInput() }}
{{ redirectInput('thank-you?from={fromName}') }}

Which in the frontend, are rendered like this:

<input type="hidden" name="CRAFT_CSRF_TOKEN" value="UWOm80Y6Hu-ADjHBk2qH8Ds8C3MHSxut0scjX4O6skpfvZkNDtu7ySpoebWma8RJF5JLXUlCotOkyETtdhWK1BAGGjth2yploNIRzC-paXQyZslvGa5l65dG8dcBv_MNb_uBvUvwe2LzzdZHvnEtwZnHujgJHdkJ2uBEC8fX4JMWpgGYfBzsFnjoNZ4aFddqnz4i2SNtA2-A2ErBfvV-RjZTQx4YjYFZxsgKZDupMACKXTvnjCWXMRirSegj7lOXjqCT-aQYli9tB7tk9iIfBQhQ98QEYDOH9Fh7kN4kxL9Ua1MYbiVD9L6SWzjtjdACDdP9YX6O7J1WWxqHk138eSOlfW4tcMO1kP8l3UItueN1YCMMUelPUZCwJPlKkV4XBlL9VimZVt-gJ5S2Nd7FO1zN4tgokh5bkpTlFokzd-zxs-xyWFCXSpWPE1Osvo7LT8pU4Bty23Qwulv6dmWCPctCE6UHX3pLsetumVCiGzZZAgdrVtvGMoj4QSVimkF5pDRIlrln-HxL2SCgYKVl_faV9a6dIeIeGTbTF7d7Uko=">
<input type="hidden" name="redirect" value="86f9c378e703b2be657e4a742b9ff62ab3909e6c2ebb2cc64ad7a6ed7495dfc3thank-you?from={fromName}">

I don't remember seeing the long string of characters in the redirect input's value. Instead, I thought it would be something like this thank-you?from{fromName} only.

I wonder if the update is the reason behind this problem. Thanks so much for your help.

EDIT When I disable the JS validation, the contact form works well. However, when it is enabled, the sent post request is empty. I'm pasting below the validation script. What I find strange is that right before doing a form.submit(), I'm logging the FormData parameters, and all the entered information is logged(!).

<script>
    (function() {
        // form elements
        const form = document.querySelector(".contactForm");
        const form_button = form.querySelector(".outsetSend");
        const fieldset = form.querySelector('fieldset');
        
        // Adds listener to the form
        formListener = ()=>{
            form.addEventListener("submit", e => {
                e.preventDefault();
                let formData = new FormData(form);
                // Validates form, returns false when there's no errors
                if ( !formErrors(formData) ) {
                    // logs the data the form validated 
                    for(var pair of formData.entries()) {
                       console.log(pair[0]+ ': '+ pair[1]);
                    }
                    // shows spinner and disables fields and buttons
                    hyper.formSending();
                    // sends form 
                    form.submit();
                }
                return false;
            });
        };
        
        // adds listener to required fields so that error messages hide when field is on focus
        requiredFieldsListener = ()=>{
            // figures which fields are required
            let required = form.querySelectorAll("[required]");
            // Adds listener to each entry
            for (let i = 0; i < required.length; ++i) {
               required[i].addEventListener('focus', e =>{
                   let parent = e.target.closest(".outsetField");
                   parent.classList.remove('validationError');
                   // removes text content
                   let errorArea = parent.querySelector('.outsetField-error');
                   errorArea.textContent = '';
               } );
            }
        };

        // Validates form
        // Checks for errors in the contact form submission
        // returns false when there's no errors
        formErrors = (formData)=>{
            let error = false;
            let errorArray = new Array();
            let toValidate = new Array();

            // checks if there's a honeypot field AND if it is filled
            if (formData.get('myInterest') !== null && formData.get('myInterest')  != ''){
                // honeyF violation, we should abort here
                // console.log ('HoneyPot Error')
                finalMessage();
                return true;
            }
            
            // figures which fields are required
            let required = form.querySelectorAll("[required]");
            // creates an array with required fields, Each entry is an object
            for (let i = 0; i < required.length; ++i) {
                toValidate.push({
                    id: required[i].getAttribute("id"),
                    type: required[i].getAttribute("type"),
                    value: required[i].value,
                    placeholder: required[i].getAttribute("placeholder"),
                    name: required[i].name,
                });
            }

            // Figures which required fields are invalid
            for (const element of toValidate) {
                if (element.type == "text" && element.value.length == 0) {
                    // Checks none of the text fields, including email, is empty
                    error = true;
                    // adds new error message to object 
                    element.errorMessage = "Please enter " + element.placeholder;
                    errorArray.push(element);
                }  else if (element.type == "text" && element.name == 'fromEmail' && element.value.length > 0) {
                    // specific to the email field validation
                    const re =
                    /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
                    let emailValid = re.test(element.value);
                    if (!emailValid) {
                        error = true;
                        element.errorMessage = "Please review " + element.placeholder;
                        errorArray.push(element);
                    }
                }
            }
            if (error == true) {
                // displays errors
                insertErrors(errorArray);
            }
            console.log('VALIDATION FAILED?=',error);
            return error;
        };

        // Receives error array and displays error messages associated with validation of input
        insertErrors = (errorArray) => {
            for (const element of errorArray) {
                const el = document.querySelector("#" + element.id);
                const parentEl = el.parentElement;
                const errorEl = parentEl.querySelector(".outsetField-error");
                parentEl.classList.add("validationError");
                errorEl.innerHTML = element.errorMessage;
            }
        };        

        /* {# Shows failure message when the error is not due to direct user input #}
         {# for instance, the hidden honeypot field has been filled. Or there's missing key inputs #} */
        finalMessage = () => {
            hyper.failureMessage();
        };

        // Initiates the form and listeners for the required fields
        formListener();
        requiredFieldsListener();
        
  })();  
</script>
7
  • The long string of characters is normal, Craft hashes the redirect URL to prevent tampering. Since Firefox shows an empty request body, it's likely an error with either the form HTML or the JS validation, can you post the full code for those?
    – MoritzLost
    Nov 5, 2021 at 9:00
  • 1
    Thank you so much for your answer. I spent many hours yesterday trying to figure out what was wrong with the form. It's nice to hear that the long strings are expected. When you asked to see the JS validation, i decided to turn it off and test submitting the form without it. To my surprise it worked! So I'm not sure how, and when, the validation, that was working starting acting out. Before posting more I'm going to review it and try to figure it out what is going on. The validation code is quite long. If I get stuck I'll be posting it here. Thank you so much.
    – mauricio
    Nov 5, 2021 at 12:36
  • You're welcome! If you post the validation code I'll see if I spot any errors, sometimes it's easier with a fresh pair of eyes :)
    – MoritzLost
    Nov 5, 2021 at 13:36
  • @MoritzLost Thanks so much for taking the time to look at this. I have reviewed the JS routine several times and cannot find what's going on. So I pasted the script in the original post above. Thank you so much for looking at it.
    – mauricio
    Nov 5, 2021 at 14:16
  • @MoritzLost I think I found the issue. Before, I was disabling the form first and then submitting it. That was working well, but not anymore. However, if I change the order in the JS execution, submitting first, and then disabling, things work as they used to. Besides updating CraftCMS, the other only change in my setup was an update to Firefox Developer Edition. Perhaps this latest Firefox version handles things differently when a form is disabled?
    – mauricio
    Nov 5, 2021 at 14:27

1 Answer 1

2

Not sure I have the full picture yet, but as per the comments, the problem appears to be clear:

I think I found the issue. Before, I was disabling the form first and then submitting it. That was working well, but not anymore. However, if I change the order in the JS execution, submitting first, and then disabling, things work as they used to.

If you're disabling the form's input elements using the disabled attribute before sending it, an empty post body is expected. Disabled input elements aren't included in the submitted form, this is by design. Not sure why it ever worked before, but in this case, submitting the form first and then disabling the inputs will solve the issue. Or just disable the submit button to prevent duplicate submissions and leave the rest of the inputs alone.

Another possibility would be to submit the FormData object with a fetch() call – of course, this is only practical if you want to handle the response in JavaScript.


For future readers:

I don't remember seeing the long string of characters in the redirect input's value. Instead I thought it would be something like this thank-you?from{fromName} only.

This is working as intended, the long string of characters is a hashed value. Craft hashes the redirect URL to prevent tampering. See Security::hashData for details.

3
  • 1
    thank you for the clarifications. The problem was indeed the attempt to submit the form after disabling the fields. Strange that Firefox was letting that pass through. Really, really appreciate the help. Wish I could change the title of this post.
    – mauricio
    Nov 5, 2021 at 22:08
  • @mauricio Glad I could help! You can change the title of the question, there should be a small Edit link at the bottom of your question. Not sure if that requires a particular privilege though, as far as I know it shouldn't for your own question.
    – MoritzLost
    Nov 8, 2021 at 10:17
  • 1
    awesome, this was a lack of attention! I didn't realize that one can also edit the question title. Thank you so much!
    – mauricio
    Nov 9, 2021 at 14:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.