[ PHPXref.com ] [ Generated: Sun Jul 20 20:30:05 2008 ] [ Swift Mailer 1.2.1 ]
[ Index ]     [ Variables ]     [ Functions ]     [ Classes ]     [ Constants ]     [ Statistics ]

title

Body

[close]

/ -> Swift.php (source)

   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 &copy; 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  ?>


[ Powered by PHPXref - Served by Debian GNU/Linux ]