2008-08-12 04:59:27 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Usage: Object::add_extension("SiteTree", "FilesystemPublisher('../static-folder/')")
|
2009-02-04 21:18:12 +01:00
|
|
|
*
|
2009-07-23 00:57:02 +02:00
|
|
|
* You may also have a method $page->pagesAffectedByUnpublishing() to return other URLS
|
|
|
|
* that should be de-cached if $page is unpublished.
|
|
|
|
*
|
2009-02-04 21:18:12 +01:00
|
|
|
* @see http://doc.silverstripe.com/doku.php?id=staticpublisher
|
|
|
|
*
|
|
|
|
* @package cms
|
|
|
|
* @subpackage publishers
|
2008-08-12 04:59:27 +02:00
|
|
|
*/
|
|
|
|
class FilesystemPublisher extends StaticPublisher {
|
|
|
|
protected $destFolder;
|
2009-07-23 00:57:02 +02:00
|
|
|
protected $fileExtension = 'html';
|
2008-08-12 04:59:27 +02:00
|
|
|
|
2008-11-18 00:03:39 +01:00
|
|
|
protected static $static_base_url = null;
|
|
|
|
|
2009-07-13 06:42:02 +02:00
|
|
|
/**
|
|
|
|
* Use domain based cacheing (put cache files into a domain subfolder)
|
|
|
|
* This must be true if you are using this with subsites.
|
|
|
|
*/
|
|
|
|
public static $domain_based_caching = false;
|
|
|
|
|
2008-11-18 00:03:39 +01:00
|
|
|
/**
|
|
|
|
* Set a different base URL for the static copy of the site.
|
|
|
|
* This can be useful if you are running the CMS on a different domain from the website.
|
|
|
|
*/
|
|
|
|
static function set_static_base_url($url) {
|
|
|
|
self::$static_base_url = $url;
|
|
|
|
}
|
|
|
|
|
2008-08-12 04:59:27 +02:00
|
|
|
/**
|
|
|
|
* @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
|
2008-12-15 02:31:01 +01:00
|
|
|
* 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
|
2008-08-12 04:59:27 +02:00
|
|
|
*/
|
|
|
|
function __construct($destFolder, $fileExtension = null) {
|
|
|
|
if(substr($destFolder, -1) == '/') $destFolder = substr($destFolder, 0, -1);
|
|
|
|
$this->destFolder = $destFolder;
|
|
|
|
$this->fileExtension = $fileExtension;
|
2009-07-07 03:27:51 +02:00
|
|
|
parent::__construct();
|
2008-08-12 04:59:27 +02:00
|
|
|
}
|
|
|
|
|
2009-07-23 00:57:02 +02:00
|
|
|
function urlsToPaths($urls) {
|
|
|
|
$mappedUrls = array();
|
|
|
|
foreach($urls as $url) {
|
|
|
|
$urlParts = @parse_url($url);
|
|
|
|
$urlParts['path'] = isset($urlParts['path']) ? $urlParts['path'] : '';
|
2010-02-19 01:32:16 +01:00
|
|
|
// perform similar transformations to SiteTree::generateURLSegment()
|
|
|
|
$urlSegment = $urlParts['path'];
|
|
|
|
$urlSegment = str_replace('&','-and-',$urlSegment);
|
|
|
|
$urlSegment = str_replace('&','-and-',$urlSegment);
|
|
|
|
$urlSegment = ereg_replace('[^A-Za-z0-9\/-]+','-',$urlSegment);
|
|
|
|
$urlSegment = ereg_replace('-+','-',$urlSegment);
|
|
|
|
$urlSegment = trim($urlSegment, '/');
|
2009-07-23 00:57:02 +02:00
|
|
|
|
|
|
|
$filename = $urlSegment ? "$urlSegment.$this->fileExtension" : "index.$this->fileExtension";
|
|
|
|
|
|
|
|
if (self::$domain_based_caching) {
|
|
|
|
if (!$urlParts) continue; // seriously malformed url here...
|
|
|
|
$filename = $urlParts['host'] . '/' . $filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
$mappedUrls[$url] = ((dirname($filename) == '/') ? '' : (dirname($filename).'/')).basename($filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $mappedUrls;
|
|
|
|
}
|
|
|
|
|
|
|
|
function unpublishPages($urls) {
|
|
|
|
// Do we need to map these?
|
|
|
|
// Detect a numerically indexed arrays
|
|
|
|
if (is_numeric(join('', array_keys($urls)))) $urls = $this->urlsToPaths($urls);
|
|
|
|
|
2008-12-15 02:31:01 +01:00
|
|
|
// This can be quite memory hungry and time-consuming
|
|
|
|
// @todo - Make a more memory efficient publisher
|
2009-06-28 04:37:15 +02:00
|
|
|
increase_time_limit_to();
|
|
|
|
increase_memory_limit_to();
|
2008-08-15 08:31:36 +02:00
|
|
|
|
2009-07-23 00:57:02 +02:00
|
|
|
$cacheBaseDir = $this->getDestDir();
|
2008-11-18 00:03:39 +01:00
|
|
|
|
2009-07-23 00:57:02 +02:00
|
|
|
foreach($urls as $url => $path) {
|
|
|
|
if (file_exists($cacheBaseDir.'/'.$path)) {
|
|
|
|
@unlink($cacheBaseDir.'/'.$path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function publishPages($urls) {
|
|
|
|
// Do we need to map these?
|
|
|
|
// Detect a numerically indexed arrays
|
|
|
|
if (is_numeric(join('', array_keys($urls)))) $urls = $this->urlsToPaths($urls);
|
|
|
|
|
|
|
|
// This can be quite memory hungry and time-consuming
|
|
|
|
// @todo - Make a more memory efficient publisher
|
|
|
|
increase_time_limit_to();
|
|
|
|
increase_memory_limit_to();
|
|
|
|
|
2010-04-21 07:13:02 +02:00
|
|
|
// Set the appropriate theme for this publication batch.
|
|
|
|
// This may have been set explicitly via StaticPublisher::static_publisher_theme,
|
|
|
|
// or we can use the last non-null theme.
|
|
|
|
if(!StaticPublisher::static_publisher_theme())
|
|
|
|
SSViewer::set_theme(SSViewer::current_custom_theme());
|
|
|
|
else
|
|
|
|
SSViewer::set_theme(StaticPublisher::static_publisher_theme());
|
|
|
|
|
2009-07-23 00:57:02 +02:00
|
|
|
$currentBaseURL = Director::baseURL();
|
2008-11-18 00:03:39 +01:00
|
|
|
if(self::$static_base_url) Director::setBaseURL(self::$static_base_url);
|
2009-11-05 02:19:31 +01:00
|
|
|
if($this->fileExtension == 'php') SSViewer::setOption('rewriteHashlinks', 'php');
|
2010-02-16 03:12:07 +01:00
|
|
|
if(StaticPublisher::echo_progress()) echo $this->class.": Publishing to " . self::$static_base_url . "\n";
|
2008-08-12 04:59:27 +02:00
|
|
|
$files = array();
|
|
|
|
$i = 0;
|
|
|
|
$totalURLs = sizeof($urls);
|
2009-07-23 00:57:02 +02:00
|
|
|
foreach($urls as $url => $path) {
|
2009-10-12 05:41:38 +02:00
|
|
|
if(self::$static_base_url) Director::setBaseURL(self::$static_base_url);
|
2008-08-12 04:59:27 +02:00
|
|
|
$i++;
|
2008-12-15 02:31:01 +01:00
|
|
|
|
|
|
|
if($url && !is_string($url)) {
|
|
|
|
user_error("Bad url:" . var_export($url,true), E_USER_WARNING);
|
|
|
|
continue;
|
|
|
|
}
|
2008-08-12 04:59:27 +02:00
|
|
|
|
|
|
|
if(StaticPublisher::echo_progress()) {
|
|
|
|
echo " * Publishing page $i/$totalURLs: $url\n";
|
|
|
|
flush();
|
|
|
|
}
|
2010-02-19 01:32:16 +01:00
|
|
|
|
2008-08-12 04:59:27 +02:00
|
|
|
Requirements::clear();
|
2008-12-15 02:31:01 +01:00
|
|
|
$response = Director::test(str_replace('+', ' ', $url));
|
2008-08-12 04:59:27 +02:00
|
|
|
Requirements::clear();
|
2008-12-15 02:31:01 +01:00
|
|
|
|
2010-01-13 01:02:48 +01:00
|
|
|
singleton('DataObject')->flushCache();
|
2009-07-13 06:42:02 +02:00
|
|
|
|
2008-12-15 02:31:01 +01:00
|
|
|
// 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 = "<meta http-equiv=\"refresh\" content=\"2; URL=$absoluteURL\">";
|
|
|
|
} else {
|
|
|
|
$content = $response->getBody();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$content = $response . '';
|
|
|
|
}
|
|
|
|
}
|
2009-07-13 06:42:02 +02:00
|
|
|
|
2009-07-23 00:57:02 +02:00
|
|
|
$files[] = array(
|
2008-08-12 04:59:27 +02:00
|
|
|
'Content' => $content,
|
2009-07-23 00:57:02 +02:00
|
|
|
'Folder' => dirname($path).'/',
|
|
|
|
'Filename' => basename($path),
|
2008-08-12 04:59:27 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
// Add externals
|
|
|
|
/*
|
|
|
|
$externals = $this->externalReferencesFor($content);
|
|
|
|
if($externals) foreach($externals as $external) {
|
|
|
|
// Skip absolute URLs
|
|
|
|
if(preg_match('/^[a-zA-Z]+:\/\//', $external)) continue;
|
|
|
|
// Drop querystring parameters
|
|
|
|
$external = strtok($external, '?');
|
|
|
|
|
|
|
|
if(file_exists("../" . $external)) {
|
|
|
|
// Break into folder and filename
|
|
|
|
if(preg_match('/^(.*\/)([^\/]+)$/', $external, $matches)) {
|
|
|
|
$files[$external] = array(
|
|
|
|
"Copy" => "../$external",
|
|
|
|
"Folder" => $matches[1],
|
|
|
|
"Filename" => $matches[2],
|
|
|
|
);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
user_error("Can't parse external: $external", E_USER_WARNING);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$missingFiles[$external] = true;
|
|
|
|
}
|
|
|
|
}*/
|
|
|
|
}
|
2008-11-18 00:03:39 +01:00
|
|
|
|
2009-07-23 00:57:02 +02:00
|
|
|
if(self::$static_base_url) Director::setBaseURL($currentBaseURL);
|
2009-11-05 02:19:31 +01:00
|
|
|
if($this->fileExtension == 'php') SSViewer::setOption('rewriteHashlinks', true);
|
2009-07-23 00:57:02 +02:00
|
|
|
|
2010-01-28 06:09:47 +01:00
|
|
|
$base = BASE_PATH . "/$this->destFolder";
|
2008-08-12 04:59:27 +02:00
|
|
|
foreach($files as $file) {
|
|
|
|
Filesystem::makeFolder("$base/$file[Folder]");
|
|
|
|
|
|
|
|
if(isset($file['Content'])) {
|
|
|
|
$fh = fopen("$base/$file[Folder]$file[Filename]", "w");
|
|
|
|
fwrite($fh, $file['Content']);
|
|
|
|
fclose($fh);
|
|
|
|
} else if(isset($file['Copy'])) {
|
|
|
|
copy($file['Copy'], "$base/$file[Folder]$file[Filename]");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-12-15 02:31:01 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2010-01-28 06:09:47 +01:00
|
|
|
$template = file_get_contents(BASE_PATH . '/cms/code/staticpublisher/CachedPHPPage.tmpl');
|
2008-12-15 02:31:01 +01:00
|
|
|
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) {
|
2010-01-28 06:09:47 +01:00
|
|
|
$template = file_get_contents(BASE_PATH . '/cms/code/staticpublisher/CachedPHPRedirection.tmpl');
|
2008-12-15 02:31:01 +01:00
|
|
|
return str_replace(
|
|
|
|
array('**DESTINATION**'),
|
|
|
|
array($destination),
|
|
|
|
$template);
|
|
|
|
}
|
2009-07-23 00:57:02 +02:00
|
|
|
|
|
|
|
public function getDestDir() {
|
2010-01-28 06:09:47 +01:00
|
|
|
return BASE_PATH . '/' . $this->destFolder;
|
2009-07-23 00:57:02 +02:00
|
|
|
}
|
2010-01-13 01:06:38 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return an array of all the existing static cache files, as a map of URL => file.
|
|
|
|
* Only returns cache files that will actually map to a URL, based on urlsToPaths.
|
|
|
|
*/
|
|
|
|
public function getExistingStaticCacheFiles() {
|
2010-01-28 06:09:47 +01:00
|
|
|
$cacheDir = BASE_PATH . '/' . $this->destFolder;
|
2010-01-13 01:06:38 +01:00
|
|
|
|
|
|
|
$urlMapper = array_flip($this->urlsToPaths($this->owner->allPagesToCache()));
|
|
|
|
|
|
|
|
$output = array();
|
|
|
|
|
|
|
|
// Glob each dir, then glob each one of those
|
|
|
|
foreach(glob("$cacheDir/*", GLOB_ONLYDIR) as $cacheDir) {
|
|
|
|
foreach(glob($cacheDir.'/*') as $cacheFile) {
|
2010-01-28 06:09:47 +01:00
|
|
|
$mapKey = str_replace(BASE_PATH . "/cache/","",$cacheFile);
|
2010-01-13 01:06:38 +01:00
|
|
|
if(isset($urlMapper[$mapKey])) {
|
|
|
|
$url = $urlMapper[$mapKey];
|
|
|
|
$output[$url] = $cacheFile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
2008-08-12 04:59:27 +02:00
|
|
|
}
|
2008-12-04 23:38:58 +01:00
|
|
|
|
2010-04-21 07:13:02 +02:00
|
|
|
?>
|