~lzap/cupooy/trunk

« back to all changes in this revision

Viewing changes to lib/vendor/swift/classes/Swift/Transport/AbstractSmtpTransport.php

  • Committer: Lukáš Zapletal
  • Date: 2009-11-16 15:18:26 UTC
  • Revision ID: lzap@shark-20091116151826-4287asrnx59j26g0
Mailing

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}