| [ PHPXref.com ] | [ Generated: Sun Jul 20 20:30:05 2008 ] | [ Swift Mailer 1.2.1 ] |
| [ Index ] [ Variables ] [ Functions ] [ Classes ] [ Constants ] [ Statistics ] | ||
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Swift Mailer: A Flexible PHP Mailer Class. 5 * 6 * Current functionality: 7 * 8 * * Send uses one single connection to the SMTP server 9 * * Doesn't rely on mail() 10 * * Unlimited redundant connections (via plugin) 11 * * Custom Headers 12 * * Sends Multipart messages, handles encoding 13 * * Sends Plain-text single-part emails 14 * * Fast Cc and Bcc handling 15 * * Set Priority Level 16 * * Request Read Receipts 17 * * Batch emailing with multiple To's or without 18 * * Support for multiple attachments 19 * * Sendmail (or other binary) support 20 * * Pluggable SMTP Authentication (LOGIN, PLAIN, MD5-CRAM, POP Before SMTP) 21 * * Secure Socket Layer connections (SSL) 22 * * Transport Layer security (TLS) - Gmail account holders! 23 * * Send mail with inline embedded images easily! 24 * * Loadable plugin support with event handling features 25 * 26 * @package Swift 27 * @version 1.2.1 28 * @author Chris Corbyn 29 * @date 8th June 2006 30 * @license http://www.gnu.org/licenses/lgpl.txt Lesser GNU Public License 31 * 32 * @copyright Copyright © 2006 Chris Corbyn - All Rights Reserved. 33 * @filesource 34 * 35 * ----------------------------------------------------------------------- 36 * 37 * This library is free software; you can redistribute it and/or 38 * modify it under the terms of the GNU Lesser General Public 39 * License as published by the Free Software Foundation; either 40 * version 2.1 of the License, or (at your option) any later version. 41 * 42 * This library is distributed in the hope that it will be useful, 43 * but WITHOUT ANY WARRANTY; without even the implied warranty of 44 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 45 * Lesser General Public License for more details. 46 * 47 * You should have received a copy of the GNU Lesser General Public 48 * License along with this library; if not, write to 49 * 50 * The Free Software Foundation, Inc., 51 * 51 Franklin Street, 52 * Fifth Floor, 53 * Boston, 54 * MA 02110-1301 USA 55 * 56 * "Chris Corbyn" <chris@w3style.co.uk> 57 * 58 */ 59 60 61 /** 62 * Swift Plugin Interface. Describes the methods which plugins should implement 63 * @package Swift 64 */ 65 interface Swift_IPlugin 66 { 67 /** 68 * Required Properties 69 * 70 * private SwiftInstance; 71 * public pluginName; 72 */ 73 74 /** 75 * Loads an instance of Swift to the Plugin 76 * 77 * @param object SwiftInstance 78 * @return void 79 */ 80 public function loadBaseObject(&$object); //Void 81 82 /** 83 * Optional Methods to implement 84 * 85 * public function onLoad(); 86 * public function onClose(); 87 * public function onFail(); 88 * public function onError(); 89 * public function onBeforeSend(); 90 * public function onSend(); 91 * public function onBeforeCommand(); 92 * public function onCommand(); 93 * public function onLog(); 94 * public function onAuthenticate(); 95 * public function onFlush(); 96 * public function onResponse(); 97 */ 98 } 99 100 /** 101 * Swift Authenticator Interface. Describes the methods which authenticators should implement 102 * @package Swift 103 */ 104 interface Swift_IAuthenticator 105 { 106 /** 107 * Required Properties 108 * private SwiftInstance; 109 * public serverString; 110 */ 111 112 /** 113 * Loads an instance of Swift to the Plugin 114 * 115 * @param object SwiftInstance 116 * @return void 117 */ 118 public function loadBaseObject(&$object); 119 /** 120 * Executes the logic in the authentication mechanism 121 * 122 * @param string username 123 * @param string password 124 * @return bool successful 125 */ 126 public function run($username, $password); //bool 127 128 } 129 130 /** 131 * Swift Connection Handler Interface. 132 * Describes the methods which connection handlers should implement 133 * @package Swift 134 */ 135 interface Swift_IConnection 136 { 137 /** 138 * Required properties 139 * 140 * public readHook; 141 * public writeHook; 142 * public error 143 */ 144 145 /** 146 * Establishes a connection with the MTA 147 * 148 * @return bool connected 149 */ 150 public function start(); 151 /** 152 * Closes the connection with the MTA 153 * 154 * @return void 155 */ 156 public function stop(); 157 /** 158 * Returns a boolean value TRUE if the connection is active. 159 * @return bool connected 160 */ 161 public function isConnected(); 162 } 163 164 /** 165 * Swift Mailer Class. 166 * Accepts connections to an MTA and deals with the sending and processing of 167 * commands and responses. 168 * @package Swift 169 */ 170 class Swift 171 { 172 /** 173 * Plugins container 174 * @var array plugins 175 * @private 176 */ 177 private $plugins = array(); 178 private $esmtp = false; 179 private $autoCompliance = true; 180 /** 181 * Whether or not Swift should send unique emails to all "To" 182 * recipients or just bulk them together in the To header. 183 * @var bool use_exact 184 */ 185 private $useExactCopy = false; 186 private $domain = 'SwiftUser'; 187 private $mimeBoundary; 188 private $mimeWarning; 189 /** 190 * MIME Parts container 191 * @var array parts 192 * @private 193 */ 194 private $parts = array(); 195 /** 196 * Attachment data container 197 * @var array attachments 198 * @private 199 */ 200 private $attachments = array(); 201 /** 202 * Inline image container 203 * @var array image parts 204 * @private 205 */ 206 private $images = array(); 207 /** 208 * Response codes expected for commands 209 * $command => $code 210 * @var array codes 211 * @private 212 */ 213 private $expectedCodes = array( 214 'ehlo' => 250, 215 'helo' => 250, 216 'auth' => 334, 217 'mail' => 250, 218 'rcpt' => 250, 219 'data' => 354 220 ); 221 /** 222 * Blind-carbon-copy address container 223 * @var array addresses 224 */ 225 private $Bcc = array(); 226 /** 227 * Carbon-copy address container 228 * @var array addresses 229 */ 230 private $Cc = array(); 231 /** 232 * The address any replies will go to 233 * @var string address 234 */ 235 private $replyTo; 236 /** 237 * The addresses we're sending to 238 * @var string address 239 */ 240 private $to = array(); 241 /** 242 * Priority value 1 (high) to 5 (low) 243 * @var int priority (1-5) 244 */ 245 private $priority = 3; 246 /** 247 * Whether a read-receipt is required 248 * @var bool read receipt 249 */ 250 private $readReceipt = false; 251 252 /** 253 * Connection object (container holding a socket) 254 * @var object connection 255 */ 256 public $connection; 257 /** 258 * Authenticators container 259 * @var array authenticators 260 */ 261 public $authenticators = array(); 262 public $authTypes = array(); 263 /** 264 * Holds the username used in authentication (if any) 265 * @var string username 266 */ 267 public $username; 268 /** 269 * Holds the password used in authentication (if any) 270 * @var string password 271 */ 272 public $password; 273 274 public $charset = "UTF-8"; 275 /** 276 * Boolean value representing if Swift has failed or not 277 * @var bool failed 278 */ 279 public $failed = false; 280 /** 281 * If Swift should clear headers etc automatically 282 * @var bool autoFlush 283 */ 284 public $autoFlush = true; 285 /** 286 * Numeric code from the last MTA response 287 * @var int code 288 */ 289 public $responseCode; 290 /** 291 * Keyword of the command being sent 292 * @var string keyword 293 */ 294 public $commandKeyword; 295 /** 296 * Last email sent or email about to be sent (dependant on location) 297 * @var array commands 298 */ 299 public $currentMail = array(); 300 // * Hey this library is FREE so it's not much to ask ;) But if you really do want to 301 // remove this header then go ahead of course... what's GPL for? :P 302 /** 303 * Email headers 304 * @var string headers 305 */ 306 public $headers = "X-Mailer: Swift by Chris Corbyn\r\n"; 307 public $currentCommand = ''; 308 /** 309 * Errors container 310 * @var array errors 311 */ 312 public $errors = array(); 313 /** 314 * Log container 315 * @var array transactions 316 */ 317 public $transactions = array(); 318 319 public $lastTransaction; 320 public $lastError; 321 /** 322 * The very most recent response received from the MTA 323 * @var string response 324 */ 325 public $lastResponse; 326 327 /** 328 * Swift Constructor 329 * @param object Swift_IConnection 330 * @param string user_domain, optional 331 */ 332 public function __construct(Swift_IConnection &$object, $domain=false) 333 { 334 if (!$domain) $domain = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'SwiftUser'; 335 336 $this->domain = $domain; 337 $this->connection =& $object; 338 339 $this->connect(); 340 341 $this->mimeWarning = "This part of the E-mail should never be seen. If\r\n". 342 "you are reading this, consider upgrading your e-mail\r\n". 343 "client to a MIME-compatible client."; 344 } 345 /** 346 * Connect to the server 347 * @return bool connected 348 */ 349 public function connect() 350 { 351 if (!$this->connection->start()) 352 { 353 $this->fail(); 354 $error = 'Connection to the given MTA failed.'; 355 if (!empty($this->connection->error)) $error .= ' The Connection Interface said: '.$this->connection->error; 356 $this->logError($error, 0); 357 return false; 358 } 359 else 360 { 361 $this->handshake(); 362 return true; 363 } 364 } 365 /** 366 * Returns TRUE if the connection is active. 367 */ 368 public function isConnected() 369 { 370 return $this->connection->isConnected(); 371 } 372 /** 373 * Sends the standard polite greetings to the MTA and then 374 * identifies the MTA's capabilities 375 */ 376 public function handshake() 377 { 378 $this->commandKeyword = ""; 379 //What did the server greet us with on connect? 380 $this->logTransaction(); 381 if ($this->supportsESMTP($this->lastResponse)) 382 { 383 //Just being polite 384 $list = $this->command("EHLO {$this->domain}\r\n"); 385 386 $this->getAuthenticationMethods($list); 387 388 $this->esmtp = true; 389 } 390 else $this->command("HELO {$this->domain}\r\n"); 391 } 392 /** 393 * Checks for Extended SMTP support 394 * @param string MTA greeting 395 * @return bool ESMTP 396 * @private 397 */ 398 private function supportsESMTP($greeting) 399 { 400 //Not mentiioned in RFC 2821 but this how it's done 401 if (strpos($greeting, 'ESMTP')) return true; 402 else return false; 403 } 404 /** 405 * Sets the priority level of the email 406 * This must be 1 to 5 where 1 is highest 407 * @param int priority 408 */ 409 public function setPriority($level) 410 { 411 $level = (int) $level; 412 if ($level < 1) $level = 1; 413 if ($level > 5) $level = 5; 414 switch ($level) 415 { 416 case 1: case 2: 417 $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: High"); 418 break; 419 case 4: case 5: 420 $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: Low"); 421 break; 422 case 3: default: 423 $this->addHeaders("X-Priority: $level\r\nX-MSMail-Priority: Normal"); 424 } 425 } 426 /** 427 * Request a read receipt from all recipients 428 * @param bool request receipt 429 */ 430 public function requestReadReceipt($request=true) 431 { 432 $this->readReceipt = (bool) $request; 433 } 434 /** 435 * Set the character encoding were using 436 * @param string charset 437 */ 438 public function setCharset($string="UTF-8") 439 { 440 $this->charset = $string; 441 } 442 /** 443 * Whether or not Swift should send unique emails to all To recipients 444 * @param bool unique 445 */ 446 public function useExactCopy($use=true) 447 { 448 $this->useExactCopy = (bool) $use; 449 } 450 /** 451 * Sets the Reply-To address used for sending mail 452 * @param string address 453 */ 454 public function setReplyTo($string) 455 { 456 $this->replyTo = $this->getAddress($string); 457 } 458 /** 459 * Add one or more Blind-carbon-copy recipients to the mail 460 * @param mixed addresses 461 */ 462 public function addBcc($addresses) 463 { 464 $this->Bcc = array_merge($this->Bcc, $this->parseAddressList((array) $addresses)); 465 } 466 /** 467 * Add one or more Carbon-copy recipients to the mail 468 * @param mixed addresses 469 */ 470 public function addCc($addresses) 471 { 472 $this->Cc = array_merge($this->Cc, $this->parseAddressList((array) $addresses)); 473 } 474 /** 475 * Force swift to break lines longer than 76 characters long 476 * @param bool resize 477 */ 478 public function useAutoLineResizing($use=true) 479 { 480 $this->autoCompliance = (bool) $use; 481 } 482 /** 483 * Associate a code with a command. Swift will fail quietly if the code 484 * returned does not match. 485 * @param string command 486 * @param int code 487 */ 488 public function addExpectedCode($command, $code) 489 { 490 $this->expectedCodes[$command] = (int) $code; 491 } 492 /** 493 * Reads the EHLO return string to see what AUTH methods are supported 494 * @param string EHLO response 495 * @return void 496 * @private 497 */ 498 private function getAuthenticationMethods($list) 499 { 500 preg_match("/^250[\-\ ]AUTH\ (.*)\r\n/m", $list, $matches); 501 if (!empty($matches[1])) 502 { 503 $types = explode(' ', $matches[1]); 504 $this->authTypes = $types; 505 } 506 } 507 /** 508 * Load a plugin object into Swift 509 * @param object Swift_IPlugin 510 * @return void 511 */ 512 public function loadPlugin(Swift_IPlugin &$object) 513 { 514 $this->plugins[$object->pluginName] =& $object; 515 $this->plugins[$object->pluginName]->loadBaseObject($this); 516 517 if (method_exists($this->plugins[$object->pluginName], 'onLoad')) 518 { 519 $this->plugins[$object->pluginName]->onLoad(); 520 } 521 } 522 /** 523 * Fetch a reference to a plugin in Swift 524 * @param string plugin name 525 * @return object Swift_IPlugin 526 */ 527 public function &getPlugin($name) 528 { 529 if (isset($this->plugins[$name])) 530 { 531 return $this->plugins[$name]; 532 } 533 } 534 /** 535 * Un-plug a loaded plugin. Returns false on failure. 536 * @param string plugin_name 537 * @return bool success 538 */ 539 public function removePlugin($name) 540 { 541 if (!isset($this->plugins[$name])) return false; 542 543 if (method_exists($this->plugins[$name], 'onUnload')) 544 { 545 $this->plugins[$name]->onUnload(); 546 } 547 unset($this->plugins[$name]); 548 return true; 549 } 550 /** 551 * Trigger event handlers 552 * @param string event handler 553 * @return void 554 * @private 555 */ 556 private function triggerEventHandler($func) 557 { 558 foreach ($this->plugins as $name => $object) 559 { 560 if (method_exists($this->plugins[$name], $func)) 561 { 562 $this->plugins[$name]->$func(); 563 } 564 } 565 } 566 /** 567 * Attempt to load any authenticators from the Swift/ directory 568 * @see RFC 2554 569 * @return void 570 * @private 571 */ 572 private function loadDefaultAuthenticators() 573 { 574 $dir = dirname(__FILE__).'/Swift'; 575 if (file_exists($dir) && is_dir($dir)) 576 { 577 $handle = opendir($dir); 578 while ($file = readdir($handle)) 579 { 580 if (preg_match('@^(Swift_\w*?_Authenticator)\.php$@', $file, $matches)) 581 { 582 require_once($dir.'/'.$file); 583 $class = $matches[1]; 584 $this->loadAuthenticator(new $class); 585 } 586 } 587 closedir($handle); 588 } 589 } 590 /** 591 * Use SMTP authentication 592 * @param string username 593 * @param string password 594 * @return bool successful 595 */ 596 public function authenticate($username, $password) 597 { 598 $this->username = $username; 599 $this->password = $password; 600 601 if (empty($this->authenticators)) $this->loadDefaultAuthenticators(); 602 603 if (!$this->esmtp || empty($this->authTypes)) 604 { 605 $this->logError('The MTA doesn\'t support any of Swift\'s loaded authentication mechanisms', 0); 606 return false; 607 } 608 foreach ($this->authenticators as $name => $object) 609 { 610 //An asterisk means that the auth type is not advertised by ESMTP 611 if (in_array($name, $this->authTypes) || substr($name, 0, 1) == '*') 612 { 613 if ($this->authenticators[$name]->run($username, $password)) 614 { 615 $this->triggerEventHandler('onAuthenticate'); 616 return true; 617 } 618 else return false; 619 } 620 } 621 //If we get this far, no authenticators were used 622 $this->logError('The MTA doesn\'t support any of Swift\'s loaded authentication mechanisms', 0); 623 $this->fail(); 624 return false; 625 } 626 /** 627 * Load an authentication mechanism object into Swift 628 * @param object Swift_IAuthenticator 629 * @return void 630 */ 631 public function loadAuthenticator(Swift_IAuthenticator &$object) 632 { 633 $this->authenticators[$object->serverString] =& $object; 634 $this->authenticators[$object->serverString]->loadBaseObject($this); 635 } 636 /** 637 * Get a unique multipart MIME boundary 638 * @param string mail data, optional 639 * @return string boundary 640 * @private 641 */ 642 private function getMimeBoundary($string=false) 643 { 644 $force = true; 645 if (!$string) 646 { 647 $force = false; 648 $string = implode('', $this->parts); 649 $string .= implode('', $this->attachments); 650 } 651 if ($this->mimeBoundary && !$force) return $this->mimeBoundary; 652 else 653 { //Make sure we don't (as if it would ever happen!) - 654 // produce a hash that's actually in the email already 655 do 656 { 657 $this->mimeBoundary = 'swift-'.strtoupper(md5($string.microtime())); 658 } while(strpos($string, $this->mimeBoundary)); 659 } 660 return $this->mimeBoundary; 661 } 662 /** 663 * Append a string to the message header 664 * @param string headers 665 * @return void 666 */ 667 public function addHeaders($string) 668 { 669 $this->headers .= $string; 670 if (substr($this->headers, -2) != "\r\n") 671 $this->headers .= "\r\n"; 672 } 673 /** 674 * Set the multipart MIME boundary (only works for first part) 675 * @param string boundary 676 * @return void 677 */ 678 public function setMimeBoundary($string) 679 { 680 $this->mimeBoundary = $string; 681 } 682 /** 683 * Set the text that displays in non-MIME clients 684 * @param string warning 685 * @return void 686 */ 687 public function setMimeWarning($warning) 688 { 689 $this->mimeWarning = $warning; 690 } 691 /** 692 * Tells Swift to clear out attachment, parts, headers etc 693 * automatically upon sending - this is the default. 694 * @param bool flush 695 */ 696 public function autoFlush($flush=true) 697 { 698 $this->autoFlush = (bool) $flush; 699 } 700 /** 701 * Empty out the MIME parts and attachments 702 * @param bool reset headers 703 * @return void 704 */ 705 public function flush($clear_headers=false) 706 { 707 $this->parts = array(); 708 $this->attachments = array(); 709 $this->images = array(); 710 $this->mimeBoundary = null; 711 $this->Bcc = array(); 712 $this->to = array(); 713 $this->Cc = array(); 714 $this->replyTo = null; 715 //See comment above the headers property above the constructor before editing this line! * 716 if ($clear_headers) $this->headers = "X-Mailer: Swift by Chris Corbyn\r\n"; 717 $this->triggerEventHandler('onFlush'); 718 } 719 /** 720 * Reset to 721 */ 722 public function flushTo() 723 { 724 $this->to = array(); 725 } 726 /** 727 * Reset Cc 728 */ 729 public function flushCc() 730 { 731 $this->Cc = array(); 732 } 733 /** 734 * Reset Bcc 735 */ 736 public function flushBcc() 737 { 738 $this->Bcc = array(); 739 } 740 /** 741 * Reset parts 742 */ 743 public function flushParts() 744 { 745 $this->parts = array(); 746 $this->images = array(); 747 } 748 /** 749 * Reset attachments 750 */ 751 public function flushAttachments() 752 { 753 $this->attchments = array(); 754 } 755 /** 756 * Reset headers 757 */ 758 public function flushHeaders() 759 { 760 $this->headers = "X-Mailer: Swift by Chris Corbyn\r\n"; 761 } 762 /** 763 * Log an error in Swift::errors 764 * @param string error string 765 * @param int error number 766 * @return void 767 */ 768 public function logError($errstr, $errno=0) 769 { 770 $this->errors[] = array( 771 'num' => $errno, 772 'time' => microtime(), 773 'message' => $errstr 774 ); 775 $this->lastError = $errstr; 776 777 $this->triggerEventHandler('onError'); 778 } 779 /** 780 * Log a transaction in Swift::transactions 781 * @param string command 782 * @return void 783 */ 784 public function logTransaction($command='') 785 { 786 $this->lastTransaction = array( 787 'command' => $command, 788 'time' => microtime(), 789 'response' => $this->getResponse() 790 ); 791 $this->triggerEventHandler('onLog'); 792 $this->transactions[] = $this->lastTransaction; 793 } 794 /** 795 * Read the data from the socket 796 * @return string response 797 * @private 798 */ 799 private function getResponse() 800 { 801 if (!$this->connection->readHook || !$this->isConnected() || $this->commandKeyword == 'quit') return false; 802 $ret = ""; 803 while (true) 804 { 805 $tmp = fgets($this->connection->readHook); 806 $ret .= $tmp; 807 //The last line of SMTP replies have a space after the status number 808 // They do NOT have an EOF so while(!feof($socket)) will hang! 809 if (substr($tmp, 3, 1) == ' ') break; 810 } 811 $this->responseCode = $this->getResponseCode($ret); 812 $this->lastResponse = $ret; 813 $this->triggerEventHandler('onResponse'); 814 return $this->lastResponse; 815 } 816 /** 817 * Get the number of the last server response 818 * @param string response string 819 * @return int response code 820 * @private 821 */ 822 private function getResponseCode($string) 823 { 824 return (int) sprintf("%d", $string); 825 } 826 /** 827 * Get the first word of the command 828 * @param string command 829 * @return string keyword 830 * @private 831 */ 832 private function getCommandKeyword($comm) 833 { 834 if (false !== $pos = strpos($comm, ' ')) 835 { 836 return strtolower(substr($comm, 0, $pos)); 837 } 838 else return strtolower($comm); 839 } 840 /** 841 * Issue a command to the socket 842 * @param string command 843 * @return string response 844 */ 845 public function command($comm) 846 { 847 $this->currentCommand = ltrim($comm); 848 849 $this->triggerEventHandler('onBeforeCommand'); 850 851 if (!$this->connection->writeHook || !$this->isConnected() || $this->failed) 852 { 853 $this->logError('Error running command: '.trim($comm).'. No connection available', 0); 854 return false; 855 } 856 857 $command_keyword = $this->getCommandKeyword($this->currentCommand); 858 859 //SMTP commands must end with CRLF 860 if (substr($this->currentCommand, -2) != "\r\n") $this->currentCommand .= "\r\n"; 861 862 if (@fwrite($this->connection->writeHook, $this->currentCommand)) 863 { 864 $this->logTransaction($this->currentCommand); 865 if (array_key_exists($command_keyword, $this->expectedCodes)) 866 { 867 if ($this->expectedCodes[$command_keyword] != $this->responseCode) 868 { 869 $this->fail(); 870 $this->logError('MTA Error: '.$this->lastResponse, $this->responseCode); 871 return false; 872 } 873 } 874 $this->triggerEventHandler('onCommand'); 875 return $this->lastResponse; 876 } 877 else return false; 878 } 879 /** 880 * Splits lines longer than 76 characters to multiple lines 881 * @param string text 882 * @return string chunked output 883 */ 884 public function chunkSplitLines($string) 885 { 886 return wordwrap($string, 74, "\r\n"); 887 } 888 /** 889 * Add a part to a multipart message 890 * @param string body 891 * @param string content-type, optional 892 * @param string content-transfer-encoding, optional 893 * @return void 894 */ 895 public function addPart($string, $type='text/plain', $encoding='7bit') 896 { 897 $body_string = $this->encode($string, $encoding); 898 if ($this->autoCompliance) $body_string = $this->chunkSplitLines($body_string); 899 $ret = "Content-Type: $type; charset=\"{$this->charset}\"\r\n". 900 "Content-Transfer-Encoding: $encoding\r\n\r\n". 901 $body_string; 902 903 if (strtolower($type) == 'text/html') $this->parts[] = $ret; 904 else $this->parts = array_merge((array) $ret, $this->parts); 905 } 906 /** 907 * Add an attachment to a multipart message. 908 * Attachments are added as base64 encoded data. 909 * @param string data 910 * @param string filename 911 * @param string content-type 912 * @return void 913 */ 914 public function addAttachment($data, $filename, $type='application/octet-stream') 915 { 916 $ret = "Content-Type: $type; ". 917 "name=\"$filename\";\r\n". 918 "Content-Transfer-Encoding: base64\r\n". 919 "Content-Description: $filename\r\n". 920 "Content-Disposition: attachment; ". 921 "filename=\"$filename\"\r\n\r\n". 922 chunk_split($this->encode($data, 'base64')); 923 $this->attachments[] = $ret; 924 } 925 /** 926 * Insert an inline image and return it's name 927 * These work like attachments but have a content-id 928 * and are inline/related. 929 * @param string path 930 * @return string name 931 */ 932 public function addImage($path) 933 { 934 if (!file_exists($path)) return false; 935 936 $gpc = ini_get('magic_quotes_gpc'); 937 ini_set('magic_quotes_gpc', 0); 938 $gpc_run = ini_get('magic_quotes_runtime'); 939 ini_set('magic_quotes_runtime', 0); 940 941 $img_data = @getimagesize($path); 942 if (!$img_data) return false; 943 944 $type = image_type_to_mime_type($img_data[2]); 945 $filename = basename($path); 946 $data = file_get_contents($path); 947 $cid = 'SWM'.md5(uniqid(rand(), true)); 948 949 $ret = "Content-Type: $type\r\n". 950 "Content-Transfer-Encoding: base64\r\n". 951 "Content-Disposition: inline; ". 952 "filename=\"$filename\"\r\n". 953 "Content-ID: <$cid>\r\n\r\n". 954 chunk_split($this->encode($data, 'base64')); 955 $this->images[] = $ret; 956 957 ini_set('magic_quotes_gpc', $gpc); 958 ini_set('magic_quotes_runtime', $gpc_run); 959 960 return 'cid:'.$cid; 961 } 962 /** 963 * Close the connection in the connecion object 964 * @return void 965 */ 966 public function close() 967 { 968 if ($this->connection->writeHook && $this->isConnected()) 969 { 970 $this->command("QUIT\r\n"); 971 $this->connection->stop(); 972 } 973 $this->triggerEventHandler('onClose'); 974 } 975 /** 976 * Check if Swift has failed and stopped processing 977 * @return bool failed 978 */ 979 public function hasFailed() 980 { 981 return $this->failed; 982 } 983 /** 984 * Force Swift to fail and stop processing 985 * @return void 986 */ 987 public function fail() 988 { 989 $this->failed = true; 990 $this->triggerEventHandler('onFail'); 991 } 992 /** 993 * Encode a string (mail) in a given format 994 * Currently supports: 995 * - BASE64 996 * - Quoted-Printable 997 * - Ascii 7-bit 998 * 999 * @param string input 1000 * @param string encoding 1001 * @return string encoded output 1002 */ 1003 public function encode($string, $type) 1004 { 1005 $type = strtolower($type); 1006 1007 switch ($type) 1008 { 1009 case 'base64': 1010 $string = base64_encode($string); 1011 break; 1012 // 1013 case 'quoted-printable': 1014 $string = $this->quotedPrintableEncode($string); 1015 // 1016 case '7bit': 1017 default: 1018 break; 1019 } 1020 1021 return $string; 1022 } 1023 /** 1024 * Handles quoted-printable encoding 1025 * From php.net by user bendi at interia dot pl 1026 * @param string input 1027 * @return string encoded output 1028 * @private 1029 */ 1030 private function quotedPrintableEncode($string) 1031 { 1032 $string = preg_replace('/[^\x21-\x3C\x3E-\x7E\x09\x20]/e', 'sprintf( "=%02x", ord ( "$0" ) ) ;', $string); 1033 preg_match_all('/.{1,73}([^=]{0,3})?/', $string, $matches); 1034 return implode("=\r\n", $matches[0]); 1035 } 1036 /** 1037 * Converts lone LF characters to CRLF 1038 * @param string input 1039 * @return string converted output 1040 */ 1041 public function LFtoCRLF($string) 1042 { 1043 return preg_replace("@(?<!\r)\n@", "\r\n", $string); 1044 } 1045 /** 1046 * Prevents premature <CRLF>.<CRLF> strings 1047 * Converts any lone LF characters to CRLF 1048 * @param string input 1049 * @return string escaped output 1050 */ 1051 public function makeSafe($string) 1052 { 1053 $stack = array(); 1054 $lines = explode("\r\n", $string); 1055 foreach ($lines as $l) 1056 { 1057 //The dot will be deleted at the server end (see RFC 2821 4.5.2) 1058 if (substr($l, 0, 1) == '.') $l = '.'.$l; 1059 $stack[] = $l; 1060 } 1061 return $this->LFtoCRLF(implode("\r\n", $stack)); 1062 } 1063 /** 1064 * Pulls an email address from a "Name" <add@ress> string 1065 * @param string input 1066 * @return string address 1067 */ 1068 private function getAddress($string) 1069 { 1070 if (preg_match('/^.*?<([^>]+)>\s*$/', $string, $matches)) 1071 { 1072 return '<'.$matches[1].'>'; 1073 } 1074 elseif (!preg_match('/<|>/', $string)) return '<'.$string.'>'; 1075 else return $string; 1076 } 1077 /** 1078 * Builds the headers needed to reflect who the mail is sent to 1079 * Presently this is just the "To: " header 1080 * @param string address 1081 * @return string headers 1082 * @private 1083 */ 1084 private function makeRecipientHeaders($address=false) 1085 { 1086 if ($address) return "To: $address\r\n"; 1087 else 1088 { 1089 $ret = "To: ".implode(",\r\n\t", $this->to)."\r\n"; 1090 if (!empty($this->Cc)) $ret .= "Cc: ".implode(",\r\n\t", $this->Cc)."\r\n"; 1091 return $ret; 1092 } 1093 } 1094 /** 1095 * Structure a given array of addresses into the 1-dim we want 1096 * @param array unstructured 1097 * @return array structured 1098 * @private 1099 */ 1100 private function parseAddressList($u_array) 1101 { 1102 $ret = array(); 1103 foreach ($u_array as $val) 1104 { 1105 if (is_array($val)) $ret[] = '"'.$val[0].'" <'.$val[1].'>'; 1106 else $ret[] = $val; 1107 } 1108 return $ret; 1109 } 1110 /** 1111 * Send an email using Swift (send commands) 1112 * @param string to_address 1113 * @param string from_address 1114 * @param string subject 1115 * @param string body, optional 1116 * @param string content-type,optional 1117 * @param string content-transfer-encoding,optional 1118 * @return bool successful 1119 */ 1120 public function send($to, $from, $subject, $body=false, $type='text/plain', $encoding='7bit') 1121 { 1122 $to = (array) $to; 1123 $this->to = $this->parseAddressList($to); 1124 //In these cases we just send the one email 1125 if ($this->useExactCopy || !empty($this->Cc) || !empty($this->Bcc)) 1126 { 1127 $this->currentMail = $this->buildMail(false, $from, $subject, $body, $type, $encoding, 1); 1128 $this->triggerEventHandler('onBeforeSend'); 1129 foreach ($this->currentMail as $command) 1130 { 1131 if (is_array($command)) 1132 { //Commands can be returned as 1-dimensional arrays 1133 foreach ($command as $c) 1134 { 1135 if (!$this->command($c)) 1136 { 1137 $this->logError('Sending failed on command: '.$c, 0); 1138 return false; 1139 } 1140 } 1141 } 1142 else if (!$this->command($command)) 1143 { 1144 $this->logError('Sending failed on command: '.$command, 0); 1145 return false; 1146 } 1147 } 1148 $this->triggerEventHandler('onSend'); 1149 } 1150 else 1151 { 1152 $get_body = true; 1153 $cached_body = ''; 1154 foreach ($this->to as $address) 1155 { 1156 $this->currentMail = $this->buildMail($address, $from, $subject, $body, $type, $encoding, $get_body); 1157 //If we have a cached version 1158 if (!$get_body) $this->currentMail[] = $this->makeRecipientHeaders($address).$cached_body; 1159 $this->triggerEventHandler('onBeforeSend'); 1160 foreach ($this->currentMail as $command) 1161 { 1162 //This means we're about to send the DATA part 1163 if ($get_body && $this->responseCode == 354) 1164 { 1165 $cached_body = $command; 1166 $command = $this->makeRecipientHeaders($address).$command; 1167 } 1168 if (is_array($command)) 1169 { 1170 foreach ($command as $c) 1171 { 1172 if (!$this->command($c)) 1173 { 1174 $this->logError('Sending failed on command: '.$c, 0); 1175 return false; 1176 } 1177 } 1178 } 1179 else if (!$this->command($command)) 1180 { 1181 $this->logError('Sending failed on command: '.$command, 0); 1182 return false; 1183 } 1184 } 1185 $this->triggerEventHandler('onSend'); 1186 $get_body = false; 1187 } 1188 } 1189 if ($this->autoFlush) $this->flush(true); //Tidy up a bit 1190 return true; 1191 } 1192 /** 1193 * Builds the list of commands to send the email 1194 * The last command in the output is the email itself (DATA) 1195 * The commands are as follows: 1196 * - MAIL FROM: <address> (0) 1197 * - RCPT TO: <address> (1) 1198 * - DATA (2) 1199 * - <email> (3) 1200 * 1201 * @param string to_address 1202 * @param string from_address 1203 * @param string subject 1204 * @param string body, optional 1205 * @param string content-type, optional 1206 * @param string encoding, optional 1207 * @return array commands 1208 * @private 1209 */ 1210 private function buildMail($to, $from, $subject, $body, $type='text/plain', $encoding='7bit', $return_data_part=true) 1211 { 1212 $date = date('r'); //RFC 2822 date 1213 $ret = array("MAIL FROM: ".$this->getAddress($from)."\r\n"); //Always 1214 //If the user specifies a different reply-to 1215 $reply_to = !empty($this->replyTo) ? $this->getAddress($this->replyTo) : $this->getAddress($from); 1216 //Standard headers 1217 $data = "From: $from\r\n". 1218 "Reply-To: $reply_to\r\n". 1219 "Subject: $subject\r\n". 1220 "Date: $date\r\n"; 1221 if ($this->readReceipt) $data .= "Disposition-Notification-To: $from\r\n"; 1222 1223 if (!$to) //Only need one mail if no address was given 1224 { //We'll collate the addresses from the class properties 1225 $data .= $this->getMimeBody($body, $type, $encoding)."\r\n.\r\n"; 1226 $headers = $this->makeRecipientHeaders(); 1227 //Rcpt can be run several times 1228 $rcpt = array(); 1229 foreach ($this->to as $address) $rcpt[] = "RCPT TO: ".$this->getAddress($address)."\r\n"; 1230 foreach ($this->Cc as $address) $rcpt[] = "RCPT TO: ".$this->getAddress($address)."\r\n"; 1231 $ret[] = $rcpt; 1232 $ret[] = "DATA\r\n"; 1233 $ret[] = $headers.$this->headers.$data; 1234 //Bcc recipients get to see their own Bcc header but nobody else's 1235 foreach ($this->Bcc as $address) 1236 { 1237 $ret[] = "MAIL FROM: ".$this->getAddress($from)."\r\n"; 1238 $ret[] = "RCPT TO: ".$this->getAddress($address)."\r\n"; 1239 $ret[] = "DATA\r\n"; 1240 $ret[] = $headers."Bcc: $address\r\n".$this->headers.$data; 1241 } 1242 } 1243 else //Just make this individual email 1244 { 1245 if ($return_data_part) $mail_body = $this->getMimeBody($body, $type, $encoding); 1246 $ret[] = "RCPT TO: ".$this->getAddress($to)."\r\n"; 1247 $ret[] = "DATA\r\n"; 1248 if ($return_data_part) $ret[] = $data.$this->headers.$mail_body."\r\n.\r\n"; 1249 } 1250 return $ret; 1251 } 1252 /** 1253 * Returns the MIME-specific headers followed by the email 1254 * content as a string. 1255 * @param string body 1256 * @param string content-type 1257 * @param string encoding 1258 * @return string mime data 1259 * @private 1260 */ 1261 private function getMimeBody($string, $type, $encoding) 1262 { 1263 if ($string) //Not using MIME parts 1264 { 1265 $body = $this->encode($string, $encoding); 1266 if ($this->autoCompliance) $body = $this->chunkSplitLines($body); 1267 $data = "Content-Type: $type; charset=\"{$this->charset}\"\r\n". 1268 "Content-Transfer-Encoding: $encoding\r\n\r\n". 1269 $body; 1270 } 1271 else 1272 { //Build a full email from the parts we have 1273 $boundary = $this->getMimeBoundary(); 1274 $alternative_boundary = $this->getMimeBoundary(implode($this->parts)); 1275 1276 if (!empty($this->images)) 1277 { 1278 $related_boundary = $this->getMimeBoundary(implode($this->parts).implode($this->images)); 1279 1280 $message_body = "Content-Type: multipart/related; ". 1281 "boundary=\"{$related_boundary}\"\r\n\r\n". 1282 "--{$related_boundary}\r\n"; 1283 1284 $parts_body = "Content-Type: multipart/alternative; ". 1285 "boundary=\"{$alternative_boundary}\"\r\n\r\n". 1286 "--{$alternative_boundary}\r\n". 1287 implode("\r\n\r\n--$alternative_boundary\r\n", $this->parts). 1288 "\r\n--$alternative_boundary--\r\n"; 1289 1290 $message_body .= $parts_body. 1291 "--$related_boundary\r\n"; 1292 1293 $images_body = implode("\r\n\r\n--$related_boundary\r\n", $this->images); 1294 1295 $message_body .= $images_body. 1296 "\r\n--$related_boundary--\r\n"; 1297 1298 } 1299 else $message_body = "Content-Type: multipart/alternative; ". 1300 "boundary=\"{$alternative_boundary}\"\r\n\r\n". 1301 "--{$alternative_boundary}\r\n". 1302 implode("\r\n\r\n--$alternative_boundary\r\n", $this->parts). 1303 "\r\n--$alternative_boundary--\r\n"; 1304 1305 if (!empty($this->attachments)) //Make a sub-message that contains attachment data 1306 { 1307 $message_body .= "\r\n\r\n--$boundary\r\n". 1308 implode("\r\n--$boundary\r\n", $this->attachments); 1309 } 1310 1311 $data = "MIME-Version: 1.0\r\n". 1312 "Content-Type: multipart/mixed;\r\n". 1313 " boundary=\"{$boundary}\"\r\n". 1314 "Content-Transfer-Encoding: 7bit\r\n\r\n". 1315 "--$boundary\r\n". 1316 "$message_body\r\n". 1317 "--$boundary--"; 1318 } 1319 return $this->makeSafe($data); 1320 } 1321 } 1322 1323 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| [ Powered by PHPXref - Served by Debian GNU/Linux ] |