sourceExt && $info['extension'] !== $this->cacheExt ) ) { throw new XHTMLCompiler_Exception(403, 'Forbidden extension', 'File extension cannot be processed by XHTML Compiler, check for faulty .htaccess rules.'); } // test for directory's existence and resolve to real path $dir = $info['dirname']; if ($dir == '.') $dir .= '/'; $dir = $php->realpath($dir); if ($dir === false) { throw new XHTMLCompiler_Exception(404, 'Missing directory', 'Requested directory cannot be found; check your file path and try again.' ); } if ($dir[strlen($dir)-1] == '/') $dir = substr($dir, 0, -1); $allowed_dirs = $xc->getConf('allowed_dirs'); $ok = false; foreach ($allowed_dirs as $allowed_dir => $recursive) { $allowed_dir = $php->realpath($allowed_dir); // factor out! if (!is_string($allowed_dir)) continue; if ($dir === $allowed_dir) { $ok = true; break; // slash is required to prevent $allowed_dir = 'subdir' from // matching $dir = 'subdirectory', thanks Mordred! } elseif (strpos($dir, $allowed_dir . '/') === 0 && $recursive) { $ok = true; break; } } if (!$ok) throw new XHTMLCompiler_Exception(403, 'Forbidden directory', 'Requested directory is forbidden to XHTML Compiler; try accessing it directly or check for faulty .htaccess rules.'); // cannot use pathinfo, since PATHINFO_FILENAME is PHP 5.2.0 $this->pathStem = substr($path, 0, strrpos($path, '.')); // setup the files $this->source = new XHTMLCompiler_File($this->pathStem . '.' . $this->sourceExt); $this->cache = new XHTMLCompiler_File($this->pathStem . '.' . $this->cacheExt); $this->deps = new XHTMLCompiler_File($this->pathStem . '.' . $this->depsExt); $this->dir = new XHTMLCompiler_Directory(dirname($this->pathStem)); if (!$mute && !$this->source->exists()) { // Apache may have redirected to an ErrorDocument which got directed // via mod_rewrite to us, in that case, output the corresponding // status code. Otherwise, we can give the regular 404. $code = $php->getRedirectStatus(); if (!$code || $code == 200) $code = 404; throw new XHTMLCompiler_Exception($code, 'Page not found', 'Requested page not found; check the URL in your address bar.'); } } // Note: Do not use this functions internally inside the class /** Returns path stem, full filename without file extension */ public function getPathStem() { return $this->pathStem; } /** Returns relative path to cache */ public function getCachePath() { return $this->cache->getName(); } /** Returns relative path to source */ public function getSourcePath() { return $this->source->getName(); } /** Returns XHTMLCompiler_Directory representation of directory */ public function getDir() { return $this->dir; } /** Returns directory of the files without trailing slash */ public function getDirName() { return $this->dir->getName(); } /** Returns directory of the files with trailing slash (unless there is none) */ public function getDirSName() { return $this->dir->getSName(); } /** Returns how deep from the root the file is */ public function getDepth() { return substr_count($this->getSourcePath(), '/'); } /** Normalizes a relative path as if it were from this page's directory */ public function normalizePath($path) { return $this->getDirName() . '/' . $path; } /** * Returns a fully formed web path to the file */ public function getWebPath() { $xc = XHTMLCompiler::getInstance(); $domain = $xc->getConf('web_domain'); if (!$domain) { throw new Exception('Configuration value web_domain must be set for command line'); } return 'http://' . $domain . $xc->getConf('web_path') . '/' . $this->cache->getName(); } /** Returns contents of the cache/served file */ public function getCache() { return $this->cache->get(); } /** Returns contents of the source file */ public function getSource() { return $this->source->get(); } /** Reports whether or not cache file exists and is a file */ public function isCacheExistent() { return $this->cache->exists(); } /** Reports whether or not source file exists and is a file */ public function isSourceExistent() { return $this->source->exists(); } /** * Reports whether or not the cache is stale by comparing the file * modification times between the source file and the cache file. * @warning You must not call this function until you've also called * isCacheExistent(). */ public function isCacheStale() { if (!$this->cache->exists()) { throw new Exception('Cannot check for stale cache when cache does not exist, please call isCacheExistent and take appropriate action with the result'); } if ($this->source->getMTime() > $this->cache->getMTime()) return true; // check dependencies if (!$this->deps->exists()) return true; // we need a dependency file! $deps = unserialize($this->deps->get()); foreach ($deps as $filename => $time) { if ($time < filemtime($filename)) return true; } return false; } /** * Writes text to the cache file, overwriting any previous contents * and creating the cache file if it doesn't exist. * @param $contents String contents to write to cache */ public function writeCache($contents) {$this->cache->write($contents);} /** * Attempts to display contents from the cache, otherwise returns false * @return True if successful, false if not. * @todo Purge check needs to be factored into XHTMLCompiler */ public function tryCache() { if ( !isset($_GET['purge']) && $this->cache->exists() && !$this->isCacheStale() ) { // cached version is fresh, serve it. This shouldn't happen normally set_response_code(200); // if we used ErrorDocument, override readfile($this->getCachePath()); return true; } return false; } /** * Generates the final version of a page from the source file and writes * it to the cache. * @note This function needs to be extended greatly * @return Generated contents from source */ public function generate() { $source = $this->source->get(); $xc = XHTMLCompiler::getInstance(); $filters = $xc->getFilterManager(); $contents = $filters->process($source, $this); $deps = $filters->getDeps(); if (empty($contents)) return ''; // don't write, probably an error $contents .= ''; $this->cache->write($contents); $this->cache->chmod(0664); $this->deps->write(serialize($deps)); return $contents; } /** * Displays the page, either from cache or fresh regeneration. */ public function display() { if($this->tryCache()) return; $ret = $this->generate(); if ($ret) { if (stripos($_SERVER["HTTP_ACCEPT"], 'application/xhtml+xml') !== false) { header("Content-type: application/xhtml+xml"); } else { header("Content-type: text/html"); } } echo $ret; } // Subversion related functions protected $svnDate, $svnRevision, $svnAuthor, $svnHeadURL, $svnHeadURLMunged; public function registerSVNKeywords( $date, $revision, $author, $head_url ) { $this->svnDate = $date; $this->svnRevision = (int) $revision; $this->svnAuthor = $author; $this->svnHeadURL = $head_url; } protected function loadSVNKeywords() { // this is an expensive function // we should log calls to it $raw_status = shell_exec('svn info "'.$this->getSourcePath().'"'); if (!$raw_status) { throw new Exception('Attempt to grab SVN info for non-versioned file ' . $this->getCachePath()); } $raw_status = str_replace("\r", '', $raw_status); $raw_status = explode("\n", $raw_status); $status = array(); foreach ($raw_status as $i => $keyval) { if (empty($keyval)) continue; if (!strpos($keyval, ':')) continue; list($key, $value) = explode(': ', $keyval, 2); $status[$key] = $value; } $this->svnDate = $status['Last Changed Date']; $this->svnRevision = $status['Last Changed Rev']; $this->svnAuthor = $status['Last Changed Author']; $this->svnHeadURL = $status['URL']; } public function getSVNDate() { if (empty($this->svnDate)) $this->loadSVNKeywords(); return $this->svnDate; } public function getSVNRevision() { if (empty($this->svnRevision)) $this->loadSVNKeywords(); return $this->svnRevision; } public function getSVNAuthor() { if (empty($this->svnAuthor)) $this->loadSVNKeywords(); return $this->svnAuthor; } /** * @warning The Head URL may not be publically accessible if * svn+ssh:// or file:// protocols were used in the * working copy. */ public function getSVNHeadURL() { if (empty($this->svnHeadURL)) $this->loadSVNKeywords(); return $this->svnHeadURL; } /** * Returns the Head URL, but munged with svn_headurl_replace to * an accessible representation (see config.default.php for details) */ public function getSVNHeadURLMunged() { if (!empty($this->svnHeadURLMunged)) return $this->svnHeadURLMunged; $head_url = $this->getSVNHeadURL(); $xc = XHTMLCompiler::getInstance(); $pairs = $xc->getConf('svn_headurl_munge'); foreach ($pairs as $pair) { if (!(strpos($head_url, $pair[0]) === 0)) continue; $head_url = substr_replace($head_url, $pair[1], 0, strlen($pair[0])); break; } return $this->svnHeadURLMunged = $head_url; } } ?>