SamC Posted January 11, 2018 Share Posted January 11, 2018 Trying to make an ajax form (I think this option would work better than my current one, plus I've never used ajax before so wth), got the following: <form id="contact-form" method="post" class="ajax" action="<?= $templatesUrl . 'controller/suggest-form-controller.php'; ?>"> <div class="row"> <div class="form-group col-sm-12 col-lg-6 py-2"> <label for="name">Name (required)</label> <input class="form-control" name="name" id="name" type="text"> </div> <div class="form-group col-sm-12 col-lg-6 py-2"> <label for="email">Email (required)</label> <input class="form-control" name="email" id="email" type="text"> </div> </div> <div class="form-group py-2"> <label for="message">Message (required)</label> <textarea class="form-control" name="message" id="message" rows="8"></textarea> </div> <div> <label for="recaptcha">Recaptcha (required)</label> <!-- Google Recaptcha code START --> <?php echo $captcha->render(); ?> <!-- Google Recaptcha code END --> </div> <div class="form-group"> <button type="submit" class="btn btn-primary mt-3" name="sendMe" value="1">Suggest tutorial <i class="fa fa-angle-right" aria-hidden="true"></i></button> </div> </form> ...and the JS: // ajax form $("form.ajax").on("submit", function() { var self = $(this), url = self.attr("action"), type = self.attr("method"), data = {}; self.find("[name=name], [name=email], [name=message]").each(function(index, value) { var self = $(this), name = self.attr("name"), value = self.val(); // create object with name/value pairs data[name] = value; }); $.ajax({ url: url, type: type, data: data, success: function(res) { console.log(res); } }); return false; }); Problem is, when I click the submit button, I get a 403 on the post url (so at least the JS works so far). No idea how I'm going to integrate recaptcha and valitron into all of this but need to get passed this hurdle first. Do I have to create a matching PW path or something with a hidden page? The file is currently in: /site/templates/controller/suggest-form-controller.php Any hints would be awesome, thanks. Link to comment Share on other sites More sharing options...
psy Posted January 11, 2018 Share Posted January 11, 2018 Maybe make the form action '/' and then specify the actual URL in the js code rather than pulling it from the form? Link to comment Share on other sites More sharing options...
kongondo Posted January 11, 2018 Share Posted January 11, 2018 (edited) 2 hours ago, SamC said: <form id="contact-form" method="post" class="ajax" action="<?= $templatesUrl . 'controller/suggest-form-controller.php'; ?>"> ProcessWire will not allow you to POST to or call a script (PHP and such) directly if that script is located in one of its protected folders. Hence, the 403 (forbidden). Your options: Throw your script into a template file and post to a page using that template Throw your script into a module and post to that module's page (if backend; otherwise, will still get a 403) include/require once the script in some template file but post to a page using that template (e.g. posting to self as per @psy's suggestion) Etc... Ajax stuff: Edited January 11, 2018 by kongondo more links 1 1 Link to comment Share on other sites More sharing options...
SamC Posted January 13, 2018 Author Share Posted January 13, 2018 Thanks for the info. I used option 1 so I have the following files: suggest-form.php (the form) <?php namespace ProcessWire; // get recaptcha module $captcha = $modules->get("MarkupGoogleRecaptcha"); // save current page id in session $session->currentPageId = $page->id; ?> <div id="form-top"></div> <h2>Suggest a tutorial</h2> <div id="form-alert" role="alert"></div> <form id="contact-form" method="post" class="ajax" action="<?= $pages->get(1832)->url; ?>"> <div class="row"> <div class="form-group col-sm-12 col-lg-6 py-2" id="name"> <label for="name">Name (required)</label> <input class="form-control" name="name" type="text"> </div> <div class="form-group col-sm-12 col-lg-6 py-2" id="email"> <label for="email">Email (required)</label> <input class="form-control" name="email" type="text"> </div> </div> <div class="form-group py-2" id="message"> <label for="message">Message (required)</label> <textarea class="form-control" name="message" id="message" rows="8"></textarea> </div> <div class="form-group py-1" id="recaptcha"> <label for="recaptcha">Recaptcha (required)</label> <!-- Google Recaptcha code START --> <?php echo $captcha->render(); ?> <!-- Google Recaptcha code END --> </div> <div class="form-group"> <button type="submit" id="sendMe" class="btn btn-primary mt-3" name="sendMe" value="1">Suggest tutorial <i class="fa fa-angle-right" aria-hidden="true"></i></button> </div> </form> <?php echo $captcha->getScript(); ?> suggest.php (process and echo JSON or output plain messages to '/suggest/' for no JS fallback) <?php namespace ProcessWire; // is form submitted $isSubmitted = ($input->post->sendMe) ? true : false; if ($isSubmitted) { // default out string for no JS $out = "<h2>Please fix the following errors</h2>"; // require Valitron class require_once("./vendor/vlucas/valitron/src/Valitron/Validator.php"); // where to send email $myEmail = "EMAIL_HERE"; // sanitize input variables $name = $sanitizer->text($input->post->name); $email = $sanitizer->email($input->post->email); $message = $sanitizer->textarea($input->post->message); // create new valitron $v = new \Valitron\Validator(array( "name" => $name, "email" => $email, "message" => $message ) ); // create valitron rules $v->rule("required", ["name", "email", "message"]); $v->rule("email", "email"); // does form validate $formValidates = $v->validate(); // fields array if error, otherwise false $nameHasError = $v->errors("name"); $emailHasError = $v->errors("email"); $messageHasError = $v->errors("message"); // multidimensional array of errors $errors = $v->errors(); // loop errors and concat for // no JS output foreach ($errors as $error) { foreach ($error as $value) { $out .= "<p>" . $value . "</p>"; } } // get recaptcha module $captcha = $modules->get("MarkupGoogleRecaptcha"); // does recaptcha validate? $recaptcha = $captcha->verifyResponse(); // if not, concat for no JS output if (!$recaptcha) { $out .= "<p>Recaptcha is required</p>"; } // default form submitted value $isSent = false; // if fields validate and recaptcha complete if ($recaptcha && $formValidates) { // create email message $msg = " <html> <body> <p><b>Name:</b> {$name}</p> <p><b>Email:</b> {$email}</p> <p><b>Message:</b></p> <p>{$message}</p> </body> </html>"; $mail = wireMail(); $mail->to($myEmail) ->from($email, $name) ->subject('Email from website...') ->bodyHTML($msg); // did email send? $isSent = ($mail->send()) ? true : false; // success message for no JS if ($isSent) { $out = "<h2>Thanks for the suggestion!</h2>"; } } // form sent via ajax if ($config->ajax) { $result = [ "name" => $name, "email" => $email, "message" => $message, "nameHasError" => $nameHasError, "emailHasError" => $emailHasError, "messageHasError" => $messageHasError, "fieldsValidate" => $formValidates, "recaptchaComplete" => $recaptcha, "formSent" => $isSent ]; // output JSON for ajax echo json_encode($result); } // output for basic no JS page else { include("./includes/header.php"); echo " <div class='container py-5'> <div class='row justify-content-center '> <div class='col-lg-7'> {$out} <h4><a href='{$pages->get($session->currentPageId)->url}'>Go back to previous page</a></h4> </div> </div> </div> "; include("./includes/footer.php"); } } // if url accessed with no form submission else { $session->redirect("/"); } app.js (ajax) // ajax form $("form.ajax").on("submit", function() { var self = $(this), url = self.attr("action"), type = self.attr("method"), data = {}; self.find("[name]").each(function(index, value) { var self = $(this), name = self.attr("name"), value = self.val(); // add field values to data obj data[name] = value; }); // send data obj to /suggest/ php page for processing $.ajax({ url: url, type: type, data: data, dataType: "json", success: function(res) { var nameField = self.find("#name"), emailField = self.find("#email"), messageField = self.find("#message"), alert = $("#form-alert"); dataObj = res; function errors(obj, key, field) { // if key value is empty string or array if (obj[key].length === 0 || Array.isArray(obj[key])) { alert.removeClass("alert alert-success"); field.addClass("has-danger"); alert.addClass("alert alert-danger"); alert.text("Please complete required fields"); } else { field.removeClass("has-danger"); } } // copy function and set default value of 1st parameter // just for revision of bind function... var generateErrors = errors.bind(this, dataObj); // false = no error // array of errors = error generateErrors("nameHasError", nameField); generateErrors("emailHasError", emailField); // string = no error // empty string = error generateErrors("message", messageField); // return value of $mail->send() in suggest.php if (dataObj["formSent"]) { var form = $("form.ajax").html(); $("form.ajax").html(form); alert.removeClass("alert alert-danger"); alert.addClass("alert alert-success"); alert.text("Thanks for the suggestion!"); } } }); return false; }); Ignoring the goofy code itself (unless it presents a security risk), this all works. The question I have, are there any security issues associated with ajax itself rather than just posting straight to a php file? I mean, the values in the fields are unsanitized when they get added to the data object just after ajax on("submit"). I noticed jquery serialize() for getting data from a form but not looked into that yet, could possibly replace the manual looping input fields in app.js. Anyway, security is my biggest concern right now so any info would be awesome, thanks. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now