diff --git a/_config.php b/_config.php index dea406da..ec6b3419 100644 --- a/_config.php +++ b/_config.php @@ -15,7 +15,7 @@ Director::addRules(50, array( 'admin//ImageEditor/$Action' => 'ImageEditor', 'admin/cms//$Action/$ID/$OtherID' => 'CMSMain', 'PageComment//$Action/$ID' => 'PageComment_Controller', - 'dev/buildcache' => 'RebuildStaticCacheTask', + 'dev/buildcache/$Action' => 'RebuildStaticCacheTask', )); CMSMenu::populate_menu(); diff --git a/code/staticpublisher/CachedPHPPage.tmpl b/code/staticpublisher/CachedPHPPage.tmpl new file mode 100644 index 00000000..2fe875b6 --- /dev/null +++ b/code/staticpublisher/CachedPHPPage.tmpl @@ -0,0 +1,28 @@ + 0) { + header("Cache-Control: max-age=" . MAX_AGE); + header("Pragma:"); +} else { + header("Cache-Control: no-cache, max-age=0, must-revalidate"); +} + +header("Expires: " . gmdate('D, d M Y H:i:s', time() + MAX_AGE) . ' GMT'); +header("Last-modified: " . gmdate('D, d M Y H:i:s', strtotime(LAST_MODIFIED)) . ' GMT'); + +if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + if(strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= strtotime(LAST_MODIFIED)) { + header("Last-modified: " . gmdate('D, d M Y H:i:s', strtotime(LAST_MODIFIED)) . ' GMT', true, 304); + exit; + } +} + +?> +**CONTENT** diff --git a/code/staticpublisher/CachedPHPRedirection.tmpl b/code/staticpublisher/CachedPHPRedirection.tmpl new file mode 100644 index 00000000..755fcddd --- /dev/null +++ b/code/staticpublisher/CachedPHPRedirection.tmpl @@ -0,0 +1,20 @@ + 0) { + header("Cache-Control: max-age=" . MAX_AGE); + header("Pragma:"); +} else { + header("Cache-Control: no-cache, max-age=0, must-revalidate"); +} + +header("Expires: " . gmdate('D, d M Y H:i:s', time() + MAX_AGE) . ' GMT'); +header("Location: " . DESTINATION, true, 301); + +?> diff --git a/code/staticpublisher/FilesystemPublisher.php b/code/staticpublisher/FilesystemPublisher.php index 1a133226..1ddb12d4 100644 --- a/code/staticpublisher/FilesystemPublisher.php +++ b/code/staticpublisher/FilesystemPublisher.php @@ -20,7 +20,8 @@ class FilesystemPublisher extends StaticPublisher { /** * @param $destFolder The folder to save the cached site into * @param $fileExtension The file extension to use, for example, 'html'. If omitted, then each page will be placed - * in its own directory, with the filename 'index.html' + * in its own directory, with the filename 'index.html'. If you set the extension to PHP, then a simple PHP script will + * be generated that can do appropriate cache & redirect header negotation */ function __construct($destFolder, $fileExtension = null) { if(substr($destFolder, -1) == '/') $destFolder = substr($destFolder, 0, -1); @@ -28,7 +29,9 @@ class FilesystemPublisher extends StaticPublisher { $this->fileExtension = $fileExtension; } - function publishPages($urls) { + function publishPages($urls) { + // This can be quite memory hungry and time-consuming + // @todo - Make a more memory efficient publisher set_time_limit(0); ini_set("memory_limit" , -1); @@ -42,6 +45,11 @@ class FilesystemPublisher extends StaticPublisher { $totalURLs = sizeof($urls); foreach($urls as $url) { $i++; + + if($url && !is_string($url)) { + user_error("Bad url:" . var_export($url,true), E_USER_WARNING); + continue; + } if(StaticPublisher::echo_progress()) { echo " * Publishing page $i/$totalURLs: $url\n"; @@ -49,15 +57,47 @@ class FilesystemPublisher extends StaticPublisher { } Requirements::clear(); - $response = Director::test($url); + $response = Director::test(str_replace('+', ' ', $url)); Requirements::clear(); + + DataObject::flush_and_destroy_cache(); + DataObject::destroy_cached_get_calls(false); + DataObject::cache_get_calls(false); + + //echo 'Memory: ' . round(memory_get_usage()/100000)/10 . "\n"; /* if(!is_object($response)) { echo "String response for url '$url'\n"; print_r($response); }*/ - if(is_object($response)) $content = $response->getBody(); - else $content = $response . ''; + + // Generate file content + // PHP file caching will generate a simple script from a template + if($this->fileExtension == 'php') { + if(is_object($response)) { + if($response->getStatusCode() == '301' || $response->getStatusCode() == '302') { + $content = $this->generatePHPCacheRedirection($response->getHeader('Location')); + } else { + $content = $this->generatePHPCacheFile($response->getBody(), HTTP::get_cache_age(), date('Y-m-d H:i:s')); + } + } else { + $content = $this->generatePHPCacheFile($response . '', HTTP::get_cache_age(), date('Y-m-d H:i:s')); + } + + // HTML file caching generally just creates a simple file + } else { + if(is_object($response)) { + if($response->getStatusCode() == '301' || $response->getStatusCode() == '302') { + $absoluteURL = Director::absoluteURL($response->getHeader('Location')); + $content = ""; + } else { + $content = $response->getBody(); + } + } else { + $content = $response . ''; + } + } + if($this->fileExtension) $filename = $url ? "$url.$this->fileExtension" : "index.$this->fileExtension"; else $filename = $url ? "$url/index.html" : "index.html"; @@ -113,6 +153,27 @@ class FilesystemPublisher extends StaticPublisher { } } } + + /** + * Generate the templated content for a PHP script that can serve up the given piece of content with the given age and expiry + */ + protected function generatePHPCacheFile($content, $age, $lastModified) { + $template = file_get_contents('../cms/code/staticpublisher/CachedPHPPage.tmpl'); + return str_replace( + array('**MAX_AGE**', '**LAST_MODIFIED**', '**CONTENT**'), + array((int)$age, $lastModified, $content), + $template); + } + /** + * Generate the templated content for a PHP script that can serve up a 301 redirect to the given destionation + */ + protected function generatePHPCacheRedirection($destination) { + $template = file_get_contents('../cms/code/staticpublisher/CachedPHPRedirection.tmpl'); + return str_replace( + array('**DESTINATION**'), + array($destination), + $template); + } } -?> \ No newline at end of file +?> diff --git a/code/staticpublisher/RsyncMultiHostPublisher.php b/code/staticpublisher/RsyncMultiHostPublisher.php new file mode 100644 index 00000000..2f7b2e51 --- /dev/null +++ b/code/staticpublisher/RsyncMultiHostPublisher.php @@ -0,0 +1,37 @@ + $url) { + if(!is_string($url)) { + user_error("Bad URL: " . var_export($url, true), E_USER_WARNING); + continue; + } $url = Director::makeRelative($url); if(substr($url,-1) == '/') $url = substr($url,0,-1); $urls[$i] = $url; @@ -76,4 +80,4 @@ abstract class StaticPublisher extends DataObjectDecorator { } } -?> \ No newline at end of file +?> diff --git a/tasks/RebuildStaticCacheTask.php b/tasks/RebuildStaticCacheTask.php index 14a93a9b..e943ecf6 100644 --- a/tasks/RebuildStaticCacheTask.php +++ b/tasks/RebuildStaticCacheTask.php @@ -5,6 +5,8 @@ */ class RebuildStaticCacheTask extends Controller { function init() { + Versioned::reading_stage('live'); + if(!Director::is_cli() && !Director::isDev() && !Permission::check("ADMIN")) Security::permissionFailure(); parent::init(); } @@ -23,7 +25,7 @@ class RebuildStaticCacheTask extends Controller { if($_GET['urls']) $urls = $_GET['urls']; else $urls = $page->allPagesToCache(); - + $this->rebuildCache($urls, true); } @@ -40,21 +42,43 @@ class RebuildStaticCacheTask extends Controller { $page = singleton('Page'); foreach($urls as $i => $url) { + if($url && !is_string($url)) { + user_error("Bad URL: " . var_export($url, true), E_USER_WARNING); + continue; + } + $url = Director::makeRelative($url); - if(substr($url,-1) == '/') $url = substr($url,0,-1); - $urls[$i] = $url; + // Exclude absolute links + if(preg_match('/^https?:/', $url)) { + unset($urls[$i]); + } else { + if(substr($url,-1) == '/') $url = substr($url,0,-1); + $urls[$i] = $url; + } } $urls = array_unique($urls); - - if($removeAll) { - echo "Removing old cache... \n"; - flush(); + sort($urls); + + $start = isset($_GET['start']) ? $_GET['start'] : 0; + $count = isset($_GET['count']) ? $_GET['count'] : sizeof($urls); + if(($start + $count) > sizeof($urls)) $count = sizeof($urls) - $start; + + $urls = array_slice($urls, $start, $count); + + if(!isset($_GET['urls']) && $start == 0) { + echo "Removing old cache... "; Filesystem::removeFolder("../cache", true); echo "done.\n\n"; } - echo "Republishing " . sizeof($urls) . " urls...\n\n"; $page->publishPages($urls); echo "\n\n== Done! =="; } + + function show() { + $urls = singleton('Page')->allPagesToCache(); + echo "
\n";
+		print_r($urls);
+		echo "\n
"; + } }