mirror of
				https://github.com/Icinga/icingaweb2.git
				synced 2025-11-04 05:05:01 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			276 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
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.
 | 
						|
     *
 | 
						|
     * @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;
 | 
						|
        }
 | 
						|
 | 
						|
        $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];
 | 
						|
    }
 | 
						|
}
 |