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 2011-2017 Horde LLC (http://www.horde.org/)
   4   *
   5   * See the enclosed file LICENSE for license information (LGPL). If you
   6   * did not receive this file, see http://www.horde.org/licenses/lgpl21.
   7   *
   8   * @category  Horde
   9   * @copyright 2011-2017 Horde LLC
  10   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  11   * @package   Imap_Client
  12   */
  13  
  14  /**
  15   * An object that provides a way to identify a list of IMAP indices.
  16   *
  17   * @author    Michael Slusarz <slusarz@horde.org>
  18   * @category  Horde
  19   * @copyright 2011-2017 Horde LLC
  20   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  21   * @package   Imap_Client
  22   *
  23   * @property-read boolean $all  Does this represent an ALL message set?
  24   * @property-read array $ids  The list of IDs.
  25   * @property-read boolean $largest  Does this represent the largest ID in use?
  26   * @property-read string $max  The largest ID (@since 2.20.0).
  27   * @property-read string $min  The smallest ID (@since 2.20.0).
  28   * @property-read string $range_string  Generates a range string consisting of
  29   *                                      all messages between begin and end of
  30   *                                      ID list.
  31   * @property-read boolean $search_res  Does this represent a search result?
  32   * @property-read boolean $sequence  Are these sequence IDs? If false, these
  33   *                                   are UIDs.
  34   * @property-read boolean $special  True if this is a "special" ID
  35   *                                  representation.
  36   * @property-read string $tostring  Return the non-sorted string
  37   *                                  representation.
  38   * @property-read string $tostring_sort  Return the sorted string
  39   *                                       representation.
  40   */
  41  class Horde_Imap_Client_Ids implements Countable, Iterator, Serializable
  42  {
  43      /**
  44       * "Special" representation constants.
  45       */
  46      const ALL = "\01";
  47      const SEARCH_RES = "\02";
  48      const LARGEST = "\03";
  49  
  50      /**
  51       * Allow duplicate IDs?
  52       *
  53       * @var boolean
  54       */
  55      public $duplicates = false;
  56  
  57      /**
  58       * List of IDs.
  59       *
  60       * @var mixed
  61       */
  62      protected $_ids = array();
  63  
  64      /**
  65       * Are IDs message sequence numbers?
  66       *
  67       * @var boolean
  68       */
  69      protected $_sequence = false;
  70  
  71      /**
  72       * Are IDs sorted?
  73       *
  74       * @var boolean
  75       */
  76      protected $_sorted = false;
  77  
  78      /**
  79       * Constructor.
  80       *
  81       * @param mixed $ids         See self::add().
  82       * @param boolean $sequence  Are $ids message sequence numbers?
  83       */
  84      public function __construct($ids = null, $sequence = false)
  85      {
  86          $this->add($ids);
  87          $this->_sequence = $sequence;
  88      }
  89  
  90      /**
  91       */
  92      public function __get($name)
  93      {
  94          switch ($name) {
  95          case 'all':
  96              return ($this->_ids === self::ALL);
  97  
  98          case 'ids':
  99              return is_array($this->_ids)
 100                  ? $this->_ids
 101                  : array();
 102  
 103          case 'largest':
 104              return ($this->_ids === self::LARGEST);
 105  
 106          case 'max':
 107              $this->sort();
 108              return end($this->_ids);
 109  
 110          case 'min':
 111              $this->sort();
 112              return reset($this->_ids);
 113  
 114          case 'range_string':
 115              if (!count($this)) {
 116                  return '';
 117              }
 118  
 119              $min = $this->min;
 120              $max = $this->max;
 121  
 122              return ($min == $max)
 123                  ? $min
 124                  : $min . ':' . $max;
 125  
 126          case 'search_res':
 127              return ($this->_ids === self::SEARCH_RES);
 128  
 129          case 'sequence':
 130              return (bool)$this->_sequence;
 131  
 132          case 'special':
 133              return is_string($this->_ids);
 134  
 135          case 'tostring':
 136          case 'tostring_sort':
 137              if ($this->all) {
 138                  return '1:*';
 139              } elseif ($this->largest) {
 140                  return '*';
 141              } elseif ($this->search_res) {
 142                  return '$';
 143              }
 144              return strval($this->_toSequenceString($name == 'tostring_sort'));
 145          }
 146      }
 147  
 148      /**
 149       */
 150      public function __toString()
 151      {
 152          return $this->tostring;
 153      }
 154  
 155      /**
 156       * Add IDs to the current object.
 157       *
 158       * @param mixed $ids  Either self::ALL, self::SEARCH_RES, self::LARGEST,
 159       *                    Horde_Imap_Client_Ids object, array, or sequence
 160       *                    string.
 161       */
 162      public function add($ids)
 163      {
 164          if (!is_null($ids)) {
 165              if (is_string($ids) &&
 166                  in_array($ids, array(self::ALL, self::SEARCH_RES, self::LARGEST))) {
 167                  $this->_ids = $ids;
 168              } elseif ($add = $this->_resolveIds($ids)) {
 169                  if (is_array($this->_ids) && !empty($this->_ids)) {
 170                      foreach ($add as $val) {
 171                          $this->_ids[] = $val;
 172                      }
 173                  } else {
 174                      $this->_ids = $add;
 175                  }
 176                  if (!$this->duplicates) {
 177                      $this->_ids = (count($this->_ids) > 25000)
 178                          ? array_unique($this->_ids)
 179                          : array_keys(array_flip($this->_ids));
 180                  }
 181              }
 182  
 183              $this->_sorted = is_array($this->_ids) && (count($this->_ids) === 1);
 184          }
 185      }
 186  
 187      /**
 188       * Removed IDs from the current object.
 189       *
 190       * @since 2.17.0
 191       *
 192       * @param mixed $ids  Either Horde_Imap_Client_Ids object, array, or
 193       *                    sequence string.
 194       */
 195      public function remove($ids)
 196      {
 197          if (!$this->isEmpty() &&
 198              ($remove = $this->_resolveIds($ids))) {
 199              $this->_ids = array_diff($this->_ids, array_unique($remove));
 200          }
 201      }
 202  
 203      /**
 204       * Is this object empty (i.e. does not contain IDs)?
 205       *
 206       * @return boolean  True if object is empty.
 207       */
 208      public function isEmpty()
 209      {
 210          return (is_array($this->_ids) && !count($this->_ids));
 211      }
 212  
 213      /**
 214       * Reverses the order of the IDs.
 215       */
 216      public function reverse()
 217      {
 218          if (is_array($this->_ids)) {
 219              $this->_ids = array_reverse($this->_ids);
 220          }
 221      }
 222  
 223      /**
 224       * Sorts the IDs.
 225       */
 226      public function sort()
 227      {
 228          if (!$this->_sorted && is_array($this->_ids)) {
 229              $this->_sort($this->_ids);
 230              $this->_sorted = true;
 231          }
 232      }
 233  
 234      /**
 235       * Sorts the IDs numerically.
 236       *
 237       * @param array $ids  The array list.
 238       */
 239      protected function _sort(&$ids)
 240      {
 241          sort($ids, SORT_NUMERIC);
 242      }
 243  
 244      /**
 245       * Split the sequence string at an approximate length.
 246       *
 247       * @since 2.7.0
 248       *
 249       * @param integer $length  Length to split.
 250       *
 251       * @return array  A list containing individual sequence strings.
 252       */
 253      public function split($length)
 254      {
 255          $id = new Horde_Stream_Temp();
 256          $id->add($this->tostring_sort, true);
 257  
 258          $out = array();
 259  
 260          do {
 261              $out[] = $id->substring(0, $length) . $id->getToChar(',');
 262          } while (!$id->eof());
 263  
 264          return $out;
 265      }
 266  
 267      /**
 268       * Resolve the $ids input to add() and remove().
 269       *
 270       * @param mixed $ids  Either Horde_Imap_Client_Ids object, array, or
 271       *                    sequence string.
 272       *
 273       * @return array  An array of IDs.
 274       */
 275      protected function _resolveIds($ids)
 276      {
 277          if ($ids instanceof Horde_Imap_Client_Ids) {
 278              return $ids->ids;
 279          } elseif (is_array($ids)) {
 280              return $ids;
 281          } elseif (is_string($ids) || is_integer($ids)) {
 282              return is_numeric($ids)
 283                  ? array($ids)
 284                  : $this->_fromSequenceString($ids);
 285          }
 286  
 287          return array();
 288      }
 289  
 290      /**
 291       * Create an IMAP message sequence string from a list of indices.
 292       *
 293       * Index Format: range_start:range_end,uid,uid2,...
 294       *
 295       * @param boolean $sort  Numerically sort the IDs before creating the
 296       *                       range?
 297       *
 298       * @return string  The IMAP message sequence string.
 299       */
 300      protected function _toSequenceString($sort = true)
 301      {
 302          if (empty($this->_ids)) {
 303              return '';
 304          }
 305  
 306          $in = $this->_ids;
 307  
 308          if ($sort && !$this->_sorted) {
 309              $this->_sort($in);
 310          }
 311  
 312          $first = $last = array_shift($in);
 313          $i = count($in) - 1;
 314          $out = array();
 315  
 316          foreach ($in as $key => $val) {
 317              if (($last + 1) == $val) {
 318                  $last = $val;
 319              }
 320  
 321              if (($i == $key) || ($last != $val)) {
 322                  if ($last == $first) {
 323                      $out[] = $first;
 324                      if ($i == $key) {
 325                          $out[] = $val;
 326                      }
 327                  } else {
 328                      $out[] = $first . ':' . $last;
 329                      if (($i == $key) && ($last != $val)) {
 330                          $out[] = $val;
 331                      }
 332                  }
 333                  $first = $last = $val;
 334              }
 335          }
 336  
 337          return empty($out)
 338              ? $first
 339              : implode(',', $out);
 340      }
 341  
 342      /**
 343       * Parse an IMAP message sequence string into a list of indices.
 344       *
 345       * @see _toSequenceString()
 346       *
 347       * @param string $str  The IMAP message sequence string.
 348       *
 349       * @return array  An array of indices.
 350       */
 351      protected function _fromSequenceString($str)
 352      {
 353          $ids = array();
 354          $str = trim($str);
 355  
 356          if (!strlen($str)) {
 357              return $ids;
 358          }
 359  
 360          $idarray = explode(',', $str);
 361  
 362          foreach ($idarray as $val) {
 363              $range = explode(':', $val);
 364              if (isset($range[1])) {
 365                  for ($i = min($range), $j = max($range); $i <= $j; ++$i) {
 366                      $ids[] = $i;
 367                  }
 368              } else {
 369                  $ids[] = $val;
 370              }
 371          }
 372  
 373          return $ids;
 374      }
 375  
 376      /* Countable methods. */
 377  
 378      /**
 379       */
 380      #[ReturnTypeWillChange]
 381      public function count()
 382      {
 383          return is_array($this->_ids)
 384              ? count($this->_ids)
 385              : 0;
 386      }
 387  
 388      /* Iterator methods. */
 389  
 390      /**
 391       */
 392      #[ReturnTypeWillChange]
 393      public function current()
 394      {
 395          return is_array($this->_ids)
 396              ? current($this->_ids)
 397              : null;
 398      }
 399  
 400      /**
 401       */
 402      #[ReturnTypeWillChange]
 403      public function key()
 404      {
 405          return is_array($this->_ids)
 406              ? key($this->_ids)
 407              : null;
 408      }
 409  
 410      /**
 411       */
 412      #[ReturnTypeWillChange]
 413      public function next()
 414      {
 415          if (is_array($this->_ids)) {
 416              next($this->_ids);
 417          }
 418      }
 419  
 420      /**
 421       */
 422      #[ReturnTypeWillChange]
 423      public function rewind()
 424      {
 425          if (is_array($this->_ids)) {
 426              reset($this->_ids);
 427          }
 428      }
 429  
 430      /**
 431       */
 432      #[ReturnTypeWillChange]
 433      public function valid()
 434      {
 435          return !is_null($this->key());
 436      }
 437  
 438      public function serialize()
 439      {
 440          return serialize($this->__serialize());
 441      }
 442  
 443      public function unserialize($data)
 444      {
 445          $data = @unserialize($data);
 446          if (!is_array($data)) {
 447              throw new Exception('Cache version change.');
 448          }
 449  
 450          $this->__unserialize($data);
 451      }
 452  
 453      /**
 454       */
 455      public function __serialize()
 456      {
 457          $save = array();
 458  
 459          if ($this->duplicates) {
 460              $save['d'] = 1;
 461          }
 462  
 463          if ($this->_sequence) {
 464              $save['s'] = 1;
 465          }
 466  
 467          if ($this->_sorted) {
 468              $save['is'] = 1;
 469          }
 470  
 471          switch ($this->_ids) {
 472          case self::ALL:
 473              $save['a'] = true;
 474              break;
 475  
 476          case self::LARGEST:
 477              $save['l'] = true;
 478              break;
 479  
 480          case self::SEARCH_RES:
 481              $save['sr'] = true;
 482              break;
 483  
 484          default:
 485              $save['i'] = strval($this);
 486              break;
 487          }
 488  
 489          return $save;
 490      }
 491  
 492      /**
 493       */
 494      public function __unserialize($data)
 495      {
 496          $this->duplicates = !empty($data['d']);
 497          $this->_sequence = !empty($data['s']);
 498          $this->_sorted = !empty($data['is']);
 499  
 500          if (isset($data['a'])) {
 501              $this->_ids = self::ALL;
 502          } elseif (isset($data['l'])) {
 503              $this->_ids = self::LARGEST;
 504          } elseif (isset($data['sr'])) {
 505              $this->_ids = self::SEARCH_RES;
 506          } elseif (isset($data['i'])) {
 507              $this->add($data['i']);
 508          }
 509      }
 510  
 511  }