简体   繁体   中英

PHPMailer multiple attachments sends email but no files

I've tried seemingly every StackOverflow I could find on this particular subject(and there are a lot), but most forget the enctype in the form tag or something. I'm still getting emails sent, just without any files attached.

My $_FILES array doesn't seem to be getting filled when I submit the form. The below code sends the html email perfectly fine with the submitted fields data, but no attachment.

This is the latest error in the log when the form submits with input data, but no attachments:

[24-Jan-2020 22:06:33 UTC] PHP Warning: count(): Parameter must be an array or an object that implements Countable in *******/public_html/rtform/contact.php on line 89

I've pointed out line 89 in the code below.

Here is the beginning and end of my form, with my file input at the bottom (the rest of the form is too long to include the whole thing here:

<form id="contact-form" method="POST" action="contact.php" role="form" enctype="multipart/form-data">
    <div class="controls">

        <!-- the middle of the form here -->

        <div class="row">
            <h3 class="form_title">Upload Additional Supporting Documents</h3>
            <div class="col-lg-12">
                <label for="attachments[]">Select one or more files:
                    <input name="attachments[]" type="file" multiple="multiple">
                </label>
            </div>
        </div>

        <hr/>

        <div class="form-group">
            <div class="g-recaptcha" data-sitekey="**************************************" data-callback="verifyRecaptchaCallback" data-expired-callback="expiredRecaptchaCallback"></div>
            <input class="form-control d-none" data-recaptcha="true" required data-error="Please complete the Captcha">
            <div class="help-block with-errors"></div>
        </div>

        <p><span class="red">Fields marked with * denotes a required field.</span></p>
        <input type="submit" class="btn btn-success btn-send" value="Submit Order">

        <div class="messages"></div>

    </div><!-- end .controls -->

</form>

My form processing PHP file is long, but probably necessary to include everything, so:

<?php
// error_reporting(E_ALL); ini_set('display_errors', 1);

// Import PHPMailer classes into the global namespace
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'includes/phpmailer/src/Exception.php';
require 'includes/phpmailer/src/PHPMailer.php';

// Require ReCaptcha class
require('recaptcha-master/src/autoload.php');

// ReCaptch Secret
$recaptchaSecret = *******************************;

/*******************************************************
 * 
 *   Here I have a long array with all of the input names and variables which I use for 
 *   the logic below for my html email template(which works great). Shouldn't be necessary 
 *   for this problem, so I'm excluding it.
 *
 *******************************************************/

// Instantiation and passing `true` enables exceptions
$mail = new PHPMailer(true);

try {
    if ( !empty($_POST) ) {
        $msg = ''; // To be used for dynamic messaging

        // ReCaptcha
        // Validate the ReCaptcha, if something is wrong, we throw an Exception,
        // i.e. code stops executing and goes to catch() block
        if (!isset($_POST['g-recaptcha-response'])) {
            throw new \Exception('ReCaptcha is not set.');
        }
        $recaptcha = new \ReCaptcha\ReCaptcha($recaptchaSecret, new \ReCaptcha\RequestMethod\CurlPost());
        // we validate the ReCaptcha field together with the user's IP address
        $response = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
        if (!$response->isSuccess()) {
            throw new \Exception('ReCaptcha was not validated.');
        }

        $requestorEmail = "";
        //Make sure the address they provided is valid before trying to use it
        if (array_key_exists('requestoremail', $_POST) && PHPMailer::validateAddress($_POST['requestoremail'])) {
            $requestorEmail = $_POST['requestoremail'];
        } else {
            $msg = 'Error: invalid requestor email address provided';
            throw new \Exception('Requestor email, ' . $requestorEmail . ', is not a valid email address.');
        }

        //Recipients
        $mail->setFrom('senderemail@email.com', 'Sender');
        $mail->addAddress('recipientemail@email.com', 'Recipient');    // Add a recipient
        $mail->addAddress($requestorEmail, 'Requestor');               // Name is optional
        $mail->addReplyTo('replytoemail@email.com', 'Reply To Email');

        /*************************************
        *
        *   The attachment-related stuff:
        *
        *************************************/

        $maxSize = 2 * 1024 * 1024; // 2 MB
        $fileTypes = array('text/plain', 'application/pdf', 'application/msword', 'application/rtf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'); // allowed mime-types

        // Passing these variables to my jQuery ajax request at the bottom of this file to try to get some helpful info
        $theFiles = $_FILES;        
        $filesCount = count($_FILES['attachments']['tmp_name']);

        if (isset($_FILES)) {

            //Attach multiple files one by one

            //******************** The line below is line 89 ********************/
            for ($ct = 0; $ct < count($_FILES['attachments']['tmp_name']); $ct++) {
                $uploadfile = tempnam(sys_get_temp_dir(), hash('sha256', $_FILES['attachments']['name'][$ct]));
                $filename = $_FILES['attachments']['name'][$ct];
                $filesize = $_FILES['attachments']['size'][$ct];
                $filemime = mime_content_type($uploadfile);

                // Check $_FILES['upfile']['error'] value.
                switch ($_FILES['attachments']['error'][$ct]) {
                    case UPLOAD_ERR_OK:
                        break;
                    case UPLOAD_ERR_NO_FILE:
                        throw new RuntimeException('No file sent.');
                    case UPLOAD_ERR_INI_SIZE:
                    case UPLOAD_ERR_FORM_SIZE:
                        throw new RuntimeException('Exceeded filesize limit.');
                    default:
                        throw new RuntimeException('Unknown errors.');
                }

                try {
                    if (in_array($filemime, $fileTypes)){
                        if ($filesize <= $maxSize) {
                            if (move_uploaded_file($_FILES['attachments']['tmp_name'][$ct], $uploadfile)) {
                                $mail->addAttachment($uploadfile, $filename);
                            } else {
                                $msg .= 'Failed to move file to ' . $uploadfile;
                                throw new \Exception('Failed to move file to ' . $uploadfile);
                            }
                        }
                        else {
                            $msg = "File[s] are too large. They must each be less than or equal to 2MB.";
                            throw new \Exception('File[s] are too large. They must each be less than or equal to 2MB.');
                        }
                    }
                    else {
                        $msg = "File[s] must only be of these types: .txt, .rtf, .pdf, .doc, .docx. Please try again.";
                        throw new \Exception('File[s] must only be of these types: .txt, .rtf, .pdf, .doc, .docx. Please try again.');
                    }
                }
                catch (Exception $e) {
                    $responseArray = array('type' => 'danger', 'message' => $msg); // or $e->getMessage()
                }
            }

        }
        else {
            $msg = "No file added.";
            throw new \Exception('No file added.');
        }

        // Get template file contents
        $template = file_get_contents("email-template.html");

/*************************************************************************
 *
 *   This is constructing my email html template with input data
 *
**************************************************************************/

        // Loop $_POST content, compare to $fields array, and include/replace if not empty
        foreach($_POST as $key => $value) {

            // Create a generic row to use for each 
            $value_row = "<li>$fields[$key]: $value</li>";

            if ( !empty( $value ) ) {
                // Check each group's first member and insert/replace appropriate title string
                if( $key == "casenumber" ) {
                    $value_row = "<h2>General Information</h2>\n<ul style='list-style:none;'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "client-fullname" ) {
                    $value_row = "</ul><h2>Client/Applicant</h2>\n<ul style='list-style:none;'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "employer-fullname" ) {
                    $value_row = "</ul><h2>Employer/Insured</h2>\n<ul style='list-style:none;'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "requestor-attorney" ) {
                    $value_row = "</ul><h2>Requestor</h2>\n<ul style='list-style:none;'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "billingcarrier" ) {
                    $value_row = "</ul><h2>Billing Information</h2>\n<ul style='list-style:none;'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "caseplantiff" ) {
                    $value_row = "</ul><h2>Case Caption</h2>\n<ul style='list-style:none;'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "oclattorneyname" ) {
                    $value_row = "</ul><h2>Opposing Counsel</h2>\n<ul style='list-style:none;'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location1facility" ) {
                    $value_row = "</ul><h2>Delivery Instructions</h2><h3>Location 1</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location2facility" ) {
                    $value_row = "</ul><h3>Location 2</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }    
                elseif( $key == "location3facility" ) {
                    $value_row = "</ul><h3>Location 3</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location4facility" ) {
                    $value_row = "</ul><h3>Location 4</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location5facility" ) {
                    $value_row = "</ul><h3>Location 5</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location6facility" ) {
                    $value_row = "</ul><h3>Location 6</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location7facility" ) {
                    $value_row = "</ul><h3>Location 7</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location8facility" ) {
                    $value_row = "</ul><h3>Location 8</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location9facility" ) {
                    $value_row = "</ul><h3>Location 9</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                elseif( $key == "location10facility" ) {
                    $value_row = "</ul><h3>Location 10</h3>\n<ul style='list-style:none;' class='location-list'><li>$fields[$key]: $value</li>";
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
                else {
                    $template = str_replace('{{ '.$key.' }}', $value_row, $template);
                }
            }
            else {
                $template = str_replace('{{ '.$key.' }}', '', $template);
            }
        }

        // Content
        $mail->isHTML(true); // Set email format to HTML
        $mail->Subject = 'Order Form Submission from Real Time Records Website';
        $mail->Body    = $template;

        $mail->send();
        // Generic success message
        $msg = 'Your order has been received successfully. We will contact you shortly. Please contact us if you have any questions.';
        $responseArray = array('type' => 'success', 'message' => $msg, 'files' => json_encode($theFiles), 'number-of-files' => "$filesCount");
    } // end if( !empty( $_POST ) )
} catch (Exception $e) {
    // Generic error message
    $msg = 'There was an error while submitting the form. Please try again later';
    $responseArray = array('type' => 'danger', 'message' => $msg); // or $e->getMessage()
    // echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

// This encodes the output message(with some of those attachment details) for the jQuery ajax request to pickup
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
    $encoded = json_encode($responseArray);

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

    echo $encoded;
} else {
    echo $responseArray['message'];
}

My jQuery really just displays the passed error or success mesage, though I've passed some info from $_FILES to it to see if it's getting filled, which it is not.

When the ajax request is made, the received data I see in the response related to the attachments is:

files = '[]', 'number-of-files' = '0'

Can anyone with some PHPMailer experience see what I'm doing wrong?

EDIT x2

Here's my updated jQuery, per Synchro's answer below. It's getting the files from the form now, and appending multiple files correctly to the FormData object, but the php $_FILES object only counts/shows one file(when there are two) and doesn't attach any to the sent email:

$(function () {

    window.verifyRecaptchaCallback = function (response) {
        $('input[data-recaptcha]').val(response).trigger('change');
    }

    window.expiredRecaptchaCallback = function () {
        $('input[data-recaptcha]').val("").trigger('change');
    }

    $('#contact-form').validator();

    $('#contact-form').on('submit', function (e) {
        if (!e.isDefaultPrevented()) {
            var url = "contact.php";

            const theForm = document.getElementById('contact-form');
            var fd = new FormData(theForm);
            var files = $("#file_attachments").prop('files');

            for (var i = 0; i < files.length; i++) {
              fd.append("attachments", files[i]);
              console.log("File #"+(i+1)+" attached: "+files[i]);
            }

            $.ajax({
                type: "POST",
                url: url,
                data: fd,
                processData: false,
                contentType: false,
                success: function(data) {
                    var messageAlert = 'alert-' + data.type;
                    var messageText = data.message;
                    var messageFiles = data.files;
                    var messageFilesCount = data.numberOfFiles

                    var alertBox = '<div class="alert ' + messageAlert + ' alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>' + messageText + '</div><div>Number of Files: ' + messageFilesCount + ' - ' + messageFiles + '</div>';
                    if (messageAlert && messageText) {
                        $('#contact-form').find('.messages').html(alertBox);
                        $('#contact-form')[0].reset();
                        grecaptcha.reset();
                    }
                },
                error: function(data) {
                    //this is going to happen when you send something different from a 200 OK HTTP
                    alert('Ooops, something happened: ' + data.message);
                }
            });
            return false;
        }
    })
});

It was worth asking for your JS...

You're doing this:

data: $(this).serialize(),

That will not include file attachments. You need to use the JS FormData class and turn off some jQuery features.

First of all, give the file input an id so you can target it more easily (your label tag should also target this id, not the name attribute):

<input name="attachments[]" id="attachments" type="file" multiple="multiple">

Then alter your ajax code to get the form data into a FormData object, and then add the file elements to it:

var fd = new FormData('contact-form');
var files = $("#attachments").get(0).files;

for (var i = 0; i < files.length; i++) {
  fd.append("attachments", files[i]);
}
$.ajax({
  type: "POST",
  url: url,
  data: fd,
  processData: false,
  contentType: false,
  success: function (data) {
    var messageAlert = 'alert-' + data.type;
    var messageText = data.message;

    var alertBox = '<div class="alert ' + messageAlert + ' alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>' + messageText + '</div>';
    if (messageAlert && messageText) {
      $('#contact-form').find('.messages').html(alertBox);
      $('#contact-form')[0].reset();
      grecaptcha.reset();
    }
  },
  error: function (data) {
    //this is going to happen when you send something different from a 200 OK HTTP
    alert('Ooops, something happened: ' + data.message);
  }
});

Note: this approach will not work in Internet Explorer prior to version 10.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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