PageRenderTime 48ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/program/include/rcube_result_index.php

https://github.com/netconstructor/roundcubemail
PHP | 453 lines | 233 code | 80 blank | 140 comment | 44 complexity | f0023e366bc59e81c39a2daf6092e6a0 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1
  1. <?php
  2. /*
  3. +-----------------------------------------------------------------------+
  4. | program/include/rcube_result_index.php |
  5. | |
  6. | This file is part of the Roundcube Webmail client |
  7. | Copyright (C) 2005-2011, The Roundcube Dev Team |
  8. | Copyright (C) 2011, Kolab Systems AG |
  9. | |
  10. | Licensed under the GNU General Public License version 3 or |
  11. | any later version with exceptions for skins & plugins. |
  12. | See the README file for a full license statement. |
  13. | |
  14. | PURPOSE: |
  15. | SORT/SEARCH/ESEARCH response handler |
  16. | |
  17. +-----------------------------------------------------------------------+
  18. | Author: Thomas Bruederli <roundcube@gmail.com> |
  19. | Author: Aleksander Machniak <alec@alec.pl> |
  20. +-----------------------------------------------------------------------+
  21. */
  22. /**
  23. * Class for accessing IMAP's SORT/SEARCH/ESEARCH result
  24. *
  25. * @package Framework
  26. * @subpackage Storage
  27. */
  28. class rcube_result_index
  29. {
  30. protected $raw_data;
  31. protected $mailbox;
  32. protected $meta = array();
  33. protected $params = array();
  34. protected $order = 'ASC';
  35. const SEPARATOR_ELEMENT = ' ';
  36. /**
  37. * Object constructor.
  38. */
  39. public function __construct($mailbox = null, $data = null)
  40. {
  41. $this->mailbox = $mailbox;
  42. $this->init($data);
  43. }
  44. /**
  45. * Initializes object with SORT command response
  46. *
  47. * @param string $data IMAP response string
  48. */
  49. public function init($data = null)
  50. {
  51. $this->meta = array();
  52. $data = explode('*', (string)$data);
  53. // ...skip unilateral untagged server responses
  54. for ($i=0, $len=count($data); $i<$len; $i++) {
  55. $data_item = &$data[$i];
  56. if (preg_match('/^ SORT/i', $data_item)) {
  57. // valid response, initialize raw_data for is_error()
  58. $this->raw_data = '';
  59. $data_item = substr($data_item, 5);
  60. break;
  61. }
  62. else if (preg_match('/^ (E?SEARCH)/i', $data_item, $m)) {
  63. // valid response, initialize raw_data for is_error()
  64. $this->raw_data = '';
  65. $data_item = substr($data_item, strlen($m[0]));
  66. if (strtoupper($m[1]) == 'ESEARCH') {
  67. $data_item = trim($data_item);
  68. // remove MODSEQ response
  69. if (preg_match('/\(MODSEQ ([0-9]+)\)$/i', $data_item, $m)) {
  70. $data_item = substr($data_item, 0, -strlen($m[0]));
  71. $this->params['MODSEQ'] = $m[1];
  72. }
  73. // remove TAG response part
  74. if (preg_match('/^\(TAG ["a-z0-9]+\)\s*/i', $data_item, $m)) {
  75. $data_item = substr($data_item, strlen($m[0]));
  76. }
  77. // remove UID
  78. $data_item = preg_replace('/^UID\s*/i', '', $data_item);
  79. // ESEARCH parameters
  80. while (preg_match('/^([a-z]+) ([0-9:,]+)\s*/i', $data_item, $m)) {
  81. $param = strtoupper($m[1]);
  82. $value = $m[2];
  83. $this->params[$param] = $value;
  84. $data_item = substr($data_item, strlen($m[0]));
  85. if (in_array($param, array('COUNT', 'MIN', 'MAX'))) {
  86. $this->meta[strtolower($param)] = (int) $value;
  87. }
  88. }
  89. // @TODO: Implement compression using compressMessageSet() in __sleep() and __wakeup() ?
  90. // @TODO: work with compressed result?!
  91. if (isset($this->params['ALL'])) {
  92. $data_item = implode(self::SEPARATOR_ELEMENT,
  93. rcube_imap_generic::uncompressMessageSet($this->params['ALL']));
  94. }
  95. }
  96. break;
  97. }
  98. unset($data[$i]);
  99. }
  100. $data = array_filter($data);
  101. if (empty($data)) {
  102. return;
  103. }
  104. $data = array_shift($data);
  105. $data = trim($data);
  106. $data = preg_replace('/[\r\n]/', '', $data);
  107. $data = preg_replace('/\s+/', ' ', $data);
  108. $this->raw_data = $data;
  109. }
  110. /**
  111. * Checks the result from IMAP command
  112. *
  113. * @return bool True if the result is an error, False otherwise
  114. */
  115. public function is_error()
  116. {
  117. return $this->raw_data === null ? true : false;
  118. }
  119. /**
  120. * Checks if the result is empty
  121. *
  122. * @return bool True if the result is empty, False otherwise
  123. */
  124. public function is_empty()
  125. {
  126. return empty($this->raw_data) ? true : false;
  127. }
  128. /**
  129. * Returns number of elements in the result
  130. *
  131. * @return int Number of elements
  132. */
  133. public function count()
  134. {
  135. if ($this->meta['count'] !== null)
  136. return $this->meta['count'];
  137. if (empty($this->raw_data)) {
  138. $this->meta['count'] = 0;
  139. $this->meta['length'] = 0;
  140. }
  141. else {
  142. $this->meta['count'] = 1 + substr_count($this->raw_data, self::SEPARATOR_ELEMENT);
  143. }
  144. return $this->meta['count'];
  145. }
  146. /**
  147. * Returns number of elements in the result.
  148. * Alias for count() for compatibility with rcube_result_thread
  149. *
  150. * @return int Number of elements
  151. */
  152. public function count_messages()
  153. {
  154. return $this->count();
  155. }
  156. /**
  157. * Returns maximal message identifier in the result
  158. *
  159. * @return int Maximal message identifier
  160. */
  161. public function max()
  162. {
  163. if (!isset($this->meta['max'])) {
  164. $this->meta['max'] = (int) @max($this->get());
  165. }
  166. return $this->meta['max'];
  167. }
  168. /**
  169. * Returns minimal message identifier in the result
  170. *
  171. * @return int Minimal message identifier
  172. */
  173. public function min()
  174. {
  175. if (!isset($this->meta['min'])) {
  176. $this->meta['min'] = (int) @min($this->get());
  177. }
  178. return $this->meta['min'];
  179. }
  180. /**
  181. * Slices data set.
  182. *
  183. * @param $offset Offset (as for PHP's array_slice())
  184. * @param $length Number of elements (as for PHP's array_slice())
  185. *
  186. */
  187. public function slice($offset, $length)
  188. {
  189. $data = $this->get();
  190. $data = array_slice($data, $offset, $length);
  191. $this->meta = array();
  192. $this->meta['count'] = count($data);
  193. $this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
  194. }
  195. /**
  196. * Filters data set. Removes elements listed in $ids list.
  197. *
  198. * @param array $ids List of IDs to remove.
  199. */
  200. public function filter($ids = array())
  201. {
  202. $data = $this->get();
  203. $data = array_diff($data, $ids);
  204. $this->meta = array();
  205. $this->meta['count'] = count($data);
  206. $this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
  207. }
  208. /**
  209. * Filters data set. Removes elements not listed in $ids list.
  210. *
  211. * @param array $ids List of IDs to keep.
  212. */
  213. public function intersect($ids = array())
  214. {
  215. $data = $this->get();
  216. $data = array_intersect($data, $ids);
  217. $this->meta = array();
  218. $this->meta['count'] = count($data);
  219. $this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
  220. }
  221. /**
  222. * Reverts order of elements in the result
  223. */
  224. public function revert()
  225. {
  226. $this->order = $this->order == 'ASC' ? 'DESC' : 'ASC';
  227. if (empty($this->raw_data)) {
  228. return;
  229. }
  230. // @TODO: maybe do this in chunks
  231. $data = $this->get();
  232. $data = array_reverse($data);
  233. $this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
  234. $this->meta['pos'] = array();
  235. }
  236. /**
  237. * Check if the given message ID exists in the object
  238. *
  239. * @param int $msgid Message ID
  240. * @param bool $get_index When enabled element's index will be returned.
  241. * Elements are indexed starting with 0
  242. *
  243. * @return mixed False if message ID doesn't exist, True if exists or
  244. * index of the element if $get_index=true
  245. */
  246. public function exists($msgid, $get_index = false)
  247. {
  248. if (empty($this->raw_data)) {
  249. return false;
  250. }
  251. $msgid = (int) $msgid;
  252. $begin = implode('|', array('^', preg_quote(self::SEPARATOR_ELEMENT, '/')));
  253. $end = implode('|', array('$', preg_quote(self::SEPARATOR_ELEMENT, '/')));
  254. if (preg_match("/($begin)$msgid($end)/", $this->raw_data, $m,
  255. $get_index ? PREG_OFFSET_CAPTURE : null)
  256. ) {
  257. if ($get_index) {
  258. $idx = 0;
  259. if ($m[0][1]) {
  260. $idx = 1 + substr_count($this->raw_data, self::SEPARATOR_ELEMENT, 0, $m[0][1]);
  261. }
  262. // cache position of this element, so we can use it in get_element()
  263. $this->meta['pos'][$idx] = (int)$m[0][1];
  264. return $idx;
  265. }
  266. return true;
  267. }
  268. return false;
  269. }
  270. /**
  271. * Return all messages in the result.
  272. *
  273. * @return array List of message IDs
  274. */
  275. public function get()
  276. {
  277. if (empty($this->raw_data)) {
  278. return array();
  279. }
  280. return explode(self::SEPARATOR_ELEMENT, $this->raw_data);
  281. }
  282. /**
  283. * Return all messages in the result.
  284. *
  285. * @return array List of message IDs
  286. */
  287. public function get_compressed()
  288. {
  289. if (empty($this->raw_data)) {
  290. return '';
  291. }
  292. return rcube_imap_generic::compressMessageSet($this->get());
  293. }
  294. /**
  295. * Return result element at specified index
  296. *
  297. * @param int|string $index Element's index or "FIRST" or "LAST"
  298. *
  299. * @return int Element value
  300. */
  301. public function get_element($index)
  302. {
  303. $count = $this->count();
  304. if (!$count) {
  305. return null;
  306. }
  307. // first element
  308. if ($index === 0 || $index === '0' || $index === 'FIRST') {
  309. $pos = strpos($this->raw_data, self::SEPARATOR_ELEMENT);
  310. if ($pos === false)
  311. $result = (int) $this->raw_data;
  312. else
  313. $result = (int) substr($this->raw_data, 0, $pos);
  314. return $result;
  315. }
  316. // last element
  317. if ($index === 'LAST' || $index == $count-1) {
  318. $pos = strrpos($this->raw_data, self::SEPARATOR_ELEMENT);
  319. if ($pos === false)
  320. $result = (int) $this->raw_data;
  321. else
  322. $result = (int) substr($this->raw_data, $pos);
  323. return $result;
  324. }
  325. // do we know the position of the element or the neighbour of it?
  326. if (!empty($this->meta['pos'])) {
  327. if (isset($this->meta['pos'][$index]))
  328. $pos = $this->meta['pos'][$index];
  329. else if (isset($this->meta['pos'][$index-1]))
  330. $pos = strpos($this->raw_data, self::SEPARATOR_ELEMENT,
  331. $this->meta['pos'][$index-1] + 1);
  332. else if (isset($this->meta['pos'][$index+1]))
  333. $pos = strrpos($this->raw_data, self::SEPARATOR_ELEMENT,
  334. $this->meta['pos'][$index+1] - $this->length() - 1);
  335. if (isset($pos) && preg_match('/([0-9]+)/', $this->raw_data, $m, null, $pos)) {
  336. return (int) $m[1];
  337. }
  338. }
  339. // Finally use less effective method
  340. $data = explode(self::SEPARATOR_ELEMENT, $this->raw_data);
  341. return $data[$index];
  342. }
  343. /**
  344. * Returns response parameters, e.g. ESEARCH's MIN/MAX/COUNT/ALL/MODSEQ
  345. * or internal data e.g. MAILBOX, ORDER
  346. *
  347. * @param string $param Parameter name
  348. *
  349. * @return array|string Response parameters or parameter value
  350. */
  351. public function get_parameters($param=null)
  352. {
  353. $params = $this->params;
  354. $params['MAILBOX'] = $this->mailbox;
  355. $params['ORDER'] = $this->order;
  356. if ($param !== null) {
  357. return $params[$param];
  358. }
  359. return $params;
  360. }
  361. /**
  362. * Returns length of internal data representation
  363. *
  364. * @return int Data length
  365. */
  366. protected function length()
  367. {
  368. if (!isset($this->meta['length'])) {
  369. $this->meta['length'] = strlen($this->raw_data);
  370. }
  371. return $this->meta['length'];
  372. }
  373. }