<?php
/**
* @version	$Id: cache.php 12734 2009-10-20 19:28:11Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class kCache extends kBase {

		/**
		 * Object, that represents cache storage
		 *
		 * @var CacheStorage
		 */
		var $_storage = null;

		var $statistics = Array ();
		var $debugCache = false;

		function kCache() {
			parent::kBase();

			$this->debugCache = $this->Application->isDebugMode() && constOn('DBG_CACHE');

			$memcached_servers = false; // 'localhost:11211'; // $this->Application->ConfigValue('MemcachedServers');

			if ($memcached_servers && class_exists('Memcache')) {
				$this->_storage = new MemcacheCacheStorage($memcached_servers);
			}
			/*else if (false || $this->Application->ConfigValue('UseFileCache')) {
				$this->_storage = new FileCacheStorage('file_cache.tmp');
			}*/
			else {
				$this->_storage = new CacheStorage();
			}

			if (!$this->_storage->isWorking()) {
				// when one of above cache storages fails to initialize fallback to memory cache
				$this->_storage = new CacheStorage();
			}
		}

		/**
		 * Adds new value to cache $cache_name and identified by key $key
		 *
		 * @param string $cache_name cache name
		 * @param int $key key name to add to cache
		 * @param mixed $value value of chached record
		 */
		function setCache($cache_name, $key, $value, $expires = 3600)
		{
			return $this->_storage->set($cache_name, $key, $value, $expires);
		}

		/**
		 * Returns cached $key value from cache named $cache_name
		 *
		 * @param string $cache_name cache name
		 * @param int $key key name from cache
		 * @return mixed
		 */
		function getCache($cache_name, $key)
		{
			$ret = $this->_storage->get($cache_name, $key);

			$this->setStatistics($cache_name, $key, $ret);

			return $ret;
		}

		function setStatistics($cache_name, $key, $found)
		{
			if (!$this->debugCache) {
				return true;
			}

			if (!array_key_exists($cache_name, $this->statistics)) {
				$this->statistics[$cache_name] = Array ();
			}

			if (!array_key_exists($key, $this->statistics[$cache_name])) {
				$this->statistics[$cache_name][$key] = Array ();
			}

			$status_key = $found ? 'found' : 'not_found';
			if (!isset($this->statistics[$cache_name][$key][$status_key])) {
				$this->statistics[$cache_name][$key][$status_key] = 0;
			}

			$this->statistics[$cache_name][$key][$status_key]++;
		}

		function printStatistics()
		{
			$cache_size = strlen(serialize($this->_storage));

			$this->Application->Debugger->appendHTML('<strong>Cache Size:</strong> ' . formatSize($cache_size) . ' (' . $cache_size . ')');

			foreach ($this->statistics as $cache_name => $cache_data) {
				foreach ($cache_data as $key => $value) {
					if (!array_key_exists('found', $value) || $value['found'] == 1) {
						// remove cached records, that were used only 1 or 2 times
						unset($this->statistics[$cache_name][$key]);
					}
				}
			}

			print_pre($this->statistics, 'Cache Statistics:');
		}
	}

	class CacheStorage extends Params {

		/**
		 * Determines, that cache storage is working fine
		 *
		 * @return bool
		 */
		function isWorking()
		{
			return true;
		}

		/**
		 * Stores value to cache
		 *
		 * @param string $cache_name
		 * @param string $key
		 * @param mixed $value
		 * @param int $expires cache record expiration time in seconds
		 */
		function set($cache_name, $key, $value, $expires)
		{
			$cache = parent::Get($cache_name, Array());
			$cache[$key] = $value;

			parent::Set($cache_name, $cache);
		}

		/**
		 * Returns value from cache
		 *
		 * @param string $cache_name
		 * @param string $key
		 * @return mixed
		 */
		function get($cache_name, $key)
		{
			$cache = parent::Get($cache_name, Array());
			$ret = array_key_exists($key, $cache) ? $cache[$key] : false;

			return $ret;
		}
	}

	class MemcacheCacheStorage {

		/**
		 * Memcache connection
		 *
		 * @var Memcache
		 */
		var $_handler = null;

		function MemcacheCacheStorage($servers)
		{
			$this->_handler = new Memcache;

			$servers = explode(';', $servers);
		 	foreach ($servers as $server) {
		 		list ($server, $port) = strpos($server, ':') !== false ? explode(':', $server, 2) : Array ($server, 11211);
		 		$this->_handler->addServer($server, $port);
		 	}
		}

		/**
		 * Determines, that cache storage is working fine
		 *
		 * @return bool
		 */
		function isWorking()
		{
			return $this->_handler->getVersion() !== false;
		}

		/**
		 * Stores value to cache
		 *
		 * @param string $cache_name
		 * @param string $key
		 * @param mixed $value
		 * @param int $expires cache record expiration time in seconds
		 */
		function set($cache_name, $key, $value, $expires)
		{
			return $this->_handler->set($cache_name . '-' . $key, $value, false, $expires); // false could be MEMCACHE_COMPRESSED to compress values in memory
		}

		/**
		 * Returns value from cache
		 *
		 * @param string $cache_name
		 * @param string $key
		 * @return mixed
		 */
		function get($cache_name, $key)
		{
			return $this->_handler->get($cache_name . '-' . $key);
		}
	}

	class FileCacheStorage extends Params {

		/**
		 * Expiration time for each variable in cache
		 *
		 * @var resource
		 */
		var $_expiration = Array ();

		/**
		 * Filename for storing cache
		 *
		 * @var string
		 */
		var $_filename = '';

		function FileCacheStorage($filename = '')
		{
			$this->_filename = WRITEABLE . '/cache' . '/' . $filename;

			if (file_exists($this->_filename)) {
				$cache_data = unserialize(file_get_contents($this->_filename));
			}
			else {
				$cache_data = Array ();
			}
		}

		/**
		 * Determines, that cache storage is working fine
		 *
		 * @return bool
		 */
		function isWorking()
		{
			return false;
		}

		/**
		 * Stores value to cache
		 *
		 * @param string $cache_name
		 * @param string $key
		 * @param mixed $value
		 * @param int $expires cache record expiration time in seconds
		 */
		function set($cache_name, $key, $value, $expires)
		{
			$cache = parent::Get($cache_name, Array());
			$cache[$key] = $value;

			parent::Set($cache_name, $cache);
		}

		/**
		 * Returns value from cache
		 *
		 * @param string $cache_name
		 * @param string $key
		 * @return mixed
		 */
		function get($cache_name, $key)
		{
			$cache = parent::Get($cache_name, Array());
			$ret = array_key_exists($key, $cache) ? $cache[$key] : false;

			return $ret;
		}
	}