Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  /**
   3   * Copyright 2013-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 2013-2017 Horde LLC
  10   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  11   * @package   Imap_Client
  12   */
  13  
  14  /**
  15   * A Horde_HashTable implementation for caching IMAP/POP data.
  16   * Requires the Horde_HashTable and Horde_Pack packages.
  17   *
  18   * @author    Michael Slusarz <slusarz@horde.org>
  19   * @category  Horde
  20   * @copyright 2013-2017 Horde LLC
  21   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  22   * @package   Imap_Client
  23   * @since     2.17.0
  24   */
  25  class Horde_Imap_Client_Cache_Backend_Hashtable
  26  extends Horde_Imap_Client_Cache_Backend
  27  {
  28      /** Separator for CID between mailbox and UID. */
  29      const CID_SEPARATOR = '|';
  30  
  31      /**
  32       * The working data for the current pageload. All changes take place to
  33       * this data.
  34       *
  35       * @var array
  36       */
  37      protected $_data = array();
  38  
  39      /**
  40       * HashTable object.
  41       *
  42       * @var Horde_HashTable
  43       */
  44      protected $_hash;
  45  
  46      /**
  47       * Mailbox level data.
  48       *
  49       * @var array
  50       */
  51      protected $_mbox = array();
  52  
  53      /**
  54       * Horde_Pack singleton object.
  55       *
  56       * @var Horde_Pack
  57       */
  58      protected $_pack;
  59  
  60      /**
  61       * List of mailbox/UIDs to update.
  62       * Keys are mailboxes. Values are arrays with three possible keys:
  63       * <pre>
  64       *   - d: UIDs to delete
  65       *   - m: Was metadata updated?
  66       *   - u: UIDs to update
  67       * </pre>
  68       *
  69       * @var array
  70       */
  71      protected $_update = array();
  72  
  73      /**
  74       * Constructor.
  75       *
  76       * @param array $params  Configuration parameters:
  77       * <pre>
  78       *   - REQUIRED parameters:
  79       *     - hashtable: (Horde_HashTable) A HashTable object.
  80       *
  81       *   - Optional Parameters:
  82       *     - lifetime: (integer) The lifetime of the cache data (in seconds).
  83       *                 DEFAULT: 604800 seconds (1 week) [@since 2.19.0]
  84       * </pre>
  85       */
  86      public function __construct(array $params = array())
  87      {
  88          if (!isset($params['hashtable'])) {
  89              throw new InvalidArgumentException('Missing hashtable parameter.');
  90          }
  91  
  92          parent::__construct(array_merge(array(
  93              'lifetime' => 604800
  94          ), $params));
  95      }
  96  
  97      /**
  98       */
  99      protected function _initOb()
 100      {
 101          $this->_hash = $this->_params['hashtable'];
 102          $this->_pack = new Horde_Pack();
 103          register_shutdown_function(array($this, 'save'));
 104      }
 105  
 106      /**
 107       */
 108      public function get($mailbox, $uids, $fields, $uidvalid)
 109      {
 110          $ret = array();
 111  
 112          if (empty($uids)) {
 113              return $ret;
 114          }
 115  
 116          $this->_loadUids($mailbox, $uids, $uidvalid);
 117  
 118          if (empty($this->_data[$mailbox])) {
 119              return $ret;
 120          }
 121  
 122          if (!empty($fields)) {
 123              $fields = array_flip($fields);
 124          }
 125          $ptr = &$this->_data[$mailbox];
 126          $to_delete = array();
 127  
 128          foreach ($uids as $val) {
 129              if (isset($ptr[$val])) {
 130                  if (is_string($ptr[$val])) {
 131                      try {
 132                          $ptr[$val] = $this->_pack->unpack($ptr[$val]);
 133                      } catch (Horde_Pack_Exception $e) {
 134                          $to_delete[] = $val;
 135                          continue;
 136                      }
 137                  }
 138  
 139                  $ret[$val] = (empty($fields) || empty($ptr[$val]))
 140                      ? $ptr[$val]
 141                      : array_intersect_key($ptr[$val], $fields);
 142              } else {
 143                  $to_delete[] = $val;
 144              }
 145          }
 146  
 147          $this->deleteMsgs($mailbox, $to_delete);
 148  
 149          return $ret;
 150      }
 151  
 152      /**
 153       */
 154      public function getCachedUids($mailbox, $uidvalid)
 155      {
 156          $this->_loadMailbox($mailbox, $uidvalid);
 157          return $this->_mbox[$mailbox]['u']->ids;
 158      }
 159  
 160      /**
 161       */
 162      public function set($mailbox, $data, $uidvalid)
 163      {
 164          $this->_loadUids($mailbox, array_keys($data), $uidvalid);
 165  
 166          $d = &$this->_data[$mailbox];
 167          $to_add = array();
 168  
 169          foreach ($data as $k => $v) {
 170              if (isset($d[$k]) && is_string($d[$k])) {
 171                  try {
 172                      $d[$k] = $this->_pack->unpack($d[$k]);
 173                  } catch (Horde_Pack_Exception $e) {
 174                      continue;
 175                  }
 176              }
 177  
 178              $d[$k] = (isset($d[$k]) && is_array($d[$k]))
 179                  ? array_merge($d[$k], $v)
 180                  : $v;
 181              $this->_update[$mailbox]['u'][$k] = true;
 182              unset($this->_update[$mailbox]['d'][$k]);
 183              $to_add[] = $k;
 184          }
 185  
 186          if (!empty($to_add)) {
 187              $this->_mbox[$mailbox]['u']->add($to_add);
 188              $this->_update[$mailbox]['m'] = true;
 189          }
 190      }
 191  
 192      /**
 193       */
 194      public function getMetaData($mailbox, $uidvalid, $entries)
 195      {
 196          $this->_loadMailbox($mailbox, $uidvalid);
 197  
 198          return empty($entries)
 199              ? $this->_mbox[$mailbox]['d']
 200              : array_intersect_key($this->_mbox[$mailbox]['d'], array_flip($entries));
 201      }
 202  
 203      /**
 204       */
 205      public function setMetaData($mailbox, $data)
 206      {
 207          $this->_loadMailbox($mailbox, isset($data['uidvalid']) ? $data['uidvalid'] : null);
 208  
 209          $this->_mbox[$mailbox]['d'] = array_merge(
 210              $this->_mbox[$mailbox]['d'],
 211              $data
 212          );
 213          $this->_update[$mailbox]['m'] = true;
 214      }
 215  
 216      /**
 217       */
 218      public function deleteMsgs($mailbox, $uids)
 219      {
 220          if (empty($uids)) {
 221              return;
 222          }
 223  
 224          $this->_loadMailbox($mailbox);
 225  
 226          foreach ($uids as $val) {
 227              unset(
 228                  $this->_data[$mailbox][$val],
 229                  $this->_update[$mailbox]['u'][$val]
 230              );
 231              $this->_update[$mailbox]['d'][$val] = true;
 232          }
 233  
 234          $this->_mbox[$mailbox]['u']->remove($uids);
 235          $this->_update[$mailbox]['m'] = true;
 236      }
 237  
 238      /**
 239       */
 240      public function deleteMailbox($mailbox)
 241      {
 242          /* Do this action immediately, instead of at shutdown. Makes coding
 243           * simpler. */
 244          $this->_loadMailbox($mailbox);
 245  
 246          $this->_hash->delete(array_merge(
 247              array($this->_getCid($mailbox)),
 248              array_values($this->_getMsgCids($mailbox, $this->_mbox[$mailbox]['u']))
 249          ));
 250  
 251          unset(
 252              $this->_data[$mailbox],
 253              $this->_mbox[$mailbox],
 254              $this->_update[$mailbox]
 255          );
 256      }
 257  
 258      /**
 259       */
 260      public function clear($lifetime)
 261      {
 262          /* Only can clear mailboxes we know about. */
 263          foreach (array_keys($this->_mbox) as $val) {
 264              $this->deleteMailbox($val);
 265          }
 266  
 267          $this->_data = $this->_mbox = $this->_update = array();
 268      }
 269  
 270      /**
 271       * Updates the cache.
 272       */
 273      public function save()
 274      {
 275          foreach ($this->_update as $mbox => $val) {
 276              try {
 277                  if (!empty($val['u'])) {
 278                       $ptr = &$this->_data[$mbox];
 279                       foreach ($this->_getMsgCids($mbox, array_keys($val['u'])) as $k2 => $v2) {
 280                           try {
 281                               $this->_hash->set(
 282                                   $v2,
 283                                   $this->_pack->pack($ptr[$k2]),
 284                                   array('expire' => $this->_params['lifetime'])
 285                               );
 286                           } catch (Horde_Pack_Exception $e) {
 287                               $this->deleteMsgs($mbox, array($v2));
 288                               $val['d'][] = $v2;
 289                           }
 290                       }
 291                   }
 292  
 293                   if (!empty($val['d'])) {
 294                       $this->_hash->delete(array_values(
 295                           $this->_getMsgCids($mbox, $val['d'])
 296                       ));
 297                   }
 298  
 299                   if (!empty($val['m'])) {
 300                       try {
 301                           $this->_hash->set(
 302                               $this->_getCid($mbox),
 303                               $this->_pack->pack($this->_mbox[$mbox]),
 304                               array('expire' => $this->_params['lifetime'])
 305                           );
 306                       } catch (Horde_Pack_Exception $e) {}
 307                   }
 308              } catch (Horde_Exception $e) {
 309              }
 310          }
 311  
 312          $this->_update = array();
 313      }
 314  
 315      /**
 316       * Loads basic mailbox information.
 317       *
 318       * @param string $mailbox    The mailbox to load.
 319       * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
 320       */
 321      protected function _loadMailbox($mailbox, $uidvalid = null)
 322      {
 323          if (!isset($this->_mbox[$mailbox]) &&
 324              ($ob = $this->_hash->get($this->_getCid($mailbox)))) {
 325              try {
 326                  $this->_mbox[$mailbox] = $this->_pack->unpack($ob);
 327              } catch (Horde_Pack_Exception $e) {}
 328          }
 329  
 330          if (isset($this->_mbox[$mailbox])) {
 331              if (is_null($uidvalid) ||
 332                  ($uidvalid == $this->_mbox[$mailbox]['d']['uidvalid'])) {
 333                  return;
 334              }
 335              $this->deleteMailbox($mailbox);
 336          }
 337  
 338          $this->_mbox[$mailbox] = array(
 339              // Metadata storage
 340              // By default includes UIDVALIDITY of mailbox.
 341              'd' => array('uidvalid' => $uidvalid),
 342              // List of UIDs
 343              'u' => new Horde_Imap_Client_Ids()
 344          );
 345      }
 346  
 347      /**
 348       * Load UIDs by regenerating from the cache.
 349       *
 350       * @param string $mailbox    The mailbox to load.
 351       * @param array $uids        The UIDs to load.
 352       * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
 353       */
 354      protected function _loadUids($mailbox, $uids, $uidvalid = null)
 355      {
 356          if (!isset($this->_data[$mailbox])) {
 357              $this->_data[$mailbox] = array();
 358          }
 359  
 360          $this->_loadMailbox($mailbox, $uidvalid);
 361  
 362          if (empty($uids)) {
 363              return;
 364          }
 365  
 366          $ptr = &$this->_data[$mailbox];
 367  
 368          $load = array_flip(
 369              array_diff_key(
 370                  $this->_getMsgCids(
 371                      $mailbox,
 372                      array_unique(array_intersect($this->_mbox[$mailbox]['u']->ids, $uids))
 373                  ),
 374                  $this->_data[$mailbox]
 375              )
 376          );
 377  
 378          foreach (array_filter($this->_hash->get(array_keys($load))) as $key => $val) {
 379              $ptr[$load[$key]] = $val;
 380          }
 381      }
 382  
 383      /**
 384       * Create the unique ID used to store the mailbox data in the cache.
 385       *
 386       * @param string $mailbox  The mailbox to cache.
 387       *
 388       * @return string  The cache ID.
 389       */
 390      protected function _getCid($mailbox)
 391      {
 392          return implode(self::CID_SEPARATOR, array(
 393              'horde_imap_client',
 394              $this->_params['username'],
 395              $mailbox,
 396              $this->_params['hostspec'],
 397              $this->_params['port']
 398          ));
 399      }
 400  
 401      /**
 402       * Return a list of cache IDs for mailbox/UID pairs.
 403       *
 404       * @param string $mailbox  The mailbox to cache.
 405       * @param array $ids       The UID list.
 406       *
 407       * @return array  List of UIDs => cache IDs.
 408       */
 409      protected function _getMsgCids($mailbox, $ids)
 410      {
 411          $cid = $this->_getCid($mailbox);
 412          $out = array();
 413  
 414          foreach ($ids as $val) {
 415              $out[$val] = $cid . self::CID_SEPARATOR . $val;
 416          }
 417  
 418          return $out;
 419      }
 420  
 421  }