PageRenderTime 43ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/horde/framework/Horde/Imap/Client/Cache/Backend/Cache.php

https://gitlab.com/JrLucena/moodle
PHP | 491 lines | 287 code | 63 blank | 141 comment | 34 complexity | 2c6af49280c0fa95145254987d6dee81 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright 2005-2014 Horde LLC (http://www.horde.org/)
  4. *
  5. * See the enclosed file COPYING 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 2005-2014 Horde LLC
  10. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  11. * @package Imap_Client
  12. */
  13. /**
  14. * A Horde_Cache implementation for caching IMAP/POP data.
  15. * Requires the Horde_Cache package.
  16. *
  17. * @author Michael Slusarz <slusarz@horde.org>
  18. * @category Horde
  19. * @copyright 2005-2014 Horde LLC
  20. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  21. * @package Imap_Client
  22. */
  23. class Horde_Imap_Client_Cache_Backend_Cache extends Horde_Imap_Client_Cache_Backend
  24. {
  25. /** Cache structure version. */
  26. const VERSION = 3;
  27. /**
  28. * The cache object.
  29. *
  30. * @var Horde_Cache
  31. */
  32. protected $_cache;
  33. /**
  34. * The working data for the current pageload. All changes take place to
  35. * this data.
  36. *
  37. * @var array
  38. */
  39. protected $_data = array();
  40. /**
  41. * The list of cache slices loaded.
  42. *
  43. * @var array
  44. */
  45. protected $_loaded = array();
  46. /**
  47. * The mapping of UIDs to slices.
  48. *
  49. * @var array
  50. */
  51. protected $_slicemap = array();
  52. /**
  53. * The list of items to update:
  54. * - add: (array) List of IDs that were added.
  55. * - slice: (array) List of slices that were modified.
  56. * - slicemap: (boolean) Was slicemap info changed?
  57. *
  58. * @var array
  59. */
  60. protected $_update = array();
  61. /**
  62. * Constructor.
  63. *
  64. * @param array $params Configuration parameters:
  65. * <pre>
  66. * - REQUIRED Parameters:
  67. * - cacheob: (Horde_Cache) The cache object to use.
  68. *
  69. * - Optional Parameters:
  70. * - lifetime: (integer) The lifetime of the cache data (in seconds).
  71. * DEFAULT: 1 week (604800 seconds)
  72. * - slicesize: (integer) The slicesize to use.
  73. * DEFAULT: 50
  74. * </pre>
  75. */
  76. public function __construct(array $params = array())
  77. {
  78. // Default parameters.
  79. $params = array_merge(array(
  80. 'lifetime' => 604800,
  81. 'slicesize' => 50
  82. ), array_filter($params));
  83. if (!isset($params['cacheob'])) {
  84. throw new InvalidArgumentException('Missing cacheob parameter.');
  85. }
  86. foreach (array('lifetime', 'slicesize') as $val) {
  87. $params[$val] = intval($params[$val]);
  88. }
  89. parent::__construct($params);
  90. }
  91. /**
  92. * Initialization tasks.
  93. */
  94. protected function _initOb()
  95. {
  96. $this->_cache = $this->_params['cacheob'];
  97. register_shutdown_function(array($this, 'save'));
  98. }
  99. /**
  100. * Updates the cache.
  101. */
  102. public function save()
  103. {
  104. $lifetime = $this->_params['lifetime'];
  105. foreach ($this->_update as $mbox => $val) {
  106. $s = &$this->_slicemap[$mbox];
  107. if (!empty($val['add'])) {
  108. if ($s['c'] <= $this->_params['slicesize']) {
  109. $val['slice'][] = $s['i'];
  110. $this->_loadSlice($mbox, $s['i']);
  111. }
  112. $val['slicemap'] = true;
  113. foreach (array_keys(array_flip($val['add'])) as $uid) {
  114. if ($s['c']++ > $this->_params['slicesize']) {
  115. $s['c'] = 0;
  116. $val['slice'][] = ++$s['i'];
  117. $this->_loadSlice($mbox, $s['i']);
  118. }
  119. $s['s'][$uid] = $s['i'];
  120. }
  121. }
  122. if (!empty($val['slice'])) {
  123. $d = &$this->_data[$mbox];
  124. $val['slicemap'] = true;
  125. foreach (array_keys(array_flip($val['slice'])) as $slice) {
  126. $data = array();
  127. foreach (array_keys($s['s'], $slice) as $uid) {
  128. $data[$uid] = is_array($d[$uid])
  129. ? serialize($d[$uid])
  130. : $d[$uid];
  131. }
  132. $this->_cache->set($this->_getCid($mbox, $slice), serialize($data), $lifetime);
  133. }
  134. }
  135. if (!empty($val['slicemap'])) {
  136. $this->_cache->set($this->_getCid($mbox, 'slicemap'), serialize($s), $lifetime);
  137. }
  138. }
  139. $this->_update = array();
  140. }
  141. /**
  142. */
  143. public function get($mailbox, $uids, $fields, $uidvalid)
  144. {
  145. $ret = array();
  146. $this->_loadUids($mailbox, $uids, $uidvalid);
  147. if (empty($this->_data[$mailbox])) {
  148. return $ret;
  149. }
  150. if (!empty($fields)) {
  151. $fields = array_flip($fields);
  152. }
  153. $ptr = &$this->_data[$mailbox];
  154. foreach (array_intersect($uids, array_keys($ptr)) as $val) {
  155. if (is_string($ptr[$val])) {
  156. $ptr[$val] = @unserialize($ptr[$val]);
  157. }
  158. $ret[$val] = (empty($fields) || empty($ptr[$val]))
  159. ? $ptr[$val]
  160. : array_intersect_key($ptr[$val], $fields);
  161. }
  162. return $ret;
  163. }
  164. /**
  165. */
  166. public function getCachedUids($mailbox, $uidvalid)
  167. {
  168. $this->_loadSliceMap($mailbox, $uidvalid);
  169. return array_unique(array_merge(
  170. array_keys($this->_slicemap[$mailbox]['s']),
  171. (isset($this->_update[$mailbox]) ? $this->_update[$mailbox]['add'] : array())
  172. ));
  173. }
  174. /**
  175. */
  176. public function set($mailbox, $data, $uidvalid)
  177. {
  178. $update = array_keys($data);
  179. try {
  180. $this->_loadUids($mailbox, $update, $uidvalid);
  181. } catch (Horde_Imap_Client_Exception $e) {
  182. // Ignore invalidity - just start building the new cache
  183. }
  184. $d = &$this->_data[$mailbox];
  185. $s = &$this->_slicemap[$mailbox]['s'];
  186. $add = $updated = array();
  187. foreach ($data as $k => $v) {
  188. if (isset($d[$k])) {
  189. if (is_string($d[$k])) {
  190. $d[$k] = @unserialize($d[$k]);
  191. }
  192. $d[$k] = is_array($d[$k])
  193. ? array_merge($d[$k], $v)
  194. : $v;
  195. if (isset($s[$k])) {
  196. $updated[$s[$k]] = true;
  197. }
  198. } else {
  199. $d[$k] = $v;
  200. $add[] = $k;
  201. }
  202. }
  203. $this->_toUpdate($mailbox, 'add', $add);
  204. $this->_toUpdate($mailbox, 'slice', array_keys($updated));
  205. }
  206. /**
  207. */
  208. public function getMetaData($mailbox, $uidvalid, $entries)
  209. {
  210. $this->_loadSliceMap($mailbox, $uidvalid);
  211. return empty($entries)
  212. ? $this->_slicemap[$mailbox]['d']
  213. : array_intersect_key($this->_slicemap[$mailbox]['d'], array_flip($entries));
  214. }
  215. /**
  216. */
  217. public function setMetaData($mailbox, $data)
  218. {
  219. $this->_loadSliceMap($mailbox, isset($data['uidvalid']) ? $data['uidvalid'] : null);
  220. $this->_slicemap[$mailbox]['d'] = array_merge($this->_slicemap[$mailbox]['d'], $data);
  221. $this->_toUpdate($mailbox, 'slicemap', true);
  222. }
  223. /**
  224. */
  225. public function deleteMsgs($mailbox, $uids)
  226. {
  227. if (empty($uids)) {
  228. return;
  229. }
  230. $this->_loadSliceMap($mailbox);
  231. $slicemap = &$this->_slicemap[$mailbox];
  232. $deleted = array_intersect_key($slicemap['s'], array_flip($uids));
  233. if (isset($this->_update[$mailbox])) {
  234. $this->_update[$mailbox]['add'] = array_diff(
  235. $this->_update[$mailbox]['add'],
  236. $uids
  237. );
  238. }
  239. if (empty($deleted)) {
  240. return;
  241. }
  242. $this->_loadUids($mailbox, array_keys($deleted));
  243. $d = &$this->_data[$mailbox];
  244. foreach (array_keys($deleted) as $id) {
  245. unset($d[$id], $slicemap['s'][$id]);
  246. }
  247. foreach (array_unique($deleted) as $slice) {
  248. /* Get rid of slice if less than 10% of capacity. */
  249. if (($slice != $slicemap['i']) &&
  250. ($slice_uids = array_keys($slicemap['s'], $slice)) &&
  251. ($this->_params['slicesize'] * 0.1) > count($slice_uids)) {
  252. $this->_toUpdate($mailbox, 'add', $slice_uids);
  253. $this->_cache->expire($this->_getCid($mailbox, $slice));
  254. foreach ($slice_uids as $val) {
  255. unset($slicemap['s'][$val]);
  256. }
  257. } else {
  258. $this->_toUpdate($mailbox, 'slice', array($slice));
  259. }
  260. }
  261. }
  262. /**
  263. */
  264. public function deleteMailbox($mailbox)
  265. {
  266. $this->_loadSliceMap($mailbox);
  267. $this->_deleteMailbox($mailbox);
  268. }
  269. /**
  270. */
  271. public function clear($lifetime)
  272. {
  273. $this->_cache->clear();
  274. $this->_data = $this->_loaded = $this->_slicemap = $this->_update = array();
  275. }
  276. /**
  277. * Create the unique ID used to store the data in the cache.
  278. *
  279. * @param string $mailbox The mailbox to cache.
  280. * @param string $slice The cache slice.
  281. *
  282. * @return string The cache ID.
  283. */
  284. protected function _getCid($mailbox, $slice)
  285. {
  286. return implode('|', array(
  287. 'horde_imap_client',
  288. $this->_params['username'],
  289. $mailbox,
  290. $this->_params['hostspec'],
  291. $this->_params['port'],
  292. $slice,
  293. self::VERSION
  294. ));
  295. }
  296. /**
  297. * Delete a mailbox from the cache.
  298. *
  299. * @param string $mbox The mailbox to delete.
  300. */
  301. protected function _deleteMailbox($mbox)
  302. {
  303. foreach (array_merge(array_keys(array_flip($this->_slicemap[$mbox]['s'])), array('slicemap')) as $slice) {
  304. $cid = $this->_getCid($mbox, $slice);
  305. $this->_cache->expire($cid);
  306. unset($this->_loaded[$cid]);
  307. }
  308. unset(
  309. $this->_data[$mbox],
  310. $this->_slicemap[$mbox],
  311. $this->_update[$mbox]
  312. );
  313. }
  314. /**
  315. * Load UIDs by regenerating from the cache.
  316. *
  317. * @param string $mailbox The mailbox to load.
  318. * @param array $uids The UIDs to load.
  319. * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
  320. */
  321. protected function _loadUids($mailbox, $uids, $uidvalid = null)
  322. {
  323. if (!isset($this->_data[$mailbox])) {
  324. $this->_data[$mailbox] = array();
  325. }
  326. $this->_loadSliceMap($mailbox, $uidvalid);
  327. if (!empty($uids)) {
  328. foreach (array_unique(array_intersect_key($this->_slicemap[$mailbox]['s'], array_flip($uids))) as $slice) {
  329. $this->_loadSlice($mailbox, $slice);
  330. }
  331. }
  332. }
  333. /**
  334. * Load UIDs from a cache slice.
  335. *
  336. * @param string $mailbox The mailbox to load.
  337. * @param integer $slice The slice to load.
  338. */
  339. protected function _loadSlice($mailbox, $slice)
  340. {
  341. $cache_id = $this->_getCid($mailbox, $slice);
  342. if (!empty($this->_loaded[$cache_id])) {
  343. return;
  344. }
  345. if ((($data = $this->_cache->get($cache_id, 0)) !== false) &&
  346. ($data = @unserialize($data)) &&
  347. is_array($data)) {
  348. $this->_data[$mailbox] += $data;
  349. $this->_loaded[$cache_id] = true;
  350. } else {
  351. $ptr = &$this->_slicemap[$mailbox];
  352. // Slice data is corrupt; remove from slicemap.
  353. foreach (array_keys($ptr['s'], $slice) as $val) {
  354. unset($ptr['s'][$val]);
  355. }
  356. if ($slice == $ptr['i']) {
  357. $ptr['c'] = 0;
  358. }
  359. }
  360. }
  361. /**
  362. * Load the slicemap for a given mailbox. The slicemap contains
  363. * the uidvalidity information, the UIDs->slice lookup table, and any
  364. * metadata that needs to be saved for the mailbox.
  365. *
  366. * @param string $mailbox The mailbox.
  367. * @param integer $uidvalid The IMAP uidvalidity value of the mailbox.
  368. */
  369. protected function _loadSliceMap($mailbox, $uidvalid = null)
  370. {
  371. if (!isset($this->_slicemap[$mailbox]) &&
  372. (($data = $this->_cache->get($this->_getCid($mailbox, 'slicemap'), 0)) !== false) &&
  373. ($slice = @unserialize($data)) &&
  374. is_array($slice)) {
  375. $this->_slicemap[$mailbox] = $slice;
  376. }
  377. if (isset($this->_slicemap[$mailbox])) {
  378. $ptr = &$this->_slicemap[$mailbox];
  379. if (is_null($ptr['d']['uidvalid'])) {
  380. $ptr['d']['uidvalid'] = $uidvalid;
  381. return;
  382. } elseif (!is_null($uidvalid) &&
  383. ($ptr['d']['uidvalid'] != $uidvalid)) {
  384. $this->_deleteMailbox($mailbox);
  385. } else {
  386. return;
  387. }
  388. }
  389. $this->_slicemap[$mailbox] = array(
  390. // Tracking count for purposes of determining slices
  391. 'c' => 0,
  392. // Metadata storage
  393. // By default includes UIDVALIDITY of mailbox.
  394. 'd' => array('uidvalid' => $uidvalid),
  395. // The ID of the last slice.
  396. 'i' => 0,
  397. // The slice list.
  398. 's' => array()
  399. );
  400. }
  401. /**
  402. * Add update entry for a mailbox.
  403. *
  404. * @param string $mailbox The mailbox.
  405. * @param string $type 'add', 'slice', or 'slicemap'.
  406. * @param mixed $data The data to update.
  407. */
  408. protected function _toUpdate($mailbox, $type, $data)
  409. {
  410. if (!isset($this->_update[$mailbox])) {
  411. $this->_update[$mailbox] = array(
  412. 'add' => array(),
  413. 'slice' => array()
  414. );
  415. }
  416. $this->_update[$mailbox][$type] = ($type == 'slicemap')
  417. ? $data
  418. : array_merge($this->_update[$mailbox][$type], $data);
  419. }
  420. /* Serializable methods. */
  421. /**
  422. */
  423. public function serialize()
  424. {
  425. $this->save();
  426. return parent::serialize();
  427. }
  428. }