Overview

Packages

  • Imap
    • Client

Classes

  • Horde_Imap_Client
  • Horde_Imap_Client_Auth_DigestMD5
  • Horde_Imap_Client_Base
  • Horde_Imap_Client_Cache
  • Horde_Imap_Client_Data_Acl
  • Horde_Imap_Client_Data_AclCommon
  • Horde_Imap_Client_Data_AclNegative
  • Horde_Imap_Client_Data_AclRights
  • Horde_Imap_Client_Data_Envelope
  • Horde_Imap_Client_Data_Fetch
  • Horde_Imap_Client_Data_Fetch_Pop3
  • Horde_Imap_Client_Data_Thread
  • Horde_Imap_Client_DateTime
  • Horde_Imap_Client_Exception
  • Horde_Imap_Client_Exception_NoSupportExtension
  • Horde_Imap_Client_Fetch_Query
  • Horde_Imap_Client_Ids
  • Horde_Imap_Client_Ids_Pop3
  • Horde_Imap_Client_Mailbox
  • Horde_Imap_Client_Search_Query
  • Horde_Imap_Client_Socket
  • Horde_Imap_Client_Socket_Pop3
  • Horde_Imap_Client_Sort
  • Horde_Imap_Client_Translation
  • Horde_Imap_Client_Utf7imap
  • Horde_Imap_Client_Utils
  • Horde_Imap_Client_Utils_Pop3
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * An interface to cache various data retrieved from the IMAP server.
  4:  * Requires the Horde_Cache package.
  5:  *
  6:  * Copyright 2005-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * See the enclosed file COPYING for license information (LGPL). If you
  9:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 10:  *
 11:  * @author   Michael Slusarz <slusarz@horde.org>
 12:  * @category Horde
 13:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 14:  * @package  Imap_Client
 15:  */
 16: class Horde_Imap_Client_Cache
 17: {
 18:     /** Cache structure version. */
 19:     const VERSION = 2;
 20: 
 21:     /**
 22:      * Singleton instances.
 23:      *
 24:      * @deprecated
 25:      *
 26:      * @var array
 27:      */
 28:     static protected $_instances = array();
 29: 
 30:     /**
 31:      * The base driver object.
 32:      *
 33:      * @var Horde_Imap_Client_Base
 34:      */
 35:     protected $_base;
 36: 
 37:     /**
 38:      * The cache object.
 39:      *
 40:      * @var Horde_Cache
 41:      */
 42:     protected $_cache;
 43: 
 44:     /**
 45:      * The working data for the current pageload.  All changes take place to
 46:      * this data.
 47:      *
 48:      * @var array
 49:      */
 50:     protected $_data = array();
 51: 
 52:     /**
 53:      * The list of cache slices loaded.
 54:      *
 55:      * @var array
 56:      */
 57:     protected $_loaded = array();
 58: 
 59:     /**
 60:      * The configuration params.
 61:      *
 62:      * @var array
 63:      */
 64:     protected $_params = array();
 65: 
 66:     /**
 67:      * The mapping of UIDs to slices.
 68:      *
 69:      * @var array
 70:      */
 71:     protected $_slicemap = array();
 72: 
 73:     /**
 74:      * The list of items to update:
 75:      *   - add: (array) List of IDs that were added.
 76:      *   - slice: (array) List of slices that were modified.
 77:      *   - slicemap: (boolean) Was slicemap info changed?
 78:      *
 79:      * @var array
 80:      */
 81:     protected $_update = array();
 82: 
 83:     /**
 84:      * Return a reference to a concrete cache instance.
 85:      *
 86:      * @deprecated  As of 1.2.0
 87:      *
 88:      * @param array $params  The configuration parameters.
 89:      *
 90:      * @return Horde_Imap_Client_Cache  The global instance.
 91:      */
 92:     static public function singleton($params = array())
 93:     {
 94:         ksort($params);
 95:         $sig = hash('md5', serialize($params));
 96:         if (!isset(self::$_instances[$sig])) {
 97:             self::$_instances[$sig] = new self($params);
 98:         }
 99: 
100:         return self::$_instances[$sig];
101:     }
102: 
103:     /**
104:      * Constructor.
105:      *
106:      * @param array $params  Configuration parameters:
107:      * <ul>
108:      *  <li>
109:      *   REQUIRED Parameters:
110:      *   <ul>
111:      *    <li>
112:      *     baseob: (Horde_Imap_Client_Base) The driver object (@since 1.2.0).
113:      *    </li>
114:      *    <li>
115:      *     cacheob: (Horde_Cache) The cache object to use.
116:      *    </li>
117:      *   </ul>
118:      *  </li>
119:      *  <li>
120:      *   DEPRECATED Parameters (as of 1.2.0; replaced by 'baseob'):
121:      *   <ul>
122:      *    <li>
123:      *     hostspec: (string) The IMAP hostspec.
124:      *    </li>
125:      *    <li>
126:      *     port: (string) The IMAP port.
127:      *    </li>
128:      *    <li>
129:      *     username: (string) The IMAP username.
130:      *    </li>
131:      *   </ul>
132:      *  </li>
133:      *  <li>
134:      *   Optional Parameters:
135:      *   <ul>
136:      *    <li>
137:      *     debug: (boolean) If true, will output debug information.
138:      *            DEFAULT: No debug output
139:      *    </li>
140:      *    <li>
141:      *     lifetime: (integer) The lifetime of the cache data (in seconds).
142:      *               DEFAULT: 1 week (604800 seconds)
143:      *    </li>
144:      *    <li>
145:      *     slicesize: (integer) The slicesize to use.
146:      *                DEFAULT: 50
147:      *    </li>
148:      *   </ul>
149:      *  </li>
150:      * </ul>
151:      */
152:     public function __construct(array $params = array())
153:     {
154:         // Don't mark 'baseob' as required yet.
155:         $required = array('cacheob');
156:         foreach ($required as $val) {
157:             if (empty($params[$val])) {
158:                 throw new InvalidArgumentException('Missing required parameter for ' . __CLASS__ . ': ' . $val);
159:             }
160:         }
161: 
162:         // Default parameters.
163:         $params = array_merge(array(
164:             'debug' => false,
165:             'lifetime' => 604800,
166:             'slicesize' => 50
167:         ), array_filter($params));
168: 
169:         if (isset($params['baseob'])) {
170:             $this->_base = $params['baseob'];
171:             $this->_params = array(
172:                 'hostspec' => $this->_base->getParam('hostspec'),
173:                 'port' => $this->_base->getParam('port'),
174:                 'username' => $this->_base->getParam('username')
175:             );
176:         } else {
177:             $this->_params = array(
178:                 'hostspec' => $params['hostspec'],
179:                 'port' => $params['port'],
180:                 'username' => $params['username']
181:             );
182:             $params['debug'] = false;
183:         }
184: 
185:         $this->_cache = $params['cacheob'];
186: 
187:         $this->_params['debug'] = (bool)$params['debug'];
188:         $this->_params['lifetime'] = intval($params['lifetime']);
189:         $this->_params['slicesize'] = intval($params['slicesize']);
190: 
191:         register_shutdown_function(array($this, 'shutdown'));
192:     }
193: 
194:     /**
195:      * Updates the cache on shutdown.
196:      */
197:     public function shutdown()
198:     {
199:         $lifetime = $this->_params['lifetime'];
200: 
201:         foreach ($this->_update as $mbox => $val) {
202:             $s = &$this->_slicemap[$mbox];
203: 
204:             if (!empty($val['add'])) {
205:                 if ($s['c'] <= $this->_params['slicesize']) {
206:                     $val['slice'][] = $s['i'];
207:                     $this->_loadSlice($mbox, $s['i']);
208:                 }
209:                 $val['slicemap'] = true;
210: 
211:                 foreach (array_keys(array_flip($val['add'])) as $uid) {
212:                     if ($s['c']++ > $this->_params['slicesize']) {
213:                         $s['c'] = 0;
214:                         $val['slice'][] = ++$s['i'];
215:                         $this->_loadSlice($mbox, $s['i']);
216:                     }
217:                     $s['s'][$uid] = $s['i'];
218:                 }
219:             }
220: 
221:             if (!empty($val['slice'])) {
222:                 $d = &$this->_data[$mbox];
223:                 $val['slicemap'] = true;
224: 
225:                 foreach (array_keys(array_flip($val['slice'])) as $slice) {
226:                     $data = array();
227:                     foreach (array_keys($s['s'], $slice) as $uid) {
228:                         $data[$uid] = is_array($d[$uid])
229:                             ? serialize($d[$uid])
230:                             : $d[$uid];
231:                     }
232:                     $this->_cache->set($this->_getCid($mbox, $slice), serialize($data), $lifetime);
233:                 }
234:             }
235: 
236:             if (!empty($val['slicemap'])) {
237:                 $this->_cache->set($this->_getCid($mbox, 'slicemap'), serialize($s), $lifetime);
238:             }
239:         }
240:     }
241: 
242:     /**
243:      * Create the unique ID used to store the data in the cache.
244:      *
245:      * @param string $mailbox  The mailbox to cache.
246:      * @param string $slice    The cache slice.
247:      *
248:      * @return string  The cache ID.
249:      */
250:     protected function _getCid($mailbox, $slice)
251:     {
252:         return implode('|', array(
253:             'horde_imap_client',
254:             $this->_params['username'],
255:             $mailbox,
256:             $this->_params['hostspec'],
257:             $this->_params['port'],
258:             $slice,
259:             self::VERSION
260:         ));
261:     }
262: 
263:     /**
264:      * Get information from the cache.
265:      *
266:      * @param string $mailbox    An IMAP mailbox string.
267:      * @param array $uids        The list of message UIDs to retrieve
268:      *                           information for. If empty, returns the list
269:      *                           of cached UIDs.
270:      * @param array $fields      An array of fields to retrieve. If null,
271:      *                           returns all cached fields.
272:      * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
273:      *
274:      * @return array  An array of arrays with the UID of the message as the
275:      *                key (if found) and the fields as values (will be
276:      *                undefined if not found). If $uids is empty, returns the
277:      *                full (unsorted) list of cached UIDs.
278:      */
279:     public function get($mailbox, array $uids = array(), $fields = array(),
280:                         $uidvalid = null)
281:     {
282:         $mailbox = strval($mailbox);
283: 
284:         if (empty($uids)) {
285:             $this->_loadSliceMap($mailbox, $uidvalid);
286:             return array_keys($this->_slicemap[$mailbox]['s']);
287:         }
288: 
289:         $ret = array();
290:         $this->_loadUids($mailbox, $uids, $uidvalid);
291: 
292:         if (empty($this->_data[$mailbox])) {
293:             return $ret;
294:         }
295: 
296:         if (!is_null($fields)) {
297:             $fields = array_flip($fields);
298:         }
299:         $ptr = &$this->_data[$mailbox];
300: 
301:         foreach (array_intersect($uids, array_keys($ptr)) as $val) {
302:             if (is_string($ptr[$val])) {
303:                 $ptr[$val] = @unserialize($ptr[$val]);
304:             }
305: 
306:             $ret[$val] = (is_null($fields) || empty($ptr[$val]))
307:                 ? $ptr[$val]
308:                 : array_intersect_key($ptr[$val], $fields);
309:         }
310: 
311:         if ($this->_params['debug'] && !empty($ret)) {
312:             $this->_base->writeDebug('CACHE: Retrieved messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_base->getIdsOb(array_keys($ret))->tostring_sort . ")\n", Horde_Imap_Client::DEBUG_INFO);
313:         }
314: 
315:         return $ret;
316:     }
317: 
318:     /**
319:      * Store information in cache.
320:      *
321:      * @param string $mailbox    An IMAP mailbox string.
322:      * @param array $data        The list of data to save. The keys are the
323:      *                           UIDs, the values are an array of information
324:      *                           to save. If empty, do a check to make sure
325:      *                           the uidvalidity is still valid.
326:      * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
327:      */
328:     public function set($mailbox, $data, $uidvalid)
329:     {
330:         $mailbox = strval($mailbox);
331: 
332:         if (empty($data)) {
333:             $this->_loadSliceMap($mailbox, $uidvalid);
334:             return;
335:         }
336: 
337:         $update = array_keys($data);
338: 
339:         try {
340:             $this->_loadUids($mailbox, $update, $uidvalid);
341:         } catch (Horde_Imap_Client_Exception $e) {
342:             // Ignore invalidity - just start building the new cache
343:         }
344: 
345:         $d = &$this->_data[$mailbox];
346:         $s = &$this->_slicemap[$mailbox]['s'];
347:         $add = $updated = array();
348: 
349:         foreach ($data as $k => $v) {
350:             if (isset($d[$k])) {
351:                 if (is_string($d[$k])) {
352:                     $d[$k] = @unserialize($d[$k]);
353:                 }
354:                 $d[$k] = is_array($d[$k])
355:                     ? array_merge($d[$k], $v)
356:                     : $v;
357:                 if (isset($s[$k])) {
358:                     $updated[$s[$k]] = true;
359:                 }
360:             } else {
361:                 $d[$k] = $v;
362:                 $add[] = $k;
363:             }
364:         }
365: 
366:         $this->_toUpdate($mailbox, 'add', $add);
367:         $this->_toUpdate($mailbox, 'slice', array_keys($updated));
368: 
369:         if ($this->_params['debug']) {
370:             $this->_base->writeDebug('CACHE: Stored messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_base->getIdsOb($update)->tostring_sort . ")\n", Horde_Imap_Client::DEBUG_INFO);
371:         }
372:     }
373: 
374:     /**
375:      * Get metadata information for a mailbox.
376:      *
377:      * @param string $mailbox    An IMAP mailbox string.
378:      * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
379:      * @param array $entries     An array of entries to return. If empty,
380:      *                           returns all metadata.
381:      *
382:      * @return array  The requested metadata. Requested entries that do not
383:      *                exist will be undefined. The following entries are
384:      *                defaults and always present:
385:      *   - uidvalid: (integer) The UIDVALIDITY of the mailbox.
386:      */
387:     public function getMetaData($mailbox, $uidvalid = null,
388:                                 array $entries = array())
389:     {
390:         $mailbox = strval($mailbox);
391:         $this->_loadSliceMap($mailbox, $uidvalid);
392: 
393:         return empty($entries)
394:             ? $this->_slicemap[$mailbox]['d']
395:             : array_intersect_key($this->_slicemap[$mailbox]['d'], array_flip($entries));
396:     }
397: 
398:     /**
399:      * Set metadata information for a mailbox.
400:      *
401:      * @param string $mailbox    An IMAP mailbox string.
402:      * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
403:      * @param array $data        The list of data to save. The keys are the
404:      *                           metadata IDs, the values are the associated
405:      *                           data. The following labels are reserved:
406:      *                           'uidvalid'.
407:      */
408:     public function setMetaData($mailbox, $uidvalid, array $data = array())
409:     {
410:         unset($data['uidvalid']);
411:         if (empty($data)) {
412:             return;
413:         }
414: 
415:         $mailbox = strval($mailbox);
416:         $this->_loadSliceMap($mailbox, $uidvalid);
417:         $this->_slicemap[$mailbox]['d'] = array_merge($this->_slicemap[$mailbox]['d'], $data);
418:         $this->_toUpdate($mailbox, 'slicemap', true);
419:     }
420: 
421:     /**
422:      * Delete messages in the cache.
423:      *
424:      * @param string $mailbox  An IMAP mailbox string.
425:      * @param array $uids      The list of message UIDs to delete.
426:      */
427:     public function deleteMsgs($mailbox, $uids)
428:     {
429:         if (empty($uids)) {
430:             return;
431:         }
432: 
433:         $mailbox = strval($mailbox);
434:         $this->_loadSliceMap($mailbox);
435: 
436:         $slicemap = &$this->_slicemap[$mailbox];
437:         $update = array_intersect_key($slicemap['s'], array_flip($uids));
438: 
439:         if (!empty($update)) {
440:             $this->_loadUids($mailbox, array_keys($update));
441:             $d = &$this->_data[$mailbox];
442: 
443:             foreach (array_keys($update) as $id) {
444:                 unset($d[$id], $slicemap['s'][$id]);
445:             }
446: 
447:             foreach (array_unique($update) as $slice) {
448:                 /* Get rid of slice if less than 10% of capacity. */
449:                 if (($slice != $slicemap['i']) &&
450:                     ($slice_uids = array_keys($slicemap['s'], $slice)) &&
451:                     ($this->_params['slicesize'] * 0.1) > count($slice_uids)) {
452:                     $this->_toUpdate($mailbox, 'add', $slice_uids);
453:                     $this->_cache->expire($this->_getCid($mailbox, $slice));
454:                     foreach ($slice_uids as $val) {
455:                         unset($slicemap['s'][$val]);
456:                     }
457:                 } else {
458:                     $this->_toUpdate($mailbox, 'slice', array($slice));
459:                 }
460:             }
461: 
462:             if ($this->_params['debug']) {
463:                 $this->_base->writeDebug('CACHE: Deleted messages (mailbox: ' . $mailbox . '; UIDs: ' . $this->_base->getIdsOb(array_keys($update))->tostring_sort . ")\n", Horde_Imap_Client::DEBUG_INFO);
464:             }
465:         }
466:     }
467: 
468:     /**
469:      * Delete a mailbox from the cache.
470:      *
471:      * @param string $mbox  The mailbox to delete.
472:      */
473:     public function deleteMailbox($mbox)
474:     {
475:         $mbox = strval($mbox);
476:         $this->_loadSliceMap($mbox);
477:         $this->_deleteMailbox($mbox);
478:     }
479: 
480:     /**
481:      * Delete a mailbox from the cache.
482:      *
483:      * @param string $mbox  The mailbox to delete.
484:      */
485:     protected function _deleteMailbox($mbox)
486:     {
487:         foreach (array_merge(array_keys(array_flip($this->_slicemap[$mbox]['s'])), array('slicemap')) as $slice) {
488:             $cid = $this->_getCid($mbox, $slice);
489:             $this->_cache->expire($cid);
490:             unset($this->_loaded[$cid]);
491:         }
492: 
493:         unset(
494:             $this->_data[$mbox],
495:             $this->_slicemap[$mbox],
496:             $this->_update[$mbox]
497:         );
498: 
499:         if ($this->_params['debug']) {
500:             $this->_base->writeDebug('CACHE: Deleted mailbox (mailbox: ' . $mbox . ")\n", Horde_Imap_Client::DEBUG_INFO);
501:         }
502:     }
503: 
504:     /**
505:      * Load UIDs by regenerating from the cache.
506:      *
507:      * @param string $mailbox    The mailbox to load.
508:      * @param array $uids        The UIDs to load.
509:      * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
510:      */
511:     protected function _loadUids($mailbox, $uids, $uidvalid = null)
512:     {
513:         if (!isset($this->_data[$mailbox])) {
514:             $this->_data[$mailbox] = array();
515:         }
516: 
517:         $this->_loadSliceMap($mailbox, $uidvalid);
518: 
519:         if (!empty($uids)) {
520:             foreach (array_unique(array_intersect_key($this->_slicemap[$mailbox]['s'], array_flip($uids))) as $slice) {
521:                 $this->_loadSlice($mailbox, $slice);
522:             }
523:         }
524:     }
525: 
526:     /**
527:      * Load UIDs from a cache slice.
528:      *
529:      * @param string $mailbox  The mailbox to load.
530:      * @param integer $slice   The slice to load.
531:      */
532:     protected function _loadSlice($mailbox, $slice)
533:     {
534:         $cache_id = $this->_getCid($mailbox, $slice);
535: 
536:         if (!empty($this->_loaded[$cache_id])) {
537:             return;
538:         }
539: 
540:         if ((($data = $this->_cache->get($cache_id, $this->_params['lifetime'])) !== false) &&
541:             ($data = @unserialize($data)) &&
542:             is_array($data)) {
543:             $this->_data[$mailbox] += $data;
544:             $this->_loaded[$cache_id] = true;
545:         } else {
546:             $ptr = &$this->_slicemap[$mailbox];
547: 
548:             // Slice data is corrupt; remove from slicemap.
549:             foreach (array_keys($ptr['s'], $slice) as $val) {
550:                 unset($ptr['s'][$val]);
551:             }
552: 
553:             if ($slice == $ptr['i']) {
554:                 $ptr['c'] = 0;
555:             }
556:         }
557:     }
558: 
559:     /**
560:      * Load the slicemap for a given mailbox.  The slicemap contains
561:      * the uidvalidity information, the UIDs->slice lookup table, and any
562:      * metadata that needs to be saved for the mailbox.
563:      *
564:      * @param string $mailbox    The mailbox.
565:      * @param integer $uidvalid  The IMAP uidvalidity value of the mailbox.
566:      */
567:     protected function _loadSliceMap($mailbox, $uidvalid = null)
568:     {
569:         if (!isset($this->_slicemap[$mailbox]) &&
570:             (($data = $this->_cache->get($this->_getCid($mailbox, 'slicemap'), $this->_params['lifetime'])) !== false) &&
571:             ($slice = @unserialize($data)) &&
572:             is_array($slice)) {
573:             $this->_slicemap[$mailbox] = $slice;
574:         }
575: 
576:         if (isset($this->_slicemap[$mailbox])) {
577:             $ptr = &$this->_slicemap[$mailbox];
578:             if (is_null($ptr['d']['uidvalid'])) {
579:                 $ptr['d']['uidvalid'] = $uidvalid;
580:                 return;
581:             } elseif (!is_null($uidvalid) &&
582:                       ($ptr['d']['uidvalid'] != $uidvalid)) {
583:                 $this->_deleteMailbox($mailbox);
584:             } else {
585:                 return;
586:             }
587:         }
588: 
589:         $this->_slicemap[$mailbox] = array(
590:             // Tracking count for purposes of determining slices
591:             'c' => 0,
592:             // Metadata storage
593:             // By default includes UIDVALIDITY of mailbox.
594:             'd' => array('uidvalid' => $uidvalid),
595:             // The ID of the last slice.
596:             'i' => 0,
597:             // The slice list.
598:             's' => array()
599:         );
600:     }
601: 
602:     /**
603:      * Add update entry for a mailbox.
604:      *
605:      * @param string $mailbox  The mailbox.
606:      * @param string $type     'add', 'slice', or 'slicemap'.
607:      * @param mixed $data      The data to update.
608:      */
609:     protected function _toUpdate($mailbox, $type, $data)
610:     {
611:         if (!isset($this->_update[$mailbox])) {
612:             $this->_update[$mailbox] = array(
613:                 'add' => array(),
614:                 'slice' => array()
615:             );
616:         }
617: 
618:         $this->_update[$mailbox][$type] = ($type == 'slicemap')
619:             ? $data
620:             : array_merge($this->_update[$mailbox][$type], $data);
621:     }
622: 
623: }
624: 
API documentation generated by ApiGen