_eventDispatcher = $dispatcher; $this->_buffer = $buf; $this->_lookupHostname(); } /** * Set the name of the local domain which Swift will identify itself as. * * This should be a fully-qualified domain name and should be truly the domain * you're using. * * If your server doesn't have a domain name, use the IP in square * brackets (i.e. [127.0.0.1]). * * @param string $domain * * @return Swift_Transport_AbstractSmtpTransport */ public function setLocalDomain($domain) { $this->_domain = $domain; return $this; } /** * Get the name of the domain Swift will identify as. * * @return string */ public function getLocalDomain() { return $this->_domain; } /** * Sets the source IP. * * @param string $source */ public function setSourceIp($source) { $this->_sourceIp = $source; } /** * Returns the IP used to connect to the destination. * * @return string */ public function getSourceIp() { return $this->_sourceIp; } /** * Start the SMTP connection. */ public function start() { if (!$this->_started) { if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) { $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted'); if ($evt->bubbleCancelled()) { return; } } try { $this->_buffer->initialize($this->_getBufferParams()); } catch (Swift_TransportException $e) { $this->_throwException($e); } $this->_readGreeting(); $this->_doHeloCommand(); if ($evt) { $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted'); } $this->_started = true; } } /** * Test if an SMTP connection has been established. * * @return bool */ public function isStarted() { return $this->_started; } /** * Send the given Message. * * Recipient/sender data will be retrieved from the Message API. * The return value is the number of recipients who were accepted for delivery. * * @param Swift_Mime_Message $message * @param string[] $failedRecipients An array of failures by-reference * * @return int */ public function send(Swift_Mime_Message $message, &$failedRecipients = null) { $sent = 0; $failedRecipients = (array) $failedRecipients; if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); if ($evt->bubbleCancelled()) { return 0; } } if (!$reversePath = $this->_getReversePath($message)) { $this->_throwException(new Swift_TransportException( 'Cannot send message without a sender address' ) ); } $to = (array) $message->getTo(); $cc = (array) $message->getCc(); $tos = array_merge($to, $cc); $bcc = (array) $message->getBcc(); $message->setBcc(array()); try { $sent += $this->_sendTo($message, $reversePath, $tos, $failedRecipients); $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients); } catch (Exception $e) { $message->setBcc($bcc); throw $e; } $message->setBcc($bcc); if ($evt) { if ($sent == count($to) + count($cc) + count($bcc)) { $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); } elseif ($sent > 0) { $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE); } else { $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); } $evt->setFailedRecipients($failedRecipients); $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); } $message->generateId(); //Make sure a new Message ID is used return $sent; } /** * Stop the SMTP connection. */ public function stop() { if ($this->_started) { if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) { $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped'); if ($evt->bubbleCancelled()) { return; } } try { $this->executeCommand("QUIT\r\n", array(221)); } catch (Swift_TransportException $e) { } try { $this->_buffer->terminate(); if ($evt) { $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped'); } } catch (Swift_TransportException $e) { $this->_throwException($e); } } $this->_started = false; } /** * Register a plugin. * * @param Swift_Events_EventListener $plugin */ public function registerPlugin(Swift_Events_EventListener $plugin) { $this->_eventDispatcher->bindEventListener($plugin); } /** * Reset the current mail transaction. */ public function reset() { $this->executeCommand("RSET\r\n", array(250)); } /** * Get the IoBuffer where read/writes are occurring. * * @return Swift_Transport_IoBuffer */ public function getBuffer() { return $this->_buffer; } /** * Run a command against the buffer, expecting the given response codes. * * If no response codes are given, the response will not be validated. * If codes are given, an exception will be thrown on an invalid response. * * @param string $command * @param int[] $codes * @param string[] $failures An array of failures by-reference * * @return string */ public function executeCommand($command, $codes = array(), &$failures = null) { $failures = (array) $failures; $seq = $this->_buffer->write($command); $response = $this->_getFullResponse($seq); if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes)) { $this->_eventDispatcher->dispatchEvent($evt, 'commandSent'); } $this->_assertResponseCode($response, $codes); return $response; } /** Read the opening SMTP greeting */ protected function _readGreeting() { $this->_assertResponseCode($this->_getFullResponse(0), array(220)); } /** Send the HELO welcome */ protected function _doHeloCommand() { $this->executeCommand( sprintf("HELO %s\r\n", $this->_domain), array(250) ); } /** Send the MAIL FROM command */ protected function _doMailFromCommand($address) { $this->executeCommand( sprintf("MAIL FROM:<%s>\r\n", $address), array(250) ); } /** Send the RCPT TO command */ protected function _doRcptToCommand($address) { $this->executeCommand( sprintf("RCPT TO:<%s>\r\n", $address), array(250, 251, 252) ); } /** Send the DATA command */ protected function _doDataCommand() { $this->executeCommand("DATA\r\n", array(354)); } /** Stream the contents of the message over the buffer */ protected function _streamMessage(Swift_Mime_Message $message) { $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n..")); try { $message->toByteStream($this->_buffer); $this->_buffer->flushBuffers(); } catch (Swift_TransportException $e) { $this->_throwException($e); } $this->_buffer->setWriteTranslations(array()); $this->executeCommand("\r\n.\r\n", array(250)); } /** Determine the best-use reverse path for this message */ protected function _getReversePath(Swift_Mime_Message $message) { $return = $message->getReturnPath(); $sender = $message->getSender(); $from = $message->getFrom(); $path = null; if (!empty($return)) { $path = $return; } elseif (!empty($sender)) { // Don't use array_keys reset($sender); // Reset Pointer to first pos $path = key($sender); // Get key } elseif (!empty($from)) { reset($from); // Reset Pointer to first pos $path = key($from); // Get key } return $path; } /** Throw a TransportException, first sending it to any listeners */ protected function _throwException(Swift_TransportException $e) { if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) { $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown'); if (!$evt->bubbleCancelled()) { throw $e; } } else { throw $e; } } /** Throws an Exception if a response code is incorrect */ protected function _assertResponseCode($response, $wanted) { list($code) = sscanf($response, '%3d'); $valid = (empty($wanted) || in_array($code, $wanted)); if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response, $valid)) { $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived'); } if (!$valid) { $this->_throwException( new Swift_TransportException( 'Expected response code '.implode('/', $wanted).' but got code '. '"'.$code.'", with message "'.$response.'"', $code) ); } } /** Get an entire multi-line response using its sequence number */ protected function _getFullResponse($seq) { $response = ''; try { do { $line = $this->_buffer->readLine($seq); $response .= $line; } while (null !== $line && false !== $line && ' ' != $line{3}); } catch (Swift_TransportException $e) { $this->_throwException($e); } catch (Swift_IoException $e) { $this->_throwException( new Swift_TransportException( $e->getMessage()) ); } return $response; } /** Send an email to the given recipients from the given reverse path */ private function _doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients) { $sent = 0; $this->_doMailFromCommand($reversePath); foreach ($recipients as $forwardPath) { try { $this->_doRcptToCommand($forwardPath); ++$sent; } catch (Swift_TransportException $e) { $failedRecipients[] = $forwardPath; } } if ($sent != 0) { $this->_doDataCommand(); $this->_streamMessage($message); } else { $this->reset(); } return $sent; } /** Send a message to the given To: recipients */ private function _sendTo(Swift_Mime_Message $message, $reversePath, array $to, array &$failedRecipients) { if (empty($to)) { return 0; } return $this->_doMailTransaction($message, $reversePath, array_keys($to), $failedRecipients); } /** Send a message to all Bcc: recipients */ private function _sendBcc(Swift_Mime_Message $message, $reversePath, array $bcc, array &$failedRecipients) { $sent = 0; foreach ($bcc as $forwardPath => $name) { $message->setBcc(array($forwardPath => $name)); $sent += $this->_doMailTransaction( $message, $reversePath, array($forwardPath), $failedRecipients ); } return $sent; } /** Try to determine the hostname of the server this is run on */ private function _lookupHostname() { if (!empty($_SERVER['SERVER_NAME']) && $this->_isFqdn($_SERVER['SERVER_NAME'])) { $this->_domain = $_SERVER['SERVER_NAME']; } elseif (!empty($_SERVER['SERVER_ADDR'])) { // Set the address literal tag (See RFC 5321, section: 4.1.3) if (false === strpos($_SERVER['SERVER_ADDR'], ':')) { $prefix = ''; // IPv4 addresses are not tagged. } else { $prefix = 'IPv6:'; // Adding prefix in case of IPv6. } $this->_domain = sprintf('[%s%s]', $prefix, $_SERVER['SERVER_ADDR']); } } /** Determine is the $hostname is a fully-qualified name */ private function _isFqdn($hostname) { // We could do a really thorough check, but there's really no point if (false !== $dotPos = strpos($hostname, '.')) { return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1); } return false; } /** * Destructor. */ public function __destruct() { $this->stop(); } }