Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.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 SQL database implementation for caching IMAP/POP data.
  16   * Requires the Horde_Db package.
  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   */
  24  class Horde_Imap_Client_Cache_Backend_Db
  25  extends Horde_Imap_Client_Cache_Backend
  26  {
  27      /** SQL table names. */
  28      const BASE_TABLE = 'horde_imap_client_data';
  29      const MD_TABLE = 'horde_imap_client_metadata';
  30      const MSG_TABLE = 'horde_imap_client_message';
  31  
  32      /**
  33       * Handle for the database connection.
  34       *
  35       * @var Horde_Db_Adapter
  36       */
  37      protected $_db;
  38  
  39      /**
  40       * Constructor.
  41       *
  42       * @param array $params  Configuration parameters:
  43       * <pre>
  44       *   - REQUIRED Parameters:
  45       *     - db: (Horde_Db_Adapter) DB object.
  46       * </pre>
  47       */
  48      public function __construct(array $params = array())
  49      {
  50          if (!isset($params['db'])) {
  51              throw new InvalidArgumentException('Missing db parameter.');
  52          }
  53  
  54          parent::__construct($params);
  55      }
  56  
  57      /**
  58       */
  59      protected function _initOb()
  60      {
  61          $this->_db = $this->_params['db'];
  62      }
  63  
  64      /**
  65       */
  66      public function get($mailbox, $uids, $fields, $uidvalid)
  67      {
  68          $this->getMetaData($mailbox, $uidvalid, array('uidvalid'));
  69  
  70          $query = $this->_baseSql($mailbox, self::MSG_TABLE);
  71          $query[0] = 'SELECT t.data, t.msguid ' . $query[0];
  72  
  73          $uid_query = array();
  74          foreach ($uids as $val) {
  75              $uid_query[] = 't.msguid = ?';
  76              $query[1][] = strval($val);
  77          }
  78          $query[0] .= ' AND (' . implode(' OR ', $uid_query) . ')';
  79  
  80          $compress = new Horde_Compress_Fast();
  81          $out = array();
  82  
  83          try {
  84              $columns = $this->_db->columns(self::MSG_TABLE);
  85              $res = $this->_db->select($query[0], $query[1]);
  86  
  87              foreach ($res as $row) {
  88                  try {
  89                      $out[$row['msguid']] = @unserialize($compress->decompress(
  90                          $columns['data']->binaryToString($row['data'])
  91                      ));
  92                  } catch (Exception $e) {}
  93              }
  94          } catch (Horde_Db_Exception $e) {}
  95  
  96          return $out;
  97      }
  98  
  99      /**
 100       */
 101      public function getCachedUids($mailbox, $uidvalid)
 102      {
 103          $this->getMetaData($mailbox, $uidvalid, array('uidvalid'));
 104  
 105          $query = $this->_baseSql($mailbox, self::MSG_TABLE);
 106          $query[0] = 'SELECT DISTINCT t.msguid ' . $query[0];
 107  
 108          try {
 109              return $this->_db->selectValues($query[0], $query[1]);
 110          } catch (Horde_Db_Exception $e) {
 111              return array();
 112          }
 113      }
 114  
 115      /**
 116       */
 117      public function set($mailbox, $data, $uidvalid)
 118      {
 119          if ($uid = $this->_getUid($mailbox)) {
 120              $res = $this->get($mailbox, array_keys($data), array(), $uidvalid);
 121          } else {
 122              $res = array();
 123              $uid = $this->_createUid($mailbox);
 124          }
 125  
 126          $compress = new Horde_Compress_Fast();
 127  
 128          foreach ($data as $key => $val) {
 129              if (isset($res[$key])) {
 130                  try {
 131                      /* Update */
 132                      $this->_db->updateBlob(
 133                          self::MSG_TABLE,
 134                          array('data' => new Horde_Db_Value_Binary($compress->compress(serialize(array_merge($res[$key], $val))))),
 135                          array(
 136                              'messageid = ? AND msguid = ?',
 137                              array($uid, strval($key))
 138                          )
 139                      );
 140                  } catch (Horde_Db_Exception $e) {}
 141              } else {
 142                  /* Insert */
 143                  try {
 144                      $this->_db->insertBlob(
 145                          self::MSG_TABLE,
 146                          array(
 147                              'data' => new Horde_Db_Value_Binary($compress->compress(serialize($val))),
 148                              'msguid' => strval($key),
 149                              'messageid' => $uid
 150                          )
 151                      );
 152                  } catch (Horde_Db_Exception $e) {}
 153              }
 154          }
 155  
 156          /* Update modified time. */
 157          try {
 158              $this->_db->update(
 159                  sprintf(
 160                      'UPDATE %s SET modified = ? WHERE messageid = ?',
 161                      self::BASE_TABLE
 162                  ),
 163                  array(time(), $uid)
 164              );
 165          } catch (Horde_Db_Exception $e) {}
 166  
 167          /* Update uidvalidity. */
 168          $this->setMetaData($mailbox, array('uidvalid' => $uidvalid));
 169      }
 170  
 171      /**
 172       */
 173      public function getMetaData($mailbox, $uidvalid, $entries)
 174      {
 175          $query = $this->_baseSql($mailbox, self::MD_TABLE);
 176          $query[0] = 'SELECT t.field, t.data ' . $query[0];
 177  
 178          if (!empty($entries)) {
 179              $entries[] = 'uidvalid';
 180              $entry_query = array();
 181  
 182              foreach (array_unique($entries) as $val) {
 183                  $entry_query[] = 't.field = ?';
 184                  $query[1][] = $val;
 185              }
 186              $query[0] .= ' AND (' . implode(' OR ', $entry_query) . ')';
 187          }
 188  
 189          try {
 190              if ($res = $this->_db->selectAssoc($query[0], $query[1])) {
 191                  $columns = $this->_db->columns(self::MD_TABLE);
 192                  foreach ($res as $key => $val) {
 193                      switch ($key) {
 194                      case 'uidvalid':
 195                          $res[$key] = $columns['data']->binaryToString($val);
 196                          break;
 197  
 198                      default:
 199                          try {
 200                              $res[$key] = @unserialize(
 201                                  $columns['data']->binaryToString($val)
 202                              );
 203                          } catch (Exception $e) {}
 204                          break;
 205                      }
 206                  }
 207  
 208                  if (is_null($uidvalid) ||
 209                      !isset($res['uidvalid']) ||
 210                      ($res['uidvalid'] == $uidvalid)) {
 211                      return $res;
 212                  }
 213  
 214                  $this->deleteMailbox($mailbox);
 215              }
 216          } catch (Horde_Db_Exception $e) {}
 217  
 218          return array();
 219      }
 220  
 221      /**
 222       */
 223      public function setMetaData($mailbox, $data)
 224      {
 225          if (!($uid = $this->_getUid($mailbox))) {
 226              $uid = $this->_createUid($mailbox);
 227          }
 228  
 229          $query = sprintf('SELECT field FROM %s where messageid = ?', self::MD_TABLE);
 230          $values = array($uid);
 231  
 232          try {
 233              $fields = $this->_db->selectValues($query, $values);
 234          } catch (Horde_Db_Exception $e) {
 235              return;
 236          }
 237  
 238          foreach ($data as $key => $val) {
 239              $val = new Horde_Db_Value_Binary(($key == 'uidvalid') ? $val : serialize($val));
 240  
 241              if (in_array($key, $fields)) {
 242                  /* Update */
 243                  try {
 244                      $this->_db->updateBlob(
 245                          self::MD_TABLE,
 246                          array('data' => $val),
 247                          array('field = ? AND messageid = ?', array($key, $uid))
 248                      );
 249                  } catch (Horde_Db_Exception $e) {}
 250              } else {
 251                  /* Insert */
 252                  try {
 253                      $this->_db->insertBlob(
 254                          self::MD_TABLE,
 255                          array('data' => $val, 'field' => $key, 'messageid' => $uid)
 256                      );
 257                  } catch (Horde_Db_Exception $e) {}
 258              }
 259          }
 260      }
 261  
 262      /**
 263       */
 264      public function deleteMsgs($mailbox, $uids)
 265      {
 266          if (empty($uids)) {
 267              return;
 268          }
 269  
 270          $query = $this->_baseSql($mailbox);
 271          $query[0] = sprintf(
 272              'DELETE FROM %s WHERE messageid IN (SELECT messageid ' . $query[0] . ')',
 273              self::MSG_TABLE
 274          );
 275  
 276          $uid_query = array();
 277          foreach ($uids as $val) {
 278              $uid_query[] = 'msguid = ?';
 279              $query[1][] = strval($val);
 280          }
 281          $query[0] .= ' AND (' . implode(' OR ', $uid_query) . ')';
 282  
 283          try {
 284              $this->_db->delete($query[0], $query[1]);
 285          } catch (Horde_Db_Exception $e) {}
 286      }
 287  
 288      /**
 289       */
 290      public function deleteMailbox($mailbox)
 291      {
 292          if (is_null($uid = $this->_getUid($mailbox))) {
 293              return;
 294          }
 295  
 296          foreach (array(self::BASE_TABLE, self::MD_TABLE, self::MSG_TABLE) as $val) {
 297              try {
 298                  $this->_db->delete(
 299                      sprintf('DELETE FROM %s WHERE messageid = ?', $val),
 300                      array($uid)
 301                  );
 302              } catch (Horde_Db_Exception $e) {}
 303          }
 304      }
 305  
 306      /**
 307       */
 308      public function clear($lifetime)
 309      {
 310          if (is_null($lifetime)) {
 311              try {
 312                  $this->_db->delete(sprintf('DELETE FROM %s', self::BASE_TABLE));
 313                  $this->_db->delete(sprintf('DELETE FROM %s', self::MD_TABLE));
 314                  $this->_db->delete(sprintf('DELETE FROM %s', self::MSG_TABLE));
 315              } catch (Horde_Db_Exception $e) {}
 316              return;
 317          }
 318  
 319          $purge = time() - $lifetime;
 320          $sql = 'DELETE FROM %s WHERE messageid IN (SELECT messageid FROM %s WHERE modified < ?)';
 321  
 322          foreach (array(self::MD_TABLE, self::MSG_TABLE) as $val) {
 323              try {
 324                  $this->_db->delete(
 325                      sprintf($sql, $val, self::BASE_TABLE),
 326                      array($purge)
 327                  );
 328              } catch (Horde_Db_Exception $e) {
 329              }
 330          }
 331  
 332          try {
 333              $this->_db->delete(
 334                  sprintf('DELETE FROM %s WHERE modified < ?', self::BASE_TABLE),
 335                  array($purge)
 336              );
 337          } catch (Horde_Db_Exception $e) {
 338          }
 339      }
 340  
 341      /**
 342       * Prepare the base SQL query.
 343       *
 344       * @param string $mailbox  The mailbox.
 345       * @param string $join     The table to join with the base table.
 346       *
 347       * @return array  SQL query and bound parameters.
 348       */
 349      protected function _baseSql($mailbox, $join = null)
 350      {
 351          $sql = sprintf('FROM %s d', self::BASE_TABLE);
 352  
 353          if (!is_null($join)) {
 354              $sql .= sprintf(' INNER JOIN %s t ON d.messageid = t.messageid', $join);
 355          }
 356  
 357          return array(
 358              $sql . ' WHERE d.hostspec = ? AND d.port = ? AND d.username = ? AND d.mailbox = ?',
 359              array(
 360                  $this->_params['hostspec'],
 361                  $this->_params['port'],
 362                  $this->_params['username'],
 363                  $mailbox
 364              )
 365          );
 366      }
 367  
 368      /**
 369       * @param string $mailbox
 370       *
 371       * @return string  UID from base table.
 372       */
 373      protected function _getUid($mailbox)
 374      {
 375          $query = $this->_baseSql($mailbox);
 376          $query[0] = 'SELECT d.messageid ' . $query[0];
 377  
 378          try {
 379              return $this->_db->selectValue($query[0], $query[1]);
 380          } catch (Horde_Db_Exception $e) {
 381              return null;
 382          }
 383      }
 384  
 385      /**
 386       * @param string $mailbox
 387       *
 388       * @return string  UID from base table.
 389       */
 390      protected function _createUid($mailbox)
 391      {
 392          return $this->_db->insert(
 393              sprintf(
 394                  'INSERT INTO %s (hostspec, mailbox, port, username) ' .
 395                      'VALUES (?, ?, ?, ?)',
 396                  self::BASE_TABLE
 397              ),
 398              array(
 399                  $this->_params['hostspec'],
 400                  $mailbox,
 401                  $this->_params['port'],
 402                  $this->_params['username']
 403              )
 404          );
 405      }
 406  
 407  }