<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Cache
 *
 * @copyright   Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Cache lite storage handler
 *
 * @see    http://pear.php.net/package/Cache_Lite/
 * @since  11.1
 */
class JCacheStorageCachelite extends JCacheStorage
{
	/**
	 * Static cache of the Cache_Lite instance
	 *
	 * @var    object
	 * @since  11.1
	 */
	protected static $CacheLiteInstance = null;

	/**
	 * Root path
	 *
	 * @var    string
	 * @since  11.1
	 */
	protected $_root;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   11.1
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		$this->_root = $options['cachebase'];

		$cloptions = array(
			'cacheDir' => $this->_root . '/',
			'lifeTime' => $this->_lifetime,
			'fileLocking' => $this->_locking,
			'automaticCleaningFactor' => isset($options['autoclean']) ? $options['autoclean'] : 200,
			'fileNameProtection' => false,
			'hashedDirectoryLevel' => 0,
			'caching' => $options['caching']);

		if (self::$CacheLiteInstance === null)
		{
			$this->initCache($cloptions);
		}
	}

	/**
	 * Instantiates the appropriate CacheLite object.
	 * Only initializes the engine if it does not already exist.
	 * Note this is a protected method
	 *
	 * @param   array  $cloptions  optional parameters
	 *
	 * @return  object
	 *
	 * @since   11.1
	 */
	protected function initCache($cloptions)
	{
		if (!class_exists('Cache_Lite'))
		{
			require_once 'Cache/Lite.php';
		}

		self::$CacheLiteInstance = new Cache_Lite($cloptions);

		return self::$CacheLiteInstance;
	}

	/**
	 * Get cached data from a file by id and group
	 *
	 * @param   string   $id         The cache data id.
	 * @param   string   $group      The cache data group.
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold.
	 *
	 * @return  mixed  Boolean false on failure or a cached data string.
	 *
	 * @since   11.1
	 */
	public function get($id, $group, $checkTime = true)
	{
		self::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');
		$this->_getCacheId($id, $group);
		$data = self::$CacheLiteInstance->get($this->rawname, $group);

		return $data;
	}

	/**
	 * Get all cached data
	 *
	 * @return  array
	 *
	 * @since   11.1
	 */
	public function getAll()
	{
		parent::getAll();

		$path = $this->_root;
		$folders = new DirectoryIterator($path);
		$data = array();

		foreach ($folders as $folder)
		{
			if (!$folder->isDir() || $folder->isDot())
			{
				continue;
			}

			$foldername = $folder->getFilename();

			$files = new DirectoryIterator($path . '/' . $foldername);
			$item  = new JCacheStorageHelper($foldername);

			foreach ($files as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				$filename = $file->getFilename();

				$item->updateSize(filesize($path . '/' . $foldername . '/' . $filename) / 1024);
			}

			$data[$foldername] = $item;
		}

		return $data;
	}

	/**
	 * Store the data to a file by id and group
	 *
	 * @param   string  $id     The cache data id.
	 * @param   string  $group  The cache data group.
	 * @param   string  $data   The data to store in cache.
	 *
	 * @return  boolean  True on success, false otherwise
	 *
	 * @since   11.1
	 */
	public function store($id, $group, $data)
	{
		$dir = $this->_root . '/' . $group;

		// If the folder doesn't exist try to create it
		if (!is_dir($dir))
		{
			// Make sure the index file is there
			$indexFile = $dir . '/index.html';
			@mkdir($dir) && file_put_contents($indexFile, '<!DOCTYPE html><title></title>');
		}

		// Make sure the folder exists
		if (!is_dir($dir))
		{
			return false;
		}

		self::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');
		$this->_getCacheId($id, $group);
		$success = self::$CacheLiteInstance->save($data, $this->rawname, $group);

		if ($success == true)
		{
			return $success;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Remove a cached data file by id and group
	 *
	 * @param   string  $id     The cache data id
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean  True on success, false otherwise
	 *
	 * @since   11.1
	 */
	public function remove($id, $group)
	{
		self::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');
		$this->_getCacheId($id, $group);
		$success = self::$CacheLiteInstance->remove($this->rawname, $group);

		if ($success == true)
		{
			return $success;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * @param   string  $group  The cache data group.
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup].
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   11.1
	 */
	public function clean($group, $mode = null)
	{
		jimport('joomla.filesystem.folder');
		jimport('joomla.filesystem.file');

		switch ($mode)
		{
			case 'notgroup':
				$clmode = 'notingroup';
				$success = self::$CacheLiteInstance->clean($group, $clmode);
				break;

			case 'group':
				if (is_dir($this->_root . '/' . $group))
				{
					$clmode = $group;
					self::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');
					$success = self::$CacheLiteInstance->clean($group, $clmode);
					// Remove sub-folders of folder; disable all filtering
					$folders = JFolder::folders($this->_root . '/' . $group, '.', false, true, array(), array());

					foreach ($folders as $folder)
					{
						if (is_link($folder))
						{
							if (JFile::delete($folder) !== true)
							{
								return false;
							}
						}
						elseif (JFolder::delete($folder) !== true)
						{
							return false;
						}
					}
				}
				else
				{
					$success = true;
				}

				break;

			default:
				if (is_dir($this->_root . '/' . $group))
				{
					$clmode = $group;
					self::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');
					$success = self::$CacheLiteInstance->clean($group, $clmode);
				}
				else
				{
					$success = true;
				}

				break;
		}

		if ($success == true)
		{
			return $success;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Garbage collect expired cache data
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   11.1
	 */
	public function gc()
	{
		$result = true;
		self::$CacheLiteInstance->setOption('automaticCleaningFactor', 1);
		self::$CacheLiteInstance->setOption('hashedDirectoryLevel', 1);
		$success1 = self::$CacheLiteInstance->_cleanDir($this->_root . '/', false, 'old');

		if (!($dh = opendir($this->_root . '/')))
		{
			return false;
		}

		while ($file = readdir($dh))
		{
			if (($file != '.') && ($file != '..') && ($file != '.svn'))
			{
				$file2 = $this->_root . '/' . $file;

				if (is_dir($file2))
				{
					$result = ($result && (self::$CacheLiteInstance->_cleanDir($file2 . '/', false, 'old')));
				}
			}
		}

		$success = ($success1 && $result);

		return $success;
	}

	/**
	 * Test to see if the cache storage is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		@include_once 'Cache/Lite.php';

		return class_exists('Cache_Lite');
	}
}
