name = $name; $tmpDir = sys_get_temp_dir(); $runtimePath = $tmpDir . '/FileCache_' . $name; if (is_dir($runtimePath)) { // Don't combine the following if with the above because else the elseif path will be evaluated if the // runtime path exists and is not writeable if (is_writeable($runtimePath)) { $this->basedir = $runtimePath; $this->enabled = true; } } elseif (is_dir($tmpDir) && is_writeable($tmpDir) && @mkdir($runtimePath, octdec('1750'), true)) { // Suppress mkdir errors because it may error w/ no such file directory if the systemd private tmp directory // for the web server has been removed $this->basedir = $runtimePath; $this->enabled = true; } } /** * 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($filename); 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; } /** * Prepare a sub directory with the given name and return its path * * @param string $name * * @return string|false Returns FALSE in case the cache is not enabled or an error occurred */ public function directory($name) { if (! $this->enabled) { return false; } $path = $this->filename($name); if (! is_dir($path) && ! @mkdir($path, octdec('1750'), true)) { return false; } return $path; } /** * 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 * * @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 (preg_match('/([0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{8})-\w+/i', $match, $matches)) { // Removes compression suffixes as our custom algorithm can't handle compressed cache files anyway $match = $matches[1]; } $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]; } }