PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/horde/framework/Horde/Imap/Client/Socket/ClientSort.php

https://github.com/pauln/moodle
PHP | 317 lines | 188 code | 41 blank | 88 comment | 26 complexity | c174cb0a0dcb88db82e7233b4d8a4717 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright 2012-2014 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 2012-2014 Horde LLC
  10. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  11. * @package Imap_Client
  12. */
  13. /**
  14. * Client sorting methods for the Socket driver.
  15. *
  16. * NOTE: This class is NOT intended to be accessed outside of a Base object.
  17. * There is NO guarantees that the API of this class will not change across
  18. * versions.
  19. *
  20. * @author Michael Slusarz <slusarz@horde.org>
  21. * @category Horde
  22. * @copyright 2012-2014 Horde LLC
  23. * @internal
  24. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  25. * @package Imap_Client
  26. */
  27. class Horde_Imap_Client_Socket_ClientSort
  28. {
  29. /**
  30. * Socket object.
  31. *
  32. * @var Horde_Imap_Client_Socket
  33. */
  34. protected $_socket;
  35. /**
  36. * Constructor.
  37. *
  38. * @param Horde_Imap_Client_Socket $socket Socket object.
  39. */
  40. public function __construct(Horde_Imap_Client_Socket $socket)
  41. {
  42. $this->_socket = $socket;
  43. }
  44. /**
  45. * Sort search results client side if the server does not support the SORT
  46. * IMAP extension (RFC 5256).
  47. *
  48. * @param Horde_Imap_Client_Ids $res The search results.
  49. * @param array $opts The options to _search().
  50. *
  51. * @return array The sort results.
  52. *
  53. * @throws Horde_Imap_Client_Exception
  54. */
  55. public function clientSort($res, $opts)
  56. {
  57. if (!count($res)) {
  58. return $res;
  59. }
  60. /* Generate the FETCH command needed. */
  61. $query = new Horde_Imap_Client_Fetch_Query();
  62. foreach ($opts['sort'] as $val) {
  63. switch ($val) {
  64. case Horde_Imap_Client::SORT_ARRIVAL:
  65. $query->imapDate();
  66. break;
  67. case Horde_Imap_Client::SORT_DATE:
  68. $query->imapDate();
  69. $query->envelope();
  70. break;
  71. case Horde_Imap_Client::SORT_CC:
  72. case Horde_Imap_Client::SORT_DISPLAYFROM:
  73. case Horde_Imap_Client::SORT_DISPLAYTO:
  74. case Horde_Imap_Client::SORT_FROM:
  75. case Horde_Imap_Client::SORT_SUBJECT:
  76. case Horde_Imap_Client::SORT_TO:
  77. $query->envelope();
  78. break;
  79. case Horde_Imap_Client::SORT_SIZE:
  80. $query->size();
  81. break;
  82. }
  83. }
  84. if (!count($query)) {
  85. return $res;
  86. }
  87. $mbox = $this->_socket->currentMailbox();
  88. $fetch_res = $this->_socket->fetch($mbox['mailbox'], $query, array(
  89. 'ids' => $res
  90. ));
  91. return $this->_clientSortProcess($res->ids, $fetch_res, $opts['sort']);
  92. }
  93. /**
  94. * If server does not support the THREAD IMAP extension (RFC 5256), do
  95. * ORDEREDSUBJECT threading on the client side.
  96. *
  97. * @param Horde_Imap_Client_Fetch_Results $data Fetch results.
  98. * @param boolean $uids Are IDs UIDs?
  99. *
  100. * @return array The thread sort results.
  101. */
  102. public function threadOrderedSubject(Horde_Imap_Client_Fetch_Results $data,
  103. $uids)
  104. {
  105. $dates = $this->_getSentDates($data, $data->ids());
  106. $out = $sorted = $tsort = array();
  107. foreach ($data as $k => $v) {
  108. $subject = strval(new Horde_Imap_Client_Data_BaseSubject($v->getEnvelope()->subject));
  109. $sorted[$subject][$k] = $dates[$k];
  110. }
  111. /* Step 1: Sort by base subject (already done).
  112. * Step 2: Sort by sent date within each thread. */
  113. foreach (array_keys($sorted) as $key) {
  114. asort($sorted[$key], SORT_NUMERIC);
  115. $tsort[$key] = reset($sorted[$key]);
  116. }
  117. /* Step 3: Sort by the sent date of the first message in the
  118. * thread. */
  119. asort($tsort, SORT_NUMERIC);
  120. /* Now, $tsort contains the order of the threads, and each thread
  121. * is sorted in $sorted. */
  122. foreach (array_keys($tsort) as $key) {
  123. $keys = array_keys($sorted[$key]);
  124. $out[$keys[0]] = array(
  125. $keys[0] => 0
  126. ) + array_fill_keys(array_slice($keys, 1) , 1);
  127. }
  128. return new Horde_Imap_Client_Data_Thread($out, $uids ? 'uid' : 'sequence');
  129. }
  130. /**
  131. */
  132. protected function _clientSortProcess($res, $fetch_res, $sort)
  133. {
  134. /* The initial sort is on the entire set. */
  135. $slices = array(0 => $res);
  136. $reverse = false;
  137. foreach ($sort as $val) {
  138. if ($val == Horde_Imap_Client::SORT_REVERSE) {
  139. $reverse = true;
  140. continue;
  141. }
  142. $slices_list = $slices;
  143. $slices = array();
  144. foreach ($slices_list as $slice_start => $slice) {
  145. $sorted = array();
  146. if ($reverse) {
  147. $slice = array_reverse($slice);
  148. }
  149. switch ($val) {
  150. case Horde_Imap_Client::SORT_SEQUENCE:
  151. /* There is no requirement that IDs be returned in
  152. * sequence order (see RFC 4549 [4.3.1]). So we must sort
  153. * ourselves. */
  154. $sorted = array_flip($slice);
  155. ksort($sorted, SORT_NUMERIC);
  156. break;
  157. case Horde_Imap_Client::SORT_SIZE:
  158. foreach ($slice as $num) {
  159. $sorted[$num] = $fetch_res[$num]->getSize();
  160. }
  161. asort($sorted, SORT_NUMERIC);
  162. break;
  163. case Horde_Imap_Client::SORT_DISPLAYFROM:
  164. case Horde_Imap_Client::SORT_DISPLAYTO:
  165. $field = ($val == Horde_Imap_Client::SORT_DISPLAYFROM)
  166. ? 'from'
  167. : 'to';
  168. foreach ($slice as $num) {
  169. $env = $fetch_res[$num]->getEnvelope();
  170. if (empty($env->$field)) {
  171. $sorted[$num] = null;
  172. } else {
  173. $addr_ob = reset($env->$field);
  174. if (is_null($sorted[$num] = $addr_ob->personal)) {
  175. $sorted[$num] = $addr_ob->mailbox;
  176. }
  177. }
  178. }
  179. asort($sorted, SORT_LOCALE_STRING);
  180. break;
  181. case Horde_Imap_Client::SORT_CC:
  182. case Horde_Imap_Client::SORT_FROM:
  183. case Horde_Imap_Client::SORT_TO:
  184. if ($val == Horde_Imap_Client::SORT_CC) {
  185. $field = 'cc';
  186. } elseif ($val == Horde_Imap_Client::SORT_FROM) {
  187. $field = 'from';
  188. } else {
  189. $field = 'to';
  190. }
  191. foreach ($slice as $num) {
  192. $tmp = $fetch_res[$num]->getEnvelope()->$field;
  193. $sorted[$num] = count($tmp)
  194. ? $tmp[0]->mailbox
  195. : null;
  196. }
  197. asort($sorted, SORT_LOCALE_STRING);
  198. break;
  199. case Horde_Imap_Client::SORT_ARRIVAL:
  200. $sorted = $this->_getSentDates($fetch_res, $slice, true);
  201. asort($sorted, SORT_NUMERIC);
  202. break;
  203. case Horde_Imap_Client::SORT_DATE:
  204. // Date sorting rules in RFC 5256 [2.2]
  205. $sorted = $this->_getSentDates($fetch_res, $slice);
  206. asort($sorted, SORT_NUMERIC);
  207. break;
  208. case Horde_Imap_Client::SORT_SUBJECT:
  209. // Subject sorting rules in RFC 5256 [2.1]
  210. foreach ($slice as $num) {
  211. $sorted[$num] = strval(new Horde_Imap_Client_Data_BaseSubject($fetch_res[$num]->getEnvelope()->subject));
  212. }
  213. asort($sorted, SORT_LOCALE_STRING);
  214. break;
  215. }
  216. // At this point, keys of $sorted are sequence/UID and values
  217. // are the sort strings
  218. if (!empty($sorted)) {
  219. if (count($sorted) === count($res)) {
  220. $res = array_keys($sorted);
  221. } else {
  222. array_splice($res, $slice_start, count($slice), array_keys($sorted));
  223. }
  224. // Check for ties.
  225. $last = $start = null;
  226. $i = 0;
  227. reset($sorted);
  228. while (list($k, $v) = each($sorted)) {
  229. if (is_null($last) || ($last != $v)) {
  230. if ($i) {
  231. $slices[array_search($start, $res)] = array_slice($sorted, array_search($start, $sorted), $i + 1);
  232. $i = 0;
  233. }
  234. $last = $v;
  235. $start = $k;
  236. } else {
  237. ++$i;
  238. }
  239. }
  240. if ($i) {
  241. $slices[array_search($start, $res)] = array_slice($sorted, array_search($start, $sorted), $i + 1);
  242. }
  243. }
  244. }
  245. $reverse = false;
  246. }
  247. return $res;
  248. }
  249. /**
  250. * Get the sent dates for purposes of SORT/THREAD sorting under RFC 5256
  251. * [2.2].
  252. *
  253. * @param Horde_Imap_Client_Fetch_Results $data Data returned from
  254. * fetch() that includes
  255. * both date and envelope
  256. * items.
  257. * @param array $ids The IDs to process.
  258. * @param boolean $internal Only use internal date?
  259. *
  260. * @return array A mapping of IDs -> UNIX timestamps.
  261. */
  262. protected function _getSentDates(Horde_Imap_Client_Fetch_Results $data,
  263. $ids, $internal = false)
  264. {
  265. $dates = array();
  266. foreach ($ids as $num) {
  267. $dt = ($internal || !isset($data[$num]->getEnvelope()->date))
  268. // RFC 5256 [3] & 3501 [6.4.4]: disregard timezone when
  269. // using internaldate.
  270. ? $data[$num]->getImapDate()
  271. : $data[$num]->getEnvelope()->date;
  272. $dates[$num] = $dt->format('U');
  273. }
  274. return $dates;
  275. }
  276. }