See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 /** 3 * Copyright 2012-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file LICENSE for license information (BSD). If you 6 * did not receive this file, see http://www.horde.org/licenses/bsd. 7 * 8 * @category Horde 9 * @copyright 2012-2017 Horde LLC 10 * @license http://www.horde.org/licenses/bsd New BSD License 11 * @package Mail 12 */ 13 14 /** 15 * Container object for a collection of RFC 822 elements. 16 * 17 * @author Michael Slusarz <slusarz@horde.org> 18 * @category Horde 19 * @copyright 2012-2017 Horde LLC 20 * @license http://www.horde.org/licenses/bsd New BSD License 21 * @package Mail 22 * 23 * @property-read array $addresses The list of all addresses (address 24 * w/personal parts). 25 * @property-read array $bare_addresses The list of all addresses (mail@host). 26 * @property-read array $bare_addresses_idn The list of all addresses 27 * (mail@host; IDN encoded). 28 * (@since 2.1.0) 29 * @property-read array $base_addresses The list of ONLY base addresses 30 * (Address objects). 31 * @property-read array $raw_addresses The list of all addresses (Address 32 * objects). 33 */ 34 class Horde_Mail_Rfc822_List 35 extends Horde_Mail_Rfc822_Object 36 implements ArrayAccess, Countable, SeekableIterator, Serializable 37 { 38 /** Filter masks. */ 39 const HIDE_GROUPS = 1; 40 const BASE_ELEMENTS = 2; 41 42 /** 43 * List data. 44 * 45 * @var array 46 */ 47 protected $_data = array(); 48 49 /** 50 * Current Iterator filter. 51 * 52 * @var array 53 */ 54 protected $_filter = array(); 55 56 /** 57 * Current Iterator pointer. 58 * 59 * @var array 60 */ 61 protected $_ptr; 62 63 /** 64 * Constructor. 65 * 66 * @param mixed $obs Address data to store in this object. 67 */ 68 public function __construct($obs = null) 69 { 70 if (!is_null($obs)) { 71 $this->add($obs); 72 } 73 } 74 75 /** 76 */ 77 public function __get($name) 78 { 79 switch ($name) { 80 case 'addresses': 81 case 'bare_addresses': 82 case 'bare_addresses_idn': 83 case 'base_addresses': 84 case 'raw_addresses': 85 $old = $this->_filter; 86 $mask = ($name == 'base_addresses') 87 ? self::BASE_ELEMENTS 88 : self::HIDE_GROUPS; 89 $this->setIteratorFilter($mask, empty($old['filter']) ? null : $old['filter']); 90 91 $out = array(); 92 foreach ($this as $val) { 93 switch ($name) { 94 case 'addresses': 95 $out[] = strval($val); 96 break; 97 98 case 'bare_addresses': 99 $out[] = $val->bare_address; 100 break; 101 102 case 'bare_addresses_idn': 103 $out[] = $val->bare_address_idn; 104 break; 105 106 case 'base_addresses': 107 case 'raw_addresses': 108 $out[] = clone $val; 109 break; 110 } 111 } 112 113 $this->_filter = $old; 114 return $out; 115 } 116 } 117 118 /** 119 * Add objects to the container. 120 * 121 * @param mixed $obs Address data to store in this object. 122 */ 123 public function add($obs) 124 { 125 foreach ($this->_normalize($obs) as $val) { 126 $this->_data[] = $val; 127 } 128 } 129 130 /** 131 * Remove addresses from the container. This method ignores Group objects. 132 * 133 * @param mixed $obs Addresses to remove. 134 */ 135 public function remove($obs) 136 { 137 $old = $this->_filter; 138 $this->setIteratorFilter(self::HIDE_GROUPS | self::BASE_ELEMENTS); 139 140 foreach ($this->_normalize($obs) as $val) { 141 $remove = array(); 142 143 foreach ($this as $key => $val2) { 144 if ($val2->match($val)) { 145 $remove[] = $key; 146 } 147 } 148 149 foreach (array_reverse($remove) as $key) { 150 unset($this[$key]); 151 } 152 } 153 154 $this->_filter = $old; 155 } 156 157 /** 158 * Removes duplicate addresses from list. This method ignores Group 159 * objects. 160 */ 161 public function unique() 162 { 163 $exist = $remove = array(); 164 165 $old = $this->_filter; 166 $this->setIteratorFilter(self::HIDE_GROUPS | self::BASE_ELEMENTS); 167 168 // For duplicates, we use the first address that contains personal 169 // information. 170 foreach ($this as $key => $val) { 171 $bare = $val->bare_address; 172 if (isset($exist[$bare])) { 173 if (($exist[$bare] == -1) || is_null($val->personal)) { 174 $remove[] = $key; 175 } else { 176 $remove[] = $exist[$bare]; 177 $exist[$bare] = -1; 178 } 179 } else { 180 $exist[$bare] = is_null($val->personal) 181 ? $key 182 : -1; 183 } 184 } 185 186 foreach (array_reverse($remove) as $key) { 187 unset($this[$key]); 188 } 189 190 $this->_filter = $old; 191 } 192 193 /** 194 * Group count. 195 * 196 * @return integer The number of groups in the list. 197 */ 198 public function groupCount() 199 { 200 $ret = 0; 201 202 foreach ($this->_data as $val) { 203 if ($val instanceof Horde_Mail_Rfc822_Group) { 204 ++$ret; 205 } 206 } 207 208 return $ret; 209 } 210 211 /** 212 * Set the Iterator filter. 213 * 214 * @param integer $mask Filter masks. 215 * @param mixed $filter An e-mail, or as list of e-mails, to filter by. 216 */ 217 public function setIteratorFilter($mask = 0, $filter = null) 218 { 219 $this->_filter = array(); 220 221 if ($mask) { 222 $this->_filter['mask'] = $mask; 223 } 224 225 if (!is_null($filter)) { 226 $rfc822 = new Horde_Mail_Rfc822(); 227 $this->_filter['filter'] = $rfc822->parseAddressList($filter); 228 } 229 } 230 231 /** 232 */ 233 protected function _writeAddress($opts) 234 { 235 $out = array(); 236 237 foreach ($this->_data as $val) { 238 $out[] = $val->writeAddress($opts); 239 } 240 241 return implode(', ', $out); 242 } 243 244 /** 245 */ 246 public function match($ob) 247 { 248 if (!($ob instanceof Horde_Mail_Rfc822_List)) { 249 $ob = new Horde_Mail_Rfc822_List($ob); 250 } 251 252 $a = $this->bare_addresses; 253 sort($a); 254 $b = $ob->bare_addresses; 255 sort($b); 256 257 return ($a == $b); 258 } 259 260 /** 261 * Does this list contain the given e-mail address? 262 * 263 * @param mixed $address An e-mail address. 264 * 265 * @return boolean True if the e-mail address is contained in the list. 266 */ 267 public function contains($address) 268 { 269 $ob = new Horde_Mail_Rfc822_Address($address); 270 271 foreach ($this->raw_addresses as $val) { 272 if ($val->match($ob)) { 273 return true; 274 } 275 } 276 277 return false; 278 } 279 280 /** 281 * Convenience method to return the first element in a list. 282 * 283 * Useful since it allows chaining; older PHP versions did not allow array 284 * access dereferencing from the results of a function call. 285 * 286 * @since 2.5.0 287 * 288 * @return Horde_Mail_Rfc822_Object Rfc822 object, or null if no object. 289 */ 290 public function first() 291 { 292 return $this[0]; 293 } 294 295 /** 296 * Normalize objects to add to list. 297 * 298 * @param mixed $obs Address data to store in this object. 299 * 300 * @return array Entries to add. 301 */ 302 protected function _normalize($obs) 303 { 304 $add = array(); 305 306 if (!($obs instanceof Horde_Mail_Rfc822_List) && 307 !is_array($obs)) { 308 $obs = array($obs); 309 } 310 311 foreach ($obs as $val) { 312 if (is_string($val)) { 313 $rfc822 = new Horde_Mail_Rfc822(); 314 $val = $rfc822->parseAddressList($val); 315 } 316 317 if ($val instanceof Horde_Mail_Rfc822_List) { 318 $val->setIteratorFilter(self::BASE_ELEMENTS); 319 foreach ($val as $val2) { 320 $add[] = $val2; 321 } 322 } elseif ($val instanceof Horde_Mail_Rfc822_Object) { 323 $add[] = $val; 324 } 325 } 326 327 return $add; 328 } 329 330 /* ArrayAccess methods. */ 331 332 /** 333 */ 334 #[\ReturnTypeWillChange] 335 public function offsetExists($offset) 336 { 337 return !is_null($this[$offset]); 338 } 339 340 /** 341 */ 342 #[\ReturnTypeWillChange] 343 public function offsetGet($offset) 344 { 345 try { 346 $this->seek($offset); 347 return $this->current(); 348 } catch (OutOfBoundsException $e) { 349 return null; 350 } 351 } 352 353 /** 354 */ 355 #[\ReturnTypeWillChange] 356 public function offsetSet($offset, $value) 357 { 358 if ($ob = $this[$offset]) { 359 if (is_null($this->_ptr['subidx'])) { 360 $tmp = $this->_normalize($value); 361 if (isset($tmp[0])) { 362 $this->_data[$this->_ptr['idx']] = $tmp[0]; 363 } 364 } else { 365 $ob[$offset] = $value; 366 } 367 $this->_ptr = null; 368 } 369 } 370 371 /** 372 */ 373 #[\ReturnTypeWillChange] 374 public function offsetUnset($offset) 375 { 376 if ($ob = $this[$offset]) { 377 if (is_null($this->_ptr['subidx'])) { 378 unset($this->_data[$this->_ptr['idx']]); 379 $this->_data = array_values($this->_data); 380 } else { 381 unset($ob->addresses[$this->_ptr['subidx']]); 382 } 383 $this->_ptr = null; 384 } 385 } 386 387 /* Countable methods. */ 388 389 /** 390 * Address count. 391 * 392 * @return integer The number of addresses. 393 */ 394 #[\ReturnTypeWillChange] 395 public function count() 396 { 397 return count($this->addresses); 398 } 399 400 /* Iterator methods. */ 401 402 #[\ReturnTypeWillChange] 403 public function current() 404 { 405 if (!$this->valid()) { 406 return null; 407 } 408 409 $ob = $this->_data[$this->_ptr['idx']]; 410 411 return is_null($this->_ptr['subidx']) 412 ? $ob 413 : $ob->addresses[$this->_ptr['subidx']]; 414 } 415 416 #[\ReturnTypeWillChange] 417 public function key() 418 { 419 return $this->_ptr['key']; 420 } 421 422 #[\ReturnTypeWillChange] 423 public function next() 424 { 425 if (is_null($this->_ptr['subidx'])) { 426 $curr = $this->current(); 427 if (($curr instanceof Horde_Mail_Rfc822_Group) && count($curr)) { 428 $this->_ptr['subidx'] = 0; 429 } else { 430 ++$this->_ptr['idx']; 431 } 432 $curr = $this->current(); 433 } elseif (!($curr = $this->_data[$this->_ptr['idx']]->addresses[++$this->_ptr['subidx']])) { 434 $this->_ptr['subidx'] = null; 435 ++$this->_ptr['idx']; 436 $curr = $this->current(); 437 } 438 439 if (!is_null($curr)) { 440 if (!empty($this->_filter) && $this->_iteratorFilter($curr)) { 441 $this->next(); 442 } else { 443 ++$this->_ptr['key']; 444 } 445 } 446 } 447 448 #[\ReturnTypeWillChange] 449 public function rewind() 450 { 451 $this->_ptr = array( 452 'idx' => 0, 453 'key' => 0, 454 'subidx' => null 455 ); 456 457 if ($this->valid() && 458 !empty($this->_filter) && 459 $this->_iteratorFilter($this->current())) { 460 $this->next(); 461 $this->_ptr['key'] = 0; 462 } 463 } 464 465 #[\ReturnTypeWillChange] 466 public function valid() 467 { 468 return (!empty($this->_ptr) && isset($this->_data[$this->_ptr['idx']])); 469 } 470 471 #[\ReturnTypeWillChange] 472 public function seek($position) 473 { 474 if (!$this->valid() || 475 ($position < $this->_ptr['key'])) { 476 $this->rewind(); 477 } 478 479 for ($i = $this->_ptr['key']; ; ++$i) { 480 if ($i == $position) { 481 return; 482 } 483 484 $this->next(); 485 if (!$this->valid()) { 486 throw new OutOfBoundsException('Position not found.'); 487 } 488 } 489 } 490 491 protected function _iteratorFilter($ob) 492 { 493 if (!empty($this->_filter['mask'])) { 494 if (($this->_filter['mask'] & self::HIDE_GROUPS) && 495 ($ob instanceof Horde_Mail_Rfc822_Group)) { 496 return true; 497 } 498 499 if (($this->_filter['mask'] & self::BASE_ELEMENTS) && 500 !is_null($this->_ptr['subidx'])) { 501 return true; 502 } 503 } 504 505 if (!empty($this->_filter['filter']) && 506 ($ob instanceof Horde_Mail_Rfc822_Address)) { 507 foreach ($this->_filter['filter'] as $val) { 508 if ($ob->match($val)) { 509 return true; 510 } 511 } 512 } 513 514 return false; 515 } 516 517 /* Serializable methods. */ 518 519 public function serialize() 520 { 521 return serialize($this->_data); 522 } 523 524 public function unserialize($data) 525 { 526 $this->_data = unserialize($data); 527 } 528 529 public function __serialize() { 530 return array( 531 'data' => $this->_data 532 ); 533 } 534 535 public function __unserialize(array $data) { 536 $this->_data = $data['data']; 537 } 538 539 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body