diff --git a/PHPMailer/README.md b/PHPMailer/README.md new file mode 100644 index 0000000..671141d --- /dev/null +++ b/PHPMailer/README.md @@ -0,0 +1,134 @@ +# PHPMailer - A full-featured email creation and transfer class for PHP + +Build status: [![Build Status](https://travis-ci.org/Synchro/PHPMailer.png)](https://travis-ci.org/Synchro/PHPMailer) + +## Class Features + +- Probably the world's most popular code for sending email from PHP! +- Used by many open-source projects: Drupal, SugarCRM, Yii, Joomla! and many more +- Integrated SMTP support - send without a local mail server +- Send emails with multiple TOs, CCs, BCCs and REPLY-TOs +- Multipart/alternative emails for mail clients that do not read HTML email +- Support for 8bit, base64, binary, and quoted-printable encoding +- SMTP authentication with LOGIN, PLAIN, NTLM and CRAM-MD5 mechanisms +- Native language support +- Compatible with PHP 5.0 and later +- Much more! + +## Why you might need it + +Many PHP developers utilize email in their code. The only PHP function that supports this is the mail() function. However, it does not provide any assistance for making use of popular features such as HTML-based emails and attachments. + +Formatting email correctly is surprisingly difficult. There are myriad overlapping RFCs, requiring tight adherence to horribly complicated formatting and encoding rules - the vast majority of code that you'll find online that uses the mail() function directly is just plain wrong! +*Please* don't be tempted to do it yourself - if you don't use PHPMailer, there are many other excellent libraries that you should look at before rolling your own - try SwiftMailer, Zend_Mail, eZcomponents etc. + +The PHP mail() function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD and OS X platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP implementation allows email sending on Windows platforms without a local mail server. + +## License + +This software is licenced under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html). Please read LICENSE for information on the +software availability and distribution. + +## Installation + +PHPMailer is available via [Composer/Packagist](https://packagist.org/packages/phpmailer/phpmailer). Alternatively, just copy the contents of the PHPMailer folder into somewhere that's in your PHP `include_path` setting. If you don't speak git or just want a tarball, click the 'zip' button at the top of the page in GitHub. + + +## A Simple Example + +```php +IsSMTP(); // Set mailer to use SMTP +$mail->Host = 'smtp1.example.com;smtp2.example.com'; // Specify main and backup server +$mail->SMTPAuth = true; // Enable SMTP authentication +$mail->Username = 'jswan'; // SMTP username +$mail->Password = 'secret'; // SMTP password +$mail->SMTPSecure = 'tls'; // Enable encryption, 'ssl' also accepted + +$mail->From = 'from@example.com'; +$mail->FromName = 'Mailer'; +$mail->AddAddress('josh@example.net', 'Josh Adams'); // Add a recipient +$mail->AddAddress('ellen@example.com'); // Name is optional +$mail->AddReplyTo('info@example.com', 'Information'); +$mail->AddCC('cc@example.com'); +$mail->AddBCC('bcc@example.com'); + +$mail->WordWrap = 50; // Set word wrap to 50 characters +$mail->AddAttachment('/var/tmp/file.tar.gz'); // Add attachments +$mail->AddAttachment('/tmp/image.jpg', 'new.jpg'); // Optional name +$mail->IsHTML(true); // Set email format to HTML + +$mail->Subject = 'Here is the subject'; +$mail->Body = 'This is the HTML message body in bold!'; +$mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + +if(!$mail->Send()) { + echo 'Message could not be sent.'; + echo 'Mailer Error: ' . $mail->ErrorInfo; + exit; +} + +echo 'Message has been sent'; +``` + +You'll find plenty more to play with in the `examples` folder. + +That's it. You should now be ready to use PHPMailer! + +## Localization +PHPMailer defaults to English, but in the `languages` folder you'll find numerous translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: + +```php +// To load the French version +$mail->SetLanguage('fr', '/optional/path/to/language/directory/'); +``` + +## Documentation + +You'll find some basic user-level docs in the docs folder, and you can generate complete API-level documentation using the `generatedocs.sh` shell script in the docs folder, though you'll need to install [PHPDocumentor](http://www.phpdoc.org) first. + +## Tests + +You'll find a PHPUnit test script in the `test` folder. + +Build status: [![Build Status](https://travis-ci.org/PHPMailer/PHPMailer.png)](https://travis-ci.org/PHPMailer/PHPMailer) + +If this isn't passing, is there something you can do to help? + +## Contributing + +Please submit bug reports, suggestions and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues). + +We're particularly interested in fixing edge-cases, expanding test coverage and updating translations. + +With the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone: + +git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git + +Please *don't* use the SourceForge or Google Code projects any more. + +## Changelog + +See changelog.md + +## History +- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/). +- Marcus Bointon (coolbru on SF) and Andy Prevost (codeworxtech) took over the project in 2004. +- Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski. +- Marcus created his fork on [GitHub](https://github.com/Synchro/PHPMailer). +- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer. +- PHPMailer moves to the [PHPMailer organisation](https://github.com/PHPMailer) on GitHub. + +### What's changed since moving from SourceForge? +- Official successor to the SourceForge and Google Code projects. +- Test suite. +- Continuous integration with Travis-CI. +- Composer support. +- Rolling releases. +- Additional languages and language strings. +- CRAM-MD5 authentication support. +- Preserves full repo history of authors, commits and branches from the original SourceForge project. diff --git a/PHPMailer/class.phpmailer.php b/PHPMailer/class.phpmailer.php index b3266a8..543fd29 100644 --- a/PHPMailer/class.phpmailer.php +++ b/PHPMailer/class.phpmailer.php @@ -2,12 +2,13 @@ /*~ class.phpmailer.php .---------------------------------------------------------------------------. | Software: PHPMailer - PHP email class | -| Version: 5.2.2 | -| Site: https://code.google.com/a/apache-extras.org/p/phpmailer/ | +| Version: 5.2.6 | +| Site: https://github.com/PHPMailer/PHPMailer/ | | ------------------------------------------------------------------------- | -| Admin: Jim Jagielski (project admininistrator) | +| Admins: Marcus Bointon | +| Admins: Jim Jagielski | | Authors: Andy Prevost (codeworxtech) codeworxtech@users.sourceforge.net | -| : Marcus Bointon (coolbru) coolbru@users.sourceforge.net | +| : Marcus Bointon (coolbru) phpmailer@synchromedia.co.uk | | : Jim Jagielski (jimjag) jimjag@gmail.com | | Founder: Brent R. Matzelle (original founder) | | Copyright (c) 2010-2012, Jim Jagielski. All Rights Reserved. | @@ -34,7 +35,9 @@ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ -if (version_compare(PHP_VERSION, '5.0.0', '<') ) exit("Sorry, this version of PHPMailer will only run on PHP version 5 or greater!\n"); +if (version_compare(PHP_VERSION, '5.0.0', '<') ) { + exit("Sorry, PHPMailer will only run on PHP version 5 or greater!\n"); +} /** * PHP email creation and transport class @@ -90,8 +93,8 @@ class PHPMailer { public $FromName = 'Root User'; /** - * Sets the Sender email (Return-Path) of the message. If not empty, - * will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. + * Sets the Sender email (Return-Path) of the message. + * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. * @var string */ public $Sender = ''; @@ -110,21 +113,31 @@ class PHPMailer { public $Subject = ''; /** - * Sets the Body of the message. This can be either an HTML or text body. - * If HTML then run IsHTML(true). + * An HTML or plain text message body. + * If HTML then call IsHTML(true). * @var string */ public $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. + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. * @var string */ public $AltBody = ''; + /** + * An iCal message part body + * Only supported in simple alt or alt_inline message types + * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator + * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @link http://kigkonsult.se/iCalcreator/ + * @var string + */ + public $Ical = ''; + /** * Stores the complete compiled MIME message body. * @var string @@ -143,7 +156,7 @@ class PHPMailer { * Stores the extra header list which CreateHeader() doesn't fold in * @var string * @access protected - */ + */ protected $mailHeader = ''; /** @@ -171,7 +184,7 @@ class PHPMailer { * @var boolean */ public $UseSendmailOptions = true; - + /** * Path to PHPMailer plugins. Useful if the SMTP class * is in a different directory than the PHP include path. @@ -260,11 +273,11 @@ class PHPMailer { public $Password = ''; /** - * Sets SMTP auth type. Options are LOGIN | PLAIN | NTLM (default LOGIN) + * Sets SMTP auth type. Options are LOGIN | PLAIN | NTLM | CRAM-MD5 (default LOGIN) * @var string */ public $AuthType = ''; - + /** * Sets SMTP realm. * @var string @@ -312,13 +325,26 @@ class PHPMailer { */ public $SingleTo = false; - /** + /** + * Should we generate VERP addresses when sending via SMTP? + * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path + * @var bool + */ + public $do_verp = false; + + /** * If SingleTo is true, this provides the array to hold the email addresses * @var bool */ public $SingleToArray = array(); - /** + /** + * Should we allow sending messages with empty body? + * @var bool + */ + public $AllowEmpty = false; + + /** * Provides the ability to change the generic line ending * NOTE: The default remains '\n'. We force CRLF where we KNOW * it must be used via self::CRLF @@ -390,7 +416,7 @@ class PHPMailer { * Sets the PHPMailer Version number * @var string */ - public $Version = '5.2.2'; + public $Version = '5.2.6'; /** * What to use in the X-Mailer header @@ -491,7 +517,7 @@ class PHPMailer { const STOP_CONTINUE = 1; // message?, likely ok to continue processing const STOP_CRITICAL = 2; // message, plus full stop, critical error reached const CRLF = "\r\n"; // SMTP RFC specified EOL - + ///////////////////////////////////////////////// // METHODS, VARIABLES ///////////////////////////////////////////////// @@ -522,10 +548,18 @@ private function mail_passthru($to, $subject, $body, $header, $params) { * Outputs debugging info via user-defined method * @param string $str */ - private function edebug($str) { - if ($this->Debugoutput == "error_log") { + protected function edebug($str) { + switch ($this->Debugoutput) { + case 'error_log': error_log($str); - } else { + 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; } } @@ -538,6 +572,15 @@ 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 @@ -554,7 +597,6 @@ public function IsHTML($ishtml = true) { /** * Sets Mailer to send message using SMTP. * @return void - * @deprecated */ public function IsSMTP() { $this->Mailer = 'smtp'; @@ -563,7 +605,6 @@ public function IsSMTP() { /** * Sets Mailer to send message using PHP mail() function. * @return void - * @deprecated */ public function IsMail() { $this->Mailer = 'mail'; @@ -572,7 +613,6 @@ public function IsMail() { /** * Sets Mailer to send message using the $Sendmail program. * @return void - * @deprecated */ public function IsSendmail() { if (!stristr(ini_get('sendmail_path'), 'sendmail')) { @@ -584,7 +624,6 @@ public function IsSendmail() { /** * Sets Mailer to send message using the qmail MTA. * @return void - * @deprecated */ public function IsQmail() { if (stristr(ini_get('sendmail_path'), 'qmail')) { @@ -687,15 +726,15 @@ protected function AddAnAddress($kind, $address, $name = '') { return false; } -/** - * Set the From and FromName properties - * @param string $address - * @param string $name - * @param int $auto Also set Reply-To and Sender + /** + * 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 = 1) { + * @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)) { @@ -711,9 +750,6 @@ public function SetFrom($address, $name = '', $auto = 1) { $this->From = $address; $this->FromName = $name; if ($auto) { - if (empty($this->ReplyTo)) { - $this->AddAnAddress('Reply-To', $address, $name); - } if (empty($this->Sender)) { $this->Sender = $address; } @@ -735,7 +771,18 @@ public function SetFrom($address, $name = '', $auto = 1) { * @access public */ public static function ValidateAddress($address) { - return preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[ ])+|(?>[ ]*\x0D\x0A)?[ ]+)?)(\((?>(?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][:\]]){7,})((?6)(?>:(?6)){0,5})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f0-9]:){5,})(?8)?::(?>((?6)(?>:(?6)){0,3}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $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); + } } ///////////////////////////////////////////////// @@ -782,8 +829,8 @@ public function PreSend() { $this->error_count = 0; // reset errors $this->SetMessageType(); - //Refuse to send an empty message - if (empty($this->Body)) { + //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); } @@ -799,14 +846,11 @@ public function PreSend() { $this->mailHeader .= $this->HeaderLine("To", "undisclosed-recipients:;"); } $this->mailHeader .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader(trim($this->Subject)))); - // if(count($this->cc) > 0) { - // $this->mailHeader .= $this->AddrAppend("Cc", $this->cc); - // } } // 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->EncodeHeader($this->SecureHeader($this->Subject)), $this->MIMEBody); + $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; } @@ -903,7 +947,7 @@ protected function SendmailSend($header, $body) { * Sends mail using the PHP mail() function. * @param string $header The message headers * @param string $body The message body - * @throws phpmailerException + * @throws phpmailerException * @access protected * @return bool */ @@ -915,9 +959,9 @@ protected function MailSend($header, $body) { $to = implode(', ', $toArr); if (empty($this->Sender)) { - $params = "-oi "; + $params = " "; } else { - $params = sprintf("-oi -f%s", $this->Sender); + $params = sprintf("-f%s", $this->Sender); } if ($this->Sender != '' and !ini_get('safe_mode')) { $old_from = ini_get('sendmail_from'); @@ -965,7 +1009,8 @@ protected function SmtpSend($header, $body) { } $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; if(!$this->smtp->Mail($smtp_from)) { - throw new phpmailerException($this->Lang('from_failed') . $smtp_from, self::STOP_CRITICAL); + $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 @@ -1026,71 +1071,76 @@ protected function SmtpSend($header, $body) { /** * 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() { + 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; - $hosts = explode(';', $this->Host); + $this->smtp->Debugoutput = $this->Debugoutput; + $this->smtp->do_verp = $this->do_verp; $index = 0; - $connection = $this->smtp->Connected(); - - // Retry while there is no connection - try { - while($index < count($hosts) && !$connection) { - $hostinfo = array(); - if (preg_match('/^(.+):([0-9]+)$/', $hosts[$index], $hostinfo)) { - $host = $hostinfo[1]; - $port = $hostinfo[2]; - } else { - $host = $hosts[$index]; - $port = $this->Port; - } - - $tls = ($this->SMTPSecure == 'tls'); - $ssl = ($this->SMTPSecure == 'ssl'); - - if ($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout)) { - - $hello = ($this->Helo != '' ? $this->Helo : $this->ServerHostname()); + $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('tls')); + throw new phpmailerException($this->Lang('connect_host')); } - //We must resend HELO after tls negotiation $this->smtp->Hello($hello); } - - $connection = true; if ($this->SMTPAuth) { - if (!$this->smtp->Authenticate($this->Username, $this->Password, $this->AuthType, - $this->Realm, $this->Workstation)) { + 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(); } - $index++; - if (!$connection) { - throw new phpmailerException($this->Lang('connect_host')); - } - } - } catch (phpmailerException $e) { - $this->smtp->Reset(); - if ($this->exceptions) { - throw $e; } } - return true; + //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; } /** @@ -1107,13 +1157,13 @@ public function SmtpClose() { } /** - * 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 + * 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 - */ + * @access public + */ function SetLanguage($langcode = 'en', $lang_path = 'language/') { //Define full set of translatable strings $PHPMAILER_LANG = array( @@ -1242,6 +1292,9 @@ public function WrapText($message, $length, $qp_mode = false) { $buf = ''; } while (strlen($word) > 0) { + if ($length <= 0) { + break; + } $len = $length; if ($is_utf8) { $len = $this->UTF8CharBoundary($word, $len); @@ -1361,11 +1414,11 @@ public function CreateHeader() { } if ($this->ReturnPath) { - $result .= $this->HeaderLine('Return-Path', trim($this->ReturnPath)); + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->ReturnPath).'>'); } elseif ($this->Sender == '') { - $result .= $this->HeaderLine('Return-Path', trim($this->From)); + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->From).'>'); } else { - $result .= $this->HeaderLine('Return-Path', trim($this->Sender)); + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->Sender).'>'); } // To be created automatically by mail() @@ -1414,7 +1467,7 @@ public function CreateHeader() { } $result .= $this->HeaderLine('X-Priority', $this->Priority); if ($this->XMailer == '') { - $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (http://code.google.com/a/apache-extras.org/p/phpmailer/)'); + $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (https://github.com/PHPMailer/PHPMailer/)'); } else { $myXmailer = trim($this->XMailer); if ($myXmailer) { @@ -1448,26 +1501,29 @@ public function GetMailMIME() { switch($this->message_type) { case 'inline': $result .= $this->HeaderLine('Content-Type', 'multipart/related;'); - $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + $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] . '"'); + $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] . '"'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1].'"'); break; default: // Catches case 'plain': and case '': - $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding); $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; @@ -1506,25 +1562,25 @@ public function CreateBody() { $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]); + $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]); + $body .= $this->AttachAll('attachment', $this->boundary[1]); break; case 'inline_attach': - $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->TextLine('--' . $this->boundary[1]); $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); - $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $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->AttachAll('inline', $this->boundary[2]); $body .= $this->LE; - $body .= $this->AttachAll("attachment", $this->boundary[1]); + $body .= $this->AttachAll('attachment', $this->boundary[1]); break; case 'alt': $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); @@ -1533,27 +1589,32 @@ public function CreateBody() { $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->TextLine('--' . $this->boundary[1]); $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); - $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $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->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->TextLine('--' . $this->boundary[1]); $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); - $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $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); @@ -1563,28 +1624,28 @@ public function CreateBody() { $body .= $this->LE.$this->LE; $body .= $this->EndBoundary($this->boundary[2]); $body .= $this->LE; - $body .= $this->AttachAll("attachment", $this->boundary[1]); + $body .= $this->AttachAll('attachment', $this->boundary[1]); break; case 'alt_inline_attach': - $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->TextLine('--' . $this->boundary[1]); $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); - $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $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->TextLine('--' . $this->boundary[2]); $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); - $body .= $this->TextLine("\tboundary=\"" . $this->boundary[3] . '"'); + $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->AttachAll('inline', $this->boundary[3]); $body .= $this->LE; $body .= $this->EndBoundary($this->boundary[2]); $body .= $this->LE; - $body .= $this->AttachAll("attachment", $this->boundary[1]); + $body .= $this->AttachAll('attachment', $this->boundary[1]); break; default: // catch case 'plain' and case '' @@ -1596,17 +1657,20 @@ public function CreateBody() { $body = ''; } elseif ($this->sign_key_file) { try { - $file = tempnam('', 'mail'); + 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("", "signed"); - if (@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), NULL)) { + $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()); + throw new phpmailerException($this->Lang('signing').openssl_error_string()); } } catch (phpmailerException $e) { $body = ''; @@ -1615,7 +1679,6 @@ public function CreateBody() { } } } - return $body; } @@ -1673,7 +1736,7 @@ protected function SetMessageType() { } /** - * Returns a formatted header line. + * Returns a formatted header line. * @access public * @param string $name * @param string $value @@ -1708,11 +1771,17 @@ public function TextLine($value) { * @throws phpmailerException * @return bool */ - public function AddAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + 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; @@ -1800,7 +1869,13 @@ protected function AttachAll($disposition_type, $boundary) { $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); } - $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$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) { @@ -1839,17 +1914,12 @@ protected function EncodeFile($path, $encoding = 'base64') { if (!is_readable($path)) { throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); } - // if (!function_exists('get_magic_quotes')) { - // function get_magic_quotes() { - // return false; - // } - // } $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); + ini_set('magic_quotes_runtime', 0); } } $file_buffer = file_get_contents($path); @@ -1858,7 +1928,7 @@ protected function EncodeFile($path, $encoding = 'base64') { if (version_compare(PHP_VERSION, '5.3.0', '<')) { set_magic_quotes_runtime($magic_quotes); } else { - ini_set('magic_quotes_runtime', $magic_quotes); + ini_set('magic_quotes_runtime', $magic_quotes); } } return $file_buffer; @@ -1934,13 +2004,13 @@ public function EncodeHeader($str, $position = 'text') { break; } - if ($x == 0) { + 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 (strlen($str)/3 < $x) { + 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 @@ -2023,87 +2093,34 @@ public function Base64EncodeWrapMB($str, $lf=null) { } /** - * Encode string to quoted-printable. - * Only uses standard PHP, slow, but will always work - * @access public - * @param string $input - * @param integer $line_max Number of chars allowed on a line before wrapping - * @param bool $space_conv - * @internal param string $string the text to encode - * @return string - */ - public function EncodeQPphp( $input = '', $line_max = 76, $space_conv = false) { - $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'); - $lines = preg_split('/(?:\r\n|\r|\n)/', $input); - $eol = "\r\n"; - $escape = '='; - $output = ''; - while( list(, $line) = each($lines) ) { - $linlen = strlen($line); - $newline = ''; - for($i = 0; $i < $linlen; $i++) { - $c = substr( $line, $i, 1 ); - $dec = ord( $c ); - if ( ( $i == 0 ) && ( $dec == 46 ) ) { // convert first point in the line into =2E - $c = '=2E'; - } - if ( $dec == 32 ) { - if ( $i == ( $linlen - 1 ) ) { // convert space at eol only - $c = '=20'; - } else if ( $space_conv ) { - $c = '=20'; - } - } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required - $h2 = (integer)floor($dec/16); - $h1 = (integer)floor($dec%16); - $c = $escape.$hex[$h2].$hex[$h1]; - } - if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted - $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay - $newline = ''; - // check if newline first character will be point or not - if ( $dec == 46 ) { - $c = '=2E'; - } - } - $newline .= $c; - } // end of for - $output .= $newline.$eol; - } // end of while - return $output; - } - - /** - * Encode string to RFC2045 (6.7) quoted-printable format - * Uses a PHP5 stream filter to do the encoding about 64x faster than the old version - * Also results in same content as you started with after decoding - * @see EncodeQPphp() - * @access public - * @param string $string the text to encode - * @param integer $line_max Number of chars allowed on a line before wrapping - * @param boolean $space_conv Dummy param for compatibility with existing EncodeQP function - * @return string - * @author Marcus Bointon - */ - public function EncodeQP($string, $line_max = 76, $space_conv = false) { + * 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); } - $filters = stream_get_filters(); - if (!in_array('convert.*', $filters)) { //Got convert stream filter? - return $this->EncodeQPphp($string, $line_max, $space_conv); //Fall back to old implementation - } - $fp = fopen('php://temp/', 'r+'); - $string = preg_replace('/\r\n?/', $this->LE, $string); //Normalise line breaks - $params = array('line-length' => $line_max, 'line-break-chars' => $this->LE); - $s = stream_filter_append($fp, 'convert.quoted-printable-encode', STREAM_FILTER_READ, $params); - fputs($fp, $string); - rewind($fp); - $out = stream_get_contents($fp); - stream_filter_remove($s); - $out = preg_replace('/^\./m', '=2E', $out); //Encode . if it is first char on a line, workaround for bug in Exchange - fclose($fp); - return $out; + //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); } /** @@ -2116,7 +2133,7 @@ public function EncodeQP($string, $line_max = 76, $space_conv = false) { */ public function EncodeQ($str, $position = 'text') { //There should not be any EOL in the string - $pattern=""; + $pattern = ''; $encoded = str_replace(array("\r", "\n"), '', $str); switch (strtolower($position)) { case 'phrase': @@ -2125,8 +2142,8 @@ public function EncodeQ($str, $position = 'text') { case 'comment': $pattern = '\(\)"'; - //note that we dont break here! - //for this reason we build the $pattern withoud including delimiters and [] + //note that we don't break here! + //for this reason we build the $pattern without including delimiters and [] case 'text': default: @@ -2135,13 +2152,13 @@ public function EncodeQ($str, $position = 'text') { $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); } @@ -2157,7 +2174,11 @@ public function EncodeQ($str, $position = 'text') { * @param string $type File extension (MIME) type. * @return void */ - public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream') { + 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, @@ -2172,25 +2193,27 @@ public function AddStringAttachment($string, $filename, $encoding = 'base64', $t } /** - * Adds an embedded attachment. This can include images, sounds, and - * just about any other document. Make sure to set the $type to an - * image type. For JPEG images use "image/jpeg" and for GIF images - * use "image/gif". + * 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 identify - * the Id for accessing the image in an HTML form. + * @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 extension (MIME) type. - * @return bool + * @param string $type File MIME type. + * @return bool True on successfully adding an attachment */ - public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { - + 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; @@ -2207,24 +2230,29 @@ public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', 6 => 'inline', 7 => $cid ); - return true; } + /** - * Adds an embedded stringified attachment. This can include images, sounds, and - * just about any other document. Make sure to set the $type to an - * image type. For JPEG images use "image/jpeg" and for GIF images - * use "image/gif". - * @param string $string The attachment. - * @param string $cid Content ID of the attachment. Use this to identify - * the Id for accessing the image in an HTML form. - * @param string $name Overrides the attachment name. + * 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 File extension (MIME) type. - * @return bool + * @param string $type MIME type. + * @return bool True on successfully adding an attachment */ - public function AddStringEmbeddedImage($string, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + 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, @@ -2236,6 +2264,7 @@ public function AddStringEmbeddedImage($string, $cid, $name = '', $encoding = 'b 6 => 'inline', 7 => $cid ); + return true; } /** @@ -2375,13 +2404,10 @@ protected function SetError($msg) { * @static */ public static function RFCDate() { - $tz = date('Z'); - $tzs = ($tz < 0) ? '-' : '+'; - $tz = abs($tz); - $tz = (int)($tz/3600)*100 + ($tz%3600)/60; - $result = sprintf("%s %s%04d", date('D, j M Y H:i:s'), $tzs, $tz); - - return $result; + //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'); } /** @@ -2462,16 +2488,19 @@ public function AddCustomHeader($name, $value=null) { } /** - * Evaluates the message and returns modifications for inline images and backgrounds + * 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 Text to be HTML modified + * @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 = '') { + 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) { + 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); @@ -2479,29 +2508,42 @@ public function MsgHTML($message, $basedir = '') { if ($directory == '.') { $directory = ''; } - $cid = 'cid:' . md5($filename); - $ext = pathinfo($filename, PATHINFO_EXTENSION); - $mimeType = self::_mime_types($ext); - if ( strlen($basedir) > 1 && substr($basedir, -1) != '/') { $basedir .= '/'; } - if ( strlen($directory) > 1 && substr($directory, -1) != '/') { $directory .= '/'; } - if ( $this->AddEmbeddedImage($basedir.$directory.$filename, md5($filename), $filename, 'base64', $mimeType) ) { - $message = preg_replace("/".$images[1][$i]."=[\"']".preg_quote($url, '/')."[\"']/Ui", $images[1][$i]."=\"".$cid."\"", $message); + $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); - $this->Body = $message; - if (empty($this->AltBody)) { - $textMsg = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s', '', $message))); - if (!empty($textMsg)) { - $this->AltBody = html_entity_decode($textMsg, ENT_QUOTES, $this->CharSet); - } - } if (empty($this->AltBody)) { $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n"; } - return $message; + //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); } /** @@ -2604,19 +2646,82 @@ public static function _mime_types($ext = '') { } /** - * Set (or reset) Class Objects (variables) - * - * Usage Example: - * $page->set('X-Priority', '3'); - * - * @access public - * @param string $name Parameter Name - * @param mixed $value Parameter Value - * NOTE: will not work with arrays, there are no arrays to set/reset + * 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? - */ + * @todo Should this not be using __set() magic function? + */ public function set($name, $value = '') { try { if (isset($this->$name) ) { @@ -2636,7 +2741,7 @@ public function set($name, $value = '') { /** * Strips newlines to prevent header injection. * @access public - * @param string $str String + * @param string $str * @return string */ public function SecureHeader($str) { @@ -2644,11 +2749,25 @@ public function SecureHeader($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 $cert_filename - * @param string $key_filename Parameter File Name + * @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) { @@ -2682,9 +2801,16 @@ public function DKIM_QP($txt) { * * @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); @@ -2752,13 +2878,22 @@ public function DKIM_Add($headers_line, $subject, $body) { $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 = ""; + $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)); @@ -2779,7 +2914,7 @@ public function DKIM_Add($headers_line, $subject, $body) { "\tb="; $toSign = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs); $signed = $this->DKIM_Sign($toSign); - return "X-PHPMAILER-DKIM: code.google.com/a/apache-extras.org/p/phpmailer/\r\n".$dkimhdrs.$signed."\r\n"; + return $dkimhdrs.$signed."\r\n"; } /** @@ -2792,7 +2927,7 @@ public function DKIM_Add($headers_line, $subject, $body) { * @param string $body * @param string $from */ - protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from=null) { + 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); @@ -2814,4 +2949,3 @@ public function errorMessage() { return $errorMsg; } } -?> diff --git a/PHPMailer/class.pop3.php b/PHPMailer/class.pop3.php index f4172a5..17bb675 100644 --- a/PHPMailer/class.pop3.php +++ b/PHPMailer/class.pop3.php @@ -2,10 +2,11 @@ /*~ class.pop3.php .---------------------------------------------------------------------------. | Software: PHPMailer - PHP email class | -| Version: 5.2.2 | -| Site: https://code.google.com/a/apache-extras.org/p/phpmailer/ | +| Version: 5.2.6 | +| Site: https://github.com/PHPMailer/PHPMailer/ | | ------------------------------------------------------------------------- | -| Admin: Jim Jagielski (project admininistrator) | +| Admins: Marcus Bointon | +| Admins: Jim Jagielski | | Authors: Andy Prevost (codeworxtech) codeworxtech@users.sourceforge.net | | : Marcus Bointon (coolbru) coolbru@users.sourceforge.net | | : Jim Jagielski (jimjag) jimjag@gmail.com | @@ -37,7 +38,7 @@ /** * PHP POP-Before-SMTP Authentication Class * - * Version 5.2.2 + * Version 5.2.6 * * @license: LGPL, see PHPMailer License * @@ -115,7 +116,7 @@ class POP3 { * Sets the POP3 PHPMailer Version number * @var string */ - public $Version = '5.2.2'; + public $Version = '5.2.6'; ///////////////////////////////////////////////// // PROPERTIES, PRIVATE AND PROTECTED @@ -269,8 +270,8 @@ public function Connect ($host, $port = false, $tval = 30) { // Check for the +OK if ($this->checkResponse($pop3_response)) { - // The connection is established and the POP3 server is talking - $this->connected = true; + // The connection is established and the POP3 server is talking + $this->connected = true; return true; } return false; @@ -415,4 +416,3 @@ private function catchWarning ($errno, $errstr, $errfile, $errline) { // End of class } -?> diff --git a/PHPMailer/class.smtp.php b/PHPMailer/class.smtp.php index 8d9cd7d..158eff5 100644 --- a/PHPMailer/class.smtp.php +++ b/PHPMailer/class.smtp.php @@ -2,12 +2,13 @@ /*~ class.smtp.php .---------------------------------------------------------------------------. | Software: PHPMailer - PHP email class | -| Version: 5.2.2 | -| Site: https://code.google.com/a/apache-extras.org/p/phpmailer/ | +| Version: 5.2.6 | +| Site: https://github.com/PHPMailer/PHPMailer/ | | ------------------------------------------------------------------------- | -| Admin: Jim Jagielski (project admininistrator) | +| Admins: Marcus Bointon | +| Admins: Jim Jagielski | | Authors: Andy Prevost (codeworxtech) codeworxtech@users.sourceforge.net | -| : Marcus Bointon (coolbru) coolbru@users.sourceforge.net | +| : Marcus Bointon (coolbru) phpmailer@synchromedia.co.uk | | : Jim Jagielski (jimjag) jimjag@gmail.com | | Founder: Brent R. Matzelle (original founder) | | Copyright (c) 2010-2012, Jim Jagielski. All Rights Reserved. | @@ -57,17 +58,17 @@ class SMTP { public $CRLF = "\r\n"; /** - * Sets whether debugging is turned on - * @var bool + * Debug output level; 0 for no output + * @var int */ - public $do_debug; // the level of debug to perform + public $do_debug = 0; /** * Sets the function/method to use for debugging output. - * Right now we only honor "echo" or "error_log" + * Right now we only honor 'echo', 'html' or 'error_log' * @var string */ - public $Debugoutput = "echo"; + public $Debugoutput = 'echo'; /** * Sets VERP use on/off (default is off) @@ -91,7 +92,7 @@ class SMTP { * Sets the SMTP PHPMailer Version number * @var string */ - public $Version = '5.2.2'; + public $Version = '5.2.6'; ///////////////////////////////////////////////// // PROPERTIES, PRIVATE AND PROTECTED @@ -100,24 +101,32 @@ class SMTP { /** * @var resource The socket to the server */ - private $smtp_conn; + protected $smtp_conn; /** * @var string Error message, if any, for the last call */ - private $error; + protected $error; /** * @var string The reply the server sent to us for HELO */ - private $helo_rply; + protected $helo_rply; /** * Outputs debugging info via user-defined method * @param string $str */ - private function edebug($str) { - if ($this->Debugoutput == "error_log") { + protected function edebug($str) { + switch ($this->Debugoutput) { + case 'error_log': error_log($str); - } else { + 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; } } @@ -140,29 +149,25 @@ public function __construct() { ///////////////////////////////////////////////// /** - * Connect to the server specified on the port specified. - * If the port is not specified use the default SMTP_PORT. - * If tval is specified then a connection will try and be - * established with the server for that number of seconds. - * If tval is not specified the default is 30 seconds to - * try on the connection. + * Connect to an SMTP server * * SMTP CODE SUCCESS: 220 * SMTP CODE FAILURE: 421 * @access public - * @param string $host - * @param int $port - * @param int $tval + * @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, $tval = 30) { - // set the error val to null so there is no confusion + public function Connect($host, $port = 0, $timeout = 30, $options = array()) { + // Clear errors to avoid confusion $this->error = null; - // make sure we are __not__ connected + // Make sure we are __not__ connected if($this->connected()) { - // already connected, generate error - $this->error = array("error" => "Already connected to a server"); + // Already connected, generate error + $this->error = array('error' => 'Already connected to a server'); return false; } @@ -170,38 +175,39 @@ public function Connect($host, $port = 0, $tval = 30) { $port = $this->SMTP_PORT; } - // connect to the smtp server - $this->smtp_conn = @fsockopen($host, // the host of the server - $port, // the port to use - $errno, // error number if any - $errstr, // error message if any - $tval); // give up after ? secs - // verify we connected properly + // 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); + $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)" . $this->CRLF . '
'); + $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 && $tval > $max) { // don't bother if unlimited - @set_time_limit($tval); - } - stream_set_timeout($this->smtp_conn, $tval, 0); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> FROM SERVER:' . $announce); } return true; @@ -220,26 +226,26 @@ public function StartTLS() { $this->error = null; # to avoid confusion if(!$this->connected()) { - $this->error = array("error" => "Called StartTLS() without being connected"); + $this->error = array('error' => 'Called StartTLS() without being connected'); return false; } - fputs($this->smtp_conn,"STARTTLS" . $this->CRLF); + $this->client_send('STARTTLS' . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($this->do_debug >= 2) { - $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'); + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -271,86 +277,86 @@ public function Authenticate($username, $password, $authtype='LOGIN', $realm='', switch ($authtype) { case 'PLAIN': // Start authentication - fputs($this->smtp_conn,"AUTH PLAIN" . $this->CRLF); - + $this->client_send('AUTH PLAIN' . $this->CRLF); + $rply = $this->get_lines(); - $code = substr($rply,0,3); - + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } // Send encoded username and password - fputs($this->smtp_conn, base64_encode("\0".$username."\0".$password) . $this->CRLF); + $this->client_send(base64_encode("\0".$username."\0".$password) . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); - + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } break; case 'LOGIN': // Start authentication - fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF); - + $this->client_send('AUTH LOGIN' . $this->CRLF); + $rply = $this->get_lines(); - $code = substr($rply,0,3); - + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } - + // Send encoded username - fputs($this->smtp_conn, base64_encode($username) . $this->CRLF); - + $this->client_send(base64_encode($username) . $this->CRLF); + $rply = $this->get_lines(); - $code = substr($rply,0,3); - + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } - + // Send encoded password - fputs($this->smtp_conn, base64_encode($password) . $this->CRLF); - + $this->client_send(base64_encode($password) . $this->CRLF); + $rply = $this->get_lines(); - $code = substr($rply,0,3); - + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -363,60 +369,132 @@ public function Authenticate($username, $password, $authtype='LOGIN', $realm='', ** 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('ntlm_sasl_client.php'); + 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); + $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"] . $this->CRLF); + $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 - - fputs($this->smtp_conn,"AUTH NTLM " . base64_encode($msg1) . $this->CRLF); + + $this->client_send('AUTH NTLM ' . base64_encode($msg1) . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); - + $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)); + 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 . $this->CRLF); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } - - $challange = substr($rply,3);//though 0 based, there is a white space after the 3 digit number....//msg2 - $challange = base64_decode($challange); - $ntlm_res = $ntlm_client->NTLMResponse(substr($challange,24,8),$password); - $msg3 = $ntlm_client->TypeMsg3($ntlm_res,$username,$realm,$workstation);//msg3 + + $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 - fputs($this->smtp_conn, base64_encode($msg3) . $this->CRLF); + $this->client_send(base64_encode($msg3) . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($code != 235) { $this->error = - array("error" => "Could not authenticate", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); + 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 . $this->CRLF); + $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 @@ -424,11 +502,11 @@ public function Authenticate($username, $password, $authtype='LOGIN', $realm='', */ public function Connected() { if(!empty($this->smtp_conn)) { - $sock_status = socket_get_status($this->smtp_conn); - if($sock_status["eof"]) { + $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:" . $this->CRLF . "EOF caught while checking if connected"); + $this->edebug('SMTP -> NOTICE: EOF caught while checking if connected'); } $this->Close(); return false; @@ -472,9 +550,9 @@ public function Close() { * [data] * . * SMTP CODE SUCCESS: 250 - * SMTP CODE FAILURE: 552,554,451,452 - * SMTP CODE FAILURE: 451,554 - * SMTP CODE ERROR : 500,501,503,421 + * 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 @@ -484,26 +562,26 @@ public function Data($msg_data) { if(!$this->connected()) { $this->error = array( - "error" => "Called Data() without being connected"); + 'error' => 'Called Data() without being connected'); return false; } - fputs($this->smtp_conn,"DATA" . $this->CRLF); + $this->client_send('DATA' . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($this->do_debug >= 2) { - $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'); + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -520,9 +598,9 @@ public function Data($msg_data) { */ // 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); + $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 @@ -533,31 +611,31 @@ public function Data($msg_data) { * headers. */ - $field = substr($lines[0],0,strpos($lines[0],":")); + $field = substr($lines[0], 0, strpos($lines[0], ':')); $in_headers = false; - if(!empty($field) && !strstr($field," ")) { + 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)) { + while(list(, $line) = @each($lines)) { $lines_out = null; - if($line == "" && $in_headers) { + 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)," "); + $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); + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); } else { - $lines_out[] = substr($line,0,$pos); - $line = substr($line,$pos + 1); + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos + 1); } /* if processing headers add a LWSP-char to the front of new line @@ -570,34 +648,34 @@ public function Data($msg_data) { $lines_out[] = $line; // send the lines to the server - while(list(,$line_out) = @each($lines_out)) { + while(list(, $line_out) = @each($lines_out)) { if(strlen($line_out) > 0) { - if(substr($line_out, 0, 1) == ".") { - $line_out = "." . $line_out; + if(substr($line_out, 0, 1) == '.') { + $line_out = '.' . $line_out; } } - fputs($this->smtp_conn,$line_out . $this->CRLF); + $this->client_send($line_out . $this->CRLF); } } // message data has been sent - fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF); + $this->client_send($this->CRLF . '.' . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($this->do_debug >= 2) { - $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'); + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -622,19 +700,19 @@ public function Hello($host = '') { if(!$this->connected()) { $this->error = array( - "error" => "Called Hello() without being connected"); + '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"; + $host = 'localhost'; } // Send extended hello first (RFC 2821) - if(!$this->SendHello("EHLO", $host)) { - if(!$this->SendHello("HELO", $host)) { + if(!$this->SendHello('EHLO', $host)) { + if(!$this->SendHello('HELO', $host)) { return false; } } @@ -644,28 +722,28 @@ public function Hello($host = '') { /** * Sends a HELO/EHLO command. - * @access private + * @access protected * @param string $hello * @param string $host * @return bool */ - private function SendHello($hello, $host) { - fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF); + protected function SendHello($hello, $host) { + $this->client_send($hello . ' ' . $host . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($this->do_debug >= 2) { - $this->edebug("SMTP -> FROM SERVER: " . $rply . $this->CRLF . '
'); + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -684,8 +762,8 @@ private function SendHello($hello, $host) { * Implements rfc 821: MAIL FROM: * * SMTP CODE SUCCESS: 250 - * SMTP CODE SUCCESS: 552,451,452 - * SMTP CODE SUCCESS: 500,501,421 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 421 * @access public * @param string $from * @return bool @@ -695,27 +773,27 @@ public function Mail($from) { if(!$this->connected()) { $this->error = array( - "error" => "Called Mail() without being connected"); + 'error' => 'Called Mail() without being connected'); return false; } - $useVerp = ($this->do_verp ? " XVERP" : ""); - fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF); + $useVerp = ($this->do_verp ? ' XVERP' : ''); + $this->client_send('MAIL FROM:<' . $from . '>' . $useVerp . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($this->do_debug >= 2) { - $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'); + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -739,32 +817,32 @@ public function Quit($close_on_error = true) { if(!$this->connected()) { $this->error = array( - "error" => "Called Quit() without being connected"); + 'error' => 'Called Quit() without being connected'); return false; } // send the quit command to the server - fputs($this->smtp_conn,"quit" . $this->CRLF); + $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 . $this->CRLF . '
'); + $this->edebug('SMTP -> FROM SERVER:' . $byemsg); } $rval = true; $e = null; - $code = substr($byemsg,0,3); + $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)); + $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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $e['error'] . ': ' . $byemsg); } } @@ -781,9 +859,9 @@ public function Quit($close_on_error = true) { * * 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 + * 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 @@ -793,26 +871,26 @@ public function Recipient($to) { if(!$this->connected()) { $this->error = array( - "error" => "Called Recipient() without being connected"); + 'error' => 'Called Recipient() without being connected'); return false; } - fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF); + $this->client_send('RCPT TO:<' . $to . '>' . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($this->do_debug >= 2) { - $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'); + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -827,7 +905,7 @@ public function Recipient($to) { * Implements rfc 821: RSET * * SMTP CODE SUCCESS: 250 - * SMTP CODE ERROR : 500,501,504,421 + * SMTP CODE ERROR : 500, 501, 504, 421 * @access public * @return bool */ @@ -835,27 +913,26 @@ public function Reset() { $this->error = null; // so no confusion is caused if(!$this->connected()) { - $this->error = array( - "error" => "Called Reset() without being connected"); + $this->error = array('error' => 'Called Reset() without being connected'); return false; } - fputs($this->smtp_conn,"RSET" . $this->CRLF); + $this->client_send('RSET' . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($this->do_debug >= 2) { - $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'); + $this->edebug('SMTP -> FROM SERVER:' . $rply); } if($code != 250) { $this->error = - array("error" => "RSET failed", - "smtp_code" => $code, - "smtp_msg" => substr($rply,4)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -874,8 +951,8 @@ public function Reset() { * Implements rfc 821: SAML FROM: * * SMTP CODE SUCCESS: 250 - * SMTP CODE SUCCESS: 552,451,452 - * SMTP CODE SUCCESS: 500,501,502,421 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 502, 421 * @access public * @param string $from * @return bool @@ -885,26 +962,26 @@ public function SendAndMail($from) { if(!$this->connected()) { $this->error = array( - "error" => "Called SendAndMail() without being connected"); + 'error' => 'Called SendAndMail() without being connected'); return false; } - fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF); + $this->client_send('SAML FROM:' . $from . $this->CRLF); $rply = $this->get_lines(); - $code = substr($rply,0,3); + $code = substr($rply, 0, 3); if($this->do_debug >= 2) { - $this->edebug("SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'); + $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)); + 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 . $this->CRLF . '
'); + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); } return false; } @@ -925,14 +1002,27 @@ public function SendAndMail($from) { * @return bool */ public function Turn() { - $this->error = array("error" => "This method, TURN, of the SMTP ". - "is not implemented"); + $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"] . $this->CRLF . '
'); + $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 @@ -952,11 +1042,11 @@ public function getError() { * 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 private + * @access protected * @return string */ - private function get_lines() { - $data = ""; + 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)) { @@ -967,22 +1057,22 @@ private function get_lines() { $endtime = time() + $this->Timelimit; } while(is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { - $str = @fgets($this->smtp_conn,515); + $str = @fgets($this->smtp_conn, 515); if($this->do_debug >= 4) { - $this->edebug("SMTP -> get_lines(): \$data was \"$data\"" . $this->CRLF . '
'); - $this->edebug("SMTP -> get_lines(): \$str is \"$str\"" . $this->CRLF . '
'); + $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\"" . $this->CRLF . '
'); + $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; } + 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)
"); + $this->edebug('SMTP -> get_lines(): timed-out (' . $this->Timeout . ' seconds)'); } break; } @@ -990,7 +1080,7 @@ private function get_lines() { if ($endtime) { if (time() > $endtime) { if($this->do_debug >= 4) { - $this->edebug("SMTP -> get_lines(): timelimit reached (" . $this->Timelimit . " seconds)
"); + $this->edebug('SMTP -> get_lines(): timelimit reached (' . $this->Timelimit . ' seconds)'); } break; } @@ -1000,4 +1090,3 @@ private function get_lines() { } } -?> diff --git a/README.md b/README.md index 9407371..063f617 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,21 @@ # YiiMailer -Yii extension for sending emails with layouts using [PHPMailer](http://code.google.com/a/apache-extras.org/p/phpmailer/) +Yii extension for sending emails with layouts using [PHPMailer](https://github.com/PHPMailer/PHPMailer) ## Features -* Based on latest PHPMailer (version 5.2.2 bundled) +* Based on latest PHPMailer (version 5.2.6 bundled) * Supports Yii layouts and translation * Supports web and console applications -* Send full HTML emails with embeded images +* Send full HTML emails with embedded images * Work with views like you usually do in Yii +* Use test mode to save emails instead of sending them (useful when you don't have mail server installed locally) ## Installation 1. Copy YiiMailer folder to protected/extensions 2. Add 'ext.YiiMailer.YiiMailer' line to your imports in main and/or console yii config -3. Copy mail.php config file to protected/config +3. Copy mail.php config file to protected/config or add configuration array in 'params' under the key 'YiiMailer' 4. Create email layout file mail.php in protected/views/layouts/ (default path, can be changed in config) 5. Create all the views you want to use in protected/views/mail/ (default path, can be changed in config) 6. Put all images you want to embed in emails in images/mail/ (default path, can be changed in config) @@ -31,28 +32,73 @@ $mail = new YiiMailer(); $mail->setView('contact'); $mail->setData(array('message' => 'Message to send', 'name' => 'John Doe', 'description' => 'Contact form')); -Layout is automatically set from config but you may override it with $mail->setLayout('layoutName') +Layout is automatically set from config but you may override it with +
+$mail->setLayout('layoutName');
+
-Render HTML mail and set properties: +Set the properties:
-$mail->render();
-$mail->From = 'from@example.com';
-$mail->FromName = 'John Doe';
-$mail->Subject = 'Mail subject';
-$mail->AddAddress(Yii::app()->params['adminEmail']);
+$mail->setFrom('from@example.com', 'John Doe');
+$mail->setTo(Yii::app()->params['adminEmail']);
+$mail->setSubject('Mail subject');
 
You may use all PHPMailer properties you would usually use. And finally send email(s):
-if ($mail->Send()) {
-	$mail->ClearAddresses();
+if ($mail->send()) {
 	Yii::app()->user->setFlash('contact','Thank you for contacting us. We will respond to you as soon as possible.');
 } else {
-	Yii::app()->user->setFlash('error','Error while sending email: '.$mail->ErrorInfo);
+	Yii::app()->user->setFlash('error','Error while sending email: '.$mail->getError());
 }
 
+### Sending simple messages + +You can send email without both the layout and view by using: +
+$mail = new YiiMailer();
+//$mail->clearLayout();//if layout is already set in config
+$mail->setFrom('from@example.com', 'John Doe');
+$mail->setTo(Yii::app()->params['adminEmail']);
+$mail->setSubject('Mail subject');
+$mail->setBody('Simple message');
+$mail->send();
+
+ +Alternatively, you may also send email message with layout but without specific view (set layout and set body) or with view but without layout (clear layout and set view). + +### Setting addresses + +When using methods for setting addresses (setTo(), setCc(), setBcc(), setReplyTo()) any of the following is valid for arguments: +
+$mail->setTo('john@example.com');
+$mail->setTo(array('john@example.com','jane@example.com'));
+$mail->setTo(array('john@example.com'=>'John Doe','jane@example.com'));
+
+ +### Sending attachments + +You may send one or more attachments using setAttachemnt() method: +
+$mail->setAttachment('something.pdf');
+$mail->setAttachment(array('something.pdf','something_else.pdf','another.doc'));
+$mail->setAttachment(array('something.pdf'=>'Some file','something_else.pdf'=>'Another file'));
+
+ +### Test mode + +When working locally without mail server installed, it may be useful to save emails as files instead of trying to send them and getting errors in the process. +To use test mode, you must specify path to directory where you want to save your emails and set 'testMode' property to 'true' in your config: + +
+	'savePath' => 'webroot.assets.mail',
+	'testMode' => true,
+
+ +Emails are saved as .eml files and you can use software like Mozilla Thunderbird to open them. + ## Examples Two examples included: one for standard contact form in yii web app and the other one for yii console app. \ No newline at end of file diff --git a/YiiMailer.php b/YiiMailer.php index e749c23..e861135 100644 --- a/YiiMailer.php +++ b/YiiMailer.php @@ -2,6 +2,7 @@ /** * YiiMailer class - wrapper for PHPMailer * Yii extension for sending emails using views and layouts + * https://github.com/vernes/YiiMailer * Copyright (c) 2013 YiiMailer * * This library is free software; you can redistribute it and/or @@ -23,7 +24,7 @@ * @author Vernes Šiljegović * @copyright Copyright (c) 2013 YiiMailer * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL - * @version 1.1, 2013-02-02 + * @version 1.5, 2013-06-03 */ @@ -56,6 +57,10 @@ class YiiMailer extends PHPMailer { private $layoutPath='application.views.mail.layouts'; private $baseDirPath='webroot.images.mail'; + + private $testMode=false; + + private $savePath='webroot.assets.mail'; private $layout; @@ -132,6 +137,14 @@ public function getView() return $this->view; } + /** + * Clear currently used view + */ + public function clearView() + { + $this->view=null; + } + /** * Send data to be used in mail body * @param array $data Data array @@ -150,6 +163,14 @@ public function getData() return $this->data; } + /** + * Clear current data array + */ + public function clearData() + { + $this->data=array(); + } + /** * Set layout file to be used * @param string $layout Layout filename @@ -174,6 +195,14 @@ public function getLayout() return $this->layout; } + /** + * Clear current layout + */ + public function clearLayout() + { + $this->layout=null; + } + /** * Set path for email views * @param string $path Yii path @@ -237,6 +266,156 @@ public function getBaseDirPath() return $this->baseDirPath; } + /** + * Set From address and name + * @param string $address Email address of the sender + * @param string $name Name of the sender + * @param boolean $auto Also set the Reply-To + * @return boolean True on success, false if address not valid + */ + public function setFrom($address, $name = '', $auto = true) + { + return parent::SetFrom($address, $name, (int)$auto); + } + + /** + * Set one or more email addresses to send to + * Valid arguments: + * $mail->setTo('john@example.com'); + * $mail->setTo(array('john@example.com','jane@example.com')); + * $mail->setTo(array('john@example.com'=>'John Doe','jane@example.com')); + * @param mixed $addresses Email address or array of email addresses + * @return boolean True on success, false if addresses not valid + */ + public function setTo($addresses) + { + $this->ClearAddresses(); + return $this->setAddresses('to',$addresses); + } + + /** + * Set one or more CC email addresses + * @param mixed $addresses Email address or array of email addresses + * @return boolean True on success, false if addresses not valid + */ + public function setCc($addresses) + { + $this->ClearCCs(); + return $this->setAddresses('cc',$addresses); + } + + /** + * Set one or more BCC email addresses + * @param mixed $addresses Email address or array of email addresses + * @return boolean True on success, false if addresses not valid + */ + public function setBcc($addresses) + { + $this->ClearBCCs(); + return $this->setAddresses('bcc',$addresses); + } + + /** + * Set one or more Reply-To email addresses + * @param mixed $addresses Email address or array of email addresses + * @return boolean True on success, false if addresses not valid + */ + public function setReplyTo($addresses) + { + $this->ClearReplyTos(); + return $this->setAddresses('Reply-To',$addresses); + } + + /** + * Set one or more email addresses of different kinds + * @param string $type Type of the recipient (to, cc, bcc or Reply-To) + * @param mixed $addresses Email address or array of email addresses + * @return boolean True on success, false if addresses not valid + */ + private function setAddresses($type,$addresses) + { + if(!is_array($addresses)) + { + $addresses=(array)$addresses; + } + + $result=true; + foreach ($addresses as $key => $value) { + if(is_int($key)) + $r=$this->AddAnAddress($type,$value); + else + $r=$this->AddAnAddress($type,$key,$value); + if($result && !$r) + $result=false; + } + + return $result; + } + + /** + * Set subject of the email + * @param string $subject Subject of the email + */ + public function setSubject($subject) + { + $this->Subject=$subject; + } + + /** + * Set text body of the email + * @param string $body Textual body of the email + */ + public function setBody($body) + { + $this->Body=$body; + } + + /** + * Set one or more email attachments + * Valid arguments: + * $mail->setAttachment('something.pdf'); + * $mail->setAttachment(array('something.pdf','something_else.pdf','another.doc')); + * $mail->setAttachment(array('something.pdf'=>'Some file','something_else.pdf'=>'Another file')); + * @param mixed $attachments Path to the file or array of files to attach + * @return boolean True on success, false if addresses not valid + */ + public function setAttachment($attachments) + { + if(!is_array($attachments)) + $attachments=(array)$attachments; + + $result=true; + foreach ($attachments as $key => $value) { + if(is_int($key)) + $r=$this->AddAttachment($value); + else + $r=$this->AddAttachment($key,$value); + if($result && !$r) + $result=false; + } + + return $result; + } + + /** + * Clear all recipients and attachments + */ + public function clear() + { + $this->ClearAllRecipients(); + $this->ClearReplyTos(); + $this->ClearAttachments(); + } + + /** + * Get current error message + * @return string Error message + */ + public function getError() + { + return $this->ErrorInfo; + } + /** * Find the view file for the given view name * @param string $viewName Name of the view @@ -293,17 +472,20 @@ public function renderView($viewName,$viewData=null) */ public function render() { - //render body - $body=$this->renderView($this->viewPath.'.'.$this->view, $this->data); + //render view as body if specified + if(isset($this->view)) + $this->setBody($this->renderView($this->viewPath.'.'.$this->view, $this->data)); + + //render with layout if given if($this->layout) { //has layout - $this->MsgHTMLWithLayout($body, Yii::getPathOfAlias($this->baseDirPath)); + $this->MsgHTMLWithLayout($this->Body, Yii::getPathOfAlias($this->baseDirPath)); } else { //no layout - $this->MsgHTML($body, Yii::getPathOfAlias($this->baseDirPath)); + $this->MsgHTML($this->Body, Yii::getPathOfAlias($this->baseDirPath)); } } @@ -316,5 +498,60 @@ protected function MsgHTMLWithLayout($message, $basedir = '') { $this->MsgHTML($this->renderView($this->layoutPath.'.'.$this->layout, array('content'=>$message,'data'=>$this->data)), $basedir); } - + + /** + * Render message and send emails + * @return boolean True if sent successfully, false otherwise + */ + public function send() + { + //render message + $this->render(); + + //send the message + try{ + //prepare the message + if(!$this->PreSend()) + return false; + + //in test mode, save message as a file + if($this->testMode) + return $this->save(); + else + return $this->PostSend(); + } catch (phpmailerException $e) { + $this->mailHeader = ''; + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + /** + * Save message as eml file + * @return boolean True if saved successfully, false otherwise + */ + public function save() + { + $filename = date('YmdHis') . '_' . uniqid() . '.eml'; + $dir = Yii::getPathOfAlias($this->savePath); + //check if dir exists and is writable + if(!is_writable($dir)) + throw new CException('Directory "'.$dir.'" does not exist or is not writable!'); + + try { + $file = fopen($dir . DIRECTORY_SEPARATOR . $filename,'w+'); + fwrite($file, $this->GetSentMIMEMessage()); + fclose($file); + + return true; + } catch(Exception $e) { + $this->SetError($e->getMessage()); + + return false; + } + } + } \ No newline at end of file diff --git a/example/files/yii-1.1.0-validator-cheatsheet.pdf b/example/files/yii-1.1.0-validator-cheatsheet.pdf new file mode 100644 index 0000000..ec156d7 Binary files /dev/null and b/example/files/yii-1.1.0-validator-cheatsheet.pdf differ diff --git a/example/protected/commands/CronCommand.php b/example/protected/commands/CronCommand.php index f2c2e2c..ff8bc0d 100644 --- a/example/protected/commands/CronCommand.php +++ b/example/protected/commands/CronCommand.php @@ -11,23 +11,21 @@ public function run($args) //Do some cron processing... $cronResult="Cron job finished successfuly"; - $mail = new YiiMailer(); + $mail = new YiiMailer; //use "cron" view from views/mail $mail->setView('cron'); $mail->setData(array('message' => $cronResult, 'name' => get_class($this), 'description' => 'Cron job', 'mailer' => $mail)); - //render HTML mail, layout is set from config file or with $mail->setLayout('layoutName') - $mail->render(); - //set properties as usually with PHPMailer - $mail->From = 'from@example.com'; - $mail->FromName = 'Console application'; - $mail->Subject = $cronResult; - $mail->AddAddress('to@example.com'); + + //set properties + $mail->setFrom('from@example.com', 'Console application'); + $mail->setSubject($cronResult); + $mail->setTo('to@example.com'); + $mail->setAttachment(Yii::getPathOfAlias('webroot.files') . '/yii-1.1.0-validator-cheatsheet.pdf'); //send - if ($mail->Send()) { - $mail->ClearAddresses(); + if ($mail->send()) { echo 'Mail sent successfuly'; } else { - echo 'Error while sending email: '.$mail->ErrorInfo; + echo 'Error while sending email: '.$mail->getError(); } echo PHP_EOL; } diff --git a/example/protected/config/mail.php b/example/protected/config/mail.php index ef47de2..27aa602 100644 --- a/example/protected/config/mail.php +++ b/example/protected/config/mail.php @@ -3,6 +3,8 @@ 'viewPath' => 'application.views.mail', 'layoutPath' => 'application.views.layouts', 'baseDirPath' => 'webroot.images.mail', + 'savePath' => 'webroot.assets.mail', + 'testMode' => false, 'layout' => 'mail', 'CharSet' => 'UTF-8', 'AltBody' => Yii::t('YiiMailer','You need an HTML capable viewer to read this message.'), diff --git a/example/protected/controllers/SiteController.php b/example/protected/controllers/SiteController.php index 1849efa..f337fa3 100644 --- a/example/protected/controllers/SiteController.php +++ b/example/protected/controllers/SiteController.php @@ -59,19 +59,16 @@ public function actionContact() { //use 'contact' view from views/mail $mail = new YiiMailer('contact', array('message' => $model->body, 'name' => $model->name, 'description' => 'Contact form')); - //render HTML mail, layout is set from config file or with $mail->setLayout('layoutName') - $mail->render(); - //set properties as usually with PHPMailer - $mail->From = $model->email; - $mail->FromName = $model->name; - $mail->Subject = $model->subject; - $mail->AddAddress(Yii::app()->params['adminEmail']); + + //set properties + $mail->setFrom($model->email, $model->name); + $mail->setSubject($model->subject); + $mail->setTo(Yii::app()->params['adminEmail']); //send - if ($mail->Send()) { - $mail->ClearAddresses(); + if ($mail->send()) { Yii::app()->user->setFlash('contact','Thank you for contacting us. We will respond to you as soon as possible.'); } else { - Yii::app()->user->setFlash('error','Error while sending email: '.$mail->ErrorInfo); + Yii::app()->user->setFlash('error','Error while sending email: '.$mail->getError()); } $this->refresh();