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/** 12 * A collection of MIME headers. 13 * 14 * @author Chris Corbyn 15 */ 16class Swift_Mime_SimpleHeaderSet implements Swift_Mime_CharsetObserver 17{ 18 /** HeaderFactory */ 19 private $factory; 20 21 /** Collection of set Headers */ 22 private $headers = array(); 23 24 /** Field ordering details */ 25 private $order = array(); 26 27 /** List of fields which are required to be displayed */ 28 private $required = array(); 29 30 /** The charset used by Headers */ 31 private $charset; 32 33 /** 34 * Create a new SimpleHeaderSet with the given $factory. 35 * 36 * @param Swift_Mime_SimpleHeaderFactory $factory 37 * @param string $charset 38 */ 39 public function __construct(Swift_Mime_SimpleHeaderFactory $factory, $charset = null) 40 { 41 $this->factory = $factory; 42 if (isset($charset)) { 43 $this->setCharset($charset); 44 } 45 } 46 47 public function newInstance() 48 { 49 return new self($this->factory); 50 } 51 52 /** 53 * Set the charset used by these headers. 54 * 55 * @param string $charset 56 */ 57 public function setCharset($charset) 58 { 59 $this->charset = $charset; 60 $this->factory->charsetChanged($charset); 61 $this->notifyHeadersOfCharset($charset); 62 } 63 64 /** 65 * Add a new Mailbox Header with a list of $addresses. 66 * 67 * @param string $name 68 * @param array|string $addresses 69 */ 70 public function addMailboxHeader($name, $addresses = null) 71 { 72 $this->storeHeader($name, 73 $this->factory->createMailboxHeader($name, $addresses)); 74 } 75 76 /** 77 * Add a new Date header using $dateTime. 78 * 79 * @param string $name 80 * @param DateTimeInterface $dateTime 81 */ 82 public function addDateHeader($name, DateTimeInterface $dateTime = null) 83 { 84 $this->storeHeader($name, 85 $this->factory->createDateHeader($name, $dateTime)); 86 } 87 88 /** 89 * Add a new basic text header with $name and $value. 90 * 91 * @param string $name 92 * @param string $value 93 */ 94 public function addTextHeader($name, $value = null) 95 { 96 $this->storeHeader($name, 97 $this->factory->createTextHeader($name, $value)); 98 } 99 100 /** 101 * Add a new ParameterizedHeader with $name, $value and $params. 102 * 103 * @param string $name 104 * @param string $value 105 * @param array $params 106 */ 107 public function addParameterizedHeader($name, $value = null, $params = array()) 108 { 109 $this->storeHeader($name, $this->factory->createParameterizedHeader($name, $value, $params)); 110 } 111 112 /** 113 * Add a new ID header for Message-ID or Content-ID. 114 * 115 * @param string $name 116 * @param string|array $ids 117 */ 118 public function addIdHeader($name, $ids = null) 119 { 120 $this->storeHeader($name, $this->factory->createIdHeader($name, $ids)); 121 } 122 123 /** 124 * Add a new Path header with an address (path) in it. 125 * 126 * @param string $name 127 * @param string $path 128 */ 129 public function addPathHeader($name, $path = null) 130 { 131 $this->storeHeader($name, $this->factory->createPathHeader($name, $path)); 132 } 133 134 /** 135 * Returns true if at least one header with the given $name exists. 136 * 137 * If multiple headers match, the actual one may be specified by $index. 138 * 139 * @param string $name 140 * @param int $index 141 * 142 * @return bool 143 */ 144 public function has($name, $index = 0) 145 { 146 $lowerName = strtolower($name); 147 148 if (!array_key_exists($lowerName, $this->headers)) { 149 return false; 150 } 151 152 if (func_num_args() < 2) { 153 // index was not specified, so we only need to check that there is at least one header value set 154 return (bool) count($this->headers[$lowerName]); 155 } 156 157 return array_key_exists($index, $this->headers[$lowerName]); 158 } 159 160 /** 161 * Set a header in the HeaderSet. 162 * 163 * The header may be a previously fetched header via {@link get()} or it may 164 * be one that has been created separately. 165 * 166 * If $index is specified, the header will be inserted into the set at this 167 * offset. 168 * 169 * @param Swift_Mime_Header $header 170 * @param int $index 171 */ 172 public function set(Swift_Mime_Header $header, $index = 0) 173 { 174 $this->storeHeader($header->getFieldName(), $header, $index); 175 } 176 177 /** 178 * Get the header with the given $name. 179 * 180 * If multiple headers match, the actual one may be specified by $index. 181 * Returns NULL if none present. 182 * 183 * @param string $name 184 * @param int $index 185 * 186 * @return Swift_Mime_Header 187 */ 188 public function get($name, $index = 0) 189 { 190 $name = strtolower($name); 191 192 if (func_num_args() < 2) { 193 if ($this->has($name)) { 194 $values = array_values($this->headers[$name]); 195 196 return array_shift($values); 197 } 198 } else { 199 if ($this->has($name, $index)) { 200 return $this->headers[$name][$index]; 201 } 202 } 203 } 204 205 /** 206 * Get all headers with the given $name. 207 * 208 * @param string $name 209 * 210 * @return array 211 */ 212 public function getAll($name = null) 213 { 214 if (!isset($name)) { 215 $headers = array(); 216 foreach ($this->headers as $collection) { 217 $headers = array_merge($headers, $collection); 218 } 219 220 return $headers; 221 } 222 223 $lowerName = strtolower($name); 224 if (!array_key_exists($lowerName, $this->headers)) { 225 return array(); 226 } 227 228 return $this->headers[$lowerName]; 229 } 230 231 /** 232 * Return the name of all Headers. 233 * 234 * @return array 235 */ 236 public function listAll() 237 { 238 $headers = $this->headers; 239 if ($this->canSort()) { 240 uksort($headers, array($this, 'sortHeaders')); 241 } 242 243 return array_keys($headers); 244 } 245 246 /** 247 * Remove the header with the given $name if it's set. 248 * 249 * If multiple headers match, the actual one may be specified by $index. 250 * 251 * @param string $name 252 * @param int $index 253 */ 254 public function remove($name, $index = 0) 255 { 256 $lowerName = strtolower($name); 257 unset($this->headers[$lowerName][$index]); 258 } 259 260 /** 261 * Remove all headers with the given $name. 262 * 263 * @param string $name 264 */ 265 public function removeAll($name) 266 { 267 $lowerName = strtolower($name); 268 unset($this->headers[$lowerName]); 269 } 270 271 /** 272 * Define a list of Header names as an array in the correct order. 273 * 274 * These Headers will be output in the given order where present. 275 * 276 * @param array $sequence 277 */ 278 public function defineOrdering(array $sequence) 279 { 280 $this->order = array_flip(array_map('strtolower', $sequence)); 281 } 282 283 /** 284 * Set a list of header names which must always be displayed when set. 285 * 286 * Usually headers without a field value won't be output unless set here. 287 * 288 * @param array $names 289 */ 290 public function setAlwaysDisplayed(array $names) 291 { 292 $this->required = array_flip(array_map('strtolower', $names)); 293 } 294 295 /** 296 * Notify this observer that the entity's charset has changed. 297 * 298 * @param string $charset 299 */ 300 public function charsetChanged($charset) 301 { 302 $this->setCharset($charset); 303 } 304 305 /** 306 * Returns a string with a representation of all headers. 307 * 308 * @return string 309 */ 310 public function toString() 311 { 312 $string = ''; 313 $headers = $this->headers; 314 if ($this->canSort()) { 315 uksort($headers, array($this, 'sortHeaders')); 316 } 317 foreach ($headers as $collection) { 318 foreach ($collection as $header) { 319 if ($this->isDisplayed($header) || $header->getFieldBody() != '') { 320 $string .= $header->toString(); 321 } 322 } 323 } 324 325 return $string; 326 } 327 328 /** 329 * Returns a string representation of this object. 330 * 331 * @return string 332 * 333 * @see toString() 334 */ 335 public function __toString() 336 { 337 return $this->toString(); 338 } 339 340 /** Save a Header to the internal collection */ 341 private function storeHeader($name, Swift_Mime_Header $header, $offset = null) 342 { 343 if (!isset($this->headers[strtolower($name)])) { 344 $this->headers[strtolower($name)] = array(); 345 } 346 if (!isset($offset)) { 347 $this->headers[strtolower($name)][] = $header; 348 } else { 349 $this->headers[strtolower($name)][$offset] = $header; 350 } 351 } 352 353 /** Test if the headers can be sorted */ 354 private function canSort() 355 { 356 return count($this->order) > 0; 357 } 358 359 /** uksort() algorithm for Header ordering */ 360 private function sortHeaders($a, $b) 361 { 362 $lowerA = strtolower($a); 363 $lowerB = strtolower($b); 364 $aPos = array_key_exists($lowerA, $this->order) ? $this->order[$lowerA] : -1; 365 $bPos = array_key_exists($lowerB, $this->order) ? $this->order[$lowerB] : -1; 366 367 if (-1 === $aPos && -1 === $bPos) { 368 // just be sure to be determinist here 369 return $a > $b ? -1 : 1; 370 } 371 372 if ($aPos == -1) { 373 return 1; 374 } elseif ($bPos == -1) { 375 return -1; 376 } 377 378 return $aPos < $bPos ? -1 : 1; 379 } 380 381 /** Test if the given Header is always displayed */ 382 private function isDisplayed(Swift_Mime_Header $header) 383 { 384 return array_key_exists(strtolower($header->getFieldName()), $this->required); 385 } 386 387 /** Notify all Headers of the new charset */ 388 private function notifyHeadersOfCharset($charset) 389 { 390 foreach ($this->headers as $headerGroup) { 391 foreach ($headerGroup as $header) { 392 $header->setCharset($charset); 393 } 394 } 395 } 396 397 /** 398 * Make a deep copy of object. 399 */ 400 public function __clone() 401 { 402 $this->factory = clone $this->factory; 403 foreach ($this->headers as $groupKey => $headerGroup) { 404 foreach ($headerGroup as $key => $header) { 405 $this->headers[$groupKey][$key] = clone $header; 406 } 407 } 408 } 409} 410