Различное поведение при успехе / неудаче на PHPMailer

У меня есть форма PHPMailer, и я использую.ajax, используя этот SO ответ /questions/17776905/primer-jquery-ajax-post-s-php

Он успешно отправляет или не отправляет, так что я знаю, что Mailer и captcha работают. Теперь, если бы я мог получить разные варианты поведения для успеха и неудачи, это было бы здорово, но я как-то не правильно понял.

ПРЕДПОЧТИТЕЛЬНОЕ ПОВЕДЕНИЕ

УСПЕХ -> перезагрузить экран и показать мод загрузки с успехом MSG

FAIL -> перезагрузить экран и показать модальность начальной загрузки с ошибкой msg

У меня много кода, так что думайте о модальностях как о ванильных, насколько это возможно, и PHPMailer, по сути, также отправляет. У меня проблема должна быть в коде ниже, но если вам нужно что-то еще, просто спросите. Попытка сохранить вопрос максимально удаленным

  <script type="text/javascript">
        $(document).ready(function() {
            var request;

            $("#contactForm").submit(function(event){
                event.preventDefault();

                // Abort any pending request
                if (request) {
                    request.abort();
                }

                // setup some local variables
                var $form = $(this);

                // Let's select and cache all the fields
                var $inputs = $form.find("input, select, button, textarea");

                // Serialize the data in the form
                var serializedData = $form.serialize();

                // Let's disable the inputs for the duration of the Ajax request.
                // Note: we disable elements AFTER the form data has been serialized.
                // Disabled form elements will not be serialized.
                $inputs.prop("disabled", true);

                request = $.ajax({
                    url: "processLogin.php",
                    type: "post",
                    data: serializedData
                });

                // Callback handler that will be called on success
                request.done(function (response, textStatus, jqXHR){
                    if (response == true ) {
                        top.location.reload();
                        // top.location.href="/";
                        // $('#successEmail').modal('show');
                    } else {
                        // top.location.reload();
                        $('#failEmail').modal('show');
                    }
                });

                // Callback handler that will be called on failure
                request.fail(function (jqXHR, textStatus, errorThrown){
                    // Log the error to the console
                    // console.error(
                    //     "The following error occurred: "+
                    //     textStatus, errorThrown
                    // );
                    // top.location.reload();
                });

                // Callback handler that will be called regardless
                // if the request failed or succeeded
                request.always(function () {
                    // Reenable the inputs
                    $inputs.prop("disabled", false);
                });

            });
        });
    </script>

PHPMailer Form

<?php
    session_start();
?>
<?php
    /**
    * This example shows how to handle a simple contact form.
    */
    $msg = '';


    use PHPMailer\PHPMailer\PHPMailer;

    require './mail/PHPMailer.php';
    require './mail/Exception.php';
    require './mail/SMTP.php';
    require './mail/PHPMailerAutoload.php';

    include_once $_SERVER['DOCUMENT_ROOT'] . '/securimage/securimage.php';

    $securimage = new Securimage();

    //Don't run this unless we're handling a form submission
    if (array_key_exists('email', $_POST)) {
        date_default_timezone_set('Etc/UTC');

        //Create a new PHPMailer instance
        $mail = new PHPMailer;

        $mail->SMTPDebug = 0;                                 
        $mail->isSMTP();    
        $mail->Host = 'smtp.live.com';                 
        $mail->SMTPAuth = true;     
        $mail->Username = 'email@outlook.com';       
        $mail->Password = 'password';                    
        $mail->SMTPSecure = 'tls';                   
        $mail->Port = 587;        

        $mail->setFrom('email@outlook.com', 'Mailer');
        $mail->addAddress('email@outlook.com', 'First Last');


        $email = isset($_POST['email']) ? $_POST['email'] : null;
        $name = isset($_POST['name']) ? $_POST['name'] : null;
        $phone = isset($_POST['phone']) ? $_POST['phone'] : null;
        $message = isset($_POST['message']) ? $_POST['message'] : null;


        if ($mail->addReplyTo($_POST['email'], $_POST['name'])) {
            $mail->Subject = 'Contact request';
            $mail->isHTML(true);
            $mail->Body = <<<EOT
    <div style="width:100%">
    <div><label style="color: #044F69; font-weight:bold">Email:</label> <span>{$_POST['email']}</span></div>
    <div><label style="color: #044F69; font-weight:bold">Name:</label> <span>{$_POST['name']}</span></div>
    <div><label style="color: #044F69; font-weight:bold">Phone:</label> <span>{$_POST['phone']}</span></div>
    <div><label style="color: #044F69; font-weight:bold">Message:</label> <span>{$_POST['message']}</span></div>
    </div>
    EOT;

            if ($securimage->check($_POST['captcha_code']) == false) {
                // echo "<meta http-equiv='refresh' content='0'>";
                exit;
            }

            //Send the message, check for errors
            if (!$mail->send()) {
                $msg = 'Sorry, something went wrong. Please try again later.';
            } else {
                header("Location: /");
                exit;
            }
        } else {
            $msg = 'Invalid email address, message ignored.';
        }
    }
    ?>

PS Код, как он у меня (см. Выше), показывает модальный сбой и без перезагрузки, независимо от того, прошел он или нет.

Заранее спасибо!

3 ответа

Решение

Вы выбрали относительно сложный пример для изучения. Перезагрузка после успеха / неудачи означает, что вам нужно ввести сеансы, куки или параметры URL для отслеживания состояния между запросами. Есть несколько движущихся частей, которые вы должны получить правильно. У вас должны быть причины для этого, но если вы можете обойтись без перезагрузки, это упростит вещи и, вероятно, будет более удобным для пользователя. Я включил информацию ниже для обоих вариантов.

Прежде всего, PHP. У вас есть несколько возможных состояний выхода, в зависимости от того, было ли успешно отправлено письмо и т. Д., Но они обрабатываются непоследовательно. В одном случае он выполняет перенаправление, в некоторых случаях он выходит молча. Все случаи должны быть последовательными; запрос на эту страницу PHP должен генерировать ответ, независимо от того, что происходит, и в идеале передавать этот ответ обратно к вызывающему Javascript, чтобы он мог действовать в любом случае.

Похоже, вы начали это настраивать, но не завершили - вы готовите $msg с информацией о том, что произошло, но это никогда не отображается, и $msg полностью отсутствует в нескольких случаях выхода (в частности, в случае успеха и в том, что похоже на сбой CAPTCHA).

В дополнение к вашему $msgЯ бы включил status пометка, чтобы JS мог легко определить, был ли запрос успешным или нет; и так как вы читаете этот ответ в JS, я бы сделал это JSON.

Обратите внимание, что, поскольку вы хотите перезагрузить страницу в обоих случаях успеха / неудачи, строго говоря, ответ JSON на самом деле не нужен, но я думаю, что это хорошая практика и она поможет с отладкой при разработке.

Чтобы убедиться, что каждое условие выхода генерирует стандартный ответ, мне пришлось вложить реальный код отправки почты в тест CAPTCHA. Это IMO немного запутанно, и лично я реструктурирую вещи, вернувшись рано, чтобы упростить тестирование различных условий, чтобы не было 3-4 вложенных тестов. Например, вместо того, чтобы вложить все внутри array_key_exists('email', $_POST) test, протестируйте обратное (если значение электронной почты отсутствует) и немедленно завершите работу в случае сбоя.

// Don't run this unless we're handling a form submission
if (!array_key_exists('email', $_POST)) {
    // Generate response and bail out!
    echo $some_response_or_whatever;
    die();
}

Я бы также предложил, возможно, переосмыслить поток, например, если CAPTCHA не работает, вы действительно хотите перезагрузить компьютер, чтобы показать эту ошибку? Вы саботируете одну из тонкостей использования AJAX - получение ответов без перезагрузки. Может быть, если что-то не получится, вы можете просто показать сообщение об ошибке в форме, где пользователь нажал "Отправить"? Если это удастся, вы открываете свой модал с перезагрузкой, если это действительно необходимо.

Резюме изменений, которые я сделал ниже:

  1. Гнездо $mail->send() внутри капча состояние;

  2. Создать $response с соответствующим сообщением для сбоя CAPTCHA;

  3. Создать $response для успеха дела;

  4. добавлять status пометить все ответы;

  5. Добавить $response в сеанс, чтобы он был доступен после перезагрузки страницы;

  6. Наконец, передайте ответ в виде JSON, чтобы вызывающий AJAX мог сказать, что произошло.

Вот обновленный, сокращенный (я удалил большие куски неизмененного кода) код:

<?php
session_start();

// ... your code

//Don't run this unless we're handling a form submission
if (array_key_exists('email', $_POST)) {

    // ... your code

    if ($mail->addReplyTo($_POST['email'], $_POST['name'])) {

        // ... your code

        if ($securimage->check($_POST['captcha_code']) == false) {
            // Generate a response in this failure case, including a message and a status flag
            $response = [
                'status'=> 1,
                'msg'   => 'CAPTCHA test failed!'
            ];

        } else {
            //Send the message, check for errors
            if (!$mail->send()) {
                // Generate a response in this failure case, including a message and a status flag
                $response = [
                    'status'=> 1,
                    'msg'   => 'Sorry, something went wrong. Please try again later.'
                ];
            } else {
                // Generate a response in the success case, including a message and a status flag
                $response = [
                    'status'=> 0,
                    'msg'   => 'Success!'
                ];
            }
        }
    } else {
        // Generate a response in this failure case, including a message and a status flag
        $response = [
            'status'=> 1,
            'msg'   => 'Invalid email address, message ignored.'
        ];
    }
}

// Add the response to the session, so that it will be available after reload
$_SESSION['response'] = $response;

// Finally display the response as JSON so calling JS can see what happened
header('Content-Type: application/json');
echo json_encode($response);
?>

Далее ваш Javascript. Поскольку вы хотите, чтобы страница перезагружалась как в случае успеха, так и в случае неудачи, вам не нужно проверять ответ.

request.done(function (response, textStatus, jqXHR){
    // You could test the response here, but since both success and failure 
    // should reload, there is no point.
    window.location.reload();

    // If you did want to test response and act on it, here's how to do that:
    /*
    if (response.status == 0) {
        // Success! Do something successful, like show success modal
        $('#successEmail').modal('show');
    } else {
        // Oh no, failure - notify the user
        $('.message').html(response.msg);
    }
    */
});

Вы не упомянули в своем описании, что должно произойти в fail дело. Обратите внимание fail обратный вызов запускается, когда запрос не выполняется, например, 404 или тайм-аут или что-то в этом роде, а не когда срабатывает один из ваших случаев сбоя, например, недействительный адрес электронной почты. В этом случае я просто сгенерирую сообщение в интерфейсе без перезагрузки:

request.fail(function (jqXHR, textStatus, errorThrown){
    // Say you have a <div class="message"></div> somewhere, maybe under your form
    $('.message').html('Error: ' + textStatus + ', ' + errorThrown)
});

Итак, теперь ваш PHP сработал, установил переменную сеанса и вернулся, а ваш JS перезагрузил страницу. Вам необходимо проверить содержимое вашей переменной сеанса и запустить несколько JS соответственно. На вашей главной странице, внутри вашего существующего $(document).ready( ...:

<script>
$(document).ready(function() {
    var request;

    // ... your code 

    <?php 
    // New PHP/JS to handle what happens when the page reloads
    if (isset($_SESSION['response'])) { 
        if ($_SESSION['response']['status'] == 0) { ?>
            // Success!
            $('#successEmail').modal('show');
        <?php } else { ?>
            $('#failEmail').modal('show');
        <?php }

        // In both cases we need to clear the response so next reload does not
        // fire a modal again
        unset($_SESSION['response']);
    } ?>
});
</script>

Вы можете получить доступ к содержанию сообщения об успехе / неудаче в ваших модальностях, используя $_SESSION['response']['msg'],

Еще одно предложение - если модальности вашего успеха и неудачи схожи, вы можете использовать только один и обновить его содержимое с помощью Javascript.

<script>
<?php if (isset($_SESSION['response'])) { ?>
    // First add the message to the modal, assuming you have a 
    // <div class="message"></div> in your modal
    $('#modal .message').html('<?php echo $_SESSION['response']['msg']; ?>');

    // Now open the one modal to rule them all
    $('#modal').modal('show');

    <?php
    unset($_SESSION['response']);
} ?>
</script>

Примечание: я не уверен, что ваш Abort any pending request действительно делает то, что вы хотите. request заявлено в документе готово, так что он всегда будет существовать, AFAICT?

Я давно не занимался PHP, но я верю, что приведенный ниже код решит ваши проблемы. Вот что я хотел бы изменить ваш код PHP:

<?php 
   $messageSent = true;
    try {
        $mail -> send();
    }
    catch (Exception $e) {
        $messageSent = false
    }
    $response = array('success' => $messageSent);
    echo json_encode($response)

Теперь в вашем коде jQuery используйте обратный вызов $.always() вместо success:

    request.always(function (data) {
        if (data.success) {
          // success handler    
        } else {
           // error handler
        } 
    });

В вашем коде у вас есть следующий PHP:

if (!$mail->send()) {}

Я не думаю, что эта линия когда-нибудь исполнится. Вы предполагаете, что $mail->send() возвращает false, если сообщение не отправлено. Я не думаю, что это так. Если вы посмотрите на пример, используемый на домашней странице PHP mailer: https://github.com/PHPMailer/PHPMailer Автор использует try / catch. Это говорит мне о том, что если сообщение не удается отправить, PHPMailer выдает исключение, которое, если uncaught будет распространять ошибку 500. Я считаю, что ваша установка PHPMailer не выдает никаких исключений (пока), поэтому ваш обработчик успеха Ajax всегда вызывается. Тем не менее, в следующей строке вашего JavaScript: if (response == true ) { вы неправильно предположили, что первый аргумент, отправленный вашему обработчику успеха, является логическим (истина или ложь). Это не так (хотя мы можем сделать это так). Следовательно, (response == true) никогда не оценю как истину.

То, что я делаю в своем коде, использует try / catch, как в примере с PHPMailer. Таким образом, ваш сервер не выдаст ошибку 500, если PHPMailer завершится неудачно. Однако, если выполняется условие catch, я устанавливаю логический флаг в false. Это позволяет нам знать, что произошел сбой, без фактического сбоя вашего бэкенда. Теперь мы просто создаем объект JSON с ключом успеха. Это будет истина, если сообщение было успешно отправлено, и ложь, если это не так. Теперь мы можем вернуть это в интерфейс.

Поскольку мы отлавливаем любые возможные исключения на бэкенде, веб-интерфейс всегда будет получать код состояния HTTP 200 (хорошо). Поэтому нет смысла использовать .success() или же .error() поскольку jQuery не будет знать, основываясь на HTTP-коде, удалось нам или нет. Вместо этого мы используем .always() обратный вызов и проверка значения возвращаемого объекта JSON для определения успеха или ошибки. Теперь мы можем просто использовать оператор if внутри .always() обработчик для выполнения кода успеха ("if") или кода ошибки ("else"). Все, что вы хотите сделать, чтобы показать или скрыть модалы и т. Д., Может быть выполнено в JavaScript на внешнем интерфейсе, к которому относится этот код.

Надеюсь, это поможет.

Ваша функция обратного вызова для done должна отражать только условие успеха, в то время как ошибка должна отражать альтернативу.

// Callback handler that will be called on success
request.done(function(response, textStatus, jqXHR) {
  $('#successEmail').modal('show');
});

// Callback handler that will be called on failure
request.fail(function(jqXHR, textStatus, errorThrown) {
  // Log the error to the console
  // console.error(
  //     "The following error occurred: "+
  //     textStatus, errorThrown
  // );
  $("#failEmail").modal("show");
});

Как указано / описано в вашем вопросе, перезагрузка страницы не позволит скрипту отображать модальное значение. Если вам необходимо перезагрузить, вы должны сохранить переменную localStorage или cookie-файл и проверить его значение при загрузке страницы, чтобы показать модальное при необходимости.

Другие вопросы по тегам