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