PHPMailer, jQuery + AJAX — обработка формы

REDGROUP

Новичок
Всем привет! Делаю обработку формы с помощью AJAX, при отправке формы я получаю следующую ошибку из JS дебага: {"readyState":4,"responseText":"","status":200,"statusText":"parsererror"}
Спустя часы сёрфинга гугла и stackoverflow, я так и не нашёл решения своей проблемы, но пришёл к выводу, что ошибка на стороне PHP обработчика. Буду благодарен за любую помощь связанную с данной темой, потому что уже всю голову сломал и не очень понимаю в чём проблема ;(

К этому выводу я пришёл потому что если в методе success в AJAX-запросе указывать data, и если смотреть вкладку Network там всё нормально вроде как..

PHP-код:
Код:
<?php

header('Content-type: application/json');

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require_once('PHPMailer/Exception.php');
require_once('PHPMailer/PHPMailer.php');
require_once('PHPMailer/SMTP.php');


if($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['name']) && isset($_POST['message'])) {

    function secure_input($data)
    {
        $data = trim($data);
        $data = filter_var($data, FILTER_SANITIZE_STRING);
        $data = str_replace(array("\r", "\n"), array(" ", " "), $data);
        $data = stripslashes($data);
        $data = htmlspecialchars($data);
        return $data;
    }

    $name = secure_input($_POST["name"]);
    $message = secure_input($_POST["message"]);

    if(!empty($_FILES['attachment']))
    {
        $file_name = $_FILES['attachment']['name'];
        $file_tmp_name = $_FILES['attachment']['tmp_name'];
        $file_size = $_FILES['attachment']['size'];
        if($file_size < '2048000')
        {
            $file_path = move_uploaded_file($file_tmp_name, $file_name);
        }else{
            print json_encode(['error'=>1,'msg'=>"Oops! File upload size exceeded...", $file_size]);
            exit;
        }
    }

    $mail = new PHPMailer(true);
    $mail->CharSet = 'UTF-8';
    $mail->IsSMTP();
    $mail->SMTPDebug = 4;
    $mail->SMTPAuth = TRUE;
    $mail->SMTPSecure = "ssl";
    $mail->Port     = 465;
    $mail->Username = "smtp.user";
    $mail->Password = "smt.pass";
    $mail->Host     = "smtp.domain.com";
    $mail->Mailer   = "smtp";
    $mail->SetFrom("[email protected]");
    $mail->AddReplyTo($_POST["email"]);
    $mail->AddAddress("[email protected]");
    $mail->IsHTML(true);
    $mail->Subject = ("MyForm Subject");
    $mail->Body = "
    <h1>Hello dear admin!</h1>
    <p>I am your personal mail delivery robot.</p>
    <b>I have something new for you submitted from the form myForm</b>
    Message: <p>$message</p>
    ";
    $mail->WordWrap   = 80;
    $mail->MsgHTML($_POST["message"]);

    if (is_array($_FILES['attachment'])) {
        $mail->AddAttachment($file_tmp_name, $file_name);
    }
        if (!$mail->send()) {
            $message = 'Error';
        }else{
            $message = 'Email sent successfully';
        }
    }
    $response = ['message' => $message];

    echo json_encode($response);
jQuery-код:

JavaScript:
$(document).ready(function() {
            $("#submitMyForm").click(function(e) {
                e.preventDefault();
                jQuery.validator.addMethod("validDate", function(value, element) {
                    return this.optional(element) || moment(value, "MM/DD/YY").isValid();
                }, "Please enter a valid date in the format MM/DD/YY");
                jQuery.validator.addMethod("phoneTest", function(value, element) {
                    if (/\(?([0-9]{3})\)?([ .-]?)([0-9]{3})\2([0-9]{4})/.test(value)) {
                        return true;
                    } else {
                        return false;
                    };
                }, "Invalid phone number");
                $("#myForm").validate({
                    rules: {
                        scheduleAudience: {
                            required: true,
                        },
                        date: {
                            required: true,
                            validDate: true
                        },
                        time: {
                            required: true
                        },
                        fName: {
                            required: true,
                            lettersonly: true,
                            minlength: 2,
                            maxlength: 30
                        },
                        lName: {
                            required: true,
                            lettersonly: true,
                            minlength: 2,
                            maxlength: 30
                        },
                        email: {
                            required: true,
                            email: true
                        },
                        phone: {
                            required: true,
                            phoneTest: true
                        },
                        preferences: {
                            required: true
                        },
                        message: {
                            required: true,
                            minlength: 20,
                            maxlength: 500
                        },
                        messages: {
                            fName: {
                                minlength: "Please, provide information with minimum - 2 Characters",
                                maxlength: "Please, provide information with maximum - 30 Characters"
                            },
                            lName: {
                                minlength: "Please, provide information with minimum - 2 Characters",
                                maxlength: "Please, provide information with maximum - 30 Characters"
                            },
                            message: {
                                minlength: "Please, provide information with minimum - 20 Characters",
                                maxlength: "Please, provide information with maximum - 500 Characters"
                            }
                        },
                    }
                });

                if ((!$('#myForm').valid())) {
                    return false;
                }
                if (($('#myForm').valid())) {
                    var formData = $("#myForm").serialize();
                    var URL = $("#myForm").attr("action");
                    $.ajax({
                        url: URL,
                        type: "POST",
                        data: formData,
                        cache: false,
                        processData: false,
                        contentType: false,
                        success: function(data) {
                            alert(JSON.stringify(response));
                            if (response.status == 1) {
                                swal({
                                    title: "Good job!",
                                    text: "We will get in touch with you soon",
                                    icon: "success",
                                });
                            }
                        },
                        error: function(response) {
                            alert(JSON.stringify(response));
                        }
                    });
                }
            })
            $("#attachment").change(function() {
                var file = this.files[0];
                var fileType = file.type;
                var match = ['image/jpeg', 'image/jpg', 'image/png'];
                if (!((fileType == match[0]) || (fileType == match[1]) || (fileType == match[2]))) {
                    swal({
                        title: "Invalid file format",
                        text: "Sorry, only JPEG, JPG and PNG files are allowed to upload!",
                        icon: "error",
                    });
                    $("#attachment").val('');
                    return false;
                }
            });
        });
HTML-форма (инпуты name совпадают 100%, во вкладке Network информация в Payload/FormData есть).

HTML:
<form class="contact100-form validate-form" id="myForm" name="myForm" action="URL_TO_PHPSCRIPT" method="post">
SMTP-данные 100% корректные, если я использую например форму отсюда https://phppot.com/jquery/jquery-contact-form-with-attachment-using-php/ письма отправляются, а в моём коде видимо какая-то проблема.
Валидация на стороне клиента работает без проблем.
Благодарю всех за внимание и буду очень благодарен за любую помощь и подсказки.
P.S. , я сократил количество переменных в PHP коде, чтобы было удобнее читать код, с переменными и их name точно проблем нет, перепроверял и не раз.

Что я делаю не так?
 

WMix

герр M:)ller
Партнер клуба
дело в том, что в приведенном тобой php-коде нет такого места, где возвращается
Код:
{"readyState":4,"responseText":"","status":200,"statusText":"parsererror"}
по этой причине, ответить что либо конкретное не возможно.
 

REDGROUP

Новичок
дело в том, что в приведенном тобой php-коде нет такого места, где возвращается
Код:
{"readyState":4,"responseText":"","status":200,"statusText":"parsererror"}
по этой причине, ответить что либо конкретное не возможно.
Проблема решилась через

PHP:
$unserialized = array();
parse_str($_POST['formData'], $unserialized);
echo json_encode(array('success' => 1));

$message = secure_input($unserialized["message"]);
Письма отсылаются, только у меня ещё вопрос.
htmlspecialchars и прочее работают с сериализацией?
И как заставить работать $_FILES с сериализацией?
 

WMix

герр M:)ller
Партнер клуба
прочее это что? что значит "работают с сериализацией"?
serialize($_FILES) будет работать, смысла нет, того "временного файла" к следующему запросу не будет
 

REDGROUP

Новичок
прочее это что? что значит "работают с сериализацией"?
Вот у меня функция secure_input
PHP:
 function secure_input($data)
    {
        $data = trim($data);
        $data = filter_var($data, FILTER_SANITIZE_STRING);
        $data = str_replace(array("\r", "\n"), array(" ", " "), $data);
        $data = stripslashes($data);
        $data = htmlspecialchars($data);
        return $data;
    }
Нормально ли она отработает в таком виде?
PHP:
$message = secure_input($unserialized["message"]);
По поводу $_FILES, не очень понимаю как таким образом его слать на почту тоже, как будто массив $_FILES пустой, из-за того что AJAX запрос состоит теперь из serialize(); видимо... Почитал на stackoverflow, но не очень понял.
 

WMix

герр M:)ller
Партнер клуба
Нормально ли она отработает в таком виде?
нормально только для тех переменных, которые будут представленны в html но не в json

По поводу $_FILES, не очень понимаю как таким образом его слать на почту
$file_path = move_uploaded_file($file_tmp_name, $file_name);
вероятно использавать правильный путь к файлу

$mail->AddAttachment($file_tmp_name, $file_name);
 

REDGROUP

Новичок
нормально только для тех переменных, которые будут представленны в html но не в json




вероятно использавать правильный путь к файлу
То есть получается в текущем коде моя функция работать не будет и XSS пройдёт, правильно?
Можете пожалуйста подсказать что тогда необходимо использовать при таком json?
Спасибо, попробую $_FILES путь указать.
 

Squats

Новичок
Письма отсылаются, только у меня ещё вопрос.
htmlspecialchars и прочее работают с сериализацией?
htmlspecialchars и прочее работают с сериализацией - им по барабану с чем работать - это лишь фильтры для строковых значений, главное, чтобы кодировка нигде не конфликтовала, а то может дырка появиться.
Есть крутая функция, filter_input или filter_input_array, рекомендую, избавляет от многих недоразумений, подобно secure_input =)), если правильно применять фильтрацию данных.
И как заставить работать $_FILES с сериализацией?
А зачем?
Кстати, лучше наверно посмотрите-ка в сторону FormData, чтобы себя не мучить.
JavaScript:
let form = new FormData();
for (const [key, value] of Object.entries(object)) {
   form.append(key, value);
}
А потом форму хоть с файлами, хоть с чем отправляйте.
По поводу вашего валидатора:
Если на серверной стороне, не будет валидатора, в точности как на клиентской, можно забыть о клиенте и просто кидать что угодно.
Не забывайте об этом, пишите клиент всегда после, как уже готова серверная часть, со всеми условиями.
А также, не забывайте делать единую точку входа и защищать файл через константу, по крайне мере, потому, чтобы нельзя было через адрес обращаться к файлу и слать ересь или же убить.
 
Последнее редактирование:

REDGROUP

Новичок
htmlspecialchars и прочее работают с сериализацией - им по барабану с чем работать - это лишь фильтры для строковых значений, главное, чтобы кодировка нигде не конфликтовала, а то может дырка появиться.
Есть крутая функция, filter_input или filter_input_array, рекомендую, избавляет от многих недоразумений, подобно secure_input =)), если правильно применять фильтрацию данных.

А зачем?
Кстати, лучше наверно посмотрите-ка в сторону FormData, чтобы себя не мучить.
JavaScript:
let form = new FormData();
for (const [key, value] of Object.entries(object)) {
   form.append(key, value);
}
А потом форму хоть с файлами, хоть с чем отправляйте.
По поводу вашего валидатора:
Если на серверной стороне, не будет валидатора, в точности как на клиентской, можно забыть о клиенте и просто кидать что угодно.
Не забывайте об этом, пишите клиент всегда после, как уже готова серверная часть, со всеми условиями.
А также, не забывайте делать единую точку входа и защищать файл через константу, по крайне мере, потому, чтобы нельзя было через адрес обращаться к файлу и слать ересь или же убить.
Да, я добавлю ещё условия различные. Filter_var для почты. mb_strlen для длины email и различные регулярки я добавлю на стороне бэка само собой.
Через formData пробовал, но не выходило.
 
Сверху