1<?php 2 3/** 4 * Handles batch mailing with Swift Mailer with fail-safe support. 5 * Restarts the connection if it dies and then continues where it left off. 6 * Please read the LICENSE file 7 * @copyright Chris Corbyn <chris@w3style.co.uk> 8 * @author Chris Corbyn <chris@w3style.co.uk> 9 * @package Swift 10 * @license GNU Lesser General Public License 11 */ 12class Swift_BatchMailer 13{ 14 /** 15 * The current instance of Swift. 16 * @var Swift 17 */ 18 protected $swift; 19 /** 20 * The maximum number of times a single recipient can be attempted before giving up. 21 * @var int 22 */ 23 protected $maxTries = 2; 24 /** 25 * The number of seconds to sleep for if an error occurs. 26 * @var int 27 */ 28 protected $sleepTime = 0; 29 /** 30 * Failed recipients (undeliverable) 31 * @var array 32 */ 33 protected $failed = array(); 34 /** 35 * The maximum number of successive failures before giving up. 36 * @var int 37 */ 38 protected $maxFails = 0; 39 /** 40 * A temporary copy of some message headers. 41 * @var array 42 */ 43 protected $headers = array(); 44 45 /** 46 * Constructor. 47 * @param Swift The current instance of Swift 48 */ 49 public function __construct(Swift $swift) 50 { 51 $this->setSwift($swift); 52 } 53 /** 54 * Set the current Swift instance. 55 * @param Swift The instance 56 */ 57 public function setSwift(Swift $swift) 58 { 59 $this->swift = $swift; 60 } 61 /** 62 * Get the Swift instance which is running. 63 * @return Swift 64 */ 65 public function getSwift() 66 { 67 return $this->swift; 68 } 69 /** 70 * Set the maximum number of times a single address is allowed to be retried. 71 * @param int The maximum number of tries. 72 */ 73 public function setMaxTries($max) 74 { 75 $this->maxTries = abs($max); 76 } 77 /** 78 * Get the number of times a single address will be attempted in a batch. 79 * @return int 80 */ 81 public function getMaxTries() 82 { 83 return $this->maxTries; 84 } 85 /** 86 * Set the amount of time to sleep for if an error occurs. 87 * @param int Number of seconds 88 */ 89 public function setSleepTime($secs) 90 { 91 $this->sleepTime = abs($secs); 92 } 93 /** 94 * Get the amount of time to sleep for on errors. 95 * @return int 96 */ 97 public function getSleepTime() 98 { 99 return $this->sleepTime; 100 } 101 /** 102 * Log a failed recipient. 103 * @param string The email address. 104 */ 105 public function addFailedRecipient($address) 106 { 107 $this->failed[] = $address; 108 $this->failed = array_unique($this->failed); 109 } 110 /** 111 * Get all recipients which failed in this batch. 112 * @return array 113 */ 114 public function getFailedRecipients() 115 { 116 return $this->failed; 117 } 118 /** 119 * Clear out the list of failed recipients. 120 */ 121 public function flushFailedRecipients() 122 { 123 $this->failed = null; 124 $this->failed = array(); 125 } 126 /** 127 * Set the maximum number of times an error can be thrown in succession and still be hidden. 128 * @param int 129 */ 130 public function setMaxSuccessiveFailures($fails) 131 { 132 $this->maxFails = abs($fails); 133 } 134 /** 135 * Get the maximum number of times an error can be thrown and still be hidden. 136 * @return int 137 */ 138 public function getMaxSuccessiveFailures() 139 { 140 return $this->maxFails; 141 } 142 /** 143 * Restarts Swift forcibly. 144 */ 145 protected function forceRestartSwift() 146 { 147 //Pre-empting problems trying to issue "QUIT" to a dead connection 148 $this->swift->connection->stop(); 149 $this->swift->connection->start(); 150 $this->swift->disconnect(); 151 //Restart swift 152 $this->swift->connect(); 153 } 154 /** 155 * Takes a temporary copy of original message headers in case an error occurs and they need restoring. 156 * @param Swift_Message The message object 157 */ 158 protected function copyMessageHeaders(&$message) 159 { 160 $this->headers["To"] = $message->headers->has("To") ? 161 $message->headers->get("To") : null; 162 $this->headers["Reply-To"] = $message->headers->has("Reply-To") ? 163 $message->headers->get("Reply-To") : null; 164 $this->headers["Return-Path"] = $message->headers->has("Return-Path") ? 165 $message->headers->get("Return-Path") : null; 166 $this->headers["From"] = $message->headers->has("From") ? 167 $message->headers->get("From") : null; 168 } 169 /** 170 * Restore message headers to original values in the event of a failure. 171 * @param Swift_Message The message 172 */ 173 protected function restoreMessageHeaders(&$message) 174 { 175 foreach ($this->headers as $name => $value) 176 { 177 $message->headers->set($name, $value); 178 } 179 } 180 /** 181 * Run a batch send in a fail-safe manner. 182 * This operates as Swift::batchSend() except it deals with errors itself. 183 * @param Swift_Message To send 184 * @param Swift_RecipientList Recipients (To: only) 185 * @param Swift_Address The sender's address 186 * @return int The number sent to 187 */ 188 public function send(Swift_Message $message, Swift_RecipientList $recipients, $sender) 189 { 190 $sent = 0; 191 $successive_fails = 0; 192 193 $it = $recipients->getIterator("to"); 194 while ($it->hasNext()) 195 { 196 $it->next(); 197 $recipient = $it->getValue(); 198 $tried = 0; 199 $loop = true; 200 while ($loop && $tried < $this->getMaxTries()) 201 { 202 try { 203 $tried++; 204 $loop = false; 205 $this->copyMessageHeaders($message); 206 $sent += ($n = $this->swift->send($message, $recipient, $sender)); 207 if (!$n) $this->addFailedRecipient($recipient->getAddress()); 208 $successive_fails = 0; 209 } catch (Exception $e) { 210 $successive_fails++; 211 $this->restoreMessageHeaders($message); 212 if (($max = $this->getMaxSuccessiveFailures()) 213 && $successive_fails > $max) 214 { 215 throw new Exception( 216 "Too many successive failures. BatchMailer is configured to allow no more than " . $max . 217 " successive failures."); 218 } 219 //If an exception was thrown, give it one more go 220 if ($t = $this->getSleepTime()) sleep($t); 221 $this->forceRestartSwift(); 222 $loop = true; 223 } 224 } 225 } 226 227 return $sent; 228 } 229} 230