1<?php 2 3/** 4 * Swift Mailer Message Decorating Plugin. 5 * Please read the LICENSE file 6 * @author Chris Corbyn <chris@w3style.co.uk> 7 * @package Swift_Plugin 8 * @subpackage Decorator 9 * @license GNU Lesser General Public License 10 */ 11 12require_once dirname(__FILE__) . "/../ClassLoader.php"; 13Swift_ClassLoader::load("Swift_Events_BeforeSendListener"); 14Swift_ClassLoader::load("Swift_Plugin_Decorator_Replacements"); 15 16/** 17 * Swift Decorator Plugin. 18 * Allows messages to be slightly different for each recipient. 19 * @package Swift_Plugin 20 * @subpackage Decorator 21 * @author Chris Corbyn <chris@w3style.co.uk> 22 */ 23class Swift_Plugin_Decorator implements Swift_Events_BeforeSendListener 24{ 25 /** 26 * The replacements object. 27 * @var Swift_Plugin_Decorator_Replacements 28 */ 29 protected $replacements; 30 /** 31 * Temporary storage so we can restore changes we make. 32 * @var array 33 */ 34 protected $store; 35 /** 36 * A list of allowed mime types to replace bodies for. 37 * @var array 38 */ 39 protected $permittedTypes = array("text/plain" => 1, "text/html" => 1); 40 /** 41 * True if values in the headers can be replaced 42 * @var boolean 43 */ 44 protected $permittedInHeaders = true; 45 46 /** 47 * Ctor. 48 * @param mixed Replacements as a 2-d array or Swift_Plugin_Decorator_Replacements instance. 49 */ 50 public function __construct($replacements=null) 51 { 52 $this->setReplacements($replacements); 53 } 54 /** 55 * Enable of disable the ability to replace values in the headers 56 * @param boolean 57 */ 58 public function setPermittedInHeaders($bool) 59 { 60 $this->permittedInHeaders = (bool) $bool; 61 } 62 /** 63 * Check if replacements in headers are allowed. 64 * @return boolean 65 */ 66 public function getPermittedInHeaders() 67 { 68 return $this->permittedInHeaders; 69 } 70 /** 71 * Add a mime type to the list of permitted type to replace values in the body. 72 * @param string The mime type (e.g. text/plain) 73 */ 74 public function addPermittedType($type) 75 { 76 $type = strtolower($type); 77 $this->permittedTypes[$type] = 1; 78 } 79 /** 80 * Remove the ability to replace values in the body of the given mime type 81 * @param string The mime type 82 */ 83 public function removePermittedType($type) 84 { 85 unset($this->permittedTypes[$type]); 86 } 87 /** 88 * Get the list of mime types for which the body can be changed. 89 * @return array 90 */ 91 public function getPermittedTypes() 92 { 93 return array_keys($this->permittedTypes); 94 } 95 /** 96 * Check if the body can be replaced in the given mime type. 97 * @param string The mime type 98 * @return boolean 99 */ 100 public function isPermittedType($type) 101 { 102 return array_key_exists(strtolower($type), $this->permittedTypes); 103 } 104 /** 105 * Called just before Swift sends a message. 106 * We perform operations on the message here. 107 * @param Swift_Events_SendEvent The event object for sending a message 108 */ 109 public function beforeSendPerformed(Swift_Events_SendEvent $e) 110 { 111 $message = $e->getMessage(); 112 $this->recursiveRestore($message, $this->store); //3.3.3 bugfix 113 114 $recipients = $e->getRecipients(); 115 $to = array_keys($recipients->getTo()); 116 if (count($to) > 0) $to = $to[0]; 117 else return; 118 119 $replacements = (array)$this->replacements->getReplacementsFor($to); 120 121 $this->store = array( 122 "headers" => array(), 123 "body" => false, 124 "children" => array() 125 ); 126 $this->recursiveReplace($message, $replacements, $this->store); 127 } 128 /** 129 * Replace strings in the message searching through all the allowed sub-parts. 130 * @param Swift_Message_Mime The message (or part) 131 * @param array The list of replacements 132 * @param array The array to cache original values into where needed 133 */ 134 protected function recursiveReplace(Swift_Message_Mime $mime, $replacements, &$store) 135 { 136 //Check headers 137 if ($this->getPermittedInHeaders()) 138 { 139 foreach ($mime->headers->getList() as $name => $value) 140 { 141 if (is_string($value) && ($replaced = $this->replace($replacements, $value)) != $value) 142 { 143 $mime->headers->set($name, $replaced); 144 $store["headers"][$name] = array(); 145 $store["headers"][$name]["value"] = $value; 146 $store["headers"][$name]["attributes"] = array(); 147 } 148 foreach ($mime->headers->listAttributes($name) as $att_name => $att_value) 149 { 150 if (is_string($att_value) 151 && ($att_replaced = $this->replace($replacements, $att_value)) != $att_value) 152 { 153 if (!isset($store["headers"][$name])) 154 { 155 $store["headers"][$name] = array("value" => false, "attributes" => array()); 156 } 157 $mime->headers->setAttribute($name, $att_name, $att_replaced); 158 $store["headers"][$name]["attributes"][$att_name] = $att_value; 159 } 160 } 161 } 162 } 163 //Check body 164 $body = $mime->getData(); 165 if ($this->isPermittedType($mime->getContentType()) 166 && is_string($body) && ($replaced = $this->replace($replacements, $body)) != $body) 167 { 168 $mime->setData($replaced); 169 $store["body"] = $body; 170 } 171 //Check sub-parts 172 foreach ($mime->listChildren() as $id) 173 { 174 $store["children"][$id] = array( 175 "headers" => array(), 176 "body" => false, 177 "children" => array() 178 ); 179 $child = $mime->getChild($id); 180 $this->recursiveReplace($child, $replacements, $store["children"][$id]); 181 } 182 } 183 /** 184 * Perform a str_replace() over the given value. 185 * @param array The list of replacements as (search => replacement) 186 * @param string The string to replace 187 * @return string 188 */ 189 protected function replace($replacements, $value) 190 { 191 return str_replace(array_keys($replacements), array_values($replacements), $value); 192 } 193 /** 194 * Put the original values back in the message after it was modified before sending. 195 * @param Swift_Message_Mime The message (or part) 196 * @param array The location of the stored values 197 */ 198 protected function recursiveRestore(Swift_Message_Mime $mime, &$store) 199 { 200 if (empty($store)) //3.3.3 bugfix 201 { 202 return; 203 } 204 205 //Restore headers 206 foreach ($store["headers"] as $name => $array) 207 { 208 if ($array["value"] !== false) $mime->headers->set($name, $array["value"]); 209 foreach ($array["attributes"] as $att_name => $att_value) 210 { 211 $mime->headers->setAttribute($name, $att_name, $att_value); 212 } 213 } 214 //Restore body 215 if ($store["body"] !== false) 216 { 217 $mime->setData($store["body"]); 218 } 219 //Restore children 220 foreach ($store["children"] as $id => $child_store) 221 { 222 $child = $mime->getChild($id); 223 $this->recursiveRestore($child, $child_store); 224 } 225 } 226 /** 227 * Set the replacements as a 2-d array or an instance of Swift_Plugin_Decorator_Replacements. 228 * @param mixed Array or Swift_Plugin_Decorator_Replacements 229 */ 230 public function setReplacements($replacements) 231 { 232 if ($replacements === null) 233 { 234 $r = array(); 235 $this->replacements = new Swift_Plugin_Decorator_Replacements($r); 236 } 237 elseif (is_array($replacements)) 238 { 239 $this->replacements = new Swift_Plugin_Decorator_Replacements($replacements); 240 } 241 elseif ($replacements instanceof Swift_Plugin_Decorator_Replacements) 242 { 243 $this->replacements = $replacements; 244 } 245 else 246 { 247 throw new Exception( 248 "Decorator replacements must be array or instance of Swift_Plugin_Decorator_Replacements."); 249 } 250 } 251 /** 252 * Get the replacements object. 253 * @return Swift_Plugin_Decorator_Replacements 254 */ 255 public function getReplacements() 256 { 257 return $this->replacements; 258 } 259} 260