<repo_name>niilante/Ajax-Contact-Form<file_sep>/public/js/contact-form.js
(function (root, factory) {
    "use strict";
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.ContactForm = factory();
    }
}(this, function () {
    "use strict";

    var ContactForm = function (form, options) {
        if (!this || !(this instanceof ContactForm)) {
            return new ContactForm(form, options);
        }

        if (!form || !options) {
            return;
        }

        this.form     = form instanceof Node ? form : document.querySelector(form);
        this.endpoint = options.endpoint;

        this.send();
    };

    ContactForm.prototype = {
        hasClass: function (el, name) {
            return new RegExp('(\\s|^)' + name + '(\\s|$)').test(el.className);
        },
        addClass: function (el, name) {
            if (!this.hasClass(el, name)) {
                el.className += (el.className ? ' ' : '') + name;
            }
        },
        removeClass: function (el, name) {
            if (this.hasClass(el, name)) {
                el.className = el.className.replace(new RegExp('(\\s|^)' + name + '(\\s|$)'), ' ').replace(/^\s+|\s+$/g, '');
            }
        },
        each: function (collection, iterator) {
            var i, len;

            for (i = 0, len = collection.length; i < len; i += 1) {
                iterator(collection[i], i, collection);
            }
        },
        template: function (string, data) {
            var piece;

            for (piece in data) {
                if (Object.prototype.hasOwnProperty.call(data, piece)) {
                    string = string.replace(new RegExp('{' + piece + '}', 'g'), data[piece]);
                }
            }

            return string;
        },
        empty: function (el) {
            while (el.firstChild) {
                el.removeChild(el.firstChild);
            }
        },
        removeElementsByClass: function (className) {
            var elements = document.getElementsByClassName(className);

            while(elements.length > 0){
                elements[0].parentNode.removeChild(elements[0]);
            }
        },
        post: function (path, data, success, fail) {
            var xhttp = new XMLHttpRequest();

            xhttp.open('POST', path, true);
            xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
            xhttp.onreadystatechange = function () {
                if (this.readyState === 4) {
                    if (this.status === 200) {
                        var response = '';
                        try {
                            response = JSON.parse(this.responseText);
                        } catch (err) {
                            response = this.responseText;
                        }
                        success.call(this, response);
                    } else {
                        fail.call(this, this.responseText);
                    }
                }
            };
            xhttp.send(data);
            xhttp = null;
        },
        param: function (data) {
            var params = typeof data === 'string' ? data : Object.keys(data).map(
                function (k) { return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]); }
            ).join('&');

            return params;
        },
        send: function () {
            this.form.addEventListener('submit', function (e) {
                e.preventDefault();

                var elements = document.querySelectorAll('.form-control'),
                    formData;

                this.each(elements, function (el, i) {
                    if (this.hasClass(el.parentNode, 'has-error')) {
                        this.removeClass(el.parentNode, 'has-error');
                        this.removeElementsByClass('help-block');
                    }
                }.bind(this));

                formData = {
                    'name'    : document.querySelector('input[name="form-name"]').value,
                    'email'   : document.querySelector('input[name="form-email"]').value,
                    'subject' : document.querySelector('input[name="form-subject"]').value,
                    'message' : document.querySelector('textarea[name="form-message"]').value
                };

                this.post(this.endpoint, this.param(formData), this.feedback.bind(this), this.fail.bind(this));
            }.bind(this), false);
        },
        feedback: function (data) {
            if (!data.success) {
                if (data.errors.name) {
                    var name = document.querySelector('input[name="form-name"]').parentNode,
                        error;

                    this.addClass(name, 'has-error');
                    error = this.template(
                        '<span class="help-block">{report}</span>', {
                        report: data.errors.name
                    });

                    name.insertAdjacentHTML('beforeend', error);
                }

                if (data.errors.email) {
                    var email = document.querySelector('input[name="form-email"]').parentNode,
                        error;

                    this.addClass(email, 'has-error');
                    error = this.template(
                        '<span class="help-block">{report}</span>', {
                        report: data.errors.email
                    });

                    email.insertAdjacentHTML('beforeend', error);
                }

                if (data.errors.subject) {
                    var subject = document.querySelector('input[name="form-subject"]').parentNode,
                        error;

                    this.addClass(subject, 'has-error');
                    error = this.template(
                        '<span class="help-block">{report}</span>', {
                        report: data.errors.subject
                    });

                    subject.insertAdjacentHTML('beforeend', error);
                }

                if (data.errors.message) {
                    var message = document.querySelector('textarea[name="form-message"]').parentNode,
                        error;

                    this.addClass(message, 'has-error');
                    error = this.template(
                        '<span class="help-block">{report}</span>', {
                        report: data.errors.message
                    });

                    message.insertAdjacentHTML('beforeend', error);  
                }
            } else {
                var success = this.template(
                    '<div class="alert alert-success">{report}</div>', {
                    report: data.message
                });

                this.empty(this.form);
                this.form.insertAdjacentHTML('beforeend', success);
            }
        },
        fail: function (data) {
            console.log(data);
        }
    };

    return ContactForm;
}));<file_sep>/index.php
<?php
require_once './vendor/autoload.php';

$helperLoader = new SplClassLoader('Helpers', './vendor');
$helperLoader->register();

use Helpers\Config;

$config = new Config;
$config->load('./config/config.php');

?><!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Ajax contact form</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="jumbotron">
        <div class="container">
            <h1>Ajax Contact Form</h1>
            <p>A simple ajax based contact form using pure JavaScript and PHP.</p>
            <p><a class="btn btn-primary btn-lg" href="https://github.com/pinceladasdaweb/Ajax-Contact-Form" role="button">Learn more &raquo;</a></p>
        </div>
    </div>

    <div class="col-md-6 col-md-offset-3">
        <form enctype="application/x-www-form-urlencoded;" id="contact-form" class="form-horizontal" role="form" method="post">
            <div class="form-group" id="name-field">
                <label for="form-name" class="col-lg-2 control-label"><?php echo $config->get('fields.name'); ?></label>
                <div class="col-lg-10">
                    <input type="text" class="form-control" id="form-name" name="form-name" placeholder="<?php echo $config->get('fields.name'); ?>" required>
                </div>
            </div>
            <div class="form-group" id="email-field">
                <label for="form-email" class="col-lg-2 control-label"><?php echo $config->get('fields.email'); ?></label>
                <div class="col-lg-10">
                    <input type="email" class="form-control" id="form-email" name="form-email" placeholder="<?php echo $config->get('fields.email'); ?>" required>
                </div>
            </div>
            <div class="form-group" id="subject-field">
                <label for="form-subject" class="col-lg-2 control-label"><?php echo $config->get('fields.subject'); ?></label>
                <div class="col-lg-10">
                    <input type="text" class="form-control" id="form-subject" name="form-subject" placeholder="<?php echo $config->get('fields.subject'); ?>" required>
                </div>
            </div>
            <div class="form-group" id="message-field">
                <label for="form-message" class="col-lg-2 control-label"><?php echo $config->get('fields.message'); ?></label>
                <div class="col-lg-10">
                    <textarea class="form-control" rows="6" id="form-message" name="form-message" placeholder="<?php echo $config->get('fields.message'); ?>" required></textarea>
                </div>
            </div>
            <div class="form-group">
                <div class="col-lg-offset-2 col-lg-10">
                    <button type="submit" class="btn btn-default"><?php echo $config->get('fields.btn-send'); ?></button>
                </div>
            </div>
        </form>
    </div>
    
    <script src="public/js/contact-form.js"></script>
    <script type="text/javascript">
        new ContactForm('#contact-form', {
            endpoint: './process.php'
        });
    </script>
</body>
</html><file_sep>/process.php
<?php
require_once './vendor/autoload.php';

$helperLoader = new SplClassLoader('Helpers', './vendor');
$mailLoader   = new SplClassLoader('SimpleMail', './vendor');

$helperLoader->register();
$mailLoader->register();

use Helpers\Config;
use SimpleMail\SimpleMail;

$config = new Config;
$config->load('./config/config.php');

$errors = array();
$data   = array();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name    = stripslashes(trim($_POST['name']));
    $email   = stripslashes(trim($_POST['email']));
    $subject = stripslashes(trim($_POST['subject']));
    $message = stripslashes(trim($_POST['message']));
    $pattern = '/[\r\n]|Content-Type:|Bcc:|Cc:/i';

    if (preg_match($pattern, $name) || preg_match($pattern, $email) || preg_match($pattern, $subject)) {
        die("Header injection detected");
    }

    if (empty($name)) {
        $errors['name'] = $config->get('messages.validation.emptyname');
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = $config->get('messages.validation.emptyemail');
    }

    if (empty($subject)) {
        $errors['subject'] = $config->get('messages.validation.emptysubject');
    }

    if (empty($message)) {
        $errors['message'] = $config->get('messages.validation.emptymessage');
    }

    if (!empty($errors)) {
        $data['success'] = false;
        $data['errors']  = $errors;
    } else {
        $mail = new SimpleMail();

        $mail->setTo($config->get('emails.to'));
        $mail->setFrom($config->get('emails.from'));
        $mail->setSender($name);
        $mail->setSubject($config->get('subject.prefix') . ' ' . $subject);

        $body = "
        <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
        <html>
            <head>
                <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />
            </head>
            <body>
                <h1>{$subject}</h1>
                <p><strong>{$config->get('fields.name')}:</strong> {$name}</p>
                <p><strong>{$config->get('fields.email')}:</strong> {$email}</p>
                <p><strong>{$config->get('fields.message')}:</strong> {$message}</p>
            </body>
        </html>";

        $mail->setHtml($body);
        $mail->send();

        $data['success'] = true;
        $data['message'] = $config->get('messages.success');
    }

    echo json_encode($data);
}