PageRenderTime 43ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/horde/framework/Horde/Imap/Client/Ids.php

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