--- /dev/null
+CREATE TABLE horde_cache (
+ cache_id VARCHAR(32) NOT NULL,
+ cache_timestamp BIGINT NOT NULL,
+ cache_data LONGBLOB,
+-- Or, on some DBMS systems:
+-- cache_data IMAGE,
+
+ PRIMARY KEY (cache_id)
+);
--- /dev/null
+<?php
+/**
+ * The Horde_Cache:: class provides a common abstracted interface into
+ * the various caching backends. It also provides functions for
+ * checking in, retrieving, and flushing a cache.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Anil Madhavapeddy <anil@recoil.org>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Cache
+ */
+class Horde_Cache
+{
+ /**
+ * Singleton instances.
+ *
+ * @var array
+ */
+ static protected $_instances = array();
+
+ /**
+ * Attempts to return a concrete Horde_Cache instance based on $driver.
+ *
+ * @param mixed $driver The type of concrete Horde_Cache subclass to
+ * return. If $driver is an array, then we will look
+ * in $driver[0]/lib/Cache/ for the subclass
+ * implementation named $driver[1].php.
+ * @param array $params A hash containing any additional configuration
+ * or connection parameters a subclass might need.
+ *
+ * @return Horde_Cache The newly created concrete Horde_Cache instance.
+ * @throws Horde_Exception
+ */
+ static public function factory($driver, $params = array())
+ {
+ if (is_array($driver)) {
+ list($app, $driv_name) = $driver;
+ $driver = basename($driv_name);
+ } else {
+ $driver = basename($driver);
+ }
+
+ if (empty($driver) || $driver == 'none') {
+ return new Horde_Cache_Null($params);
+ }
+
+ $class = (empty($app) ? 'Horde' : $app) . '_Cache_' . ucfirst($driver);
+
+ if (class_exists($class)) {
+ return new $class($params);
+ }
+
+ throw new Horde_Exception('Class definition of ' . $class . ' not found.');
+ }
+
+ /**
+ * Attempts to return a reference to a concrete Horde_Cache instance
+ * based on $driver. It will only create a new instance if no
+ * Horde_Cache instance with the same parameters currently exists.
+ *
+ * This should be used if multiple cache backends (and, thus,
+ * multiple Horde_Cache instances) are required.
+ *
+ * This method must be invoked as:
+ * $var = &Horde_Cache::singleton()
+ *
+ * @param mixed $driver The type of concrete Horde_Cache subclass to
+ * return. If $driver is an array, then we will look
+ * in $driver[0]/lib/Cache/ for the subclass
+ * implementation named $driver[1].php.
+ * @param array $params A hash containing any additional configuration or
+ * connection parameters a subclass might need.
+ *
+ * @return Horde_Cache The concrete Horde_Cache reference.
+ * @throws Horde_Exception
+ */
+ static public function singleton($driver, $params = array())
+ {
+ ksort($params);
+ $signature = hash('md5', serialize(array($driver, $params)));
+
+ if (!isset(self::$_instances[$signature])) {
+ self::$_instances[$signature] = Horde_Cache::factory($driver, $params);
+ }
+
+ return self::$_instances[$signature];
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_Apc:: class provides an Alternative PHP Cache
+ * implementation of the Horde caching system.
+ *
+ * Copyright 2006-2007 Duck <duck@obala.net>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Horde_Cache
+ */
+class Horde_Cache_Apc extends Horde_Cache_Base
+{
+ /**
+ * Attempts to retrieve a piece of cached data and return it to
+ * the caller.
+ *
+ * @param string $key Cache key to fetch.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ public function get($key, $lifetime = 1)
+ {
+ $this->_setExpire($key, $lifetime);
+ return apc_fetch($key);
+ }
+
+ /**
+ * Attempts to store an object to the cache.
+ *
+ * @param string $key Cache key (identifier).
+ * @param mixed $data Data to store in the cache.
+ * @param integer $lifetime Data lifetime.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function set($key, $data, $lifetime = null)
+ {
+ $lifetime = $this->_getLifetime($lifetime);
+ apc_store($key . '_expire', time(), $lifetime);
+ return apc_store($key, $data, $lifetime);
+ }
+
+ /**
+ * Checks if a given key exists in the cache, valid for the given
+ * lifetime.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return boolean Existence.
+ */
+ public function exists($key, $lifetime = 1)
+ {
+ $this->_setExpire($key, $lifetime);
+ return (apc_fetch($key) === false) ? false : true;
+ }
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ public function expire($key)
+ {
+ apc_delete($key . '_expire');
+ return apc_delete($key);
+ }
+
+ /**
+ * Set expire time on each call since APC sets it on cache creation.
+ *
+ * @param string $key Cache key to expire.
+ * @param integer $lifetime Lifetime of the data in seconds.
+ */
+ protected function _setExpire($key, $lifetime)
+ {
+ if ($lifetime == 0) {
+ // Don't expire.
+ return;
+ }
+
+ $expire = apc_fetch($key . '_expire');
+
+ // Set prune period.
+ if ($expire + $lifetime < time()) {
+ // Expired
+ apc_delete($key);
+ apc_delete($key . '_expire');
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_Base:: class provides the abstract class definition for
+ * Horde_Cache drivers.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Anil Madhavapeddy <anil@recoil.org>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Cache
+ */
+abstract class Horde_Cache_Base
+{
+ /**
+ * Cache parameters.
+ *
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Construct a new Horde_Cache object.
+ *
+ * @param array $params Parameter array.
+ */
+ public function __construct($params = array())
+ {
+ if (!isset($params['lifetime'])) {
+ $params['lifetime'] = isset($GLOBALS['conf']['cache']['default_lifetime'])
+ ? $GLOBALS['conf']['cache']['default_lifetime']
+ : 86400;
+ }
+
+ $this->_params = $params;
+ }
+
+ /**
+ * Attempts to retrieve a cached object and return it to the
+ * caller.
+ *
+ * @param string $key Object ID to query.
+ * @param integer $lifetime Lifetime of the object in seconds.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ abstract public function get($key, $lifetime = 1);
+
+ /**
+ * Attempts to store an object in the cache.
+ *
+ * @param string $key Object ID used as the caching key.
+ * @param mixed $data Data to store in the cache.
+ * @param integer $lifetime Object lifetime - i.e. the time before the
+ * data becomes available for garbage
+ * collection. If null use the default Horde GC
+ * time. If 0 will not be GC'd.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ abstract public function set($key, $data, $lifetime = null);
+
+ /**
+ * Checks if a given key exists in the cache, valid for the given
+ * lifetime.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return boolean Existence.
+ */
+ abstract public function exists($key, $lifetime = 1);
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ abstract public function expire($key);
+
+ /**
+ * Attempts to directly output a cached object.
+ *
+ * @param string $key Object ID to query.
+ * @param integer $lifetime Lifetime of the object in seconds.
+ *
+ * @return boolean True if output or false if no object was found.
+ */
+ public function output($key, $lifetime = 1)
+ {
+ $data = $this->get($key, $lifetime);
+ if ($data === false) {
+ return false;
+ }
+
+ echo $data;
+ return true;
+ }
+
+ /**
+ * Determine the default lifetime for data.
+ *
+ * @param mixed $lifetime The lifetime to use or null for default.
+ *
+ * @return integer The lifetime, in seconds.
+ */
+ protected function _getLifetime($lifetime)
+ {
+ return is_null($lifetime) ? $this->_params['lifetime'] : $lifetime;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_Eaccelerator:: class provides a eAccelerator content cache
+ * (version 0.9.5+) implementation of the Horde caching system.
+ *
+ * Copyright 2006-2007 Duck <duck@obala.net>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Horde_Cache
+ */
+class Horde_Cache_Eaccelerator extends Horde_Cache_Base
+{
+ /**
+ * Attempts to retrieve a piece of cached data and return it to the caller.
+ *
+ * @param string $key Cache key to fetch.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ public function get($key, $lifetime = 1)
+ {
+ $this->_setExpire($key, $lifetime);
+ return eaccelerator_get($key);
+ }
+
+ /**
+ * Attempts to store an object to the cache.
+ *
+ * @param string $key Cache key (identifier).
+ * @param mixed $data Data to store in the cache.
+ * @param integer $lifetime Data lifetime.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function set($key, $data, $lifetime = null)
+ {
+ $lifetime = $this->_getLifetime($lifetime);
+ eaccelerator_put($key . '_expire', time(), $lifetime);
+ return eaccelerator_put($key, $data, $lifetime);
+ }
+
+ /**
+ * Checks if a given key exists in the cache, valid for the given
+ * lifetime.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return boolean Existence.
+ */
+ public function exists($key, $lifetime = 1)
+ {
+ $this->_setExpire($key, $lifetime);
+ return (eaccelerator_get($key) === false) ? false : true;
+ }
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ public function expire($key)
+ {
+ eaccelerator_rm($key . '_expire');
+ return eaccelerator_rm($key);
+ }
+
+ /**
+ * Set expire time on each call since eAccelerator sets it on
+ * cache creation.
+ *
+ * @param string $key Cache key to expire.
+ * @param integer $lifetime Lifetime of the data in seconds.
+ */
+ protected function _setExpire($key, $lifetime)
+ {
+ if ($lifetime == 0) {
+ // Don't expire.
+ return;
+ }
+
+ $expire = eaccelerator_get($key . '_expire');
+
+ // Set prune period.
+ if ($expire + $lifetime < time()) {
+ // Expired
+ eaccelerator_rm($key);
+ eaccelerator_rm($key . '_expire');
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_File:: class provides a filesystem implementation of the
+ * Horde caching system.
+ *
+ * Optional parameters:<pre>
+ * 'dir' The base directory to store the cache files in.
+ * 'prefix' The filename prefix to use for the cache files.
+ * 'sub' An integer. If non-zero, the number of subdirectories to
+ * create to store the file (i.e. PHP's session.save_path).</pre>
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Anil Madhavapeddy <anil@recoil.org>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Cache
+ */
+class Horde_Cache_File extends Horde_Cache_Base
+{
+ /**
+ * The location of the temp directory.
+ *
+ * @var string
+ */
+ protected $_dir;
+
+ /**
+ * The filename prefix for cache files.
+ *
+ * @var string
+ */
+ protected $_prefix = 'cache_';
+
+ /**
+ * The subdirectory level the cache files should live at.
+ *
+ * @var integer
+ */
+ protected $_sub = 0;
+
+ /**
+ * List of key to filename mappings.
+ *
+ * @var array
+ */
+ protected $_file = array();
+
+ /**
+ * Construct a new Horde_Cache_File object.
+ *
+ * @param array $params Parameter array.
+ */
+ public function __construct($params = array())
+ {
+ if (!empty($params['dir']) && @is_dir($params['dir'])) {
+ $this->_dir = $params['dir'];
+ } else {
+ require_once 'Horde/Util.php';
+ $this->_dir = Util::getTempDir();
+ }
+
+ foreach (array('prefix', 'sub') as $val) {
+ if (isset($params[$val])) {
+ $name = '_' . $val;
+ $this->$name = $params[$val];
+ }
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ /* Only do garbage collection 0.1% of the time we create an object. */
+ if (rand(0, 999) != 0) {
+ return;
+ }
+
+ $filename = $this->_dir . '/horde_cache_gc';
+ $excepts = array();
+
+ if (file_exists($filename)) {
+ $flags = defined('FILE_IGNORE_NEW_LINES')
+ ? FILE_IGNORE_NEW_LINES
+ : 0;
+
+ $gc_file = file($filename, $flags);
+ array_pop($gc_file);
+ reset($gc_file);
+ while (list(,$data) = each($gc_file)) {
+ if (!$flags) {
+ $data = rtrim($data);
+ }
+ $parts = explode("\t", $data, 2);
+ $excepts[$parts[0]] = $parts[1];
+ }
+ }
+
+ try {
+ $this->_gcDir($this->_dir, $excepts);
+ } catch (Horde_Exception $e) {}
+
+ $out = '';
+ foreach ($excepts as $key => $val) {
+ $out .= $key . "\t" . $val . "\n";
+ }
+
+ file_put_contents($filename, $out);
+ }
+
+ /**
+ * Attempts to retrieve cached data from the filesystem and return it to
+ * the caller.
+ *
+ * @param string $key Cache key to fetch.
+ * @param integer $lifetime Lifetime of the data in seconds.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ public function get($key, $lifetime = 1)
+ {
+ if (!$this->exists($key, $lifetime)) {
+ /* Nothing cached, return failure. */
+ return false;
+ }
+
+ $filename = $this->_keyToFile($key);
+ $size = filesize($filename);
+ if (!$size) {
+ return '';
+ }
+ $old_error = error_reporting(0);
+ $data = file_get_contents($filename);
+ error_reporting($old_error);
+
+ return $data;
+ }
+
+ /**
+ * Attempts to store data to the filesystem.
+ *
+ * @param string $key Cache key.
+ * @param mixed $data Data to store in the cache. (MUST BE A STRING)
+ * @param integer $lifetime Data lifetime.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function set($key, $data, $lifetime = null)
+ {
+ require_once 'Horde/Util.php';
+ $filename = $this->_keyToFile($key, true);
+ $tmp_file = Util::getTempFile('HordeCache', true, $this->_dir);
+ if (isset($this->_params['umask'])) {
+ chmod($tmp_file, 0666 & ~$this->_params['umask']);
+ }
+
+ if (file_put_contents($tmp_file, $data) === false) {
+ return false;
+ }
+
+ @rename($tmp_file, $filename);
+
+ $lifetime = $this->_getLifetime($lifetime);
+ if ($lifetime != $this->_params['lifetime']) {
+ // This may result in duplicate entries in horde_cache_gc, but we
+ // will take care of these whenever we do GC and this is quicker
+ // than having to check every time we access the file.
+ $fp = fopen($this->_dir . '/horde_cache_gc', 'a');
+ fwrite($fp, $filename . "\t" . (empty($lifetime) ? 0 : time() + $lifetime) . "\n");
+ fclose($fp);
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if a given key exists in the cache, valid for the given
+ * lifetime. If it exists but is expired, delete the file.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return boolean Existence.
+ */
+ public function exists($key, $lifetime = 1)
+ {
+ $filename = $this->_keyToFile($key);
+
+ /* Key exists in the cache */
+ if (file_exists($filename)) {
+ /* 0 means no expire. */
+ if ($lifetime == 0) {
+ return true;
+ }
+
+ /* If the file was been created after the supplied value,
+ * the data is valid (fresh). */
+ if (time() - $lifetime <= filemtime($filename)) {
+ return true;
+ } else {
+ @unlink($filename);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ public function expire($key)
+ {
+ $filename = $this->_keyToFile($key);
+ return @unlink($filename);
+ }
+
+ /**
+ * Attempts to directly output a cached object.
+ *
+ * @param string $key Object ID to query.
+ * @param integer $lifetime Lifetime of the object in seconds.
+ *
+ * @return boolean True if output or false if no object was found.
+ */
+ public function output($key, $lifetime = 1)
+ {
+ if (!$this->exists($key, $lifetime)) {
+ return false;
+ }
+
+ $filename = $this->_keyToFile($key);
+ return @readfile($filename);
+ }
+
+ /**
+ * Map a cache key to a unique filename.
+ *
+ * @param string $key Cache key.
+ * @param string $create Create path if it doesn't exist?
+ *
+ * @return string Fully qualified filename.
+ */
+ protected function _keyToFile($key, $create = false)
+ {
+ if ($create || !isset($this->_file[$key])) {
+ $dir = $this->_dir . '/';
+ $sub = '';
+ $md5 = md5($key);
+ if (!empty($this->_sub)) {
+ $max = min($this->_sub, strlen($md5));
+ for ($i = 0; $i < $max; $i++) {
+ $sub .= $md5[$i];
+ if ($create && !is_dir($dir . $sub)) {
+ if (!mkdir($dir . $sub)) {
+ $sub = '';
+ break;
+ }
+ }
+ $sub .= '/';
+ }
+ }
+ $this->_file[$key] = $dir . $sub . $this->_prefix . $md5;
+ }
+
+ return $this->_file[$key];
+ }
+
+ /**
+ * TODO
+ *
+ * @throws Horde_Exception
+ */
+ protected function _gcDir($dir, &$excepts)
+ {
+ $d = @dir($dir);
+ if (!$d) {
+ throw new Horde_Exception('Permission denied to ' . $dir);
+ }
+
+ $c_time = time();
+
+ while (($entry = $d->read()) !== false) {
+ $path = $dir . '/' . $entry;
+ if (($entry == '.') || ($entry == '..')) {
+ continue;
+ }
+
+ if (strpos($entry, $this->_prefix) === 0) {
+ $d_time = isset($excepts[$path]) ? $excepts[$path] : $this->_params['lifetime'];
+ if (!empty($d_time) &&
+ (($c_time - $d_time) > filemtime($path))) {
+ @unlink($path);
+ unset($excepts[$path]);
+ }
+ } elseif (!empty($this->_sub) && is_dir($path)) {
+ $this->_gcDir($path, $excepts);
+ }
+ }
+ $d->close();
+ }
+
+}
+
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_Memcache:: class provides a memcached implementation of the
+ * Horde caching system.
+ *
+ * Copyright 2006-2007 Duck <duck@obala.net>
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @package Horde_Cache
+ */
+class Horde_Cache_Memcache extends Horde_Cache_Base
+{
+ /**
+ * Horde_memcache object.
+ *
+ * @var Horde_Memcache
+ */
+ protected $_memcache;
+
+ /**
+ * Cache results of expire() calls (since we will get the entire object
+ * on an expire() call anyway).
+ */
+ protected $_expirecache = array();
+
+ /**
+ * Construct a new Horde_Cache_Memcache object.
+ *
+ * @param array $params Parameter array.
+ */
+ public function __construct($params = array())
+ {
+ $this->_memcache = &Horde_Memcache::singleton();
+
+ parent::__construct($params);
+ }
+
+ /**
+ * Attempts to retrieve cached data from the memcache and return it to
+ * the caller.
+ *
+ * @param string $key Cache key to fetch.
+ * @param integer $lifetime Lifetime of the data in seconds.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ public function get($key, $lifetime = 1)
+ {
+ if (isset($this->_expirecache[$key])) {
+ return $this->_expirecache[$key];
+ }
+
+ $key_list = array($key);
+ if (!empty($lifetime)) {
+ $key_list[] = $key . '_e';
+ }
+
+ $res = $this->_memcache->get($key_list);
+
+ if ($res === false) {
+ unset($this->_expirecache[$key]);
+ } else {
+ // If we can't find the expire time, assume we have exceeded it.
+ if (empty($lifetime) ||
+ (($res[$key . '_e'] !== false) && ($res[$key . '_e'] + $lifetime > time()))) {
+ $this->_expirecache[$key] = $res[$key];
+ } else {
+ $res[$key] = false;
+ $this->expire($key);
+ }
+ }
+
+ return $res[$key];
+ }
+
+ /**
+ * Attempts to store data to the memcache.
+ *
+ * @param string $key Cache key.
+ * @param mixed $data Data to store in the cache.
+ * @param integer $lifetime Data lifetime.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function set($key, $data, $lifetime = null)
+ {
+ $lifetime = $this->_getLifetime($lifetime);
+ return ($this->_memcache->set($key . '_e', time(), $lifetime) === false)
+ ? false
+ : $this->_memcache->set($key, $data, $lifetime);
+ }
+
+ /**
+ * Checks if a given key exists in the cache.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return boolean Existence.
+ */
+ public function exists($key, $lifetime = 1)
+ {
+ return ($this->_get($key, $lifetime) !== false);
+ }
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ public function expire($key)
+ {
+ unset($this->_expirecache[$key]);
+ $this->_memcache->delete($key . '_e');
+ return $this->_memcache->delete($key);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_Null:: class provides a null implementation of the Horde
+ * caching system.
+ *
+ * Copyright 2006-2007 Duck <duck@obala.net>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Horde_Cache
+ */
+class Horde_Cache_Apc extends Horde_Cache_Base
+{
+ /**
+ * Attempts to retrieve a piece of cached data and return it to
+ * the caller.
+ *
+ * @param string $key Cache key to fetch.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ public function get($key, $lifetime = 1)
+ {
+ return false;
+ }
+
+ /**
+ * Attempts to store an object to the cache.
+ *
+ * @param string $key Cache key (identifier).
+ * @param mixed $data Data to store in the cache.
+ * @param integer $lifetime Data lifetime.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function set($key, $data, $lifetime = null)
+ {
+ return false;
+ }
+
+ /**
+ * Checks if a given key exists in the cache, valid for the given
+ * lifetime.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return boolean Existence.
+ */
+ public function exists($key, $lifetime = 1)
+ {
+ return false;
+ }
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ public function expire($key)
+ {
+ return false;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_Sql:: class provides a SQL implementation of the Horde
+ * Caching system.
+ *
+ * Required parameters:<pre>
+ * 'phptype' The database type (ie. 'pgsql', 'mysql', etc.).</pre>
+ *
+ * Required by some database implementations:<pre>
+ * 'database' The name of the database.
+ * 'hostspec' The hostname of the database server.
+ * 'username' The username with which to connect to the database.
+ * 'password' The password associated with 'username'.
+ * 'options' Additional options to pass to the database.
+ * 'tty' The TTY on which to connect to the database.
+ * 'port' The port on which to connect to the database.</pre>
+ *
+ * Optional parameters:<pre>
+ * 'table' The name of the cache table in 'database'.
+ * Defaults to 'horde_cache'.
+ * 'use_memorycache' Use a Horde_Cache:: memory caching driver to cache
+ * the data (to avoid DB accesses). Either empty or
+ * 'none' if not needed, or else the name of a valid
+ * Horde_Cache:: driver.</pre>
+ *
+ * Optional values when using separate reading and writing servers, for example
+ * in replication settings:<pre>
+ * 'splitread' Boolean, whether to implement the separation or not.
+ * 'read' Array containing the parameters which are different for
+ * the read database connection, currently supported
+ * only 'hostspec' and 'port' parameters.</pre>
+ *
+ * The table structure for the cache is as follows:
+ * <pre>
+ * CREATE TABLE horde_cache (
+ * cache_id VARCHAR(32) NOT NULL,
+ * cache_timestamp BIGINT NOT NULL,
+ * cache_data LONGBLOB,
+ * (Or on PostgreSQL:)
+ * cache_data TEXT,
+ * (Or on some other DBMS systems:)
+ * cache_data IMAGE,
+ *
+ * PRIMARY KEY (cache_id)
+ * );
+ * </pre>
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Michael Slusarz <slusarz@curecanti.org>
+ * @author Ben Klang <ben@alkaloid.net>
+ * @package Horde_Cache
+ */
+class Horde_Cache_Sql extends Horde_Cache_Base
+{
+ /**
+ * Handle for the current database connection.
+ *
+ * @var DB
+ */
+ protected $_db;
+
+ /**
+ * Handle for the current database connection, used for writing. Defaults
+ * to the same handle as $_db if a separate write database isn't required.
+ *
+ * @var DB
+ */
+ protected $_write_db;
+
+ /**
+ * Boolean indicating whether or not we're connected to the SQL server.
+ *
+ * @var boolean
+ */
+ protected $_connected = false;
+
+ /**
+ * The memory cache object to use, if configured.
+ *
+ * @var Horde_Cache
+ */
+ protected $_mc = null;
+
+ /**
+ * Constructs a new Horde_Cache_Sql object.
+ *
+ * @param array $params A hash containing configuration parameters.
+ */
+ public function __construct($params = array())
+ {
+ $options = array(
+ 'database' => '',
+ 'username' => '',
+ 'password' => '',
+ 'hostspec' => '',
+ 'table' => '',
+ );
+ $this->_params = array_merge($options, $params);
+ if (empty($this->_params['table'])) {
+ $this->_params['table'] = 'horde_cache';
+ }
+
+ /* Create the memory cache object, if configured. */
+ if (!empty($this->_params['use_memorycache'])) {
+ $this->_mc = &Horde_Cache::singleton($params['use_memorycache'], !empty($conf['cache'][$params['use_memorycache']]) ? $conf['cache'][$params['use_memorycache']] : array());
+ }
+
+ parent::__construct($this->_params);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ /* Only do garbage collection 0.1% of the time we create an object. */
+ if (rand(0, 999) != 0) {
+ return;
+ }
+
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return;
+ }
+
+ $query = 'DELETE FROM ' . $this->_params['table'] .
+ ' WHERE cache_expiration < ? AND cache_expiration <> 0';
+ $values = array(time());
+
+ $result = $this->_write_db->query($query, $values);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ }
+
+ /**
+ * Attempts to retrieve cached data.
+ *
+ * @param string $key Cache key to fetch.
+ * @param integer $lifetime Maximum age of the data in seconds or
+ * 0 for any object.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ public function get($key, $lifetime = 1)
+ {
+ $okey = $key;
+ $key = hash('md5', $key);
+
+ if ($this->_mc) {
+ $data = $this->_mc->get($key, $lifetime);
+ if ($data !== false) {
+ return $data;
+ }
+ }
+
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return false;
+ }
+
+ $timestamp = time();
+ $maxage = $timestamp - $lifetime;
+
+ /* Build SQL query. */
+ $query = 'SELECT cache_data FROM ' . $this->_params['table'] .
+ ' WHERE cache_id = ?';
+ $values = array($key);
+
+ // 0 lifetime checks for objects which have no expiration
+ if ($lifetime != 0) {
+ $query .= ' AND cache_timestamp >= ?';
+ $values[] = $maxage;
+ }
+
+ $result = $this->_db->getOne($query, $values);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return false;
+ } elseif (is_null($result)) {
+ /* No rows were found - cache miss */
+ Horde::logMessage(sprintf('Cache miss: %s (Id %s newer than %d)', $okey, $key, $maxage), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ return false;
+ }
+
+ if ($this->_mc) {
+ $this->_mc->set($key, $result);
+ }
+ Horde::logMessage(sprintf('Cache hit: %s (Id %s newer than %d)', $okey, $key, $maxage), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ return $result;
+ }
+
+ /**
+ * Attempts to store data.
+ *
+ * @param string $key Cache key.
+ * @param mixed $data Data to store in the cache. (MUST BE A STRING)
+ * @param integer $lifetime Maximum data life span or 0 for a
+ * non-expiring object.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function set($key, $data, $lifetime = null)
+ {
+ $okey = $key;
+ $key = hash('md5', $key);
+
+ if ($this->_mc) {
+ $this->_mc->set($key, $data);
+ }
+
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return false;
+ }
+
+ $timestamp = time();
+
+ // 0 lifetime indicates the object should not be GC'd.
+ $expiration = ($lifetime === 0)
+ ? 0
+ : $this->_getLifetime($lifetime) + $timestamp;
+
+ Horde::logMessage(sprintf('Cache set: %s (Id %s set at %d expires at %d)', $okey, $key, $timestamp, $expiration), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ // Remove any old cache data and prevent duplicate keys
+ $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE cache_id=?';
+ $values = array($key);
+ $this->_write_db->query($query, $values);
+
+ /* Build SQL query. */
+ $query = 'INSERT INTO ' . $this->_params['table'] .
+ ' (cache_id, cache_timestamp, cache_expiration, cache_data)' .
+ ' VALUES (?, ?, ?, ?)';
+ $values = array($key, $timestamp, $expiration, $data);
+
+ $result = $this->_write_db->query($query, $values);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if a given key exists in the cache, valid for the given
+ * lifetime.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Maximum age of the key in seconds or 0 for
+ * any object.
+ *
+ * @return boolean Existence.
+ */
+ public function exists($key, $lifetime = 1)
+ {
+ $okey = $key;
+ $key = hash('md5', $key);
+
+ if ($this->_mc && $this->_mc->exists($key, $lifetime)) {
+ return true;
+ }
+
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return false;
+ }
+
+ /* Build SQL query. */
+ $query = 'SELECT 1 FROM ' . $this->_params['table'] .
+ ' WHERE cache_id = ?';
+ $values = array($key);
+
+ // 0 lifetime checks for objects which have no expiration
+ if ($lifetime != 0) {
+ $query .= ' AND cache_timestamp >= ?';
+ $values[] = time() - $lifetime;
+ }
+
+ $result = $this->_db->getRow($query, $values);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return false;
+ }
+
+ $timestamp = time();
+ if (empty($result)) {
+ Horde::logMessage(sprintf('Cache exists() miss: %s (Id %s newer than %d)', $okey, $key, $timestamp), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ return false;
+ }
+ Horde::logMessage(sprintf('Cache exists() hit: %s (Id %s newer than %d)', $okey, $key, $timestamp), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ return true;
+ }
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ public function expire($key)
+ {
+ $key = hash('md5', $key);
+
+ if ($this->_mc) {
+ $this->_mc->expire($key);
+ }
+
+ try {
+ $this->_connect();
+ } catch (Horde_Exception $e) {
+ Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return false;
+ }
+
+ $query = 'DELETE FROM ' . $this->_params['table'] .
+ ' WHERE cache_id = ?';
+ $values = array($key);
+
+ $result = $this->_write_db->query($query, $values);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Opens a connection to the SQL server.
+ *
+ * @throws Horde_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connected) {
+ return true;
+ }
+
+ $result = Util::assertDriverConfig($this->_params, array('phptype'),
+ 'cache SQL', array('driver' => 'cache'));
+ if (is_a($result, 'PEAR_Error')) {
+ throw new Horde_Exception($result->getMessage());
+ }
+
+ require_once 'DB.php';
+ $this->_write_db = &DB::connect(
+ $this->_params,
+ array('persistent' => !empty($this->_params['persistent']),
+ 'ssl' => !empty($this->_params['ssl']))
+ );
+ if (is_a($this->_write_db, 'PEAR_Error')) {
+ throw new Horde_Exception($this->_write_db->getMessage());
+ }
+
+ // Set DB portability options.
+ $portability = DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS;
+ if ($this->_write_db->phptype) {
+ $portability |= DB_PORTABILITY_RTRIM;
+ }
+ $this->_write_db->setOption('portability', $portability);
+
+ /* Check if we need to set up the read DB connection
+ * seperately. */
+ if (!empty($this->_params['splitread'])) {
+ $params = array_merge($this->_params, $this->_params['read']);
+ $this->_db = &DB::connect(
+ $params,
+ array('persistent' => !empty($params['persistent']),
+ 'ssl' => !empty($params['ssl']))
+ );
+ if (is_a($this->_db, 'PEAR_Error')) {
+ throw new Horde_Exception($this->_db->getMessage());
+ }
+
+ // Set DB portability options.
+ $portability = DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS;
+ if ($this->_db->phptype) {
+ $portability |= DB_PORTABILITY_RTRIM;
+ }
+ $this->_db->setOption('portability', $portability);
+ } else {
+ /* Default to the same DB handle for read. */
+ $this->_db = $this->_write_db;
+ }
+
+ $this->_connected = true;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_Xcache:: class provides an XCache implementation of
+ * the Horde caching system.
+ *
+ * Copyright 2006-2007 Duck <duck@obala.net>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Duck <duck@obala.net>
+ * @package Horde_Cache
+ */
+class Horde_Cache_Xcache extends Horde_Cache_Base
+{
+ /**
+ * Construct a new Horde_Cache_Xcache object.
+ *
+ * @param array $params Parameter array.
+ */
+ public function __construct($params = array())
+ {
+ parent::__construct($params);
+
+ if (empty($this->_params['prefix'])) {
+ $this->_params['prefix'] = $_SERVER['SERVER_NAME']
+ ? $_SERVER['SERVER_NAME']
+ : $_SERVER['HOSTNAME'];
+ }
+ }
+
+ /**
+ * Attempts to retrieve a piece of cached data and return it to the caller.
+ *
+ * @param string $key Cache key to fetch.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ public function get($key, $lifetime = 1)
+ {
+ $key = $this->_params['prefix'] . $key;
+ $this->_setExpire($key, $lifetime);
+ $result = xcache_get($key);
+
+ return empty($result)
+ ? false
+ : $result;
+ }
+
+ /**
+ * Attempts to store an object to the cache.
+ *
+ * @param string $key Cache key (identifier).
+ * @param mixed $data Data to store in the cache.
+ * @param integer $lifetime Data lifetime.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function set($key, $data, $lifetime = null)
+ {
+ $key = $this->_params['prefix'] . $key;
+ $lifetime = $this->_getLifetime($lifetime);
+ xcache_set($key . '_expire', time(), $lifetime);
+ return xcache_set($key, $data, $lifetime);
+ }
+
+ /**
+ * Checks if a given key exists in the cache, valid for the given
+ * lifetime.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return boolean Existence.
+ */
+ public function exists($key, $lifetime = 1)
+ {
+ $key = $this->_params['prefix'] . $key;
+ $this->_setExpire($key, $lifetime);
+ return xcache_isset($key);
+ }
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ public function expire($key)
+ {
+ $key = $this->_params['prefix'] . $key;
+ xcache_unset($key . '_expire');
+ return xcache_unset($key);
+ }
+
+ /**
+ * Set expire time on each call since memcache sets it on cache creation.
+ *
+ * @param string $key Cache key to expire.
+ * @param integer $lifetime Lifetime of the data in seconds.
+ */
+ protected function _setExpire($key, $lifetime)
+ {
+ if ($lifetime == 0) {
+ // don't expire
+ return;
+ }
+
+ $expire = xcache_get($key . '_expire');
+
+ // set prune period
+ if ($expire + $lifetime < time()) {
+ // Expired
+ xcache_unset($key . '_expire');
+ xcache_unset($key);
+ }
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * The Horde_Cache_Zps4:: class provides a Zend Performance Suite
+ * (version 4.0+) implementation of the Horde caching system.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Cache
+ */
+class Horde_Cache_Zps4 extends Horde_Cache_Base
+{
+ /**
+ * Attempts to retrieve a piece of cached data and return it to the
+ * caller.
+ *
+ * @param string $key Cache key to fetch.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return mixed Cached data, or false if none was found.
+ */
+ public function get($key, $lifetime = 1)
+ {
+ return output_cache_get($key, $lifetime);
+ }
+
+ /**
+ * Attempts to store an object to the cache.
+ *
+ * @param string $key Cache key (identifier).
+ * @param mixed $data Data to store in the cache.
+ * @param integer $lifetime Data lifetime. [Not used]
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function set($key, $data, $lifetime = null)
+ {
+ output_cache_put($key, $data);
+ return true;
+ }
+
+ /**
+ * Checks if a given key exists in the cache, valid for the given
+ * lifetime.
+ *
+ * @param string $key Cache key to check.
+ * @param integer $lifetime Lifetime of the key in seconds.
+ *
+ * @return boolean Existence.
+ */
+ public function exists($key, $lifetime = 1)
+ {
+ $exists = output_cache_exists($key, $lifetime);
+ output_cache_stop();
+ return $exists;
+ }
+
+ /**
+ * Expire any existing data for the given key.
+ *
+ * @param string $key Cache key to expire.
+ *
+ * @return boolean Success or failure.
+ */
+ public function expire($key)
+ {
+ return output_cache_remove_key($key);
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Cache</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Caching API</summary>
+ <description>This package provides a simple, functional caching API,
+with the option to store the cached data on the filesystem, in one of
+the PHP opcode cache systems (APC, eAcclerator, XCache, or Zend
+Performance Suite's content cache), memcached, or an SQL table.
+ </description>
+ <lead>
+ <name>Chuck Hagenbuch</name>
+ <user>chuck</user>
+ <email>chuck@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Michael Slusarz</name>
+ <user>slusarz</user>
+ <email>slusarz@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <date>2008-03-04</date>
+ <version>
+ <release>0.2.0</release>
+ <api>0.2.0</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial Horde 4 package.</notes>
+ <contents>
+ <dir name="/">
+ <dir name="data">
+ <file name="cache.sql" role="data" />
+ </dir> <!-- /data -->
+ <dir name="lib">
+ <dir name="Horde">
+ <dir name="Cache">
+ <file name="Apc.php" role="php" />
+ <file name="Base.php" role="php" />
+ <file name="Eaccelerator.php" role="php" />
+ <file name="File.php" role="php" />
+ <file name="Memcache.php" role="php" />
+ <file name="Null.php" role="php" />
+ <file name="Sql.php" role="php" />
+ <file name="Xcache.php" role="php" />
+ <file name="Zps4.php" role="php" />
+ </dir> <!-- /lib/Horde/Cache -->
+ <file name="Cache.php" role="php" />
+ </dir> <!-- /lib/Horde -->
+ </dir> <!-- /lib -->
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.5.0</min>
+ </pearinstaller>
+ <package>
+ <name>Util</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ </required>
+ <optional>
+ <extension>
+ <name>apc</name>
+ </extension>
+ <extension>
+ <name>eaccelerator</name>
+ </extension>
+ <extension>
+ <name>memcache</name>
+ </extension>
+ </optional>
+ </dependencies>
+ <phprelease>
+ <filelist>
+ <install name="lib/Horde/Cache/Apc.php" as="Horde/Cache/Apc.php" />
+ <install name="lib/Horde/Cache/Base.php" as="Horde/Cache/Base.php" />
+ <install name="lib/Horde/Cache/Eaccelerator.php" as="Horde/Cache/Eaccelerator.php" />
+ <install name="lib/Horde/Cache/File.php" as="Horde/Cache/File.php" />
+ <install name="lib/Horde/Cache/Memcache.php" as="Horde/Cache/Memcache.php" />
+ <install name="lib/Horde/Cache/Null.php" as="Horde/Cache/Null.php" />
+ <install name="lib/Horde/Cache/Sql.php" as="Horde/Cache/Sql.php" />
+ <install name="lib/Horde/Cache/Xcache.php" as="Horde/Cache/Xcache.php" />
+ <install name="lib/Horde/Cache/Zps4.php" as="Horde/Cache/Zps4.php" />
+ <install name="lib/Horde/Cache.php" as="Horde/Cache.php" />
+ </filelist>
+ </phprelease>
+ <changelog>
+ <release>
+ <date>2006-05-08</date>
+ <version>
+ <release>0.1.0</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Add SQL backend.
+* Converted to package.xml 2.0 for pear.horde.org.
+* Add APC, eAccelerator, and XCache backends (duck@obala.net).
+ </notes>
+ </release>
+ <release>
+ <version>
+ <release>0.0.1</release>
+ <api>0.0.1</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <date>2004-01-01</date>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>Initial packaging.
+ </notes>
+ </release>
+ </changelog>
+</package>