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

507 lines
14 KiB
PHP

<?php
/**
* Copyright 2005-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 2005-2017 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Imap_Client
*/
/**
* A Horde_Cache implementation for caching IMAP/POP data.
* Requires the Horde_Cache package.
*
* @author Michael Slusarz <slusarz@horde.org>
* @category Horde
* @copyright 2005-2017 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Imap_Client
*/
class Horde_Imap_Client_Cache_Backend_Cache
extends Horde_Imap_Client_Cache_Backend
{
/** Cache structure version. */
const VERSION = 3;
/**
* The cache object.
*
* @var Horde_Cache
*/
protected $_cache;
/**
* The working data for the current pageload. All changes take place to
* this data.
*
* @var array
*/
protected $_data = array();
/**
* The list of cache slices loaded.
*
* @var array
*/
protected $_loaded = array();
/**
* The mapping of UIDs to slices.
*
* @var array
*/
protected $_slicemap = array();
/**
* The list of items to update:
* - add: (array) List of IDs that were added.
* - slice: (array) List of slices that were modified.
* - slicemap: (boolean) Was slicemap info changed?
*
* @var array
*/
protected $_update = array();
/**
* Constructor.
*
* @param array $params Configuration parameters:
* <pre>
* - REQUIRED Parameters:
* - cacheob: (Horde_Cache) The cache object to use.
*
* - Optional Parameters:
* - lifetime: (integer) The lifetime of the cache data (in seconds).
* DEFAULT: 1 week (604800 seconds)
* - slicesize: (integer) The slicesize to use.
* DEFAULT: 50
* </pre>
*/
public function __construct(array $params = array())
{
// Default parameters.
$params = array_merge(array(
'lifetime' => 604800,
'slicesize' => 50
), array_filter($params));
if (!isset($params['cacheob'])) {
throw new InvalidArgumentException('Missing cacheob parameter.');
}
foreach (array('lifetime', 'slicesize') as $val) {
$params[$val] = intval($params[$val]);
}
parent::__construct($params);
}
/**
* Initialization tasks.
*/
protected function _initOb()
{
$this->_cache = $this->_params['cacheob'];
register_shutdown_function(array($this, 'save'));
}
/**
* Updates the cache.
*/
public function save()
{
$lifetime = $this->_params['lifetime'];
foreach ($this->_update as $mbox => $val) {
$s = &$this->_slicemap[$mbox];
try {
if (!empty($val['add'])) {
if ($s['c'] <= $this->_params['slicesize']) {
$val['slice'][] = $s['i'];
$this->_loadSlice($mbox, $s['i']);
}
$val['slicemap'] = true;
foreach (array_keys(array_flip($val['add'])) as $uid) {
if ($s['c']++ > $this->_params['slicesize']) {
$s['c'] = 0;
$val['slice'][] = ++$s['i'];
$this->_loadSlice($mbox, $s['i']);
}
$s['s'][$uid] = $s['i'];
}
}
if (!empty($val['slice'])) {
$d = &$this->_data[$mbox];
$val['slicemap'] = true;
foreach (array_keys(array_flip($val['slice'])) as $slice) {
$data = array();
foreach (array_keys($s['s'], $slice) as $uid) {
$data[$uid] = is_array($d[$uid])
? serialize($d[$uid])
: $d[$uid];
}
$this->_cache->set($this->_getCid($mbox, $slice), serialize($data), $lifetime);
}
}
if (!empty($val['slicemap'])) {
$this->_cache->set($this->_getCid($mbox, 'slicemap'), serialize($s), $lifetime);
}
} catch (Horde_Exception $e) {
}
}
$this->_update = array();
}
/**
*/
public function get($mailbox, $uids, $fields, $uidvalid)
{
$ret = array();
$this->_loadUids($mailbox, $uids, $uidvalid);
if (empty($this->_data[$mailbox])) {
return $ret;
}
if (!empty($fields)) {
$fields = array_flip($fields);
}
$ptr = &$this->_data[$mailbox];
foreach (array_intersect($uids, array_keys($ptr)) as $val) {
if (is_string($ptr[$val])) {
try {
$ptr[$val] = @unserialize($ptr[$val]);
} catch (Exception $e) {}
}
$ret[$val] = (empty($fields) || empty($ptr[$val]))
? $ptr[$val]
: array_intersect_key($ptr[$val], $fields);
}
return $ret;
}
/**
*/
public function getCachedUids($mailbox, $uidvalid)
{
$this->_loadSliceMap($mailbox, $uidvalid);
return array_unique(array_merge(
array_keys($this->_slicemap[$mailbox]['s']),
(isset($this->_update[$mailbox]) ? $this->_update[$mailbox]['add'] : array())
));
}
/**
*/
public function set($mailbox, $data, $uidvalid)
{
$update = array_keys($data);
try {
$this->_loadUids($mailbox, $update, $uidvalid);
} catch (Horde_Imap_Client_Exception $e) {
// Ignore invalidity - just start building the new cache
}
$d = &$this->_data[$mailbox];
$s = &$this->_slicemap[$mailbox]['s'];
$add = $updated = array();
foreach ($data as $k => $v) {
if (isset($d[$k])) {
if (is_string($d[$k])) {
try {
$d[$k] = @unserialize($d[$k]);
} catch (Exception $e) {}
}
$d[$k] = is_array($d[$k])
? array_merge($d[$k], $v)
: $v;
if (isset($s[$k])) {
$updated[$s[$k]] = true;
}
} else {
$d[$k] = $v;
$add[] = $k;
}
}
$this->_toUpdate($mailbox, 'add', $add);
$this->_toUpdate($mailbox, 'slice', array_keys($updated));
}
/**
*/
public function getMetaData($mailbox, $uidvalid, $entries)
{
$this->_loadSliceMap($mailbox, $uidvalid);
return empty($entries)
? $this->_slicemap[$mailbox]['d']
: array_intersect_key($this->_slicemap[$mailbox]['d'], array_flip($entries));
}
/**
*/
public function setMetaData($mailbox, $data)
{
$this->_loadSliceMap($mailbox, isset($data['uidvalid']) ? $data['uidvalid'] : null);
$this->_slicemap[$mailbox]['d'] = array_merge($this->_slicemap[$mailbox]['d'], $data);
$this->_toUpdate($mailbox, 'slicemap', true);
}
/**
*/
public function deleteMsgs($mailbox, $uids)
{
if (empty($uids)) {
return;
}
$this->_loadSliceMap($mailbox);
$slicemap = &$this->_slicemap[$mailbox];
$deleted = array_intersect_key($slicemap['s'], array_flip($uids));
if (isset($this->_update[$mailbox])) {
$this->_update[$mailbox]['add'] = array_diff(
$this->_update[$mailbox]['add'],
$uids
);
}
if (empty($deleted)) {
return;
}
$this->_loadUids($mailbox, array_keys($deleted));
$d = &$this->_data[$mailbox];
foreach (array_keys($deleted) as $id) {
unset($d[$id], $slicemap['s'][$id]);
}
foreach (array_unique($deleted) as $slice) {
/* Get rid of slice if less than 10% of capacity. */
if (($slice != $slicemap['i']) &&
($slice_uids = array_keys($slicemap['s'], $slice)) &&
($this->_params['slicesize'] * 0.1) > count($slice_uids)) {
$this->_toUpdate($mailbox, 'add', $slice_uids);
$this->_cache->expire($this->_getCid($mailbox, $slice));
foreach ($slice_uids as $val) {
unset($slicemap['s'][$val]);
}
} else {
$this->_toUpdate($mailbox, 'slice', array($slice));
}
}
}
/**
*/
public function deleteMailbox($mailbox)
{
$this->_loadSliceMap($mailbox);
$this->_deleteMailbox($mailbox);
}
/**
*/
public function clear($lifetime)
{
$this->_cache->clear();
$this->_data = $this->_loaded = $this->_slicemap = $this->_update = array();
}
/**
* Create the unique ID used to store the data in the cache.
*
* @param string $mailbox The mailbox to cache.
* @param string $slice The cache slice.
*
* @return string The cache ID.
*/
protected function _getCid($mailbox, $slice)
{
return implode('|', array(
'horde_imap_client',
$this->_params['username'],
$mailbox,
$this->_params['hostspec'],
$this->_params['port'],
$slice,
self::VERSION
));
}
/**
* Delete a mailbox from the cache.
*
* @param string $mbox The mailbox to delete.
*/
protected function _deleteMailbox($mbox)
{
foreach (array_merge(array_keys(array_flip($this->_slicemap[$mbox]['s'])), array('slicemap')) as $slice) {
$cid = $this->_getCid($mbox, $slice);
$this->_cache->expire($cid);
unset($this->_loaded[$cid]);
}
unset(
$this->_data[$mbox],
$this->_slicemap[$mbox],
$this->_update[$mbox]
);
}
/**
* Load UIDs by regenerating from the cache.
*
* @param string $mailbox The mailbox to load.
* @param array $uids The UIDs to load.
* @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
*/
protected function _loadUids($mailbox, $uids, $uidvalid = null)
{
if (!isset($this->_data[$mailbox])) {
$this->_data[$mailbox] = array();
}
$this->_loadSliceMap($mailbox, $uidvalid);
if (!empty($uids)) {
foreach (array_unique(array_intersect_key($this->_slicemap[$mailbox]['s'], array_flip($uids))) as $slice) {
$this->_loadSlice($mailbox, $slice);
}
}
}
/**
* Load UIDs from a cache slice.
*
* @param string $mailbox The mailbox to load.
* @param integer $slice The slice to load.
*/
protected function _loadSlice($mailbox, $slice)
{
$cache_id = $this->_getCid($mailbox, $slice);
if (!empty($this->_loaded[$cache_id])) {
return;
}
if (($data = $this->_cache->get($cache_id, 0)) !== false) {
try {
$data = @unserialize($data);
} catch (Exception $e) {}
}
if (($data !== false) && is_array($data)) {
$this->_data[$mailbox] += $data;
$this->_loaded[$cache_id] = true;
} else {
$ptr = &$this->_slicemap[$mailbox];
// Slice data is corrupt; remove from slicemap.
foreach (array_keys($ptr['s'], $slice) as $val) {
unset($ptr['s'][$val]);
}
if ($slice == $ptr['i']) {
$ptr['c'] = 0;
}
}
}
/**
* Load the slicemap for a given mailbox. The slicemap contains
* the uidvalidity information, the UIDs->slice lookup table, and any
* metadata that needs to be saved for the mailbox.
*
* @param string $mailbox The mailbox.
* @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
*/
protected function _loadSliceMap($mailbox, $uidvalid = null)
{
if (!isset($this->_slicemap[$mailbox]) &&
(($data = $this->_cache->get($this->_getCid($mailbox, 'slicemap'), 0)) !== false)) {
try {
if (($slice = @unserialize($data)) &&
is_array($slice)) {
$this->_slicemap[$mailbox] = $slice;
}
} catch (Exception $e) {}
}
if (isset($this->_slicemap[$mailbox])) {
$ptr = &$this->_slicemap[$mailbox];
if (is_null($ptr['d']['uidvalid'])) {
$ptr['d']['uidvalid'] = $uidvalid;
return;
} elseif (!is_null($uidvalid) &&
($ptr['d']['uidvalid'] != $uidvalid)) {
$this->_deleteMailbox($mailbox);
} else {
return;
}
}
$this->_slicemap[$mailbox] = array(
// Tracking count for purposes of determining slices
'c' => 0,
// Metadata storage
// By default includes UIDVALIDITY of mailbox.
'd' => array('uidvalid' => $uidvalid),
// The ID of the last slice.
'i' => 0,
// The slice list.
's' => array()
);
}
/**
* Add update entry for a mailbox.
*
* @param string $mailbox The mailbox.
* @param string $type 'add', 'slice', or 'slicemap'.
* @param mixed $data The data to update.
*/
protected function _toUpdate($mailbox, $type, $data)
{
if (!isset($this->_update[$mailbox])) {
$this->_update[$mailbox] = array(
'add' => array(),
'slice' => array()
);
}
$this->_update[$mailbox][$type] = ($type == 'slicemap')
? $data
: array_merge($this->_update[$mailbox][$type], $data);
}
/* Serializable methods. */
/**
*/
public function serialize()
{
$this->save();
return parent::serialize();
}
}