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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body