Add oodles of documentation to Page class, do refactoring, add smoketest guidelines.
[htmlpurifier-web.git] / xhtml-compiler / XHTMLCompiler / Page.php
blob01b8a1ef32c68872dba1815c1f5c911405c102a0
1 <?php
3 // TODO: Documentation
4 /**
5 * Represents a page in our content management system. This is loosely
6 * bound to the filesystem, although it doesn't actually refer to a
7 * specific file, just a class of files.
8 */
9 class XHTMLCompiler_Page
12 /**
13 * Filename identifier of this page without extension
15 protected $pathStem;
17 /** File extension of source files, no period */
18 protected $sourceExt = 'xhtml';
19 /** File extension of cache/served files, no period */
20 protected $cacheExt = 'html';
22 /**
23 * Constructs a page object, validates filename for correctness
24 * @param $path String path filename, can be from untrusted source
25 * @todo Cleanup into subroutines
26 * @todo Factor out allowed_directories realpath'ing to config class
27 * @todo Use exceptions rather than display_error_and_quit
29 public function __construct($path) {
31 $xc = XHTMLCompiler::getInstance();
33 // test file extension
34 $info = pathinfo($path);
35 if (
36 $info['extension'] !== $this->sourceExt &&
37 $info['extension'] !== $this->cacheExt
38 ) {
39 return display_error_and_quit(403);
42 // test for directory's existence and resolve to real path
43 $dir = $info['dirname'];
44 $dir = $xc->realpath($dir);
45 if ($dir === false) return display_error_and_quit(403);
47 $allowed_dirs = $xc->getConf('allowed_dirs');
48 $ok = false;
50 foreach ($allowed_dirs as $allowed_dir => $recursive) {
51 $allowed_dir = $xc->realpath($allowed_dir); // factor out!
52 if ($allowed_dir === false) continue;
53 if ($dir === $allowed_dir) {
54 $ok = true;
55 break;
56 // slash is required to prevent $allowed_dir = 'subdir' from
57 // matching $dir = 'subdirectory', thanks Mordred!
58 } elseif (strpos($dir, $allowed_dir . '/') === 0 && $recursive) {
59 $ok = true;
60 break;
64 if (!$ok) return display_error_and_quit(403);
66 // cannot use pathinfo, since PATHINFO_FILENAME is PHP 5.2.0
67 $this->pathStem = substr($path, 0, strrpos($path, '.'));
69 if (!$xc->isFile($this->getSourcePath())) {
70 // Apache may have redirected to an ErrorDocument which got directed
71 // via mod_rewrite to us, in that case, output the corresponding
72 // status code. Otherwise, we can give the regular 404.
73 $code = $xc->getRedirectStatus();
74 if (!$code || $code == 200) $code = 404;
75 display_error_and_quit($code);
79 /**
80 * Returns path stem, which is full filename without file extension
82 public function getPathStem() {
83 return $this->pathStem;
85 /** Returns path to cache/served file */
86 public function getCachePath() {
87 return $this->pathStem . '.' . $this->cacheExt;
89 /** Returns path to source file */
90 public function getSourcePath() {
91 return $this->pathStem . '.' . $this->sourceExt;
94 /** Returns contents of the cache/served file */
95 public function getCache() {
96 return file_get_contents($this->getCachePath());
98 /** Returns contents of the source file */
99 public function getSource() {
100 return file_get_contents($this->getSourcePath());
104 * Reports whether or not cache file exists and is a file
105 * @note Might want to use XHTMLCompiler server stub
107 public function isCacheExistent() {
108 return is_file($this->getCachePath());
111 * Reports whether or not source file exists and is a file
112 * @note Might want to use XHTMLCompiler server stub
114 public function isSourceExistent() {
115 return is_file($this->getSourcePath());
119 * Reports whether or not the cache is stale by comparing the file
120 * modification times between the source file and the cache file.
121 * @warning You must not call this function until you've also called
122 * isCacheExistent().
124 public function isCacheStale() {
125 if (!$this->isCacheExistent()) {
126 throw new Exception('Cannot check for stale cache when cache
127 does not exist, please call isCacheExistent and take
128 appropriate action with the result');
130 return filemtime($this->getSourcePath()) > filemtime($this->getCachePath());
134 * Writes text to the cache file, overwriting any previous contents
135 * and creating the cache file if it doesn't exist.
136 * @param $contents String contents to write to cache
138 public function writeCache($contents) {
139 file_put_contents($this->getCachePath(), $contents);
143 * Attempts to display contents from the cache, otherwise returns false
144 * @return True if successful, false if not.
145 * @note Purge check needs to be factored into XHTMLCompiler
147 public function tryCache() {
148 if (
149 !isset($_GET['purge']) &&
150 $this->isCacheExistent() &&
151 !$this->isCacheStale()
153 // cached version is fresh, serve it. This shouldn't happen normally
154 set_response_code(200); // if we used ErrorDocument, override
155 readfile($this->getCachePath());
156 return true;
158 return false;
162 * Generates the final version of a page from the source file and writes
163 * it to the cache.
164 * @note This function needs to be extended greatly
165 * @return Generated contents from source
167 public function generate() {
168 $source = $this->getSource();
169 $contents = $source . '<!-- generated by HTML Compiler -->'; // do processing
170 $this->writeCache($contents);
171 return $contents;
175 * Displays the page, either from cache or fresh regeneration.
177 public function display() {
178 if($this->tryCache()) return;
179 echo $this->generate();