Differences Between: [Versions 310 and 311] [Versions 39 and 311]
1 <?php 2 /** 3 * Copyright 2012-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 2012-2017 Horde LLC 10 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 11 * @package Imap_Client 12 */ 13 14 /** 15 * Client sorting methods for the Socket driver. 16 * 17 * NOTE: This class is NOT intended to be accessed outside of a Base object. 18 * There is NO guarantees that the API of this class will not change across 19 * versions. 20 * 21 * @author Michael Slusarz <slusarz@horde.org> 22 * @category Horde 23 * @copyright 2012-2017 Horde LLC 24 * @internal 25 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 26 * @package Imap_Client 27 */ 28 class Horde_Imap_Client_Socket_ClientSort 29 { 30 /** 31 * Collator object to use for sotring. 32 * 33 * @var Collator 34 */ 35 protected $_collator; 36 37 /** 38 * Socket object. 39 * 40 * @var Horde_Imap_Client_Socket 41 */ 42 protected $_socket; 43 44 /** 45 * Constructor. 46 * 47 * @param Horde_Imap_Client_Socket $socket Socket object. 48 */ 49 public function __construct(Horde_Imap_Client_Socket $socket) 50 { 51 $this->_socket = $socket; 52 53 if (class_exists('Collator')) { 54 $this->_collator = new Collator(null); 55 } 56 } 57 58 /** 59 * Sort search results client side if the server does not support the SORT 60 * IMAP extension (RFC 5256). 61 * 62 * @param Horde_Imap_Client_Ids $res The search results. 63 * @param array $opts The options to _search(). 64 * 65 * @return array The sort results. 66 * 67 * @throws Horde_Imap_Client_Exception 68 */ 69 public function clientSort($res, $opts) 70 { 71 if (!count($res)) { 72 return $res; 73 } 74 75 /* Generate the FETCH command needed. */ 76 $query = new Horde_Imap_Client_Fetch_Query(); 77 78 foreach ($opts['sort'] as $val) { 79 switch ($val) { 80 case Horde_Imap_Client::SORT_ARRIVAL: 81 $query->imapDate(); 82 break; 83 84 case Horde_Imap_Client::SORT_DATE: 85 $query->imapDate(); 86 $query->envelope(); 87 break; 88 89 case Horde_Imap_Client::SORT_CC: 90 case Horde_Imap_Client::SORT_DISPLAYFROM: 91 case Horde_Imap_Client::SORT_DISPLAYTO: 92 case Horde_Imap_Client::SORT_FROM: 93 case Horde_Imap_Client::SORT_SUBJECT: 94 case Horde_Imap_Client::SORT_TO: 95 $query->envelope(); 96 break; 97 98 case Horde_Imap_Client::SORT_SEQUENCE: 99 $query->seq(); 100 break; 101 102 case Horde_Imap_Client::SORT_SIZE: 103 $query->size(); 104 break; 105 } 106 } 107 108 if (!count($query)) { 109 return $res; 110 } 111 112 $mbox = $this->_socket->currentMailbox(); 113 $fetch_res = $this->_socket->fetch(isset($mbox['mailbox']) ? $mbox['mailbox'] : null, $query, array( 114 'ids' => $res 115 )); 116 117 return $this->_clientSortProcess($res->ids, $fetch_res, $opts['sort']); 118 } 119 120 /** 121 * If server does not support the THREAD IMAP extension (RFC 5256), do 122 * ORDEREDSUBJECT threading on the client side. 123 * 124 * @param Horde_Imap_Client_Fetch_Results $data Fetch results. 125 * @param boolean $uids Are IDs UIDs? 126 * 127 * @return array The thread sort results. 128 */ 129 public function threadOrderedSubject(Horde_Imap_Client_Fetch_Results $data, 130 $uids) 131 { 132 $dates = $this->_getSentDates($data, $data->ids()); 133 $out = $sorted = $tsort = array(); 134 135 foreach ($data as $k => $v) { 136 $subject = strval(new Horde_Imap_Client_Data_BaseSubject($v->getEnvelope()->subject)); 137 $sorted[$subject][$k] = $dates[$k]; 138 } 139 140 /* Step 1: Sort by base subject (already done). 141 * Step 2: Sort by sent date within each thread. */ 142 foreach (array_keys($sorted) as $key) { 143 $this->_stableAsort($sorted[$key]); 144 $tsort[$key] = reset($sorted[$key]); 145 } 146 147 /* Step 3: Sort by the sent date of the first message in the 148 * thread. */ 149 $this->_stableAsort($tsort); 150 151 /* Now, $tsort contains the order of the threads, and each thread 152 * is sorted in $sorted. */ 153 foreach (array_keys($tsort) as $key) { 154 $keys = array_keys($sorted[$key]); 155 $out[$keys[0]] = array( 156 $keys[0] => 0 157 ) + array_fill_keys(array_slice($keys, 1) , 1); 158 } 159 160 return new Horde_Imap_Client_Data_Thread( 161 $out, 162 $uids ? 'uid' : 'sequence' 163 ); 164 } 165 166 /** 167 */ 168 protected function _clientSortProcess($res, $fetch_res, $sort) 169 { 170 /* The initial sort is on the entire set. */ 171 $slices = array(0 => $res); 172 $reverse = false; 173 174 foreach ($sort as $val) { 175 if ($val == Horde_Imap_Client::SORT_REVERSE) { 176 $reverse = true; 177 continue; 178 } 179 180 $slices_list = $slices; 181 $slices = array(); 182 183 foreach ($slices_list as $slice_start => $slice) { 184 $sorted = array(); 185 186 switch ($val) { 187 case Horde_Imap_Client::SORT_SEQUENCE: 188 /* There is no requirement that IDs be returned in 189 * sequence order (see RFC 4549 [4.3.1]). So we must sort 190 * ourselves. */ 191 $sorted = array_flip($slice); 192 ksort($sorted, SORT_NUMERIC); 193 break; 194 195 case Horde_Imap_Client::SORT_SIZE: 196 foreach ($slice as $num) { 197 $sorted[$num] = $fetch_res[$num]->getSize(); 198 } 199 asort($sorted, SORT_NUMERIC); 200 break; 201 202 case Horde_Imap_Client::SORT_DISPLAYFROM: 203 case Horde_Imap_Client::SORT_DISPLAYTO: 204 $field = ($val == Horde_Imap_Client::SORT_DISPLAYFROM) 205 ? 'from' 206 : 'to'; 207 208 foreach ($slice as $num) { 209 $ob = $fetch_res[$num]->getEnvelope()->$field; 210 $sorted[$num] = ($addr_ob = $ob[0]) 211 ? $addr_ob->personal ?: $addr_ob->mailbox 212 : null; 213 } 214 215 $this->_sortString($sorted); 216 break; 217 218 case Horde_Imap_Client::SORT_CC: 219 case Horde_Imap_Client::SORT_FROM: 220 case Horde_Imap_Client::SORT_TO: 221 if ($val == Horde_Imap_Client::SORT_CC) { 222 $field = 'cc'; 223 } elseif ($val == Horde_Imap_Client::SORT_FROM) { 224 $field = 'from'; 225 } else { 226 $field = 'to'; 227 } 228 229 foreach ($slice as $num) { 230 $tmp = $fetch_res[$num]->getEnvelope()->$field; 231 $sorted[$num] = count($tmp) 232 ? $tmp[0]->mailbox 233 : null; 234 } 235 236 $this->_sortString($sorted); 237 break; 238 239 case Horde_Imap_Client::SORT_ARRIVAL: 240 $sorted = $this->_getSentDates($fetch_res, $slice, true); 241 asort($sorted, SORT_NUMERIC); 242 break; 243 244 case Horde_Imap_Client::SORT_DATE: 245 // Date sorting rules in RFC 5256 [2.2] 246 $sorted = $this->_getSentDates($fetch_res, $slice); 247 asort($sorted, SORT_NUMERIC); 248 break; 249 250 case Horde_Imap_Client::SORT_SUBJECT: 251 // Subject sorting rules in RFC 5256 [2.1] 252 foreach ($slice as $num) { 253 $sorted[$num] = strval(new Horde_Imap_Client_Data_BaseSubject($fetch_res[$num]->getEnvelope()->subject)); 254 } 255 256 $this->_sortString($sorted); 257 break; 258 } 259 260 // At this point, keys of $sorted are sequence/UID and values 261 // are the sort strings 262 if (!empty($sorted)) { 263 if ($reverse) { 264 $sorted = array_reverse($sorted, true); 265 } 266 267 if (count($sorted) === count($res)) { 268 $res = array_keys($sorted); 269 } else { 270 array_splice($res, $slice_start, count($slice), array_keys($sorted)); 271 } 272 273 // Check for ties. 274 $last = $start = null; 275 $i = 0; 276 $todo = array(); 277 278 foreach ($sorted as $k => $v) { 279 if (is_null($last) || ($last != $v)) { 280 if ($i) { 281 $todo[] = array($start, $i); 282 $i = 0; 283 } 284 $last = $v; 285 $start = $k; 286 } else { 287 ++$i; 288 } 289 } 290 if ($i) { 291 $todo[] = array($start, $i); 292 } 293 294 foreach ($todo as $v) { 295 $slices[array_search($v[0], $res)] = array_keys( 296 array_slice( 297 $sorted, 298 array_search($v[0], $sorted), 299 $v[1] + 1, 300 true 301 ) 302 ); 303 } 304 } 305 } 306 307 $reverse = false; 308 } 309 310 return $res; 311 } 312 313 /** 314 * Get the sent dates for purposes of SORT/THREAD sorting under RFC 5256 315 * [2.2]. 316 * 317 * @param Horde_Imap_Client_Fetch_Results $data Data returned from 318 * fetch() that includes 319 * both date and envelope 320 * items. 321 * @param array $ids The IDs to process. 322 * @param boolean $internal Only use internal date? 323 * 324 * @return array A mapping of IDs -> UNIX timestamps. 325 */ 326 protected function _getSentDates(Horde_Imap_Client_Fetch_Results $data, 327 $ids, $internal = false) 328 { 329 $dates = array(); 330 331 foreach ($ids as $num) { 332 $dt = ($internal || !isset($data[$num]->getEnvelope()->date)) 333 // RFC 5256 [3] & 3501 [6.4.4]: disregard timezone when 334 // using internaldate. 335 ? $data[$num]->getImapDate() 336 : $data[$num]->getEnvelope()->date; 337 $dates[$num] = $dt->format('U'); 338 } 339 340 return $dates; 341 } 342 343 /** 344 * Stable asort() function. 345 * 346 * PHP's asort() (BWT) is not a stable sort - identical values have no 347 * guarantee of key order. Use Schwartzian Transform instead. See: 348 * http://notmysock.org/blog/php/schwartzian-transform.html 349 * 350 * @param array &$a Array to sort. 351 */ 352 protected function _stableAsort(&$a) 353 { 354 array_walk($a, function(&$v, $k) { $v = array($v, $k); }); 355 asort($a); 356 array_walk($a, function(&$v, $k) { $v = $v[0]; }); 357 } 358 359 /** 360 * Sort an array of strings based on current locale. 361 * 362 * @param array &$sorted Array of strings. 363 */ 364 protected function _sortString(&$sorted) 365 { 366 if (empty($this->_collator)) { 367 asort($sorted, SORT_LOCALE_STRING); 368 } else { 369 $this->_collator->asort($sorted, Collator::SORT_STRING); 370 } 371 } 372 373 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body