From 94843398427b7db16b15e1f6f67ab6bac8e70f9d Mon Sep 17 00:00:00 2001 From: Pepijn Over Date: Tue, 11 Feb 2014 18:16:00 +0100 Subject: [PATCH] issue #14: adding SMTP support and upgrading phpmailer --- CHANGELOG | 2 + src/includes/functions.inc.php | 40 + src/lang/br.lang.php | 6 + src/lang/de.lang.php | 6 + src/lang/en.lang.php | 6 + src/lang/fr.lang.php | 6 + src/lang/kr.lang.php | 6 + src/lang/nl.lang.php | 6 + src/psm/Module/Config.class.php | 89 +- src/psm/Util/Mailer.class.php | 57 + src/psm/Util/Updater/Status.class.php | 5 +- src/templates/config.tpl.html | 25 + vendor/PHPMailer/class.phpmailer.php | 2949 +++++++++++++++++++++++++ vendor/PHPMailer/class.smtp.php | 1092 +++++++++ vendor/phpmailer.class.php | 1615 -------------- 15 files changed, 4262 insertions(+), 1648 deletions(-) create mode 100755 src/psm/Util/Mailer.class.php create mode 100755 vendor/PHPMailer/class.phpmailer.php create mode 100644 vendor/PHPMailer/class.smtp.php delete mode 100644 vendor/phpmailer.class.php diff --git a/CHANGELOG b/CHANGELOG index b28c15ee..2462bd4a 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,8 @@ # ######################### - Switched from mysql_* to PDO. +- Added SMTP support. +- Updated PHPMailer package to v5.2.6. ######################### # diff --git a/src/includes/functions.inc.php b/src/includes/functions.inc.php index 46bf5195..4743e0fa 100755 --- a/src/includes/functions.inc.php +++ b/src/includes/functions.inc.php @@ -267,6 +267,46 @@ function psm_check_updates() { return false; } +/** + * Prepare a new Mailer util. + * + * If the from name and email are left blank they will be prefilled from the config. + * @param string $from_name + * @param string $from_email + * @return \psm\Util\Mailer + */ +function psm_build_mail($from_name = null, $from_email = null) { + $phpmailer = new \psm\Util\Mailer(); + $phpmailer->Encoding = "base64"; + $phpmailer->SMTPDebug = false; + + if(psm_get_conf('email_smtp') == '1') { + $phpmailer->IsSMTP(); + $phpmailer->Host = psm_get_conf('email_smtp_host'); + $phpmailer->Port = psm_get_conf('email_smtp_port'); + + $smtp_user = psm_get_conf('email_smtp_username'); + $smtp_pass = psm_get_conf('email_smtp_password'); + + if($smtp_user != '' && $smtp_pass != '') { + $phpmailer->SMTPAuth = true; + $phpmailer->Username = $smtp_user; + $phpmailer->Password = $smtp_pass; + } + } else { + $phpmailer->IsMail(); + } + if($from_name == null) { + $from_name = psm_get_conf('email_from_name'); + } + if($from_email == null) { + $from_email = psm_get_conf('email_from_email'); + } + $phpmailer->SetFrom($from_email, $from_name); + + return $phpmailer; +} + ############################################### # # Debug functions diff --git a/src/lang/br.lang.php b/src/lang/br.lang.php index 56774e3d..bd09e7e9 100755 --- a/src/lang/br.lang.php +++ b/src/lang/br.lang.php @@ -95,6 +95,12 @@ $sm_lang = array( 'email_status' => 'Habilitar envio de email?', 'email_from_email' => 'Endereço do envio de email', 'email_from_name' => 'Nome do envio de email', + 'email_smtp' => 'Enable SMTP', + 'email_smtp_host' => 'SMTP host', + 'email_smtp_port' => 'SMTP port', + 'email_smtp_username' => 'SMTP username', + 'email_smtp_password' => 'SMTP password', + 'email_smtp_noauth' => 'Leave blank for no authentication', 'sms_status' => 'Habilitar o envio de mensagem de texto?', 'sms_gateway' => 'Gateway para o uso de envio de mensagens', 'sms_gateway_mosms' => 'Mosms', diff --git a/src/lang/de.lang.php b/src/lang/de.lang.php index ff5633ce..1c210746 100755 --- a/src/lang/de.lang.php +++ b/src/lang/de.lang.php @@ -95,6 +95,12 @@ $sm_lang = array( 'email_status' => 'Email senden erlauben?', 'email_from_email' => 'Email from address', 'email_from_name' => 'Email from name', + 'email_smtp' => 'Enable SMTP', + 'email_smtp_host' => 'SMTP host', + 'email_smtp_port' => 'SMTP port', + 'email_smtp_username' => 'SMTP username', + 'email_smtp_password' => 'SMTP password', + 'email_smtp_noauth' => 'Leave blank for no authentication', 'sms_status' => 'SMS Nachricht senden erlauben?', 'sms_gateway' => 'SMS Gateway', 'sms_gateway_mosms' => 'Mosms', diff --git a/src/lang/en.lang.php b/src/lang/en.lang.php index bc8b0fba..da57f02e 100755 --- a/src/lang/en.lang.php +++ b/src/lang/en.lang.php @@ -95,6 +95,12 @@ $sm_lang = array( 'email_status' => 'Allow sending email?', 'email_from_email' => 'Email from address', 'email_from_name' => 'Email from name', + 'email_smtp' => 'Enable SMTP', + 'email_smtp_host' => 'SMTP host', + 'email_smtp_port' => 'SMTP port', + 'email_smtp_username' => 'SMTP username', + 'email_smtp_password' => 'SMTP password', + 'email_smtp_noauth' => 'Leave blank for no authentication', 'sms_status' => 'Allow sending text messages?', 'sms_gateway' => 'Gateway to use for sending messages', 'sms_gateway_mosms' => 'Mosms', diff --git a/src/lang/fr.lang.php b/src/lang/fr.lang.php index 7c582b32..72be4ecc 100755 --- a/src/lang/fr.lang.php +++ b/src/lang/fr.lang.php @@ -95,6 +95,12 @@ $sm_lang = array( 'email_status' => 'Autoriser l envoi de mail?', 'email_from_email' => 'Expéditeur', 'email_from_name' => 'Nom de l expéditeur', + 'email_smtp' => 'Enable SMTP', + 'email_smtp_host' => 'SMTP host', + 'email_smtp_port' => 'SMTP port', + 'email_smtp_username' => 'SMTP username', + 'email_smtp_password' => 'SMTP password', + 'email_smtp_noauth' => 'Leave blank for no authentication', 'sms_status' => 'Autoriser l envoi de SMS?', 'sms_gateway' => 'Passerelle à utiliser pour l envoi de SMS', 'sms_gateway_mosms' => 'Mosms', diff --git a/src/lang/kr.lang.php b/src/lang/kr.lang.php index 630f0f46..bd0b1ad6 100755 --- a/src/lang/kr.lang.php +++ b/src/lang/kr.lang.php @@ -96,6 +96,12 @@ $sm_lang = array( 'email_status' => '메일전송 허용', 'email_from_email' => 'Email 주소', 'email_from_name' => 'Email 사용자', + 'email_smtp' => 'Enable SMTP', + 'email_smtp_host' => 'SMTP host', + 'email_smtp_port' => 'SMTP port', + 'email_smtp_username' => 'SMTP username', + 'email_smtp_password' => 'SMTP password', + 'email_smtp_noauth' => 'Leave blank for no authentication', 'sms_status' => 'SMS전송 허용', 'sms_gateway' => '메세지 전송을 위한 게이트웨이 허용', 'sms_gateway_mosms' => 'Mosms', diff --git a/src/lang/nl.lang.php b/src/lang/nl.lang.php index 21a2aeeb..c64c1004 100755 --- a/src/lang/nl.lang.php +++ b/src/lang/nl.lang.php @@ -95,6 +95,12 @@ $sm_lang = array( 'email_status' => 'Sta email berichten toe?', 'email_from_email' => 'Email van adres', 'email_from_name' => 'Email van naam', + 'email_smtp' => 'SMTP gebruiken', + 'email_smtp_host' => 'SMTP host', + 'email_smtp_port' => 'SMTP poort', + 'email_smtp_username' => 'SMTP gebruikersnaam', + 'email_smtp_password' => 'SMTP wachtwoord', + 'email_smtp_noauth' => 'Laat leeg voor geen authenticatie', 'sms_status' => 'Sta SMS berichten toe?', 'sms_gateway' => 'Gateway voor het sturen van SMS', 'sms_gateway_mosms' => 'Mosms', diff --git a/src/psm/Module/Config.class.php b/src/psm/Module/Config.class.php index 61a81e86..327315f6 100755 --- a/src/psm/Module/Config.class.php +++ b/src/psm/Module/Config.class.php @@ -31,6 +31,36 @@ use psm\Service\Template; class Config extends AbstractModule { + /** + * Checkboxes + * @var array $checkboxes + */ + protected $checkboxes = array( + 'email_status', + 'email_smtp', + 'sms_status', + 'log_status', + 'log_email', + 'log_sms', + 'show_update', + ); + + /** + * Fields for saving + * @var array $fields + */ + protected $fields = array( + 'email_from_name', + 'email_from_email', + 'email_smtp_host', + 'email_smtp_port', + 'email_smtp_username', + 'email_smtp_password', + 'sms_gateway_username', + 'sms_gateway_password', + 'sms_from', + ); + function __construct(Database $db, Template $tpl) { parent::__construct($db, $tpl); @@ -74,25 +104,23 @@ class Config extends AbstractModule { } $this->tpl->addTemplateDataRepeat($this->getTemplateId(), 'languages', $languages); - $this->tpl->addTemplateData( - $this->getTemplateId(), - array( - 'email_status_checked' => ($config['email_status'] == '1') ? 'checked="checked"' : '', - 'email_from_name' => $config['email_from_name'], - 'email_from_email' => $config['email_from_email'], - 'sms_status_checked' => ($config['sms_status'] == '1') ? 'checked="checked"' : '', - 'sms_selected_' . $config['sms_gateway'] => 'selected="selected"', - 'sms_gateway_username' => $config['sms_gateway_username'], - 'sms_gateway_password' => $config['sms_gateway_password'], - 'sms_from' => $config['sms_from'], - 'alert_type_selected_' . $config['alert_type'] => 'selected="selected"', - 'log_status_checked' => ($config['log_status'] == '1') ? 'checked="checked"' : '', - 'log_email_checked' => ($config['log_email'] == '1') ? 'checked="checked"' : '', - 'log_sms_checked' => ($config['log_sms'] == '1') ? 'checked="checked"' : '', - 'show_update_checked' => ($config['show_update'] == '1') ? 'checked="checked"' : '', - 'auto_refresh_servers' => (isset($config['auto_refresh_servers'])) ? $config['auto_refresh_servers'] : '0', - ) + $tpl_data = array( + 'sms_selected_' . $config['sms_gateway'] => 'selected="selected"', + 'alert_type_selected_' . $config['alert_type'] => 'selected="selected"', + 'auto_refresh_servers' => (isset($config['auto_refresh_servers'])) ? $config['auto_refresh_servers'] : '0', ); + + foreach($this->checkboxes as $input_key) { + $tpl_data[$input_key . '_checked'] = + (isset($config[$input_key]) && (int) $config[$input_key] == 1) + ? 'checked="checked"' + : ''; + } + foreach($this->fields as $input_key) { + $tpl_data[$input_key] = (isset($config[$input_key])) ? $config[$input_key] : ''; + } + + $this->tpl->addTemplateData($this->getTemplateId(), $tpl_data); } /** @@ -104,21 +132,18 @@ class Config extends AbstractModule { // save new config $clean = array( 'language' => $_POST['language'], - 'show_update' => (isset($_POST['show_update'])) ? '1' : '0', - 'email_status' => (isset($_POST['email_status'])) ? '1' : '0', - 'email_from_name' => $_POST['email_from_name'], - 'email_from_email' => $_POST['email_from_email'], - 'sms_status' => (isset($_POST['sms_status'])) ? '1' : '0', 'sms_gateway' => $_POST['sms_gateway'], - 'sms_gateway_username' => $_POST['sms_gateway_username'], - 'sms_gateway_password' => $_POST['sms_gateway_password'], - 'sms_from' => $_POST['sms_from'], 'alert_type' => $_POST['alert_type'], - 'log_status' => (isset($_POST['log_status'])) ? '1' : '0', - 'log_email' => (isset($_POST['log_email'])) ? '1' : '0', - 'log_sms' => (isset($_POST['log_sms'])) ? '1' : '0', 'auto_refresh_servers' => (isset($_POST['auto_refresh_servers'])) ? intval($_POST['auto_refresh_servers']) : '0', ); + foreach($this->checkboxes as $input_key) { + $clean[$input_key] = (isset($_POST[$input_key])) ? '1': '0'; + } + foreach($this->fields as $input_key) { + if(isset($_POST[$input_key])) { + $clean[$input_key] = $_POST[$input_key]; + } + } // save all values to the database foreach($clean as $key => $value) { @@ -167,6 +192,12 @@ class Config extends AbstractModule { 'label_email_status' => psm_get_lang('config', 'email_status'), 'label_email_from_email' => psm_get_lang('config', 'email_from_email'), 'label_email_from_name' => psm_get_lang('config', 'email_from_name'), + 'label_email_smtp' => psm_get_lang('config', 'email_smtp'), + 'label_email_smtp_host' => psm_get_lang('config', 'email_smtp_host'), + 'label_email_smtp_port' => psm_get_lang('config', 'email_smtp_port'), + 'label_email_smtp_username' => psm_get_lang('config', 'email_smtp_username'), + 'label_email_smtp_password' => psm_get_lang('config', 'email_smtp_password'), + 'label_email_smtp_noauth' => psm_get_lang('config', 'email_smtp_noauth'), 'label_sms_status' => psm_get_lang('config', 'sms_status'), 'label_sms_gateway' => psm_get_lang('config', 'sms_gateway'), 'label_sms_gateway_mosms' => psm_get_lang('config', 'sms_gateway_mosms'), diff --git a/src/psm/Util/Mailer.class.php b/src/psm/Util/Mailer.class.php new file mode 100755 index 00000000..cd4931f9 --- /dev/null +++ b/src/psm/Util/Mailer.class.php @@ -0,0 +1,57 @@ +. + * + * @package phpservermon + * @author Pepijn Over + * @copyright Copyright (c) 2008-2014 Pepijn Over + * @license http://www.gnu.org/licenses/gpl.txt GNU GPL v3 + * @version Release: @package_version@ + * @link http://www.phpservermonitor.org/ + * @since phpservermon 2.2.0 + **/ + +namespace psm\Util; + +/** + * PHPMailer is not using namespaces so unable to load files in autoloader. + */ +require_once(PSM_PATH_VENDOR . '/PHPMailer/class.phpmailer.php'); +require_once(PSM_PATH_VENDOR . '/PHPMailer/class.smtp.php'); + +/** + * PSM Mailer utility + * + * The PHPMailer is an open source lib that can be found in vendor/PHPMailer. + * + * @see \PHPMailer + */ +class Mailer extends \PHPMailer { + + /** + * Open new PHPMailer + * + * @param boolean $exceptions + */ + function __construct($exceptions = false) { + parent::__construct($exceptions); + + } +} + +?> \ No newline at end of file diff --git a/src/psm/Util/Updater/Status.class.php b/src/psm/Util/Updater/Status.class.php index 07b6c731..f248b826 100755 --- a/src/psm/Util/Updater/Status.class.php +++ b/src/psm/Util/Updater/Status.class.php @@ -260,10 +260,7 @@ class Status { } // build mail object with some default values - $mail = new \phpmailer(); - - $mail->From = psm_get_conf('email_from_email'); - $mail->FromName = psm_get_conf('email_from_name'); + $mail = psm_build_mail(); $mail->Subject = psm_parse_msg($this->status_new, 'email_subject', $this->server); $mail->Priority = 1; diff --git a/src/templates/config.tpl.html b/src/templates/config.tpl.html index f91fe49c..7b574598 100755 --- a/src/templates/config.tpl.html +++ b/src/templates/config.tpl.html @@ -75,6 +75,31 @@ +
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
diff --git a/vendor/PHPMailer/class.phpmailer.php b/vendor/PHPMailer/class.phpmailer.php new file mode 100755 index 00000000..bfb8ecad --- /dev/null +++ b/vendor/PHPMailer/class.phpmailer.php @@ -0,0 +1,2949 @@ +UseSendmailOptions) ) { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($subject)), $body, $header); + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($subject)), $body, $header, $params); + } + return $rt; + } + + /** + * Outputs debugging info via user-defined method + * @param string $str + */ + protected function edebug($str) { + switch ($this->Debugoutput) { + case 'error_log': + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking display that's HTML-safe + echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, $this->CharSet)."
\n"; + break; + case 'echo': + default: + //Just echoes exactly what was received + echo $str; + } + } + + /** + * Constructor + * @param boolean $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = false) { + $this->exceptions = ($exceptions == true); + } + + /** + * Destructor + */ + public function __destruct() { + if ($this->Mailer == 'smtp') { //Close any open SMTP connection nicely + $this->SmtpClose(); + } + } + + /** + * Sets message type to HTML. + * @param bool $ishtml + * @return void + */ + public function IsHTML($ishtml = true) { + if ($ishtml) { + $this->ContentType = 'text/html'; + } else { + $this->ContentType = 'text/plain'; + } + } + + /** + * Sets Mailer to send message using SMTP. + * @return void + */ + public function IsSMTP() { + $this->Mailer = 'smtp'; + } + + /** + * Sets Mailer to send message using PHP mail() function. + * @return void + */ + public function IsMail() { + $this->Mailer = 'mail'; + } + + /** + * Sets Mailer to send message using the $Sendmail program. + * @return void + */ + public function IsSendmail() { + if (!stristr(ini_get('sendmail_path'), 'sendmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + /** + * Sets Mailer to send message using the qmail MTA. + * @return void + */ + public function IsQmail() { + if (stristr(ini_get('sendmail_path'), 'qmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + ///////////////////////////////////////////////// + // METHODS, RECIPIENTS + ///////////////////////////////////////////////// + + /** + * Adds a "To" address. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddAddress($address, $name = '') { + return $this->AddAnAddress('to', $address, $name); + } + + /** + * Adds a "Cc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddCC($address, $name = '') { + return $this->AddAnAddress('cc', $address, $name); + } + + /** + * Adds a "Bcc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddBCC($address, $name = '') { + return $this->AddAnAddress('bcc', $address, $name); + } + + /** + * Adds a "Reply-to" address. + * @param string $address + * @param string $name + * @return boolean + */ + public function AddReplyTo($address, $name = '') { + return $this->AddAnAddress('Reply-To', $address, $name); + } + + /** + * Adds an address to one of the recipient arrays + * Addresses that have been added already return false, but do not throw exceptions + * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' + * @param string $address The email address to send to + * @param string $name + * @throws phpmailerException + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function AddAnAddress($kind, $address, $name = '') { + if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) { + $this->SetError($this->Lang('Invalid recipient array').': '.$kind); + if ($this->exceptions) { + throw new phpmailerException('Invalid recipient array: ' . $kind); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('Invalid recipient array').': '.$kind); + } + return false; + } + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!$this->ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('invalid_address').': '.$address); + } + return false; + } + if ($kind != 'Reply-To') { + if (!isset($this->all_recipients[strtolower($address)])) { + array_push($this->$kind, array($address, $name)); + $this->all_recipients[strtolower($address)] = true; + return true; + } + } else { + if (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = array($address, $name); + return true; + } + } + return false; +} + + /** + * Set the From and FromName properties + * @param string $address + * @param string $name + * @param boolean $auto Whether to also set the Sender address, defaults to true + * @throws phpmailerException + * @return boolean + */ + public function SetFrom($address, $name = '', $auto = true) { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!$this->ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('invalid_address').': '.$address); + } + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto) { + if (empty($this->Sender)) { + $this->Sender = $address; + } + } + return true; + } + + /** + * Check that a string looks roughly like an email address should + * Static so it can be used without instantiation, public so people can overload + * Conforms to RFC5322: Uses *correct* regex on which FILTER_VALIDATE_EMAIL is + * based; So why not use FILTER_VALIDATE_EMAIL? Because it was broken to + * not allow a@b type valid addresses :( + * @link http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright regex Copyright Michael Rushton 2009-10 | http://squiloople.com/ | Feel free to use and redistribute this code. But please keep this copyright notice. + * @param string $address The email address to check + * @return boolean + * @static + * @access public + */ + public static function ValidateAddress($address) { + if (defined('PCRE_VERSION')) { //Check this instead of extension_loaded so it works when that function is disabled + if (version_compare(PCRE_VERSION, '8.0') >= 0) { + return (boolean)preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $address); + } else { + //Fall back to an older regex that doesn't need a recent PCRE + return (boolean)preg_match('/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', $address); + } + } else { + //No PCRE! Do something _very_ approximate! + //Check the address is 3 chars or longer and contains an @ that's not the first or last char + return (strlen($address) >= 3 and strpos($address, '@') >= 1 and strpos($address, '@') != strlen($address) - 1); + } + } + + ///////////////////////////////////////////////// + // METHODS, MAIL SENDING + ///////////////////////////////////////////////// + + /** + * Creates message and assigns Mailer. If the message is + * not sent successfully then it returns false. Use the ErrorInfo + * variable to view description of the error. + * @throws phpmailerException + * @return bool + */ + public function Send() { + try { + if(!$this->PreSend()) return false; + return $this->PostSend(); + } catch (phpmailerException $e) { + $this->mailHeader = ''; + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + /** + * Prep mail by constructing all message entities + * @throws phpmailerException + * @return bool + */ + public function PreSend() { + try { + $this->mailHeader = ""; + if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { + throw new phpmailerException($this->Lang('provide_address'), self::STOP_CRITICAL); + } + + // Set whether the message is multipart/alternative + if(!empty($this->AltBody)) { + $this->ContentType = 'multipart/alternative'; + } + + $this->error_count = 0; // reset errors + $this->SetMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty and empty($this->Body)) { + throw new phpmailerException($this->Lang('empty_message'), self::STOP_CRITICAL); + } + + $this->MIMEHeader = $this->CreateHeader(); + $this->MIMEBody = $this->CreateBody(); + + // To capture the complete message when using mail(), create + // an extra header list which CreateHeader() doesn't fold in + if ($this->Mailer == 'mail') { + if (count($this->to) > 0) { + $this->mailHeader .= $this->AddrAppend("To", $this->to); + } else { + $this->mailHeader .= $this->HeaderLine("To", "undisclosed-recipients:;"); + } + $this->mailHeader .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader(trim($this->Subject)))); + } + + // digitally sign with DKIM if enabled + if (!empty($this->DKIM_domain) && !empty($this->DKIM_private) && !empty($this->DKIM_selector) && !empty($this->DKIM_domain) && file_exists($this->DKIM_private)) { + $header_dkim = $this->DKIM_Add($this->MIMEHeader . $this->mailHeader, $this->EncodeHeader($this->SecureHeader($this->Subject)), $this->MIMEBody); + $this->MIMEHeader = str_replace("\r\n", "\n", $header_dkim) . $this->MIMEHeader; + } + + return true; + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + /** + * Actual Email transport function + * Send the email via the selected mechanism + * @throws phpmailerException + * @return bool + */ + public function PostSend() { + try { + // Choose the mailer and send through it + switch($this->Mailer) { + case 'sendmail': + return $this->SendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->SmtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->MailSend($this->MIMEHeader, $this->MIMEBody); + default: + return $this->MailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + if ($this->SMTPDebug) { + $this->edebug($e->getMessage()."\n"); + } + } + return false; + } + + /** + * Sends mail using the $Sendmail program. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @access protected + * @return bool + */ + protected function SendmailSend($header, $body) { + if ($this->Sender != '') { + $sendmail = sprintf("%s -oi -f%s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); + } else { + $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail)); + } + if ($this->SingleTo === true) { + foreach ($this->SingleToArray as $val) { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, "To: " . $val . "\n"); + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + return true; + } + + /** + * Sends mail using the PHP mail() function. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @access protected + * @return bool + */ + protected function MailSend($header, $body) { + $toArr = array(); + foreach($this->to as $t) { + $toArr[] = $this->AddrFormat($t); + } + $to = implode(', ', $toArr); + + if (empty($this->Sender)) { + $params = " "; + } else { + $params = sprintf("-f%s", $this->Sender); + } + if ($this->Sender != '' and !ini_get('safe_mode')) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $rt = false; + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $val) { + $rt = $this->mail_passthru($val, $this->Subject, $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + $rt = $this->mail_passthru($to, $this->Subject, $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if(!$rt) { + throw new phpmailerException($this->Lang('instantiate'), self::STOP_CRITICAL); + } + return true; + } + + /** + * Sends mail via SMTP using PhpSMTP + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @uses SMTP + * @access protected + * @return bool + */ + protected function SmtpSend($header, $body) { + require_once $this->PluginDir . 'class.smtp.php'; + $bad_rcpt = array(); + + if(!$this->SmtpConnect()) { + throw new phpmailerException($this->Lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; + if(!$this->smtp->Mail($smtp_from)) { + $this->SetError($this->Lang('from_failed') . $smtp_from . ' : ' .implode(',', $this->smtp->getError())); + throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); + } + + // Attempt to send attach all recipients + foreach($this->to as $to) { + if (!$this->smtp->Recipient($to[0])) { + $bad_rcpt[] = $to[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } + } + foreach($this->cc as $cc) { + if (!$this->smtp->Recipient($cc[0])) { + $bad_rcpt[] = $cc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } + } + foreach($this->bcc as $bcc) { + if (!$this->smtp->Recipient($bcc[0])) { + $bad_rcpt[] = $bcc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } + } + + + if (count($bad_rcpt) > 0 ) { //Create error message for any bad addresses + $badaddresses = implode(', ', $bad_rcpt); + throw new phpmailerException($this->Lang('recipients_failed') . $badaddresses); + } + if(!$this->smtp->Data($header . $body)) { + throw new phpmailerException($this->Lang('data_not_accepted'), self::STOP_CRITICAL); + } + if($this->SMTPKeepAlive == true) { + $this->smtp->Reset(); + } else { + $this->smtp->Quit(); + $this->smtp->Close(); + } + return true; + } + + /** + * Initiates a connection to an SMTP server. + * Returns false if the operation failed. + * @param array $options An array of options compatible with stream_context_create() + * @uses SMTP + * @access public + * @throws phpmailerException + * @return bool + */ + public function SmtpConnect($options = array()) { + if(is_null($this->smtp)) { + $this->smtp = new SMTP; + } + + //Already connected? + if ($this->smtp->Connected()) { + return true; + } + + $this->smtp->Timeout = $this->Timeout; + $this->smtp->do_debug = $this->SMTPDebug; + $this->smtp->Debugoutput = $this->Debugoutput; + $this->smtp->do_verp = $this->do_verp; + $index = 0; + $tls = ($this->SMTPSecure == 'tls'); + $ssl = ($this->SMTPSecure == 'ssl'); + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = array(); + $host = $hostentry; + $port = $this->Port; + if (preg_match('/^(.+):([0-9]+)$/', $hostentry, $hostinfo)) { //If $hostentry contains 'address:port', override default + $host = $hostinfo[1]; + $port = $hostinfo[2]; + } + if ($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->ServerHostname(); + } + $this->smtp->Hello($hello); + + if ($tls) { + if (!$this->smtp->StartTLS()) { + throw new phpmailerException($this->Lang('connect_host')); + } + //We must resend HELO after tls negotiation + $this->smtp->Hello($hello); + } + if ($this->SMTPAuth) { + if (!$this->smtp->Authenticate($this->Username, $this->Password, $this->AuthType, $this->Realm, $this->Workstation)) { + throw new phpmailerException($this->Lang('authenticate')); + } + } + return true; + } catch (phpmailerException $e) { + $lastexception = $e; + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->Quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->Close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions and !is_null($lastexception)) { + throw $lastexception; + } + return false; + } + + /** + * Closes the active SMTP session if one exists. + * @return void + */ + public function SmtpClose() { + if ($this->smtp !== null) { + if($this->smtp->Connected()) { + $this->smtp->Quit(); + $this->smtp->Close(); + } + } + } + + /** + * Sets the language for all class error messages. + * Returns false if it cannot load the language file. The default language is English. + * @param string $langcode ISO 639-1 2-character language code (e.g. Portuguese: "br") + * @param string $lang_path Path to the language file directory + * @return bool + * @access public + */ + function SetLanguage($langcode = 'en', $lang_path = 'language/') { + //Define full set of translatable strings + $PHPMAILER_LANG = array( + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: Data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP Connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ' + ); + //Overwrite language-specific strings. This way we'll never have missing translations - no more "language string failed to load"! + $l = true; + if ($langcode != 'en') { //There is no English translation file + $l = @include $lang_path.'phpmailer.lang-'.$langcode.'.php'; + } + $this->language = $PHPMAILER_LANG; + return ($l == true); //Returns false if language not found + } + + /** + * Return the current array of language strings + * @return array + */ + public function GetTranslations() { + return $this->language; + } + + ///////////////////////////////////////////////// + // METHODS, MESSAGE CREATION + ///////////////////////////////////////////////// + + /** + * Creates recipient headers. + * @access public + * @param string $type + * @param array $addr + * @return string + */ + public function AddrAppend($type, $addr) { + $addr_str = $type . ': '; + $addresses = array(); + foreach ($addr as $a) { + $addresses[] = $this->AddrFormat($a); + } + $addr_str .= implode(', ', $addresses); + $addr_str .= $this->LE; + + return $addr_str; + } + + /** + * Formats an address correctly. + * @access public + * @param string $addr + * @return string + */ + public function AddrFormat($addr) { + if (empty($addr[1])) { + return $this->SecureHeader($addr[0]); + } else { + return $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">"; + } + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @param boolean $qp_mode Whether to run in Quoted-Printable mode + * @access public + * @return string + */ + public function WrapText($message, $length, $qp_mode = false) { + $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = (strtolower($this->CharSet) == "utf-8"); + $lelen = strlen($this->LE); + $crlflen = strlen(self::CRLF); + + $message = $this->FixEOL($message); + if (substr($message, -$lelen) == $this->LE) { + $message = substr($message, 0, -$lelen); + } + + $line = explode($this->LE, $message); // Magic. We know FixEOL uses $LE + $message = ''; + for ($i = 0 ;$i < count($line); $i++) { + $line_part = explode(' ', $line[$i]); + $buf = ''; + for ($e = 0; $e $length)) { + $space_left = $length - strlen($buf) - $crlflen; + if ($e != 0) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf("=%s", self::CRLF); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while (strlen($word) > 0) { + if ($length <= 0) { + break; + } + $len = $length; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) { + $message .= $part . sprintf("=%s", self::CRLF); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (' ' . $word); + + if (strlen($buf) > $length and $buf_o != '') { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf . self::CRLF; + } + + return $message; + } + + /** + * Finds last character boundary prior to maxLength in a utf-8 + * quoted (printable) encoded string. + * Original written by Colin Brown. + * @access public + * @param string $encodedText utf-8 QP text + * @param int $maxLength find last character boundary prior to this length + * @return int + */ + public function UTF8CharBoundary($encodedText, $maxLength) { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, "="); + if ($encodedCharPos !== false) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + $maxLength = ($encodedCharPos == 0) ? $maxLength : + $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec >= 192) { // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength = $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + return $maxLength; + } + + + /** + * Set the body wrapping. + * @access public + * @return void + */ + public function SetWordWrap() { + if($this->WordWrap < 1) { + return; + } + + switch($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->WrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assembles message header. + * @access public + * @return string The assembled header + */ + public function CreateHeader() { + $result = ''; + + // Set the boundaries + $uniq_id = md5(uniqid(time())); + $this->boundary[1] = 'b1_' . $uniq_id; + $this->boundary[2] = 'b2_' . $uniq_id; + $this->boundary[3] = 'b3_' . $uniq_id; + + if ($this->MessageDate == '') { + $result .= $this->HeaderLine('Date', self::RFCDate()); + } else { + $result .= $this->HeaderLine('Date', $this->MessageDate); + } + + if ($this->ReturnPath) { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->ReturnPath).'>'); + } elseif ($this->Sender == '') { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->From).'>'); + } else { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->Sender).'>'); + } + + // To be created automatically by mail() + if($this->Mailer != 'mail') { + if ($this->SingleTo === true) { + foreach($this->to as $t) { + $this->SingleToArray[] = $this->AddrFormat($t); + } + } else { + if(count($this->to) > 0) { + $result .= $this->AddrAppend('To', $this->to); + } elseif (count($this->cc) == 0) { + $result .= $this->HeaderLine('To', 'undisclosed-recipients:;'); + } + } + } + + $from = array(); + $from[0][0] = trim($this->From); + $from[0][1] = $this->FromName; + $result .= $this->AddrAppend('From', $from); + + // sendmail and mail() extract Cc from the header before sending + if(count($this->cc) > 0) { + $result .= $this->AddrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) { + $result .= $this->AddrAppend('Bcc', $this->bcc); + } + + if(count($this->ReplyTo) > 0) { + $result .= $this->AddrAppend('Reply-To', $this->ReplyTo); + } + + // mail() sets the subject itself + if($this->Mailer != 'mail') { + $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject))); + } + + if($this->MessageID != '') { + $result .= $this->HeaderLine('Message-ID', $this->MessageID); + } else { + $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE); + } + $result .= $this->HeaderLine('X-Priority', $this->Priority); + if ($this->XMailer == '') { + $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (https://github.com/PHPMailer/PHPMailer/)'); + } else { + $myXmailer = trim($this->XMailer); + if ($myXmailer) { + $result .= $this->HeaderLine('X-Mailer', $myXmailer); + } + } + + if($this->ConfirmReadingTo != '') { + $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); + } + + // Add custom headers + for($index = 0; $index < count($this->CustomHeader); $index++) { + $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1]))); + } + if (!$this->sign_key_file) { + $result .= $this->HeaderLine('MIME-Version', '1.0'); + $result .= $this->GetMailMIME(); + } + + return $result; + } + + /** + * Returns the message MIME. + * @access public + * @return string + */ + public function GetMailMIME() { + $result = ''; + switch($this->message_type) { + case 'inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1].'"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1].'"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1].'"'); + break; + default: + // Catches case 'plain': and case '': + $result .= $this->TextLine('Content-Type: '.$this->ContentType.'; charset='.$this->CharSet); + break; + } + //RFC1341 part 5 says 7bit is assumed if not specified + if ($this->Encoding != '7bit') { + $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding); + } + + if($this->Mailer != 'mail') { + $result .= $this->LE; + } + + return $result; + } + + /** + * Returns the MIME message (headers and body). Only really valid post PreSend(). + * @access public + * @return string + */ + public function GetSentMIMEMessage() { + return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody; + } + + + /** + * Assembles the message body. Returns an empty string on failure. + * @access public + * @throws phpmailerException + * @return string The assembled message body + */ + public function CreateBody() { + $body = ''; + + if ($this->sign_key_file) { + $body .= $this->GetMailMIME().$this->LE; + } + + $this->SetWordWrap(); + + switch($this->message_type) { + case 'inline': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[1]); + break; + case 'attach': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'inline_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'alt': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[1], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + if(!empty($this->Ical)) { + $body .= $this->GetBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', ''); + $body .= $this->EncodeString($this->Ical, $this->Encoding); + $body .= $this->LE.$this->LE; + } + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[2]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine('--' . $this->boundary[2]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[3].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[3], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[3]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + default: + // catch case 'plain' and case '' + $body .= $this->EncodeString($this->Body, $this->Encoding); + break; + } + + if ($this->IsError()) { + $body = ''; + } elseif ($this->sign_key_file) { + try { + if (!defined('PKCS7_TEXT')) { + throw new phpmailerException($this->Lang('signing').' OpenSSL extension missing.'); + } + $file = tempnam(sys_get_temp_dir(), 'mail'); + file_put_contents($file, $body); //TODO check this worked + $signed = tempnam(sys_get_temp_dir(), 'signed'); + if (@openssl_pkcs7_sign($file, $signed, 'file://'.realpath($this->sign_cert_file), array('file://'.realpath($this->sign_key_file), $this->sign_key_pass), null)) { + @unlink($file); + $body = file_get_contents($signed); + @unlink($signed); + } else { + @unlink($file); + @unlink($signed); + throw new phpmailerException($this->Lang('signing').openssl_error_string()); + } + } catch (phpmailerException $e) { + $body = ''; + if ($this->exceptions) { + throw $e; + } + } + } + return $body; + } + + /** + * Returns the start of a message boundary. + * @access protected + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * @return string + */ + protected function GetBoundary($boundary, $charSet, $contentType, $encoding) { + $result = ''; + if($charSet == '') { + $charSet = $this->CharSet; + } + if($contentType == '') { + $contentType = $this->ContentType; + } + if($encoding == '') { + $encoding = $this->Encoding; + } + $result .= $this->TextLine('--' . $boundary); + $result .= sprintf("Content-Type: %s; charset=%s", $contentType, $charSet); + $result .= $this->LE; + $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding); + $result .= $this->LE; + + return $result; + } + + /** + * Returns the end of a message boundary. + * @access protected + * @param string $boundary + * @return string + */ + protected function EndBoundary($boundary) { + return $this->LE . '--' . $boundary . '--' . $this->LE; + } + + /** + * Sets the message type. + * @access protected + * @return void + */ + protected function SetMessageType() { + $this->message_type = array(); + if($this->AlternativeExists()) $this->message_type[] = "alt"; + if($this->InlineImageExists()) $this->message_type[] = "inline"; + if($this->AttachmentExists()) $this->message_type[] = "attach"; + $this->message_type = implode("_", $this->message_type); + if($this->message_type == "") $this->message_type = "plain"; + } + + /** + * Returns a formatted header line. + * @access public + * @param string $name + * @param string $value + * @return string + */ + public function HeaderLine($name, $value) { + return $name . ': ' . $value . $this->LE; + } + + /** + * Returns a formatted mail line. + * @access public + * @param string $value + * @return string + */ + public function TextLine($value) { + return $value . $this->LE; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, ATTACHMENTS + ///////////////////////////////////////////////// + + /** + * Adds an attachment from a path on the filesystem. + * Returns false if the file could not be found + * or accessed. + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @throws phpmailerException + * @return bool + */ + public function AddAttachment($path, $name = '', $encoding = 'base64', $type = '') { + try { + if ( !@is_file($path) ) { + throw new phpmailerException($this->Lang('file_access') . $path, self::STOP_CONTINUE); + } + + //If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($path); + } + + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + if ($this->SMTPDebug) { + $this->edebug($e->getMessage()."\n"); + } + return false; + } + return true; + } + + /** + * Return the current array of attachments + * @return array + */ + public function GetAttachments() { + return $this->attachment; + } + + /** + * Attaches all fs, string, and binary attachments to the message. + * Returns an empty string on failure. + * @access protected + * @param string $disposition_type + * @param string $boundary + * @return string + */ + protected function AttachAll($disposition_type, $boundary) { + // Return text of body + $mime = array(); + $cidUniq = array(); + $incl = array(); + + // Add all attachments + foreach ($this->attachment as $attachment) { + // CHECK IF IT IS A VALID DISPOSITION_FILTER + if($attachment[6] == $disposition_type) { + // Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = md5(serialize($attachment)); + if (in_array($inclhash, $incl)) { continue; } + $incl[] = $inclhash; + $filename = $attachment[1]; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ( $disposition == 'inline' && isset($cidUniq[$cid]) ) { continue; } + $cidUniq[$cid] = true; + + $mime[] = sprintf("--%s%s", $boundary, $this->LE); + $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $this->EncodeHeader($this->SecureHeader($name)), $this->LE); + $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); + + if($disposition == 'inline') { + $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); + } + + //If a filename contains any of these chars, it should be quoted, but not otherwise: RFC2183 & RFC2045 5.1 + //Fixes a warning in IETF's msglint MIME checker + if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $name)) { + $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE); + } else { + $mime[] = sprintf("Content-Disposition: %s; filename=%s%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE); + } + + // Encode as string attachment + if($bString) { + $mime[] = $this->EncodeString($string, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } else { + $mime[] = $this->EncodeFile($path, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } + } + } + + $mime[] = sprintf("--%s--%s", $boundary, $this->LE); + + return implode("", $mime); + } + + /** + * Encodes attachment in requested format. + * Returns an empty string on failure. + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @throws phpmailerException + * @see EncodeFile() + * @access protected + * @return string + */ + protected function EncodeFile($path, $encoding = 'base64') { + try { + if (!is_readable($path)) { + throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); + } + $magic_quotes = get_magic_quotes_runtime(); + if ($magic_quotes) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime(0); + } else { + ini_set('magic_quotes_runtime', 0); + } + } + $file_buffer = file_get_contents($path); + $file_buffer = $this->EncodeString($file_buffer, $encoding); + if ($magic_quotes) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime($magic_quotes); + } else { + ini_set('magic_quotes_runtime', $magic_quotes); + } + } + return $file_buffer; + } catch (Exception $e) { + $this->SetError($e->getMessage()); + return ''; + } + } + + /** + * Encodes string to requested format. + * Returns an empty string on failure. + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @access public + * @return string + */ + public function EncodeString($str, $encoding = 'base64') { + $encoded = ''; + switch(strtolower($encoding)) { + case 'base64': + $encoded = chunk_split(base64_encode($str), 76, $this->LE); + break; + case '7bit': + case '8bit': + $encoded = $this->FixEOL($str); + //Make sure it ends with a line break + if (substr($encoded, -(strlen($this->LE))) != $this->LE) + $encoded .= $this->LE; + break; + case 'binary': + $encoded = $str; + break; + case 'quoted-printable': + $encoded = $this->EncodeQP($str); + break; + default: + $this->SetError($this->Lang('encoding') . $encoding); + break; + } + return $encoded; + } + + /** + * Encode a header string to best (shortest) of Q, B, quoted or none. + * @access public + * @param string $str + * @param string $position + * @return string + */ + public function EncodeHeader($str, $position = 'text') { + $x = 0; + + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know what value has magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return ($encoded); + } else { + return ("\"$encoded\""); + } + } + $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + case 'comment': + $x = preg_match_all('/[()"]/', $str, $matches); + // Fall-through + case 'text': + default: + $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($x == 0) { //There are no chars that need encoding + return ($str); + } + + $maxlen = 75 - 7 - strlen($this->CharSet); + // Try to select the encoding which should produce the shortest output + if ($x > strlen($str)/3) { //More than a third of the content will need encoding, so B encoding will be most efficient + $encoding = 'B'; + if (function_exists('mb_strlen') && $this->HasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->Base64EncodeWrapMB($str, "\n"); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + } else { + $encoding = 'Q'; + $encoded = $this->EncodeQ($str, $position); + $encoded = $this->WrapText($encoded, $maxlen, true); + $encoded = str_replace('='.self::CRLF, "\n", trim($encoded)); + } + + $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded); + $encoded = trim(str_replace("\n", $this->LE, $encoded)); + + return $encoded; + } + + /** + * Checks if a string contains multibyte characters. + * @access public + * @param string $str multi-byte text to wrap encode + * @return bool + */ + public function HasMultiBytes($str) { + if (function_exists('mb_strlen')) { + return (strlen($str) > mb_strlen($str, $this->CharSet)); + } else { // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + } + + /** + * Correctly encodes and wraps long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php + * @access public + * @param string $str multi-byte text to wrap encode + * @param string $lf string to use as linefeed/end-of-line + * @return string + */ + public function Base64EncodeWrapMB($str, $lf=null) { + $start = "=?".$this->CharSet."?B?"; + $end = "?="; + $encoded = ""; + if ($lf === null) { + $lf = $this->LE; + } + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $offset = $avgLength = floor($length * $ratio * .75); + + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + $lookBack++; + } + while (strlen($chunk) > $length); + + $encoded .= $chunk . $lf; + } + + // Chomp the last linefeed + $encoded = substr($encoded, 0, -strlen($lf)); + return $encoded; + } + + /** + * Encode string to RFC2045 (6.7) quoted-printable format + * @access public + * @param string $string The text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @return string + * @link PHP version adapted from http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 + */ + public function EncodeQP($string, $line_max = 76) { + if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) + return quoted_printable_encode($string); + } + //Fall back to a pure PHP implementation + $string = str_replace(array('%20', '%0D%0A.', '%0D%0A', '%'), array(' ', "\r\n=2E", "\r\n", '='), rawurlencode($string)); + $string = preg_replace('/[^\r\n]{'.($line_max - 3).'}[^=\r\n]{2}/', "$0=\r\n", $string); + return $string; + } + + /** + * Wrapper to preserve BC for old QP encoding function that was removed + * @see EncodeQP() + * @access public + * @param string $string + * @param integer $line_max + * @param bool $space_conv + * @return string + */ + public function EncodeQPphp($string, $line_max = 76, $space_conv = false) { + return $this->EncodeQP($string, $line_max); + } + + /** + * Encode string to q encoding. + * @link http://tools.ietf.org/html/rfc2047 + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * @access public + * @return string + */ + public function EncodeQ($str, $position = 'text') { + //There should not be any EOL in the string + $pattern = ''; + $encoded = str_replace(array("\r", "\n"), '', $str); + switch (strtolower($position)) { + case 'phrase': + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + + case 'comment': + $pattern = '\(\)"'; + //note that we don't break here! + //for this reason we build the $pattern without including delimiters and [] + + case 'text': + default: + //Replace every high ascii, control =, ? and _ characters + //We put \075 (=) as first value to make sure it's the first one in being converted, preventing double encode + $pattern = '\075\000-\011\013\014\016-\037\077\137\177-\377' . $pattern; + break; + } + + if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { + foreach (array_unique($matches[0]) as $char) { + $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); + } + } + + //Replace every spaces to _ (more readable than =20) + return str_replace(' ', '_', $encoded); +} + + + /** + * Adds a string or binary attachment (non-filesystem) to the list. + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return void + */ + public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = '') { + //If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($filename); + } + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + } + + /** + * Add an embedded attachment from a file. + * This can include images, sounds, and just about any other document type. + * @param string $path Path to the attachment. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File MIME type. + * @return bool True on successfully adding an attachment + */ + public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '') { + if ( !@is_file($path) ) { + $this->SetError($this->Lang('file_access') . $path); + return false; + } + + //If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($path); + } + + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + return true; + } + + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * Be sure to set the $type to an image type for images: + * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. + * @param string $string The attachment binary data. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name + * @param string $encoding File encoding (see $Encoding). + * @param string $type MIME type. + * @return bool True on successfully adding an attachment + */ + public function AddStringEmbeddedImage($string, $cid, $name = '', $encoding = 'base64', $type = '') { + //If a MIME type is not specified, try to work it out from the name + if ($type == '') { + $type = self::filenameToType($name); + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + return true; + } + + /** + * Returns true if an inline attachment is present. + * @access public + * @return bool + */ + public function InlineImageExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'inline') { + return true; + } + } + return false; + } + + /** + * Returns true if an attachment (non-inline) is present. + * @return bool + */ + public function AttachmentExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'attachment') { + return true; + } + } + return false; + } + + /** + * Does this message have an alternative body set? + * @return bool + */ + public function AlternativeExists() { + return !empty($this->AltBody); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MESSAGE RESET + ///////////////////////////////////////////////// + + /** + * Clears all recipients assigned in the TO array. Returns void. + * @return void + */ + public function ClearAddresses() { + foreach($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = array(); + } + + /** + * Clears all recipients assigned in the CC array. Returns void. + * @return void + */ + public function ClearCCs() { + foreach($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = array(); + } + + /** + * Clears all recipients assigned in the BCC array. Returns void. + * @return void + */ + public function ClearBCCs() { + foreach($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = array(); + } + + /** + * Clears all recipients assigned in the ReplyTo array. Returns void. + * @return void + */ + public function ClearReplyTos() { + $this->ReplyTo = array(); + } + + /** + * Clears all recipients assigned in the TO, CC and BCC + * array. Returns void. + * @return void + */ + public function ClearAllRecipients() { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + $this->all_recipients = array(); + } + + /** + * Clears all previously set filesystem, string, and binary + * attachments. Returns void. + * @return void + */ + public function ClearAttachments() { + $this->attachment = array(); + } + + /** + * Clears all custom headers. Returns void. + * @return void + */ + public function ClearCustomHeaders() { + $this->CustomHeader = array(); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MISCELLANEOUS + ///////////////////////////////////////////////// + + /** + * Adds the error message to the error container. + * @access protected + * @param string $msg + * @return void + */ + protected function SetError($msg) { + $this->error_count++; + if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { + $msg .= '

' . $this->Lang('smtp_error') . $lasterror['smtp_msg'] . "

\n"; + } + } + $this->ErrorInfo = $msg; + } + + /** + * Returns the proper RFC 822 formatted date. + * @access public + * @return string + * @static + */ + public static function RFCDate() { + //Set the time zone to whatever the default is to avoid 500 errors + //Will default to UTC if it's not set properly in php.ini + date_default_timezone_set(@date_default_timezone_get()); + return date('D, j M Y H:i:s O'); + } + + /** + * Returns the server hostname or 'localhost.localdomain' if unknown. + * @access protected + * @return string + */ + protected function ServerHostname() { + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER['SERVER_NAME'])) { + $result = $_SERVER['SERVER_NAME']; + } else { + $result = 'localhost.localdomain'; + } + + return $result; + } + + /** + * Returns a message in the appropriate language. + * @access protected + * @param string $key + * @return string + */ + protected function Lang($key) { + if(count($this->language) < 1) { + $this->SetLanguage('en'); // set the default language + } + + if(isset($this->language[$key])) { + return $this->language[$key]; + } else { + return 'Language string failed to load: ' . $key; + } + } + + /** + * Returns true if an error occurred. + * @access public + * @return bool + */ + public function IsError() { + return ($this->error_count > 0); + } + + /** + * Changes every end of line from CRLF, CR or LF to $this->LE. + * @access public + * @param string $str String to FixEOL + * @return string + */ + public function FixEOL($str) { + // condense down to \n + $nstr = str_replace(array("\r\n", "\r"), "\n", $str); + // Now convert LE as needed + if ($this->LE !== "\n") { + $nstr = str_replace("\n", $this->LE, $nstr); + } + return $nstr; + } + + /** + * Adds a custom header. $name value can be overloaded to contain + * both header name and value (name:value) + * @access public + * @param string $name custom header name + * @param string $value header value + * @return void + */ + public function AddCustomHeader($name, $value=null) { + if ($value === null) { + // Value passed in as name:value + $this->CustomHeader[] = explode(':', $name, 2); + } else { + $this->CustomHeader[] = array($name, $value); + } + } + + /** + * Creates a message from an HTML string, making modifications for inline images and backgrounds + * and creates a plain-text version by converting the HTML + * Overwrites any existing values in $this->Body and $this->AltBody + * @access public + * @param string $message HTML message string + * @param string $basedir baseline directory for path + * @param bool $advanced Whether to use the advanced HTML to text converter + * @return string $message + */ + public function MsgHTML($message, $basedir = '', $advanced = false) { + preg_match_all("/(src|background)=[\"'](.*)[\"']/Ui", $message, $images); + if (isset($images[2])) { + foreach ($images[2] as $i => $url) { + // do not change urls for absolute images (thanks to corvuscorax) + if (!preg_match('#^[A-z]+://#', $url)) { + $filename = basename($url); + $directory = dirname($url); + if ($directory == '.') { + $directory = ''; + } + $cid = md5($url).'@phpmailer.0'; //RFC2392 S 2 + if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { + $basedir .= '/'; + } + if (strlen($directory) > 1 && substr($directory, -1) != '/') { + $directory .= '/'; + } + if ($this->AddEmbeddedImage($basedir.$directory.$filename, $cid, $filename, 'base64', self::_mime_types(self::mb_pathinfo($filename, PATHINFO_EXTENSION)))) { + $message = preg_replace("/".$images[1][$i]."=[\"']".preg_quote($url, '/')."[\"']/Ui", $images[1][$i]."=\"cid:".$cid."\"", $message); + } + } + } + } + $this->IsHTML(true); + if (empty($this->AltBody)) { + $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n"; + } + //Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better + $this->Body = $this->NormalizeBreaks($message); + $this->AltBody = $this->NormalizeBreaks($this->html2text($message, $advanced)); + return $this->Body; + } + + /** + * Convert an HTML string into a plain text version + * @param string $html The HTML text to convert + * @param bool $advanced Should this use the more complex html2text converter or just a simple one? + * @return string + */ + public function html2text($html, $advanced = false) { + if ($advanced) { + require_once 'extras/class.html2text.php'; + $h = new html2text($html); + return $h->get_text(); + } + return html_entity_decode(trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), ENT_QUOTES, $this->CharSet); + } + + /** + * Gets the MIME type of the embedded or inline image + * @param string $ext File extension + * @access public + * @return string MIME type of ext + * @static + */ + public static function _mime_types($ext = '') { + $mimes = array( + 'xl' => 'application/excel', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/x-javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie' + ); + return (!isset($mimes[strtolower($ext)])) ? 'application/octet-stream' : $mimes[strtolower($ext)]; + } + + /** + * Try to map a file name to a MIME type, default to application/octet-stream + * @param string $filename A file name or full path, does not need to exist as a file + * @return string + * @static + */ + public static function filenameToType($filename) { + //In case the path is a URL, strip any query string before getting extension + $qpos = strpos($filename, '?'); + if ($qpos !== false) { + $filename = substr($filename, 0, $qpos); + } + $pathinfo = self::mb_pathinfo($filename); + return self::_mime_types($pathinfo['extension']); + } + + /** + * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. + * Works similarly to the one in PHP >= 5.2.0 + * @link http://www.php.net/manual/en/function.pathinfo.php#107461 + * @param string $path A filename or path, does not need to exist as a file + * @param integer|string $options Either a PATHINFO_* constant, or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 + * @return string|array + * @static + */ + public static function mb_pathinfo($path, $options = null) { + $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); + $m = array(); + preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $m); + if(array_key_exists(1, $m)) { + $ret['dirname'] = $m[1]; + } + if(array_key_exists(2, $m)) { + $ret['basename'] = $m[2]; + } + if(array_key_exists(5, $m)) { + $ret['extension'] = $m[5]; + } + if(array_key_exists(3, $m)) { + $ret['filename'] = $m[3]; + } + switch($options) { + case PATHINFO_DIRNAME: + case 'dirname': + return $ret['dirname']; + break; + case PATHINFO_BASENAME: + case 'basename': + return $ret['basename']; + break; + case PATHINFO_EXTENSION: + case 'extension': + return $ret['extension']; + break; + case PATHINFO_FILENAME: + case 'filename': + return $ret['filename']; + break; + default: + return $ret; + } + } + + /** + * Set (or reset) Class Objects (variables) + * + * Usage Example: + * $page->set('X-Priority', '3'); + * + * @access public + * @param string $name + * @param mixed $value + * NOTE: will not work with arrays, there are no arrays to set/reset + * @throws phpmailerException + * @return bool + * @todo Should this not be using __set() magic function? + */ + public function set($name, $value = '') { + try { + if (isset($this->$name) ) { + $this->$name = $value; + } else { + throw new phpmailerException($this->Lang('variable_set') . $name, self::STOP_CRITICAL); + } + } catch (Exception $e) { + $this->SetError($e->getMessage()); + if ($e->getCode() == self::STOP_CRITICAL) { + return false; + } + } + return true; + } + + /** + * Strips newlines to prevent header injection. + * @access public + * @param string $str + * @return string + */ + public function SecureHeader($str) { + return trim(str_replace(array("\r", "\n"), '', $str)); + } + + /** + * Normalize UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format + * Defaults to CRLF (for message bodies) and preserves consecutive breaks + * @param string $text + * @param string $breaktype What kind of line break to use, defaults to CRLF + * @return string + * @access public + * @static + */ + public static function NormalizeBreaks($text, $breaktype = "\r\n") { + return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); + } + + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + */ + public function Sign($cert_filename, $key_filename, $key_pass) { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $txt + * @return string + */ + public function DKIM_QP($txt) { + $line = ''; + for ($i = 0; $i < strlen($txt); $i++) { + $ord = ord($txt[$i]); + if ( ((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E)) ) { + $line .= $txt[$i]; + } else { + $line .= "=".sprintf("%02X", $ord); + } + } + return $line; + } + + /** + * Generate DKIM signature + * + * @access public + * @param string $s Header + * @throws phpmailerException + * @return string + */ + public function DKIM_Sign($s) { + if (!defined('PKCS7_TEXT')) { + if ($this->exceptions) { + throw new phpmailerException($this->Lang("signing").' OpenSSL extension missing.'); + } + return ''; + } + $privKeyStr = file_get_contents($this->DKIM_private); + if ($this->DKIM_passphrase != '') { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = $privKeyStr; + } + if (openssl_sign($s, $signature, $privKey)) { + return base64_encode($signature); + } + return ''; + } + + /** + * Generate DKIM Canonicalization Header + * + * @access public + * @param string $s Header + * @return string + */ + public function DKIM_HeaderC($s) { + $s = preg_replace("/\r\n\s+/", " ", $s); + $lines = explode("\r\n", $s); + foreach ($lines as $key => $line) { + list($heading, $value) = explode(":", $line, 2); + $heading = strtolower($heading); + $value = preg_replace("/\s+/", " ", $value) ; // Compress useless spaces + $lines[$key] = $heading.":".trim($value) ; // Don't forget to remove WSP around the value + } + $s = implode("\r\n", $lines); + return $s; + } + + /** + * Generate DKIM Canonicalization Body + * + * @access public + * @param string $body Message Body + * @return string + */ + public function DKIM_BodyC($body) { + if ($body == '') return "\r\n"; + // stabilize line endings + $body = str_replace("\r\n", "\n", $body); + $body = str_replace("\n", "\r\n", $body); + // END stabilize line endings + while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { + $body = substr($body, 0, strlen($body) - 2); + } + return $body; + } + + /** + * Create the DKIM header, body, as new header + * + * @access public + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * @return string + */ + public function DKIM_Add($headers_line, $subject, $body) { + $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time() ; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode($this->LE, $headers_line); + $from_header = ''; + $to_header = ''; + $current = ''; + foreach($headers as $header) { + if (strpos($header, 'From:') === 0) { + $from_header = $header; + $current = 'from_header'; + } elseif (strpos($header, 'To:') === 0) { + $to_header = $header; + $current = 'to_header'; + } else { + if($current && strpos($header, ' =?') === 0){ + $current .= $header; + } else { + $current = ''; + } + } + } + $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); + $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); + $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header)) ; // Copied header fields (dkim-quoted-printable + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body) ; // Length of body + $DKIMb64 = base64_encode(pack("H*", sha1($body))) ; // Base64 of packed binary SHA-1 hash of body + $ident = ($this->DKIM_identity == '')? '' : " i=" . $this->DKIM_identity . ";"; + $dkimhdrs = "DKIM-Signature: v=1; a=" . $DKIMsignatureType . "; q=" . $DKIMquery . "; l=" . $DKIMlen . "; s=" . $this->DKIM_selector . ";\r\n". + "\tt=" . $DKIMtime . "; c=" . $DKIMcanonicalization . ";\r\n". + "\th=From:To:Subject;\r\n". + "\td=" . $this->DKIM_domain . ";" . $ident . "\r\n". + "\tz=$from\r\n". + "\t|$to\r\n". + "\t|$subject;\r\n". + "\tbh=" . $DKIMb64 . ";\r\n". + "\tb="; + $toSign = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs); + $signed = $this->DKIM_Sign($toSign); + return $dkimhdrs.$signed."\r\n"; + } + + /** + * Perform callback + * @param boolean $isSent + * @param string $to + * @param string $cc + * @param string $bcc + * @param string $subject + * @param string $body + * @param string $from + */ + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from = null) { + if (!empty($this->action_function) && is_callable($this->action_function)) { + $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); + call_user_func_array($this->action_function, $params); + } + } +} + +/** + * Exception handler for PHPMailer + * @package PHPMailer + */ +class phpmailerException extends Exception { + /** + * Prettify error message output + * @return string + */ + public function errorMessage() { + $errorMsg = '' . $this->getMessage() . "
\n"; + return $errorMsg; + } +} diff --git a/vendor/PHPMailer/class.smtp.php b/vendor/PHPMailer/class.smtp.php new file mode 100644 index 00000000..4b02f999 --- /dev/null +++ b/vendor/PHPMailer/class.smtp.php @@ -0,0 +1,1092 @@ +Debugoutput) { + case 'error_log': + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking display that's HTML-safe + echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8')."
\n"; + break; + case 'echo': + default: + //Just echoes exactly what was received + echo $str; + } + } + + /** + * Initialize the class so that the data is in a known state. + * @access public + * @return SMTP + */ + public function __construct() { + $this->smtp_conn = 0; + $this->error = null; + $this->helo_rply = null; + + $this->do_debug = 0; + } + + ///////////////////////////////////////////////// + // CONNECTION FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Connect to an SMTP server + * + * SMTP CODE SUCCESS: 220 + * SMTP CODE FAILURE: 421 + * @access public + * @param string $host SMTP server IP or host name + * @param int $port The port number to connect to, or use the default port if not specified + * @param int $timeout How long to wait for the connection to open + * @param array $options An array of options compatible with stream_context_create() + * @return bool + */ + public function Connect($host, $port = 0, $timeout = 30, $options = array()) { + // Clear errors to avoid confusion + $this->error = null; + + // Make sure we are __not__ connected + if($this->connected()) { + // Already connected, generate error + $this->error = array('error' => 'Already connected to a server'); + return false; + } + + if(empty($port)) { + $port = $this->SMTP_PORT; + } + + // Connect to the SMTP server + $errno = 0; + $errstr = ''; + $socket_context = stream_context_create($options); + //Need to suppress errors here as connection failures can be handled at a higher level + $this->smtp_conn = @stream_socket_client($host.":".$port, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $socket_context); + + // Verify we connected properly + if(empty($this->smtp_conn)) { + $this->error = array('error' => 'Failed to connect to server', + 'errno' => $errno, + 'errstr' => $errstr); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ": $errstr ($errno)"); + } + return false; + } + + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if(substr(PHP_OS, 0, 3) != 'WIN') { + $max = ini_get('max_execution_time'); + if ($max != 0 && $timeout > $max) { // Don't bother if unlimited + @set_time_limit($timeout); + } + stream_set_timeout($this->smtp_conn, $timeout, 0); + } + + // get any announcement + $announce = $this->get_lines(); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $announce); + } + + return true; + } + + /** + * Initiate a TLS communication with the server. + * + * SMTP CODE 220 Ready to start TLS + * SMTP CODE 501 Syntax error (no parameters allowed) + * SMTP CODE 454 TLS not available due to temporary reason + * @access public + * @return bool success + */ + public function StartTLS() { + $this->error = null; # to avoid confusion + + if(!$this->connected()) { + $this->error = array('error' => 'Called StartTLS() without being connected'); + return false; + } + + $this->client_send('STARTTLS' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 220) { + $this->error = + array('error' => 'STARTTLS not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + // Begin encrypted connection + if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + return false; + } + + return true; + } + + /** + * Performs SMTP authentication. Must be run after running the + * Hello() method. Returns true if successfully authenticated. + * @access public + * @param string $username + * @param string $password + * @param string $authtype + * @param string $realm + * @param string $workstation + * @return bool + */ + public function Authenticate($username, $password, $authtype='LOGIN', $realm='', $workstation='') { + if (empty($authtype)) { + $authtype = 'LOGIN'; + } + + switch ($authtype) { + case 'PLAIN': + // Start authentication + $this->client_send('AUTH PLAIN' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + // Send encoded username and password + $this->client_send(base64_encode("\0".$username."\0".$password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Authentication not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + break; + case 'LOGIN': + // Start authentication + $this->client_send('AUTH LOGIN' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + // Send encoded username + $this->client_send(base64_encode($username) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'Username not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + // Send encoded password + $this->client_send(base64_encode($password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Password not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + break; + case 'NTLM': + /* + * ntlm_sasl_client.php + ** Bundled with Permission + ** + ** How to telnet in windows: http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx + ** PROTOCOL Documentation http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication + */ + require_once 'extras/ntlm_sasl_client.php'; + $temp = new stdClass(); + $ntlm_client = new ntlm_sasl_client_class; + if(! $ntlm_client->Initialize($temp)){//let's test if every function its available + $this->error = array('error' => $temp->error); + if($this->do_debug >= 1) { + $this->edebug('You need to enable some modules in your php.ini file: ' . $this->error['error']); + } + return false; + } + $msg1 = $ntlm_client->TypeMsg1($realm, $workstation);//msg1 + + $this->client_send('AUTH NTLM ' . base64_encode($msg1) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + $challenge = substr($rply, 3);//though 0 based, there is a white space after the 3 digit number....//msg2 + $challenge = base64_decode($challenge); + $ntlm_res = $ntlm_client->NTLMResponse(substr($challenge, 24, 8), $password); + $msg3 = $ntlm_client->TypeMsg3($ntlm_res, $username, $realm, $workstation);//msg3 + // Send encoded username + $this->client_send(base64_encode($msg3) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Could not authenticate', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + break; + case 'CRAM-MD5': + // Start authentication + $this->client_send('AUTH CRAM-MD5' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + // Get the challenge + $challenge = base64_decode(substr($rply, 4)); + + // Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); + + // Send encoded credentials + $this->client_send(base64_encode($response) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Credentials not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + break; + } + return true; + } + + /** + * Works like hash_hmac('md5', $data, $key) in case that function is not available + * @access protected + * @param string $data + * @param string $key + * @return string + */ + protected function hmac($data, $key) { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + // The following borrowed from http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // Hacked by Lance Rushing + + $b = 64; // byte length for md5 + if (strlen($key) > $b) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $b, chr(0x00)); + $ipad = str_pad('', $b, chr(0x36)); + $opad = str_pad('', $b, chr(0x5c)); + $k_ipad = $key ^ $ipad ; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Returns true if connected to a server otherwise false + * @access public + * @return bool + */ + public function Connected() { + if(!empty($this->smtp_conn)) { + $sock_status = stream_get_meta_data($this->smtp_conn); + if($sock_status['eof']) { + // the socket is valid but we are not connected + if($this->do_debug >= 1) { + $this->edebug('SMTP -> NOTICE: EOF caught while checking if connected'); + } + $this->Close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Closes the socket and cleans up the state of the class. + * It is not considered good to use this function without + * first trying to use QUIT. + * @access public + * @return void + */ + public function Close() { + $this->error = null; // so there is no confusion + $this->helo_rply = null; + if(!empty($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = 0; + } + } + + ///////////////////////////////////////////////// + // SMTP COMMANDS + ///////////////////////////////////////////////// + + /** + * Issues a data command and sends the msg_data to the server + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by and additional . + * + * Implements rfc 821: DATA + * + * SMTP CODE INTERMEDIATE: 354 + * [data] + * . + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 552, 554, 451, 452 + * SMTP CODE FAILURE: 451, 554 + * SMTP CODE ERROR : 500, 501, 503, 421 + * @access public + * @param string $msg_data + * @return bool + */ + public function Data($msg_data) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Data() without being connected'); + return false; + } + + $this->client_send('DATA' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 354) { + $this->error = + array('error' => 'DATA command not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + /* the server is ready to accept data! + * according to rfc 821 we should not send more than 1000 + * including the CRLF + * characters on a single line so we will break the data up + * into lines by \r and/or \n then if needed we will break + * each of those into smaller lines to fit within the limit. + * in addition we will be looking for lines that start with + * a period '.' and append and additional period '.' to that + * line. NOTE: this does not count towards limit. + */ + + // normalize the line breaks so we know the explode works + $msg_data = str_replace("\r\n", "\n", $msg_data); + $msg_data = str_replace("\r", "\n", $msg_data); + $lines = explode("\n", $msg_data); + + /* we need to find a good way to determine is headers are + * in the msg_data or if it is a straight msg body + * currently I am assuming rfc 822 definitions of msg headers + * and if the first field of the first line (':' sperated) + * does not contain a space then it _should_ be a header + * and we can process all lines before a blank "" line as + * headers. + */ + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if(!empty($field) && !strstr($field, ' ')) { + $in_headers = true; + } + + $max_line_length = 998; // used below; set here for ease in change + + while(list(, $line) = @each($lines)) { + $lines_out = null; + if($line == '' && $in_headers) { + $in_headers = false; + } + // ok we need to break this line up into several smaller lines + while(strlen($line) > $max_line_length) { + $pos = strrpos(substr($line, 0, $max_line_length), ' '); + + // Patch to fix DOS attack + if(!$pos) { + $pos = $max_line_length - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos + 1); + } + + /* if processing headers add a LWSP-char to the front of new line + * rfc 822 on long msg headers + */ + if($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + // send the lines to the server + while(list(, $line_out) = @each($lines_out)) { + if(strlen($line_out) > 0) + { + if(substr($line_out, 0, 1) == '.') { + $line_out = '.' . $line_out; + } + } + $this->client_send($line_out . $this->CRLF); + } + } + + // message data has been sent + $this->client_send($this->CRLF . '.' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => 'DATA not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + return true; + } + + /** + * Sends the HELO command to the smtp server. + * This makes sure that we and the server are in + * the same known state. + * + * Implements from rfc 821: HELO + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @param string $host + * @return bool + */ + public function Hello($host = '') { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Hello() without being connected'); + return false; + } + + // if hostname for HELO was not specified send default + if(empty($host)) { + // determine appropriate default to send to server + $host = 'localhost'; + } + + // Send extended hello first (RFC 2821) + if(!$this->SendHello('EHLO', $host)) { + if(!$this->SendHello('HELO', $host)) { + return false; + } + } + + return true; + } + + /** + * Sends a HELO/EHLO command. + * @access protected + * @param string $hello + * @param string $host + * @return bool + */ + protected function SendHello($hello, $host) { + $this->client_send($hello . ' ' . $host . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER: ' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => $hello . ' not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + $this->helo_rply = $rply; + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. + * + * Implements rfc 821: MAIL FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 421 + * @access public + * @param string $from + * @return bool + */ + public function Mail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Mail() without being connected'); + return false; + } + + $useVerp = ($this->do_verp ? ' XVERP' : ''); + $this->client_send('MAIL FROM:<' . $from . '>' . $useVerp . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => 'MAIL not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + return true; + } + + /** + * Sends the quit command to the server and then closes the socket + * if there is no error or the $close_on_error argument is true. + * + * Implements from rfc 821: QUIT + * + * SMTP CODE SUCCESS: 221 + * SMTP CODE ERROR : 500 + * @access public + * @param bool $close_on_error + * @return bool + */ + public function Quit($close_on_error = true) { + $this->error = null; // so there is no confusion + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Quit() without being connected'); + return false; + } + + // send the quit command to the server + $this->client_send('quit' . $this->CRLF); + + // get any good-bye messages + $byemsg = $this->get_lines(); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $byemsg); + } + + $rval = true; + $e = null; + + $code = substr($byemsg, 0, 3); + if($code != 221) { + // use e as a tmp var cause Close will overwrite $this->error + $e = array('error' => 'SMTP server rejected quit command', + 'smtp_code' => $code, + 'smtp_rply' => substr($byemsg, 4)); + $rval = false; + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $e['error'] . ': ' . $byemsg); + } + } + + if(empty($e) || $close_on_error) { + $this->Close(); + } + + return $rval; + } + + /** + * Sends the command RCPT to the SMTP server with the TO: argument of $to. + * Returns true if the recipient was accepted false if it was rejected. + * + * Implements from rfc 821: RCPT TO: + * + * SMTP CODE SUCCESS: 250, 251 + * SMTP CODE FAILURE: 550, 551, 552, 553, 450, 451, 452 + * SMTP CODE ERROR : 500, 501, 503, 421 + * @access public + * @param string $to + * @return bool + */ + public function Recipient($to) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Recipient() without being connected'); + return false; + } + + $this->client_send('RCPT TO:<' . $to . '>' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250 && $code != 251) { + $this->error = + array('error' => 'RCPT not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + return true; + } + + /** + * Sends the RSET command to abort and transaction that is + * currently in progress. Returns true if successful false + * otherwise. + * + * Implements rfc 821: RSET + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @return bool + */ + public function Reset() { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array('error' => 'Called Reset() without being connected'); + return false; + } + + $this->client_send('RSET' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => 'RSET failed', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * + * Implements rfc 821: SAML FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 502, 421 + * @access public + * @param string $from + * @return bool + */ + public function SendAndMail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called SendAndMail() without being connected'); + return false; + } + + $this->client_send('SAML FROM:' . $from . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => 'SAML not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + return true; + } + + /** + * This is an optional command for SMTP that this class does not + * support. This method is here to make the RFC821 Definition + * complete for this class and __may__ be implimented in the future + * + * Implements from rfc 821: TURN + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 502 + * SMTP CODE ERROR : 500, 503 + * @access public + * @return bool + */ + public function Turn() { + $this->error = array('error' => 'This method, TURN, of the SMTP '. + 'is not implemented'); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> NOTICE: ' . $this->error['error']); + } + return false; + } + + /** + * Sends data to the server + * @param string $data + * @access public + * @return Integer number of bytes sent to the server or FALSE on error + */ + public function client_send($data) { + if ($this->do_debug >= 1) { + $this->edebug("CLIENT -> SMTP: $data"); + } + return fwrite($this->smtp_conn, $data); + } + + /** + * Get the current error + * @access public + * @return array + */ + public function getError() { + return $this->error; + } + + ///////////////////////////////////////////////// + // INTERNAL FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Read in as many lines as possible + * either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access protected + * @return string + */ + protected function get_lines() { + $data = ''; + $endtime = 0; + /* If for some reason the fp is bad, don't inf loop */ + if (!is_resource($this->smtp_conn)) { + return $data; + } + stream_set_timeout($this->smtp_conn, $this->Timeout); + if ($this->Timelimit > 0) { + $endtime = time() + $this->Timelimit; + } + while(is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { + $str = @fgets($this->smtp_conn, 515); + if($this->do_debug >= 4) { + $this->edebug("SMTP -> get_lines(): \$data was \"$data\""); + $this->edebug("SMTP -> get_lines(): \$str is \"$str\""); + } + $data .= $str; + if($this->do_debug >= 4) { + $this->edebug("SMTP -> get_lines(): \$data is \"$data\""); + } + // if 4th character is a space, we are done reading, break the loop + if(substr($str, 3, 1) == ' ') { break; } + // Timed-out? Log and break + $info = stream_get_meta_data($this->smtp_conn); + if ($info['timed_out']) { + if($this->do_debug >= 4) { + $this->edebug('SMTP -> get_lines(): timed-out (' . $this->Timeout . ' seconds)'); + } + break; + } + // Now check if reads took too long + if ($endtime) { + if (time() > $endtime) { + if($this->do_debug >= 4) { + $this->edebug('SMTP -> get_lines(): timelimit reached (' . $this->Timelimit . ' seconds)'); + } + break; + } + } + } + return $data; + } + +} diff --git a/vendor/phpmailer.class.php b/vendor/phpmailer.class.php deleted file mode 100644 index 0da0e8c9..00000000 --- a/vendor/phpmailer.class.php +++ /dev/null @@ -1,1615 +0,0 @@ - -// -// License: LGPL, see LICENSE -//////////////////////////////////////////////////// - -/** - * phpmailer - PHP email transport class - * @author Brent R. Matzelle - */ -class phpmailer -{ - ///////////////////////////////////////////////// - // PUBLIC VARIABLES - ///////////////////////////////////////////////// - - /** - * Email priority (1 = High, 3 = Normal, 5 = low). Default value is 3. - * @public - * @type int - */ - var $Priority = 3; - - /** - * Sets the CharSet of the message. Default value is "iso-8859-1". - * @public - * @type string - */ - var $CharSet = "iso-8859-1"; - - /** - * Sets the Content-type of the message. Default value is "text/plain". - * @public - * @type string - */ - var $ContentType = "text/plain"; - - /** - * Sets the Encoding of the message. Options for this are "8bit" (default), - * "7bit", "binary", "base64", and "quoted-printable". - * @public - * @type string - */ - var $Encoding = "8bit"; - - /** - * Holds the most recent mailer error message. Default value is "". - * @public - * @type string - */ - var $ErrorInfo = ""; - - /** - * Sets the From email address for the message. Default value is "root@localhost". - * @public - * @type string - */ - var $From = "root@localhost"; - - /** - * Sets the From name of the message. Default value is "Root User". - * @public - * @type string - */ - var $FromName = "Root User"; - - /** - * Sets the Sender email of the message. If not empty, will be sent via -f to sendmail - * or as 'MAIL FROM' in smtp mode. Default value is "". - * @public - * @type string - */ - var $Sender = ""; - - /** - * Sets the Subject of the message. Default value is "". - * @public - * @type string - */ - var $Subject = ""; - - /** - * Sets the Body of the message. This can be either an HTML or text body. - * If HTML then run IsHTML(true). Default value is "". - * @public - * @type string - */ - var $Body = ""; - - /** - * Sets the text-only body of the message. This automatically sets the - * email to multipart/alternative. This body can be read by mail - * clients that do not have HTML email capability such as mutt. Clients - * that can read HTML will view the normal Body. - * Default value is "". - * @public - * @type string - */ - var $AltBody = ""; - - /** - * Sets word wrapping on the body of the message to a given number of - * characters. Default value is 0 (off). - * @public - * @type int - */ - var $WordWrap = 0; - - /** - * Method to send mail: ("mail", "sendmail", or "smtp"). - * Default value is "mail". - * @public - * @type string - */ - var $Mailer = "mail"; - - /** - * Sets the path of the sendmail program. Default value is - * "/usr/sbin/sendmail". - * @public - * @type string - */ - var $Sendmail = "/usr/sbin/sendmail"; - - /** - * Turns Microsoft mail client headers on and off. Useful mostly - * for older clients. Default value is false (off). - * @public - * @type bool - */ - var $UseMSMailHeaders = false; - - /** - * Path to phpmailer plugins. This is now only useful if the SMTP class - * is in a different directory than the PHP include path. - * Default is empty (""). - * @public - * @type string - */ - var $PluginDir = ""; - - /** - * Holds phpmailer version. - * @public - * @type string - */ - var $Version = "1.54"; - - /** - * Sets the email address that a reading confirmation will be sent. Default value is "". - * @public - * @type string - */ - var $ConfirmReadingTo = ""; - - /** - * Sets the line endings of the message. Default is "\n"; - * @public - * @type string - */ - var $LE = "\n"; - - - ///////////////////////////////////////////////// - // SMTP VARIABLES - ///////////////////////////////////////////////// - - /** - * Sets the SMTP hosts. All hosts must be separated by a - * semicolon. You can also specify a different port - * for each host by using this format: [hostname:port] - * (e.g. "smtp1.domain.com:25;smtp2.domain.com"). - * Hosts will be tried in order. - * Default value is "localhost". - * @public - * @type string - */ - var $Host = "localhost"; - - /** - * Sets the default SMTP server port. Default value is 25. - * @public - * @type int - */ - var $Port = 25; - - /** - * Sets the SMTP HELO of the message. - * Default value is "localhost.localdomain". - * @public - * @type string - */ - var $Helo = "localhost.localdomain"; - - /** - * Sets SMTP authentication. Utilizes the Username and Password variables. - * Default value is false (off). - * @public - * @type bool - */ - var $SMTPAuth = false; - - /** - * Sets SMTP username. Default value is "". - * @public - * @type string - */ - var $Username = ""; - - /** - * Sets SMTP password. Default value is "". - * @public - * @type string - */ - var $Password = ""; - - /** - * Sets the SMTP server timeout in seconds. This function will not - * work with the win32 version. Default value is 10. - * @public - * @type int - */ - var $Timeout = 10; - - /** - * Sets SMTP class debugging on or off. Default value is false (off). - * @public - * @type bool - */ - var $SMTPDebug = false; - - - ///////////////////////////////////////////////// - // PRIVATE VARIABLES - ///////////////////////////////////////////////// - - /** - * Holds all "To" addresses. - * @type array - */ - var $to = array(); - - /** - * Holds all "CC" addresses. - * @type array - */ - var $cc = array(); - - /** - * Holds all "BCC" addresses. - * @type array - */ - var $bcc = array(); - - /** - * Holds all "Reply-To" addresses. - * @type array - */ - var $ReplyTo = array(); - - /** - * Holds all string and binary attachments. - * @type array - */ - var $attachment = array(); - - /** - * Holds all custom headers. - * @type array - */ - var $CustomHeader = array(); - - /** - * Holds the type of the message. - * @type string - */ - var $message_type = ""; - - /** - * Holds the message boundaries. - * @type string array - */ - var $boundary = array(); - - ///////////////////////////////////////////////// - // VARIABLE METHODS - ///////////////////////////////////////////////// - - /** - * Sets message type to HTML. Returns void. - * @public - * @returns void - */ - function IsHTML($bool) { - if($bool == true) - $this->ContentType = "text/html"; - else - $this->ContentType = "text/plain"; - } - - /** - * Sets Mailer to send message using SMTP. - * Returns void. - * @public - * @returns void - */ - function IsSMTP() { - $this->Mailer = "smtp"; - } - - /** - * Sets Mailer to send message using PHP mail() function. - * Returns void. - * @public - * @returns void - */ - function IsMail() { - $this->Mailer = "mail"; - } - - /** - * Sets Mailer to send message using the $Sendmail program. - * Returns void. - * @public - * @returns void - */ - function IsSendmail() { - $this->Mailer = "sendmail"; - } - - /** - * Sets Mailer to send message using the qmail MTA. Returns void. - * @public - * @returns void - */ - function IsQmail() { - //$this->Sendmail = "/var/qmail/bin/qmail-inject"; - $this->Sendmail = "/var/qmail/bin/sendmail"; - $this->Mailer = "sendmail"; - } - - - ///////////////////////////////////////////////// - // RECIPIENT METHODS - ///////////////////////////////////////////////// - - /** - * Adds a "To" address. Returns void. - * @public - * @returns void - */ - function AddAddress($address, $name = "") { - $cur = count($this->to); - $this->to[$cur][0] = trim($address); - $this->to[$cur][1] = $name; - } - - /** - * Adds a "Cc" address. Note: this function works - * with the SMTP mailer on win32, not with the "mail" - * mailer. This is a PHP bug that has been submitted - * on http://bugs.php.net. The *NIX version of PHP - * functions correctly. Returns void. - * @public - * @returns void - */ - function AddCC($address, $name = "") { - $cur = count($this->cc); - $this->cc[$cur][0] = trim($address); - $this->cc[$cur][1] = $name; - } - - /** - * Adds a "Bcc" address. Note: this function works - * with the SMTP mailer on win32, not with the "mail" - * mailer. This is a PHP bug that has been submitted - * on http://bugs.php.net. The *NIX version of PHP - * functions correctly. - * Returns void. - * @public - * @returns void - */ - function AddBCC($address, $name = "") { - $cur = count($this->bcc); - $this->bcc[$cur][0] = trim($address); - $this->bcc[$cur][1] = $name; - } - - /** - * Adds a "Reply-to" address. Returns void. - * @public - * @returns void - */ - function AddReplyTo($address, $name = "") { - $cur = count($this->ReplyTo); - $this->ReplyTo[$cur][0] = trim($address); - $this->ReplyTo[$cur][1] = $name; - } - - - ///////////////////////////////////////////////// - // MAIL SENDING METHODS - ///////////////////////////////////////////////// - - /** - * Creates message and assigns Mailer. If the message is - * not sent successfully then it returns false. Use the ErrorInfo - * variable to view description of the error. Returns bool. - * @public - * @returns bool - */ - function Send() { - $header = ""; - $body = ""; - - if((count($this->to) + count($this->cc) + count($this->bcc)) < 1) - { - $this->error_handler("You must provide at least one recipient email address"); - return false; - } - - // Set whether the message is multipart/alternative - if(!empty($this->AltBody)) - $this->ContentType = "multipart/alternative"; - - // Attach sender information & date - $header = $this->received(); - $header .= sprintf("Date: %s%s", $this->rfc_date(), $this->LE); - $header .= $this->create_header(); - - if(!$body = $this->create_body()) - return false; - - //echo "
".$header . $body . "
"; // debugging - - // Choose the mailer - if($this->Mailer == "sendmail") - { - if(!$this->sendmail_send($header, $body)) - return false; - } - elseif($this->Mailer == "mail") - { - if(!$this->mail_send($header, $body)) - return false; - } - elseif($this->Mailer == "smtp") - { - if(!$this->smtp_send($header, $body)) - return false; - } - else - { - $this->error_handler(sprintf("%s mailer is not supported", $this->Mailer)); - return false; - } - - return true; - } - - /** - * Sends mail message to an assigned queue directory. Has an optional - * sendTime argument. This is used when the user wants the - * message to be sent from the queue at a predetermined time. - * The data must be a valid timestamp like that returned from - * the time() or strtotime() functions. Returns false on failure - * or the message file name if success. - * @public - * @returns string - */ - function SendToQueue($queue_path, $send_time = 0) { - $message = array(); - $header = ""; - $body = ""; - - // If invalid or empty just set to the current time - if($send_time == 0) - $send_time = time(); - - if(!is_dir($queue_path)) - { - $this->error_handler("The supplied queue directory does not exist"); - return false; - } - - if((count($this->to) + count($this->cc) + count($this->bcc)) < 1) - { - $this->error_handler("You must provide at least one recipient email address"); - return false; - } - - // Set whether the message is multipart/alternative - if(!empty($this->AltBody)) - $this->ContentType = "multipart/alternative"; - - $header = $this->create_header(); - if(!$body = $this->create_body()) - return false; - - // Seed randomizer - mt_srand(time()); - $msg_id = md5(uniqid(mt_rand())); - - $fp = fopen($queue_path . $msg_id . ".pqm", "wb"); - if(!$fp) - { - $this->error_handler(sprintf("Could not write to %s directory", $queue_path)); - return false; - } - - $message[] = sprintf("----START PQM HEADER----%s", $this->LE); - $message[] = sprintf("SendTime: %s%s", $send_time, $this->LE); - $message[] = sprintf("Mailer: %s%s", $this->Mailer, $this->LE); - - // Choose the mailer - if($this->Mailer == "sendmail") - { - $message[] = sprintf("Sendmail: %s%s", $this->Sendmail, $this->LE); - $message[] = sprintf("Sender: %s%s", $this->Sender, $this->LE); - } - elseif($this->Mailer == "mail") - { - $message[] = sprintf("Sender: %s%s", $this->Sender, $this->LE); - $message[] = sprintf("Subject: %s%s", $this->Subject, $this->LE); - $message[] = sprintf("to: %s%s", $this->addr_list($this->to), $this->LE); - } - elseif($this->Mailer == "smtp") - { - $message[] = sprintf("Host: %s%s", $this->Host, $this->LE); - $message[] = sprintf("Port: %d%s", $this->Port, $this->LE); - $message[] = sprintf("Helo: %s%s", $this->Helo, $this->LE); - $message[] = sprintf("Timeout: %d%s", $this->Timeout, $this->LE); - - if($this->SMTPAuth) - $auth_no = 1; - else - $auth_no = 0; - $message[] = sprintf("SMTPAuth: %d%s", $auth_no, $this->LE); - $message[] = sprintf("Username: %s%s", $this->Username, $this->LE); - $message[] = sprintf("Password: %s%s", $this->Password, $this->LE); - $message[] = sprintf("From: %s%s", $this->From, $this->LE); - - $message[] = sprintf("to: %s%s", $this->addr_list($this->to), $this->LE); - $message[] = sprintf("cc: %s%s", $this->addr_list($this->cc), $this->LE); - $message[] = sprintf("bcc: %s%s", $this->addr_list($this->bcc), $this->LE); - } - else - { - $this->error_handler(sprintf("%s mailer is not supported", $this->Mailer)); - return false; - } - - $message[] = sprintf("----END PQM HEADER----%s", $this->LE); // end of pqm header - $message[] = $header; - $message[] = $body; - - fwrite($fp, join("", $message)); - - return ($msg_id . ".pqm"); - } - - /** - * Sends mail using the $Sendmail program. Returns bool. - * @private - * @returns bool - */ - function sendmail_send($header, $body) { - if ($this->Sender != "") - $sendmail = sprintf("%s -oi -f %s -t", $this->Sendmail, $this->Sender); - else - $sendmail = sprintf("%s -oi -t", $this->Sendmail); - - if(!@$mail = popen($sendmail, "w")) - { - $this->error_handler(sprintf("Could not execute %s", $this->Sendmail)); - return false; - } - - fputs($mail, $header); - fputs($mail, $body); - - $result = pclose($mail) >> 8 & 0xFF; - if($result != 0) - { - $this->error_handler(sprintf("Could not execute %s", $this->Sendmail)); - return false; - } - - return true; - } - - /** - * Sends mail using the PHP mail() function. Returns bool. - * @private - * @returns bool - */ - function mail_send($header, $body) { - //$to = substr($this->addr_append("To", $this->to), 4, -2); - - // Cannot add Bcc's to the $to - $to = $this->to[0][0]; // no extra comma - for($i = 1; $i < count($this->to); $i++) - $to .= sprintf(",%s", $this->to[$i][0]); - - if ($this->Sender != "" && PHP_VERSION >= "4.0") - { - $old_from = ini_get("sendmail_from"); - ini_set("sendmail_from", $this->Sender); - } - - if ($this->Sender != "" && PHP_VERSION >= "4.0.5") - { - // The fifth parameter to mail is only available in PHP >= 4.0.5 - $params = sprintf("-oi -f %s", $this->Sender); - $rt = @mail($to, $this->Subject, $body, $header, $params); - } - else - { - $rt = @mail($to, $this->Subject, $body, $header); - } - - if (isset($old_from)) - ini_set("sendmail_from", $old_from); - - if(!$rt) - { - $this->error_handler("Could not instantiate mail()"); - return false; - } - - return true; - } - - /** - * Sends mail via SMTP using PhpSMTP (Author: - * Chris Ryan). Returns bool. Returns false if there is a - * bad MAIL FROM, RCPT, or DATA input. - * @private - * @returns bool - */ - function smtp_send($header, $body) { - // Include SMTP class code, but not twice - include_once($this->PluginDir . "class.smtp.php"); - - $smtp = new SMTP; - - $smtp->do_debug = $this->SMTPDebug; - - // Try to connect to all SMTP servers - $hosts = explode(";", $this->Host); - $index = 0; - $connection = false; - $smtp_from = ""; - $bad_rcpt = array(); - $e = ""; - - // Retry while there is no connection - while($index < count($hosts) && $connection == false) - { - if(strstr($hosts[$index], ":")) - list($host, $port) = explode(":", $hosts[$index]); - else - { - $host = $hosts[$index]; - $port = $this->Port; - } - - if($smtp->Connect($host, $port, $this->Timeout)) - $connection = true; - //printf("%s host could not connect
", $hosts[$index]); //debug only - $index++; - } - if(!$connection) - { - $this->error_handler("SMTP Error: could not connect to SMTP host server(s)"); - return false; - } - - // Must perform HELO before authentication - $smtp->Hello($this->Helo); - - // If user requests SMTP authentication - if($this->SMTPAuth) - { - if(!$smtp->Authenticate($this->Username, $this->Password)) - { - $this->error_handler("SMTP Error: Could not authenticate"); - return false; - } - } - - if ($this->Sender == "") - $smtp_from = $this->From; - else - $smtp_from = $this->Sender; - - if(!$smtp->Mail(sprintf("<%s>", $smtp_from))) - { - $e = sprintf("SMTP Error: From address [%s] failed", $smtp_from); - $this->error_handler($e); - return false; - } - - // Attempt to send attach all recipients - for($i = 0; $i < count($this->to); $i++) - { - if(!$smtp->Recipient(sprintf("<%s>", $this->to[$i][0]))) - $bad_rcpt[] = $this->to[$i][0]; - } - for($i = 0; $i < count($this->cc); $i++) - { - if(!$smtp->Recipient(sprintf("<%s>", $this->cc[$i][0]))) - $bad_rcpt[] = $this->cc[$i][0]; - } - for($i = 0; $i < count($this->bcc); $i++) - { - if(!$smtp->Recipient(sprintf("<%s>", $this->bcc[$i][0]))) - $bad_rcpt[] = $this->bcc[$i][0]; - } - - // Create error message - if(count($bad_rcpt) > 0) - { - for($i = 0; $i < count($bad_rcpt); $i++) - { - if($i != 0) - $e .= ", "; - $e .= $bad_rcpt[$i]; - } - $e = sprintf("SMTP Error: The following recipients failed [%s]", $e); - $this->error_handler($e); - - return false; - } - - - if(!$smtp->Data(sprintf("%s%s", $header, $body))) - { - $this->error_handler("SMTP Error: Data not accepted"); - return false; - } - $smtp->Quit(); - - return true; - } - - - ///////////////////////////////////////////////// - // MESSAGE CREATION METHODS - ///////////////////////////////////////////////// - - /** - * Creates recipient headers. Returns string. - * @private - * @returns string - */ - function addr_append($type, $addr) { - $addr_str = $type . ": "; - $addr_str .= $this->addr_format($addr[0]); - if(count($addr) > 1) - { - for($i = 1; $i < count($addr); $i++) - { - $addr_str .= sprintf(", %s", $this->addr_format($addr[$i])); - } - $addr_str .= $this->LE; - } - else - $addr_str .= $this->LE; - - return($addr_str); - } - - /** - * Creates a semicolon delimited list for use in pqm files. - * @private - * @returns string - */ - function addr_list($list_array) { - $addr_list = ""; - for($i = 0; $i < count($list_array); $i++) - { - if($i > 0) - $addr_list .= ";"; - $addr_list .= $list_array[$i][0]; - } - - return $addr_list; - } - - /** - * Formats an address correctly. - * @private - * @returns string - */ - function addr_format($addr) { - if(empty($addr[1])) - $formatted = $addr[0]; - else - $formatted = sprintf('"%s" <%s>', addslashes($addr[1]), $addr[0]); - - return $formatted; - } - - /** - * Wraps message for use with mailers that do not - * automatically perform wrapping and for quoted-printable. - * Original written by philippe. Returns string. - * @private - * @returns string - */ - function word_wrap($message, $length, $qp_mode = false) { - if ($qp_mode) - $soft_break = sprintf(" =%s", $this->LE); - else - $soft_break = $this->LE; - - $message = $this->fix_eol($message); - if (substr($message, -1) == $this->LE) - $message = substr($message, 0, -1); - - $line = explode($this->LE, $message); - $message = ""; - for ($i=0 ;$i < count($line); $i++) - { - $line_part = explode(" ", $line[$i]); - $buf = ""; - for ($e = 0; $e $length)) - { - $space_left = $length - strlen($buf) - 1; - if ($e != 0) - { - if ($space_left > 20) - { - $len = $space_left; - if (substr($word, $len - 1, 1) == "=") - $len--; - elseif (substr($word, $len - 2, 1) == "=") - $len -= 2; - $part = substr($word, 0, $len); - $word = substr($word, $len); - $buf .= " " . $part; - $message .= $buf . sprintf("=%s", $this->LE); - } - else - { - $message .= $buf . $soft_break; - } - $buf = ""; - } - while (strlen($word) > 0) - { - $len = $length; - if (substr($word, $len - 1, 1) == "=") - $len--; - elseif (substr($word, $len - 2, 1) == "=") - $len -= 2; - $part = substr($word, 0, $len); - $word = substr($word, $len); - - if (strlen($word) > 0) - $message .= $part . sprintf("=%s", $this->LE); - else - $buf = $part; - } - } - else - { - $buf_o = $buf; - if ($e == 0) - $buf .= $word; - else - $buf .= " " . $word; - if (strlen($buf) > $length and $buf_o != "") - { - $message .= $buf_o . $soft_break; - $buf = $word; - } - } - } - $message .= $buf . $this->LE; - } - - return ($message); - } - - /** - * Assembles message header. Returns a string if successful - * or false if unsuccessful. - * @private - * @returns string - */ - function create_header() { - $header = array(); - - // Set the boundaries - $uniq_id = md5(uniqid(time())); - $this->boundary[1] = "b1_" . $uniq_id; - $this->boundary[2] = "b2_" . $uniq_id; - - // To be created automatically by mail() - if(($this->Mailer != "mail") && (count($this->to) > 0)) - $header[] = $this->addr_append("To", $this->to); - - $header[] = sprintf("From: \"%s\" <%s>%s", addslashes($this->FromName), - trim($this->From), $this->LE); - if(count($this->cc) > 0) - $header[] = $this->addr_append("Cc", $this->cc); - - // sendmail and mail() extract Bcc from the header before sending - if((($this->Mailer == "sendmail") || ($this->Mailer == "mail")) && (count($this->bcc) > 0)) - $header[] = $this->addr_append("Bcc", $this->bcc); - - if(count($this->ReplyTo) > 0) - $header[] = $this->addr_append("Reply-to", $this->ReplyTo); - - // mail() sets the subject itself - if($this->Mailer != "mail") - $header[] = sprintf("Subject: %s%s", trim($this->Subject), $this->LE); - - $header[] = sprintf("X-Priority: %d%s", $this->Priority, $this->LE); - $header[] = sprintf("X-Mailer: phpmailer [version %s]%s", $this->Version, $this->LE); - $header[] = sprintf("Return-Path: %s%s", trim($this->From), $this->LE); - - if($this->ConfirmReadingTo != "") - $header[] = sprintf("Disposition-Notification-To: <%s>%s", - trim($this->ConfirmReadingTo), $this->LE); - - // Add custom headers - for($index = 0; $index < count($this->CustomHeader); $index++) - $header[] = sprintf("%s%s", $this->CustomHeader[$index], $this->LE); - - if($this->UseMSMailHeaders) - $header[] = $this->AddMSMailHeaders(); - - $header[] = sprintf("MIME-Version: 1.0%s", $this->LE); - - // Determine what type of message this is - if(count($this->attachment) < 1 && strlen($this->AltBody) < 1) - $this->message_type = "plain"; - else - { - if(count($this->attachment) > 0) - $this->message_type = "attachments"; - if(strlen($this->AltBody) > 0 && count($this->attachment) < 1) - $this->message_type = "alt"; - if(strlen($this->AltBody) > 0 && count($this->attachment) > 0) - $this->message_type = "alt_attachments"; - } - - switch($this->message_type) - { - case "plain": - $header[] = sprintf("Content-Transfer-Encoding: %s%s", - $this->Encoding, $this->LE); - $header[] = sprintf("Content-Type: %s; charset = \"%s\"", - $this->ContentType, $this->CharSet); - break; - case "attachments": - case "alt_attachments": - if($this->EmbeddedImageCount() > 0) - { - $header[] = sprintf("Content-Type: %s;%s\ttype=\"text/html\";%s\tboundary=\"%s\"%s", - "multipart/related", $this->LE, $this->LE, - $this->boundary[1], $this->LE); - } - else - { - $header[] = sprintf("Content-Type: %s;%s", - "multipart/mixed", $this->LE); - $header[] = sprintf("\tboundary=\"%s\"%s", $this->boundary[1], $this->LE); - } - break; - case "alt": - $header[] = sprintf("Content-Type: %s;%s", - "multipart/alternative", $this->LE); - $header[] = sprintf("\tboundary=\"%s\"%s", $this->boundary[1], $this->LE); - break; - } - - // No additional lines when using mail() function - if($this->Mailer != "mail") - $header[] = $this->LE.$this->LE; - - return(join("", $header)); - } - - /** - * Assembles the message body. Returns a string if successful - * or false if unsuccessful. - * @private - * @returns string - */ - function create_body() { - $body = array(); - - // wordwrap the message body if set - if($this->WordWrap > 0) - $this->Body = $this->word_wrap($this->Body, $this->WordWrap); - - switch($this->message_type) - { - case "alt": - // Return text of body - $bndry = new Boundary($this->boundary[1]); - $bndry->CharSet = $this->CharSet; - $bndry->Encoding = $this->Encoding; - $body[] = $bndry->GetSource(); - - $body[] = sprintf("%s%s", $this->AltBody, $this->LE.$this->LE); - - $bndry = new Boundary($this->boundary[1]); - $bndry->CharSet = $this->CharSet; - $bndry->ContentType = "text/html"; - $bndry->Encoding = $this->Encoding; - $body[] = $bndry->GetSource(); - - $body[] = sprintf("%s%s", $this->Body, $this->LE.$this->LE); - - // End the boundary - $body[] = sprintf("%s--%s--%s", $this->LE, - $this->boundary[1], $this->LE.$this->LE); - break; - case "plain": - $body[] = $this->Body; - break; - case "attachments": - $bndry = new Boundary($this->boundary[1]); - $bndry->CharSet = $this->CharSet; - $bndry->ContentType = $this->ContentType; - $bndry->Encoding = $this->Encoding; - $body[] = sprintf("%s%s%s%s", $bndry->GetSource(false), $this->LE, - $this->Body, $this->LE); - - if(!$body[] = $this->attach_all()) - return false; - break; - case "alt_attachments": - $body[] = sprintf("--%s%s", $this->boundary[1], $this->LE); - $body[] = sprintf("Content-Type: %s;%s" . - "\tboundary=\"%s\"%s", - "multipart/alternative", $this->LE, - $this->boundary[2], $this->LE.$this->LE); - - // Create text body - $bndry = new Boundary($this->boundary[2]); - $bndry->CharSet = $this->CharSet; - $bndry->ContentType = "text/plain"; - $bndry->Encoding = $this->Encoding; - $body[] = $bndry->GetSource() . $this->LE; - - $body[] = sprintf("%s%s", $this->AltBody, $this->LE.$this->LE); - - // Create the HTML body - $bndry = new Boundary($this->boundary[2]); - $bndry->CharSet = $this->CharSet; - $bndry->ContentType = "text/html"; - $bndry->Encoding = $this->Encoding; - $body[] = $bndry->GetSource() . $this->LE; - - $body[] = sprintf("%s%s", $this->Body, $this->LE.$this->LE); - - $body[] = sprintf("%s--%s--%s", $this->LE, - $this->boundary[2], $this->LE.$this->LE); - - if(!$body[] = $this->attach_all()) - return false; - break; - } - // Add the encode string code here - $sBody = join("", $body); - $sBody = $this->encode_string($sBody, $this->Encoding); - - return $sBody; - } - - - ///////////////////////////////////////////////// - // ATTACHMENT METHODS - ///////////////////////////////////////////////// - - /** - * Adds an attachment from a path on the filesystem. - * Checks if attachment is valid and then adds - * the attachment to the list. - * Returns false if the file could not be found - * or accessed. - * @public - * @returns bool - */ - function AddAttachment($path, $name = "", $encoding = "base64", $type = "application/octet-stream") { - if(!@is_file($path)) - { - $this->error_handler(sprintf("Could not access [%s] file", $path)); - return false; - } - - $filename = basename($path); - if($name == "") - $name = $filename; - - // Append to $attachment array - $cur = count($this->attachment); - $this->attachment[$cur][0] = $path; - $this->attachment[$cur][1] = $filename; - $this->attachment[$cur][2] = $name; - $this->attachment[$cur][3] = $encoding; - $this->attachment[$cur][4] = $type; - $this->attachment[$cur][5] = false; // isStringAttachment - $this->attachment[$cur][6] = "attachment"; - $this->attachment[$cur][7] = 0; - - return true; - } - - /** - * Attaches all fs, string, and binary attachments to the message. - * Returns a string if successful or false if unsuccessful. - * @private - * @returns string - */ - function attach_all() { - // Return text of body - $mime = array(); - - // Add all attachments - for($i = 0; $i < count($this->attachment); $i++) - { - // Check for string attachment - $isString = $this->attachment[$i][5]; - if ($isString) - { - $string = $this->attachment[$i][0]; - } - else - { - $path = $this->attachment[$i][0]; - } - $filename = $this->attachment[$i][1]; - $name = $this->attachment[$i][2]; - $encoding = $this->attachment[$i][3]; - $type = $this->attachment[$i][4]; - $disposition = $this->attachment[$i][6]; - $cid = $this->attachment[$i][7]; - - $mime[] = sprintf("--%s%s", $this->boundary[1], $this->LE); - $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $name, $this->LE); - $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); - - if($disposition == "inline") - $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); - else - $mime[] = sprintf("Content-ID: <%s>%s", $name, $this->LE); - - $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", - $disposition, $name, $this->LE.$this->LE); - - // Encode as string attachment - if($isString) - { - if(!$mime[] = sprintf("%s%s", $this->encode_string($string, $encoding), - $this->LE.$this->LE)) - return false; - } - else - { - if(!$mime[] = sprintf("%s%s", $this->encode_file($path, $encoding), - $this->LE.$this->LE)) - return false; - - $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE); - - } - } - - return(join("", $mime)); - } - - /** - * Encodes attachment in requested format. Returns a - * string if successful or false if unsuccessful. - * @private - * @returns string - */ - function encode_file ($path, $encoding = "base64") { - if(!@$fd = fopen($path, "rb")) - { - $this->error_handler(sprintf("File Error: Could not open file %s", $path)); - return false; - } - $file = fread($fd, filesize($path)); - $encoded = $this->encode_string($file, $encoding); - fclose($fd); - - return($encoded); - } - - /** - * Encodes string to requested format. Returns a - * string if successful or false if unsuccessful. - * @private - * @returns string - */ - function encode_string ($str, $encoding = "base64") { - switch(strtolower($encoding)) { - case "base64": - // chunk_split is found in PHP >= 3.0.6 - $encoded = chunk_split(base64_encode($str)); - break; - - case "7bit": - case "8bit": - $encoded = $this->fix_eol($str); - if (substr($encoded, -2) != $this->LE) - $encoded .= $this->LE; - break; - - case "binary": - $encoded = $str; - break; - - case "quoted-printable": - $encoded = $this->encode_qp($str); - break; - - default: - $this->error_handler(sprintf("Unknown encoding: %s", $encoding)); - return false; - } - return($encoded); - } - - /** - * Encode string to quoted-printable. Returns a string. - * @private - * @returns string - */ - function encode_qp ($str) { - $encoded = $this->fix_eol($str); - if (substr($encoded, -2) != $this->LE) - $encoded .= $this->LE; - - // Replace every high ascii, control and = characters - $encoded = preg_replace("/([\001-\010\013\014\016-\037\075\177-\377])/e", - "'='.sprintf('%02X', ord('\\1'))", $encoded); - // Replace every spaces and tabs when it's the last character on a line - $encoded = preg_replace("/([\011\040])".$this->LE."/e", - "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded); - - // Maximum line length of 76 characters before CRLF (74 + space + '=') - $encoded = $this->word_wrap($encoded, 74, true); - - return $encoded; - } - - /** - * Adds a string or binary attachment (non-filesystem) to the list. - * This method can be used to attach ascii or binary data, - * such as a BLOB record from a database. - * @public - * @returns void - */ - function AddStringAttachment($string, $filename, $encoding = "base64", $type = "application/octet-stream") { - // Append to $attachment array - $cur = count($this->attachment); - $this->attachment[$cur][0] = $string; - $this->attachment[$cur][1] = $filename; - $this->attachment[$cur][2] = $filename; - $this->attachment[$cur][3] = $encoding; - $this->attachment[$cur][4] = $type; - $this->attachment[$cur][5] = true; // isString - $this->attachment[$cur][6] = "attachment"; - $this->attachment[$cur][7] = 0; - } - - /** - * Adds an embedded attachment. This can include images, sounds, and - * just about any other document. - * @param cid this is the Content Id of the attachment. Use this to identify - * the Id for accessing the image in an HTML form. - * @public - * @returns bool - */ - function AddEmbeddedImage($path, $cid, $name = "", $encoding = "base64", $type = "application/octet-stream") { - - if(!@is_file($path)) - { - $this->error_handler(sprintf("Could not access [%s] file", $path)); - return false; - } - - $filename = basename($path); - if($name == "") - $name = $filename; - - // Append to $attachment array - $cur = count($this->attachment); - $this->attachment[$cur][0] = $path; - $this->attachment[$cur][1] = $filename; - $this->attachment[$cur][2] = $name; - $this->attachment[$cur][3] = $encoding; - $this->attachment[$cur][4] = $type; - $this->attachment[$cur][5] = false; // isStringAttachment - $this->attachment[$cur][6] = "inline"; - $this->attachment[$cur][7] = $cid; - - return true; - } - - /** - * Returns the number of embedded images in an email. - * @private - * @returns int - */ - function EmbeddedImageCount() { - $ret = 0; - for($i = 0; $i < count($this->attachment); $i++) - { - if($this->attachment[$i][6] == "inline") - $ret++; - } - - return $ret; - } - - ///////////////////////////////////////////////// - // MESSAGE RESET METHODS - ///////////////////////////////////////////////// - - /** - * Clears all recipients assigned in the TO array. Returns void. - * @public - * @returns void - */ - function ClearAddresses() { - $this->to = array(); - } - - /** - * Clears all recipients assigned in the CC array. Returns void. - * @public - * @returns void - */ - function ClearCCs() { - $this->cc = array(); - } - - /** - * Clears all recipients assigned in the BCC array. Returns void. - * @public - * @returns void - */ - function ClearBCCs() { - $this->bcc = array(); - } - - /** - * Clears all recipients assigned in the ReplyTo array. Returns void. - * @public - * @returns void - */ - function ClearReplyTos() { - $this->ReplyTo = array(); - } - - /** - * Clears all recipients assigned in the TO, CC and BCC - * array. Returns void. - * @public - * @returns void - */ - function ClearAllRecipients() { - $this->to = array(); - $this->cc = array(); - $this->bcc = array(); - } - - /** - * Clears all previously set filesystem, string, and binary - * attachments. Returns void. - * @public - * @returns void - */ - function ClearAttachments() { - $this->attachment = array(); - } - - /** - * Clears all custom headers. Returns void. - * @public - * @returns void - */ - function ClearCustomHeaders() { - $this->CustomHeader = array(); - } - - - ///////////////////////////////////////////////// - // MISCELLANEOUS METHODS - ///////////////////////////////////////////////// - - /** - * Adds the error message to the error container. - * Returns void. - * @private - * @returns void - */ - function error_handler($msg) { - $this->ErrorInfo = $msg; - } - - /** - * Returns the proper RFC 822 formatted date. Returns string. - * @private - * @returns string - */ - function rfc_date() { - $tz = date("Z"); - $tzs = ($tz < 0) ? "-" : "+"; - $tz = abs($tz); - $tz = ($tz/3600)*100 + ($tz%3600)/60; - $date = sprintf("%s %s%04d", date("D, j M Y H:i:s"), $tzs, $tz); - return $date; - } - - /** - * Returns received header for message tracing. Returns string. - * @private - * @returns string - */ - function received() { - // Check for vars because they might not exist. Possibly - // write a small retrieval function (that mailer can use too!) - - $str = sprintf("Received: from phpmailer ([%s]) by %s " . - "with HTTP;%s\t %s%s", - $this->get_server_var("REMOTE_ADDR"), - $this->get_server_var("SERVER_NAME"), - $this->LE, - $this->rfc_date(), - $this->LE); - - return $str; - } - - /** - * Returns the appropriate server variable. Should work with both - * PHP 4.1.0+ as well as older versions. Returns an empty string - * if nothing is found. - * @private - * @returns mixed - */ - function get_server_var($varName) { - global $HTTP_SERVER_VARS; - global $HTTP_ENV_VARS; - - if(!isset($_SERVER)) - { - $_SERVER = $HTTP_SERVER_VARS; - if(!isset($_SERVER["REMOTE_ADDR"])) - $_SERVER = $HTTP_ENV_VARS; // must be Apache - } - - if(isset($_SERVER[$varName])) - return $_SERVER[$varName]; - else - return ""; - } - - /** - * Changes every end of line from CR or LF to CRLF. Returns string. - * @private - * @returns string - */ - function fix_eol($str) { - $str = str_replace("\r\n", "\n", $str); - $str = str_replace("\r", "\n", $str); - $str = str_replace("\n", $this->LE, $str); - return $str; - } - - /** - * Adds a custom header. Returns void. - * @public - * @returns void - */ - function AddCustomHeader($custom_header) { - $this->CustomHeader[] = $custom_header; - } - - /** - * Adds all the Microsoft message headers. Returns string. - * @private - * @returns string - */ - function AddMSMailHeaders() { - $MSHeader = ""; - if($this->Priority == 1) - $MSPriority = "High"; - elseif($this->Priority == 5) - $MSPriority = "Low"; - else - $MSPriority = "Medium"; - - $MSHeader .= sprintf("X-MSMail-Priority: %s%s", $MSPriority, $this->LE); - $MSHeader .= sprintf("Importance: %s%s", $MSPriority, $this->LE); - - return($MSHeader); - } - -} - - -/** - * Boundary - MIME message boundary class - * @author Brent R. Matzelle - */ -class Boundary -{ - /** - * Sets the boundary ID. - * @private - * @type string - */ - var $ID = 0; - - /** - * Sets the boundary Content Type. - * @public - * @type string - */ - var $ContentType = "text/plain"; - - /** - * Sets the Encoding. - * @public - * @type string - */ - var $Encoding = ""; - - /** - * Sets an attachment disposition. - * @public - * @type string - */ - var $Disposition = ""; - - /** - * Sets an attachment file name. - * @public - * @type string - */ - var $FileName = ""; - - /** - * Sets the Char set. - * @public - * @type string - */ - var $CharSet = ""; - - /** - * Sets the line endings of the message. Default is "\n"; - * @public - * @type string - */ - var $LE = "\n"; - - /** - * Main constructor. - */ - function Boundary($boundary_id) { - $this->ID = $boundary_id; - } - - /** - * Returns the source of the boundary. - * @public - * @returns string - */ - function GetSource($bLineEnding = true) { - $ret = array(); - $mime[] = sprintf("--%s%s", $this->ID, $this->LE); - $mime[] = sprintf("Content-Type: %s; charset = \"%s\"%s", - $this->ContentType, $this->CharSet, $this->LE); - //$mime[] = sprintf("Content-Transfer-Encoding: %s%s", $this->Encoding, - // $this->LE); - - if(strlen($this->Disposition) > 0) - { - $mime[] = sprintf("Content-Disposition: %s;"); - if(strlen($this->FileName) > 0) - $mime[] = sprinf("filename=\"%s\"", $this->$this->FileName); - } - - if($bLineEnding) - $mime[] = $this->LE; - - return join("", $mime); - } -} - -?> \ No newline at end of file