Added feature #15998: Create a new API to send mails based on SwiftMailer to replace...
[Packages/TYPO3.CMS.git] / typo3 / contrib / swiftmailer / classes / Swift / Transport / AbstractSmtpTransport.php
1 <?php
2
3 /*
4 * This file is part of SwiftMailer.
5 * (c) 2004-2009 Chris Corbyn
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11 //@require 'Swift/Transport.php';
12 //@require 'Swift/Transport/IoBuffer.php';
13 //@require 'Swift/Transport/CommandSentException.php';
14 //@require 'Swift/TransportException.php';
15 //@require 'Swift/Mime/Message.php';
16 //@require 'Swift/Events/EventDispatcher.php';
17 //@require 'Swift/Events/EventListener.php';
18
19 /**
20 * Sends Messages over SMTP.
21 *
22 * @package Swift
23 * @subpackage Transport
24 * @author Chris Corbyn
25 */
26 abstract class Swift_Transport_AbstractSmtpTransport
27 implements Swift_Transport
28 {
29
30 /** Input-Output buffer for sending/receiving SMTP commands and responses */
31 protected $_buffer;
32
33 /** Connection status */
34 protected $_started = false;
35
36 /** The domain name to use in HELO command */
37 protected $_domain = '[127.0.0.1]';
38
39 /** The event dispatching layer */
40 protected $_eventDispatcher;
41
42 /** Return an array of params for the Buffer */
43 abstract protected function _getBufferParams();
44
45 /**
46 * Creates a new EsmtpTransport using the given I/O buffer.
47 *
48 * @param Swift_Transport_IoBuffer $buf
49 * @param Swift_Events_EventDispatcher $dispatcher
50 */
51 public function __construct(Swift_Transport_IoBuffer $buf,
52 Swift_Events_EventDispatcher $dispatcher)
53 {
54 $this->_eventDispatcher = $dispatcher;
55 $this->_buffer = $buf;
56 $this->_lookupHostname();
57 }
58
59 /**
60 * Set the name of the local domain which Swift will identify itself as.
61 * This should be a fully-qualified domain name and should be truly the domain
62 * you're using. If your server doesn't have a domain name, use the IP in square
63 * brackets (i.e. [127.0.0.1]).
64 *
65 * @param string $domain
66 */
67 public function setLocalDomain($domain)
68 {
69 $this->_domain = $domain;
70 return $this;
71 }
72
73 /**
74 * Get the name of the domain Swift will identify as.
75 *
76 * @return string
77 */
78 public function getLocalDomain()
79 {
80 return $this->_domain;
81 }
82
83 /**
84 * Start the SMTP connection.
85 */
86 public function start()
87 {
88 if (!$this->_started)
89 {
90 if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this))
91 {
92 $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted');
93 if ($evt->bubbleCancelled())
94 {
95 return;
96 }
97 }
98
99 try
100 {
101 $this->_buffer->initialize($this->_getBufferParams());
102 }
103 catch (Swift_TransportException $e)
104 {
105 $this->_throwException($e);
106 }
107 $this->_readGreeting();
108 $this->_doHeloCommand();
109
110 if ($evt)
111 {
112 $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted');
113 }
114
115 $this->_started = true;
116 }
117 }
118
119 /**
120 * Test if an SMTP connection has been established.
121 *
122 * @return boolean
123 */
124 public function isStarted()
125 {
126 return $this->_started;
127 }
128
129 /**
130 * Send the given Message.
131 *
132 * Recipient/sender data will be retreived from the Message API.
133 * The return value is the number of recipients who were accepted for delivery.
134 *
135 * @param Swift_Mime_Message $message
136 * @param string[] &$failedRecipients to collect failures by-reference
137 * @return int
138 */
139 public function send(Swift_Mime_Message $message, &$failedRecipients = null)
140 {
141 $sent = 0;
142 $failedRecipients = (array) $failedRecipients;
143
144 if (!$reversePath = $this->_getReversePath($message))
145 {
146 throw new Swift_TransportException(
147 'Cannot send message without a sender address'
148 );
149 }
150
151 if ($evt = $this->_eventDispatcher->createSendEvent($this, $message))
152 {
153 $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
154 if ($evt->bubbleCancelled())
155 {
156 return 0;
157 }
158 }
159
160 $to = (array) $message->getTo();
161 $cc = (array) $message->getCc();
162 $bcc = (array) $message->getBcc();
163
164 $message->setBcc(array());
165
166 try
167 {
168 $sent += $this->_sendTo($message, $reversePath, $to, $failedRecipients);
169 $sent += $this->_sendCc($message, $reversePath, $cc, $failedRecipients);
170 $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients);
171 }
172 catch (Exception $e)
173 {
174 $message->setBcc($bcc);
175 throw $e;
176 }
177
178 $message->setBcc($bcc);
179
180 if ($evt)
181 {
182 if ($sent == count($to) + count($cc) + count($bcc))
183 {
184 $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
185 }
186 elseif ($sent > 0)
187 {
188 $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
189 }
190 else
191 {
192 $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
193 }
194 $evt->setFailedRecipients($failedRecipients);
195 $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
196 }
197
198 $message->generateId(); //Make sure a new Message ID is used
199
200 return $sent;
201 }
202
203 /**
204 * Stop the SMTP connection.
205 */
206 public function stop()
207 {
208 if ($this->_started)
209 {
210 if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this))
211 {
212 $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped');
213 if ($evt->bubbleCancelled())
214 {
215 return;
216 }
217 }
218
219 try
220 {
221 $this->executeCommand("QUIT\r\n", array(221));
222 }
223 catch (Swift_TransportException $e) {}
224
225 try
226 {
227 $this->_buffer->terminate();
228
229 if ($evt)
230 {
231 $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped');
232 }
233 }
234 catch (Swift_TransportException $e)
235 {
236 $this->_throwException($e);
237 }
238 }
239 $this->_started = false;
240 }
241
242 /**
243 * Register a plugin.
244 *
245 * @param Swift_Events_EventListener $plugin
246 */
247 public function registerPlugin(Swift_Events_EventListener $plugin)
248 {
249 $this->_eventDispatcher->bindEventListener($plugin);
250 }
251
252 /**
253 * Reset the current mail transaction.
254 */
255 public function reset()
256 {
257 $this->executeCommand("RSET\r\n", array(250));
258 }
259
260 /**
261 * Get the IoBuffer where read/writes are occurring.
262 *
263 * @return Swift_Transport_IoBuffer
264 */
265 public function getBuffer()
266 {
267 return $this->_buffer;
268 }
269
270 /**
271 * Run a command against the buffer, expecting the given response codes.
272 *
273 * If no response codes are given, the response will not be validated.
274 * If codes are given, an exception will be thrown on an invalid response.
275 *
276 * @param string $command
277 * @param int[] $codes
278 * @param string[] &$failures
279 * @return string
280 */
281 public function executeCommand($command, $codes = array(), &$failures = null)
282 {
283 $failures = (array) $failures;
284 $seq = $this->_buffer->write($command);
285 $response = $this->_getFullResponse($seq);
286 if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes))
287 {
288 $this->_eventDispatcher->dispatchEvent($evt, 'commandSent');
289 }
290 $this->_assertResponseCode($response, $codes);
291 return $response;
292 }
293
294 // -- Protected methods
295
296 /** Read the opening SMTP greeting */
297 protected function _readGreeting()
298 {
299 $this->_assertResponseCode($this->_getFullResponse(0), array(220));
300 }
301
302 /** Send the HELO welcome */
303 protected function _doHeloCommand()
304 {
305 $this->executeCommand(
306 sprintf("HELO %s\r\n", $this->_domain), array(250)
307 );
308 }
309
310 /** Send the MAIL FROM command */
311 protected function _doMailFromCommand($address)
312 {
313 $this->executeCommand(
314 sprintf("MAIL FROM: <%s>\r\n", $address), array(250)
315 );
316 }
317
318 /** Send the RCPT TO command */
319 protected function _doRcptToCommand($address)
320 {
321 $this->executeCommand(
322 sprintf("RCPT TO: <%s>\r\n", $address), array(250, 251, 252)
323 );
324 }
325
326 /** Send the DATA command */
327 protected function _doDataCommand()
328 {
329 $this->executeCommand("DATA\r\n", array(354));
330 }
331
332 /** Stream the contents of the message over the buffer */
333 protected function _streamMessage(Swift_Mime_Message $message)
334 {
335 $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n.."));
336 try
337 {
338 $message->toByteStream($this->_buffer);
339 $this->_buffer->flushBuffers();
340 }
341 catch (Swift_TransportException $e)
342 {
343 $this->_throwException($e);
344 }
345 $this->_buffer->setWriteTranslations(array());
346 $this->executeCommand("\r\n.\r\n", array(250));
347 }
348
349 /** Determine the best-use reverse path for this message */
350 protected function _getReversePath(Swift_Mime_Message $message)
351 {
352 $return = $message->getReturnPath();
353 $sender = $message->getSender();
354 $from = $message->getFrom();
355 $path = null;
356 if (!empty($return))
357 {
358 $path = $return;
359 }
360 elseif (!empty($sender))
361 {
362 // Don't use array_keys
363 reset($sender); // Reset Pointer to first pos
364 $path = key($sender); // Get key
365 }
366 elseif (!empty($from))
367 {
368 reset($from); // Reset Pointer to first pos
369 $path = key($from); // Get key
370 }
371 return $path;
372 }
373
374 /** Throw a TransportException, first sending it to any listeners */
375 protected function _throwException(Swift_TransportException $e)
376 {
377 if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e))
378 {
379 $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
380 if (!$evt->bubbleCancelled())
381 {
382 throw $e;
383 }
384 }
385 else
386 {
387 throw $e;
388 }
389 }
390
391 /** Throws an Exception if a response code is incorrect */
392 protected function _assertResponseCode($response, $wanted)
393 {
394 list($code, $separator, $text) = sscanf($response, '%3d%[ -]%s');
395 $valid = (empty($wanted) || in_array($code, $wanted));
396
397 if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response,
398 $valid))
399 {
400 $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived');
401 }
402
403 if (!$valid)
404 {
405 $this->_throwException(
406 new Swift_TransportException(
407 'Expected response code ' . implode('/', $wanted) . ' but got code ' .
408 '"' . $code . '", with message "' . $response . '"'
409 )
410 );
411 }
412 }
413
414 /** Get an entire multi-line response using its sequence number */
415 protected function _getFullResponse($seq)
416 {
417 $response = '';
418 try
419 {
420 do
421 {
422 $line = $this->_buffer->readLine($seq);
423 $response .= $line;
424 }
425 while (null !== $line && false !== $line && ' ' != $line{3});
426 }
427 catch (Swift_TransportException $e)
428 {
429 $this->_throwException($e);
430 }
431 return $response;
432 }
433
434 // -- Private methods
435
436 /** Send an email to the given recipients from the given reverse path */
437 private function _doMailTransaction($message, $reversePath,
438 array $recipients, array &$failedRecipients)
439 {
440 $sent = 0;
441 $this->_doMailFromCommand($reversePath);
442 foreach ($recipients as $forwardPath)
443 {
444 try
445 {
446 $this->_doRcptToCommand($forwardPath);
447 $sent++;
448 }
449 catch (Swift_TransportException $e)
450 {
451 $failedRecipients[] = $forwardPath;
452 }
453 }
454
455 if ($sent != 0)
456 {
457 $this->_doDataCommand();
458 $this->_streamMessage($message);
459 }
460 else
461 {
462 $this->reset();
463 }
464
465 return $sent;
466 }
467
468 /** Send a message to the given To: recipients */
469 private function _sendTo(Swift_Mime_Message $message, $reversePath,
470 array $to, array &$failedRecipients)
471 {
472 if (empty($to))
473 {
474 return 0;
475 }
476 return $this->_doMailTransaction($message, $reversePath, array_keys($to),
477 $failedRecipients);
478 }
479
480 /** Send a message to the given Cc: recipients */
481 private function _sendCc(Swift_Mime_Message $message, $reversePath,
482 array $cc, array &$failedRecipients)
483 {
484 if (empty($cc))
485 {
486 return 0;
487 }
488 return $this->_doMailTransaction($message, $reversePath, array_keys($cc),
489 $failedRecipients);
490 }
491
492 /** Send a message to all Bcc: recipients */
493 private function _sendBcc(Swift_Mime_Message $message, $reversePath,
494 array $bcc, array &$failedRecipients)
495 {
496 $sent = 0;
497 foreach ($bcc as $forwardPath => $name)
498 {
499 $message->setBcc(array($forwardPath => $name));
500 $sent += $this->_doMailTransaction(
501 $message, $reversePath, array($forwardPath), $failedRecipients
502 );
503 }
504 return $sent;
505 }
506
507 /** Try to determine the hostname of the server this is run on */
508 private function _lookupHostname()
509 {
510 if (!empty($_SERVER['SERVER_NAME'])
511 && $this->_isFqdn($_SERVER['SERVER_NAME']))
512 {
513 $this->_domain = $_SERVER['SERVER_NAME'];
514 }
515 elseif (!empty($_SERVER['SERVER_ADDR']))
516 {
517 $this->_domain = sprintf('[%s]', $_SERVER['SERVER_ADDR']);
518 }
519 }
520
521 /** Determine is the $hostname is a fully-qualified name */
522 private function _isFqdn($hostname)
523 {
524 //We could do a really thorough check, but there's really no point
525 if (false !== $dotPos = strpos($hostname, '.'))
526 {
527 return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1);
528 }
529 else
530 {
531 return false;
532 }
533 }
534
535 /**
536 * Destructor.
537 */
538 public function __destruct()
539 {
540 $this->stop();
541 }
542
543 }