PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/cms/code/staticpublisher/FilesystemPublisher.php

https://bitbucket.org/mwuits/mockup_nachz
PHP | 326 lines | 142 code | 48 blank | 136 comment | 46 complexity | efa7364fd611fe48ec7c9f5b00857f7c MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, GPL-2.0, AGPL-1.0, LGPL-2.1, LGPL-2.0, LGPL-3.0, GPL-3.0
  1. <?php
  2. /**
  3. * Usage: Object::add_extension("SiteTree", "FilesystemPublisher('static-folder', 'html')");
  4. *
  5. * Usage: To work with Subsite module you need to:
  6. * - Add FilesystemPublisher::$domain_based_caching = true; in mysite/_config.php
  7. * - Added main site host mapping in subsites/host-map.php after everytime a new subsite is created or modified
  8. *
  9. * You may also have a method $page->pagesAffectedByUnpublishing() to return other URLS
  10. * that should be de-cached if $page is unpublished.
  11. *
  12. * @see http://doc.silverstripe.com/doku.php?id=staticpublisher
  13. *
  14. * @package cms
  15. * @subpackage publishers
  16. */
  17. class FilesystemPublisher extends StaticPublisher {
  18. /**
  19. * @var String
  20. */
  21. protected $destFolder = 'cache';
  22. /**
  23. * @var String
  24. */
  25. protected $fileExtension = 'html';
  26. /**
  27. * @var String
  28. */
  29. protected static $static_base_url = null;
  30. /**
  31. * @var Boolean Use domain based cacheing (put cache files into a domain subfolder)
  32. * This must be true if you are using this with the "subsites" module.
  33. * Please note that this form of caching requires all URLs to be provided absolute
  34. * (not relative to the webroot) via {@link SiteTree->AbsoluteLink()}.
  35. */
  36. public static $domain_based_caching = false;
  37. /**
  38. * Set a different base URL for the static copy of the site.
  39. * This can be useful if you are running the CMS on a different domain from the website.
  40. */
  41. static function set_static_base_url($url) {
  42. self::$static_base_url = $url;
  43. }
  44. /**
  45. * @param $destFolder The folder to save the cached site into.
  46. * This needs to be set in framework/static-main.php as well through the {@link $cacheBaseDir} variable.
  47. * @param $fileExtension The file extension to use, e.g 'html'.
  48. * If omitted, then each page will be placed in its own directory,
  49. * with the filename 'index.html'. If you set the extension to PHP, then a simple PHP script will
  50. * be generated that can do appropriate cache & redirect header negotation.
  51. */
  52. function __construct($destFolder, $fileExtension = null) {
  53. // Remove trailing slash from folder
  54. if(substr($destFolder, -1) == '/') $destFolder = substr($destFolder, 0, -1);
  55. $this->destFolder = $destFolder;
  56. $this->fileExtension = $fileExtension;
  57. parent::__construct();
  58. }
  59. /**
  60. * Transforms relative or absolute URLs to their static path equivalent.
  61. * This needs to be the same logic that's used to look up these paths through
  62. * framework/static-main.php. Does not include the {@link $destFolder} prefix.
  63. *
  64. * URL filtering will have already taken place for direct SiteTree links via SiteTree->generateURLSegment()).
  65. * For all other links (e.g. custom controller actions), we assume that they're pre-sanitized
  66. * to suit the filesystem needs, as its impossible to sanitize them without risking to break
  67. * the underlying naming assumptions in URL routing (e.g. controller method names).
  68. *
  69. * Examples (without $domain_based_caching):
  70. * - http://mysite.com/mywebroot/ => /index.html (assuming your webroot is in a subfolder)
  71. * - http://mysite.com/about-us => /about-us.html
  72. * - http://mysite.com/parent/child => /parent/child.html
  73. *
  74. * Examples (with $domain_based_caching):
  75. * - http://mysite.com/mywebroot/ => /mysite.com/index.html (assuming your webroot is in a subfolder)
  76. * - http://mysite.com/about-us => /mysite.com/about-us.html
  77. * - http://myothersite.com/about-us => /myothersite.com/about-us.html
  78. * - http://subdomain.mysite.com/parent/child => /subdomain.mysite.com/parent/child.html
  79. *
  80. * @param Array $urls Absolute or relative URLs
  81. * @return Array Map of original URLs to filesystem paths (relative to {@link $destFolder}).
  82. */
  83. function urlsToPaths($urls) {
  84. $mappedUrls = array();
  85. foreach($urls as $url) {
  86. // parse_url() is not multibyte safe, see https://bugs.php.net/bug.php?id=52923.
  87. // We assume that the URL hsa been correctly encoded either on storage (for SiteTree->URLSegment),
  88. // or through URL collection (for controller method names etc.).
  89. $urlParts = @parse_url($url);
  90. // Remove base folders from the URL if webroot is hosted in a subfolder (same as static-main.php)
  91. $path = isset($urlParts['path']) ? $urlParts['path'] : '';
  92. if(mb_substr(mb_strtolower($path), 0, mb_strlen(BASE_URL)) == mb_strtolower(BASE_URL)) {
  93. $urlSegment = mb_substr($path, mb_strlen(BASE_URL));
  94. } else {
  95. $urlSegment = $path;
  96. }
  97. // Normalize URLs
  98. $urlSegment = trim($urlSegment, '/');
  99. $filename = $urlSegment ? "$urlSegment.$this->fileExtension" : "index.$this->fileExtension";
  100. if (self::$domain_based_caching) {
  101. if (!$urlParts) continue; // seriously malformed url here...
  102. $filename = $urlParts['host'] . '/' . $filename;
  103. }
  104. $mappedUrls[$url] = ((dirname($filename) == '/') ? '' : (dirname($filename).'/')).basename($filename);
  105. }
  106. return $mappedUrls;
  107. }
  108. function unpublishPages($urls) {
  109. // Do we need to map these?
  110. // Detect a numerically indexed arrays
  111. if (is_numeric(join('', array_keys($urls)))) $urls = $this->urlsToPaths($urls);
  112. // This can be quite memory hungry and time-consuming
  113. // @todo - Make a more memory efficient publisher
  114. increase_time_limit_to();
  115. increase_memory_limit_to();
  116. $cacheBaseDir = $this->getDestDir();
  117. foreach($urls as $url => $path) {
  118. if (file_exists($cacheBaseDir.'/'.$path)) {
  119. @unlink($cacheBaseDir.'/'.$path);
  120. }
  121. }
  122. }
  123. function publishPages($urls) {
  124. // Do we need to map these?
  125. // Detect a numerically indexed arrays
  126. if (is_numeric(join('', array_keys($urls)))) $urls = $this->urlsToPaths($urls);
  127. // This can be quite memory hungry and time-consuming
  128. // @todo - Make a more memory efficient publisher
  129. increase_time_limit_to();
  130. increase_memory_limit_to();
  131. // Set the appropriate theme for this publication batch.
  132. // This may have been set explicitly via StaticPublisher::static_publisher_theme,
  133. // or we can use the last non-null theme.
  134. if(!StaticPublisher::static_publisher_theme())
  135. SSViewer::set_theme(SSViewer::current_custom_theme());
  136. else
  137. SSViewer::set_theme(StaticPublisher::static_publisher_theme());
  138. $currentBaseURL = Director::baseURL();
  139. if(self::$static_base_url) Director::setBaseURL(self::$static_base_url);
  140. if($this->fileExtension == 'php') SSViewer::setOption('rewriteHashlinks', 'php');
  141. if(StaticPublisher::echo_progress()) echo $this->class.": Publishing to " . self::$static_base_url . "\n";
  142. $files = array();
  143. $i = 0;
  144. $totalURLs = sizeof($urls);
  145. foreach($urls as $url => $path) {
  146. if(self::$static_base_url) Director::setBaseURL(self::$static_base_url);
  147. $i++;
  148. if($url && !is_string($url)) {
  149. user_error("Bad url:" . var_export($url,true), E_USER_WARNING);
  150. continue;
  151. }
  152. if(StaticPublisher::echo_progress()) {
  153. echo " * Publishing page $i/$totalURLs: $url\n";
  154. flush();
  155. }
  156. Requirements::clear();
  157. if($url == "") $url = "/";
  158. if(Director::is_relative_url($url)) $url = Director::absoluteURL($url);
  159. $response = Director::test(str_replace('+', ' ', $url));
  160. Requirements::clear();
  161. singleton('DataObject')->flushCache();
  162. // Generate file content
  163. // PHP file caching will generate a simple script from a template
  164. if($this->fileExtension == 'php') {
  165. if(is_object($response)) {
  166. if($response->getStatusCode() == '301' || $response->getStatusCode() == '302') {
  167. $content = $this->generatePHPCacheRedirection($response->getHeader('Location'));
  168. } else {
  169. $content = $this->generatePHPCacheFile($response->getBody(), HTTP::get_cache_age(), date('Y-m-d H:i:s'));
  170. }
  171. } else {
  172. $content = $this->generatePHPCacheFile($response . '', HTTP::get_cache_age(), date('Y-m-d H:i:s'));
  173. }
  174. // HTML file caching generally just creates a simple file
  175. } else {
  176. if(is_object($response)) {
  177. if($response->getStatusCode() == '301' || $response->getStatusCode() == '302') {
  178. $absoluteURL = Director::absoluteURL($response->getHeader('Location'));
  179. $content = "<meta http-equiv=\"refresh\" content=\"2; URL=$absoluteURL\">";
  180. } else {
  181. $content = $response->getBody();
  182. }
  183. } else {
  184. $content = $response . '';
  185. }
  186. }
  187. $files[] = array(
  188. 'Content' => $content,
  189. 'Folder' => dirname($path).'/',
  190. 'Filename' => basename($path),
  191. );
  192. // Add externals
  193. /*
  194. $externals = $this->externalReferencesFor($content);
  195. if($externals) foreach($externals as $external) {
  196. // Skip absolute URLs
  197. if(preg_match('/^[a-zA-Z]+:\/\//', $external)) continue;
  198. // Drop querystring parameters
  199. $external = strtok($external, '?');
  200. if(file_exists("../" . $external)) {
  201. // Break into folder and filename
  202. if(preg_match('/^(.*\/)([^\/]+)$/', $external, $matches)) {
  203. $files[$external] = array(
  204. "Copy" => "../$external",
  205. "Folder" => $matches[1],
  206. "Filename" => $matches[2],
  207. );
  208. } else {
  209. user_error("Can't parse external: $external", E_USER_WARNING);
  210. }
  211. } else {
  212. $missingFiles[$external] = true;
  213. }
  214. }*/
  215. }
  216. if(self::$static_base_url) Director::setBaseURL($currentBaseURL);
  217. if($this->fileExtension == 'php') SSViewer::setOption('rewriteHashlinks', true);
  218. $base = BASE_PATH . "/$this->destFolder";
  219. foreach($files as $file) {
  220. Filesystem::makeFolder("$base/$file[Folder]");
  221. if(isset($file['Content'])) {
  222. $fh = fopen("$base/$file[Folder]$file[Filename]", "w");
  223. fwrite($fh, $file['Content']);
  224. fclose($fh);
  225. } else if(isset($file['Copy'])) {
  226. copy($file['Copy'], "$base/$file[Folder]$file[Filename]");
  227. }
  228. }
  229. }
  230. /**
  231. * Generate the templated content for a PHP script that can serve up the given piece of content with the given age and expiry
  232. */
  233. protected function generatePHPCacheFile($content, $age, $lastModified) {
  234. $template = file_get_contents(BASE_PATH . '/cms/code/staticpublisher/CachedPHPPage.tmpl');
  235. return str_replace(
  236. array('**MAX_AGE**', '**LAST_MODIFIED**', '**CONTENT**'),
  237. array((int)$age, $lastModified, $content),
  238. $template);
  239. }
  240. /**
  241. * Generate the templated content for a PHP script that can serve up a 301 redirect to the given destionation
  242. */
  243. protected function generatePHPCacheRedirection($destination) {
  244. $template = file_get_contents(BASE_PATH . '/cms/code/staticpublisher/CachedPHPRedirection.tmpl');
  245. return str_replace(
  246. array('**DESTINATION**'),
  247. array($destination),
  248. $template);
  249. }
  250. public function getDestDir() {
  251. return BASE_PATH . '/' . $this->destFolder;
  252. }
  253. /**
  254. * Return an array of all the existing static cache files, as a map of URL => file.
  255. * Only returns cache files that will actually map to a URL, based on urlsToPaths.
  256. */
  257. public function getExistingStaticCacheFiles() {
  258. $cacheDir = BASE_PATH . '/' . $this->destFolder;
  259. $urlMapper = array_flip($this->urlsToPaths($this->owner->allPagesToCache()));
  260. $output = array();
  261. // Glob each dir, then glob each one of those
  262. foreach(glob("$cacheDir/*", GLOB_ONLYDIR) as $cacheDir) {
  263. foreach(glob($cacheDir.'/*') as $cacheFile) {
  264. $mapKey = str_replace(BASE_PATH . "/cache/","",$cacheFile);
  265. if(isset($urlMapper[$mapKey])) {
  266. $url = $urlMapper[$mapKey];
  267. $output[$url] = $cacheFile;
  268. }
  269. }
  270. }
  271. return $output;
  272. }
  273. }