LDAPAccountManager/lam/lib/3rdParty/composer/pear-pear.horde.org/Horde_Imap_Client/Horde/Imap/Client/Socket/ClientSort.php

374 lines
12 KiB
PHP

<?php
/**
* Copyright 2012-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @copyright 2012-2017 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Imap_Client
*/
/**
* Client sorting methods for the Socket driver.
*
* NOTE: This class is NOT intended to be accessed outside of a Base object.
* There is NO guarantees that the API of this class will not change across
* versions.
*
* @author Michael Slusarz <slusarz@horde.org>
* @category Horde
* @copyright 2012-2017 Horde LLC
* @internal
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Imap_Client
*/
class Horde_Imap_Client_Socket_ClientSort
{
/**
* Collator object to use for sotring.
*
* @var Collator
*/
protected $_collator;
/**
* Socket object.
*
* @var Horde_Imap_Client_Socket
*/
protected $_socket;
/**
* Constructor.
*
* @param Horde_Imap_Client_Socket $socket Socket object.
*/
public function __construct(Horde_Imap_Client_Socket $socket)
{
$this->_socket = $socket;
if (class_exists('Collator')) {
$this->_collator = new Collator(null);
}
}
/**
* Sort search results client side if the server does not support the SORT
* IMAP extension (RFC 5256).
*
* @param Horde_Imap_Client_Ids $res The search results.
* @param array $opts The options to _search().
*
* @return array The sort results.
*
* @throws Horde_Imap_Client_Exception
*/
public function clientSort($res, $opts)
{
if (!count($res)) {
return $res;
}
/* Generate the FETCH command needed. */
$query = new Horde_Imap_Client_Fetch_Query();
foreach ($opts['sort'] as $val) {
switch ($val) {
case Horde_Imap_Client::SORT_ARRIVAL:
$query->imapDate();
break;
case Horde_Imap_Client::SORT_DATE:
$query->imapDate();
$query->envelope();
break;
case Horde_Imap_Client::SORT_CC:
case Horde_Imap_Client::SORT_DISPLAYFROM:
case Horde_Imap_Client::SORT_DISPLAYTO:
case Horde_Imap_Client::SORT_FROM:
case Horde_Imap_Client::SORT_SUBJECT:
case Horde_Imap_Client::SORT_TO:
$query->envelope();
break;
case Horde_Imap_Client::SORT_SEQUENCE:
$query->seq();
break;
case Horde_Imap_Client::SORT_SIZE:
$query->size();
break;
}
}
if (!count($query)) {
return $res;
}
$mbox = $this->_socket->currentMailbox();
$fetch_res = $this->_socket->fetch(isset($mbox['mailbox']) ? $mbox['mailbox'] : null, $query, array(
'ids' => $res
));
return $this->_clientSortProcess($res->ids, $fetch_res, $opts['sort']);
}
/**
* If server does not support the THREAD IMAP extension (RFC 5256), do
* ORDEREDSUBJECT threading on the client side.
*
* @param Horde_Imap_Client_Fetch_Results $data Fetch results.
* @param boolean $uids Are IDs UIDs?
*
* @return array The thread sort results.
*/
public function threadOrderedSubject(Horde_Imap_Client_Fetch_Results $data,
$uids)
{
$dates = $this->_getSentDates($data, $data->ids());
$out = $sorted = $tsort = array();
foreach ($data as $k => $v) {
$subject = strval(new Horde_Imap_Client_Data_BaseSubject($v->getEnvelope()->subject));
$sorted[$subject][$k] = $dates[$k];
}
/* Step 1: Sort by base subject (already done).
* Step 2: Sort by sent date within each thread. */
foreach (array_keys($sorted) as $key) {
$this->_stableAsort($sorted[$key]);
$tsort[$key] = reset($sorted[$key]);
}
/* Step 3: Sort by the sent date of the first message in the
* thread. */
$this->_stableAsort($tsort);
/* Now, $tsort contains the order of the threads, and each thread
* is sorted in $sorted. */
foreach (array_keys($tsort) as $key) {
$keys = array_keys($sorted[$key]);
$out[$keys[0]] = array(
$keys[0] => 0
) + array_fill_keys(array_slice($keys, 1) , 1);
}
return new Horde_Imap_Client_Data_Thread(
$out,
$uids ? 'uid' : 'sequence'
);
}
/**
*/
protected function _clientSortProcess($res, $fetch_res, $sort)
{
/* The initial sort is on the entire set. */
$slices = array(0 => $res);
$reverse = false;
foreach ($sort as $val) {
if ($val == Horde_Imap_Client::SORT_REVERSE) {
$reverse = true;
continue;
}
$slices_list = $slices;
$slices = array();
foreach ($slices_list as $slice_start => $slice) {
$sorted = array();
switch ($val) {
case Horde_Imap_Client::SORT_SEQUENCE:
/* There is no requirement that IDs be returned in
* sequence order (see RFC 4549 [4.3.1]). So we must sort
* ourselves. */
$sorted = array_flip($slice);
ksort($sorted, SORT_NUMERIC);
break;
case Horde_Imap_Client::SORT_SIZE:
foreach ($slice as $num) {
$sorted[$num] = $fetch_res[$num]->getSize();
}
asort($sorted, SORT_NUMERIC);
break;
case Horde_Imap_Client::SORT_DISPLAYFROM:
case Horde_Imap_Client::SORT_DISPLAYTO:
$field = ($val == Horde_Imap_Client::SORT_DISPLAYFROM)
? 'from'
: 'to';
foreach ($slice as $num) {
$ob = $fetch_res[$num]->getEnvelope()->$field;
$sorted[$num] = ($addr_ob = $ob[0])
? $addr_ob->personal ?: $addr_ob->mailbox
: null;
}
$this->_sortString($sorted);
break;
case Horde_Imap_Client::SORT_CC:
case Horde_Imap_Client::SORT_FROM:
case Horde_Imap_Client::SORT_TO:
if ($val == Horde_Imap_Client::SORT_CC) {
$field = 'cc';
} elseif ($val == Horde_Imap_Client::SORT_FROM) {
$field = 'from';
} else {
$field = 'to';
}
foreach ($slice as $num) {
$tmp = $fetch_res[$num]->getEnvelope()->$field;
$sorted[$num] = count($tmp)
? $tmp[0]->mailbox
: null;
}
$this->_sortString($sorted);
break;
case Horde_Imap_Client::SORT_ARRIVAL:
$sorted = $this->_getSentDates($fetch_res, $slice, true);
asort($sorted, SORT_NUMERIC);
break;
case Horde_Imap_Client::SORT_DATE:
// Date sorting rules in RFC 5256 [2.2]
$sorted = $this->_getSentDates($fetch_res, $slice);
asort($sorted, SORT_NUMERIC);
break;
case Horde_Imap_Client::SORT_SUBJECT:
// Subject sorting rules in RFC 5256 [2.1]
foreach ($slice as $num) {
$sorted[$num] = strval(new Horde_Imap_Client_Data_BaseSubject($fetch_res[$num]->getEnvelope()->subject));
}
$this->_sortString($sorted);
break;
}
// At this point, keys of $sorted are sequence/UID and values
// are the sort strings
if (!empty($sorted)) {
if ($reverse) {
$sorted = array_reverse($sorted, true);
}
if (count($sorted) === count($res)) {
$res = array_keys($sorted);
} else {
array_splice($res, $slice_start, count($slice), array_keys($sorted));
}
// Check for ties.
$last = $start = null;
$i = 0;
$todo = array();
foreach ($sorted as $k => $v) {
if (is_null($last) || ($last != $v)) {
if ($i) {
$todo[] = array($start, $i);
$i = 0;
}
$last = $v;
$start = $k;
} else {
++$i;
}
}
if ($i) {
$todo[] = array($start, $i);
}
foreach ($todo as $v) {
$slices[array_search($v[0], $res)] = array_keys(
array_slice(
$sorted,
array_search($v[0], $sorted),
$v[1] + 1,
true
)
);
}
}
}
$reverse = false;
}
return $res;
}
/**
* Get the sent dates for purposes of SORT/THREAD sorting under RFC 5256
* [2.2].
*
* @param Horde_Imap_Client_Fetch_Results $data Data returned from
* fetch() that includes
* both date and envelope
* items.
* @param array $ids The IDs to process.
* @param boolean $internal Only use internal date?
*
* @return array A mapping of IDs -> UNIX timestamps.
*/
protected function _getSentDates(Horde_Imap_Client_Fetch_Results $data,
$ids, $internal = false)
{
$dates = array();
foreach ($ids as $num) {
$dt = ($internal || !isset($data[$num]->getEnvelope()->date))
// RFC 5256 [3] & 3501 [6.4.4]: disregard timezone when
// using internaldate.
? $data[$num]->getImapDate()
: $data[$num]->getEnvelope()->date;
$dates[$num] = $dt->format('U');
}
return $dates;
}
/**
* Stable asort() function.
*
* PHP's asort() (BWT) is not a stable sort - identical values have no
* guarantee of key order. Use Schwartzian Transform instead. See:
* http://notmysock.org/blog/php/schwartzian-transform.html
*
* @param array &$a Array to sort.
*/
protected function _stableAsort(&$a)
{
array_walk($a, function(&$v, $k) { $v = array($v, $k); });
asort($a);
array_walk($a, function(&$v, $k) { $v = $v[0]; });
}
/**
* Sort an array of strings based on current locale.
*
* @param array &$sorted Array of strings.
*/
protected function _sortString(&$sorted)
{
if (empty($this->_collator)) {
asort($sorted, SORT_LOCALE_STRING);
} else {
$this->_collator->asort($sorted, Collator::SORT_STRING);
}
}
}