detectPageId(); } $that = $this; ob_start(function ($content) use ($that, $pageId) { $that->set($content, $pageId); // http://php.net/manual/function.ob-start.php // -> If output_callback returns FALSE original input is sent to the browser. return false; }); ob_implicit_flush(false); } /** * Write content to page identity * * @param string $content * @param null|string $pageId * @throws Exception\LogicException */ public function set($content, $pageId = null) { $publicDir = $this->getOptions()->getPublicDir(); if ($publicDir === null) { throw new Exception\LogicException("Option 'public_dir' no set"); } if ($pageId === null) { $pageId = $this->detectPageId(); } $path = $this->pageId2Path($pageId); $file = $path . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId); $this->createDirectoryStructure($publicDir . DIRECTORY_SEPARATOR . $path); $this->putFileContent($publicDir . DIRECTORY_SEPARATOR . $file, $content); } /** * Get from cache * * @param null|string $pageId * @return bool|string * @throws Exception\LogicException * @throws Exception\RuntimeException */ public function get($pageId = null) { $publicDir = $this->getOptions()->getPublicDir(); if ($publicDir === null) { throw new Exception\LogicException("Option 'public_dir' no set"); } if ($pageId === null) { $pageId = $this->detectPageId(); } $file = $publicDir . DIRECTORY_SEPARATOR . $this->pageId2Path($pageId) . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId); if (file_exists($file)) { ErrorHandler::start(); $content = file_get_contents($file); $error = ErrorHandler::stop(); if ($content === false) { throw new Exception\RuntimeException( "Failed to read cached pageId '{$pageId}'", 0, $error ); } return $content; } } /** * Checks if a cache with given id exists * * @param null|string $pageId * @throws Exception\LogicException * @return bool */ public function has($pageId = null) { $publicDir = $this->getOptions()->getPublicDir(); if ($publicDir === null) { throw new Exception\LogicException("Option 'public_dir' no set"); } if ($pageId === null) { $pageId = $this->detectPageId(); } $file = $publicDir . DIRECTORY_SEPARATOR . $this->pageId2Path($pageId) . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId); return file_exists($file); } /** * Remove from cache * * @param null|string $pageId * @throws Exception\LogicException * @throws Exception\RuntimeException * @return bool */ public function remove($pageId = null) { $publicDir = $this->getOptions()->getPublicDir(); if ($publicDir === null) { throw new Exception\LogicException("Option 'public_dir' no set"); } if ($pageId === null) { $pageId = $this->detectPageId(); } $file = $publicDir . DIRECTORY_SEPARATOR . $this->pageId2Path($pageId) . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId); if (file_exists($file)) { ErrorHandler::start(); $res = unlink($file); $err = ErrorHandler::stop(); if (!$res) { throw new Exception\RuntimeException( "Failed to remove cached pageId '{$pageId}'", 0, $err ); } return true; } return false; } /** * Clear cached pages matching glob pattern * * @param string $pattern * @throws Exception\LogicException */ public function clearByGlob($pattern = '**') { $publicDir = $this->getOptions()->getPublicDir(); if ($publicDir === null) { throw new Exception\LogicException("Option 'public_dir' no set"); } $it = new \GlobIterator( $publicDir . '/' . $pattern, \GlobIterator::CURRENT_AS_SELF | \GlobIterator::SKIP_DOTS | \GlobIterator::UNIX_PATHS ); foreach ($it as $pathname => $entry) { if ($entry->isFile()) { unlink($pathname); } } } /** * Determine the page to save from the request * * @throws Exception\RuntimeException * @return string */ protected function detectPageId() { if (!isset($_SERVER['REQUEST_URI'])) { throw new Exception\RuntimeException("Can't auto-detect current page identity"); } return $_SERVER['REQUEST_URI']; } /** * Get filename for page id * * @param string $pageId * @return string */ protected function pageId2Filename($pageId) { if (substr($pageId, -1) === '/') { return $this->getOptions()->getIndexFilename(); } return basename($pageId); } /** * Get path for page id * * @param string $pageId * @return string */ protected function pageId2Path($pageId) { if (substr($pageId, -1) == '/') { $path = rtrim($pageId, '/'); } else { $path = dirname($pageId); } // convert requested "/" to the valid local directory separator if ('/' != DIRECTORY_SEPARATOR) { $path = str_replace('/', DIRECTORY_SEPARATOR, $path); } return $path; } /** * Write content to a file * * @param string $file File complete path * @param string $data Data to write * @return void * @throws Exception\RuntimeException */ protected function putFileContent($file, $data) { $options = $this->getOptions(); $locking = $options->getFileLocking(); $perm = $options->getFilePermission(); $umask = $options->getUmask(); if ($umask !== false && $perm !== false) { $perm = $perm & ~$umask; } ErrorHandler::start(); $umask = ($umask !== false) ? umask($umask) : false; $rs = file_put_contents($file, $data, $locking ? LOCK_EX : 0); if ($umask) { umask($umask); } if ($rs === false) { $err = ErrorHandler::stop(); throw new Exception\RuntimeException( "Error writing file '{$file}'", 0, $err ); } if ($perm !== false && !chmod($file, $perm)) { $oct = decoct($perm); $err = ErrorHandler::stop(); throw new Exception\RuntimeException("chmod('{$file}', 0{$oct}) failed", 0, $err); } ErrorHandler::stop(); } /** * Creates directory if not already done. * * @param string $pathname * @return void * @throws Exception\RuntimeException */ protected function createDirectoryStructure($pathname) { // Directory structure already exists if (file_exists($pathname)) { return; } $options = $this->getOptions(); $perm = $options->getDirPermission(); $umask = $options->getUmask(); if ($umask !== false && $perm !== false) { $perm = $perm & ~$umask; } ErrorHandler::start(); if ($perm === false) { // build-in mkdir function is enough $umask = ($umask !== false) ? umask($umask) : false; $res = mkdir($pathname, ($perm !== false) ? $perm : 0777, true); if ($umask !== false) { umask($umask); } if (!$res) { $oct = ($perm === false) ? '777' : decoct($perm); $err = ErrorHandler::stop(); throw new Exception\RuntimeException( "mkdir('{$pathname}', 0{$oct}, true) failed", 0, $err ); } if ($perm !== false && !chmod($pathname, $perm)) { $oct = decoct($perm); $err = ErrorHandler::stop(); throw new Exception\RuntimeException( "chmod('{$pathname}', 0{$oct}) failed", 0, $err ); } } else { // build-in mkdir function sets permission together with current umask // which doesn't work well on multo threaded webservers // -> create directories one by one and set permissions // find existing path and missing path parts $parts = array(); $path = $pathname; while (!file_exists($path)) { array_unshift($parts, basename($path)); $nextPath = dirname($path); if ($nextPath === $path) { break; } $path = $nextPath; } // make all missing path parts foreach ($parts as $part) { $path.= DIRECTORY_SEPARATOR . $part; // create a single directory, set and reset umask immediately $umask = ($umask !== false) ? umask($umask) : false; $res = mkdir($path, ($perm === false) ? 0777 : $perm, false); if ($umask !== false) { umask($umask); } if (!$res) { $oct = ($perm === false) ? '777' : decoct($perm); ErrorHandler::stop(); throw new Exception\RuntimeException( "mkdir('{$path}', 0{$oct}, false) failed" ); } if ($perm !== false && !chmod($path, $perm)) { $oct = decoct($perm); ErrorHandler::stop(); throw new Exception\RuntimeException( "chmod('{$path}', 0{$oct}) failed" ); } } } ErrorHandler::stop(); } /** * Returns the generated file name. * * @param null|string $pageId * @return string */ public function getFilename($pageId = null) { if ($pageId === null) { $pageId = $this->detectPageId(); } $publicDir = $this->getOptions()->getPublicDir(); $path = $this->pageId2Path($pageId); $file = $path . DIRECTORY_SEPARATOR . $this->pageId2Filename($pageId); return $publicDir . $file; } }