useFuel(false); $path = rtrim($path, '/') . '/'; $this->globalExpireFile = $path . self::globalExpireFilename; $this->path = $id ? $path . $id . '/' : $path; if(!is_dir($path)) { if(!$this->wire('files')->mkdir($path, true)) throw new WireException("Unable to create path: $path"); } if(!is_dir($this->path)) { if(!$this->wire('files')->mkdir($this->path)) throw new WireException("Unable to create path: {$this->path}"); } if(is_file($this->globalExpireFile)) { $this->globalExpireTime = @filemtime($this->globalExpireFile); } $this->primaryID = $id ? $id : 'primaryID'; $this->cacheTimeSeconds = (int) $cacheTimeSeconds; } /** * An extra part to be appended to the filename * * When a call to remove the cache is included, then all 'secondary' versions of it will be included too * * @param string|int $id * */ public function setSecondaryID($id) { if(!ctype_alnum("$id")) { $id = preg_replace('/[^-+_a-zA-Z0-9]/', '_', $id); } $this->secondaryID = $id; } /** * Build a filename for use by the cache * * Filename takes this form: /path/primaryID/primaryID.cache * Or /path/primaryID/secondaryID.cache * * @return string * */ protected function buildFilename() { $filename = $this->path; if($this->secondaryID) $filename .= $this->secondaryID; else $filename .= $this->primaryID; $filename .= self::cacheFileExtension; return $filename; } /** * Does the cache file exist? * * @return bool * */ public function exists() { if(!$this->secondaryID) return is_dir($this->path); $filename = $this->buildFilename(); return is_file($filename); } /** * Get the contents of the cache based on the primary or secondary ID * * @return string * */ public function get() { $filename = $this->buildFilename(); if(self::isCacheFile($filename) && $this->isCacheFileExpired($filename)) { $this->removeFilename($filename); return false; } // note file_get_contents returns boolean false if file can't be opened (i.e. if it's locked from a write) return @file_get_contents($filename); } /** * Is the given cache filename expired? * * @param string $filename * @return bool * */ protected function isCacheFileExpired($filename) { if(!$mtime = @filemtime($filename)) return false; if(($mtime + $this->cacheTimeSeconds < time()) || ($this->globalExpireTime && $mtime < $this->globalExpireTime)) { return true; } return false; } /** * Is the given filename a cache file? * * @param string $filename * @return bool * */ static protected function isCacheFile($filename) { $ext = self::cacheFileExtension; if(is_file($filename) && substr($filename, -1 * strlen($ext)) == $ext) return true; return false; } /** * Saves $data to the cache * * @param string $data * @return bool * */ public function save($data) { $filename = $this->buildFilename(); if(!is_file($filename)) { $dirname = dirname($filename); if(is_dir($dirname)) { $files = glob("$dirname/*.*"); $numFiles = count($files); if($numFiles >= self::maxCacheFiles) { // if the cache file doesn't already exist, and there are too many files here // then abort the cache save for security (we don't want to fill up the disk) // also go through and remove any expired files while we are here, to avoid // this limit interrupting more cache saves. foreach($files as $file) { if(self::isCacheFile($file) && $this->isCacheFileExpired($file)) $this->removeFilename($file); } return false; } } else { $this->wire('files')->mkdir("$dirname/", true); } } $result = file_put_contents($filename, $data); $this->wire('files')->chmod($filename); return $result; } /** * Removes all cache files for primaryID * * If any secondaryIDs were used, those are removed too * */ public function remove() { $dir = new \DirectoryIterator($this->path); foreach($dir as $file) { if($file->isDir() || $file->isDot()) continue; //if(strpos($file->getFilename(), self::cacheFileExtension)) @unlink($file->getPathname()); if(self::isCacheFile($file->getPathname())) $this->wire('files')->unlink($file->getPathname()); } return @rmdir($this->path); } /** * Removes just the given file, as opposed to remove() which removes the entire cache for primaryID * * @param string $filename * */ protected function removeFilename($filename) { $this->wire('files')->unlink($filename); } /** * Remove all cache files in the given path, recursively * * @param string $path Full path to the directory you want to clear out * @param bool $rmdir Set to true if you want to also remove the directory * @return int Number of files/dirs removed * */ static public function removeAll($path, $rmdir = false) { $dir = new \DirectoryIterator($path); $numRemoved = 0; foreach($dir as $file) { if($file->isDot()) continue; $pathname = $file->getPathname(); if($file->isDir()) { $numRemoved += self::removeAll($pathname, true); } else if($file->isFile() && (self::isCacheFile($pathname) || ($file->getFilename() == self::globalExpireFilename))) { if(wire('files')->unlink($pathname)) $numRemoved++; } } if($rmdir && rmdir($path)) $numRemoved++; return $numRemoved; } /** * Causes all cache files in this type to be immediately expired * * Note it does not remove any files, only places a globalExpireFile with an mtime newer than the cache files * */ public function expireAll() { $note = "The modification time of this file represents the time of the last usable cache file. " . "Cache files older than this file are considered expired. " . date('m/d/y H:i:s'); @file_put_contents($this->globalExpireFile, $note, LOCK_EX); } /** * Set the octal mode for files created by CacheFile * * @param string $mode * @deprecated No longer used * */ public function setChmodFile($mode) { $this->chmodFile = $mode; } /** * Set the octal mode for dirs created by CacheFile * * @param string $mode * @deprecated No longer used * */ public function setChmodDir($mode) { $this->chmodDir = $mode; } /** * CacheFile classes return a string of their cache filename * */ public function __toString() { return $this->buildFilename(); } }