Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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  }