Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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      public function count()
 381      {
 382          return is_array($this->_ids)
 383              ? count($this->_ids)
 384              : 0;
 385      }
 386  
 387      /* Iterator methods. */
 388  
 389      /**
 390       */
 391      public function current()
 392      {
 393          return is_array($this->_ids)
 394              ? current($this->_ids)
 395              : null;
 396      }
 397  
 398      /**
 399       */
 400      public function key()
 401      {
 402          return is_array($this->_ids)
 403              ? key($this->_ids)
 404              : null;
 405      }
 406  
 407      /**
 408       */
 409      public function next()
 410      {
 411          if (is_array($this->_ids)) {
 412              next($this->_ids);
 413          }
 414      }
 415  
 416      /**
 417       */
 418      public function rewind()
 419      {
 420          if (is_array($this->_ids)) {
 421              reset($this->_ids);
 422          }
 423      }
 424  
 425      /**
 426       */
 427      public function valid()
 428      {
 429          return !is_null($this->key());
 430      }
 431  
 432      /* Serializable methods. */
 433  
 434      /**
 435       */
 436      public function serialize()
 437      {
 438          $save = array();
 439  
 440          if ($this->duplicates) {
 441              $save['d'] = 1;
 442          }
 443  
 444          if ($this->_sequence) {
 445              $save['s'] = 1;
 446          }
 447  
 448          if ($this->_sorted) {
 449              $save['is'] = 1;
 450          }
 451  
 452          switch ($this->_ids) {
 453          case self::ALL:
 454              $save['a'] = true;
 455              break;
 456  
 457          case self::LARGEST:
 458              $save['l'] = true;
 459              break;
 460  
 461          case self::SEARCH_RES:
 462              $save['sr'] = true;
 463              break;
 464  
 465          default:
 466              $save['i'] = strval($this);
 467              break;
 468          }
 469  
 470          return serialize($save);
 471      }
 472  
 473      /**
 474       */
 475      public function unserialize($data)
 476      {
 477          $save = @unserialize($data);
 478  
 479          $this->duplicates = !empty($save['d']);
 480          $this->_sequence = !empty($save['s']);
 481          $this->_sorted = !empty($save['is']);
 482  
 483          if (isset($save['a'])) {
 484              $this->_ids = self::ALL;
 485          } elseif (isset($save['l'])) {
 486              $this->_ids = self::LARGEST;
 487          } elseif (isset($save['sr'])) {
 488              $this->_ids = self::SEARCH_RES;
 489          } elseif (isset($save['i'])) {
 490              $this->add($save['i']);
 491          }
 492      }
 493  
 494  }