Debugoutput = ($mail_settings['debug_output'] == 'log') ? 'error_log' : 'html'; $mail->SMTPDebug = SMTP::DEBUG_CONNECTION; // show connection status, client -> server and server -> client messages } $eol = "\n"; // EOL sequence to use in mail headers. Need "\n" for mail backend if (empty($addresses['from'])) { if (isset($mail_settings['from'])) { $addresses['from'] = $mail_settings['from']; } else { trigger_error('$mail_settings["from"] has not been set in the config file.', E_USER_NOTICE); } } $mail->CharSet = Language::MAIL_CHARSET; // PHPMailer defaults to 'iso-8859-1' $mail->AllowEmpty = true; // remove this for production if (!empty($addresses['reply_to'])) { $reply_to_addresses = parse_addresses($addresses['reply_to']); foreach ($reply_to_addresses as $reply_to_address) { $mail->addReplyTo($reply_to_address['address'], $reply_to_address['name']); } } if (isset($addresses['from'])) { $from_addresses = parse_addresses($addresses['from']); $mail->setFrom($from_addresses[0]['address'], $from_addresses[0]['name']); } $to_addresses = parse_addresses($addresses['to']); foreach ($to_addresses as $to_address) { $mail->addAddress($to_address['address'], $to_address['name']); } if (isset($addresses['cc'])) { $cc_addresses = parse_addresses($addresses['cc']); foreach ($cc_addresses as $cc_address) { // for cases where the mail server refuses to send emails with cc set, put the cc addresses on the to line if ($mail_settings['treat_cc_as_to']) { $mail->addAddress($cc_address['address'], $cc_address['name']); } // otherwise treat them normally, adding them to the cc line else { $mail->addCC($cc_address['address'], $cc_address['name']); } } } if (isset($addresses['bcc'])) { $bcc_addresses = parse_addresses($addresses['bcc']); foreach ($bcc_addresses as $bcc_address) { $mail->addBCC($bcc_address['address'], $bcc_address['name']); } } $mail->Subject = $subject; // Build the email. We're going to use the "alternative" subtype which means // that we order the sub parts according to how faithful they are to the original, // putting the least faithful first, ie the ordinary plain text version. The // email client then uses the most faithful version that it can handle. (See // https://www.rfc-editor.org/rfc/rfc2046#section-5.1.4) // // If we are also adding the iCalendar information then we enclose this alternative // mime subtype in an outer mime type which is mixed. This is necessary so that // the widest variety of calendar applications can access the calendar information. // So depending on whether we are sending iCalendar information we will have a Mime // structure that looks like this: // // With iCalendar info Without iCalendar info // ------------------- ---------------------- // // multipart/mixed mutlipart/alternative // multipart/alternative text/plain // text/plain text/html // text/html // text/calendar // application/ics // First of all build the inner mime type, ie the multipart/alternative type. // If we're not sending iCalendar information this will become the outer, // otherwise we'll then enclose it in an outer mime type. $mime_params = array(); $mime_params['eol'] = $eol; $mime_params['content_type'] = "multipart/alternative"; $mime_inner = new Mail_mimePart('', $mime_params); // Add the text part $mime_params['content_type'] = "text/plain"; $mime_params['cid'] = generate_global_uid('text'); $mime_params['encoding'] = "8bit"; $mime_params['charset'] = $charset; $mime_inner->addSubPart($text_body, $mime_params); unset($mime_params['cid']); // Add the HTML mail if (isset($html_body)) { $mime_params['content_type'] = "text/html"; $mime_params['cid'] = generate_global_uid('html'); $mime_inner->addSubPart($html_body, $mime_params); unset($mime_params['cid']); } if (empty($attachment)) { // If we're not sending iCalendar information we've now got everything, // so we'll make the "inner" section the complete mime. $mime = $mime_inner; } else { // Otherwise we need to carry on and add the text version of the iCalendar $mime_params['content_type'] = "text/calendar; method=" . $attachment['method']; // The encoding needs to be base64, because Postfix will otherwise not preserve // CRLF sequences and these are mandatory terminators in the iCalendar file $mime_params['encoding'] = "base64"; $mime_inner->addSubPart($attachment['content'], $mime_params); // and then enclose the inner section in a multipart/mixed outer section. // First create the outer section $mime_params = array(); $mime_params['eol'] = $eol; $mime_params['content_type'] = "multipart/mixed"; $mime = new Mail_mimePart('', $mime_params); // Now add the inner section as the first sub part $mime_inner = $mime_inner->encode(); $mime_params = array(); $mime_params['eol'] = $eol; $mime_params['encoding'] = "8bit"; $mime_params['content_type'] = $mime_inner['headers']['Content-Type']; $mime->addSubPart($mime_inner['body'], $mime_params); // And add the attachment as the second sub part $mime_params['content_type'] = "application/ics"; $mime_params['encoding'] = "base64"; $mime_params['disposition'] = "attachment"; $mime_params['dfilename'] = $attachment['name']; $mime->addSubPart($attachment['content'], $mime_params); } // Encode the result $mime = $mime->encode(); // Construct the MIMEHeader and then call preSend() which sets up the // headers. However it also sets up the body, which we don't want, // so we have to set the MIMEBody after calling preSend(). // // This is not ideal since $MIMEHeader and $MIMEBody are protected // properties of the PHPMailer class. In the future we may have to extend // the class to do what we want, unless the features we need are added. $mime_header = ''; foreach ($mime['headers'] as $name => $value) { if ($name == 'Content-Type') { $mail->ContentType = $value; } $mime_header .= $mail->headerLine($name, $value); } $mail->set('MIMEHeader', $mime_header); if ($mail->preSend()) { $mail->set('MIMEBody', $mime['body']); mail_debug("Using backend '" . $mail_settings['admin_backend'] . "'"); mail_debug("From: " . ($addresses['from'] ?? '')); mail_debug("To: " . ($addresses['to'] ?? '')); mail_debug("Cc: " . ($addresses['cc'] ?? '')); mail_debug("Bcc: " . ($addresses['bcc'] ?? '')); // Throttle the rate of mail sending if required if (!empty($mail_settings['rate_limit'])) { $microtime_now = round(microtime(true), 6); if (isset($last_mail_sent)) { $diff = round($microtime_now - $last_mail_sent, 6); mail_debug("Last mail sent $diff seconds ago to $last_n_addresses addresses"); $min_gap = round($last_n_addresses/$mail_settings['rate_limit'], 6); if ($min_gap > $diff) { $sleep_seconds = round($min_gap - $diff, 6); mail_debug("Too soon to send the next mail; sleeping for $sleep_seconds seconds"); usleep(intval($sleep_seconds * 1000000)); } } $last_n_addresses = self::getNRecipients($addresses); $last_mail_sent = $microtime_now; } // PHPMailer uses escapeshellarg() and escapeshellcmd(). In many installations these will // have been disabled. If they have been disabled PHP will generate a warning and the functions // will return NULL. PHPMailer uses the functions to test if the sender address is shell safe // and can be used with the -f option. If the escapeshell*() functions are disabled, mail // will still be sent, but -f will not be used. [Note that if a function is disabled you cannot // redeclare it, so writing emulations of escapeshellarg() and escapeshellcmd() is not an option.] // As mail still gets through, the warning message will cause error logs to fill up rapidly, so we // suppress the standard errors in the cases when they will be generated and issue our own NOTICE error. $disabled_functions = ini_get('disable_functions'); if (!empty($disabled_functions) && (mb_strpos($disabled_functions, 'escapeshell') !== FALSE) && in_array($mail_settings['admin_backend'], array('mail', 'sendmail'))) { $message = "Your PHP system has one or both of the escapeshellarg() and escapeshellcmd() functions " . "disabled and you are using the '" . $mail_settings['admin_backend'] . "' backend. " . "PHPMailer will therefore not have used the -f option when sending mail."; mail_debug($message); trigger_error($message, E_USER_NOTICE); $result = @$mail->postSend(); } else { $result = $mail->postSend(); } if ($result) { mail_debug('Email sent successfully'); return true; } } error_log('Error sending email: ' . $mail->ErrorInfo); mail_debug('Failed to send email: ' . $mail->ErrorInfo); return false; } }