简体   繁体   English

PHPMailer上成功/失败的不同行为

[英]Different behaviors on success/fail on PHPMailer

I have a PHPMailer form and I am using .ajax using this SO answer jquery-ajax-post-example-with-php 我有一个PHPMailer表单,并且我正在使用.ajax使用此SO答案jquery-ajax-post-example-with-php

It is successfully sending or not sending, so I know the Mailer and captcha are working. 它是成功发送还是不发送,所以我知道Mailer和验证码正在工作。 Now, if I could get different behaviors for success and fail, that would be great, but I am not getting this right somehow. 现在,如果我能因成功和失败而采取不同的行为,那将是很好的,但是我并没有以某种方式正确地做到这一点。

PREFERRED BEHAVIORS 首选行为

SUCCESS --> reload screen and show bootstrap modal with success msg 成功->重新加载屏幕并显示带有成功消息的引导程序模式

FAIL --> reload screen and show bootstrap modal with fail msg 失败->重新加载屏幕并显示带有失败消息的引导模态

I have a lot of code so, Think of the modals as vanilla as possible, and the PHPMailer is, in fact, sending as well. 我有很多代码,请尽可能多地考虑模式,而PHPMailer实际上也在发送。 The problem I am having should be in the code below, but if you have to have something else, just ask. 我遇到的问题应该在下面的代码中,但是如果您还有其他问题,请询问。 Trying to keep the question as decluttered as possible 试图使问题尽可能整洁

  <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 PHPMailer表格

<?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 The code as I have it (See above), shows the fail modal and no reload regardless of whether it passes or fail. PS我所拥有的代码(请参见上文)显示了失败模式,无论是否通过,都不会重新加载。

Thanks in advance! 提前致谢!

You've chosen a relatively difficult example to learn with. 您选择了一个相对困难的示例进行学习。 Reloading after success/failure means you need to introduce sessions or cookies or URL parameters to track state between requests. 成功/失败后重新加载意味着您​​需要引入会话或cookie或URL参数以跟踪请求之间的状态。 There are several moving parts you need to get right. 您需要正确处理几个运动部件。 You must have your reasons for doing that, but if you can get away without the reload it simplifies things and would probably be a better user experience. 您必须有这样做的理由,但是如果您无需重新加载就可以脱身,这将简化事情,并且可能会带来更好的用户体验。 I've included info below for both options. 我在以下两个选项中都包含了信息。

First up, the PHP. 首先,PHP。 You have several possible exit states, depending on whether the email was successfully sent etc, but they are handled inconsistently. 您有几种可能的退出状态,具体取决于电子邮件是否已成功发送等,但它们的处理方式不一致。 In one case it does a redirect, in some cases it exits silently. 在一种情况下,它会进行重定向,在某些情况下,它会以静默方式退出。 All cases should be consistent; 所有情况应保持一致; the request to that PHP page should generate a response, no matter what happens, and ideally pass that response back to your calling Javascript so it can act on whatever happened. 无论发生什么情况,对该PHP页面的请求均应生成一个响应,并且最好将该响应传递回调用Java脚本,以便它可以对发生的任何事情进行处理。

It looks like you started setting that up, but didn't complete it - you prepare a $msg with info about what happened, but that is never displayed, and $msg is missing completely from a few exit cases (specifically the success case and what looks like a CAPTCHA failure). 它看起来像你开始设置,最多,但没有完成-你准备$msg与发生了什么信息,但永远不会显示,和$msg是从几退出案例(特别是成功的情况下,完全失踪看起来像验证码失败)。

In addition to your $msg , I'd include a status flag so the JS can easily tell whether the request was successful or not; 除了您的$msg ,我还将添加一个status标志,以便JS可以轻松判断请求是否成功; and since you're reading this response in JS, I'd make it JSON. 由于您正在用JS阅读此响应,因此我将其设为JSON。

Note that as you want to reload the page in both success/failure cases, strictly speaking a JSON response isn't really necessary, but I think it is good practice and will help with debugging while developing. 请注意,在两种成功/失败情况下都想重新加载页面时,严格来说,JSON响应并不是真正必要的,但是我认为这是一种好习惯,并且在开发时将有助于调试。

To make sure each exit condition generates a standard response, I've had to nest the actual mail sending code inside the CAPTCHA test. 为了确保每个退出条件都会生成标准响应,我不得不将实际的邮件发送代码嵌套在CAPTCHA测试中。 This IMO is a bit messy, and personally I'd restructure things, returning early to make testing the various conditions simpler and so you don't end up with 3-4 nested tests. 这个IMO有点混乱,我个人会重新组织一下事情, 尽早返回以简化各种条件的测试,因此您最终不会进行3-4个嵌套测试。 For example instead of nesting everything inside the array_key_exists('email', $_POST) test, test the inverse (if there is no email value) and exit immediately if it fails. 例如,不要将所有内容嵌套在array_key_exists('email', $_POST)测试中,而是测试反函数(如果没有电子邮件值)并在失败时立即退出。

// 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();
}

I'd also suggest perhaps rethinking the flow, eg if CAPTCHA fails, do you really want to have to reload just to show that error? 我还建议您重新考虑流程,例如,如果CAPTCHA失败,您是否真的要重新加载以显示该错误? You're sabotaging one of the niceties of using AJAX - getting responses without having to reload. 您正在破坏使用AJAX的好处之一-无需重新加载即可获取响应。 Maybe if something fails, you can just show the error msg on the form where the user clicked submit? 也许如果失败,您可以在用户单击“提交”的表单上显示错误消息。 If it succeeds, you open your modal, with a reload if really necessary. 如果成功,则打开模态,并在确实需要时重新加载。

Summary of the changes I made below: 我在下面所做的更改的摘要:

  1. Nest $mail->send() inside CAPTCHA condition; $mail->send()嵌套在验证码条件内;

  2. Create a $response with appropriate message for CAPTCHA failure; 创建一个带有适当消息的$response$response CAPTCHA失败;

  3. Create a $response for success case; 为成功案例创建一个$response

  4. Add status flag to all responses; 向所有响应添加status标志;

  5. Add the $response to the session, so that it is available after page reload; $response添加到会话中,以便在页面重新加载后可用;

  6. Finally, echo the response as JSON so the calling AJAX can tell what happened. 最后,将响应作为JSON回显,以便调用AJAX可以告知发生了什么。

Here's the updated, abbreviated (I removed large chunks of unchanged code) code: 这是更新的缩写代码(我删除了大块未更改的代码)代码:

<?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);
?>

Next, your Javascript. 接下来,您的Javascript。 Since you want the page to reload in both success and failure cases, you don't really need to test the response. 由于您希望在成功和失败情况下都重新加载页面,因此您实际上不需要测试响应。

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);
    }
    */
});

You did not mention in your description what should happen in the fail case. 您没有在描述中提及fail情况下应该发生的情况。 Note the fail callback is fired when the request fails, eg 404 or timeout or something like that, not when one of your failure cases like invalid email is triggered. 请注意,当请求失败(例如404或超时等)时,将触发fail回调,而不是在触发失败情况之一(如无效电子邮件)时触发。 I'd simply generate a msg on the front end in this case, without reloading: 在这种情况下,我只会在前端生成一个味精,而无需重新加载:

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

So, now your PHP has fired, set a session variable and returned, and your JS has reloaded the page. 因此,现在您的PHP已启动,设置了会话变量并返回了,而您的JS已重新加载了页面。 You need to test the content of your session variable, and fire some JS accordingly. 您需要测试会话变量的内容,并相应地触发一些JS。 On your front end page, inside your existing $(document).ready( ... : 在前端页面的现有$(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>

You can access the content of the success/failure message in your modals using $_SESSION['response']['msg'] . 您可以使用$_SESSION['response']['msg']在模态中访问成功/失败消息的内容。

Another suggestion - if your success and failure modals are similar, you could use just one, and update its content with Javascript. 另一个建议-如果您的成功和失败模式相似,则可以只使用其中一个,并使用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>

Side note - I am not sure if your Abort any pending request is really doing what you want. 旁注-我不确定您的Abort any pending request是否确实在做您想要的事情。 request is declared on document ready, so it is always going to exist, AFAICT? request已在准备就绪的文档中声明,因此它始终存在,AFAICT?

Your callback function for done should only reflect the success condition, while the fail should reflect the alternative. 您完成的回调函数应仅反映成功条件,而失败应反映替代条件。

// 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");
});

As provided/described in your question, reloading the page would prevent the script from getting to show either modal- if you need to reload, you should store a localStorage variable or cookie and test its value on page load to show modal as necessary. 如问题中所提供/描述的那样,重新加载页面将阻止脚本显示模式信息-如果需要重新加载,则应存储localStorage变量或cookie并在页面加载时测试其值以显示模式信息(如有必要)。

I haven't done PHP in quite a while, but I believe the code below will solve your problems. 我已经有一段时间没有写PHP了,但是我相信下面的代码可以解决您的问题。 Here is what I would change your PHP code to: 这是我将您的PHP代码更改为:

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

Now in your jQuery code, use an $.always() callback instead of success: 现在在您的jQuery代码中,使用$ .always()回调代替成功:

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

In your code, you have the following PHP: 在您的代码中,您具有以下PHP:

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

I don't think that line will ever execute. 我认为那条线永远不会执行。 You are assuming that $mail->send() returns false if the message does not send. 您假设$ mail-> send()如果消息未发送,则返回false。 I do not think this is the case. 我认为情况并非如此。 If you look at the example used on the PHP mailer home page: https://github.com/PHPMailer/PHPMailer The author uses a try / catch. 如果您查看PHP邮件程序主页上使用的示例: https : //github.com/PHPMailer/PHPMailer作者使用try / catch。 This tells me that if the message fails to send, PHPMailer throws an exception, which if uncaught will propagate a 500 error. 这告诉我,如果消息发送失败,PHPMailer会引发异常,如果未捕获该异常,则会传播500错误。 I believe that your PHPMailer setup has not thrown any exceptions (yet), which is why your Ajax success handler is always called. 我相信您的PHPMailer设置尚未引发任何异常(至今),这就是始终调用Ajax成功处理程序的原因。 However, in the following line of your javascript: if (response == true ) { you have incorrectly assumed that the first argument sent to your success handler is boolean (true or false). 但是,在javascript的以下行中: if (response == true ) {您错误地认为发送给成功处理程序的第一个参数是布尔值(true或false)。 It is not (although we can make it so). 不是(尽管我们可以做到)。 Therefore, (response == true) will never evaluate to true. 因此, (response == true)将永远不会评估为true。

What I'm doing in my code is using a try / catch like the PHPMailer example does. 我在代码中所做的就是像PHPMailer示例一样使用try / catch。 This way your backend won't throw a 500 error if PHPMailer fails. 这样,如果PHPMailer失败,您的后端将不会抛出500错误。 However, if the catch clause executes, I am setting a boolean flag to false. 但是,如果执行catch子句,则将布尔标志设置为false。 This enables us to know that there was a failure without actually having your backend crash. 这使我们能够知道发生了故障,而实际上没有发生后端崩溃。 Now we simply create a JSON object with a key called success. 现在,我们简单地创建一个具有称为成功键的JSON对象。 This will be true if the message was successfully sent, and false if it was not. 如果成功发送了消息,则为true,否则为false。 Now we can return that to the front-end. 现在我们可以将其返回到前端。

Since we are catching any possible exceptions on the backend, the frontend will always receive an HTTP 200 status code (okay). 由于我们在后端捕获了任何可能的异常,因此前端将始终收到HTTP 200状态代码(可以)。 Therefore, there is no sense in using .success() or .error() as jQuery will not know based on HTTP code whether we succeeded or failed. 因此,使用.success().error()是没有意义的,因为jQuery不会基于HTTP代码知道我们是成功还是失败。 Instead, we use an .always() callback and examine the value of the JSON object returned to determine success or error. 相反,我们使用.always()回调并检查返回的JSON对象的值以确定成功还是错误。 Now we can simply use the if statement inside of the .always() handler to execute the success code ("if") or the error code ("else"). 现在,我们可以简单地使用.always()处理程序内部的if语句来执行成功代码(“ if”)或错误代码(“ else”)。 Anything that you wish to do with respect to showing or hiding modals, etc. can be done in JavaScript on the front-end, which is where that code belongs. 您希望在显示或隐藏模式等方面做的任何事情都可以在前端的JavaScript中完成,这是代码所属的地方。

I hope this helps. 我希望这有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM