280 lines
6.6 KiB
PHP
280 lines
6.6 KiB
PHP
<?php
|
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
|
|
|
namespace Icinga\Web;
|
|
|
|
class FileCache
|
|
{
|
|
/**
|
|
* FileCache singleton instances
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static $instances = array();
|
|
|
|
/**
|
|
* Cache instance base directory
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $basedir;
|
|
|
|
/**
|
|
* Instance name
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $name;
|
|
|
|
/**
|
|
* Whether the cache is enabled
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $enabled = false;
|
|
|
|
/**
|
|
* The protected constructor creates a new instance with the given name
|
|
*
|
|
* @param string $name Cache instance name
|
|
*/
|
|
protected function __construct($name)
|
|
{
|
|
$this->name = $name;
|
|
$tmpdir = sys_get_temp_dir();
|
|
$basedir = $tmpdir . '/FileCache_' . $name;
|
|
|
|
if (file_exists($basedir) && is_writeable($basedir)) {
|
|
|
|
$this->basedir = $basedir;
|
|
$this->enabled = true;
|
|
|
|
} elseif (file_exists($tmpdir) && is_writeable($tmpdir)) {
|
|
|
|
if (mkdir($basedir, '0750', true)) {
|
|
$this->enabled = true;
|
|
$this->basedir = $basedir;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store the given content to the desired file name
|
|
*
|
|
* @param string $file new (relative) filename
|
|
* @param string $content the content to be stored
|
|
*
|
|
* @return bool whether the file has been stored
|
|
*/
|
|
public function store($file, $content)
|
|
{
|
|
if (! $this->enabled) {
|
|
|
|
return false;
|
|
}
|
|
|
|
return file_put_contents($this->filename($file), $content);
|
|
}
|
|
|
|
/**
|
|
* Find out whether a given file exists
|
|
*
|
|
* @param string $file the (relative) filename
|
|
* @param int $newerThan optional timestamp to compare against
|
|
*
|
|
* @return bool whether such file exists
|
|
*/
|
|
public function has($file, $newerThan = null)
|
|
{
|
|
if (! $this->enabled) {
|
|
|
|
return false;
|
|
}
|
|
|
|
$filename = $this->filename($file);
|
|
|
|
if (! file_exists($filename) || ! is_readable($filename)) {
|
|
|
|
return false;
|
|
}
|
|
|
|
if ($newerThan === null) {
|
|
|
|
return true;
|
|
}
|
|
|
|
$info = stat($file);
|
|
|
|
if ($info === false) {
|
|
|
|
return false;
|
|
}
|
|
|
|
return (int) $newerThan < $info['mtime'];
|
|
}
|
|
|
|
/**
|
|
* Get a specific file or false if no such file available
|
|
*
|
|
* @param string $file the disired file name
|
|
*
|
|
* @return string|bool Filename content or false
|
|
*/
|
|
public function get($file)
|
|
{
|
|
if ($this->has($file)) {
|
|
return file_get_contents($this->filename($file));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Send a specific file to the browser (output)
|
|
*
|
|
* @param string $file the disired file name
|
|
*
|
|
* @return bool Whether the file has been sent
|
|
*/
|
|
public function send($file)
|
|
{
|
|
if ($this->has($file)) {
|
|
readfile($this->filename($file));
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get absolute filename for a given file
|
|
*
|
|
* @param string $file the disired file name
|
|
*
|
|
* @return string absolute filename
|
|
*/
|
|
protected function filename($file)
|
|
{
|
|
return $this->basedir . '/' . $file;
|
|
}
|
|
|
|
/**
|
|
* Whether the given ETag matches a cached file
|
|
*
|
|
* If no ETag is given we'll try to fetch the one from the current
|
|
* HTTP request.
|
|
*
|
|
* @param string $file The cached file you want to check
|
|
* @param string $match The ETag to match against
|
|
*
|
|
* @return string|bool ETag on match, otherwise false
|
|
*/
|
|
public function etagMatchesCachedFile($file, $match = null)
|
|
{
|
|
return self::etagMatchesFiles($this->filename($file), $match);
|
|
}
|
|
|
|
/**
|
|
* Create an ETag for the given file
|
|
*
|
|
* @param string $file The desired cache file
|
|
*
|
|
* @return string your ETag
|
|
*/
|
|
public function etagForCachedFile($file)
|
|
{
|
|
return self::etagForFiles($this->filename($file));
|
|
}
|
|
|
|
/**
|
|
* Whether the given ETag matchesspecific file(s) on disk
|
|
*
|
|
* If no ETag is given we'll try to fetch the one from the current
|
|
* HTTP request. Respects HTTP Cache-Control: no-cache, if set.
|
|
*
|
|
* @param string|array $files file(s) to check
|
|
* @param string $match ETag to match against
|
|
*
|
|
* @return string|bool ETag on match, otherwise false
|
|
*/
|
|
public static function etagMatchesFiles($files, $match = null)
|
|
{
|
|
if ($match === null) {
|
|
$match = isset($_SERVER['HTTP_IF_NONE_MATCH'])
|
|
? trim($_SERVER['HTTP_IF_NONE_MATCH'], '"')
|
|
: false;
|
|
}
|
|
if (! $match) {
|
|
return false;
|
|
}
|
|
if (isset($_SERVER['HTTP_CACHE_CONTROL']) && $_SERVER['HTTP_CACHE_CONTROL'] === 'no-cache') {
|
|
return false;
|
|
}
|
|
|
|
$etag = self::etagForFiles($files);
|
|
return $match === $etag ? $etag : false;
|
|
}
|
|
|
|
/**
|
|
* Create ETag for the given files
|
|
*
|
|
* Custom algorithm creating an ETag based on filenames, mtimes
|
|
* and file sizes. Supports single files or a list of files. This
|
|
* way we are able to create ETags for virtual files depending on
|
|
* multiple source files (e.g. compressed JS, CSS).
|
|
*
|
|
* @param string|array $files Single file or a list of such
|
|
*
|
|
* @return string The generated ETag
|
|
*/
|
|
public static function etagForFiles($files)
|
|
{
|
|
if (is_string($files)) {
|
|
$files = array($files);
|
|
}
|
|
|
|
$sizes = array();
|
|
$mtimes = array();
|
|
|
|
foreach ($files as $file) {
|
|
$file = realpath($file);
|
|
if ($file !== false && $info = stat($file)) {
|
|
$mtimes[] = $info['mtime'];
|
|
$sizes[] = $info['size'];
|
|
} else {
|
|
$mtimes[] = time();
|
|
$sizes[] = 0;
|
|
}
|
|
}
|
|
|
|
return sprintf(
|
|
'%s-%s-%s',
|
|
hash('crc32', implode('|', $files)),
|
|
hash('crc32', implode('|', $sizes)),
|
|
hash('crc32', implode('|', $mtimes))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Factory creating your cache instance
|
|
*
|
|
* @param string $name Instance name
|
|
*
|
|
* @return FileCache
|
|
*/
|
|
public static function instance($name = 'icingaweb')
|
|
{
|
|
if ($name !== 'icingaweb') {
|
|
$name = 'icingaweb/modules/' . $name;
|
|
}
|
|
|
|
if (!array_key_exists($name, self::$instances)) {
|
|
self::$instances[$name] = new static($name);
|
|
}
|
|
|
|
return self::$instances[$name];
|
|
}
|
|
}
|