Merge pull request #51 from helpfulrobot/convert-to-psr-2

Converted to PSR-2
This commit is contained in:
Damian Mooyman 2015-12-21 10:30:04 +13:00
commit 1a37a8033a
7 changed files with 1262 additions and 1186 deletions

View File

@ -14,250 +14,260 @@
* *
* @package staticpublisher * @package staticpublisher
*/ */
class StaticExporter extends Controller { class StaticExporter extends Controller
{
/** /**
* @config * @config
* *
* @var array $export_objects * @var array $export_objects
*/ */
private static $export_objects = array(); private static $export_objects = array();
/** /**
* @config * @config
* *
* @var bool * @var bool
*/ */
private static $disable_sitetree_export = false; private static $disable_sitetree_export = false;
/** /**
* @var array * @var array
*/ */
private static $allowed_actions = array( private static $allowed_actions = array(
'index', 'index',
'export', 'export',
'StaticExportForm' 'StaticExportForm'
); );
/** /**
* *
*/ */
public function __construct() { public function __construct()
parent::__construct(); {
parent::__construct();
if(class_exists('SiteTree')) { if (class_exists('SiteTree')) {
if(!$this->config()->get('disable_sitetree_export')) { if (!$this->config()->get('disable_sitetree_export')) {
$objs = $this->config()->export_objects; $objs = $this->config()->export_objects;
if (!is_array($objs)) { if (!is_array($objs)) {
$objs = array($objs); $objs = array($objs);
} }
if(!in_array('SiteTree', $objs)) { if (!in_array('SiteTree', $objs)) {
$objs[] = "SiteTree"; $objs[] = "SiteTree";
} }
$this->config()->export_objects = $objs; $this->config()->export_objects = $objs;
} }
} }
} }
/** /**
* *
*/ */
public function init() { public function init()
parent::init(); {
parent::init();
$canAccess = (Director::isDev() || Director::is_cli());
$canAccess = (Director::isDev() || Director::is_cli());
if(!Permission::check("ADMIN") && !$canAccess) { if (!Permission::check("ADMIN") && !$canAccess) {
return Security::permissionFailure($this); return Security::permissionFailure($this);
} }
} }
/** /**
* @param string $action * @param string $action
* *
* @return string * @return string
*/ */
public function Link($action = null) { public function Link($action = null)
return "dev/staticexporter/$action"; {
} return "dev/staticexporter/$action";
}
/** /**
* @param string $action * @param string $action
* *
* @return string * @return string
*/ */
public function AbsoluteLink($action = null) { public function AbsoluteLink($action = null)
return Director::absoluteURL($this->Link($action)); {
} return Director::absoluteURL($this->Link($action));
}
/**
* @return array /**
*/ * @return array
public function index() { */
return array( public function index()
'Title' => _t('StaticExporter.NAME','Static exporter'), {
'Form' => $this->StaticExportForm()->forTemplate() return array(
); 'Title' => _t('StaticExporter.NAME', 'Static exporter'),
} 'Form' => $this->StaticExportForm()->forTemplate()
);
/** }
* @return Form
*/ /**
public function StaticExportForm() { * @return Form
$form = new Form($this, 'StaticExportForm', new FieldList( */
new TextField('baseurl', _t('StaticExporter.BASEURL','Base URL')) public function StaticExportForm()
), new FieldList( {
new FormAction('export', _t('StaticExporter.EXPORT','Export')) $form = new Form($this, 'StaticExportForm', new FieldList(
)); new TextField('baseurl', _t('StaticExporter.BASEURL', 'Base URL'))
), new FieldList(
new FormAction('export', _t('StaticExporter.EXPORT', 'Export'))
));
return $form; return $form;
} }
public function export() { public function export()
if(isset($_REQUEST['baseurl'])) { {
$base = $_REQUEST['baseurl']; if (isset($_REQUEST['baseurl'])) {
$base = $_REQUEST['baseurl'];
if(substr($base,-1) != '/') $base .= '/'; if (substr($base, -1) != '/') {
$base .= '/';
}
Config::inst()->update('Director', 'alternate_base_url', $base); Config::inst()->update('Director', 'alternate_base_url', $base);
} } else {
else { $base = Director::baseURL();
$base = Director::baseURL(); }
}
$folder = TEMP_FOLDER . '/static-export'; $folder = TEMP_FOLDER . '/static-export';
$project = project(); $project = project();
$exported = $this->doExport($base, $folder .'/'. $project, false); $exported = $this->doExport($base, $folder .'/'. $project, false);
`cd $folder; tar -czhf $project-export.tar.gz $project`; `cd $folder; tar -czhf $project-export.tar.gz $project`;
$archiveContent = file_get_contents("$folder/$project-export.tar.gz"); $archiveContent = file_get_contents("$folder/$project-export.tar.gz");
// return as download to the client // return as download to the client
$response = SS_HTTPRequest::send_file( $response = SS_HTTPRequest::send_file(
$archiveContent, $archiveContent,
"$project-export.tar.gz", "$project-export.tar.gz",
'application/x-tar-gz' 'application/x-tar-gz'
); );
echo $response->output(); echo $response->output();
} }
/** /**
* Exports the website with the given base url. Returns the path where the * Exports the website with the given base url. Returns the path where the
* exported version of the website is located. * exported version of the website is located.
* *
* @param string website base url * @param string website base url
* @param string folder to export the site into * @param string folder to export the site into
* @param bool symlink assets * @param bool symlink assets
* @param bool suppress output progress * @param bool suppress output progress
* *
* @return string path to export * @return string path to export
*/ */
public function doExport($base, $folder, $symlink = true, $quiet = true) { public function doExport($base, $folder, $symlink = true, $quiet = true)
ini_set('max_execution_time', 0); {
ini_set('max_execution_time', 0);
Config::inst()->update('Director', 'alternate_base_url', $base); Config::inst()->update('Director', 'alternate_base_url', $base);
if(is_dir($folder)) { if (is_dir($folder)) {
Filesystem::removeFolder($folder); Filesystem::removeFolder($folder);
} }
Filesystem::makeFolder($folder); Filesystem::makeFolder($folder);
// symlink or copy /assets // symlink or copy /assets
$f1 = ASSETS_PATH; $f1 = ASSETS_PATH;
$f2 = Director::baseFolder() . '/' . project(); $f2 = Director::baseFolder() . '/' . project();
if($symlink) { if ($symlink) {
`cd $folder; ln -s $f1; ln -s $f2`; `cd $folder; ln -s $f1; ln -s $f2`;
} } else {
else { `cp -R $f1 $folder; cp -R $f2 $folder`;
`cp -R $f1 $folder; cp -R $f2 $folder`; }
}
// iterate through items we need to export // iterate through items we need to export
$urls = $this->getExportUrls(); $urls = $this->getExportUrls();
if($urls) { if ($urls) {
$total = count($urls); $total = count($urls);
$i = 1; $i = 1;
foreach($urls as $url) { foreach ($urls as $url) {
$subfolder = "$folder/" . trim($url, '/'); $subfolder = "$folder/" . trim($url, '/');
$contentfile = "$folder/" . trim($url, '/') . '/index.html'; $contentfile = "$folder/" . trim($url, '/') . '/index.html';
// Make the folder // Make the folder
if(!file_exists($subfolder)) { if (!file_exists($subfolder)) {
Filesystem::makeFolder($subfolder); Filesystem::makeFolder($subfolder);
} }
// Run the page // Run the page
Requirements::clear(); Requirements::clear();
DataObject::flush_and_destroy_cache(); DataObject::flush_and_destroy_cache();
$response = Director::test($url); $response = Director::test($url);
// Write to file // Write to file
if($fh = fopen($contentfile, 'w')) { if ($fh = fopen($contentfile, 'w')) {
if(!$quiet) { if (!$quiet) {
printf("-- (%s/%s) Outputting page (%s)%s", printf("-- (%s/%s) Outputting page (%s)%s",
$i, $i,
$total, $total,
$url, $url,
PHP_EOL PHP_EOL
); );
} }
fwrite($fh, $response->getBody()); fwrite($fh, $response->getBody());
fclose($fh); fclose($fh);
} }
$i++; $i++;
} }
} }
return $folder; return $folder;
} }
/** /**
* Return an array of urls to publish * Return an array of urls to publish
* *
* @return array * @return array
*/ */
public function getExportUrls() { public function getExportUrls()
$classes = $this->config()->get('export_objects'); {
$urls = array(); $classes = $this->config()->get('export_objects');
$urls = array();
foreach($classes as $obj) { foreach ($classes as $obj) {
if (!class_exists($obj)) { if (!class_exists($obj)) {
continue; continue;
} }
foreach ($obj::get() as $objInstance) { foreach ($obj::get() as $objInstance) {
$link = $objInstance->Link(); $link = $objInstance->Link();
$urls[$link] = $link; $urls[$link] = $link;
} }
} }
$this->extend('alterExportUrls', $urls); $this->extend('alterExportUrls', $urls);
// older api, keep around to ensure backwards compatibility // older api, keep around to ensure backwards compatibility
$objs = new ArrayList(); $objs = new ArrayList();
$this->extend('alterObjectsToExport', $objs); $this->extend('alterObjectsToExport', $objs);
if($objs) { if ($objs) {
foreach($objs as $obj) { foreach ($objs as $obj) {
$link = $obj->Link; $link = $obj->Link;
$urls[$link] = $link; $urls[$link] = $link;
} }
} }
return $urls; return $urls;
} }
} }

View File

@ -3,409 +3,432 @@
/** /**
* @package staticpublisher * @package staticpublisher
*/ */
class FilesystemPublisher extends StaticPublisher { class FilesystemPublisher extends StaticPublisher
{
/** /**
* @var string * @var string
*/ */
protected $destFolder = 'cache'; protected $destFolder = 'cache';
/** /**
* @var string * @var string
*/ */
protected $fileExtension = 'html'; protected $fileExtension = 'html';
/** /**
* @var string * @var string
* *
* @config * @config
*/ */
private static $static_base_url = null; private static $static_base_url = null;
/** /**
* @config * @config
* *
* @var Boolean Use domain based cacheing (put cache files into a domain subfolder) * @var Boolean Use domain based cacheing (put cache files into a domain subfolder)
* This must be true if you are using this with the "subsites" module. * This must be true if you are using this with the "subsites" module.
* Please note that this form of caching requires all URLs to be provided absolute * Please note that this form of caching requires all URLs to be provided absolute
* (not relative to the webroot) via {@link SiteTree->AbsoluteLink()}. * (not relative to the webroot) via {@link SiteTree->AbsoluteLink()}.
*/ */
private static $domain_based_caching = false; private static $domain_based_caching = false;
/** /**
* Set a different base URL for the static copy of the site. * 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. * This can be useful if you are running the CMS on a different domain from the website.
* *
* @deprecated 3.2 Use the "FilesystemPublisher.static_base_url" config setting instead * @deprecated 3.2 Use the "FilesystemPublisher.static_base_url" config setting instead
*/ */
static public function set_static_base_url($url) { public static function set_static_base_url($url)
Deprecation::notice('3.2', 'Use the "FilesystemPublisher.static_base_url" config setting instead'); {
Deprecation::notice('3.2', 'Use the "FilesystemPublisher.static_base_url" config setting instead');
Config::inst()->update('FilesystemPublisher', 'static_base_url', $url); Config::inst()->update('FilesystemPublisher', 'static_base_url', $url);
} }
/** /**
* @param $destFolder The folder to save the cached site into. * @param $destFolder The folder to save the cached site into.
* This needs to be set in framework/static-main.php as well through the {@link $cacheBaseDir} variable. * This needs to be set in framework/static-main.php as well through the {@link $cacheBaseDir} variable.
* @param $fileExtension The file extension to use, e.g 'html'. * @param $fileExtension The file extension to use, e.g 'html'.
* If omitted, then each page will be placed in its own directory, * If omitted, then each page will be placed in its own directory,
* with the filename 'index.html'. If you set the extension to PHP, then a simple PHP script will * 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. * be generated that can do appropriate cache & redirect header negotation.
*/ */
public function __construct($destFolder = 'cache', $fileExtension = null) { public function __construct($destFolder = 'cache', $fileExtension = null)
// Remove trailing slash from folder {
if(substr($destFolder, -1) == '/') { // Remove trailing slash from folder
$destFolder = substr($destFolder, 0, -1); if (substr($destFolder, -1) == '/') {
} $destFolder = substr($destFolder, 0, -1);
}
$this->destFolder = $destFolder;
if($fileExtension) {
$this->fileExtension = $fileExtension;
}
parent::__construct();
}
/**
* Transforms relative or absolute URLs to their static path equivalent.
* This needs to be the same logic that's used to look up these paths through
* framework/static-main.php. Does not include the {@link $destFolder} prefix.
*
* URL filtering will have already taken place for direct SiteTree links via SiteTree->generateURLSegment()).
* For all other links (e.g. custom controller actions), we assume that they're pre-sanitized
* to suit the filesystem needs, as its impossible to sanitize them without risking to break
* the underlying naming assumptions in URL routing (e.g. controller method names).
*
* Examples (without $domain_based_caching):
* - http://mysite.com/mywebroot/ => /index.html (assuming your webroot is in a subfolder)
* - http://mysite.com/about-us => /about-us.html
* - http://mysite.com/parent/child => /parent/child.html
*
* Examples (with $domain_based_caching):
* - http://mysite.com/mywebroot/ => /mysite.com/index.html (assuming your webroot is in a subfolder)
* - http://mysite.com/about-us => /mysite.com/about-us.html
* - http://myothersite.com/about-us => /myothersite.com/about-us.html
* - http://subdomain.mysite.com/parent/child => /subdomain.mysite.com/parent/child.html
*
* @param array $urls Absolute or relative URLs
* @return array Map of original URLs to filesystem paths (relative to {@link $destFolder}).
*/
public function urlsToPaths($urls) {
$mappedUrls = array();
foreach($urls as $url) {
// parse_url() is not multibyte safe, see https://bugs.php.net/bug.php?id=52923.
// We assume that the URL hsa been correctly encoded either on storage (for SiteTree->URLSegment),
// or through URL collection (for controller method names etc.).
$urlParts = @parse_url($url);
// Remove base folders from the URL if webroot is hosted in a subfolder (same as static-main.php)
$path = isset($urlParts['path']) ? $urlParts['path'] : '';
if(mb_substr(mb_strtolower($path), 0, mb_strlen(BASE_URL)) == mb_strtolower(BASE_URL)) {
$urlSegment = mb_substr($path, mb_strlen(BASE_URL));
} else {
$urlSegment = $path;
}
// Normalize URLs
$urlSegment = trim($urlSegment, '/');
$filename = $urlSegment ? "$urlSegment.$this->fileExtension" : "index.$this->fileExtension";
if (Config::inst()->get('FilesystemPublisher', 'domain_based_caching')) {
if (!$urlParts) continue; // seriously malformed url here...
$filename = $urlParts['host'] . '/' . $filename;
}
$mappedUrls[$url] = ((dirname($filename) == '/') ? '' : (dirname($filename).'/')).basename($filename);
}
return $mappedUrls;
}
/**
* @param array $urls
*/
public 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);
// This can be quite memory hungry and time-consuming
// @todo - Make a more memory efficient publisher
increase_time_limit_to();
increase_memory_limit_to();
$cacheBaseDir = $this->getDestDir();
foreach($urls as $url => $path) {
if (file_exists($cacheBaseDir.'/'.$path)) {
@unlink($cacheBaseDir.'/'.$path);
}
}
}
/**
* Uses {@link Director::test()} to perform in-memory HTTP requests
* on the passed-in URLs.
*
* @param array $urls Relative URLs
* @return array Result, keyed by URL. Keys:
* - "statuscode": The HTTP status code
* - "redirect": A redirect location (if applicable)
* - "path": The filesystem path where the cache has been written
*/
public function publishPages($urls) {
// Save current stage and temporarily force it to Live
$oldStage = Versioned::current_stage();
Versioned::reading_stage("Live");
$result = array(); $this->destFolder = $destFolder;
//nest the config so we can make changes to the config and revert easily if ($fileExtension) {
Config::nest(); $this->fileExtension = $fileExtension;
}
parent::__construct();
}
/**
* Transforms relative or absolute URLs to their static path equivalent.
* This needs to be the same logic that's used to look up these paths through
* framework/static-main.php. Does not include the {@link $destFolder} prefix.
*
* URL filtering will have already taken place for direct SiteTree links via SiteTree->generateURLSegment()).
* For all other links (e.g. custom controller actions), we assume that they're pre-sanitized
* to suit the filesystem needs, as its impossible to sanitize them without risking to break
* the underlying naming assumptions in URL routing (e.g. controller method names).
*
* Examples (without $domain_based_caching):
* - http://mysite.com/mywebroot/ => /index.html (assuming your webroot is in a subfolder)
* - http://mysite.com/about-us => /about-us.html
* - http://mysite.com/parent/child => /parent/child.html
*
* Examples (with $domain_based_caching):
* - http://mysite.com/mywebroot/ => /mysite.com/index.html (assuming your webroot is in a subfolder)
* - http://mysite.com/about-us => /mysite.com/about-us.html
* - http://myothersite.com/about-us => /myothersite.com/about-us.html
* - http://subdomain.mysite.com/parent/child => /subdomain.mysite.com/parent/child.html
*
* @param array $urls Absolute or relative URLs
* @return array Map of original URLs to filesystem paths (relative to {@link $destFolder}).
*/
public function urlsToPaths($urls)
{
$mappedUrls = array();
foreach ($urls as $url) {
// Do we need to map these? // parse_url() is not multibyte safe, see https://bugs.php.net/bug.php?id=52923.
// Detect a numerically indexed arrays // We assume that the URL hsa been correctly encoded either on storage (for SiteTree->URLSegment),
if (is_numeric(join('', array_keys($urls)))) $urls = $this->urlsToPaths($urls); // or through URL collection (for controller method names etc.).
$urlParts = @parse_url($url);
// This can be quite memory hungry and time-consuming
// @todo - Make a more memory efficient publisher // Remove base folders from the URL if webroot is hosted in a subfolder (same as static-main.php)
increase_time_limit_to(); $path = isset($urlParts['path']) ? $urlParts['path'] : '';
increase_memory_limit_to(); if (mb_substr(mb_strtolower($path), 0, mb_strlen(BASE_URL)) == mb_strtolower(BASE_URL)) {
$urlSegment = mb_substr($path, mb_strlen(BASE_URL));
// Set the appropriate theme for this publication batch. } else {
// This may have been set explicitly via StaticPublisher::static_publisher_theme, $urlSegment = $path;
// or we can use the last non-null theme. }
$customTheme = Config::inst()->get('StaticPublisher', 'static_publisher_theme');
if($customTheme) { // Normalize URLs
Config::inst()->update('SSViewer', 'theme', $customTheme); $urlSegment = trim($urlSegment, '/');
}
// Ensure that the theme that is set gets used. $filename = $urlSegment ? "$urlSegment.$this->fileExtension" : "index.$this->fileExtension";
Config::inst()->update('SSViewer', 'theme_enabled', true);
$staticBaseUrl = Config::inst()->get('FilesystemPublisher', 'static_base_url');
if($staticBaseUrl) {
Config::inst()->update('Director', 'alternate_base_url', $staticBaseUrl);
}
if($this->fileExtension == 'php') {
Config::inst()->update('SSViewer', 'rewrite_hash_links', 'php');
}
if(Config::inst()->get('StaticPublisher', 'echo_progress')) {
echo $this->class.": Publishing to " . $staticBaseUrl . "\n";
}
$files = array();
$i = 0;
$totalURLs = sizeof($urls);
foreach($urls as $url => $path) { if (Config::inst()->get('FilesystemPublisher', 'domain_based_caching')) {
$origUrl = $url; if (!$urlParts) {
$result[$origUrl] = array( continue;
'statuscode' => null, } // seriously malformed url here...
'redirect' => null, $filename = $urlParts['host'] . '/' . $filename;
'path' => null }
);
$mappedUrls[$url] = ((dirname($filename) == '/') ? '' : (dirname($filename).'/')).basename($filename);
}
$i++; return $mappedUrls;
}
/**
* @param array $urls
*/
public 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);
}
// This can be quite memory hungry and time-consuming
// @todo - Make a more memory efficient publisher
increase_time_limit_to();
increase_memory_limit_to();
$cacheBaseDir = $this->getDestDir();
foreach ($urls as $url => $path) {
if (file_exists($cacheBaseDir.'/'.$path)) {
@unlink($cacheBaseDir.'/'.$path);
}
}
}
/**
* Uses {@link Director::test()} to perform in-memory HTTP requests
* on the passed-in URLs.
*
* @param array $urls Relative URLs
* @return array Result, keyed by URL. Keys:
* - "statuscode": The HTTP status code
* - "redirect": A redirect location (if applicable)
* - "path": The filesystem path where the cache has been written
*/
public function publishPages($urls)
{
// Save current stage and temporarily force it to Live
$oldStage = Versioned::current_stage();
Versioned::reading_stage("Live");
$result = array();
if($url && !is_string($url)) { //nest the config so we can make changes to the config and revert easily
user_error("Bad url:" . var_export($url,true), E_USER_WARNING); Config::nest();
continue;
}
if(Config::inst()->get('StaticPublisher', 'echo_progress')) {
echo " * Publishing page $i/$totalURLs: $url\n";
flush();
}
Requirements::clear(); // Do we need to map these?
// Detect a numerically indexed arrays
if($url == "") $url = "/"; if (is_numeric(join('', array_keys($urls)))) {
if(Director::is_relative_url($url)) $url = Director::absoluteURL($url); $urls = $this->urlsToPaths($urls);
$response = Director::test(str_replace('+', ' ', $url)); }
// This can be quite memory hungry and time-consuming
// @todo - Make a more memory efficient publisher
increase_time_limit_to();
increase_memory_limit_to();
// 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.
$customTheme = Config::inst()->get('StaticPublisher', 'static_publisher_theme');
if (!$response) continue; if ($customTheme) {
Config::inst()->update('SSViewer', 'theme', $customTheme);
}
if($response) { // Ensure that the theme that is set gets used.
$result[$origUrl]['statuscode'] = $response->getStatusCode(); Config::inst()->update('SSViewer', 'theme_enabled', true);
}
Requirements::clear(); $staticBaseUrl = Config::inst()->get('FilesystemPublisher', 'static_base_url');
singleton('DataObject')->flushCache(); if ($staticBaseUrl) {
Config::inst()->update('Director', 'alternate_base_url', $staticBaseUrl);
}
if ($this->fileExtension == 'php') {
Config::inst()->update('SSViewer', 'rewrite_hash_links', 'php');
}
if (Config::inst()->get('StaticPublisher', 'echo_progress')) {
echo $this->class.": Publishing to " . $staticBaseUrl . "\n";
}
$files = array();
$i = 0;
$totalURLs = sizeof($urls);
// Check for ErrorPages generating output - we want to handle this in a special way below. foreach ($urls as $url => $path) {
$isErrorPage = false; $origUrl = $url;
$pageObject = null; $result[$origUrl] = array(
if ($response && is_object($response) && ((int)$response->getStatusCode())>=400) { 'statuscode' => null,
$pageObject = SiteTree::get_by_link($url); 'redirect' => null,
if ($pageObject && $pageObject instanceof ErrorPage) $isErrorPage = true; 'path' => null
} );
// Skip any responses with a 404 status code unless it's the ErrorPage itself. $i++;
if (!$isErrorPage && is_object($response) && $response->getStatusCode()=='404') continue;
// Generate file content if ($url && !is_string($url)) {
// PHP file caching will generate a simple script from a template user_error("Bad url:" . var_export($url, true), E_USER_WARNING);
if($this->fileExtension == 'php') { continue;
if(is_object($response)) { }
if($response->getStatusCode() == '301' || $response->getStatusCode() == '302') {
$content = $this->generatePHPCacheRedirection($response->getHeader('Location')); if (Config::inst()->get('StaticPublisher', 'echo_progress')) {
} else { echo " * Publishing page $i/$totalURLs: $url\n";
$content = $this->generatePHPCacheFile($response->getBody(), HTTP::get_cache_age(), date('Y-m-d H:i:s'), $response->getHeader('Content-Type')); flush();
} }
} else {
$content = $this->generatePHPCacheFile($response . '', HTTP::get_cache_age(), date('Y-m-d H:i:s'), $response->getHeader('Content-Type'));
}
// 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'));
$result[$origUrl]['redirect'] = $response->getHeader('Location');
$content = "<meta http-equiv=\"refresh\" content=\"2; URL=$absoluteURL\">";
} else {
$content = $response->getBody();
}
} else {
$content = $response . '';
}
}
if(Config::inst()->get('StaticPublisher', 'include_caching_metadata')) {
$content = str_replace(
'</html>',
sprintf("</html>\n\n<!-- %s -->", implode(" ", $this->getMetadata($url))),
$content
);
}
if (!$isErrorPage) { Requirements::clear();
if ($url == "") {
$url = "/";
}
if (Director::is_relative_url($url)) {
$url = Director::absoluteURL($url);
}
$response = Director::test(str_replace('+', ' ', $url));
$files[$origUrl] = array( if (!$response) {
'Content' => $content, continue;
'Folder' => dirname($path).'/', }
'Filename' => basename($path),
);
} else { if ($response) {
$result[$origUrl]['statuscode'] = $response->getStatusCode();
}
Requirements::clear();
singleton('DataObject')->flushCache();
// Generate a static version of the error page with a standardised name, so they can be plugged // Check for ErrorPages generating output - we want to handle this in a special way below.
// into catch-all webserver statements such as Apache's ErrorDocument. $isErrorPage = false;
$code = (int)$response->getStatusCode(); $pageObject = null;
$files[$origUrl] = array( if ($response && is_object($response) && ((int)$response->getStatusCode())>=400) {
'Content' => $content, $pageObject = SiteTree::get_by_link($url);
'Folder' => dirname($path).'/', if ($pageObject && $pageObject instanceof ErrorPage) {
'Filename' => "error-$code.html", $isErrorPage = true;
); }
}
} // Skip any responses with a 404 status code unless it's the ErrorPage itself.
} if (!$isErrorPage && is_object($response) && $response->getStatusCode()=='404') {
continue;
}
//return config to its previous state // Generate file content
Config::unnest(); // 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'), $response->getHeader('Content-Type'));
}
} else {
$content = $this->generatePHPCacheFile($response . '', HTTP::get_cache_age(), date('Y-m-d H:i:s'), $response->getHeader('Content-Type'));
}
// 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'));
$result[$origUrl]['redirect'] = $response->getHeader('Location');
$content = "<meta http-equiv=\"refresh\" content=\"2; URL=$absoluteURL\">";
} else {
$content = $response->getBody();
}
} else {
$content = $response . '';
}
}
if (Config::inst()->get('StaticPublisher', 'include_caching_metadata')) {
$content = str_replace(
'</html>',
sprintf("</html>\n\n<!-- %s -->", implode(" ", $this->getMetadata($url))),
$content
);
}
$base = BASE_PATH . "/$this->destFolder"; if (!$isErrorPage) {
$files[$origUrl] = array(
foreach($files as $origUrl => $file) { 'Content' => $content,
Filesystem::makeFolder("$base/$file[Folder]"); 'Folder' => dirname($path).'/',
'Filename' => basename($path),
$path = "$base/$file[Folder]$file[Filename]"; );
$result[$origUrl]['path'] = $path; } else {
if(isset($file['Content'])) {
$fh = fopen($path, "w");
fwrite($fh, $file['Content']);
fclose($fh);
} else if(isset($file['Copy'])) {
copy($file['Copy'], $path);
}
}
// Revert the stage back to what it was // Generate a static version of the error page with a standardised name, so they can be plugged
Versioned::reading_stage($oldStage); // into catch-all webserver statements such as Apache's ErrorDocument.
$code = (int)$response->getStatusCode();
$files[$origUrl] = array(
'Content' => $content,
'Folder' => dirname($path).'/',
'Filename' => "error-$code.html",
);
}
}
return $result; //return config to its previous state
} Config::unnest();
/**
* Generate the templated content for a PHP script that can serve up the
* given piece of content with the given age and expiry.
*
* @param string $content
* @param string $age
* @param string $lastModified
* @param string $contentType
*
* @return string
*/
protected function generatePHPCacheFile($content, $age, $lastModified, $contentType) {
$template = file_get_contents(STATIC_MODULE_DIR . '/code/CachedPHPPage.tmpl');
return str_replace(
array('**MAX_AGE**', '**LAST_MODIFIED**', '**CONTENT**', '**CONTENT_TYPE**'),
array((int)$age, $lastModified, $content, $contentType),
$template
);
}
/** $base = BASE_PATH . "/$this->destFolder";
* Generate the templated content for a PHP script that can serve up a 301
* redirect to the given destination. foreach ($files as $origUrl => $file) {
* Filesystem::makeFolder("$base/$file[Folder]");
* @param string $destination
* $path = "$base/$file[Folder]$file[Filename]";
* @return string $result[$origUrl]['path'] = $path;
*/
protected function generatePHPCacheRedirection($destination) { if (isset($file['Content'])) {
$template = file_get_contents(STATIC_MODULE_DIR . '/code/CachedPHPRedirection.tmpl'); $fh = fopen($path, "w");
fwrite($fh, $file['Content']);
fclose($fh);
} elseif (isset($file['Copy'])) {
copy($file['Copy'], $path);
}
}
return str_replace( // Revert the stage back to what it was
array('**DESTINATION**'), Versioned::reading_stage($oldStage);
array($destination),
$template
);
}
/**
* @return string
*/
public function getDestDir() {
return BASE_PATH . '/' . $this->destFolder;
}
/**
* 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.
*
* @return array
*/
public function getExistingStaticCacheFiles() {
$cacheDir = BASE_PATH . '/' . $this->destFolder;
$urlMapper = array_flip($this->urlsToPaths($this->owner->allPagesToCache())); return $result;
}
$output = array();
/**
// Glob each dir, then glob each one of those * Generate the templated content for a PHP script that can serve up the
foreach(glob("$cacheDir/*", GLOB_ONLYDIR) as $cacheDir) { * given piece of content with the given age and expiry.
foreach(glob($cacheDir.'/*') as $cacheFile) { *
$mapKey = str_replace(BASE_PATH . "/cache/","",$cacheFile); * @param string $content
if(isset($urlMapper[$mapKey])) { * @param string $age
$url = $urlMapper[$mapKey]; * @param string $lastModified
$output[$url] = $cacheFile; * @param string $contentType
} *
} * @return string
} */
protected function generatePHPCacheFile($content, $age, $lastModified, $contentType)
return $output; {
} $template = file_get_contents(STATIC_MODULE_DIR . '/code/CachedPHPPage.tmpl');
return str_replace(
array('**MAX_AGE**', '**LAST_MODIFIED**', '**CONTENT**', '**CONTENT_TYPE**'),
array((int)$age, $lastModified, $content, $contentType),
$template
);
}
/**
* Generate the templated content for a PHP script that can serve up a 301
* redirect to the given destination.
*
* @param string $destination
*
* @return string
*/
protected function generatePHPCacheRedirection($destination)
{
$template = file_get_contents(STATIC_MODULE_DIR . '/code/CachedPHPRedirection.tmpl');
return str_replace(
array('**DESTINATION**'),
array($destination),
$template
);
}
/**
* @return string
*/
public function getDestDir()
{
return BASE_PATH . '/' . $this->destFolder;
}
/**
* 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.
*
* @return array
*/
public function getExistingStaticCacheFiles()
{
$cacheDir = BASE_PATH . '/' . $this->destFolder;
$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) {
$mapKey = str_replace(BASE_PATH . "/cache/", "", $cacheFile);
if (isset($urlMapper[$mapKey])) {
$url = $urlMapper[$mapKey];
$output[$url] = $cacheFile;
}
}
}
return $output;
}
} }

View File

@ -8,80 +8,88 @@
* *
* @package staticpublisher * @package staticpublisher
*/ */
class RsyncMultiHostPublisher extends FilesystemPublisher { class RsyncMultiHostPublisher extends FilesystemPublisher
{
/** /**
* @config * @config
* *
* Array of rsync targets to publish to. These can either be local * Array of rsync targets to publish to. These can either be local
* file names, or scp-style targets, in the form "user@server:path" * file names, or scp-style targets, in the form "user@server:path"
* *
* @var array * @var array
*/ */
private static $targets = array(); private static $targets = array();
/** /**
* @config * @config
* *
* @var array * @var array
*/ */
private static $excluded_folders = array(); private static $excluded_folders = array();
/** /**
* Set the targets to publish to. * Set the targets to publish to.
* *
* If target is an scp-style remote path, no password is accepted - we * If target is an scp-style remote path, no password is accepted - we
* assume key-based authentication to be set up on the application server * assume key-based authentication to be set up on the application server
* initiating the publication. * initiating the publication.
* *
* @deprecated 3.2 Use the "RsyncMultiHostPublisher.targets" config setting instead * @deprecated 3.2 Use the "RsyncMultiHostPublisher.targets" config setting instead
* *
* @param $targets An array of targets to publish to. * @param $targets An array of targets to publish to.
*/ */
public static function set_targets($targets) { public static function set_targets($targets)
Deprecation::notice('3.2', 'Use the "RsyncMultiHostPublisher.targets" config setting instead'); {
Deprecation::notice('3.2', 'Use the "RsyncMultiHostPublisher.targets" config setting instead');
Config::inst()->update('RsyncMultiHostPublisher', 'targets', $targets); Config::inst()->update('RsyncMultiHostPublisher', 'targets', $targets);
} }
/** /**
* Specify folders to exclude from the rsync * Specify folders to exclude from the rsync
* For example, you could exclude assets. * For example, you could exclude assets.
* *
* @deprecated 3.2 Use the "RsyncMultiHostPublisher.excluded_folders" config setting instead * @deprecated 3.2 Use the "RsyncMultiHostPublisher.excluded_folders" config setting instead
*/ */
public static function set_excluded_folders($folders) { public static function set_excluded_folders($folders)
Deprecation::notice('3.2', 'Use the "RsyncMultiHostPublisher.excluded_folders" config setting instead'); {
Deprecation::notice('3.2', 'Use the "RsyncMultiHostPublisher.excluded_folders" config setting instead');
Config::inst()->update('RsyncMultiHostPublisher', 'excluded_folders', $folders); Config::inst()->update('RsyncMultiHostPublisher', 'excluded_folders', $folders);
} }
public function publishPages($urls) { public function publishPages($urls)
parent::publishPages($urls); {
$base = Director::baseFolder(); parent::publishPages($urls);
$framework = FRAMEWORK_DIR; $base = Director::baseFolder();
$framework = FRAMEWORK_DIR;
// Get variable that can turn off the rsync component of publication // Get variable that can turn off the rsync component of publication
if(isset($_GET['norsync']) && $_GET['norsync']) return; if (isset($_GET['norsync']) && $_GET['norsync']) {
return;
$extraArg = ""; }
$excludedFolders = Config::inst()->get('RsyncMultiHostPublisher', 'excluded_folders');
if($excludedFolders) foreach($excludedFolders as $folder) { $extraArg = "";
$extraArg .= " --exclude " . escapeshellarg($folder); $excludedFolders = Config::inst()->get('RsyncMultiHostPublisher', 'excluded_folders');
} if ($excludedFolders) {
foreach ($excludedFolders as $folder) {
$targets = Config::inst()->get('RsyncMultiHostPublisher', 'targets'); $extraArg .= " --exclude " . escapeshellarg($folder);
foreach((array)$targets as $target) { }
// Transfer non-PHP content from everything to the target; that will ensure that we have all the JS/CSS/etc }
$rsyncOutput = `cd $base; rsync -av -e ssh --exclude /.htaccess --exclude /web.config --exclude '*.php' --exclude '*.svn' --exclude '*.git' --exclude '*~' $extraArg --delete . $target`;
// Then transfer "safe" PHP from the cache/ directory $targets = Config::inst()->get('RsyncMultiHostPublisher', 'targets');
$rsyncOutput .= `cd $base; rsync -av -e ssh --exclude '*.svn' --exclude '*~' $extraArg --delete cache $target`; foreach ((array)$targets as $target) {
// Transfer framework/static-main.php to the target // Transfer non-PHP content from everything to the target; that will ensure that we have all the JS/CSS/etc
$rsyncOutput .= `cd $base; rsync -av -e ssh --delete $framework/static-main.php $target/$framework`; $rsyncOutput = `cd $base; rsync -av -e ssh --exclude /.htaccess --exclude /web.config --exclude '*.php' --exclude '*.svn' --exclude '*.git' --exclude '*~' $extraArg --delete . $target`;
// Then transfer "safe" PHP from the cache/ directory
if(Config::inst()->get('StaticPublisher', 'echo_progress')) { $rsyncOutput .= `cd $base; rsync -av -e ssh --exclude '*.svn' --exclude '*~' $extraArg --delete cache $target`;
echo $rsyncOutput; // Transfer framework/static-main.php to the target
} $rsyncOutput .= `cd $base; rsync -av -e ssh --delete $framework/static-main.php $target/$framework`;
}
} if (Config::inst()->get('StaticPublisher', 'echo_progress')) {
echo $rsyncOutput;
}
}
}
} }

View File

@ -2,193 +2,205 @@
/** /**
* @package staticpublisher * @package staticpublisher
*/ */
abstract class StaticPublisher extends DataExtension { abstract class StaticPublisher extends DataExtension
{
/** /**
* Defines whether to output information about publishing or not. By * Defines whether to output information about publishing or not. By
* default, this is off, and should be turned on when you want debugging * default, this is off, and should be turned on when you want debugging
* (for example, in a cron task). * (for example, in a cron task).
* *
* @var boolean * @var boolean
* *
* @config * @config
*/ */
private static $echo_progress = false; private static $echo_progress = false;
/** /**
* Realtime static publishing... the second a page is saved, it is * Realtime static publishing... the second a page is saved, it is
* written to the cache. * written to the cache.
* *
* @var boolean * @var boolean
* *
* @config * @config
*/ */
private static $disable_realtime = false; private static $disable_realtime = false;
/** /**
* This is the current static publishing theme, which can be set at any * This is the current static publishing theme, which can be set at any
* point. If it's not set, then the last non-null theme, set via * point. If it's not set, then the last non-null theme, set via
* SSViewer::set_theme() is used. The obvious place to set this is in * SSViewer::set_theme() is used. The obvious place to set this is in
* _config.php * _config.php
* *
* @var string * @var string
* *
* @config * @config
*/ */
private static $static_publisher_theme = false; private static $static_publisher_theme = false;
/** /**
* @var boolean includes a timestamp at the bottom of the generated HTML * @var boolean includes a timestamp at the bottom of the generated HTML
* of each file, which can be useful for debugging issues with stale * of each file, which can be useful for debugging issues with stale
* caches etc. * caches etc.
* *
* @config * @config
*/ */
private static $include_caching_metadata = false; private static $include_caching_metadata = false;
/** /**
* @param array * @param array
*/ */
abstract function publishPages($pages); abstract public function publishPages($pages);
/** /**
* @param array * @param array
*/ */
abstract function unpublishPages($pages); abstract public function unpublishPages($pages);
/** /**
* @deprecated * @deprecated
* @param string * @param string
*/ */
public static function set_static_publisher_theme($theme) { public static function set_static_publisher_theme($theme)
Deprecation::notice('1.0', 'Use the new config system. SSViewer.static_publisher_theme'); {
Deprecation::notice('1.0', 'Use the new config system. SSViewer.static_publisher_theme');
Config::inst()->update('StaticPublisher', 'static_publisher_theme', $theme); Config::inst()->update('StaticPublisher', 'static_publisher_theme', $theme);
} }
/** /**
* @deprecated * @deprecated
* *
* @return string * @return string
*/ */
public static function static_publisher_theme() { public static function static_publisher_theme()
Deprecation::notice('1.0', 'Use the new config system. SSViewer.static_publisher_theme'); {
Deprecation::notice('1.0', 'Use the new config system. SSViewer.static_publisher_theme');
return Config::inst()->get('StaticPublisher', 'static_publisher_theme'); return Config::inst()->get('StaticPublisher', 'static_publisher_theme');
} }
/** /**
* @deprecated * @deprecated
* *
* @return boolean * @return boolean
*/ */
public static function echo_progress() { public static function echo_progress()
Deprecation::notice('1.0', 'Use the new config system. SSViewer.static_publisher_theme'); {
Deprecation::notice('1.0', 'Use the new config system. SSViewer.static_publisher_theme');
return Config::inst()->get('StaticPublisher', 'echo_progress'); return Config::inst()->get('StaticPublisher', 'echo_progress');
} }
/** /**
* @deprecated * @deprecated
* *
*/ */
public static function set_echo_progress($progress) { public static function set_echo_progress($progress)
Deprecation::notice('1.0', 'Use the new config system. SSViewer.static_publisher_theme'); {
Deprecation::notice('1.0', 'Use the new config system. SSViewer.static_publisher_theme');
Config::inst()->get('StaticPublisher', 'echo_progress', $progress); Config::inst()->get('StaticPublisher', 'echo_progress', $progress);
} }
/** /**
* Called after a page is published. * Called after a page is published.
* *
* @param SiteTree * @param SiteTree
*/ */
public function onAfterPublish($original) { public function onAfterPublish($original)
$this->republish($original); {
} $this->republish($original);
}
/**
* Called after link assets have been renamed, and the live site has been /**
* updated, without an actual publish event. * Called after link assets have been renamed, and the live site has been
* * updated, without an actual publish event.
* Only called if the published content exists and has been modified. *
*/ * Only called if the published content exists and has been modified.
public function onRenameLinkedAsset($original) { */
$this->republish($original); public function onRenameLinkedAsset($original)
} {
$this->republish($original);
public function republish($original) { }
if (Config::inst()->get('StaticPublisher', 'disable_realtime')) {
return; public function republish($original)
} {
if (Config::inst()->get('StaticPublisher', 'disable_realtime')) {
return;
}
$urls = array(); $urls = array();
if($this->owner->hasMethod('pagesAffectedByChanges')) { if ($this->owner->hasMethod('pagesAffectedByChanges')) {
$urls = $this->owner->pagesAffectedByChanges($original); $urls = $this->owner->pagesAffectedByChanges($original);
} else { } else {
$pages = Versioned::get_by_stage('SiteTree', 'Live', '', '', '', 10); $pages = Versioned::get_by_stage('SiteTree', 'Live', '', '', '', 10);
if($pages) { if ($pages) {
foreach($pages as $page) { foreach ($pages as $page) {
$urls[] = $page->AbsoluteLink(); $urls[] = $page->AbsoluteLink();
} }
} }
} }
// Note: Similiar to RebuildStaticCacheTask->rebuildCache() // Note: Similiar to RebuildStaticCacheTask->rebuildCache()
foreach($urls as $i => $url) { foreach ($urls as $i => $url) {
if(!is_string($url)) { if (!is_string($url)) {
user_error("Bad URL: " . var_export($url, true), E_USER_WARNING); user_error("Bad URL: " . var_export($url, true), E_USER_WARNING);
continue; continue;
} }
// Remove leading slashes from all URLs (apart from the homepage) // Remove leading slashes from all URLs (apart from the homepage)
if(substr($url,-1) == '/' && $url != '/') $url = substr($url,0,-1); if (substr($url, -1) == '/' && $url != '/') {
$url = substr($url, 0, -1);
$urls[$i] = $url; }
}
$urls[$i] = $url;
}
$urls = array_unique($urls); $urls = array_unique($urls);
$legalPages = singleton('Page')->allPagesToCache(); $legalPages = singleton('Page')->allPagesToCache();
$urls = array_intersect($urls, $legalPages); $urls = array_intersect($urls, $legalPages);
$this->publishPages($urls); $this->publishPages($urls);
} }
/** /**
* Get changes and hook into underlying functionality. * Get changes and hook into underlying functionality.
*/ */
public function onAfterUnpublish($page) { public function onAfterUnpublish($page)
if (Config::inst()->get('StaticPublisher', 'disable_realtime')) { {
return; if (Config::inst()->get('StaticPublisher', 'disable_realtime')) {
} return;
}
// Get the affected URLs
if($this->owner->hasMethod('pagesAffectedByUnpublishing')) { // Get the affected URLs
$urls = $this->owner->pagesAffectedByUnpublishing(); if ($this->owner->hasMethod('pagesAffectedByUnpublishing')) {
$urls = array_unique($urls); $urls = $this->owner->pagesAffectedByUnpublishing();
} else { $urls = array_unique($urls);
$urls = array($this->owner->AbsoluteLink()); } else {
} $urls = array($this->owner->AbsoluteLink());
}
$legalPages = singleton('Page')->allPagesToCache();
$legalPages = singleton('Page')->allPagesToCache();
$urlsToRepublish = array_intersect($urls, $legalPages);
$urlsToUnpublish = array_diff($urls, $legalPages); $urlsToRepublish = array_intersect($urls, $legalPages);
$urlsToUnpublish = array_diff($urls, $legalPages);
$this->unpublishPages($urlsToUnpublish); $this->unpublishPages($urlsToUnpublish);
$this->publishPages($urlsToRepublish); $this->publishPages($urlsToRepublish);
} }
/** /**
* *
* @param string $url * @param string $url
* @return array * @return array
*/ */
public function getMetadata($url) { public function getMetadata($url)
return array( {
'Cache generated on ' . date('Y-m-d H:i:s T (O)') return array(
); 'Cache generated on ' . date('Y-m-d H:i:s T (O)')
} );
}
} }

View File

@ -1,139 +1,142 @@
<?php <?php
/** /**
* @package staticpublisher * @package staticpublisher
*/ */
class RebuildStaticCacheTask extends BuildTask { class RebuildStaticCacheTask extends BuildTask
{
private static $quiet = false; private static $quiet = false;
public function run($request) { public function run($request)
if(Config::inst()->get('RebuildStaticCacheTask', 'quiet')) { {
Config::inst()->update('StaticPublisher', 'echo_progress', false); if (Config::inst()->get('RebuildStaticCacheTask', 'quiet')) {
} else { Config::inst()->update('StaticPublisher', 'echo_progress', false);
Config::inst()->update('StaticPublisher', 'echo_progress', true); } else {
} Config::inst()->update('StaticPublisher', 'echo_progress', true);
}
$page = singleton('Page');
$page = singleton('Page');
if(!$page->hasMethod('allPagesToCache')) { if (!$page->hasMethod('allPagesToCache')) {
user_error( user_error(
'RebuildStaticCacheTask::index(): Please define a method "allPagesToCache()" on your Page class to return all pages affected by a cache refresh.', 'RebuildStaticCacheTask::index(): Please define a method "allPagesToCache()" on your Page class to return all pages affected by a cache refresh.',
E_USER_ERROR E_USER_ERROR
); );
} }
if(!empty($_GET['urls'])) { if (!empty($_GET['urls'])) {
$urls = $_GET['urls']; $urls = $_GET['urls'];
} else { } else {
$urls = $page->allPagesToCache(); $urls = $page->allPagesToCache();
} }
$this->rebuildCache($urls, true); $this->rebuildCache($urls, true);
} }
public function log($message = null) { public function log($message = null)
$quiet = Config::inst()->get('RebuildStaticCacheTask', 'quiet'); {
$quiet = Config::inst()->get('RebuildStaticCacheTask', 'quiet');
if($quiet) { if ($quiet) {
return; return;
} }
echo $message; echo $message;
} }
/** /**
* Rebuilds the static cache for the pages passed through via $urls * Rebuilds the static cache for the pages passed through via $urls
* *
* @param array $urls The URLs of pages to re-fetch and cache. * @param array $urls The URLs of pages to re-fetch and cache.
* @param bool $removeAll Remove all stale cache files (default TRUE). * @param bool $removeAll Remove all stale cache files (default TRUE).
*/ */
public function rebuildCache($urls, $removeAll = true) { public function rebuildCache($urls, $removeAll = true)
{
if (!is_array($urls)) {
// $urls must be an array
user_error("Rebuild cache must be passed an array of urls. Make sure your allPagesToCache function returns an array", E_USER_ERROR);
return;
};
if (!Director::is_cli()) {
$this->log("<pre>\n");
$this->log("Rebuilding cache.\nNOTE: Please ensure that this page ends with 'Done!' - if not, then something may have gone wrong.\n\n");
}
if(!is_array($urls)) { $page = singleton('Page');
// $urls must be an array $cacheBaseDir = $page->getDestDir();
user_error("Rebuild cache must be passed an array of urls. Make sure your allPagesToCache function returns an array", E_USER_ERROR);
return; if (!file_exists($cacheBaseDir)) {
}; Filesystem::makeFolder($cacheBaseDir);
}
if(!Director::is_cli()) {
$this->log("<pre>\n"); $quiet = Config::inst()->get('RebuildStaticCacheTask', 'quiet');
$this->log("Rebuilding cache.\nNOTE: Please ensure that this page ends with 'Done!' - if not, then something may have gone wrong.\n\n");
}
$page = singleton('Page'); if (!$quiet) {
$cacheBaseDir = $page->getDestDir(); if (file_exists($cacheBaseDir.'/lock') && !isset($_REQUEST['force'])) {
die("There already appears to be a publishing queue running. You can skip warning this by adding ?/&force to the URL.");
if(!file_exists($cacheBaseDir)) { }
Filesystem::makeFolder($cacheBaseDir); }
}
touch($cacheBaseDir.'/lock');
$quiet = Config::inst()->get('RebuildStaticCacheTask', 'quiet');
// Note: Similiar to StaticPublisher->republish()
foreach ($urls as $i => $url) {
if ($url && !is_string($url)) {
user_error("Bad URL: " . var_export($url, true), E_USER_WARNING);
continue;
}
$urls[$i] = $url;
}
if(!$quiet) { $urls = array_unique($urls);
if (file_exists($cacheBaseDir.'/lock') && !isset($_REQUEST['force'])) { sort($urls);
die("There already appears to be a publishing queue running. You can skip warning this by adding ?/&force to the URL.");
} $start = isset($_GET['start']) ? $_GET['start'] : 0;
} $count = isset($_GET['count']) ? $_GET['count'] : sizeof($urls);
touch($cacheBaseDir.'/lock');
// Note: Similiar to StaticPublisher->republish()
foreach($urls as $i => $url) {
if($url && !is_string($url)) {
user_error("Bad URL: " . var_export($url, true), E_USER_WARNING);
continue;
}
$urls[$i] = $url;
}
$urls = array_unique($urls); if (($start + $count) > sizeof($urls)) {
sort($urls); $count = sizeof($urls) - $start;
}
$start = isset($_GET['start']) ? $_GET['start'] : 0;
$count = isset($_GET['count']) ? $_GET['count'] : sizeof($urls);
if(($start + $count) > sizeof($urls)) { $urls = array_slice($urls, $start, $count);
$count = sizeof($urls) - $start;
}
$urls = array_slice($urls, $start, $count); $mappedUrls = $page->urlsToPaths($urls);
$mappedUrls = $page->urlsToPaths($urls); if ($removeAll && !isset($_GET['urls']) && $start == 0 && file_exists("../cache")) {
$this->log("Removing stale cache files... \n");
if($removeAll && !isset($_GET['urls']) && $start == 0 && file_exists("../cache")) { if (!$quiet) {
$this->log("Removing stale cache files... \n"); flush();
}
if(!$quiet) { if (Config::inst()->get('FilesystemPublisher', 'domain_based_caching')) {
flush(); // Glob each dir, then glob each one of those
} foreach (glob(BASE_PATH . '/cache/*', GLOB_ONLYDIR) as $cacheDir) {
foreach (glob($cacheDir.'/*') as $cacheFile) {
$searchCacheFile = trim(str_replace($cacheBaseDir, '', $cacheFile), '\/');
if (!in_array($searchCacheFile, $mappedUrls)) {
$this->log(" * Deleting $cacheFile\n");
@unlink($cacheFile);
}
}
}
}
$this->log("done.\n\n");
}
if (Config::inst()->get('FilesystemPublisher', 'domain_based_caching')) { $this->log("Rebuilding cache from " . sizeof($mappedUrls) . " urls...\n\n");
// Glob each dir, then glob each one of those $page->extend('publishPages', $mappedUrls);
foreach(glob(BASE_PATH . '/cache/*', GLOB_ONLYDIR) as $cacheDir) {
foreach(glob($cacheDir.'/*') as $cacheFile) { if (file_exists($cacheBaseDir.'/lock')) {
$searchCacheFile = trim(str_replace($cacheBaseDir, '', $cacheFile), '\/'); unlink($cacheBaseDir.'/lock');
}
if (!in_array($searchCacheFile, $mappedUrls)) {
$this->log(" * Deleting $cacheFile\n"); $this->log("\n\n== Done! ==\n");
}
@unlink($cacheFile);
}
}
}
}
$this->log("done.\n\n");
}
$this->log("Rebuilding cache from " . sizeof($mappedUrls) . " urls...\n\n");
$page->extend('publishPages', $mappedUrls);
if (file_exists($cacheBaseDir.'/lock')) {
unlink($cacheBaseDir.'/lock');
}
$this->log("\n\n== Done! ==\n");
}
} }

View File

@ -3,33 +3,39 @@
/** /**
* @package staticpublisher * @package staticpublisher
*/ */
class StaticExporterTask extends BuildTask { class StaticExporterTask extends BuildTask
{
public function run($request) { public function run($request)
$now = microtime(true); {
$export = new StaticExporter(); $now = microtime(true);
$export = new StaticExporter();
$url = $request->getVar('baseurl'); $url = $request->getVar('baseurl');
$sym = $request->getVar('symlink'); $sym = $request->getVar('symlink');
$quiet = $request->getVar('quiet'); $quiet = $request->getVar('quiet');
$folder = $request->getVar('path'); $folder = $request->getVar('path');
if(!$folder) $folder = TEMP_FOLDER . '/static-export'; if (!$folder) {
$folder = TEMP_FOLDER . '/static-export';
}
$url = ($url) ? $url : Director::baseURL(); $url = ($url) ? $url : Director::baseURL();
$symlink = ($sym != "false"); $symlink = ($sym != "false");
$quiet = ($quiet) ? $quiet : false; $quiet = ($quiet) ? $quiet : false;
if(!$quiet) printf("Exporting website with %s base URL... %s", $url, PHP_EOL); if (!$quiet) {
$path = $export->doExport($url, $folder, $symlink, $quiet); printf("Exporting website with %s base URL... %s", $url, PHP_EOL);
}
$path = $export->doExport($url, $folder, $symlink, $quiet);
if(!$quiet) { if (!$quiet) {
printf("\nWebsite exported to %s\nTotal time %s\nMemory used %s. %s", printf("\nWebsite exported to %s\nTotal time %s\nMemory used %s. %s",
$path, $path,
number_format(microtime(true) - $now, 2) . 's', number_format(microtime(true) - $now, 2) . 's',
number_format(memory_get_peak_usage() / 1024 / 1024, 2) .'mb', number_format(memory_get_peak_usage() / 1024 / 1024, 2) .'mb',
PHP_EOL PHP_EOL
); );
} }
} }
} }

View File

@ -5,243 +5,257 @@
* *
* @package staticpublisher * @package staticpublisher
*/ */
class FilesystemPublisherTest extends SapphireTest { class FilesystemPublisherTest extends SapphireTest
{
protected $usesDatabase = true;
protected $usesDatabase = true;
protected $orig = array();
protected $orig = array();
public function setUp() {
parent::setUp(); public function setUp()
{
SiteTree::add_extension("FilesystemPublisher('assets/FilesystemPublisherTest-static-folder/')"); parent::setUp();
$this->orig['domain_based_caching'] = Config::inst()->get('FilesystemPublisher', 'domain_based_caching'); SiteTree::add_extension("FilesystemPublisher('assets/FilesystemPublisherTest-static-folder/')");
$this->orig['domain_based_caching'] = Config::inst()->get('FilesystemPublisher', 'domain_based_caching');
Config::inst()->update('FilesystemPublisher', 'domain_based_caching', false); Config::inst()->update('FilesystemPublisher', 'domain_based_caching', false);
} }
public function tearDown() { public function tearDown()
parent::tearDown(); {
parent::tearDown();
SiteTree::remove_extension("FilesystemPublisher('assets/FilesystemPublisherTest-static-folder/')"); SiteTree::remove_extension("FilesystemPublisher('assets/FilesystemPublisherTest-static-folder/')");
Config::inst()->update('FilesystemPublisher', 'domain_based_caching', $this->orig['domain_based_caching']); Config::inst()->update('FilesystemPublisher', 'domain_based_caching', $this->orig['domain_based_caching']);
if(file_exists(BASE_PATH . '/assets/FilesystemPublisherTest-static-folder')) { if (file_exists(BASE_PATH . '/assets/FilesystemPublisherTest-static-folder')) {
Filesystem::removeFolder(BASE_PATH . '/assets/FilesystemPublisherTest-static-folder'); Filesystem::removeFolder(BASE_PATH . '/assets/FilesystemPublisherTest-static-folder');
} }
} }
public function testUrlsToPathsWithRelativeUrls() { public function testUrlsToPathsWithRelativeUrls()
$fsp = new FilesystemPublisher('.', 'html'); {
$fsp = new FilesystemPublisher('.', 'html');
$this->assertEquals(
$fsp->urlsToPaths(array('/')), $this->assertEquals(
array('/' => './index.html'), $fsp->urlsToPaths(array('/')),
'Root URL path mapping' array('/' => './index.html'),
); 'Root URL path mapping'
);
$this->assertEquals(
$fsp->urlsToPaths(array('about-us')), $this->assertEquals(
array('about-us' => './about-us.html'), $fsp->urlsToPaths(array('about-us')),
'URLsegment path mapping' array('about-us' => './about-us.html'),
); 'URLsegment path mapping'
);
$this->assertEquals(
$fsp->urlsToPaths(array('parent/child')), $this->assertEquals(
array('parent/child' => 'parent/child.html'), $fsp->urlsToPaths(array('parent/child')),
'Nested URLsegment path mapping' array('parent/child' => 'parent/child.html'),
); 'Nested URLsegment path mapping'
} );
}
public function testUrlsToPathsWithAbsoluteUrls() {
$fsp = new FilesystemPublisher('.', 'html'); public function testUrlsToPathsWithAbsoluteUrls()
{
$url = Director::absoluteBaseUrl(); $fsp = new FilesystemPublisher('.', 'html');
$this->assertEquals(
$fsp->urlsToPaths(array($url)), $url = Director::absoluteBaseUrl();
array($url => './index.html'), $this->assertEquals(
'Root URL path mapping' $fsp->urlsToPaths(array($url)),
); array($url => './index.html'),
'Root URL path mapping'
$url = Director::absoluteBaseUrl() . 'about-us'; );
$this->assertEquals(
$fsp->urlsToPaths(array($url)), $url = Director::absoluteBaseUrl() . 'about-us';
array($url => './about-us.html'), $this->assertEquals(
'URLsegment path mapping' $fsp->urlsToPaths(array($url)),
); array($url => './about-us.html'),
'URLsegment path mapping'
$url = Director::absoluteBaseUrl() . 'parent/child'; );
$this->assertEquals(
$fsp->urlsToPaths(array($url)), $url = Director::absoluteBaseUrl() . 'parent/child';
array($url => 'parent/child.html'), $this->assertEquals(
'Nested URLsegment path mapping' $fsp->urlsToPaths(array($url)),
); array($url => 'parent/child.html'),
} 'Nested URLsegment path mapping'
);
}
public function testUrlsToPathsWithDomainBasedCaching() { public function testUrlsToPathsWithDomainBasedCaching()
$origDomainBasedCaching = Config::inst()->get('FilesystemPublisher', 'domain_based_caching'); {
Config::inst()->update('FilesystemPublisher', 'domain_based_caching', true); $origDomainBasedCaching = Config::inst()->get('FilesystemPublisher', 'domain_based_caching');
Config::inst()->update('FilesystemPublisher', 'domain_based_caching', true);
$fsp = new FilesystemPublisher('.', 'html');
$fsp = new FilesystemPublisher('.', 'html');
$url = 'http://domain1.com/';
$this->assertEquals( $url = 'http://domain1.com/';
$fsp->urlsToPaths(array($url)), $this->assertEquals(
array($url => 'domain1.com/index.html'), $fsp->urlsToPaths(array($url)),
'Root URL path mapping' array($url => 'domain1.com/index.html'),
); 'Root URL path mapping'
);
$url = 'http://domain1.com/about-us';
$this->assertEquals( $url = 'http://domain1.com/about-us';
$fsp->urlsToPaths(array($url)), $this->assertEquals(
array($url => 'domain1.com/about-us.html'), $fsp->urlsToPaths(array($url)),
'URLsegment path mapping' array($url => 'domain1.com/about-us.html'),
); 'URLsegment path mapping'
);
$url = 'http://domain2.com/parent/child';
$this->assertEquals( $url = 'http://domain2.com/parent/child';
$fsp->urlsToPaths(array($url)), $this->assertEquals(
array($url => 'domain2.com/parent/child.html'), $fsp->urlsToPaths(array($url)),
'Nested URLsegment path mapping' array($url => 'domain2.com/parent/child.html'),
); 'Nested URLsegment path mapping'
);
Config::inst()->update('FilesystemPublisher', 'domain_based_caching', $origDomainBasedCaching);
} Config::inst()->update('FilesystemPublisher', 'domain_based_caching', $origDomainBasedCaching);
}
/**
* Simple test to ensure that FileSystemPublisher::__construct() /**
* has called parent::__construct() by checking the class property. * Simple test to ensure that FileSystemPublisher::__construct()
* The class property is set on {@link Object::__construct()} and * has called parent::__construct() by checking the class property.
* this is therefore a good test to ensure it was called. * The class property is set on {@link Object::__construct()} and
* * this is therefore a good test to ensure it was called.
* If FilesystemPublisher doesn't call parent::__construct() then *
* it won't be enabled propery because {@link Object::__construct()} * If FilesystemPublisher doesn't call parent::__construct() then
* is where extension instances are set up and subsequently used by * it won't be enabled propery because {@link Object::__construct()}
* {@link DataObject::defineMethods()}. * is where extension instances are set up and subsequently used by
*/ * {@link DataObject::defineMethods()}.
public function testHasCalledParentConstructor() { */
$fsp = new FilesystemPublisher('.', '.html'); public function testHasCalledParentConstructor()
{
$fsp = new FilesystemPublisher('.', '.html');
$this->assertEquals($fsp->class, 'FilesystemPublisher'); $this->assertEquals($fsp->class, 'FilesystemPublisher');
} }
/* /*
* These are a few simple tests to check that we will be retrieving the * These are a few simple tests to check that we will be retrieving the
* correct theme when we need it. StaticPublishing needs to be able to * correct theme when we need it. StaticPublishing needs to be able to
* retrieve a non-null theme at the time publishPages() is called. * retrieve a non-null theme at the time publishPages() is called.
*/ */
public function testStaticPublisherTheme(){ public function testStaticPublisherTheme()
{
// This will be the name of the default theme of this particular project
$default_theme = Config::inst()->get('SSViewer', 'theme'); // This will be the name of the default theme of this particular project
$default_theme = Config::inst()->get('SSViewer', 'theme');
$p1 = new Page();
$p1->URLSegment = strtolower(__CLASS__).'-page-1'; $p1 = new Page();
$p1->HomepageForDomain = ''; $p1->URLSegment = strtolower(__CLASS__).'-page-1';
$p1->write(); $p1->HomepageForDomain = '';
$p1->doPublish(); $p1->write();
$p1->doPublish();
$current_theme = Config::inst()->get('SSViewer', 'theme_enabled') ? Config::inst()->get('SSViewer', 'theme') : null;
$this->assertEquals($current_theme, $default_theme, 'After a standard publication, the theme is correct'); $current_theme = Config::inst()->get('SSViewer', 'theme_enabled') ? Config::inst()->get('SSViewer', 'theme') : null;
$this->assertEquals($current_theme, $default_theme, 'After a standard publication, the theme is correct');
//We can set the static_publishing theme to something completely different:
//Static publishing will use this one instead of the current_custom_theme if it is not false //We can set the static_publishing theme to something completely different:
Config::inst()->update('StaticPublisher', 'static_publisher_theme', 'otherTheme'); //Static publishing will use this one instead of the current_custom_theme if it is not false
$current_theme = Config::inst()->get('StaticPublisher', 'static_publisher_theme'); Config::inst()->update('StaticPublisher', 'static_publisher_theme', 'otherTheme');
$current_theme = Config::inst()->get('StaticPublisher', 'static_publisher_theme');
$this->assertNotEquals($current_theme, $default_theme, 'The static publisher theme overrides the custom theme'); $this->assertNotEquals($current_theme, $default_theme, 'The static publisher theme overrides the custom theme');
} }
public function testMenu2LinkingMode() { public function testMenu2LinkingMode()
$this->logInWithPermission('ADMIN'); {
$this->logInWithPermission('ADMIN');
Config::inst()->update('SSViewer', 'theme', null);
Config::inst()->update('SSViewer', 'theme', null);
$l1 = new StaticPublisherTestPage();
$l1->URLSegment = strtolower(__CLASS__).'-level-1'; $l1 = new StaticPublisherTestPage();
$l1->write(); $l1->URLSegment = strtolower(__CLASS__).'-level-1';
$l1->doPublish(); $l1->write();
$l1->doPublish();
$l2_1 = new StaticPublisherTestPage();
$l2_1->URLSegment = strtolower(__CLASS__).'-level-2-1'; $l2_1 = new StaticPublisherTestPage();
$l2_1->ParentID = $l1->ID; $l2_1->URLSegment = strtolower(__CLASS__).'-level-2-1';
$l2_1->write(); $l2_1->ParentID = $l1->ID;
$l2_1->write();
$l2_1->doPublish();
$response = Director::test($l2_1->AbsoluteLink()); $l2_1->doPublish();
$response = Director::test($l2_1->AbsoluteLink());
$this->assertEquals(trim($response->getBody()), "current", "current page is level 2-1"); $this->assertEquals(trim($response->getBody()), "current", "current page is level 2-1");
$l2_2 = new StaticPublisherTestPage(); $l2_2 = new StaticPublisherTestPage();
$l2_2->URLSegment = strtolower(__CLASS__).'-level-2-2'; $l2_2->URLSegment = strtolower(__CLASS__).'-level-2-2';
$l2_2->ParentID = $l1->ID; $l2_2->ParentID = $l1->ID;
$l2_2->write(); $l2_2->write();
$l2_2->doPublish(); $l2_2->doPublish();
$response = Director::test($l2_2->AbsoluteLink()); $response = Director::test($l2_2->AbsoluteLink());
$this->assertEquals(trim($response->getBody()), "linkcurrent", "current page is level 2-2"); $this->assertEquals(trim($response->getBody()), "linkcurrent", "current page is level 2-2");
} }
public function testContentTypeHTML() { public function testContentTypeHTML()
SiteTree::remove_extension('FilesystemPublisher'); {
SiteTree::add_extension("FilesystemPublisher('assets/FilesystemPublisherTest-static-folder/', 'php')"); SiteTree::remove_extension('FilesystemPublisher');
$l1 = new StaticPublisherTestPage(); SiteTree::add_extension("FilesystemPublisher('assets/FilesystemPublisherTest-static-folder/', 'php')");
$l1->URLSegment = 'mimetype'; $l1 = new StaticPublisherTestPage();
$l1->write(); $l1->URLSegment = 'mimetype';
$l1->doPublish(); $l1->write();
$response = Director::test('mimetype'); $l1->doPublish();
$this->assertEquals($response->getHeader('Content-Type'), 'text/html; charset=utf-8', 'Content-Type should be text/html; charset=utf-8'); $response = Director::test('mimetype');
} $this->assertEquals($response->getHeader('Content-Type'), 'text/html; charset=utf-8', 'Content-Type should be text/html; charset=utf-8');
}
public function testContentTypeJSON() { public function testContentTypeJSON()
SiteTree::remove_extension('FilesystemPublisher'); {
SiteTree::add_extension("FilesystemPublisher('assets/FilesystemPublisherTest-static-folder/', 'php')"); SiteTree::remove_extension('FilesystemPublisher');
$l1 = new StaticPublisherTestPage(); SiteTree::add_extension("FilesystemPublisher('assets/FilesystemPublisherTest-static-folder/', 'php')");
$l1->URLSegment = 'mimetype'; $l1 = new StaticPublisherTestPage();
$l1->write(); $l1->URLSegment = 'mimetype';
$l1->doPublish(); $l1->write();
$response = Director::test('mimetype/json'); $l1->doPublish();
$this->assertEquals($response->getHeader('Content-Type'), 'application/json', 'Content-Type should be application/json'); $response = Director::test('mimetype/json');
} $this->assertEquals($response->getHeader('Content-Type'), 'application/json', 'Content-Type should be application/json');
}
} }
/** /**
* @package staticpublisher * @package staticpublisher
*/ */
class StaticPublisherTestPage extends Page implements TestOnly { class StaticPublisherTestPage extends Page implements TestOnly
{
private static $allowed_children = array( private static $allowed_children = array(
'StaticPublisherTestPage' 'StaticPublisherTestPage'
); );
public function canPublish($member = null) { public function canPublish($member = null)
return true; {
} return true;
}
public function getTemplate() { public function getTemplate()
return STATIC_MODULE_DIR . '/tests/templates/StaticPublisherTestPage.ss'; {
} return STATIC_MODULE_DIR . '/tests/templates/StaticPublisherTestPage.ss';
} }
}
/** /**
* @package staticpublisher * @package staticpublisher
*/ */
class StaticPublisherTestPage_Controller extends Page_Controller implements TestOnly { class StaticPublisherTestPage_Controller extends Page_Controller implements TestOnly
{
/**
*
* @var array
*/
private static $allowed_actions = array('json');
public function json(SS_HTTPRequest $request) {
$response = new SS_HTTPResponse('{"firstName": "John"}');
$response->addHeader('Content-Type', 'application/json');
return $response;
}
/**
*
* @var array
*/
private static $allowed_actions = array('json');
public function json(SS_HTTPRequest $request)
{
$response = new SS_HTTPResponse('{"firstName": "John"}');
$response->addHeader('Content-Type', 'application/json');
return $response;
}
} }