├── content ├── newsletter │ └── newsletter.txt ├── subscribe │ └── subscribe.txt └── unsubscribe │ └── unsubscribe.txt ├── site ├── widgets │ └── newsletter │ │ ├── newsletter.php │ │ └── template.php ├── languages │ ├── en.php │ └── de.php ├── templates │ ├── unsubscribe.php │ ├── subscribe.php │ └── newsletter.php └── config │ └── config.php ├── .gitignore ├── LICENSE ├── README.md └── assets └── phpinc ├── PHPMailerAutoload.php ├── class.pop3.php ├── class.smtp.php └── class.phpmailer.php /content/newsletter/newsletter.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /content/subscribe/subscribe.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /content/unsubscribe/unsubscribe.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/widgets/newsletter/newsletter.php: -------------------------------------------------------------------------------- 1 | 'Newsletter', 4 | 'html' => function() { 5 | return tpl::load(__DIR__ . DS . 'template.php'); 6 | }); -------------------------------------------------------------------------------- /site/languages/en.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | *.esproj 10 | 11 | # Packages # 12 | ############ 13 | # it's better to unpack these files and commit the raw source 14 | # git has its own built in compression methods 15 | *.7z 16 | *.dmg 17 | *.gz 18 | *.iso 19 | *.jar 20 | *.rar 21 | *.tar 22 | *.zip 23 | 24 | # Logs and databases # 25 | ###################### 26 | *.log 27 | *.sql 28 | *.sqlite 29 | 30 | # OS generated files # 31 | ###################### 32 | .DS_Store 33 | .DS_Store? 34 | ._* 35 | .Spotlight-V100 36 | .Trashes 37 | ehthumbs.db 38 | Thumbs.db 39 | -------------------------------------------------------------------------------- /site/languages/de.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/widgets/newsletter/template.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | Choose your post to be sent as newsletter. 4 | 5 |
6 |
7 |
8 |
9 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 | -------------------------------------------------------------------------------- /site/templates/unsubscribe.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 |

7 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | user($username[0])->delete(); 30 | echo l::get('deleteunsubscr'); 31 | 32 | } catch(Exception $e) { 33 | echo l::get('error'); 34 | // optional reason: 35 | echo $e->getMessage(); 36 | } 37 | } 38 | ?> 39 | 40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andreas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /site/templates/subscribe.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 |

7 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | users()->create(array( 31 | 'username' => $username[0], 32 | 'email' => $email, 33 | 'role' => 'newsletter', 34 | 'password' => 'noneadfadsfadsfdasfwegtjzivvaasd', 35 | )); 36 | echo l::get('thanks'); 37 | 38 | 39 | } catch(Exception $e) { 40 | 41 | echo l::get('error'); 42 | // optional error message: 43 | echo $e->getMessage(); 44 | } 45 | 46 | } 47 | ?> 48 | 49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kirby newsletter-addon 2 | this will add a newsletter functionality to kirby. 3 | 4 | >I developed this because I did not want to rely on 'professional newsletter providers'. 5 | >Basically due to privacy reasons and protecting customers email addresses from being 'sold'. 6 | 7 | ### Warning:
this plugin has a widged added, which is Kirby 2.0.7/nightly build only 8 | 9 | #features 10 | - send posts content as email to Kirby users 11 | - choose posts to be send widget in panel 12 | - create users form (a.k.a subscribe) 13 | - delete users form (a.k.a unsubscribe) 14 | - includes PHPMailer to make use of SMTP connectivity 15 | 16 | #changelog 17 | v2 has been completely rewritten.
18 | What's new: 19 | 20 | - no more background processing with shell_exec() 21 | - all recipients are native Kirby users now 22 | - user role 'newsletter' is added in config.php 23 | - subscribe and unsubscribe form added 24 | - all recipients will be added to bcc: in the email (this should be ok for smaller recipients-lists) 25 | - added a widget (min. req. [Kirby 2.0.7/nightly](https://twitter.com/getkirby/status/577520610517118976)) to list the latest 5 `children` of page `blog` (please adapt if necessary) 26 | 27 | # documentation 28 | ## installation 29 | copy the files to the corresponding folders to your installation of Kirby. 30 | Be aware that you might rather edit files than overwriting existing ones. 31 | 32 | ## config 33 | set Newsletter configuration vars in Kirbys ./site/config/config.php 34 | 35 | ## requirements/credits 36 | 37 | PHPMailer Plugin: https://github.com/PHPMailer/PHPMailer is already built in. 38 | 39 | Thanks for all the help in the [Kirby forum](http://forum.getkirby.com)! :) 40 | 41 | -------------------------------------------------------------------------------- /assets/phpinc/PHPMailerAutoload.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2012 - 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailer SPL autoloader. 22 | * @param string $classname The name of the class to load 23 | */ 24 | function PHPMailerAutoload($classname) 25 | { 26 | //Can't use __DIR__ as it's only in PHP 5.3+ 27 | $filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php'; 28 | if (is_readable($filename)) { 29 | require $filename; 30 | } 31 | } 32 | 33 | if (version_compare(PHP_VERSION, '5.1.2', '>=')) { 34 | //SPL autoloading was introduced in PHP 5.1.2 35 | if (version_compare(PHP_VERSION, '5.3.0', '>=')) { 36 | spl_autoload_register('PHPMailerAutoload', true, true); 37 | } else { 38 | spl_autoload_register('PHPMailerAutoload'); 39 | } 40 | } else { 41 | /** 42 | * Fall back to traditional autoload for old PHP versions 43 | * @param string $classname The name of the class to load 44 | */ 45 | function __autoload($classname) 46 | { 47 | PHPMailerAutoload($classname); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /site/config/config.php: -------------------------------------------------------------------------------- 1 | 'admin', 53 | 'name' => 'Admin', 54 | 'default' => true, 55 | 'panel' => true 56 | ), 57 | array( 58 | 'id' => 'editor', 59 | 'name' => 'Editor', 60 | 'panel' => true 61 | ), 62 | array( 63 | 'id' => 'newsletter', 64 | 'name' => 'Newsletter', 65 | 'panel' => false 66 | ) 67 | )); 68 | /* ----------------- end newsletter config ---------------------- */ 69 | -------------------------------------------------------------------------------- /site/templates/newsletter.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | user() ?> 6 | 7 |
8 |
9 |

Email Content:

10 | 16 | 17 | 18 | 19 | title()); ?> 20 | text()->kirbytext()); ?> 21 | 22 | ".$subj."

"; ?> 23 | ".$body."

"; ?> 24 |
25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 69 | 70 | 71 |

'.$subj.'

72 |
73 |
74 | '; 75 | // the footer 76 | $footer = ' 77 |
78 | 79 | 80 | 81 | '; 82 | $content = $header; 83 | $content .= $body; 84 | $content .= $footer; 85 | 86 | $mail = new PHPMailer; 87 | $mail->isSMTP(); 88 | $mail->Host = c::get('phpmailer_host'); 89 | $mail->Username = c::get('phpmailer_user'); 90 | $mail->Password = c::get('phpmailer_passwd'); 91 | $mail->SMTPAuth = true; 92 | $mail->SMTPSecure = 'tls'; 93 | $mail->Port = 587; 94 | $mail->ClearAllRecipients(); 95 | $mail->CharSet = 'utf-8'; 96 | $mail->From = c::get('phpmailer_from'); 97 | $mail->FromName = c::get('phpmailer_fromName'); 98 | $mail->addReplyTo(c::get('phpmailer_ReplyTo') , c::get('phpmailer_ReplyToName')); 99 | $mail->WordWrap = 75; 100 | $mail->Subject = 'Newsletter: '.$subj; 101 | $mail->Body = $content; 102 | $mail->AltBody = 'add some text for non-html mail'; 103 | $mail->isHTML(true); // Set email format to HTML 104 | 105 | $mail->addAddress(c::get('phpmailer_from')); // add yourself to avoid "undisclosed recipients 106 | 107 | foreach(kirby()->site()->users()->filterBy('role', 'newsletter') as $user) { 108 | $mail->addBCC($user->email()); 109 | echo $user->email()."
"; 110 | } 111 | 112 | 113 | if(!$mail->send()) { 114 | echo ' Message could not be sent.' . PHP_EOL; 115 | echo ' Mailer Error: ' . $mail->ErrorInfo . PHP_EOL; 116 | } else { 117 | echo "

Email sent to above recipients on ".date('Y-m-d H:i:s') . ".

"; 118 | } 119 | $mail->ClearAllRecipients(); 120 | 121 | ?> 122 |
123 |
124 | 125 | -------------------------------------------------------------------------------- /assets/phpinc/class.pop3.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2012 - 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailer POP-Before-SMTP Authentication Class. 22 | * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication. 23 | * Does not support APOP. 24 | * @package PHPMailer 25 | * @author Richard Davey (original author) 26 | * @author Marcus Bointon (Synchro/coolbru) 27 | * @author Jim Jagielski (jimjag) 28 | * @author Andy Prevost (codeworxtech) 29 | */ 30 | class POP3 31 | { 32 | /** 33 | * The POP3 PHPMailer Version number. 34 | * @type string 35 | * @access public 36 | */ 37 | public $Version = '5.2.9'; 38 | 39 | /** 40 | * Default POP3 port number. 41 | * @type integer 42 | * @access public 43 | */ 44 | public $POP3_PORT = 110; 45 | 46 | /** 47 | * Default timeout in seconds. 48 | * @type integer 49 | * @access public 50 | */ 51 | public $POP3_TIMEOUT = 30; 52 | 53 | /** 54 | * POP3 Carriage Return + Line Feed. 55 | * @type string 56 | * @access public 57 | * @deprecated Use the constant instead 58 | */ 59 | public $CRLF = "\r\n"; 60 | 61 | /** 62 | * Debug display level. 63 | * Options: 0 = no, 1+ = yes 64 | * @type integer 65 | * @access public 66 | */ 67 | public $do_debug = 0; 68 | 69 | /** 70 | * POP3 mail server hostname. 71 | * @type string 72 | * @access public 73 | */ 74 | public $host; 75 | 76 | /** 77 | * POP3 port number. 78 | * @type integer 79 | * @access public 80 | */ 81 | public $port; 82 | 83 | /** 84 | * POP3 Timeout Value in seconds. 85 | * @type integer 86 | * @access public 87 | */ 88 | public $tval; 89 | 90 | /** 91 | * POP3 username 92 | * @type string 93 | * @access public 94 | */ 95 | public $username; 96 | 97 | /** 98 | * POP3 password. 99 | * @type string 100 | * @access public 101 | */ 102 | public $password; 103 | 104 | /** 105 | * Resource handle for the POP3 connection socket. 106 | * @type resource 107 | * @access private 108 | */ 109 | private $pop_conn; 110 | 111 | /** 112 | * Are we connected? 113 | * @type boolean 114 | * @access private 115 | */ 116 | private $connected = false; 117 | 118 | /** 119 | * Error container. 120 | * @type array 121 | * @access private 122 | */ 123 | private $errors = array(); 124 | 125 | /** 126 | * Line break constant 127 | */ 128 | const CRLF = "\r\n"; 129 | 130 | /** 131 | * Simple static wrapper for all-in-one POP before SMTP 132 | * @param $host 133 | * @param boolean $port 134 | * @param boolean $tval 135 | * @param string $username 136 | * @param string $password 137 | * @param integer $debug_level 138 | * @return boolean 139 | */ 140 | public static function popBeforeSmtp( 141 | $host, 142 | $port = false, 143 | $tval = false, 144 | $username = '', 145 | $password = '', 146 | $debug_level = 0 147 | ) { 148 | $pop = new POP3; 149 | return $pop->authorise($host, $port, $tval, $username, $password, $debug_level); 150 | } 151 | 152 | /** 153 | * Authenticate with a POP3 server. 154 | * A connect, login, disconnect sequence 155 | * appropriate for POP-before SMTP authorisation. 156 | * @access public 157 | * @param string $host The hostname to connect to 158 | * @param integer|boolean $port The port number to connect to 159 | * @param integer|boolean $timeout The timeout value 160 | * @param string $username 161 | * @param string $password 162 | * @param integer $debug_level 163 | * @return boolean 164 | */ 165 | public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0) 166 | { 167 | $this->host = $host; 168 | // If no port value provided, use default 169 | if ($port === false) { 170 | $this->port = $this->POP3_PORT; 171 | } else { 172 | $this->port = (integer)$port; 173 | } 174 | // If no timeout value provided, use default 175 | if ($timeout === false) { 176 | $this->tval = $this->POP3_TIMEOUT; 177 | } else { 178 | $this->tval = (integer)$timeout; 179 | } 180 | $this->do_debug = $debug_level; 181 | $this->username = $username; 182 | $this->password = $password; 183 | // Reset the error log 184 | $this->errors = array(); 185 | // connect 186 | $result = $this->connect($this->host, $this->port, $this->tval); 187 | if ($result) { 188 | $login_result = $this->login($this->username, $this->password); 189 | if ($login_result) { 190 | $this->disconnect(); 191 | return true; 192 | } 193 | } 194 | // We need to disconnect regardless of whether the login succeeded 195 | $this->disconnect(); 196 | return false; 197 | } 198 | 199 | /** 200 | * Connect to a POP3 server. 201 | * @access public 202 | * @param string $host 203 | * @param integer|boolean $port 204 | * @param integer $tval 205 | * @return boolean 206 | */ 207 | public function connect($host, $port = false, $tval = 30) 208 | { 209 | // Are we already connected? 210 | if ($this->connected) { 211 | return true; 212 | } 213 | 214 | //On Windows this will raise a PHP Warning error if the hostname doesn't exist. 215 | //Rather than suppress it with @fsockopen, capture it cleanly instead 216 | set_error_handler(array($this, 'catchWarning')); 217 | 218 | if ($port === false) { 219 | $port = $this->POP3_PORT; 220 | } 221 | 222 | // connect to the POP3 server 223 | $this->pop_conn = fsockopen( 224 | $host, // POP3 Host 225 | $port, // Port # 226 | $errno, // Error Number 227 | $errstr, // Error Message 228 | $tval 229 | ); // Timeout (seconds) 230 | // Restore the error handler 231 | restore_error_handler(); 232 | 233 | // Did we connect? 234 | if ($this->pop_conn === false) { 235 | // It would appear not... 236 | $this->setError(array( 237 | 'error' => "Failed to connect to server $host on port $port", 238 | 'errno' => $errno, 239 | 'errstr' => $errstr 240 | )); 241 | return false; 242 | } 243 | 244 | // Increase the stream time-out 245 | stream_set_timeout($this->pop_conn, $tval, 0); 246 | 247 | // Get the POP3 server response 248 | $pop3_response = $this->getResponse(); 249 | // Check for the +OK 250 | if ($this->checkResponse($pop3_response)) { 251 | // The connection is established and the POP3 server is talking 252 | $this->connected = true; 253 | return true; 254 | } 255 | return false; 256 | } 257 | 258 | /** 259 | * Log in to the POP3 server. 260 | * Does not support APOP (RFC 2828, 4949). 261 | * @access public 262 | * @param string $username 263 | * @param string $password 264 | * @return boolean 265 | */ 266 | public function login($username = '', $password = '') 267 | { 268 | if (!$this->connected) { 269 | $this->setError('Not connected to POP3 server'); 270 | } 271 | if (empty($username)) { 272 | $username = $this->username; 273 | } 274 | if (empty($password)) { 275 | $password = $this->password; 276 | } 277 | 278 | // Send the Username 279 | $this->sendString("USER $username" . self::CRLF); 280 | $pop3_response = $this->getResponse(); 281 | if ($this->checkResponse($pop3_response)) { 282 | // Send the Password 283 | $this->sendString("PASS $password" . self::CRLF); 284 | $pop3_response = $this->getResponse(); 285 | if ($this->checkResponse($pop3_response)) { 286 | return true; 287 | } 288 | } 289 | return false; 290 | } 291 | 292 | /** 293 | * Disconnect from the POP3 server. 294 | * @access public 295 | */ 296 | public function disconnect() 297 | { 298 | $this->sendString('QUIT'); 299 | //The QUIT command may cause the daemon to exit, which will kill our connection 300 | //So ignore errors here 301 | try { 302 | @fclose($this->pop_conn); 303 | } catch (Exception $e) { 304 | //Do nothing 305 | }; 306 | } 307 | 308 | /** 309 | * Get a response from the POP3 server. 310 | * $size is the maximum number of bytes to retrieve 311 | * @param integer $size 312 | * @return string 313 | * @access private 314 | */ 315 | private function getResponse($size = 128) 316 | { 317 | $response = fgets($this->pop_conn, $size); 318 | if ($this->do_debug >= 1) { 319 | echo "Server -> Client: $response"; 320 | } 321 | return $response; 322 | } 323 | 324 | /** 325 | * Send raw data to the POP3 server. 326 | * @param string $string 327 | * @return integer 328 | * @access private 329 | */ 330 | private function sendString($string) 331 | { 332 | if ($this->pop_conn) { 333 | if ($this->do_debug >= 2) { //Show client messages when debug >= 2 334 | echo "Client -> Server: $string"; 335 | } 336 | return fwrite($this->pop_conn, $string, strlen($string)); 337 | } 338 | return 0; 339 | } 340 | 341 | /** 342 | * Checks the POP3 server response. 343 | * Looks for for +OK or -ERR. 344 | * @param string $string 345 | * @return boolean 346 | * @access private 347 | */ 348 | private function checkResponse($string) 349 | { 350 | if (substr($string, 0, 3) !== '+OK') { 351 | $this->setError(array( 352 | 'error' => "Server reported an error: $string", 353 | 'errno' => 0, 354 | 'errstr' => '' 355 | )); 356 | return false; 357 | } else { 358 | return true; 359 | } 360 | } 361 | 362 | /** 363 | * Add an error to the internal error store. 364 | * Also display debug output if it's enabled. 365 | * @param $error 366 | */ 367 | private function setError($error) 368 | { 369 | $this->errors[] = $error; 370 | if ($this->do_debug >= 1) { 371 | echo '
';
372 |             foreach ($this->errors as $error) {
373 |                 print_r($error);
374 |             }
375 |             echo '
'; 376 | } 377 | } 378 | 379 | /** 380 | * POP3 connection error handler. 381 | * @param integer $errno 382 | * @param string $errstr 383 | * @param string $errfile 384 | * @param integer $errline 385 | * @access private 386 | */ 387 | private function catchWarning($errno, $errstr, $errfile, $errline) 388 | { 389 | $this->setError(array( 390 | 'error' => "Connecting to the POP3 server raised a PHP warning: ", 391 | 'errno' => $errno, 392 | 'errstr' => $errstr, 393 | 'errfile' => $errfile, 394 | 'errline' => $errline 395 | )); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /assets/phpinc/class.smtp.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailer RFC821 SMTP email transport class. 22 | * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. 23 | * @package PHPMailer 24 | * @author Chris Ryan 25 | * @author Marcus Bointon 26 | */ 27 | class SMTP 28 | { 29 | /** 30 | * The PHPMailer SMTP version number. 31 | * @type string 32 | */ 33 | const VERSION = '5.2.9'; 34 | 35 | /** 36 | * SMTP line break constant. 37 | * @type string 38 | */ 39 | const CRLF = "\r\n"; 40 | 41 | /** 42 | * The SMTP port to use if one is not specified. 43 | * @type integer 44 | */ 45 | const DEFAULT_SMTP_PORT = 25; 46 | 47 | /** 48 | * The maximum line length allowed by RFC 2822 section 2.1.1 49 | * @type integer 50 | */ 51 | const MAX_LINE_LENGTH = 998; 52 | 53 | /** 54 | * Debug level for no output 55 | */ 56 | const DEBUG_OFF = 0; 57 | 58 | /** 59 | * Debug level to show client -> server messages 60 | */ 61 | const DEBUG_CLIENT = 1; 62 | 63 | /** 64 | * Debug level to show client -> server and server -> client messages 65 | */ 66 | const DEBUG_SERVER = 2; 67 | 68 | /** 69 | * Debug level to show connection status, client -> server and server -> client messages 70 | */ 71 | const DEBUG_CONNECTION = 3; 72 | 73 | /** 74 | * Debug level to show all messages 75 | */ 76 | const DEBUG_LOWLEVEL = 4; 77 | 78 | /** 79 | * The PHPMailer SMTP Version number. 80 | * @type string 81 | * @deprecated Use the `VERSION` constant instead 82 | * @see SMTP::VERSION 83 | */ 84 | public $Version = '5.2.9'; 85 | 86 | /** 87 | * SMTP server port number. 88 | * @type integer 89 | * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead 90 | * @see SMTP::DEFAULT_SMTP_PORT 91 | */ 92 | public $SMTP_PORT = 25; 93 | 94 | /** 95 | * SMTP reply line ending. 96 | * @type string 97 | * @deprecated Use the `CRLF` constant instead 98 | * @see SMTP::CRLF 99 | */ 100 | public $CRLF = "\r\n"; 101 | 102 | /** 103 | * Debug output level. 104 | * Options: 105 | * * self::DEBUG_OFF (`0`) No debug output, default 106 | * * self::DEBUG_CLIENT (`1`) Client commands 107 | * * self::DEBUG_SERVER (`2`) Client commands and server responses 108 | * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status 109 | * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages 110 | * @type integer 111 | */ 112 | public $do_debug = self::DEBUG_OFF; 113 | 114 | /** 115 | * How to handle debug output. 116 | * Options: 117 | * * `echo` Output plain-text as-is, appropriate for CLI 118 | * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output 119 | * * `error_log` Output to error log as configured in php.ini 120 | * 121 | * Alternatively, you can provide a callable expecting two params: a message string and the debug level: 122 | * 123 | * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; 124 | * 125 | * @type string|callable 126 | */ 127 | public $Debugoutput = 'echo'; 128 | 129 | /** 130 | * Whether to use VERP. 131 | * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path 132 | * @link http://www.postfix.org/VERP_README.html Info on VERP 133 | * @type boolean 134 | */ 135 | public $do_verp = false; 136 | 137 | /** 138 | * The timeout value for connection, in seconds. 139 | * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 140 | * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. 141 | * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 142 | * @type integer 143 | */ 144 | public $Timeout = 300; 145 | 146 | /** 147 | * How long to wait for commands to complete, in seconds. 148 | * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 149 | * @type integer 150 | */ 151 | public $Timelimit = 300; 152 | 153 | /** 154 | * The socket for the server connection. 155 | * @type resource 156 | */ 157 | protected $smtp_conn; 158 | 159 | /** 160 | * Error message, if any, for the last call. 161 | * @type array 162 | */ 163 | protected $error = array(); 164 | 165 | /** 166 | * The reply the server sent to us for HELO. 167 | * If null, no HELO string has yet been received. 168 | * @type string|null 169 | */ 170 | protected $helo_rply = null; 171 | 172 | /** 173 | * The most recent reply received from the server. 174 | * @type string 175 | */ 176 | protected $last_reply = ''; 177 | 178 | /** 179 | * Output debugging info via a user-selected method. 180 | * @see SMTP::$Debugoutput 181 | * @see SMTP::$do_debug 182 | * @param string $str Debug string to output 183 | * @param integer $level The debug level of this message; see DEBUG_* constants 184 | * @return void 185 | */ 186 | protected function edebug($str, $level = 0) 187 | { 188 | if ($level > $this->do_debug) { 189 | return; 190 | } 191 | if (is_callable($this->Debugoutput)) { 192 | call_user_func($this->Debugoutput, $str, $this->do_debug); 193 | return; 194 | } 195 | switch ($this->Debugoutput) { 196 | case 'error_log': 197 | //Don't output, just log 198 | error_log($str); 199 | break; 200 | case 'html': 201 | //Cleans up output a bit for a better looking, HTML-safe output 202 | echo htmlentities( 203 | preg_replace('/[\r\n]+/', '', $str), 204 | ENT_QUOTES, 205 | 'UTF-8' 206 | ) 207 | . "
\n"; 208 | break; 209 | case 'echo': 210 | default: 211 | //Normalize line breaks 212 | $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); 213 | echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 214 | "\n", 215 | "\n \t ", 216 | trim($str) 217 | )."\n"; 218 | } 219 | } 220 | 221 | /** 222 | * Connect to an SMTP server. 223 | * @param string $host SMTP server IP or host name 224 | * @param integer $port The port number to connect to 225 | * @param integer $timeout How long to wait for the connection to open 226 | * @param array $options An array of options for stream_context_create() 227 | * @access public 228 | * @return boolean 229 | */ 230 | public function connect($host, $port = null, $timeout = 30, $options = array()) 231 | { 232 | static $streamok; 233 | //This is enabled by default since 5.0.0 but some providers disable it 234 | //Check this once and cache the result 235 | if (is_null($streamok)) { 236 | $streamok = function_exists('stream_socket_client'); 237 | } 238 | // Clear errors to avoid confusion 239 | $this->error = array(); 240 | // Make sure we are __not__ connected 241 | if ($this->connected()) { 242 | // Already connected, generate error 243 | $this->error = array('error' => 'Already connected to a server'); 244 | return false; 245 | } 246 | if (empty($port)) { 247 | $port = self::DEFAULT_SMTP_PORT; 248 | } 249 | // Connect to the SMTP server 250 | $this->edebug( 251 | "Connection: opening to $host:$port, t=$timeout, opt=".var_export($options, true), 252 | self::DEBUG_CONNECTION 253 | ); 254 | $errno = 0; 255 | $errstr = ''; 256 | if ($streamok) { 257 | $socket_context = stream_context_create($options); 258 | //Suppress errors; connection failures are handled at a higher level 259 | $this->smtp_conn = @stream_socket_client( 260 | $host . ":" . $port, 261 | $errno, 262 | $errstr, 263 | $timeout, 264 | STREAM_CLIENT_CONNECT, 265 | $socket_context 266 | ); 267 | } else { 268 | //Fall back to fsockopen which should work in more places, but is missing some features 269 | $this->edebug( 270 | "Connection: stream_socket_client not available, falling back to fsockopen", 271 | self::DEBUG_CONNECTION 272 | ); 273 | $this->smtp_conn = fsockopen( 274 | $host, 275 | $port, 276 | $errno, 277 | $errstr, 278 | $timeout 279 | ); 280 | } 281 | // Verify we connected properly 282 | if (!is_resource($this->smtp_conn)) { 283 | $this->error = array( 284 | 'error' => 'Failed to connect to server', 285 | 'errno' => $errno, 286 | 'errstr' => $errstr 287 | ); 288 | $this->edebug( 289 | 'SMTP ERROR: ' . $this->error['error'] 290 | . ": $errstr ($errno)", 291 | self::DEBUG_CLIENT 292 | ); 293 | return false; 294 | } 295 | $this->edebug('Connection: opened', self::DEBUG_CONNECTION); 296 | // SMTP server can take longer to respond, give longer timeout for first read 297 | // Windows does not have support for this timeout function 298 | if (substr(PHP_OS, 0, 3) != 'WIN') { 299 | $max = ini_get('max_execution_time'); 300 | if ($max != 0 && $timeout > $max) { // Don't bother if unlimited 301 | @set_time_limit($timeout); 302 | } 303 | stream_set_timeout($this->smtp_conn, $timeout, 0); 304 | } 305 | // Get any announcement 306 | $announce = $this->get_lines(); 307 | $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); 308 | return true; 309 | } 310 | 311 | /** 312 | * Initiate a TLS (encrypted) session. 313 | * @access public 314 | * @return boolean 315 | */ 316 | public function startTLS() 317 | { 318 | if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { 319 | return false; 320 | } 321 | // Begin encrypted connection 322 | if (!stream_socket_enable_crypto( 323 | $this->smtp_conn, 324 | true, 325 | STREAM_CRYPTO_METHOD_TLS_CLIENT 326 | )) { 327 | return false; 328 | } 329 | return true; 330 | } 331 | 332 | /** 333 | * Perform SMTP authentication. 334 | * Must be run after hello(). 335 | * @see hello() 336 | * @param string $username The user name 337 | * @param string $password The password 338 | * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5) 339 | * @param string $realm The auth realm for NTLM 340 | * @param string $workstation The auth workstation for NTLM 341 | * @access public 342 | * @return boolean True if successfully authenticated. 343 | */ 344 | public function authenticate( 345 | $username, 346 | $password, 347 | $authtype = 'LOGIN', 348 | $realm = '', 349 | $workstation = '' 350 | ) { 351 | if (empty($authtype)) { 352 | $authtype = 'LOGIN'; 353 | } 354 | switch ($authtype) { 355 | case 'PLAIN': 356 | // Start authentication 357 | if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { 358 | return false; 359 | } 360 | // Send encoded username and password 361 | if (!$this->sendCommand( 362 | 'User & Password', 363 | base64_encode("\0" . $username . "\0" . $password), 364 | 235 365 | ) 366 | ) { 367 | return false; 368 | } 369 | break; 370 | case 'LOGIN': 371 | // Start authentication 372 | if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { 373 | return false; 374 | } 375 | if (!$this->sendCommand("Username", base64_encode($username), 334)) { 376 | return false; 377 | } 378 | if (!$this->sendCommand("Password", base64_encode($password), 235)) { 379 | return false; 380 | } 381 | break; 382 | case 'NTLM': 383 | /* 384 | * ntlm_sasl_client.php 385 | * Bundled with Permission 386 | * 387 | * How to telnet in windows: 388 | * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx 389 | * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication 390 | */ 391 | require_once 'extras/ntlm_sasl_client.php'; 392 | $temp = new stdClass(); 393 | $ntlm_client = new ntlm_sasl_client_class; 394 | //Check that functions are available 395 | if (!$ntlm_client->Initialize($temp)) { 396 | $this->error = array('error' => $temp->error); 397 | $this->edebug( 398 | 'You need to enable some modules in your php.ini file: ' 399 | . $this->error['error'], 400 | self::DEBUG_CLIENT 401 | ); 402 | return false; 403 | } 404 | //msg1 405 | $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1 406 | 407 | if (!$this->sendCommand( 408 | 'AUTH NTLM', 409 | 'AUTH NTLM ' . base64_encode($msg1), 410 | 334 411 | ) 412 | ) { 413 | return false; 414 | } 415 | //Though 0 based, there is a white space after the 3 digit number 416 | //msg2 417 | $challenge = substr($this->last_reply, 3); 418 | $challenge = base64_decode($challenge); 419 | $ntlm_res = $ntlm_client->NTLMResponse( 420 | substr($challenge, 24, 8), 421 | $password 422 | ); 423 | //msg3 424 | $msg3 = $ntlm_client->TypeMsg3( 425 | $ntlm_res, 426 | $username, 427 | $realm, 428 | $workstation 429 | ); 430 | // send encoded username 431 | return $this->sendCommand('Username', base64_encode($msg3), 235); 432 | case 'CRAM-MD5': 433 | // Start authentication 434 | if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { 435 | return false; 436 | } 437 | // Get the challenge 438 | $challenge = base64_decode(substr($this->last_reply, 4)); 439 | 440 | // Build the response 441 | $response = $username . ' ' . $this->hmac($challenge, $password); 442 | 443 | // send encoded credentials 444 | return $this->sendCommand('Username', base64_encode($response), 235); 445 | } 446 | return true; 447 | } 448 | 449 | /** 450 | * Calculate an MD5 HMAC hash. 451 | * Works like hash_hmac('md5', $data, $key) 452 | * in case that function is not available 453 | * @param string $data The data to hash 454 | * @param string $key The key to hash with 455 | * @access protected 456 | * @return string 457 | */ 458 | protected function hmac($data, $key) 459 | { 460 | if (function_exists('hash_hmac')) { 461 | return hash_hmac('md5', $data, $key); 462 | } 463 | 464 | // The following borrowed from 465 | // http://php.net/manual/en/function.mhash.php#27225 466 | 467 | // RFC 2104 HMAC implementation for php. 468 | // Creates an md5 HMAC. 469 | // Eliminates the need to install mhash to compute a HMAC 470 | // by Lance Rushing 471 | 472 | $bytelen = 64; // byte length for md5 473 | if (strlen($key) > $bytelen) { 474 | $key = pack('H*', md5($key)); 475 | } 476 | $key = str_pad($key, $bytelen, chr(0x00)); 477 | $ipad = str_pad('', $bytelen, chr(0x36)); 478 | $opad = str_pad('', $bytelen, chr(0x5c)); 479 | $k_ipad = $key ^ $ipad; 480 | $k_opad = $key ^ $opad; 481 | 482 | return md5($k_opad . pack('H*', md5($k_ipad . $data))); 483 | } 484 | 485 | /** 486 | * Check connection state. 487 | * @access public 488 | * @return boolean True if connected. 489 | */ 490 | public function connected() 491 | { 492 | if (is_resource($this->smtp_conn)) { 493 | $sock_status = stream_get_meta_data($this->smtp_conn); 494 | if ($sock_status['eof']) { 495 | // The socket is valid but we are not connected 496 | $this->edebug( 497 | 'SMTP NOTICE: EOF caught while checking if connected', 498 | self::DEBUG_CLIENT 499 | ); 500 | $this->close(); 501 | return false; 502 | } 503 | return true; // everything looks good 504 | } 505 | return false; 506 | } 507 | 508 | /** 509 | * Close the socket and clean up the state of the class. 510 | * Don't use this function without first trying to use QUIT. 511 | * @see quit() 512 | * @access public 513 | * @return void 514 | */ 515 | public function close() 516 | { 517 | $this->error = array(); 518 | $this->helo_rply = null; 519 | if (is_resource($this->smtp_conn)) { 520 | // close the connection and cleanup 521 | fclose($this->smtp_conn); 522 | $this->smtp_conn = null; //Makes for cleaner serialization 523 | $this->edebug('Connection: closed', self::DEBUG_CONNECTION); 524 | } 525 | } 526 | 527 | /** 528 | * Send an SMTP DATA command. 529 | * Issues a data command and sends the msg_data to the server, 530 | * finializing the mail transaction. $msg_data is the message 531 | * that is to be send with the headers. Each header needs to be 532 | * on a single line followed by a with the message headers 533 | * and the message body being separated by and additional . 534 | * Implements rfc 821: DATA 535 | * @param string $msg_data Message data to send 536 | * @access public 537 | * @return boolean 538 | */ 539 | public function data($msg_data) 540 | { 541 | //This will use the standard timelimit 542 | if (!$this->sendCommand('DATA', 'DATA', 354)) { 543 | return false; 544 | } 545 | 546 | /* The server is ready to accept data! 547 | * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) 548 | * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into 549 | * smaller lines to fit within the limit. 550 | * We will also look for lines that start with a '.' and prepend an additional '.'. 551 | * NOTE: this does not count towards line-length limit. 552 | */ 553 | 554 | // Normalize line breaks before exploding 555 | $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); 556 | 557 | /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field 558 | * of the first line (':' separated) does not contain a space then it _should_ be a header and we will 559 | * process all lines before a blank line as headers. 560 | */ 561 | 562 | $field = substr($lines[0], 0, strpos($lines[0], ':')); 563 | $in_headers = false; 564 | if (!empty($field) && strpos($field, ' ') === false) { 565 | $in_headers = true; 566 | } 567 | 568 | foreach ($lines as $line) { 569 | $lines_out = array(); 570 | if ($in_headers and $line == '') { 571 | $in_headers = false; 572 | } 573 | //We need to break this line up into several smaller lines 574 | //This is a small micro-optimisation: isset($str[$len]) is equivalent to (strlen($str) > $len) 575 | while (isset($line[self::MAX_LINE_LENGTH])) { 576 | //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on 577 | //so as to avoid breaking in the middle of a word 578 | $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); 579 | if (!$pos) { //Deliberately matches both false and 0 580 | //No nice break found, add a hard break 581 | $pos = self::MAX_LINE_LENGTH - 1; 582 | $lines_out[] = substr($line, 0, $pos); 583 | $line = substr($line, $pos); 584 | } else { 585 | //Break at the found point 586 | $lines_out[] = substr($line, 0, $pos); 587 | //Move along by the amount we dealt with 588 | $line = substr($line, $pos + 1); 589 | } 590 | //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 591 | if ($in_headers) { 592 | $line = "\t" . $line; 593 | } 594 | } 595 | $lines_out[] = $line; 596 | 597 | //Send the lines to the server 598 | foreach ($lines_out as $line_out) { 599 | //RFC2821 section 4.5.2 600 | if (!empty($line_out) and $line_out[0] == '.') { 601 | $line_out = '.' . $line_out; 602 | } 603 | $this->client_send($line_out . self::CRLF); 604 | } 605 | } 606 | 607 | //Message data has been sent, complete the command 608 | //Increase timelimit for end of DATA command 609 | $savetimelimit = $this->Timelimit; 610 | $this->Timelimit = $this->Timelimit * 2; 611 | $result = $this->sendCommand('DATA END', '.', 250); 612 | //Restore timelimit 613 | $this->Timelimit = $savetimelimit; 614 | return $result; 615 | } 616 | 617 | /** 618 | * Send an SMTP HELO or EHLO command. 619 | * Used to identify the sending server to the receiving server. 620 | * This makes sure that client and server are in a known state. 621 | * Implements RFC 821: HELO 622 | * and RFC 2821 EHLO. 623 | * @param string $host The host name or IP to connect to 624 | * @access public 625 | * @return boolean 626 | */ 627 | public function hello($host = '') 628 | { 629 | //Try extended hello first (RFC 2821) 630 | return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); 631 | } 632 | 633 | /** 634 | * Send an SMTP HELO or EHLO command. 635 | * Low-level implementation used by hello() 636 | * @see hello() 637 | * @param string $hello The HELO string 638 | * @param string $host The hostname to say we are 639 | * @access protected 640 | * @return boolean 641 | */ 642 | protected function sendHello($hello, $host) 643 | { 644 | $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); 645 | $this->helo_rply = $this->last_reply; 646 | return $noerror; 647 | } 648 | 649 | /** 650 | * Send an SMTP MAIL command. 651 | * Starts a mail transaction from the email address specified in 652 | * $from. Returns true if successful or false otherwise. If True 653 | * the mail transaction is started and then one or more recipient 654 | * commands may be called followed by a data command. 655 | * Implements rfc 821: MAIL FROM: 656 | * @param string $from Source address of this message 657 | * @access public 658 | * @return boolean 659 | */ 660 | public function mail($from) 661 | { 662 | $useVerp = ($this->do_verp ? ' XVERP' : ''); 663 | return $this->sendCommand( 664 | 'MAIL FROM', 665 | 'MAIL FROM:<' . $from . '>' . $useVerp, 666 | 250 667 | ); 668 | } 669 | 670 | /** 671 | * Send an SMTP QUIT command. 672 | * Closes the socket if there is no error or the $close_on_error argument is true. 673 | * Implements from rfc 821: QUIT 674 | * @param boolean $close_on_error Should the connection close if an error occurs? 675 | * @access public 676 | * @return boolean 677 | */ 678 | public function quit($close_on_error = true) 679 | { 680 | $noerror = $this->sendCommand('QUIT', 'QUIT', 221); 681 | $err = $this->error; //Save any error 682 | if ($noerror or $close_on_error) { 683 | $this->close(); 684 | $this->error = $err; //Restore any error from the quit command 685 | } 686 | return $noerror; 687 | } 688 | 689 | /** 690 | * Send an SMTP RCPT command. 691 | * Sets the TO argument to $toaddr. 692 | * Returns true if the recipient was accepted false if it was rejected. 693 | * Implements from rfc 821: RCPT TO: 694 | * @param string $toaddr The address the message is being sent to 695 | * @access public 696 | * @return boolean 697 | */ 698 | public function recipient($toaddr) 699 | { 700 | return $this->sendCommand( 701 | 'RCPT TO', 702 | 'RCPT TO:<' . $toaddr . '>', 703 | array(250, 251) 704 | ); 705 | } 706 | 707 | /** 708 | * Send an SMTP RSET command. 709 | * Abort any transaction that is currently in progress. 710 | * Implements rfc 821: RSET 711 | * @access public 712 | * @return boolean True on success. 713 | */ 714 | public function reset() 715 | { 716 | return $this->sendCommand('RSET', 'RSET', 250); 717 | } 718 | 719 | /** 720 | * Send a command to an SMTP server and check its return code. 721 | * @param string $command The command name - not sent to the server 722 | * @param string $commandstring The actual command to send 723 | * @param integer|array $expect One or more expected integer success codes 724 | * @access protected 725 | * @return boolean True on success. 726 | */ 727 | protected function sendCommand($command, $commandstring, $expect) 728 | { 729 | if (!$this->connected()) { 730 | $this->error = array( 731 | 'error' => "Called $command without being connected" 732 | ); 733 | return false; 734 | } 735 | $this->client_send($commandstring . self::CRLF); 736 | 737 | $this->last_reply = $this->get_lines(); 738 | $code = substr($this->last_reply, 0, 3); 739 | 740 | $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); 741 | 742 | if (!in_array($code, (array)$expect)) { 743 | $this->error = array( 744 | 'error' => "$command command failed", 745 | 'smtp_code' => $code, 746 | 'detail' => substr($this->last_reply, 4) 747 | ); 748 | $this->edebug( 749 | 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, 750 | self::DEBUG_CLIENT 751 | ); 752 | return false; 753 | } 754 | 755 | $this->error = array(); 756 | return true; 757 | } 758 | 759 | /** 760 | * Send an SMTP SAML command. 761 | * Starts a mail transaction from the email address specified in $from. 762 | * Returns true if successful or false otherwise. If True 763 | * the mail transaction is started and then one or more recipient 764 | * commands may be called followed by a data command. This command 765 | * will send the message to the users terminal if they are logged 766 | * in and send them an email. 767 | * Implements rfc 821: SAML FROM: 768 | * @param string $from The address the message is from 769 | * @access public 770 | * @return boolean 771 | */ 772 | public function sendAndMail($from) 773 | { 774 | return $this->sendCommand('SAML', "SAML FROM:$from", 250); 775 | } 776 | 777 | /** 778 | * Send an SMTP VRFY command. 779 | * @param string $name The name to verify 780 | * @access public 781 | * @return boolean 782 | */ 783 | public function verify($name) 784 | { 785 | return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); 786 | } 787 | 788 | /** 789 | * Send an SMTP NOOP command. 790 | * Used to keep keep-alives alive, doesn't actually do anything 791 | * @access public 792 | * @return boolean 793 | */ 794 | public function noop() 795 | { 796 | return $this->sendCommand('NOOP', 'NOOP', 250); 797 | } 798 | 799 | /** 800 | * Send an SMTP TURN command. 801 | * This is an optional command for SMTP that this class does not support. 802 | * This method is here to make the RFC821 Definition complete for this class 803 | * and _may_ be implemented in future 804 | * Implements from rfc 821: TURN 805 | * @access public 806 | * @return boolean 807 | */ 808 | public function turn() 809 | { 810 | $this->error = array( 811 | 'error' => 'The SMTP TURN command is not implemented' 812 | ); 813 | $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); 814 | return false; 815 | } 816 | 817 | /** 818 | * Send raw data to the server. 819 | * @param string $data The data to send 820 | * @access public 821 | * @return integer|boolean The number of bytes sent to the server or false on error 822 | */ 823 | public function client_send($data) 824 | { 825 | $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); 826 | return fwrite($this->smtp_conn, $data); 827 | } 828 | 829 | /** 830 | * Get the latest error. 831 | * @access public 832 | * @return array 833 | */ 834 | public function getError() 835 | { 836 | return $this->error; 837 | } 838 | 839 | /** 840 | * Get the last reply from the server. 841 | * @access public 842 | * @return string 843 | */ 844 | public function getLastReply() 845 | { 846 | return $this->last_reply; 847 | } 848 | 849 | /** 850 | * Read the SMTP server's response. 851 | * Either before eof or socket timeout occurs on the operation. 852 | * With SMTP we can tell if we have more lines to read if the 853 | * 4th character is '-' symbol. If it is a space then we don't 854 | * need to read anything else. 855 | * @access protected 856 | * @return string 857 | */ 858 | protected function get_lines() 859 | { 860 | // If the connection is bad, give up straight away 861 | if (!is_resource($this->smtp_conn)) { 862 | return ''; 863 | } 864 | $data = ''; 865 | $endtime = 0; 866 | stream_set_timeout($this->smtp_conn, $this->Timeout); 867 | if ($this->Timelimit > 0) { 868 | $endtime = time() + $this->Timelimit; 869 | } 870 | while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { 871 | $str = @fgets($this->smtp_conn, 515); 872 | $this->edebug("SMTP -> get_lines(): \$data was \"$data\"", self::DEBUG_LOWLEVEL); 873 | $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); 874 | $data .= $str; 875 | $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); 876 | // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen 877 | if ((isset($str[3]) and $str[3] == ' ')) { 878 | break; 879 | } 880 | // Timed-out? Log and break 881 | $info = stream_get_meta_data($this->smtp_conn); 882 | if ($info['timed_out']) { 883 | $this->edebug( 884 | 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', 885 | self::DEBUG_LOWLEVEL 886 | ); 887 | break; 888 | } 889 | // Now check if reads took too long 890 | if ($endtime and time() > $endtime) { 891 | $this->edebug( 892 | 'SMTP -> get_lines(): timelimit reached ('. 893 | $this->Timelimit . ' sec)', 894 | self::DEBUG_LOWLEVEL 895 | ); 896 | break; 897 | } 898 | } 899 | return $data; 900 | } 901 | 902 | /** 903 | * Enable or disable VERP address generation. 904 | * @param boolean $enabled 905 | */ 906 | public function setVerp($enabled = false) 907 | { 908 | $this->do_verp = $enabled; 909 | } 910 | 911 | /** 912 | * Get VERP address generation mode. 913 | * @return boolean 914 | */ 915 | public function getVerp() 916 | { 917 | return $this->do_verp; 918 | } 919 | 920 | /** 921 | * Set debug output method. 922 | * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. 923 | */ 924 | public function setDebugOutput($method = 'echo') 925 | { 926 | $this->Debugoutput = $method; 927 | } 928 | 929 | /** 930 | * Get debug output method. 931 | * @return string 932 | */ 933 | public function getDebugOutput() 934 | { 935 | return $this->Debugoutput; 936 | } 937 | 938 | /** 939 | * Set debug output level. 940 | * @param integer $level 941 | */ 942 | public function setDebugLevel($level = 0) 943 | { 944 | $this->do_debug = $level; 945 | } 946 | 947 | /** 948 | * Get debug output level. 949 | * @return integer 950 | */ 951 | public function getDebugLevel() 952 | { 953 | return $this->do_debug; 954 | } 955 | 956 | /** 957 | * Set SMTP timeout. 958 | * @param integer $timeout 959 | */ 960 | public function setTimeout($timeout = 0) 961 | { 962 | $this->Timeout = $timeout; 963 | } 964 | 965 | /** 966 | * Get SMTP timeout. 967 | * @return integer 968 | */ 969 | public function getTimeout() 970 | { 971 | return $this->Timeout; 972 | } 973 | } 974 | -------------------------------------------------------------------------------- /assets/phpinc/class.phpmailer.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2012 - 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailer - PHP email creation and transport class. 22 | * @package PHPMailer 23 | * @author Marcus Bointon (Synchro/coolbru) 24 | * @author Jim Jagielski (jimjag) 25 | * @author Andy Prevost (codeworxtech) 26 | * @author Brent R. Matzelle (original founder) 27 | */ 28 | class PHPMailer 29 | { 30 | /** 31 | * The PHPMailer Version number. 32 | * @type string 33 | */ 34 | public $Version = '5.2.9'; 35 | 36 | /** 37 | * Email priority. 38 | * Options: 1 = High, 3 = Normal, 5 = low. 39 | * @type integer 40 | */ 41 | public $Priority = 3; 42 | 43 | /** 44 | * The character set of the message. 45 | * @type string 46 | */ 47 | public $CharSet = 'iso-8859-1'; 48 | 49 | /** 50 | * The MIME Content-type of the message. 51 | * @type string 52 | */ 53 | public $ContentType = 'text/plain'; 54 | 55 | /** 56 | * The message encoding. 57 | * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". 58 | * @type string 59 | */ 60 | public $Encoding = '8bit'; 61 | 62 | /** 63 | * Holds the most recent mailer error message. 64 | * @type string 65 | */ 66 | public $ErrorInfo = ''; 67 | 68 | /** 69 | * The From email address for the message. 70 | * @type string 71 | */ 72 | public $From = 'root@localhost'; 73 | 74 | /** 75 | * The From name of the message. 76 | * @type string 77 | */ 78 | public $FromName = 'Root User'; 79 | 80 | /** 81 | * The Sender email (Return-Path) of the message. 82 | * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. 83 | * @type string 84 | */ 85 | public $Sender = ''; 86 | 87 | /** 88 | * The Return-Path of the message. 89 | * If empty, it will be set to either From or Sender. 90 | * @type string 91 | * @deprecated Email senders should never set a return-path header; 92 | * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything. 93 | * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference 94 | */ 95 | public $ReturnPath = ''; 96 | 97 | /** 98 | * The Subject of the message. 99 | * @type string 100 | */ 101 | public $Subject = ''; 102 | 103 | /** 104 | * An HTML or plain text message body. 105 | * If HTML then call isHTML(true). 106 | * @type string 107 | */ 108 | public $Body = ''; 109 | 110 | /** 111 | * The plain-text message body. 112 | * This body can be read by mail clients that do not have HTML email 113 | * capability such as mutt & Eudora. 114 | * Clients that can read HTML will view the normal Body. 115 | * @type string 116 | */ 117 | public $AltBody = ''; 118 | 119 | /** 120 | * An iCal message part body. 121 | * Only supported in simple alt or alt_inline message types 122 | * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator 123 | * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ 124 | * @link http://kigkonsult.se/iCalcreator/ 125 | * @type string 126 | */ 127 | public $Ical = ''; 128 | 129 | /** 130 | * The complete compiled MIME message body. 131 | * @access protected 132 | * @type string 133 | */ 134 | protected $MIMEBody = ''; 135 | 136 | /** 137 | * The complete compiled MIME message headers. 138 | * @type string 139 | * @access protected 140 | */ 141 | protected $MIMEHeader = ''; 142 | 143 | /** 144 | * Extra headers that createHeader() doesn't fold in. 145 | * @type string 146 | * @access protected 147 | */ 148 | protected $mailHeader = ''; 149 | 150 | /** 151 | * Word-wrap the message body to this number of chars. 152 | * @type integer 153 | */ 154 | public $WordWrap = 0; 155 | 156 | /** 157 | * Which method to use to send mail. 158 | * Options: "mail", "sendmail", or "smtp". 159 | * @type string 160 | */ 161 | public $Mailer = 'mail'; 162 | 163 | /** 164 | * The path to the sendmail program. 165 | * @type string 166 | */ 167 | public $Sendmail = '/usr/sbin/sendmail'; 168 | 169 | /** 170 | * Whether mail() uses a fully sendmail-compatible MTA. 171 | * One which supports sendmail's "-oi -f" options. 172 | * @type boolean 173 | */ 174 | public $UseSendmailOptions = true; 175 | 176 | /** 177 | * Path to PHPMailer plugins. 178 | * Useful if the SMTP class is not in the PHP include path. 179 | * @type string 180 | * @deprecated Should not be needed now there is an autoloader. 181 | */ 182 | public $PluginDir = ''; 183 | 184 | /** 185 | * The email address that a reading confirmation should be sent to. 186 | * @type string 187 | */ 188 | public $ConfirmReadingTo = ''; 189 | 190 | /** 191 | * The hostname to use in Message-Id and Received headers 192 | * and as default HELO string. 193 | * If empty, the value returned 194 | * by SERVER_NAME is used or 'localhost.localdomain'. 195 | * @type string 196 | */ 197 | public $Hostname = ''; 198 | 199 | /** 200 | * An ID to be used in the Message-Id header. 201 | * If empty, a unique id will be generated. 202 | * @type string 203 | */ 204 | public $MessageID = ''; 205 | 206 | /** 207 | * The message Date to be used in the Date header. 208 | * If empty, the current date will be added. 209 | * @type string 210 | */ 211 | public $MessageDate = ''; 212 | 213 | /** 214 | * SMTP hosts. 215 | * Either a single hostname or multiple semicolon-delimited hostnames. 216 | * You can also specify a different port 217 | * for each host by using this format: [hostname:port] 218 | * (e.g. "smtp1.example.com:25;smtp2.example.com"). 219 | * You can also specify encryption type, for example: 220 | * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). 221 | * Hosts will be tried in order. 222 | * @type string 223 | */ 224 | public $Host = 'localhost'; 225 | 226 | /** 227 | * The default SMTP server port. 228 | * @type integer 229 | * @TODO Why is this needed when the SMTP class takes care of it? 230 | */ 231 | public $Port = 25; 232 | 233 | /** 234 | * The SMTP HELO of the message. 235 | * Default is $Hostname. 236 | * @type string 237 | * @see PHPMailer::$Hostname 238 | */ 239 | public $Helo = ''; 240 | 241 | /** 242 | * The secure connection prefix. 243 | * Options: "", "ssl" or "tls" 244 | * @type string 245 | */ 246 | public $SMTPSecure = ''; 247 | 248 | /** 249 | * Whether to use SMTP authentication. 250 | * Uses the Username and Password properties. 251 | * @type boolean 252 | * @see PHPMailer::$Username 253 | * @see PHPMailer::$Password 254 | */ 255 | public $SMTPAuth = false; 256 | 257 | /** 258 | * SMTP username. 259 | * @type string 260 | */ 261 | public $Username = ''; 262 | 263 | /** 264 | * SMTP password. 265 | * @type string 266 | */ 267 | public $Password = ''; 268 | 269 | /** 270 | * SMTP auth type. 271 | * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5 272 | * @type string 273 | */ 274 | public $AuthType = ''; 275 | 276 | /** 277 | * SMTP realm. 278 | * Used for NTLM auth 279 | * @type string 280 | */ 281 | public $Realm = ''; 282 | 283 | /** 284 | * SMTP workstation. 285 | * Used for NTLM auth 286 | * @type string 287 | */ 288 | public $Workstation = ''; 289 | 290 | /** 291 | * The SMTP server timeout in seconds. 292 | * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 293 | * @type integer 294 | */ 295 | public $Timeout = 300; 296 | 297 | /** 298 | * SMTP class debug output mode. 299 | * Debug output level. 300 | * Options: 301 | * * `0` No output 302 | * * `1` Commands 303 | * * `2` Data and commands 304 | * * `3` As 2 plus connection status 305 | * * `4` Low-level data output 306 | * @type integer 307 | * @see SMTP::$do_debug 308 | */ 309 | public $SMTPDebug = 0; 310 | 311 | /** 312 | * How to handle debug output. 313 | * Options: 314 | * * `echo` Output plain-text as-is, appropriate for CLI 315 | * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output 316 | * * `error_log` Output to error log as configured in php.ini 317 | * 318 | * Alternatively, you can provide a callable expecting two params: a message string and the debug level: 319 | * 320 | * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; 321 | * 322 | * @type string|callable 323 | * @see SMTP::$Debugoutput 324 | */ 325 | public $Debugoutput = 'echo'; 326 | 327 | /** 328 | * Whether to keep SMTP connection open after each message. 329 | * If this is set to true then to close the connection 330 | * requires an explicit call to smtpClose(). 331 | * @type boolean 332 | */ 333 | public $SMTPKeepAlive = false; 334 | 335 | /** 336 | * Whether to split multiple to addresses into multiple messages 337 | * or send them all in one message. 338 | * @type boolean 339 | */ 340 | public $SingleTo = false; 341 | 342 | /** 343 | * Storage for addresses when SingleTo is enabled. 344 | * @type array 345 | * @TODO This should really not be public 346 | */ 347 | public $SingleToArray = array(); 348 | 349 | /** 350 | * Whether to generate VERP addresses on send. 351 | * Only applicable when sending via SMTP. 352 | * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path 353 | * @link http://www.postfix.org/VERP_README.html Postfix VERP info 354 | * @type boolean 355 | */ 356 | public $do_verp = false; 357 | 358 | /** 359 | * Whether to allow sending messages with an empty body. 360 | * @type boolean 361 | */ 362 | public $AllowEmpty = false; 363 | 364 | /** 365 | * The default line ending. 366 | * @note The default remains "\n". We force CRLF where we know 367 | * it must be used via self::CRLF. 368 | * @type string 369 | */ 370 | public $LE = "\n"; 371 | 372 | /** 373 | * DKIM selector. 374 | * @type string 375 | */ 376 | public $DKIM_selector = ''; 377 | 378 | /** 379 | * DKIM Identity. 380 | * Usually the email address used as the source of the email 381 | * @type string 382 | */ 383 | public $DKIM_identity = ''; 384 | 385 | /** 386 | * DKIM passphrase. 387 | * Used if your key is encrypted. 388 | * @type string 389 | */ 390 | public $DKIM_passphrase = ''; 391 | 392 | /** 393 | * DKIM signing domain name. 394 | * @example 'example.com' 395 | * @type string 396 | */ 397 | public $DKIM_domain = ''; 398 | 399 | /** 400 | * DKIM private key file path. 401 | * @type string 402 | */ 403 | public $DKIM_private = ''; 404 | 405 | /** 406 | * Callback Action function name. 407 | * 408 | * The function that handles the result of the send email action. 409 | * It is called out by send() for each email sent. 410 | * 411 | * Value can be any php callable: http://www.php.net/is_callable 412 | * 413 | * Parameters: 414 | * boolean $result result of the send action 415 | * string $to email address of the recipient 416 | * string $cc cc email addresses 417 | * string $bcc bcc email addresses 418 | * string $subject the subject 419 | * string $body the email body 420 | * string $from email address of sender 421 | * @type string 422 | */ 423 | public $action_function = ''; 424 | 425 | /** 426 | * What to use in the X-Mailer header. 427 | * Options: null for default, whitespace for none, or a string to use 428 | * @type string 429 | */ 430 | public $XMailer = ''; 431 | 432 | /** 433 | * An instance of the SMTP sender class. 434 | * @type SMTP 435 | * @access protected 436 | */ 437 | protected $smtp = null; 438 | 439 | /** 440 | * The array of 'to' addresses. 441 | * @type array 442 | * @access protected 443 | */ 444 | protected $to = array(); 445 | 446 | /** 447 | * The array of 'cc' addresses. 448 | * @type array 449 | * @access protected 450 | */ 451 | protected $cc = array(); 452 | 453 | /** 454 | * The array of 'bcc' addresses. 455 | * @type array 456 | * @access protected 457 | */ 458 | protected $bcc = array(); 459 | 460 | /** 461 | * The array of reply-to names and addresses. 462 | * @type array 463 | * @access protected 464 | */ 465 | protected $ReplyTo = array(); 466 | 467 | /** 468 | * An array of all kinds of addresses. 469 | * Includes all of $to, $cc, $bcc, $replyto 470 | * @type array 471 | * @access protected 472 | */ 473 | protected $all_recipients = array(); 474 | 475 | /** 476 | * The array of attachments. 477 | * @type array 478 | * @access protected 479 | */ 480 | protected $attachment = array(); 481 | 482 | /** 483 | * The array of custom headers. 484 | * @type array 485 | * @access protected 486 | */ 487 | protected $CustomHeader = array(); 488 | 489 | /** 490 | * The most recent Message-ID (including angular brackets). 491 | * @type string 492 | * @access protected 493 | */ 494 | protected $lastMessageID = ''; 495 | 496 | /** 497 | * The message's MIME type. 498 | * @type string 499 | * @access protected 500 | */ 501 | protected $message_type = ''; 502 | 503 | /** 504 | * The array of MIME boundary strings. 505 | * @type array 506 | * @access protected 507 | */ 508 | protected $boundary = array(); 509 | 510 | /** 511 | * The array of available languages. 512 | * @type array 513 | * @access protected 514 | */ 515 | protected $language = array(); 516 | 517 | /** 518 | * The number of errors encountered. 519 | * @type integer 520 | * @access protected 521 | */ 522 | protected $error_count = 0; 523 | 524 | /** 525 | * The S/MIME certificate file path. 526 | * @type string 527 | * @access protected 528 | */ 529 | protected $sign_cert_file = ''; 530 | 531 | /** 532 | * The S/MIME key file path. 533 | * @type string 534 | * @access protected 535 | */ 536 | protected $sign_key_file = ''; 537 | 538 | /** 539 | * The S/MIME password for the key. 540 | * Used only if the key is encrypted. 541 | * @type string 542 | * @access protected 543 | */ 544 | protected $sign_key_pass = ''; 545 | 546 | /** 547 | * Whether to throw exceptions for errors. 548 | * @type boolean 549 | * @access protected 550 | */ 551 | protected $exceptions = false; 552 | 553 | /** 554 | * Error severity: message only, continue processing. 555 | */ 556 | const STOP_MESSAGE = 0; 557 | 558 | /** 559 | * Error severity: message, likely ok to continue processing. 560 | */ 561 | const STOP_CONTINUE = 1; 562 | 563 | /** 564 | * Error severity: message, plus full stop, critical error reached. 565 | */ 566 | const STOP_CRITICAL = 2; 567 | 568 | /** 569 | * SMTP RFC standard line ending. 570 | */ 571 | const CRLF = "\r\n"; 572 | 573 | /** 574 | * Constructor. 575 | * @param boolean $exceptions Should we throw external exceptions? 576 | */ 577 | public function __construct($exceptions = false) 578 | { 579 | $this->exceptions = ($exceptions == true); 580 | } 581 | 582 | /** 583 | * Destructor. 584 | */ 585 | public function __destruct() 586 | { 587 | if ($this->Mailer == 'smtp') { //close any open SMTP connection nicely 588 | $this->smtpClose(); 589 | } 590 | } 591 | 592 | /** 593 | * Call mail() in a safe_mode-aware fashion. 594 | * Also, unless sendmail_path points to sendmail (or something that 595 | * claims to be sendmail), don't pass params (not a perfect fix, 596 | * but it will do) 597 | * @param string $to To 598 | * @param string $subject Subject 599 | * @param string $body Message Body 600 | * @param string $header Additional Header(s) 601 | * @param string $params Params 602 | * @access private 603 | * @return boolean 604 | */ 605 | private function mailPassthru($to, $subject, $body, $header, $params) 606 | { 607 | //Check overloading of mail function to avoid double-encoding 608 | if (ini_get('mbstring.func_overload') & 1) { 609 | $subject = $this->secureHeader($subject); 610 | } else { 611 | $subject = $this->encodeHeader($this->secureHeader($subject)); 612 | } 613 | if (ini_get('safe_mode') || !($this->UseSendmailOptions)) { 614 | $result = @mail($to, $subject, $body, $header); 615 | } else { 616 | $result = @mail($to, $subject, $body, $header, $params); 617 | } 618 | return $result; 619 | } 620 | 621 | /** 622 | * Output debugging info via user-defined method. 623 | * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). 624 | * @see PHPMailer::$Debugoutput 625 | * @see PHPMailer::$SMTPDebug 626 | * @param string $str 627 | */ 628 | protected function edebug($str) 629 | { 630 | if ($this->SMTPDebug <= 0) { 631 | return; 632 | } 633 | if (is_callable($this->Debugoutput)) { 634 | call_user_func($this->Debugoutput, $str, $this->SMTPDebug); 635 | return; 636 | } 637 | switch ($this->Debugoutput) { 638 | case 'error_log': 639 | //Don't output, just log 640 | error_log($str); 641 | break; 642 | case 'html': 643 | //Cleans up output a bit for a better looking, HTML-safe output 644 | echo htmlentities( 645 | preg_replace('/[\r\n]+/', '', $str), 646 | ENT_QUOTES, 647 | 'UTF-8' 648 | ) 649 | . "
\n"; 650 | break; 651 | case 'echo': 652 | default: 653 | //Normalize line breaks 654 | $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); 655 | echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 656 | "\n", 657 | "\n \t ", 658 | trim($str) 659 | ) . "\n"; 660 | } 661 | } 662 | 663 | /** 664 | * Sets message type to HTML or plain. 665 | * @param boolean $isHtml True for HTML mode. 666 | * @return void 667 | */ 668 | public function isHTML($isHtml = true) 669 | { 670 | if ($isHtml) { 671 | $this->ContentType = 'text/html'; 672 | } else { 673 | $this->ContentType = 'text/plain'; 674 | } 675 | } 676 | 677 | /** 678 | * Send messages using SMTP. 679 | * @return void 680 | */ 681 | public function isSMTP() 682 | { 683 | $this->Mailer = 'smtp'; 684 | } 685 | 686 | /** 687 | * Send messages using PHP's mail() function. 688 | * @return void 689 | */ 690 | public function isMail() 691 | { 692 | $this->Mailer = 'mail'; 693 | } 694 | 695 | /** 696 | * Send messages using $Sendmail. 697 | * @return void 698 | */ 699 | public function isSendmail() 700 | { 701 | $ini_sendmail_path = ini_get('sendmail_path'); 702 | 703 | if (!stristr($ini_sendmail_path, 'sendmail')) { 704 | $this->Sendmail = '/usr/sbin/sendmail'; 705 | } else { 706 | $this->Sendmail = $ini_sendmail_path; 707 | } 708 | $this->Mailer = 'sendmail'; 709 | } 710 | 711 | /** 712 | * Send messages using qmail. 713 | * @return void 714 | */ 715 | public function isQmail() 716 | { 717 | $ini_sendmail_path = ini_get('sendmail_path'); 718 | 719 | if (!stristr($ini_sendmail_path, 'qmail')) { 720 | $this->Sendmail = '/var/qmail/bin/qmail-inject'; 721 | } else { 722 | $this->Sendmail = $ini_sendmail_path; 723 | } 724 | $this->Mailer = 'qmail'; 725 | } 726 | 727 | /** 728 | * Add a "To" address. 729 | * @param string $address 730 | * @param string $name 731 | * @return boolean true on success, false if address already used 732 | */ 733 | public function addAddress($address, $name = '') 734 | { 735 | return $this->addAnAddress('to', $address, $name); 736 | } 737 | 738 | /** 739 | * Add a "CC" address. 740 | * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. 741 | * @param string $address 742 | * @param string $name 743 | * @return boolean true on success, false if address already used 744 | */ 745 | public function addCC($address, $name = '') 746 | { 747 | return $this->addAnAddress('cc', $address, $name); 748 | } 749 | 750 | /** 751 | * Add a "BCC" address. 752 | * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. 753 | * @param string $address 754 | * @param string $name 755 | * @return boolean true on success, false if address already used 756 | */ 757 | public function addBCC($address, $name = '') 758 | { 759 | return $this->addAnAddress('bcc', $address, $name); 760 | } 761 | 762 | /** 763 | * Add a "Reply-to" address. 764 | * @param string $address 765 | * @param string $name 766 | * @return boolean 767 | */ 768 | public function addReplyTo($address, $name = '') 769 | { 770 | return $this->addAnAddress('Reply-To', $address, $name); 771 | } 772 | 773 | /** 774 | * Add an address to one of the recipient arrays. 775 | * Addresses that have been added already return false, but do not throw exceptions 776 | * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' 777 | * @param string $address The email address to send to 778 | * @param string $name 779 | * @throws phpmailerException 780 | * @return boolean true on success, false if address already used or invalid in some way 781 | * @access protected 782 | */ 783 | protected function addAnAddress($kind, $address, $name = '') 784 | { 785 | if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) { 786 | $this->setError($this->lang('Invalid recipient array') . ': ' . $kind); 787 | $this->edebug($this->lang('Invalid recipient array') . ': ' . $kind); 788 | if ($this->exceptions) { 789 | throw new phpmailerException('Invalid recipient array: ' . $kind); 790 | } 791 | return false; 792 | } 793 | $address = trim($address); 794 | $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim 795 | if (!$this->validateAddress($address)) { 796 | $this->setError($this->lang('invalid_address') . ': ' . $address); 797 | $this->edebug($this->lang('invalid_address') . ': ' . $address); 798 | if ($this->exceptions) { 799 | throw new phpmailerException($this->lang('invalid_address') . ': ' . $address); 800 | } 801 | return false; 802 | } 803 | if ($kind != 'Reply-To') { 804 | if (!isset($this->all_recipients[strtolower($address)])) { 805 | array_push($this->$kind, array($address, $name)); 806 | $this->all_recipients[strtolower($address)] = true; 807 | return true; 808 | } 809 | } else { 810 | if (!array_key_exists(strtolower($address), $this->ReplyTo)) { 811 | $this->ReplyTo[strtolower($address)] = array($address, $name); 812 | return true; 813 | } 814 | } 815 | return false; 816 | } 817 | 818 | /** 819 | * Set the From and FromName properties. 820 | * @param string $address 821 | * @param string $name 822 | * @param boolean $auto Whether to also set the Sender address, defaults to true 823 | * @throws phpmailerException 824 | * @return boolean 825 | */ 826 | public function setFrom($address, $name = '', $auto = true) 827 | { 828 | $address = trim($address); 829 | $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim 830 | if (!$this->validateAddress($address)) { 831 | $this->setError($this->lang('invalid_address') . ': ' . $address); 832 | $this->edebug($this->lang('invalid_address') . ': ' . $address); 833 | if ($this->exceptions) { 834 | throw new phpmailerException($this->lang('invalid_address') . ': ' . $address); 835 | } 836 | return false; 837 | } 838 | $this->From = $address; 839 | $this->FromName = $name; 840 | if ($auto) { 841 | if (empty($this->Sender)) { 842 | $this->Sender = $address; 843 | } 844 | } 845 | return true; 846 | } 847 | 848 | /** 849 | * Return the Message-ID header of the last email. 850 | * Technically this is the value from the last time the headers were created, 851 | * but it's also the message ID of the last sent message except in 852 | * pathological cases. 853 | * @return string 854 | */ 855 | public function getLastMessageID() 856 | { 857 | return $this->lastMessageID; 858 | } 859 | 860 | /** 861 | * Check that a string looks like an email address. 862 | * @param string $address The email address to check 863 | * @param string $patternselect A selector for the validation pattern to use : 864 | * * `auto` Pick strictest one automatically; 865 | * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; 866 | * * `pcre` Use old PCRE implementation; 867 | * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; same as pcre8 but does not allow 'dotless' domains; 868 | * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. 869 | * * `noregex` Don't use a regex: super fast, really dumb. 870 | * @return boolean 871 | * @static 872 | * @access public 873 | */ 874 | public static function validateAddress($address, $patternselect = 'auto') 875 | { 876 | if (!$patternselect or $patternselect == 'auto') { 877 | //Check this constant first so it works when extension_loaded() is disabled by safe mode 878 | //Constant was added in PHP 5.2.4 879 | if (defined('PCRE_VERSION')) { 880 | //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2 881 | if (version_compare(PCRE_VERSION, '8.0.3') >= 0) { 882 | $patternselect = 'pcre8'; 883 | } else { 884 | $patternselect = 'pcre'; 885 | } 886 | } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) { 887 | //Fall back to older PCRE 888 | $patternselect = 'pcre'; 889 | } else { 890 | //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension 891 | if (version_compare(PHP_VERSION, '5.2.0') >= 0) { 892 | $patternselect = 'php'; 893 | } else { 894 | $patternselect = 'noregex'; 895 | } 896 | } 897 | } 898 | switch ($patternselect) { 899 | case 'pcre8': 900 | /** 901 | * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains. 902 | * @link http://squiloople.com/2009/12/20/email-address-validation/ 903 | * @copyright 2009-2010 Michael Rushton 904 | * Feel free to use and redistribute this code. But please keep this copyright notice. 905 | */ 906 | return (boolean)preg_match( 907 | '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . 908 | '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . 909 | '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . 910 | '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . 911 | '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . 912 | '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . 913 | '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . 914 | '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . 915 | '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', 916 | $address 917 | ); 918 | case 'pcre': 919 | //An older regex that doesn't need a recent PCRE 920 | return (boolean)preg_match( 921 | '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . 922 | '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . 923 | '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' . 924 | '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' . 925 | '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' . 926 | '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' . 927 | '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' . 928 | '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' . 929 | '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' . 930 | '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', 931 | $address 932 | ); 933 | case 'html5': 934 | /** 935 | * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. 936 | * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) 937 | */ 938 | return (boolean)preg_match( 939 | '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . 940 | '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', 941 | $address 942 | ); 943 | case 'noregex': 944 | //No PCRE! Do something _very_ approximate! 945 | //Check the address is 3 chars or longer and contains an @ that's not the first or last char 946 | return (strlen($address) >= 3 947 | and strpos($address, '@') >= 1 948 | and strpos($address, '@') != strlen($address) - 1); 949 | case 'php': 950 | default: 951 | return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL); 952 | } 953 | } 954 | 955 | /** 956 | * Create a message and send it. 957 | * Uses the sending method specified by $Mailer. 958 | * @throws phpmailerException 959 | * @return boolean false on error - See the ErrorInfo property for details of the error. 960 | */ 961 | public function send() 962 | { 963 | try { 964 | if (!$this->preSend()) { 965 | return false; 966 | } 967 | return $this->postSend(); 968 | } catch (phpmailerException $exc) { 969 | $this->mailHeader = ''; 970 | $this->setError($exc->getMessage()); 971 | if ($this->exceptions) { 972 | throw $exc; 973 | } 974 | return false; 975 | } 976 | } 977 | 978 | /** 979 | * Prepare a message for sending. 980 | * @throws phpmailerException 981 | * @return boolean 982 | */ 983 | public function preSend() 984 | { 985 | try { 986 | $this->mailHeader = ''; 987 | if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { 988 | throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); 989 | } 990 | 991 | // Set whether the message is multipart/alternative 992 | if (!empty($this->AltBody)) { 993 | $this->ContentType = 'multipart/alternative'; 994 | } 995 | 996 | $this->error_count = 0; // reset errors 997 | $this->setMessageType(); 998 | // Refuse to send an empty message unless we are specifically allowing it 999 | if (!$this->AllowEmpty and empty($this->Body)) { 1000 | throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL); 1001 | } 1002 | 1003 | $this->MIMEHeader = $this->createHeader(); 1004 | $this->MIMEBody = $this->createBody(); 1005 | 1006 | // To capture the complete message when using mail(), create 1007 | // an extra header list which createHeader() doesn't fold in 1008 | if ($this->Mailer == 'mail') { 1009 | if (count($this->to) > 0) { 1010 | $this->mailHeader .= $this->addrAppend('To', $this->to); 1011 | } else { 1012 | $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); 1013 | } 1014 | $this->mailHeader .= $this->headerLine( 1015 | 'Subject', 1016 | $this->encodeHeader($this->secureHeader(trim($this->Subject))) 1017 | ); 1018 | } 1019 | 1020 | // Sign with DKIM if enabled 1021 | if (!empty($this->DKIM_domain) 1022 | && !empty($this->DKIM_private) 1023 | && !empty($this->DKIM_selector) 1024 | && !empty($this->DKIM_domain) 1025 | && file_exists($this->DKIM_private)) { 1026 | $header_dkim = $this->DKIM_Add( 1027 | $this->MIMEHeader . $this->mailHeader, 1028 | $this->encodeHeader($this->secureHeader($this->Subject)), 1029 | $this->MIMEBody 1030 | ); 1031 | $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF . 1032 | str_replace("\r\n", "\n", $header_dkim) . self::CRLF; 1033 | } 1034 | return true; 1035 | 1036 | } catch (phpmailerException $exc) { 1037 | $this->setError($exc->getMessage()); 1038 | if ($this->exceptions) { 1039 | throw $exc; 1040 | } 1041 | return false; 1042 | } 1043 | } 1044 | 1045 | /** 1046 | * Actually send a message. 1047 | * Send the email via the selected mechanism 1048 | * @throws phpmailerException 1049 | * @return boolean 1050 | */ 1051 | public function postSend() 1052 | { 1053 | try { 1054 | // Choose the mailer and send through it 1055 | switch ($this->Mailer) { 1056 | case 'sendmail': 1057 | case 'qmail': 1058 | return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); 1059 | case 'smtp': 1060 | return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); 1061 | case 'mail': 1062 | return $this->mailSend($this->MIMEHeader, $this->MIMEBody); 1063 | default: 1064 | $sendMethod = $this->Mailer.'Send'; 1065 | if (method_exists($this, $sendMethod)) { 1066 | return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); 1067 | } 1068 | 1069 | return $this->mailSend($this->MIMEHeader, $this->MIMEBody); 1070 | } 1071 | } catch (phpmailerException $exc) { 1072 | $this->setError($exc->getMessage()); 1073 | $this->edebug($exc->getMessage()); 1074 | if ($this->exceptions) { 1075 | throw $exc; 1076 | } 1077 | } 1078 | return false; 1079 | } 1080 | 1081 | /** 1082 | * Send mail using the $Sendmail program. 1083 | * @param string $header The message headers 1084 | * @param string $body The message body 1085 | * @see PHPMailer::$Sendmail 1086 | * @throws phpmailerException 1087 | * @access protected 1088 | * @return boolean 1089 | */ 1090 | protected function sendmailSend($header, $body) 1091 | { 1092 | if ($this->Sender != '') { 1093 | if ($this->Mailer == 'qmail') { 1094 | $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); 1095 | } else { 1096 | $sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); 1097 | } 1098 | } else { 1099 | if ($this->Mailer == 'qmail') { 1100 | $sendmail = sprintf('%s', escapeshellcmd($this->Sendmail)); 1101 | } else { 1102 | $sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail)); 1103 | } 1104 | } 1105 | if ($this->SingleTo === true) { 1106 | foreach ($this->SingleToArray as $toAddr) { 1107 | if (!@$mail = popen($sendmail, 'w')) { 1108 | throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1109 | } 1110 | fputs($mail, 'To: ' . $toAddr . "\n"); 1111 | fputs($mail, $header); 1112 | fputs($mail, $body); 1113 | $result = pclose($mail); 1114 | $this->doCallback( 1115 | ($result == 0), 1116 | array($toAddr), 1117 | $this->cc, 1118 | $this->bcc, 1119 | $this->Subject, 1120 | $body, 1121 | $this->From 1122 | ); 1123 | if ($result != 0) { 1124 | throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1125 | } 1126 | } 1127 | } else { 1128 | if (!@$mail = popen($sendmail, 'w')) { 1129 | throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1130 | } 1131 | fputs($mail, $header); 1132 | fputs($mail, $body); 1133 | $result = pclose($mail); 1134 | $this->doCallback(($result == 0), $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1135 | if ($result != 0) { 1136 | throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); 1137 | } 1138 | } 1139 | return true; 1140 | } 1141 | 1142 | /** 1143 | * Send mail using the PHP mail() function. 1144 | * @param string $header The message headers 1145 | * @param string $body The message body 1146 | * @link http://www.php.net/manual/en/book.mail.php 1147 | * @throws phpmailerException 1148 | * @access protected 1149 | * @return boolean 1150 | */ 1151 | protected function mailSend($header, $body) 1152 | { 1153 | $toArr = array(); 1154 | foreach ($this->to as $toaddr) { 1155 | $toArr[] = $this->addrFormat($toaddr); 1156 | } 1157 | $to = implode(', ', $toArr); 1158 | 1159 | if (empty($this->Sender)) { 1160 | $params = ' '; 1161 | } else { 1162 | $params = sprintf('-f%s', $this->Sender); 1163 | } 1164 | if ($this->Sender != '' and !ini_get('safe_mode')) { 1165 | $old_from = ini_get('sendmail_from'); 1166 | ini_set('sendmail_from', $this->Sender); 1167 | } 1168 | $result = false; 1169 | if ($this->SingleTo === true && count($toArr) > 1) { 1170 | foreach ($toArr as $toAddr) { 1171 | $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); 1172 | $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1173 | } 1174 | } else { 1175 | $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); 1176 | $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); 1177 | } 1178 | if (isset($old_from)) { 1179 | ini_set('sendmail_from', $old_from); 1180 | } 1181 | if (!$result) { 1182 | throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL); 1183 | } 1184 | return true; 1185 | } 1186 | 1187 | /** 1188 | * Get an instance to use for SMTP operations. 1189 | * Override this function to load your own SMTP implementation 1190 | * @return SMTP 1191 | */ 1192 | public function getSMTPInstance() 1193 | { 1194 | if (!is_object($this->smtp)) { 1195 | $this->smtp = new SMTP; 1196 | } 1197 | return $this->smtp; 1198 | } 1199 | 1200 | /** 1201 | * Send mail via SMTP. 1202 | * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. 1203 | * Uses the PHPMailerSMTP class by default. 1204 | * @see PHPMailer::getSMTPInstance() to use a different class. 1205 | * @param string $header The message headers 1206 | * @param string $body The message body 1207 | * @throws phpmailerException 1208 | * @uses SMTP 1209 | * @access protected 1210 | * @return boolean 1211 | */ 1212 | protected function smtpSend($header, $body) 1213 | { 1214 | $bad_rcpt = array(); 1215 | 1216 | if (!$this->smtpConnect()) { 1217 | throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); 1218 | } 1219 | $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; 1220 | if (!$this->smtp->mail($smtp_from)) { 1221 | $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); 1222 | throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); 1223 | } 1224 | 1225 | // Attempt to send to all recipients 1226 | foreach ($this->to as $to) { 1227 | if (!$this->smtp->recipient($to[0])) { 1228 | $bad_rcpt[] = $to[0]; 1229 | $isSent = false; 1230 | } else { 1231 | $isSent = true; 1232 | } 1233 | $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From); 1234 | } 1235 | foreach ($this->cc as $cc) { 1236 | if (!$this->smtp->recipient($cc[0])) { 1237 | $bad_rcpt[] = $cc[0]; 1238 | $isSent = false; 1239 | } else { 1240 | $isSent = true; 1241 | } 1242 | $this->doCallback($isSent, array(), array($cc[0]), array(), $this->Subject, $body, $this->From); 1243 | } 1244 | foreach ($this->bcc as $bcc) { 1245 | if (!$this->smtp->recipient($bcc[0])) { 1246 | $bad_rcpt[] = $bcc[0]; 1247 | $isSent = false; 1248 | } else { 1249 | $isSent = true; 1250 | } 1251 | $this->doCallback($isSent, array(), array(), array($bcc[0]), $this->Subject, $body, $this->From); 1252 | } 1253 | 1254 | // Only send the DATA command if we have viable recipients 1255 | if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) { 1256 | throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL); 1257 | } 1258 | if ($this->SMTPKeepAlive == true) { 1259 | $this->smtp->reset(); 1260 | } else { 1261 | $this->smtp->quit(); 1262 | $this->smtp->close(); 1263 | } 1264 | if (count($bad_rcpt) > 0) { // Create error message for any bad addresses 1265 | throw new phpmailerException( 1266 | $this->lang('recipients_failed') . implode(', ', $bad_rcpt), 1267 | self::STOP_CONTINUE 1268 | ); 1269 | } 1270 | return true; 1271 | } 1272 | 1273 | /** 1274 | * Initiate a connection to an SMTP server. 1275 | * Returns false if the operation failed. 1276 | * @param array $options An array of options compatible with stream_context_create() 1277 | * @uses SMTP 1278 | * @access public 1279 | * @throws phpmailerException 1280 | * @return boolean 1281 | */ 1282 | public function smtpConnect($options = array()) 1283 | { 1284 | if (is_null($this->smtp)) { 1285 | $this->smtp = $this->getSMTPInstance(); 1286 | } 1287 | 1288 | // Already connected? 1289 | if ($this->smtp->connected()) { 1290 | return true; 1291 | } 1292 | 1293 | $this->smtp->setTimeout($this->Timeout); 1294 | $this->smtp->setDebugLevel($this->SMTPDebug); 1295 | $this->smtp->setDebugOutput($this->Debugoutput); 1296 | $this->smtp->setVerp($this->do_verp); 1297 | $hosts = explode(';', $this->Host); 1298 | $lastexception = null; 1299 | 1300 | foreach ($hosts as $hostentry) { 1301 | $hostinfo = array(); 1302 | if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { 1303 | // Not a valid host entry 1304 | continue; 1305 | } 1306 | // $hostinfo[2]: optional ssl or tls prefix 1307 | // $hostinfo[3]: the hostname 1308 | // $hostinfo[4]: optional port number 1309 | // The host string prefix can temporarily override the current setting for SMTPSecure 1310 | // If it's not specified, the default value is used 1311 | $prefix = ''; 1312 | $tls = ($this->SMTPSecure == 'tls'); 1313 | if ($hostinfo[2] == 'ssl' or ($hostinfo[2] == '' and $this->SMTPSecure == 'ssl')) { 1314 | $prefix = 'ssl://'; 1315 | $tls = false; // Can't have SSL and TLS at once 1316 | } elseif ($hostinfo[2] == 'tls') { 1317 | $tls = true; 1318 | // tls doesn't use a prefix 1319 | } 1320 | $host = $hostinfo[3]; 1321 | $port = $this->Port; 1322 | $tport = (integer)$hostinfo[4]; 1323 | if ($tport > 0 and $tport < 65536) { 1324 | $port = $tport; 1325 | } 1326 | if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { 1327 | try { 1328 | if ($this->Helo) { 1329 | $hello = $this->Helo; 1330 | } else { 1331 | $hello = $this->serverHostname(); 1332 | } 1333 | $this->smtp->hello($hello); 1334 | 1335 | if ($tls) { 1336 | if (!$this->smtp->startTLS()) { 1337 | throw new phpmailerException($this->lang('connect_host')); 1338 | } 1339 | // We must resend HELO after tls negotiation 1340 | $this->smtp->hello($hello); 1341 | } 1342 | if ($this->SMTPAuth) { 1343 | if (!$this->smtp->authenticate( 1344 | $this->Username, 1345 | $this->Password, 1346 | $this->AuthType, 1347 | $this->Realm, 1348 | $this->Workstation 1349 | ) 1350 | ) { 1351 | throw new phpmailerException($this->lang('authenticate')); 1352 | } 1353 | } 1354 | return true; 1355 | } catch (phpmailerException $exc) { 1356 | $lastexception = $exc; 1357 | // We must have connected, but then failed TLS or Auth, so close connection nicely 1358 | $this->smtp->quit(); 1359 | } 1360 | } 1361 | } 1362 | // If we get here, all connection attempts have failed, so close connection hard 1363 | $this->smtp->close(); 1364 | // As we've caught all exceptions, just report whatever the last one was 1365 | if ($this->exceptions and !is_null($lastexception)) { 1366 | throw $lastexception; 1367 | } 1368 | return false; 1369 | } 1370 | 1371 | /** 1372 | * Close the active SMTP session if one exists. 1373 | * @return void 1374 | */ 1375 | public function smtpClose() 1376 | { 1377 | if ($this->smtp !== null) { 1378 | if ($this->smtp->connected()) { 1379 | $this->smtp->quit(); 1380 | $this->smtp->close(); 1381 | } 1382 | } 1383 | } 1384 | 1385 | /** 1386 | * Set the language for error messages. 1387 | * Returns false if it cannot load the language file. 1388 | * The default language is English. 1389 | * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") 1390 | * @param string $lang_path Path to the language file directory, with trailing separator (slash) 1391 | * @return boolean 1392 | * @access public 1393 | */ 1394 | public function setLanguage($langcode = 'en', $lang_path = '') 1395 | { 1396 | // Define full set of translatable strings in English 1397 | $PHPMAILER_LANG = array( 1398 | 'authenticate' => 'SMTP Error: Could not authenticate.', 1399 | 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', 1400 | 'data_not_accepted' => 'SMTP Error: data not accepted.', 1401 | 'empty_message' => 'Message body empty', 1402 | 'encoding' => 'Unknown encoding: ', 1403 | 'execute' => 'Could not execute: ', 1404 | 'file_access' => 'Could not access file: ', 1405 | 'file_open' => 'File Error: Could not open file: ', 1406 | 'from_failed' => 'The following From address failed: ', 1407 | 'instantiate' => 'Could not instantiate mail function.', 1408 | 'invalid_address' => 'Invalid address', 1409 | 'mailer_not_supported' => ' mailer is not supported.', 1410 | 'provide_address' => 'You must provide at least one recipient email address.', 1411 | 'recipients_failed' => 'SMTP Error: The following recipients failed: ', 1412 | 'signing' => 'Signing Error: ', 1413 | 'smtp_connect_failed' => 'SMTP connect() failed.', 1414 | 'smtp_error' => 'SMTP server error: ', 1415 | 'variable_set' => 'Cannot set or reset variable: ' 1416 | ); 1417 | if (empty($lang_path)) { 1418 | // Calculate an absolute path so it can work if CWD is not here 1419 | $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; 1420 | } 1421 | $foundlang = true; 1422 | $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; 1423 | if ($langcode != 'en') { // There is no English translation file 1424 | // Make sure language file path is readable 1425 | if (!is_readable($lang_file)) { 1426 | $foundlang = false; 1427 | } else { 1428 | // Overwrite language-specific strings. 1429 | // This way we'll never have missing translations. 1430 | $foundlang = include $lang_file; 1431 | } 1432 | } 1433 | $this->language = $PHPMAILER_LANG; 1434 | return ($foundlang == true); // Returns false if language not found 1435 | } 1436 | 1437 | /** 1438 | * Get the array of strings for the current language. 1439 | * @return array 1440 | */ 1441 | public function getTranslations() 1442 | { 1443 | return $this->language; 1444 | } 1445 | 1446 | /** 1447 | * Create recipient headers. 1448 | * @access public 1449 | * @param string $type 1450 | * @param array $addr An array of recipient, 1451 | * where each recipient is a 2-element indexed array with element 0 containing an address 1452 | * and element 1 containing a name, like: 1453 | * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User')) 1454 | * @return string 1455 | */ 1456 | public function addrAppend($type, $addr) 1457 | { 1458 | $addresses = array(); 1459 | foreach ($addr as $address) { 1460 | $addresses[] = $this->addrFormat($address); 1461 | } 1462 | return $type . ': ' . implode(', ', $addresses) . $this->LE; 1463 | } 1464 | 1465 | /** 1466 | * Format an address for use in a message header. 1467 | * @access public 1468 | * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name 1469 | * like array('joe@example.com', 'Joe User') 1470 | * @return string 1471 | */ 1472 | public function addrFormat($addr) 1473 | { 1474 | if (empty($addr[1])) { // No name provided 1475 | return $this->secureHeader($addr[0]); 1476 | } else { 1477 | return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader( 1478 | $addr[0] 1479 | ) . '>'; 1480 | } 1481 | } 1482 | 1483 | /** 1484 | * Word-wrap message. 1485 | * For use with mailers that do not automatically perform wrapping 1486 | * and for quoted-printable encoded messages. 1487 | * Original written by philippe. 1488 | * @param string $message The message to wrap 1489 | * @param integer $length The line length to wrap to 1490 | * @param boolean $qp_mode Whether to run in Quoted-Printable mode 1491 | * @access public 1492 | * @return string 1493 | */ 1494 | public function wrapText($message, $length, $qp_mode = false) 1495 | { 1496 | $soft_break = ($qp_mode) ? sprintf(' =%s', $this->LE) : $this->LE; 1497 | // If utf-8 encoding is used, we will need to make sure we don't 1498 | // split multibyte characters when we wrap 1499 | $is_utf8 = (strtolower($this->CharSet) == 'utf-8'); 1500 | $lelen = strlen($this->LE); 1501 | $crlflen = strlen(self::CRLF); 1502 | 1503 | $message = $this->fixEOL($message); 1504 | if (substr($message, -$lelen) == $this->LE) { 1505 | $message = substr($message, 0, -$lelen); 1506 | } 1507 | 1508 | $line = explode($this->LE, $message); // Magic. We know fixEOL uses $LE 1509 | $message = ''; 1510 | for ($i = 0; $i < count($line); $i++) { 1511 | $line_part = explode(' ', $line[$i]); 1512 | $buf = ''; 1513 | for ($e = 0; $e < count($line_part); $e++) { 1514 | $word = $line_part[$e]; 1515 | if ($qp_mode and (strlen($word) > $length)) { 1516 | $space_left = $length - strlen($buf) - $crlflen; 1517 | if ($e != 0) { 1518 | if ($space_left > 20) { 1519 | $len = $space_left; 1520 | if ($is_utf8) { 1521 | $len = $this->utf8CharBoundary($word, $len); 1522 | } elseif (substr($word, $len - 1, 1) == '=') { 1523 | $len--; 1524 | } elseif (substr($word, $len - 2, 1) == '=') { 1525 | $len -= 2; 1526 | } 1527 | $part = substr($word, 0, $len); 1528 | $word = substr($word, $len); 1529 | $buf .= ' ' . $part; 1530 | $message .= $buf . sprintf('=%s', self::CRLF); 1531 | } else { 1532 | $message .= $buf . $soft_break; 1533 | } 1534 | $buf = ''; 1535 | } 1536 | while (strlen($word) > 0) { 1537 | if ($length <= 0) { 1538 | break; 1539 | } 1540 | $len = $length; 1541 | if ($is_utf8) { 1542 | $len = $this->utf8CharBoundary($word, $len); 1543 | } elseif (substr($word, $len - 1, 1) == '=') { 1544 | $len--; 1545 | } elseif (substr($word, $len - 2, 1) == '=') { 1546 | $len -= 2; 1547 | } 1548 | $part = substr($word, 0, $len); 1549 | $word = substr($word, $len); 1550 | 1551 | if (strlen($word) > 0) { 1552 | $message .= $part . sprintf('=%s', self::CRLF); 1553 | } else { 1554 | $buf = $part; 1555 | } 1556 | } 1557 | } else { 1558 | $buf_o = $buf; 1559 | $buf .= ($e == 0) ? $word : (' ' . $word); 1560 | 1561 | if (strlen($buf) > $length and $buf_o != '') { 1562 | $message .= $buf_o . $soft_break; 1563 | $buf = $word; 1564 | } 1565 | } 1566 | } 1567 | $message .= $buf . self::CRLF; 1568 | } 1569 | 1570 | return $message; 1571 | } 1572 | 1573 | /** 1574 | * Find the last character boundary prior to $maxLength in a utf-8 1575 | * quoted (printable) encoded string. 1576 | * Original written by Colin Brown. 1577 | * @access public 1578 | * @param string $encodedText utf-8 QP text 1579 | * @param integer $maxLength find last character boundary prior to this length 1580 | * @return integer 1581 | */ 1582 | public function utf8CharBoundary($encodedText, $maxLength) 1583 | { 1584 | $foundSplitPos = false; 1585 | $lookBack = 3; 1586 | while (!$foundSplitPos) { 1587 | $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); 1588 | $encodedCharPos = strpos($lastChunk, '='); 1589 | if ($encodedCharPos !== false) { 1590 | // Found start of encoded character byte within $lookBack block. 1591 | // Check the encoded byte value (the 2 chars after the '=') 1592 | $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); 1593 | $dec = hexdec($hex); 1594 | if ($dec < 128) { // Single byte character. 1595 | // If the encoded char was found at pos 0, it will fit 1596 | // otherwise reduce maxLength to start of the encoded char 1597 | $maxLength = ($encodedCharPos == 0) ? $maxLength : 1598 | $maxLength - ($lookBack - $encodedCharPos); 1599 | $foundSplitPos = true; 1600 | } elseif ($dec >= 192) { // First byte of a multi byte character 1601 | // Reduce maxLength to split at start of character 1602 | $maxLength = $maxLength - ($lookBack - $encodedCharPos); 1603 | $foundSplitPos = true; 1604 | } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back 1605 | $lookBack += 3; 1606 | } 1607 | } else { 1608 | // No encoded character found 1609 | $foundSplitPos = true; 1610 | } 1611 | } 1612 | return $maxLength; 1613 | } 1614 | 1615 | /** 1616 | * Set the body wrapping. 1617 | * @access public 1618 | * @return void 1619 | */ 1620 | public function setWordWrap() 1621 | { 1622 | if ($this->WordWrap < 1) { 1623 | return; 1624 | } 1625 | 1626 | switch ($this->message_type) { 1627 | case 'alt': 1628 | case 'alt_inline': 1629 | case 'alt_attach': 1630 | case 'alt_inline_attach': 1631 | $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); 1632 | break; 1633 | default: 1634 | $this->Body = $this->wrapText($this->Body, $this->WordWrap); 1635 | break; 1636 | } 1637 | } 1638 | 1639 | /** 1640 | * Assemble message headers. 1641 | * @access public 1642 | * @return string The assembled headers 1643 | */ 1644 | public function createHeader() 1645 | { 1646 | $result = ''; 1647 | 1648 | // Set the boundaries 1649 | $uniq_id = md5(uniqid(time())); 1650 | $this->boundary[1] = 'b1_' . $uniq_id; 1651 | $this->boundary[2] = 'b2_' . $uniq_id; 1652 | $this->boundary[3] = 'b3_' . $uniq_id; 1653 | 1654 | if ($this->MessageDate == '') { 1655 | $this->MessageDate = self::rfcDate(); 1656 | } 1657 | $result .= $this->headerLine('Date', $this->MessageDate); 1658 | 1659 | 1660 | // To be created automatically by mail() 1661 | if ($this->SingleTo === true) { 1662 | if ($this->Mailer != 'mail') { 1663 | foreach ($this->to as $toaddr) { 1664 | $this->SingleToArray[] = $this->addrFormat($toaddr); 1665 | } 1666 | } 1667 | } else { 1668 | if (count($this->to) > 0) { 1669 | if ($this->Mailer != 'mail') { 1670 | $result .= $this->addrAppend('To', $this->to); 1671 | } 1672 | } elseif (count($this->cc) == 0) { 1673 | $result .= $this->headerLine('To', 'undisclosed-recipients:;'); 1674 | } 1675 | } 1676 | 1677 | $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName))); 1678 | 1679 | // sendmail and mail() extract Cc from the header before sending 1680 | if (count($this->cc) > 0) { 1681 | $result .= $this->addrAppend('Cc', $this->cc); 1682 | } 1683 | 1684 | // sendmail and mail() extract Bcc from the header before sending 1685 | if (( 1686 | $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' 1687 | ) 1688 | and count($this->bcc) > 0 1689 | ) { 1690 | $result .= $this->addrAppend('Bcc', $this->bcc); 1691 | } 1692 | 1693 | if (count($this->ReplyTo) > 0) { 1694 | $result .= $this->addrAppend('Reply-To', $this->ReplyTo); 1695 | } 1696 | 1697 | // mail() sets the subject itself 1698 | if ($this->Mailer != 'mail') { 1699 | $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); 1700 | } 1701 | 1702 | if ($this->MessageID != '') { 1703 | $this->lastMessageID = $this->MessageID; 1704 | } else { 1705 | $this->lastMessageID = sprintf('<%s@%s>', $uniq_id, $this->ServerHostname()); 1706 | } 1707 | $result .= $this->HeaderLine('Message-ID', $this->lastMessageID); 1708 | $result .= $this->headerLine('X-Priority', $this->Priority); 1709 | if ($this->XMailer == '') { 1710 | $result .= $this->headerLine( 1711 | 'X-Mailer', 1712 | 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer/)' 1713 | ); 1714 | } else { 1715 | $myXmailer = trim($this->XMailer); 1716 | if ($myXmailer) { 1717 | $result .= $this->headerLine('X-Mailer', $myXmailer); 1718 | } 1719 | } 1720 | 1721 | if ($this->ConfirmReadingTo != '') { 1722 | $result .= $this->headerLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); 1723 | } 1724 | 1725 | // Add custom headers 1726 | for ($index = 0; $index < count($this->CustomHeader); $index++) { 1727 | $result .= $this->headerLine( 1728 | trim($this->CustomHeader[$index][0]), 1729 | $this->encodeHeader(trim($this->CustomHeader[$index][1])) 1730 | ); 1731 | } 1732 | if (!$this->sign_key_file) { 1733 | $result .= $this->headerLine('MIME-Version', '1.0'); 1734 | $result .= $this->getMailMIME(); 1735 | } 1736 | 1737 | return $result; 1738 | } 1739 | 1740 | /** 1741 | * Get the message MIME type headers. 1742 | * @access public 1743 | * @return string 1744 | */ 1745 | public function getMailMIME() 1746 | { 1747 | $result = ''; 1748 | $ismultipart = true; 1749 | switch ($this->message_type) { 1750 | case 'inline': 1751 | $result .= $this->headerLine('Content-Type', 'multipart/related;'); 1752 | $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); 1753 | break; 1754 | case 'attach': 1755 | case 'inline_attach': 1756 | case 'alt_attach': 1757 | case 'alt_inline_attach': 1758 | $result .= $this->headerLine('Content-Type', 'multipart/mixed;'); 1759 | $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); 1760 | break; 1761 | case 'alt': 1762 | case 'alt_inline': 1763 | $result .= $this->headerLine('Content-Type', 'multipart/alternative;'); 1764 | $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); 1765 | break; 1766 | default: 1767 | // Catches case 'plain': and case '': 1768 | $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); 1769 | $ismultipart = false; 1770 | break; 1771 | } 1772 | // RFC1341 part 5 says 7bit is assumed if not specified 1773 | if ($this->Encoding != '7bit') { 1774 | // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE 1775 | if ($ismultipart) { 1776 | if ($this->Encoding == '8bit') { 1777 | $result .= $this->headerLine('Content-Transfer-Encoding', '8bit'); 1778 | } 1779 | // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible 1780 | } else { 1781 | $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); 1782 | } 1783 | } 1784 | 1785 | if ($this->Mailer != 'mail') { 1786 | $result .= $this->LE; 1787 | } 1788 | 1789 | return $result; 1790 | } 1791 | 1792 | /** 1793 | * Returns the whole MIME message. 1794 | * Includes complete headers and body. 1795 | * Only valid post preSend(). 1796 | * @see PHPMailer::preSend() 1797 | * @access public 1798 | * @return string 1799 | */ 1800 | public function getSentMIMEMessage() 1801 | { 1802 | return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody; 1803 | } 1804 | 1805 | 1806 | /** 1807 | * Assemble the message body. 1808 | * Returns an empty string on failure. 1809 | * @access public 1810 | * @throws phpmailerException 1811 | * @return string The assembled message body 1812 | */ 1813 | public function createBody() 1814 | { 1815 | $body = ''; 1816 | 1817 | if ($this->sign_key_file) { 1818 | $body .= $this->getMailMIME() . $this->LE; 1819 | } 1820 | 1821 | $this->setWordWrap(); 1822 | 1823 | $bodyEncoding = $this->Encoding; 1824 | $bodyCharSet = $this->CharSet; 1825 | if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) { 1826 | $bodyEncoding = '7bit'; 1827 | $bodyCharSet = 'us-ascii'; 1828 | } 1829 | $altBodyEncoding = $this->Encoding; 1830 | $altBodyCharSet = $this->CharSet; 1831 | if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) { 1832 | $altBodyEncoding = '7bit'; 1833 | $altBodyCharSet = 'us-ascii'; 1834 | } 1835 | switch ($this->message_type) { 1836 | case 'inline': 1837 | $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); 1838 | $body .= $this->encodeString($this->Body, $bodyEncoding); 1839 | $body .= $this->LE . $this->LE; 1840 | $body .= $this->attachAll('inline', $this->boundary[1]); 1841 | break; 1842 | case 'attach': 1843 | $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); 1844 | $body .= $this->encodeString($this->Body, $bodyEncoding); 1845 | $body .= $this->LE . $this->LE; 1846 | $body .= $this->attachAll('attachment', $this->boundary[1]); 1847 | break; 1848 | case 'inline_attach': 1849 | $body .= $this->textLine('--' . $this->boundary[1]); 1850 | $body .= $this->headerLine('Content-Type', 'multipart/related;'); 1851 | $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 1852 | $body .= $this->LE; 1853 | $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); 1854 | $body .= $this->encodeString($this->Body, $bodyEncoding); 1855 | $body .= $this->LE . $this->LE; 1856 | $body .= $this->attachAll('inline', $this->boundary[2]); 1857 | $body .= $this->LE; 1858 | $body .= $this->attachAll('attachment', $this->boundary[1]); 1859 | break; 1860 | case 'alt': 1861 | $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); 1862 | $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 1863 | $body .= $this->LE . $this->LE; 1864 | $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding); 1865 | $body .= $this->encodeString($this->Body, $bodyEncoding); 1866 | $body .= $this->LE . $this->LE; 1867 | if (!empty($this->Ical)) { 1868 | $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', ''); 1869 | $body .= $this->encodeString($this->Ical, $this->Encoding); 1870 | $body .= $this->LE . $this->LE; 1871 | } 1872 | $body .= $this->endBoundary($this->boundary[1]); 1873 | break; 1874 | case 'alt_inline': 1875 | $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); 1876 | $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 1877 | $body .= $this->LE . $this->LE; 1878 | $body .= $this->textLine('--' . $this->boundary[1]); 1879 | $body .= $this->headerLine('Content-Type', 'multipart/related;'); 1880 | $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 1881 | $body .= $this->LE; 1882 | $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); 1883 | $body .= $this->encodeString($this->Body, $bodyEncoding); 1884 | $body .= $this->LE . $this->LE; 1885 | $body .= $this->attachAll('inline', $this->boundary[2]); 1886 | $body .= $this->LE; 1887 | $body .= $this->endBoundary($this->boundary[1]); 1888 | break; 1889 | case 'alt_attach': 1890 | $body .= $this->textLine('--' . $this->boundary[1]); 1891 | $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); 1892 | $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 1893 | $body .= $this->LE; 1894 | $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); 1895 | $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 1896 | $body .= $this->LE . $this->LE; 1897 | $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); 1898 | $body .= $this->encodeString($this->Body, $bodyEncoding); 1899 | $body .= $this->LE . $this->LE; 1900 | $body .= $this->endBoundary($this->boundary[2]); 1901 | $body .= $this->LE; 1902 | $body .= $this->attachAll('attachment', $this->boundary[1]); 1903 | break; 1904 | case 'alt_inline_attach': 1905 | $body .= $this->textLine('--' . $this->boundary[1]); 1906 | $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); 1907 | $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); 1908 | $body .= $this->LE; 1909 | $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); 1910 | $body .= $this->encodeString($this->AltBody, $altBodyEncoding); 1911 | $body .= $this->LE . $this->LE; 1912 | $body .= $this->textLine('--' . $this->boundary[2]); 1913 | $body .= $this->headerLine('Content-Type', 'multipart/related;'); 1914 | $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"'); 1915 | $body .= $this->LE; 1916 | $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding); 1917 | $body .= $this->encodeString($this->Body, $bodyEncoding); 1918 | $body .= $this->LE . $this->LE; 1919 | $body .= $this->attachAll('inline', $this->boundary[3]); 1920 | $body .= $this->LE; 1921 | $body .= $this->endBoundary($this->boundary[2]); 1922 | $body .= $this->LE; 1923 | $body .= $this->attachAll('attachment', $this->boundary[1]); 1924 | break; 1925 | default: 1926 | // catch case 'plain' and case '' 1927 | $body .= $this->encodeString($this->Body, $bodyEncoding); 1928 | break; 1929 | } 1930 | 1931 | if ($this->isError()) { 1932 | $body = ''; 1933 | } elseif ($this->sign_key_file) { 1934 | try { 1935 | if (!defined('PKCS7_TEXT')) { 1936 | throw new phpmailerException($this->lang('signing') . ' OpenSSL extension missing.'); 1937 | } 1938 | // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1 1939 | $file = tempnam(sys_get_temp_dir(), 'mail'); 1940 | file_put_contents($file, $body); // @TODO check this worked 1941 | $signed = tempnam(sys_get_temp_dir(), 'signed'); 1942 | if (@openssl_pkcs7_sign( 1943 | $file, 1944 | $signed, 1945 | 'file://' . realpath($this->sign_cert_file), 1946 | array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), 1947 | null 1948 | ) 1949 | ) { 1950 | @unlink($file); 1951 | $body = file_get_contents($signed); 1952 | @unlink($signed); 1953 | } else { 1954 | @unlink($file); 1955 | @unlink($signed); 1956 | throw new phpmailerException($this->lang('signing') . openssl_error_string()); 1957 | } 1958 | } catch (phpmailerException $exc) { 1959 | $body = ''; 1960 | if ($this->exceptions) { 1961 | throw $exc; 1962 | } 1963 | } 1964 | } 1965 | return $body; 1966 | } 1967 | 1968 | /** 1969 | * Return the start of a message boundary. 1970 | * @access protected 1971 | * @param string $boundary 1972 | * @param string $charSet 1973 | * @param string $contentType 1974 | * @param string $encoding 1975 | * @return string 1976 | */ 1977 | protected function getBoundary($boundary, $charSet, $contentType, $encoding) 1978 | { 1979 | $result = ''; 1980 | if ($charSet == '') { 1981 | $charSet = $this->CharSet; 1982 | } 1983 | if ($contentType == '') { 1984 | $contentType = $this->ContentType; 1985 | } 1986 | if ($encoding == '') { 1987 | $encoding = $this->Encoding; 1988 | } 1989 | $result .= $this->textLine('--' . $boundary); 1990 | $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); 1991 | $result .= $this->LE; 1992 | // RFC1341 part 5 says 7bit is assumed if not specified 1993 | if ($encoding != '7bit') { 1994 | $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); 1995 | } 1996 | $result .= $this->LE; 1997 | 1998 | return $result; 1999 | } 2000 | 2001 | /** 2002 | * Return the end of a message boundary. 2003 | * @access protected 2004 | * @param string $boundary 2005 | * @return string 2006 | */ 2007 | protected function endBoundary($boundary) 2008 | { 2009 | return $this->LE . '--' . $boundary . '--' . $this->LE; 2010 | } 2011 | 2012 | /** 2013 | * Set the message type. 2014 | * PHPMailer only supports some preset message types, 2015 | * not arbitrary MIME structures. 2016 | * @access protected 2017 | * @return void 2018 | */ 2019 | protected function setMessageType() 2020 | { 2021 | $type = array(); 2022 | if ($this->alternativeExists()) { 2023 | $type[] = 'alt'; 2024 | } 2025 | if ($this->inlineImageExists()) { 2026 | $type[] = 'inline'; 2027 | } 2028 | if ($this->attachmentExists()) { 2029 | $type[] = 'attach'; 2030 | } 2031 | $this->message_type = implode('_', $type); 2032 | if ($this->message_type == '') { 2033 | $this->message_type = 'plain'; 2034 | } 2035 | } 2036 | 2037 | /** 2038 | * Format a header line. 2039 | * @access public 2040 | * @param string $name 2041 | * @param string $value 2042 | * @return string 2043 | */ 2044 | public function headerLine($name, $value) 2045 | { 2046 | return $name . ': ' . $value . $this->LE; 2047 | } 2048 | 2049 | /** 2050 | * Return a formatted mail line. 2051 | * @access public 2052 | * @param string $value 2053 | * @return string 2054 | */ 2055 | public function textLine($value) 2056 | { 2057 | return $value . $this->LE; 2058 | } 2059 | 2060 | /** 2061 | * Add an attachment from a path on the filesystem. 2062 | * Returns false if the file could not be found or read. 2063 | * @param string $path Path to the attachment. 2064 | * @param string $name Overrides the attachment name. 2065 | * @param string $encoding File encoding (see $Encoding). 2066 | * @param string $type File extension (MIME) type. 2067 | * @param string $disposition Disposition to use 2068 | * @throws phpmailerException 2069 | * @return boolean 2070 | */ 2071 | public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') 2072 | { 2073 | try { 2074 | if (!@is_file($path)) { 2075 | throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); 2076 | } 2077 | 2078 | // If a MIME type is not specified, try to work it out from the file name 2079 | if ($type == '') { 2080 | $type = self::filenameToType($path); 2081 | } 2082 | 2083 | $filename = basename($path); 2084 | if ($name == '') { 2085 | $name = $filename; 2086 | } 2087 | 2088 | $this->attachment[] = array( 2089 | 0 => $path, 2090 | 1 => $filename, 2091 | 2 => $name, 2092 | 3 => $encoding, 2093 | 4 => $type, 2094 | 5 => false, // isStringAttachment 2095 | 6 => $disposition, 2096 | 7 => 0 2097 | ); 2098 | 2099 | } catch (phpmailerException $exc) { 2100 | $this->setError($exc->getMessage()); 2101 | $this->edebug($exc->getMessage()); 2102 | if ($this->exceptions) { 2103 | throw $exc; 2104 | } 2105 | return false; 2106 | } 2107 | return true; 2108 | } 2109 | 2110 | /** 2111 | * Return the array of attachments. 2112 | * @return array 2113 | */ 2114 | public function getAttachments() 2115 | { 2116 | return $this->attachment; 2117 | } 2118 | 2119 | /** 2120 | * Attach all file, string, and binary attachments to the message. 2121 | * Returns an empty string on failure. 2122 | * @access protected 2123 | * @param string $disposition_type 2124 | * @param string $boundary 2125 | * @return string 2126 | */ 2127 | protected function attachAll($disposition_type, $boundary) 2128 | { 2129 | // Return text of body 2130 | $mime = array(); 2131 | $cidUniq = array(); 2132 | $incl = array(); 2133 | 2134 | // Add all attachments 2135 | foreach ($this->attachment as $attachment) { 2136 | // Check if it is a valid disposition_filter 2137 | if ($attachment[6] == $disposition_type) { 2138 | // Check for string attachment 2139 | $string = ''; 2140 | $path = ''; 2141 | $bString = $attachment[5]; 2142 | if ($bString) { 2143 | $string = $attachment[0]; 2144 | } else { 2145 | $path = $attachment[0]; 2146 | } 2147 | 2148 | $inclhash = md5(serialize($attachment)); 2149 | if (in_array($inclhash, $incl)) { 2150 | continue; 2151 | } 2152 | $incl[] = $inclhash; 2153 | $name = $attachment[2]; 2154 | $encoding = $attachment[3]; 2155 | $type = $attachment[4]; 2156 | $disposition = $attachment[6]; 2157 | $cid = $attachment[7]; 2158 | if ($disposition == 'inline' && isset($cidUniq[$cid])) { 2159 | continue; 2160 | } 2161 | $cidUniq[$cid] = true; 2162 | 2163 | $mime[] = sprintf('--%s%s', $boundary, $this->LE); 2164 | $mime[] = sprintf( 2165 | 'Content-Type: %s; name="%s"%s', 2166 | $type, 2167 | $this->encodeHeader($this->secureHeader($name)), 2168 | $this->LE 2169 | ); 2170 | // RFC1341 part 5 says 7bit is assumed if not specified 2171 | if ($encoding != '7bit') { 2172 | $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); 2173 | } 2174 | 2175 | if ($disposition == 'inline') { 2176 | $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE); 2177 | } 2178 | 2179 | // If a filename contains any of these chars, it should be quoted, 2180 | // but not otherwise: RFC2183 & RFC2045 5.1 2181 | // Fixes a warning in IETF's msglint MIME checker 2182 | // Allow for bypassing the Content-Disposition header totally 2183 | if (!(empty($disposition))) { 2184 | $encoded_name = $this->encodeHeader($this->secureHeader($name)); 2185 | if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) { 2186 | $mime[] = sprintf( 2187 | 'Content-Disposition: %s; filename="%s"%s', 2188 | $disposition, 2189 | $encoded_name, 2190 | $this->LE . $this->LE 2191 | ); 2192 | } else { 2193 | $mime[] = sprintf( 2194 | 'Content-Disposition: %s; filename=%s%s', 2195 | $disposition, 2196 | $encoded_name, 2197 | $this->LE . $this->LE 2198 | ); 2199 | } 2200 | } else { 2201 | $mime[] = $this->LE; 2202 | } 2203 | 2204 | // Encode as string attachment 2205 | if ($bString) { 2206 | $mime[] = $this->encodeString($string, $encoding); 2207 | if ($this->isError()) { 2208 | return ''; 2209 | } 2210 | $mime[] = $this->LE . $this->LE; 2211 | } else { 2212 | $mime[] = $this->encodeFile($path, $encoding); 2213 | if ($this->isError()) { 2214 | return ''; 2215 | } 2216 | $mime[] = $this->LE . $this->LE; 2217 | } 2218 | } 2219 | } 2220 | 2221 | $mime[] = sprintf('--%s--%s', $boundary, $this->LE); 2222 | 2223 | return implode('', $mime); 2224 | } 2225 | 2226 | /** 2227 | * Encode a file attachment in requested format. 2228 | * Returns an empty string on failure. 2229 | * @param string $path The full path to the file 2230 | * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' 2231 | * @throws phpmailerException 2232 | * @see EncodeFile(encodeFile 2233 | * @access protected 2234 | * @return string 2235 | */ 2236 | protected function encodeFile($path, $encoding = 'base64') 2237 | { 2238 | try { 2239 | if (!is_readable($path)) { 2240 | throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); 2241 | } 2242 | $magic_quotes = get_magic_quotes_runtime(); 2243 | if ($magic_quotes) { 2244 | if (version_compare(PHP_VERSION, '5.3.0', '<')) { 2245 | set_magic_quotes_runtime(false); 2246 | } else { 2247 | //Doesn't exist in PHP 5.4, but we don't need to check because 2248 | //get_magic_quotes_runtime always returns false in 5.4+ 2249 | //so it will never get here 2250 | ini_set('magic_quotes_runtime', 0); 2251 | } 2252 | } 2253 | $file_buffer = file_get_contents($path); 2254 | $file_buffer = $this->encodeString($file_buffer, $encoding); 2255 | if ($magic_quotes) { 2256 | if (version_compare(PHP_VERSION, '5.3.0', '<')) { 2257 | set_magic_quotes_runtime($magic_quotes); 2258 | } else { 2259 | ini_set('magic_quotes_runtime', ($magic_quotes?'1':'0')); 2260 | } 2261 | } 2262 | return $file_buffer; 2263 | } catch (Exception $exc) { 2264 | $this->setError($exc->getMessage()); 2265 | return ''; 2266 | } 2267 | } 2268 | 2269 | /** 2270 | * Encode a string in requested format. 2271 | * Returns an empty string on failure. 2272 | * @param string $str The text to encode 2273 | * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' 2274 | * @access public 2275 | * @return string 2276 | */ 2277 | public function encodeString($str, $encoding = 'base64') 2278 | { 2279 | $encoded = ''; 2280 | switch (strtolower($encoding)) { 2281 | case 'base64': 2282 | $encoded = chunk_split(base64_encode($str), 76, $this->LE); 2283 | break; 2284 | case '7bit': 2285 | case '8bit': 2286 | $encoded = $this->fixEOL($str); 2287 | // Make sure it ends with a line break 2288 | if (substr($encoded, -(strlen($this->LE))) != $this->LE) { 2289 | $encoded .= $this->LE; 2290 | } 2291 | break; 2292 | case 'binary': 2293 | $encoded = $str; 2294 | break; 2295 | case 'quoted-printable': 2296 | $encoded = $this->encodeQP($str); 2297 | break; 2298 | default: 2299 | $this->setError($this->lang('encoding') . $encoding); 2300 | break; 2301 | } 2302 | return $encoded; 2303 | } 2304 | 2305 | /** 2306 | * Encode a header string optimally. 2307 | * Picks shortest of Q, B, quoted-printable or none. 2308 | * @access public 2309 | * @param string $str 2310 | * @param string $position 2311 | * @return string 2312 | */ 2313 | public function encodeHeader($str, $position = 'text') 2314 | { 2315 | $matchcount = 0; 2316 | switch (strtolower($position)) { 2317 | case 'phrase': 2318 | if (!preg_match('/[\200-\377]/', $str)) { 2319 | // Can't use addslashes as we don't know the value of magic_quotes_sybase 2320 | $encoded = addcslashes($str, "\0..\37\177\\\""); 2321 | if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { 2322 | return ($encoded); 2323 | } else { 2324 | return ("\"$encoded\""); 2325 | } 2326 | } 2327 | $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); 2328 | break; 2329 | /** @noinspection PhpMissingBreakStatementInspection */ 2330 | case 'comment': 2331 | $matchcount = preg_match_all('/[()"]/', $str, $matches); 2332 | // Intentional fall-through 2333 | case 'text': 2334 | default: 2335 | $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); 2336 | break; 2337 | } 2338 | 2339 | if ($matchcount == 0) { // There are no chars that need encoding 2340 | return ($str); 2341 | } 2342 | 2343 | $maxlen = 75 - 7 - strlen($this->CharSet); 2344 | // Try to select the encoding which should produce the shortest output 2345 | if ($matchcount > strlen($str) / 3) { 2346 | // More than a third of the content will need encoding, so B encoding will be most efficient 2347 | $encoding = 'B'; 2348 | if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) { 2349 | // Use a custom function which correctly encodes and wraps long 2350 | // multibyte strings without breaking lines within a character 2351 | $encoded = $this->base64EncodeWrapMB($str, "\n"); 2352 | } else { 2353 | $encoded = base64_encode($str); 2354 | $maxlen -= $maxlen % 4; 2355 | $encoded = trim(chunk_split($encoded, $maxlen, "\n")); 2356 | } 2357 | } else { 2358 | $encoding = 'Q'; 2359 | $encoded = $this->encodeQ($str, $position); 2360 | $encoded = $this->wrapText($encoded, $maxlen, true); 2361 | $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded)); 2362 | } 2363 | 2364 | $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); 2365 | $encoded = trim(str_replace("\n", $this->LE, $encoded)); 2366 | 2367 | return $encoded; 2368 | } 2369 | 2370 | /** 2371 | * Check if a string contains multi-byte characters. 2372 | * @access public 2373 | * @param string $str multi-byte text to wrap encode 2374 | * @return boolean 2375 | */ 2376 | public function hasMultiBytes($str) 2377 | { 2378 | if (function_exists('mb_strlen')) { 2379 | return (strlen($str) > mb_strlen($str, $this->CharSet)); 2380 | } else { // Assume no multibytes (we can't handle without mbstring functions anyway) 2381 | return false; 2382 | } 2383 | } 2384 | 2385 | /** 2386 | * Does a string contain any 8-bit chars (in any charset)? 2387 | * @param string $text 2388 | * @return boolean 2389 | */ 2390 | public function has8bitChars($text) 2391 | { 2392 | return (boolean)preg_match('/[\x80-\xFF]/', $text); 2393 | } 2394 | 2395 | /** 2396 | * Encode and wrap long multibyte strings for mail headers 2397 | * without breaking lines within a character. 2398 | * Adapted from a function by paravoid 2399 | * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 2400 | * @access public 2401 | * @param string $str multi-byte text to wrap encode 2402 | * @param string $linebreak string to use as linefeed/end-of-line 2403 | * @return string 2404 | */ 2405 | public function base64EncodeWrapMB($str, $linebreak = null) 2406 | { 2407 | $start = '=?' . $this->CharSet . '?B?'; 2408 | $end = '?='; 2409 | $encoded = ''; 2410 | if ($linebreak === null) { 2411 | $linebreak = $this->LE; 2412 | } 2413 | 2414 | $mb_length = mb_strlen($str, $this->CharSet); 2415 | // Each line must have length <= 75, including $start and $end 2416 | $length = 75 - strlen($start) - strlen($end); 2417 | // Average multi-byte ratio 2418 | $ratio = $mb_length / strlen($str); 2419 | // Base64 has a 4:3 ratio 2420 | $avgLength = floor($length * $ratio * .75); 2421 | 2422 | for ($i = 0; $i < $mb_length; $i += $offset) { 2423 | $lookBack = 0; 2424 | do { 2425 | $offset = $avgLength - $lookBack; 2426 | $chunk = mb_substr($str, $i, $offset, $this->CharSet); 2427 | $chunk = base64_encode($chunk); 2428 | $lookBack++; 2429 | } while (strlen($chunk) > $length); 2430 | $encoded .= $chunk . $linebreak; 2431 | } 2432 | 2433 | // Chomp the last linefeed 2434 | $encoded = substr($encoded, 0, -strlen($linebreak)); 2435 | return $encoded; 2436 | } 2437 | 2438 | /** 2439 | * Encode a string in quoted-printable format. 2440 | * According to RFC2045 section 6.7. 2441 | * @access public 2442 | * @param string $string The text to encode 2443 | * @param integer $line_max Number of chars allowed on a line before wrapping 2444 | * @return string 2445 | * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment 2446 | */ 2447 | public function encodeQP($string, $line_max = 76) 2448 | { 2449 | if (function_exists('quoted_printable_encode')) { // Use native function if it's available (>= PHP5.3) 2450 | return $this->fixEOL(quoted_printable_encode($string)); 2451 | } 2452 | // Fall back to a pure PHP implementation 2453 | $string = str_replace( 2454 | array('%20', '%0D%0A.', '%0D%0A', '%'), 2455 | array(' ', "\r\n=2E", "\r\n", '='), 2456 | rawurlencode($string) 2457 | ); 2458 | $string = preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); 2459 | return $this->fixEOL($string); 2460 | } 2461 | 2462 | /** 2463 | * Backward compatibility wrapper for an old QP encoding function that was removed. 2464 | * @see PHPMailer::encodeQP() 2465 | * @access public 2466 | * @param string $string 2467 | * @param integer $line_max 2468 | * @param boolean $space_conv 2469 | * @return string 2470 | * @deprecated Use encodeQP instead. 2471 | */ 2472 | public function encodeQPphp( 2473 | $string, 2474 | $line_max = 76, 2475 | /** @noinspection PhpUnusedParameterInspection */ $space_conv = false 2476 | ) { 2477 | return $this->encodeQP($string, $line_max); 2478 | } 2479 | 2480 | /** 2481 | * Encode a string using Q encoding. 2482 | * @link http://tools.ietf.org/html/rfc2047 2483 | * @param string $str the text to encode 2484 | * @param string $position Where the text is going to be used, see the RFC for what that means 2485 | * @access public 2486 | * @return string 2487 | */ 2488 | public function encodeQ($str, $position = 'text') 2489 | { 2490 | // There should not be any EOL in the string 2491 | $pattern = ''; 2492 | $encoded = str_replace(array("\r", "\n"), '', $str); 2493 | switch (strtolower($position)) { 2494 | case 'phrase': 2495 | // RFC 2047 section 5.3 2496 | $pattern = '^A-Za-z0-9!*+\/ -'; 2497 | break; 2498 | /** @noinspection PhpMissingBreakStatementInspection */ 2499 | case 'comment': 2500 | // RFC 2047 section 5.2 2501 | $pattern = '\(\)"'; 2502 | // intentional fall-through 2503 | // for this reason we build the $pattern without including delimiters and [] 2504 | case 'text': 2505 | default: 2506 | // RFC 2047 section 5.1 2507 | // Replace every high ascii, control, =, ? and _ characters 2508 | $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; 2509 | break; 2510 | } 2511 | $matches = array(); 2512 | if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { 2513 | // If the string contains an '=', make sure it's the first thing we replace 2514 | // so as to avoid double-encoding 2515 | $eqkey = array_search('=', $matches[0]); 2516 | if ($eqkey !== false) { 2517 | unset($matches[0][$eqkey]); 2518 | array_unshift($matches[0], '='); 2519 | } 2520 | foreach (array_unique($matches[0]) as $char) { 2521 | $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); 2522 | } 2523 | } 2524 | // Replace every spaces to _ (more readable than =20) 2525 | return str_replace(' ', '_', $encoded); 2526 | } 2527 | 2528 | 2529 | /** 2530 | * Add a string or binary attachment (non-filesystem). 2531 | * This method can be used to attach ascii or binary data, 2532 | * such as a BLOB record from a database. 2533 | * @param string $string String attachment data. 2534 | * @param string $filename Name of the attachment. 2535 | * @param string $encoding File encoding (see $Encoding). 2536 | * @param string $type File extension (MIME) type. 2537 | * @param string $disposition Disposition to use 2538 | * @return void 2539 | */ 2540 | public function addStringAttachment( 2541 | $string, 2542 | $filename, 2543 | $encoding = 'base64', 2544 | $type = '', 2545 | $disposition = 'attachment' 2546 | ) { 2547 | // If a MIME type is not specified, try to work it out from the file name 2548 | if ($type == '') { 2549 | $type = self::filenameToType($filename); 2550 | } 2551 | // Append to $attachment array 2552 | $this->attachment[] = array( 2553 | 0 => $string, 2554 | 1 => $filename, 2555 | 2 => basename($filename), 2556 | 3 => $encoding, 2557 | 4 => $type, 2558 | 5 => true, // isStringAttachment 2559 | 6 => $disposition, 2560 | 7 => 0 2561 | ); 2562 | } 2563 | 2564 | /** 2565 | * Add an embedded (inline) attachment from a file. 2566 | * This can include images, sounds, and just about any other document type. 2567 | * These differ from 'regular' attachmants in that they are intended to be 2568 | * displayed inline with the message, not just attached for download. 2569 | * This is used in HTML messages that embed the images 2570 | * the HTML refers to using the $cid value. 2571 | * @param string $path Path to the attachment. 2572 | * @param string $cid Content ID of the attachment; Use this to reference 2573 | * the content when using an embedded image in HTML. 2574 | * @param string $name Overrides the attachment name. 2575 | * @param string $encoding File encoding (see $Encoding). 2576 | * @param string $type File MIME type. 2577 | * @param string $disposition Disposition to use 2578 | * @return boolean True on successfully adding an attachment 2579 | */ 2580 | public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') 2581 | { 2582 | if (!@is_file($path)) { 2583 | $this->setError($this->lang('file_access') . $path); 2584 | return false; 2585 | } 2586 | 2587 | // If a MIME type is not specified, try to work it out from the file name 2588 | if ($type == '') { 2589 | $type = self::filenameToType($path); 2590 | } 2591 | 2592 | $filename = basename($path); 2593 | if ($name == '') { 2594 | $name = $filename; 2595 | } 2596 | 2597 | // Append to $attachment array 2598 | $this->attachment[] = array( 2599 | 0 => $path, 2600 | 1 => $filename, 2601 | 2 => $name, 2602 | 3 => $encoding, 2603 | 4 => $type, 2604 | 5 => false, // isStringAttachment 2605 | 6 => $disposition, 2606 | 7 => $cid 2607 | ); 2608 | return true; 2609 | } 2610 | 2611 | /** 2612 | * Add an embedded stringified attachment. 2613 | * This can include images, sounds, and just about any other document type. 2614 | * Be sure to set the $type to an image type for images: 2615 | * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. 2616 | * @param string $string The attachment binary data. 2617 | * @param string $cid Content ID of the attachment; Use this to reference 2618 | * the content when using an embedded image in HTML. 2619 | * @param string $name 2620 | * @param string $encoding File encoding (see $Encoding). 2621 | * @param string $type MIME type. 2622 | * @param string $disposition Disposition to use 2623 | * @return boolean True on successfully adding an attachment 2624 | */ 2625 | public function addStringEmbeddedImage( 2626 | $string, 2627 | $cid, 2628 | $name = '', 2629 | $encoding = 'base64', 2630 | $type = '', 2631 | $disposition = 'inline' 2632 | ) { 2633 | // If a MIME type is not specified, try to work it out from the name 2634 | if ($type == '') { 2635 | $type = self::filenameToType($name); 2636 | } 2637 | 2638 | // Append to $attachment array 2639 | $this->attachment[] = array( 2640 | 0 => $string, 2641 | 1 => $name, 2642 | 2 => $name, 2643 | 3 => $encoding, 2644 | 4 => $type, 2645 | 5 => true, // isStringAttachment 2646 | 6 => $disposition, 2647 | 7 => $cid 2648 | ); 2649 | return true; 2650 | } 2651 | 2652 | /** 2653 | * Check if an inline attachment is present. 2654 | * @access public 2655 | * @return boolean 2656 | */ 2657 | public function inlineImageExists() 2658 | { 2659 | foreach ($this->attachment as $attachment) { 2660 | if ($attachment[6] == 'inline') { 2661 | return true; 2662 | } 2663 | } 2664 | return false; 2665 | } 2666 | 2667 | /** 2668 | * Check if an attachment (non-inline) is present. 2669 | * @return boolean 2670 | */ 2671 | public function attachmentExists() 2672 | { 2673 | foreach ($this->attachment as $attachment) { 2674 | if ($attachment[6] == 'attachment') { 2675 | return true; 2676 | } 2677 | } 2678 | return false; 2679 | } 2680 | 2681 | /** 2682 | * Check if this message has an alternative body set. 2683 | * @return boolean 2684 | */ 2685 | public function alternativeExists() 2686 | { 2687 | return !empty($this->AltBody); 2688 | } 2689 | 2690 | /** 2691 | * Clear all To recipients. 2692 | * @return void 2693 | */ 2694 | public function clearAddresses() 2695 | { 2696 | foreach ($this->to as $to) { 2697 | unset($this->all_recipients[strtolower($to[0])]); 2698 | } 2699 | $this->to = array(); 2700 | } 2701 | 2702 | /** 2703 | * Clear all CC recipients. 2704 | * @return void 2705 | */ 2706 | public function clearCCs() 2707 | { 2708 | foreach ($this->cc as $cc) { 2709 | unset($this->all_recipients[strtolower($cc[0])]); 2710 | } 2711 | $this->cc = array(); 2712 | } 2713 | 2714 | /** 2715 | * Clear all BCC recipients. 2716 | * @return void 2717 | */ 2718 | public function clearBCCs() 2719 | { 2720 | foreach ($this->bcc as $bcc) { 2721 | unset($this->all_recipients[strtolower($bcc[0])]); 2722 | } 2723 | $this->bcc = array(); 2724 | } 2725 | 2726 | /** 2727 | * Clear all ReplyTo recipients. 2728 | * @return void 2729 | */ 2730 | public function clearReplyTos() 2731 | { 2732 | $this->ReplyTo = array(); 2733 | } 2734 | 2735 | /** 2736 | * Clear all recipient types. 2737 | * @return void 2738 | */ 2739 | public function clearAllRecipients() 2740 | { 2741 | $this->to = array(); 2742 | $this->cc = array(); 2743 | $this->bcc = array(); 2744 | $this->all_recipients = array(); 2745 | } 2746 | 2747 | /** 2748 | * Clear all filesystem, string, and binary attachments. 2749 | * @return void 2750 | */ 2751 | public function clearAttachments() 2752 | { 2753 | $this->attachment = array(); 2754 | } 2755 | 2756 | /** 2757 | * Clear all custom headers. 2758 | * @return void 2759 | */ 2760 | public function clearCustomHeaders() 2761 | { 2762 | $this->CustomHeader = array(); 2763 | } 2764 | 2765 | /** 2766 | * Add an error message to the error container. 2767 | * @access protected 2768 | * @param string $msg 2769 | * @return void 2770 | */ 2771 | protected function setError($msg) 2772 | { 2773 | $this->error_count++; 2774 | if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { 2775 | $lasterror = $this->smtp->getError(); 2776 | if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { 2777 | $msg .= '

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

\n"; 2778 | } 2779 | } 2780 | $this->ErrorInfo = $msg; 2781 | } 2782 | 2783 | /** 2784 | * Return an RFC 822 formatted date. 2785 | * @access public 2786 | * @return string 2787 | * @static 2788 | */ 2789 | public static function rfcDate() 2790 | { 2791 | // Set the time zone to whatever the default is to avoid 500 errors 2792 | // Will default to UTC if it's not set properly in php.ini 2793 | date_default_timezone_set(@date_default_timezone_get()); 2794 | return date('D, j M Y H:i:s O'); 2795 | } 2796 | 2797 | /** 2798 | * Get the server hostname. 2799 | * Returns 'localhost.localdomain' if unknown. 2800 | * @access protected 2801 | * @return string 2802 | */ 2803 | protected function serverHostname() 2804 | { 2805 | $result = 'localhost.localdomain'; 2806 | if (!empty($this->Hostname)) { 2807 | $result = $this->Hostname; 2808 | } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) { 2809 | $result = $_SERVER['SERVER_NAME']; 2810 | } elseif (function_exists('gethostname') && gethostname() !== false) { 2811 | $result = gethostname(); 2812 | } elseif (php_uname('n') !== false) { 2813 | $result = php_uname('n'); 2814 | } 2815 | return $result; 2816 | } 2817 | 2818 | /** 2819 | * Get an error message in the current language. 2820 | * @access protected 2821 | * @param string $key 2822 | * @return string 2823 | */ 2824 | protected function lang($key) 2825 | { 2826 | if (count($this->language) < 1) { 2827 | $this->setLanguage('en'); // set the default language 2828 | } 2829 | 2830 | if (isset($this->language[$key])) { 2831 | return $this->language[$key]; 2832 | } else { 2833 | return 'Language string failed to load: ' . $key; 2834 | } 2835 | } 2836 | 2837 | /** 2838 | * Check if an error occurred. 2839 | * @access public 2840 | * @return boolean True if an error did occur. 2841 | */ 2842 | public function isError() 2843 | { 2844 | return ($this->error_count > 0); 2845 | } 2846 | 2847 | /** 2848 | * Ensure consistent line endings in a string. 2849 | * Changes every end of line from CRLF, CR or LF to $this->LE. 2850 | * @access public 2851 | * @param string $str String to fixEOL 2852 | * @return string 2853 | */ 2854 | public function fixEOL($str) 2855 | { 2856 | // Normalise to \n 2857 | $nstr = str_replace(array("\r\n", "\r"), "\n", $str); 2858 | // Now convert LE as needed 2859 | if ($this->LE !== "\n") { 2860 | $nstr = str_replace("\n", $this->LE, $nstr); 2861 | } 2862 | return $nstr; 2863 | } 2864 | 2865 | /** 2866 | * Add a custom header. 2867 | * $name value can be overloaded to contain 2868 | * both header name and value (name:value) 2869 | * @access public 2870 | * @param string $name Custom header name 2871 | * @param string $value Header value 2872 | * @return void 2873 | */ 2874 | public function addCustomHeader($name, $value = null) 2875 | { 2876 | if ($value === null) { 2877 | // Value passed in as name:value 2878 | $this->CustomHeader[] = explode(':', $name, 2); 2879 | } else { 2880 | $this->CustomHeader[] = array($name, $value); 2881 | } 2882 | } 2883 | 2884 | /** 2885 | * Create a message from an HTML string. 2886 | * Automatically makes modifications for inline images and backgrounds 2887 | * and creates a plain-text version by converting the HTML. 2888 | * Overwrites any existing values in $this->Body and $this->AltBody 2889 | * @access public 2890 | * @param string $message HTML message string 2891 | * @param string $basedir baseline directory for path 2892 | * @param boolean $advanced Whether to use the advanced HTML to text converter 2893 | * @return string $message 2894 | */ 2895 | public function msgHTML($message, $basedir = '', $advanced = false) 2896 | { 2897 | preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); 2898 | if (isset($images[2])) { 2899 | foreach ($images[2] as $imgindex => $url) { 2900 | // Convert data URIs into embedded images 2901 | if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { 2902 | $data = substr($url, strpos($url, ',')); 2903 | if ($match[2]) { 2904 | $data = base64_decode($data); 2905 | } else { 2906 | $data = rawurldecode($data); 2907 | } 2908 | $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 2909 | if ($this->addStringEmbeddedImage($data, $cid, '', 'base64', $match[1])) { 2910 | $message = preg_replace( 2911 | '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', 2912 | $images[1][$imgindex] . '="cid:' . $cid . '"', 2913 | $message 2914 | ); 2915 | } 2916 | } elseif (!preg_match('#^[A-z]+://#', $url)) { 2917 | // Do not change urls for absolute images (thanks to corvuscorax) 2918 | $filename = basename($url); 2919 | $directory = dirname($url); 2920 | if ($directory == '.') { 2921 | $directory = ''; 2922 | } 2923 | $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 2924 | if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { 2925 | $basedir .= '/'; 2926 | } 2927 | if (strlen($directory) > 1 && substr($directory, -1) != '/') { 2928 | $directory .= '/'; 2929 | } 2930 | if ($this->addEmbeddedImage( 2931 | $basedir . $directory . $filename, 2932 | $cid, 2933 | $filename, 2934 | 'base64', 2935 | self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)) 2936 | ) 2937 | ) { 2938 | $message = preg_replace( 2939 | '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', 2940 | $images[1][$imgindex] . '="cid:' . $cid . '"', 2941 | $message 2942 | ); 2943 | } 2944 | } 2945 | } 2946 | } 2947 | $this->isHTML(true); 2948 | // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better 2949 | $this->Body = $this->normalizeBreaks($message); 2950 | $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced)); 2951 | if (empty($this->AltBody)) { 2952 | $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . 2953 | self::CRLF . self::CRLF; 2954 | } 2955 | return $this->Body; 2956 | } 2957 | 2958 | /** 2959 | * Convert an HTML string into plain text. 2960 | * @param string $html The HTML text to convert 2961 | * @param boolean $advanced Should this use the more complex html2text converter or just a simple one? 2962 | * @return string 2963 | */ 2964 | public function html2text($html, $advanced = false) 2965 | { 2966 | if ($advanced) { 2967 | require_once 'extras/class.html2text.php'; 2968 | $htmlconverter = new html2text($html); 2969 | return $htmlconverter->get_text(); 2970 | } 2971 | return html_entity_decode( 2972 | trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), 2973 | ENT_QUOTES, 2974 | $this->CharSet 2975 | ); 2976 | } 2977 | 2978 | /** 2979 | * Get the MIME type for a file extension. 2980 | * @param string $ext File extension 2981 | * @access public 2982 | * @return string MIME type of file. 2983 | * @static 2984 | */ 2985 | public static function _mime_types($ext = '') 2986 | { 2987 | $mimes = array( 2988 | 'xl' => 'application/excel', 2989 | 'js' => 'application/javascript', 2990 | 'hqx' => 'application/mac-binhex40', 2991 | 'cpt' => 'application/mac-compactpro', 2992 | 'bin' => 'application/macbinary', 2993 | 'doc' => 'application/msword', 2994 | 'word' => 'application/msword', 2995 | 'class' => 'application/octet-stream', 2996 | 'dll' => 'application/octet-stream', 2997 | 'dms' => 'application/octet-stream', 2998 | 'exe' => 'application/octet-stream', 2999 | 'lha' => 'application/octet-stream', 3000 | 'lzh' => 'application/octet-stream', 3001 | 'psd' => 'application/octet-stream', 3002 | 'sea' => 'application/octet-stream', 3003 | 'so' => 'application/octet-stream', 3004 | 'oda' => 'application/oda', 3005 | 'pdf' => 'application/pdf', 3006 | 'ai' => 'application/postscript', 3007 | 'eps' => 'application/postscript', 3008 | 'ps' => 'application/postscript', 3009 | 'smi' => 'application/smil', 3010 | 'smil' => 'application/smil', 3011 | 'mif' => 'application/vnd.mif', 3012 | 'xls' => 'application/vnd.ms-excel', 3013 | 'ppt' => 'application/vnd.ms-powerpoint', 3014 | 'wbxml' => 'application/vnd.wap.wbxml', 3015 | 'wmlc' => 'application/vnd.wap.wmlc', 3016 | 'dcr' => 'application/x-director', 3017 | 'dir' => 'application/x-director', 3018 | 'dxr' => 'application/x-director', 3019 | 'dvi' => 'application/x-dvi', 3020 | 'gtar' => 'application/x-gtar', 3021 | 'php3' => 'application/x-httpd-php', 3022 | 'php4' => 'application/x-httpd-php', 3023 | 'php' => 'application/x-httpd-php', 3024 | 'phtml' => 'application/x-httpd-php', 3025 | 'phps' => 'application/x-httpd-php-source', 3026 | 'swf' => 'application/x-shockwave-flash', 3027 | 'sit' => 'application/x-stuffit', 3028 | 'tar' => 'application/x-tar', 3029 | 'tgz' => 'application/x-tar', 3030 | 'xht' => 'application/xhtml+xml', 3031 | 'xhtml' => 'application/xhtml+xml', 3032 | 'zip' => 'application/zip', 3033 | 'mid' => 'audio/midi', 3034 | 'midi' => 'audio/midi', 3035 | 'mp2' => 'audio/mpeg', 3036 | 'mp3' => 'audio/mpeg', 3037 | 'mpga' => 'audio/mpeg', 3038 | 'aif' => 'audio/x-aiff', 3039 | 'aifc' => 'audio/x-aiff', 3040 | 'aiff' => 'audio/x-aiff', 3041 | 'ram' => 'audio/x-pn-realaudio', 3042 | 'rm' => 'audio/x-pn-realaudio', 3043 | 'rpm' => 'audio/x-pn-realaudio-plugin', 3044 | 'ra' => 'audio/x-realaudio', 3045 | 'wav' => 'audio/x-wav', 3046 | 'bmp' => 'image/bmp', 3047 | 'gif' => 'image/gif', 3048 | 'jpeg' => 'image/jpeg', 3049 | 'jpe' => 'image/jpeg', 3050 | 'jpg' => 'image/jpeg', 3051 | 'png' => 'image/png', 3052 | 'tiff' => 'image/tiff', 3053 | 'tif' => 'image/tiff', 3054 | 'eml' => 'message/rfc822', 3055 | 'css' => 'text/css', 3056 | 'html' => 'text/html', 3057 | 'htm' => 'text/html', 3058 | 'shtml' => 'text/html', 3059 | 'log' => 'text/plain', 3060 | 'text' => 'text/plain', 3061 | 'txt' => 'text/plain', 3062 | 'rtx' => 'text/richtext', 3063 | 'rtf' => 'text/rtf', 3064 | 'vcf' => 'text/vcard', 3065 | 'vcard' => 'text/vcard', 3066 | 'xml' => 'text/xml', 3067 | 'xsl' => 'text/xml', 3068 | 'mpeg' => 'video/mpeg', 3069 | 'mpe' => 'video/mpeg', 3070 | 'mpg' => 'video/mpeg', 3071 | 'mov' => 'video/quicktime', 3072 | 'qt' => 'video/quicktime', 3073 | 'rv' => 'video/vnd.rn-realvideo', 3074 | 'avi' => 'video/x-msvideo', 3075 | 'movie' => 'video/x-sgi-movie' 3076 | ); 3077 | return (array_key_exists(strtolower($ext), $mimes) ? $mimes[strtolower($ext)]: 'application/octet-stream'); 3078 | } 3079 | 3080 | /** 3081 | * Map a file name to a MIME type. 3082 | * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. 3083 | * @param string $filename A file name or full path, does not need to exist as a file 3084 | * @return string 3085 | * @static 3086 | */ 3087 | public static function filenameToType($filename) 3088 | { 3089 | // In case the path is a URL, strip any query string before getting extension 3090 | $qpos = strpos($filename, '?'); 3091 | if ($qpos !== false) { 3092 | $filename = substr($filename, 0, $qpos); 3093 | } 3094 | $pathinfo = self::mb_pathinfo($filename); 3095 | return self::_mime_types($pathinfo['extension']); 3096 | } 3097 | 3098 | /** 3099 | * Multi-byte-safe pathinfo replacement. 3100 | * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. 3101 | * Works similarly to the one in PHP >= 5.2.0 3102 | * @link http://www.php.net/manual/en/function.pathinfo.php#107461 3103 | * @param string $path A filename or path, does not need to exist as a file 3104 | * @param integer|string $options Either a PATHINFO_* constant, 3105 | * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 3106 | * @return string|array 3107 | * @static 3108 | */ 3109 | public static function mb_pathinfo($path, $options = null) 3110 | { 3111 | $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); 3112 | $pathinfo = array(); 3113 | if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) { 3114 | if (array_key_exists(1, $pathinfo)) { 3115 | $ret['dirname'] = $pathinfo[1]; 3116 | } 3117 | if (array_key_exists(2, $pathinfo)) { 3118 | $ret['basename'] = $pathinfo[2]; 3119 | } 3120 | if (array_key_exists(5, $pathinfo)) { 3121 | $ret['extension'] = $pathinfo[5]; 3122 | } 3123 | if (array_key_exists(3, $pathinfo)) { 3124 | $ret['filename'] = $pathinfo[3]; 3125 | } 3126 | } 3127 | switch ($options) { 3128 | case PATHINFO_DIRNAME: 3129 | case 'dirname': 3130 | return $ret['dirname']; 3131 | case PATHINFO_BASENAME: 3132 | case 'basename': 3133 | return $ret['basename']; 3134 | case PATHINFO_EXTENSION: 3135 | case 'extension': 3136 | return $ret['extension']; 3137 | case PATHINFO_FILENAME: 3138 | case 'filename': 3139 | return $ret['filename']; 3140 | default: 3141 | return $ret; 3142 | } 3143 | } 3144 | 3145 | /** 3146 | * Set or reset instance properties. 3147 | * 3148 | * Usage Example: 3149 | * $page->set('X-Priority', '3'); 3150 | * 3151 | * @access public 3152 | * @param string $name 3153 | * @param mixed $value 3154 | * NOTE: will not work with arrays, there are no arrays to set/reset 3155 | * @throws phpmailerException 3156 | * @return boolean 3157 | * @TODO Should this not be using __set() magic function? 3158 | */ 3159 | public function set($name, $value = '') 3160 | { 3161 | try { 3162 | if (isset($this->$name)) { 3163 | $this->$name = $value; 3164 | } else { 3165 | throw new phpmailerException($this->lang('variable_set') . $name, self::STOP_CRITICAL); 3166 | } 3167 | } catch (Exception $exc) { 3168 | $this->setError($exc->getMessage()); 3169 | if ($exc->getCode() == self::STOP_CRITICAL) { 3170 | return false; 3171 | } 3172 | } 3173 | return true; 3174 | } 3175 | 3176 | /** 3177 | * Strip newlines to prevent header injection. 3178 | * @access public 3179 | * @param string $str 3180 | * @return string 3181 | */ 3182 | public function secureHeader($str) 3183 | { 3184 | return trim(str_replace(array("\r", "\n"), '', $str)); 3185 | } 3186 | 3187 | /** 3188 | * Normalize line breaks in a string. 3189 | * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. 3190 | * Defaults to CRLF (for message bodies) and preserves consecutive breaks. 3191 | * @param string $text 3192 | * @param string $breaktype What kind of line break to use, defaults to CRLF 3193 | * @return string 3194 | * @access public 3195 | * @static 3196 | */ 3197 | public static function normalizeBreaks($text, $breaktype = "\r\n") 3198 | { 3199 | return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); 3200 | } 3201 | 3202 | 3203 | /** 3204 | * Set the public and private key files and password for S/MIME signing. 3205 | * @access public 3206 | * @param string $cert_filename 3207 | * @param string $key_filename 3208 | * @param string $key_pass Password for private key 3209 | */ 3210 | public function sign($cert_filename, $key_filename, $key_pass) 3211 | { 3212 | $this->sign_cert_file = $cert_filename; 3213 | $this->sign_key_file = $key_filename; 3214 | $this->sign_key_pass = $key_pass; 3215 | } 3216 | 3217 | /** 3218 | * Quoted-Printable-encode a DKIM header. 3219 | * @access public 3220 | * @param string $txt 3221 | * @return string 3222 | */ 3223 | public function DKIM_QP($txt) 3224 | { 3225 | $line = ''; 3226 | for ($i = 0; $i < strlen($txt); $i++) { 3227 | $ord = ord($txt[$i]); 3228 | if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { 3229 | $line .= $txt[$i]; 3230 | } else { 3231 | $line .= '=' . sprintf('%02X', $ord); 3232 | } 3233 | } 3234 | return $line; 3235 | } 3236 | 3237 | /** 3238 | * Generate a DKIM signature. 3239 | * @access public 3240 | * @param string $signHeader 3241 | * @throws phpmailerException 3242 | * @return string 3243 | */ 3244 | public function DKIM_Sign($signHeader) 3245 | { 3246 | if (!defined('PKCS7_TEXT')) { 3247 | if ($this->exceptions) { 3248 | throw new phpmailerException($this->lang('signing') . ' OpenSSL extension missing.'); 3249 | } 3250 | return ''; 3251 | } 3252 | $privKeyStr = file_get_contents($this->DKIM_private); 3253 | if ($this->DKIM_passphrase != '') { 3254 | $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); 3255 | } else { 3256 | $privKey = $privKeyStr; 3257 | } 3258 | if (openssl_sign($signHeader, $signature, $privKey)) { 3259 | return base64_encode($signature); 3260 | } 3261 | return ''; 3262 | } 3263 | 3264 | /** 3265 | * Generate a DKIM canonicalization header. 3266 | * @access public 3267 | * @param string $signHeader Header 3268 | * @return string 3269 | */ 3270 | public function DKIM_HeaderC($signHeader) 3271 | { 3272 | $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader); 3273 | $lines = explode("\r\n", $signHeader); 3274 | foreach ($lines as $key => $line) { 3275 | list($heading, $value) = explode(':', $line, 2); 3276 | $heading = strtolower($heading); 3277 | $value = preg_replace('/\s+/', ' ', $value); // Compress useless spaces 3278 | $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value 3279 | } 3280 | $signHeader = implode("\r\n", $lines); 3281 | return $signHeader; 3282 | } 3283 | 3284 | /** 3285 | * Generate a DKIM canonicalization body. 3286 | * @access public 3287 | * @param string $body Message Body 3288 | * @return string 3289 | */ 3290 | public function DKIM_BodyC($body) 3291 | { 3292 | if ($body == '') { 3293 | return "\r\n"; 3294 | } 3295 | // stabilize line endings 3296 | $body = str_replace("\r\n", "\n", $body); 3297 | $body = str_replace("\n", "\r\n", $body); 3298 | // END stabilize line endings 3299 | while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { 3300 | $body = substr($body, 0, strlen($body) - 2); 3301 | } 3302 | return $body; 3303 | } 3304 | 3305 | /** 3306 | * Create the DKIM header and body in a new message header. 3307 | * @access public 3308 | * @param string $headers_line Header lines 3309 | * @param string $subject Subject 3310 | * @param string $body Body 3311 | * @return string 3312 | */ 3313 | public function DKIM_Add($headers_line, $subject, $body) 3314 | { 3315 | $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms 3316 | $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body 3317 | $DKIMquery = 'dns/txt'; // Query method 3318 | $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) 3319 | $subject_header = "Subject: $subject"; 3320 | $headers = explode($this->LE, $headers_line); 3321 | $from_header = ''; 3322 | $to_header = ''; 3323 | $current = ''; 3324 | foreach ($headers as $header) { 3325 | if (strpos($header, 'From:') === 0) { 3326 | $from_header = $header; 3327 | $current = 'from_header'; 3328 | } elseif (strpos($header, 'To:') === 0) { 3329 | $to_header = $header; 3330 | $current = 'to_header'; 3331 | } else { 3332 | if ($current && strpos($header, ' =?') === 0) { 3333 | $current .= $header; 3334 | } else { 3335 | $current = ''; 3336 | } 3337 | } 3338 | } 3339 | $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); 3340 | $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); 3341 | $subject = str_replace( 3342 | '|', 3343 | '=7C', 3344 | $this->DKIM_QP($subject_header) 3345 | ); // Copied header fields (dkim-quoted-printable) 3346 | $body = $this->DKIM_BodyC($body); 3347 | $DKIMlen = strlen($body); // Length of body 3348 | $DKIMb64 = base64_encode(pack('H*', sha1($body))); // Base64 of packed binary SHA-1 hash of body 3349 | $ident = ($this->DKIM_identity == '') ? '' : ' i=' . $this->DKIM_identity . ';'; 3350 | $dkimhdrs = 'DKIM-Signature: v=1; a=' . 3351 | $DKIMsignatureType . '; q=' . 3352 | $DKIMquery . '; l=' . 3353 | $DKIMlen . '; s=' . 3354 | $this->DKIM_selector . 3355 | ";\r\n" . 3356 | "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . 3357 | "\th=From:To:Subject;\r\n" . 3358 | "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . 3359 | "\tz=$from\r\n" . 3360 | "\t|$to\r\n" . 3361 | "\t|$subject;\r\n" . 3362 | "\tbh=" . $DKIMb64 . ";\r\n" . 3363 | "\tb="; 3364 | $toSign = $this->DKIM_HeaderC( 3365 | $from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs 3366 | ); 3367 | $signed = $this->DKIM_Sign($toSign); 3368 | return $dkimhdrs . $signed . "\r\n"; 3369 | } 3370 | 3371 | /** 3372 | * Allows for public read access to 'to' property. 3373 | * @access public 3374 | * @return array 3375 | */ 3376 | public function getToAddresses() 3377 | { 3378 | return $this->to; 3379 | } 3380 | 3381 | /** 3382 | * Allows for public read access to 'cc' property. 3383 | * @access public 3384 | * @return array 3385 | */ 3386 | public function getCcAddresses() 3387 | { 3388 | return $this->cc; 3389 | } 3390 | 3391 | /** 3392 | * Allows for public read access to 'bcc' property. 3393 | * @access public 3394 | * @return array 3395 | */ 3396 | public function getBccAddresses() 3397 | { 3398 | return $this->bcc; 3399 | } 3400 | 3401 | /** 3402 | * Allows for public read access to 'ReplyTo' property. 3403 | * @access public 3404 | * @return array 3405 | */ 3406 | public function getReplyToAddresses() 3407 | { 3408 | return $this->ReplyTo; 3409 | } 3410 | 3411 | /** 3412 | * Allows for public read access to 'all_recipients' property. 3413 | * @access public 3414 | * @return array 3415 | */ 3416 | public function getAllRecipientAddresses() 3417 | { 3418 | return $this->all_recipients; 3419 | } 3420 | 3421 | /** 3422 | * Perform a callback. 3423 | * @param boolean $isSent 3424 | * @param array $to 3425 | * @param array $cc 3426 | * @param array $bcc 3427 | * @param string $subject 3428 | * @param string $body 3429 | * @param string $from 3430 | */ 3431 | protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from) 3432 | { 3433 | if (!empty($this->action_function) && is_callable($this->action_function)) { 3434 | $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); 3435 | call_user_func_array($this->action_function, $params); 3436 | } 3437 | } 3438 | } 3439 | 3440 | /** 3441 | * PHPMailer exception handler 3442 | * @package PHPMailer 3443 | */ 3444 | class phpmailerException extends Exception 3445 | { 3446 | /** 3447 | * Prettify error message output 3448 | * @return string 3449 | */ 3450 | public function errorMessage() 3451 | { 3452 | $errorMsg = '' . $this->getMessage() . "
\n"; 3453 | return $errorMsg; 3454 | } 3455 | } 3456 | --------------------------------------------------------------------------------