Uniform - multiple form and ajax

Hello everybody,

I’m writing this topic to ask for some help. I’m implementing two ajax forms on a website using Uniform (v2.3). I’ can’t find a solution to my problem: one of the two form doesn’t work!
I have a contact page, that contain a basic contact form (that works) and a “site-wide” overlay that contain another form (the accused one, that doesn’t work).
After some research I’ve decided to structure my code in this way.

  1. I’ve created a page to handle the ajax submissions, named “send-form” (folder, textfile in the folder, blueprint file), and I’ve disabled the cache for this page (by the way, the problem remains if i disable the cache for the entire site).

  2. I’ve created a template for this page, templates/send-form.php, with this code:

    <?php header::contentType('application/json'); $errors = ''; foreach (array_keys(get()) as $field) { $errors .= $form->hasError($field) ? "\"$field\"," : ''; }

    $errors = substr($errors, 0, -1);
    ?>
    {
    “success”: <?php e($form->successful(), 'true', 'false')?>,
    “message”: “<?php echo trim($form->message()) ?>”,
    “errors”: [<?php echo $errors ?>]
    }

  3. I’ve created a controller for this page, controllers/send-form.php, with this code:

    <?php

    if (empty(get(’_id’)) ) {
    echo(‘no id provided’);
    die();
    }

    return function($site, $pages, $page) {

     $form = uniform(
         get('_id'),
         array(
             'required' => array(
                 '_from'     => 'email',
             ),
             'actions' => array(
                 array(
                     '_action' => 'email',
                     'service' => 'sendgrid',
                     'to'      => 'admin@mydomain.com',
                     'sender'  => 'info@mydomain.com',
                     'subject' => 'My Subject',
                 )
             )
         )
     );
    
     return compact('form');
    

    };

  4. I’ve created the ‘sendgrid’ email service, with this code in plugins/email-services-email-services.php:

    <?php

    require ‘PHPMailer/PHPMailerAutoload.php’;

    email::$services[‘sendgrid’] = function($email) {

     $mail = new PHPMailer;
    
     $mail->isSMTP();                    // Set mailer to use SMTP
     $mail->Host = 'smtp.sendgrid.net';  // Specify main and backup SMTP servers
     $mail->SMTPAuth = true;             // Enable SMTP authentication
     $mail->Username = 'xxxxxx';      // SMTP username
     $mail->Password = 'xxxxxx';    // SMTP password
     $mail->SMTPSecure = 'tls';          // Enable TLS encryption, `ssl` also accepted
     $mail->Port = 587;                  // TCP port to connect to
         	
     $mail->CharSet = 'UTF-8';
    
     $mail->setFrom($email->from);
     $mail->addReplyTo($email->replyTo);
     
     $mail->addAddress($email->to);
    
     $mail->Subject = $email->subject;
     $mail->Body    = $email->body;
    
     if(!$mail->send()) {
         
         return array(
         'success' => false,
         'message' => l::get('uniform-email-error').' '.$email->error()
         );
    
     } else {
         
         return array(
         'success' => true,
         'message' => l::get('uniform-email-success')
         );
         
     }
    

    };

    ?>

  5. I’ve created my forms, outputting the token and passing the correct id in the ajax post with this code at the end of my forms:

    //(…) form 1 fields
    Please leave this field blank


    //(…) form 2 fields
    Please leave this field blank


Now, the first form, in the contact page, works well, I can submit the fields and receive a success response.
I can’t make the second form work.
I tried putting the form alone in a page and…it works! It is in an hidden overlay, hidden by css from the beginning, but I don’t think that this is a problem… I tried to make a specific page to handle the submission for each form but the problem remain.
Moreover, I only receive the “success: false” status from post request, without any message.

If you can find the error, please, let me know. I would be a great help! I’m stuck. :slight_smile:

I guess you have already seen this post: Uniform - multiple forms with ajax

I have invited @mzur to the thread.

Yes, I’ve taken the controller code from this post.
I’m on NGINX and the error log doesn’t show problems.
The _id value is passed correctly in my post request (with the token, the honeypot and the others fields).
I don’t know where to turn :slight_smile:

That’s quite hard to debug like this. Do you mind sending me the the erroneous part of your project in a PM so I can debug it locally?

Also check out the AJAX example of Uniform v3. Although you are using v2.3 you can adopt the concept of a route as target for the form requests. This is probably easier to implement and maintain than a dedicated page with controller and template.

Hello @mzur,
and thank you for your willingness.
I’m trying to implement Uniform v3 locally, because the “route way” seemed a lot more clean ;).
I need only to send simple emails, and the beta seemed sufficiently advanced. Am I right?

Do you mean sending you the site folder code, without images and the pages?
To remove my doubts: is it ok to generate a token based on an ID and then, in the handler (page or route) create a new Uniform instance passing the correct id? I’ve not so clear the CSRF question :slight_smile: .

I’ll update the topic after my experiments :slight_smile: .

Yes. It’s still in beta because I wanted to wait a while for some people to test the version and give feedback. So far I didn’t get any feedback so I assume that either nobody uses v3 or everything works fine :smile: There are some issues and pull requests I want closed before the stable release, too.

You can send everything you are comfortable with. But try Uniform v3 first, it will probably work better.

Yes that should be fine but with Uniform v3 there is only a single CSRF token per request/page so you won’t need multiple tokens. Just make sure you generate the token only once for multiple forms:

<?php $token = csrf() ?>

<form>
   <?php echo csrf_field($token) ?>
</form>

<form>
   <?php echo csrf_field($token) ?>
</form>

You can also implement one dedicated route for each form so you don’t need that _id stuff any more.

Ok, but I’m on a ajax website and I have the problem that I’m not sure that my token are generated on the same page.

To explain: I have the site structure (header, navigation and overlay that contain one form) loaded the first time, than, with the exception of the first loaded page, I load the content of the pages by ajax (they are generated on the server, I simply load them and animate the substitution of the content of the page).
The contact page, that is loaded in ajax, contains the other form.

In this scenario, the two form must have the same csrf_token value? Can I set it on the contact page copying it from the overlay form?

Sorry if it is not completely clear.

You should be fine if you generate the token on initial page load and manage it with JavaScript from then on. All views that are loaded via AJAX use the initial token and don’t generate a new one. Only when the page is reloaded, the token is refreshed, too. You can’t use the csrf_field PHP helper in this case, however.

Hello @mzur and hello everyone,

I tried to implement my two AJAX forms with Uniform v3, adding two custom routes to handle them… Without success :sweat:.

I tried to output the csrf token before everything, right after the opening of the body tag with this function:

<?php echo csrf_field() ?>

Then, I copy the code in my forms via Javascript, using JQuery, in this way:

$('input[name="csrf_token"]').first().clone(true).insertBefore('#formId .submit-btn')

The routes work, but only sometimes! The majority of the times I receive a 500 error with the ‘The CSRF token was invalid’ message.
I disabled the cache of Kirby setting c::set('cache',false); in my config.php file without success.

Do you see something wrong in the way I try to utilize the token?

(Or, maybe, I should copy the token from the page loaded via AJAX each time?)

Thank you for any advice!

It’s probably best if you send me your project files for local debugging after all. The token is regenerated (and the old one invalidated) for a session each time csrf() or csrf_field() is called. We have to check if this is the case for your views loaded via AJAX.

This kind of reminds me of something I ran into with Uniform (v2):

I had a case where a form was on a one-page application and sometimes the submission just didn’t work - seemingly at random. Turns out the issue was that the favicon.ico was missing. When loading the page “cold”, the browser would fetch the home page, uniform would generate a form token etc., but then, the browser tried to fetch the favicon and the (custom) 404 handling would redirect this to the home page. The home page was therefore loaded again as the favicon which generated a new token and therefore, the form that the user could actually see was no longer valid.

Maybe you have something similar going on with your form. Check the requests of the form page with your browser’s development tools to see if there might be a request going off that could lead to the form token being regenerated.

Hello everyone!

With your help I resolved my issue. I switched to Uniform v3, but it is a general issue.
The problem was, as suggested by @mzur, that I was sending more then one request, and the token generated with the first request expired.
Moreover, as suggested by @lord-executor, I had a small bug with the implementation of the lazy loading of the images that sometimes generated 404 error. Dued to this error I had this “random” 500 error, as the error page requested by the browser was creating another token.

To sum up, I resolved in this way:

  1. I’ve created two custom route, following the example on the Uniform v3 docs.

  2. I modified my two form removing the call to the csrf() helper function of Uniform.

  3. I added this code to my footer, that checks if the request comes from the script that manage my ajax navigation or is for an error page. On the contrary, that is to say on the first page load, I output the CSRF token in a global Javascript variable.

    <?php $headers = getallheaders(); ?> <?php if ( !(a::get($headers, 'X-Barba') == 'yes' || $page->isErrorPage()) ) : ?> <?php endif; ?>
  4. Before the AJAX post request to the forms routes, I append the token to the request in this way:
    var submitData = $('#form').serialize() + '&csrf_token=' + CSRF_TOKEN;

As a side note, if you are on NGINX you should “polyfill” the getallheaders() function as suggested here.

Thank you all for your support and your advices!