1<?php 2 3/** 4 * Swift Mailer Core Component. 5 * Please read the LICENSE file 6 * @copyright Chris Corbyn <chris@w3style.co.uk> 7 * @author Chris Corbyn <chris@w3style.co.uk> 8 * @package Swift 9 * @version 3.3.2 10 * @license GNU Lesser General Public License 11 */ 12 13require_once dirname(__FILE__) . "/Swift/ClassLoader.php"; 14Swift_ClassLoader::load("Swift_LogContainer"); 15Swift_ClassLoader::load("Swift_ConnectionBase"); 16Swift_ClassLoader::load("Swift_BadResponseException"); 17Swift_ClassLoader::load("Swift_Cache"); 18Swift_ClassLoader::load("Swift_CacheFactory"); 19Swift_ClassLoader::load("Swift_Message"); 20Swift_ClassLoader::load("Swift_RecipientList"); 21Swift_ClassLoader::load("Swift_BatchMailer"); 22Swift_ClassLoader::load("Swift_Events"); 23Swift_ClassLoader::load("Swift_Events_Listener"); 24 25/** 26 * Swift is the central component in the Swift library. 27 * @package Swift 28 * @author Chris Corbyn <chris@w3style.co.uk> 29 * @version 3.3.2 30 */ 31class Swift 32{ 33 /** 34 * The version number. 35 */ 36 const VERSION = "3.3.2"; 37 /** 38 * Constant to flag Swift not to try and connect upon instantiation 39 */ 40 const NO_START = 2; 41 /** 42 * Constant to tell Swift not to perform the standard SMTP handshake upon connect 43 */ 44 const NO_HANDSHAKE = 4; 45 /** 46 * Constant to ask Swift to start logging 47 */ 48 const ENABLE_LOGGING = 8; 49 /** 50 * Constant to prevent postConnect() being run in the connection 51 */ 52 const NO_POST_CONNECT = 16; 53 /** 54 * The connection object currently active 55 * @var Swift_Connection 56 */ 57 public $connection = null; 58 /** 59 * The domain name of this server (should technically be a FQDN) 60 * @var string 61 */ 62 protected $domain = null; 63 /** 64 * Flags to change the behaviour of Swift 65 * @var int 66 */ 67 protected $options; 68 /** 69 * Loaded plugins, separated into containers according to roles 70 * @var array 71 */ 72 protected $listeners = array(); 73 74 /** 75 * Constructor 76 * @param Swift_Connection The connection object to deal with I/O 77 * @param string The domain name of this server (the client) as a FQDN 78 * @param int Optional flags 79 * @throws Swift_ConnectionException If a connection cannot be established or the connection is behaving incorrectly 80 */ 81 public function __construct(Swift_Connection $conn, $domain=false, $options=null) 82 { 83 $this->initializeEventListenerContainer(); 84 $this->setOptions($options); 85 86 $log = Swift_LogContainer::getLog(); 87 88 if ($this->hasOption(self::ENABLE_LOGGING) && !$log->isEnabled()) 89 { 90 $log->setLogLevel(Swift_Log::LOG_NETWORK); 91 } 92 93 if (!$domain) $domain = !empty($_SERVER["SERVER_ADDR"]) ? "[" . $_SERVER["SERVER_ADDR"] . "]" : "localhost.localdomain"; 94 95 $this->setDomain($domain); 96 $this->connection = $conn; 97 98 if ($conn && !$this->hasOption(self::NO_START)) 99 { 100 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) $log->add("Trying to connect...", Swift_Log::NORMAL); 101 $this->connect(); 102 } 103 } 104 /** 105 * Populate the listeners array with the defined listeners ready for plugins 106 */ 107 protected function initializeEventListenerContainer() 108 { 109 Swift_ClassLoader::load("Swift_Events_ListenerMapper"); 110 foreach (Swift_Events_ListenerMapper::getMap() as $interface => $method) 111 { 112 if (!isset($this->listeners[$interface])) 113 $this->listeners[$interface] = array(); 114 } 115 } 116 /** 117 * Add a new plugin to Swift 118 * Plugins must implement one or more event listeners 119 * @param Swift_Events_Listener The plugin to load 120 */ 121 public function attachPlugin(Swift_Events_Listener $plugin, $id) 122 { 123 foreach (array_keys($this->listeners) as $key) 124 { 125 $listener = "Swift_Events_" . $key; 126 Swift_ClassLoader::load($listener); 127 if ($plugin instanceof $listener) $this->listeners[$key][$id] = $plugin; 128 } 129 } 130 /** 131 * Get an attached plugin if it exists 132 * @param string The id of the plugin 133 * @return Swift_Event_Listener 134 */ 135 public function getPlugin($id) 136 { 137 foreach ($this->listeners as $type => $arr) 138 { 139 if (isset($arr[$id])) return $this->listeners[$type][$id]; 140 } 141 return null; //If none found 142 } 143 /** 144 * Remove a plugin attached under the ID of $id 145 * @param string The ID of the plugin 146 */ 147 public function removePlugin($id) 148 { 149 foreach ($this->listeners as $type => $arr) 150 { 151 if (isset($arr[$id])) 152 { 153 $this->listeners[$type][$id] = null; 154 unset($this->listeners[$type][$id]); 155 } 156 } 157 } 158 /** 159 * Send a new type of event to all objects which are listening for it 160 * @param Swift_Events The event to send 161 * @param string The type of event 162 */ 163 public function notifyListeners($e, $type) 164 { 165 Swift_ClassLoader::load("Swift_Events_ListenerMapper"); 166 if (!empty($this->listeners[$type]) && $notifyMethod = Swift_Events_ListenerMapper::getNotifyMethod($type)) 167 { 168 $e->setSwift($this); 169 foreach ($this->listeners[$type] as $k => $listener) 170 { 171 $listener->$notifyMethod($e); 172 } 173 } 174 else $e = null; 175 } 176 /** 177 * Check if an option flag has been set 178 * @param string Option name 179 * @return boolean 180 */ 181 public function hasOption($option) 182 { 183 return ($this->options & $option); 184 } 185 /** 186 * Adjust the options flags 187 * E.g. $obj->setOptions(Swift::NO_START | Swift::NO_HANDSHAKE) 188 * @param int The bits to set 189 */ 190 public function setOptions($options) 191 { 192 $this->options = (int) $options; 193 } 194 /** 195 * Get the current options set (as bits) 196 * @return int 197 */ 198 public function getOptions() 199 { 200 return (int) $this->options; 201 } 202 /** 203 * Set the FQDN of this server as it will identify itself 204 * @param string The FQDN of the server 205 */ 206 public function setDomain($name) 207 { 208 $this->domain = (string) $name; 209 } 210 /** 211 * Attempt to establish a connection with the service 212 * @throws Swift_ConnectionException If the connection cannot be established or behaves oddly 213 */ 214 public function connect() 215 { 216 $this->connection->start(); 217 $greeting = $this->command("", 220); 218 if (!$this->hasOption(self::NO_HANDSHAKE)) 219 { 220 $this->handshake($greeting); 221 } 222 Swift_ClassLoader::load("Swift_Events_ConnectEvent"); 223 $this->notifyListeners(new Swift_Events_ConnectEvent($this->connection), "ConnectListener"); 224 } 225 /** 226 * Disconnect from the MTA 227 * @throws Swift_ConnectionException If the connection will not stop 228 */ 229 public function disconnect() 230 { 231 $this->command("QUIT"); 232 $this->connection->stop(); 233 Swift_ClassLoader::load("Swift_Events_DisconnectEvent"); 234 $this->notifyListeners(new Swift_Events_DisconnectEvent($this->connection), "DisconnectListener"); 235 } 236 /** 237 * Throws an exception if the response code wanted does not match the one returned 238 * @param Swift_Event_ResponseEvent The full response from the service 239 * @param int The 3 digit response code wanted 240 * @throws Swift_BadResponseException If the code does not match 241 */ 242 protected function assertCorrectResponse(Swift_Events_ResponseEvent $response, $codes) 243 { 244 $codes = (array)$codes; 245 if (!in_array($response->getCode(), $codes)) 246 { 247 $log = Swift_LogContainer::getLog(); 248 $error = "Expected response code(s) [" . implode(", ", $codes) . "] but got response [" . $response->getString() . "]"; 249 if ($log->hasLevel(Swift_Log::LOG_ERRORS)) $log->add($error, Swift_Log::ERROR); 250 throw new Swift_BadResponseException($error); 251 } 252 } 253 /** 254 * Have a polite greeting with the server and work out what it's capable of 255 * @param Swift_Events_ResponseEvent The initial service line respoonse 256 * @throws Swift_ConnectionException If conversation is not going very well 257 */ 258 protected function handshake(Swift_Events_ResponseEvent $greeting) 259 { 260 if ($this->connection->getRequiresEHLO() || strpos($greeting->getString(), "ESMTP")) 261 $this->setConnectionExtensions($this->command("EHLO " . $this->domain, 250)); 262 else $this->command("HELO " . $this->domain, 250); 263 //Connection might want to do something like authenticate now 264 if (!$this->hasOption(self::NO_POST_CONNECT)) $this->connection->postConnect($this); 265 } 266 /** 267 * Set the extensions which the service reports in the connection object 268 * @param Swift_Events_ResponseEvent The list of extensions as reported by the service 269 */ 270 protected function setConnectionExtensions(Swift_Events_ResponseEvent $list) 271 { 272 $le = (strpos($list->getString(), "\r\n") !== false) ? "\r\n" : "\n"; 273 $list = explode($le, $list->getString()); 274 for ($i = 1, $len = count($list); $i < $len; $i++) 275 { 276 $extension = substr($list[$i], 4); 277 $attributes = split("[ =]", $extension); 278 $this->connection->setExtension($attributes[0], (isset($attributes[1]) ? array_slice($attributes, 1) : array())); 279 } 280 } 281 /** 282 * Execute a command against the service and get the response 283 * @param string The command to execute (leave off any CRLF!!!) 284 * @param int The code to check for in the response, if any. -1 indicates that no response is wanted. 285 * @return Swift_Events_ResponseEvent The server's response (could be multiple lines) 286 * @throws Swift_ConnectionException If a code was expected but does not match the one returned 287 */ 288 public function command($command, $code=null) 289 { 290 $log = Swift_LogContainer::getLog(); 291 Swift_ClassLoader::load("Swift_Events_CommandEvent"); 292 if ($command !== "") 293 { 294 $command_event = new Swift_Events_CommandEvent($command, $code); 295 $command = null; //For memory reasons 296 $this->notifyListeners($command_event, "BeforeCommandListener"); 297 if ($log->hasLevel(Swift_Log::LOG_NETWORK) && $code != -1) $log->add($command_event->getString(), Swift_Log::COMMAND); 298 $end = ($code != -1) ? "\r\n" : null; 299 $this->connection->write($command_event->getString(), $end); 300 $this->notifyListeners($command_event, "CommandListener"); 301 } 302 303 if ($code == -1) return null; 304 305 Swift_ClassLoader::load("Swift_Events_ResponseEvent"); 306 $response_event = new Swift_Events_ResponseEvent($this->connection->read()); 307 $this->notifyListeners($response_event, "ResponseListener"); 308 if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add($response_event->getString(), Swift_Log::RESPONSE); 309 if ($command !== "" && $command_event->getCode() !== null) 310 $this->assertCorrectResponse($response_event, $command_event->getCode()); 311 return $response_event; 312 } 313 /** 314 * Reset a conversation which has gone badly 315 * @throws Swift_ConnectionException If the service refuses to reset 316 */ 317 public function reset() 318 { 319 $this->command("RSET", 250); 320 } 321 /** 322 * Send a message to any number of recipients 323 * @param Swift_Message The message to send. This does not need to (and shouldn't really) have any of the recipient headers set. 324 * @param mixed The recipients to send to. Can be a string, Swift_Address or Swift_RecipientList. Note that all addresses apart from Bcc recipients will appear in the message headers 325 * @param mixed The address to send the message from. Can either be a string or an instance of Swift_Address. 326 * @return int The number of successful recipients 327 * @throws Swift_ConnectionException If sending fails for any reason. 328 */ 329 public function send(Swift_Message $message, $recipients, $from) 330 { 331 Swift_ClassLoader::load("Swift_Message_Encoder"); 332 if (is_string($recipients) && preg_match("/^" . Swift_Message_Encoder::CHEAP_ADDRESS_RE . "\$/", $recipients)) 333 { 334 $recipients = new Swift_Address($recipients); 335 } 336 elseif (!($recipients instanceof Swift_AddressContainer)) 337 throw new Exception("The recipients parameter must either be a valid string email address, ". 338 "an instance of Swift_RecipientList or an instance of Swift_Address."); 339 340 if (is_string($from) && preg_match("/^" . Swift_Message_Encoder::CHEAP_ADDRESS_RE . "\$/", $from)) 341 { 342 $from = new Swift_Address($from); 343 } 344 elseif (!($from instanceof Swift_Address)) 345 throw new Exception("The sender parameter must either be a valid string email address or ". 346 "an instance of Swift_Address."); 347 348 $log = Swift_LogContainer::getLog(); 349 350 if (!$message->getEncoding() && !$this->connection->hasExtension("8BITMIME")) 351 { 352 $message->setEncoding("QP", true, true); 353 } 354 355 $list = $recipients; 356 if ($recipients instanceof Swift_Address) 357 { 358 $list = new Swift_RecipientList(); 359 $list->addTo($recipients); 360 } 361 362 Swift_ClassLoader::load("Swift_Events_SendEvent"); 363 $send_event = new Swift_Events_SendEvent($message, $list, $from, 0); 364 365 $this->notifyListeners($send_event, "BeforeSendListener"); 366 367 $to = $cc = array(); 368 if (!($has_from = $message->getFrom())) $message->setFrom($from); 369 if (!($has_return_path = $message->getReturnPath())) $message->setReturnPath($from->build(true)); 370 if (!($has_reply_to = $message->getReplyTo())) $message->setReplyTo($from); 371 if (!($has_message_id = $message->getId())) $message->generateId(); 372 373 $this->command("MAIL FROM: " . $message->getReturnPath(true), 250); 374 375 $failed = 0; 376 $sent = 0; 377 $tmp_sent = 0; 378 379 $it = $list->getIterator("to"); 380 while ($it->hasNext()) 381 { 382 $it->next(); 383 $address = $it->getValue(); 384 $to[] = $address->build(); 385 try { 386 $this->command("RCPT TO: " . $address->build(true), 250); 387 $tmp_sent++; 388 } catch (Swift_BadResponseException $e) { 389 $failed++; 390 $send_event->addFailedRecipient($address->getAddress()); 391 if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress()); 392 } 393 } 394 $it = $list->getIterator("cc"); 395 while ($it->hasNext()) 396 { 397 $it->next(); 398 $address = $it->getValue(); 399 $cc[] = $address->build(); 400 try { 401 $this->command("RCPT TO: " . $address->build(true), 250); 402 $tmp_sent++; 403 } catch (Swift_BadResponseException $e) { 404 $failed++; 405 $send_event->addFailedRecipient($address->getAddress()); 406 if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress()); 407 } 408 } 409 410 if ($failed == (count($to) + count($cc))) 411 { 412 $this->reset(); 413 $this->notifyListeners($send_event, "SendListener"); 414 return 0; 415 } 416 417 if (!($has_to = $message->getTo()) && !empty($to)) $message->setTo($to); 418 if (!($has_cc = $message->getCc()) && !empty($cc)) $message->setCc($cc); 419 420 $this->command("DATA", 354); 421 $data = $message->build(); 422 423 while (false !== $bytes = $data->read()) 424 $this->command($bytes, -1); 425 if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("<MESSAGE DATA>", Swift_Log::COMMAND); 426 try { 427 $this->command("\r\n.", 250); 428 $sent += $tmp_sent; 429 } catch (Swift_BadResponseException $e) { 430 $failed += $tmp_sent; 431 } 432 433 $tmp_sent = 0; 434 $has_bcc = $message->getBcc(); 435 $it = $list->getIterator("bcc"); 436 while ($it->hasNext()) 437 { 438 $it->next(); 439 $address = $it->getValue(); 440 if (!$has_bcc) $message->setBcc($address->build()); 441 try { 442 $this->command("MAIL FROM: " . $message->getReturnPath(true), 250); 443 $this->command("RCPT TO: " . $address->build(true), 250); 444 $this->command("DATA", 354); 445 $data = $message->build(); 446 while (false !== $bytes = $data->read()) 447 $this->command($bytes, -1); 448 if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("<MESSAGE DATA>", Swift_Log::COMMAND); 449 $this->command("\r\n.", 250); 450 $sent++; 451 } catch (Swift_BadResponseException $e) { 452 $failed++; 453 $send_event->addFailedRecipient($address->getAddress()); 454 if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress()); 455 $this->reset(); 456 } 457 } 458 459 $total = count($to) + count($cc) + count($list->getBcc()); 460 461 $send_event->setNumSent($sent); 462 $this->notifyListeners($send_event, "SendListener"); 463 464 if (!$has_return_path) $message->setReturnPath(""); 465 if (!$has_from) $message->setFrom(""); 466 if (!$has_to) $message->setTo(""); 467 if (!$has_reply_to) $message->setReplyTo(null); 468 if (!$has_cc) $message->setCc(null); 469 if (!$has_bcc) $message->setBcc(null); 470 if (!$has_message_id) $message->setId(null); 471 472 if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("Message sent to " . $sent . "/" . $total . " recipients", Swift_Log::NORMAL); 473 474 return $sent; 475 } 476 /** 477 * Send a message to a batch of recipients. 478 * Unlike send() this method ignores Cc and Bcc recipients and does not reveal every recipients' address in the headers 479 * @param Swift_Message The message to send (leave out the recipient headers unless you are deliberately overriding them) 480 * @param Swift_RecipientList The addresses to send to 481 * @param Swift_Address The address the mail is from (sender) 482 * @return int The number of successful recipients 483 */ 484 public function batchSend(Swift_Message $message, Swift_RecipientList $to, $from) 485 { 486 $batch = new Swift_BatchMailer($this); 487 return $batch->send($message, $to, $from); 488 } 489} 490