Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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      public function offsetExists($offset)
 335      {
 336          return !is_null($this[$offset]);
 337      }
 338  
 339      /**
 340       */
 341      public function offsetGet($offset)
 342      {
 343          try {
 344              $this->seek($offset);
 345              return $this->current();
 346          } catch (OutOfBoundsException $e) {
 347              return null;
 348          }
 349      }
 350  
 351      /**
 352       */
 353      public function offsetSet($offset, $value)
 354      {
 355          if ($ob = $this[$offset]) {
 356              if (is_null($this->_ptr['subidx'])) {
 357                  $tmp = $this->_normalize($value);
 358                  if (isset($tmp[0])) {
 359                      $this->_data[$this->_ptr['idx']] = $tmp[0];
 360                  }
 361              } else {
 362                  $ob[$offset] = $value;
 363              }
 364              $this->_ptr = null;
 365          }
 366      }
 367  
 368      /**
 369       */
 370      public function offsetUnset($offset)
 371      {
 372          if ($ob = $this[$offset]) {
 373              if (is_null($this->_ptr['subidx'])) {
 374                  unset($this->_data[$this->_ptr['idx']]);
 375                  $this->_data = array_values($this->_data);
 376              } else {
 377                  unset($ob->addresses[$this->_ptr['subidx']]);
 378              }
 379              $this->_ptr = null;
 380          }
 381      }
 382  
 383      /* Countable methods. */
 384  
 385      /**
 386       * Address count.
 387       *
 388       * @return integer  The number of addresses.
 389       */
 390      public function count()
 391      {
 392          return count($this->addresses);
 393      }
 394  
 395      /* Iterator methods. */
 396  
 397      public function current()
 398      {
 399          if (!$this->valid()) {
 400              return null;
 401          }
 402  
 403          $ob = $this->_data[$this->_ptr['idx']];
 404  
 405          return is_null($this->_ptr['subidx'])
 406              ? $ob
 407              : $ob->addresses[$this->_ptr['subidx']];
 408      }
 409  
 410      public function key()
 411      {
 412          return $this->_ptr['key'];
 413      }
 414  
 415      public function next()
 416      {
 417          if (is_null($this->_ptr['subidx'])) {
 418              $curr = $this->current();
 419              if (($curr instanceof Horde_Mail_Rfc822_Group) && count($curr)) {
 420                  $this->_ptr['subidx'] = 0;
 421              } else {
 422                  ++$this->_ptr['idx'];
 423              }
 424              $curr = $this->current();
 425          } elseif (!($curr = $this->_data[$this->_ptr['idx']]->addresses[++$this->_ptr['subidx']])) {
 426              $this->_ptr['subidx'] = null;
 427              ++$this->_ptr['idx'];
 428              $curr = $this->current();
 429          }
 430  
 431          if (!is_null($curr)) {
 432              if (!empty($this->_filter) && $this->_iteratorFilter($curr)) {
 433                  $this->next();
 434              } else {
 435                  ++$this->_ptr['key'];
 436              }
 437          }
 438      }
 439  
 440      public function rewind()
 441      {
 442          $this->_ptr = array(
 443              'idx' => 0,
 444              'key' => 0,
 445              'subidx' => null
 446          );
 447  
 448          if ($this->valid() &&
 449              !empty($this->_filter) &&
 450              $this->_iteratorFilter($this->current())) {
 451              $this->next();
 452              $this->_ptr['key'] = 0;
 453          }
 454      }
 455  
 456      public function valid()
 457      {
 458          return (!empty($this->_ptr) && isset($this->_data[$this->_ptr['idx']]));
 459      }
 460  
 461      public function seek($position)
 462      {
 463          if (!$this->valid() ||
 464              ($position < $this->_ptr['key'])) {
 465              $this->rewind();
 466          }
 467  
 468          for ($i = $this->_ptr['key']; ; ++$i) {
 469              if ($i == $position) {
 470                  return;
 471              }
 472  
 473              $this->next();
 474              if (!$this->valid()) {
 475                  throw new OutOfBoundsException('Position not found.');
 476              }
 477          }
 478      }
 479  
 480      protected function _iteratorFilter($ob)
 481      {
 482          if (!empty($this->_filter['mask'])) {
 483              if (($this->_filter['mask'] & self::HIDE_GROUPS) &&
 484                  ($ob instanceof Horde_Mail_Rfc822_Group)) {
 485                  return true;
 486              }
 487  
 488              if (($this->_filter['mask'] & self::BASE_ELEMENTS) &&
 489                  !is_null($this->_ptr['subidx'])) {
 490                  return true;
 491              }
 492          }
 493  
 494          if (!empty($this->_filter['filter']) &&
 495              ($ob instanceof Horde_Mail_Rfc822_Address)) {
 496              foreach ($this->_filter['filter'] as $val) {
 497                  if ($ob->match($val)) {
 498                      return true;
 499                  }
 500              }
 501          }
 502  
 503          return false;
 504      }
 505  
 506      /* Serializable methods. */
 507  
 508      public function serialize()
 509      {
 510          return serialize($this->_data);
 511      }
 512  
 513      public function unserialize($data)
 514      {
 515          $this->_data = unserialize($data);
 516      }
 517  
 518  }