Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
<?php
/**
 * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file LICENSE 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($mbox['mailbox'], $query, array(
> $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); } } }