prefix = $config->prefix ?: ''; $this->path = ! empty($config->path) ? $config->path : WRITEPATH.'cache'; $this->path = rtrim($this->path, '/').'/'; } //-------------------------------------------------------------------- /** * Takes care of any handler-specific setup that must be done. */ public function initialize() { // Not to see here... } //-------------------------------------------------------------------- /** * Attempts to fetch an item from the cache store. * * @param string $key Cache item name * * @return mixed */ public function get(string $key) { $key = $this->prefix.$key; $data = $this->getItem($key); return is_array($data) ? $data['data'] : false; } //-------------------------------------------------------------------- /** * Saves an item to the cache store. * * The $raw parameter is only utilized by Mamcache in order to * allow usage of increment() and decrement(). * * @param string $key Cache item name * @param $value the data to save * @param null $ttl Time To Live, in seconds (default 60) * @param bool $raw Whether to store the raw value. * * @return mixed */ public function save(string $key, $value, int $ttl = 60, bool $raw = false) { $key = $this->prefix.$key; $contents = [ 'time' => time(), 'ttl' => $ttl, 'data' => $value, ]; if ($this->writeFile($this->path.$key, serialize($contents))) { chmod($this->path.$key, 0640); return true; } return false; } //-------------------------------------------------------------------- /** * Deletes a specific item from the cache store. * * @param string $key Cache item name * * @return mixed */ public function delete(string $key) { $key = $this->prefix.$key; return file_exists($this->path.$key) ? unlink($this->path.$key) : false; } //-------------------------------------------------------------------- /** * Performs atomic incrementation of a raw stored value. * * @param string $key Cache ID * @param int $offset Step/value to increase by * * @return mixed */ public function increment(string $key, int $offset = 1) { $key = $this->prefix.$key; $data = $this->getItem($key); if ($data === false) { $data = ['data' => 0, 'ttl' => 60]; } elseif (! is_int($data['data'])) { return false; } $new_value = $data['data']+$offset; return $this->save($key, $new_value, $data['ttl']) ? $new_value : false; } //-------------------------------------------------------------------- /** * Performs atomic decrementation of a raw stored value. * * @param string $key Cache ID * @param int $offset Step/value to increase by * * @return mixed */ public function decrement(string $key, int $offset = 1) { $key = $this->prefix.$key; $data = $this->getItem($key); if ($data === false) { $data = ['data' => 0, 'ttl' => 60]; } elseif (! is_int($data['data'])) { return false; } $new_value = $data['data']-$offset; return $this->save($key, $new_value, $data['ttl']) ? $new_value : false; } //-------------------------------------------------------------------- /** * Will delete all items in the entire cache. * * @return mixed */ public function clean() { return $this->deleteFiles($this->path, false, true); } //-------------------------------------------------------------------- /** * Returns information on the entire cache. * * The information returned and the structure of the data * varies depending on the handler. * * @return mixed */ public function getCacheInfo() { return $this->getDirFileInfo($this->path); } //-------------------------------------------------------------------- /** * Returns detailed information about the specific item in the cache. * * @param string $key Cache item name. * * @return mixed */ public function getMetaData(string $key) { $key = $this->prefix.$key; if ( ! file_exists($this->path.$key)) { return FALSE; } $data = unserialize(file_get_contents($this->path.$key)); if (is_array($data)) { $mtime = filemtime($this->path.$key); if ( ! isset($data['ttl'])) { return FALSE; } return array( 'expire' => $mtime + $data['ttl'], 'mtime' => $mtime ); } return FALSE; } //-------------------------------------------------------------------- /** * Determines if the driver is supported on this system. * * @return boolean */ public function isSupported(): bool { return is_writable($this->path); } //-------------------------------------------------------------------- /** * Does the heavy lifting of actually retrieving the file and * verifying it's age. * * @param string $key * * @return bool|mixed */ protected function getItem(string $key) { if (! is_file($this->path.$key)) { return null; } $data = unserialize(file_get_contents($this->path.$key)); if ($data['ttl'] > 0 && time() > $data['time']+$data['ttl']) { unlink($this->path.$key); return null; } return $data; } //-------------------------------------------------------------------- //-------------------------------------------------------------------- // SUPPORT METHODS FOR FILES //-------------------------------------------------------------------- /** * Writes a file to disk, or returns false if not successful. * * @param $path * @param $data * @param string $mode * * @return bool */ protected function writeFile($path, $data, $mode = 'wb') { if (! $fp = @fopen($path, $mode)) { return false; } flock($fp, LOCK_EX); for ($result = $written = 0, $length = strlen($data); $written < $length; $written += $result) { if (($result = fwrite($fp, substr($data, $written))) === false) { break; } } flock($fp, LOCK_UN); fclose($fp); return is_int($result); } //-------------------------------------------------------------------- /** * Delete Files * * Deletes all files contained in the supplied directory path. * Files must be writable or owned by the system in order to be deleted. * If the second parameter is set to TRUE, any directories contained * within the supplied base directory will be nuked as well. * * @param string $path File path * @param bool $del_dir Whether to delete any directories found in the path * @param bool $htdocs Whether to skip deleting .htaccess and index page files * @param int $_level Current directory depth level (default: 0; internal use only) * * @return bool */ protected function deleteFiles($path, $del_dir = false, $htdocs = false, $_level = 0) { // Trim the trailing slash $path = rtrim($path, '/\\'); if (! $current_dir = @opendir($path)) { return false; } while (false !== ($filename = @readdir($current_dir))) { if ($filename !== '.' && $filename !== '..') { if (is_dir($path.DIRECTORY_SEPARATOR.$filename) && $filename[0] !== '.') { $this->deleteFiles($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $htdocs, $_level+1); } elseif ($htdocs !== true || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename)) { @unlink($path.DIRECTORY_SEPARATOR.$filename); } } } closedir($current_dir); return ($del_dir === true && $_level > 0) ? @rmdir($path) : true; } //-------------------------------------------------------------------- /** * Get Directory File Information * * Reads the specified directory and builds an array containing the filenames, * filesize, dates, and permissions * * Any sub-folders contained within the specified path are read as well. * * @param string path to source * @param bool Look only at the top level directory specified? * @param bool internal variable to determine recursion status - do not use in calls * * @return array */ protected function getDirFileInfo($source_dir, $top_level_only = true, $_recursion = false) { static $_filedata = []; $relative_path = $source_dir; if ($fp = @opendir($source_dir)) { // reset the array and make sure $source_dir has a trailing slash on the initial call if ($_recursion === false) { $_filedata = []; $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; } // Used to be foreach (scandir($source_dir, 1) as $file), but scandir() is simply not as fast while (false !== ($file = readdir($fp))) { if (is_dir($source_dir.$file) && $file[0] !== '.' && $top_level_only === false) { $this->getDirFileInfo($source_dir.$file.DIRECTORY_SEPARATOR, $top_level_only, true); } elseif ($file[0] !== '.') { $_filedata[$file] = $this->getFileInfo($source_dir.$file); $_filedata[$file]['relative_path'] = $relative_path; } } closedir($fp); return $_filedata; } return false; } //-------------------------------------------------------------------- /** * Get File Info * * Given a file and path, returns the name, path, size, date modified * Second parameter allows you to explicitly declare what information you want returned * Options are: name, server_path, size, date, readable, writable, executable, fileperms * Returns FALSE if the file cannot be found. * * @param string path to file * @param mixed array or comma separated string of information returned * * @return array */ protected function getFileInfo($file, $returned_values = ['name', 'server_path', 'size', 'date']) { if (! file_exists($file)) { return false; } if (is_string($returned_values)) { $returned_values = explode(',', $returned_values); } foreach ($returned_values as $key) { switch ($key) { case 'name': $fileinfo['name'] = basename($file); break; case 'server_path': $fileinfo['server_path'] = $file; break; case 'size': $fileinfo['size'] = filesize($file); break; case 'date': $fileinfo['date'] = filemtime($file); break; case 'readable': $fileinfo['readable'] = is_readable($file); break; case 'writable': $fileinfo['writable'] = is_writable($file); break; case 'executable': $fileinfo['executable'] = is_executable($file); break; case 'fileperms': $fileinfo['fileperms'] = fileperms($file); break; } } return $fileinfo; } //-------------------------------------------------------------------- }