Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
}