Merged changes from branches/2.2.2-assets:

Static publishing
Ability to lock down comments to logged-in members only

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@60470 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2008-08-12 02:59:27 +00:00
parent abd3cc0d15
commit 387b5edd21
13 changed files with 399 additions and 32 deletions

View File

@ -19,9 +19,10 @@ Director::addRules(50, array(
'admin//$Action/$ID/$OtherID' => 'CMSMain',
'unsubscribe//$Email/$MailingList' => 'Unsubscribe_Controller',
'PageComment//$Action/$ID' => 'PageComment_Controller',
'dev/buildcache' => 'RebuildStaticCacheTask',
));
// Built-in modules
LeftAndMain::populate_default_menu();
?>
?>

View File

@ -290,7 +290,7 @@ HTML;
}
// @todo: These workflow features aren't really appropriate for all projects
if( Member::currentUser()->_isAdmin() && project() == 'mot' ) {
if( Member::currentUser()->isAdmin() && project() == 'mot' ) {
$fields->addFieldsToTab( 'Root.Workflow', new DropdownField("Owner", _t('AssetAdmin.OWNER','Owner'), Member::map() ) );
$fields->addFieldsToTab( 'Root.Workflow', new TreeMultiselectField("CanUse", _t('AssetAdmin.CONTENTUSABLEBY','Content usable by')) );
$fields->addFieldsToTab( 'Root.Workflow', new TreeMultiselectField("CanEdit", _t('AssetAdmin.CONTENTMODBY','Content modifiable by')) );

View File

@ -548,20 +548,7 @@ JS;
* Actually perform the publication step
*/
public function performPublish($record) {
$record->AssignedToID = 0;
$record->RequestedByID = 0;
$record->Status = "Published";
//$record->PublishedByID = Member::currentUser()->ID;
$record->write();
$record->publish("Stage", "Live");
GoogleSitemap::ping();
// Fix the sort order for this page's siblings
DB::query("UPDATE SiteTree_Live
INNER JOIN SiteTree ON SiteTree_Live.ID = SiteTree.ID
SET SiteTree_Live.Sort = SiteTree.Sort
WHERE SiteTree_Live.ParentID = " . sprintf('%d', $record->ParentID));
$record->doPublish();
}
public function revert($urlParams, $form) {
@ -989,7 +976,7 @@ HTML;
return;
}
$ids = split(' *, *', $_REQUEST['csvIDs']);
$ids = split(' *, *', $this->requestParams['csvIDs']);
$notifications = array();
@ -1004,7 +991,7 @@ HTML;
if($record) {
// Publish this page
$this->performPublish($record);
$record->doPublish();
// Now make sure the 'changed' icon is removed
$publishedRecord = DataObject::get_by_id($this->stat('tree_class'), $id);
@ -1202,18 +1189,20 @@ HTML;
function publishall() {
ini_set("memory_limit","100M");
ini_set('max_execution_time', 300);
$response = "";
if(isset($_POST['confirm'])) {
if(isset($this->requestParams['confirm'])) {
$start = 0;
$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
$count = 0;
while(true) {
foreach($pages as $page) {
$this->performPublish($page);
$page->doPublish();
$page->destroy();
unset($page);
$count++;
echo "<li>$count</li>";
$response .= "<li>$count</li>";
}
if($pages->Count() > 29) {
$start += 30;
@ -1223,10 +1212,10 @@ HTML;
}
}
echo sprintf(_t('CMSMain.PUBPAGES',"Done: Published %d pages"), $count);
$response .= sprintf(_t('CMSMain.PUBPAGES',"Done: Published %d pages"), $count);
} else {
echo '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
$response .= '<h1>' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
<p>' . _t('CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish". It\'s
intended to be used after there have been massive edits of the content, such as when the site was
first built.') . '</p>
@ -1235,6 +1224,8 @@ HTML;
. _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",PR_LOW,'Confirmation button') .'" />
</form>';
}
return $response;
}
function restorepage() {

View File

@ -549,7 +549,7 @@ JS;
// If the 'Save & Publish' button was clicked, also publish the page
if (isset($urlParams['publish']) && $urlParams['publish'] == 1) {
$this->performPublish($record);
$record->doPublish();
$record->setClassName($record->ClassName);
$newClass = $record->ClassName;

View File

@ -14,6 +14,7 @@ class PageComment extends DataObject {
static $has_one = array(
"Parent" => "SiteTree",
"Author" => "Member" // Only set when the user is logged in when posting
);
static $casting = array(

View File

@ -9,9 +9,23 @@
class PageCommentInterface extends ViewableData {
protected $controller, $methodName, $page;
/**
* @var boolean If this is true, you must be logged in to post a comment
* (and therefore, you don't need to specify a 'Your name' field unless
* your name is blank)
*/
static $comments_require_login = false;
/**
* @var string If this is a valid permission code, you must be logged in
* and have the appropriate permission code on your account before you can
* post a comment.
*/
static $comments_require_permission = "";
/**
* Create a new page comment interface
* @param controller The controller that the U
* @param controller The controller that the interface is used on
* @param methodName The method to return this PageCommentInterface object
* @param page The page that we're commenting on
*/
@ -21,10 +35,59 @@ class PageCommentInterface extends ViewableData {
$this->page = $page;
}
/**
* See @link PageCommentInterface::$comments_require_login
* @param boolean state The new state of this static field
*/
static function set_comments_require_login($state) {
self::$comments_require_login = (boolean) $state;
}
/**
* See @link PageCommentInterface::$comments_require_permission
* @param string permission The permission to check against.
*/
static function set_comments_require_permission($permission) {
self::$comments_require_permission = $permission;
}
function forTemplate() {
return $this->renderWith('PageCommentInterface');
}
/**
* @return boolean true if the currently logged in user can post a comment,
* false if they can't. Users can post comments by default, enforce
* security by using
* @link PageCommentInterface::set_comments_require_login() and
* @link {PageCommentInterface::set_comments_require_permission()}.
*/
static function CanPostComment() {
$member = Member::currentUser();
if(self::$comments_require_permission && $member && Permission::check(self::$comments_require_permission)) {
return true; // Comments require a certain permission, and the user has the correct permission
} elseif(self::$comments_require_login && $member && !self::$comments_require_permission) {
return true; // Comments only require that a member is logged in
} elseif(!self::$comments_require_permission && !self::$comments_require_login) {
return true; // Comments don't require anything - anyone can add a comment
}
return false;
}
/**
* @return boolean true if this page comment form requires users to have a
* valid permission code in order to post (used to customize the error
* message).
*/
function PostingRequiresPermission() {
return self::$comments_require_permission;
}
function Page() {
return $this->page;
}
function PostCommentForm() {
Requirements::javascript('jsparty/behaviour.js');
Requirements::javascript('jsparty/prototype.js');
@ -33,8 +96,17 @@ class PageCommentInterface extends ViewableData {
$fields = new FieldSet(
new HiddenField("ParentID", "ParentID", $this->page->ID),
new TextField("Name", _t('PageCommentInterface.YOURNAME', 'Your name')));
new HiddenField("ParentID", "ParentID", $this->page->ID)
);
$member = Member::currentUser();
if((self::$comments_require_login || self::$comments_require_permission) && $member && $member->FirstName) {
$fields->push(new ReadonlyField("Name", _t('PageCommentInterface.YOURNAME', 'Your name'), $member->getName()));
} else {
$fields->push(new TextField("Name", _t('PageCommentInterface.YOURNAME', 'Your name')));
}
if(MathSpamProtection::isEnabled()){
$fields->push(new TextField("Math", sprintf(_t('PageCommentInterface.SPAMQUESTION', "Spam protection question: %s"), MathSpamProtection::getMathQuestion())));
@ -120,12 +192,21 @@ class PageCommentInterface_Form extends Form {
if(!Director::is_ajax()) {
Director::redirectBack();
}
return "spamprotectionfalied"; //used by javascript for checking if the spam question was wrong
return "spamprotectionfailed"; //used by javascript for checking if the spam question was wrong
}
}
Cookie::set("PageCommentInterface_Name", $data['Name']);
// If commenting can only be done by logged in users, make sure the user is logged in
$member = Member::currentUser();
if(PageCommentInterface::CanPostComment() && $member) {
$this->Fields()->push(new HiddenField("AuthorID", "Author ID", $member->ID));
} elseif(!PageCommentInterface::CanPostComment()) {
echo "You're not able to post comments to this page. Please ensure you are logged in and have an appropriate permission level.";
return;
}
$comment = Object::create('PageComment');
$this->saveInto($comment);
$comment->IsSpam = false;

View File

@ -0,0 +1,101 @@
<?php
/**
* Usage: Object::add_extension("SiteTree", "FilesystemPublisher('../static-folder/')")
*/
class FilesystemPublisher extends StaticPublisher {
protected $destFolder;
protected $fileExtension;
/**
* @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'
*/
function __construct($destFolder, $fileExtension = null) {
if(substr($destFolder, -1) == '/') $destFolder = substr($destFolder, 0, -1);
$this->destFolder = $destFolder;
$this->fileExtension = $fileExtension;
}
function publishPages($urls) {
//$base = Director::absoluteURL($this->destFolder);
//$base = preg_replace('/\/[^\/]+\/\.\./','',$base) . '/';
//Director::setBaseURL($base);
$files = array();
$i = 0;
$totalURLs = sizeof($urls);
foreach($urls as $url) {
$i++;
if(StaticPublisher::echo_progress()) {
echo " * Publishing page $i/$totalURLs: $url\n";
flush();
}
Requirements::clear();
$response = Director::test($url);
Requirements::clear();
/*
if(!is_object($response)) {
echo "String response for url '$url'\n";
print_r($response);
}*/
if(is_object($response)) $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";
$files[$filename] = array(
'Content' => $content,
'Folder' => (dirname($filename) == '/') ? '' : (dirname($filename).'/'),
'Filename' => basename($filename),
);
// 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;
}
}*/
}
Director::setBaseURL(null);
//Debug::show(array_keys($files));
//Debug::show(array_keys($missingFiles));
$base = "../$this->destFolder";
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]");
}
}
}
}

View File

@ -0,0 +1,77 @@
<?php
abstract class StaticPublisher extends DataObjectDecorator {
/**
* Defines whether to output information about publishing or not. By
* default, this is off, and should be turned on when you want debugging
* (for example, in a cron task)
*/
static $echo_progress = false;
abstract function publishPages($pages);
static function echo_progress() {
return (boolean)self::$echo_progress;
}
/**
* Either turns on (boolean true) or off (boolean false) the progress indicators.
* @see StaticPublisher::$echo_progress
*/
static function set_echo_progress($progress) {
self::$echo_progress = (boolean)$progress;
}
function onAfterPublish($original) {
if($this->owner->hasMethod('pagesAffectedByChanges')) {
$urls = $this->owner->pagesAffectedByChanges($original);
} else {
// $pages = array(Versioned::get_one_by_stage('SiteTree', 'Live', "`SiteTree`.ID = {$this->owner->ID}"));
$pages = Versioned::get_by_stage('SiteTree', 'Live', '', '', 10);
foreach($pages as $page) {
$urls[] = $page->Link();
}
}
foreach($urls as $i => $url) {
$url = Director::makeRelative($url);
if(substr($url,-1) == '/') $url = substr($url,0,-1);
$urls[$i] = $url;
}
$urls = array_unique($urls);
$this->publishPages($urls);
}
/**
* Get all external references to CSS, JS,
*/
function externalReferencesFor($content) {
$CLI_content = escapeshellarg($content);
$tidy = `echo $CLI_content | tidy -numeric -asxhtml`;
$tidy = preg_replace('/xmlns="[^"]+"/','', $tidy);
$xContent = new SimpleXMLElement($tidy);
//Debug::message($xContent->asXML());
$xlinks = array(
"//link[@rel='stylesheet']/@href" => false,
"//script/@src" => false,
"//img/@src" => false,
"//a/@href" => true,
);
$urls = array();
foreach($xlinks as $xlink => $assetsOnly) {
$matches = $xContent->xpath($xlink);
if($matches) foreach($matches as $item) {
$url = $item . '';
if($assetsOnly && substr($url,0,7) != 'assets/') continue;
$urls[] = $url;
}
}
return $urls;
}
}

View File

@ -69,7 +69,7 @@ PageCommentInterface.prototype = {
});
}
if(response.responseText != "spamprotectionfalied"){
if(response.responseText != "spamprotectionfailed"){
__newComment.className ="even";
// Load the response into the new <li>
__newComment.innerHTML = response.responseText;

View File

@ -0,0 +1,52 @@
<?php
/**
* @todo Make this use the Task interface once it gets merged back into trunk
*/
class RebuildStaticCacheTask extends Controller {
function init() {
if(!Director::is_cli() && !Director::isDev() && !Permission::check("ADMIN")) Security::permissionFailure();
parent::init();
}
function index() {
StaticPublisher::set_echo_progress(true);
$page = singleton('Page');
if($_GET['urls']) $urls = $_GET['urls'];
else $urls = $page->allPagesToCache();
$this->rebuildCache($urls, true);
}
/**
* Rebuilds the static cache for the pages passed through via $urls
* @param array $urls The URLs of pages to re-fetch and cache.
*/
function rebuildCache($urls, $removeAll = true) {
if(!is_array($urls)) return; // $urls must be an array
if(!Director::is_cli()) echo "<pre>\n";
echo "Rebuilding cache.\nNOTE: Please ensure that this page ends with 'Done!' - if not, then something may have gone wrong.\n\n";
$page = singleton('Page');
foreach($urls as $i => $url) {
$url = Director::makeRelative($url);
if(substr($url,-1) == '/') $url = substr($url,0,-1);
$urls[$i] = $url;
}
$urls = array_unique($urls);
if($removeAll) {
echo "Removing old cache... \n";
flush();
Filesystem::removeFolder("../cache", true);
echo "done.\n\n";
}
echo "Republishing " . sizeof($urls) . " urls...\n\n";
$page->publishPages($urls);
echo "\n\n== Done! ==";
}
}

View File

@ -1,7 +1,11 @@
<div id="PageComments_holder" class="typography">
<h4><% _t('POSTCOM','Post your comment') %></h4>
$PostCommentForm
<% if CanPostComment %>
<h4><% _t('POSTCOM','Post your comment') %></h4>
$PostCommentForm
<% else %>
<p>You can't post comments until you have logged in<% if PostingRequiresPermission %>, and that you have an appropriate permission level<% end_if %>. Please <a href="Security/login?BackURL={$Page.URLSegment}/" title="Login to post a comment">login by clicking here</a>.</p>
<% end_if %>
<h4><% _t('COMMENTS','Comments') %></h4>

35
tests/CMSMainTest.php Normal file
View File

@ -0,0 +1,35 @@
<?php
class CMSMainTest extends SapphireTest {
static $fixture_file = 'cms/tests/CMSMainTest.yml';
/**
* @todo Test the results of a publication better
*/
function testPublish() {
$session = new Session(array(
'loggedInAs' => $this->idFromFixture('Member', 'admin')
));
$response = Director::test("admin/publishall", array('confirm' => 1), $session);
$this->assertContains('Done: Published 4 pages', $response->getBody());
$response = Director::test("admin/publishitems", array('csvIDs' => '1,2', 'ajax' => 1), $session);
$this->assertContains('setNodeTitle(1, \'Page 1\');', $response->getBody());
$this->assertContains('setNodeTitle(2, \'Page 2\');', $response->getBody());
//$this->assertRegexp('/Done: Published 4 pages/', $response->getBody())
/*
$response = Director::test("admin/publishitems", array(
'ID' => ''
'Title' => ''
'action_publish' => 'Save and publish',
), $session);
$this->assertRegexp('/Done: Published 4 pages/', $response->getBody())
*/
}
}

24
tests/CMSMainTest.yml Normal file
View File

@ -0,0 +1,24 @@
Page:
page1:
Title: Page 1
page2:
Title: Page 2
page3:
Title: Page 2
page4:
Title: Page 2
Group:
admin:
Title: Administrators
Member:
admin:
Email: admin@example.com
Password: ZXXlkwecxz2390232233
Groups: =>Group.admin
Permission:
admin:
Code: ADMIN
GroupID: =>Group.admin