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;
}
}
?>