From 387b5edd2154d0b8a3aaf934a96e0b204540b592 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Tue, 12 Aug 2008 02:59:27 +0000 Subject: [PATCH] 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 --- _config.php | 3 +- code/AssetAdmin.php | 2 +- code/CMSMain.php | 33 +++--- code/LeftAndMain.php | 2 +- code/sitefeatures/PageComment.php | 1 + code/sitefeatures/PageCommentInterface.php | 89 +++++++++++++++- code/staticpublisher/FilesystemPublisher.php | 101 +++++++++++++++++++ code/staticpublisher/StaticPublisher.php | 77 ++++++++++++++ javascript/PageCommentInterface.js | 2 +- tasks/RebuildStaticCacheTask.php | 52 ++++++++++ templates/PageCommentInterface.ss | 10 +- tests/CMSMainTest.php | 35 +++++++ tests/CMSMainTest.yml | 24 +++++ 13 files changed, 399 insertions(+), 32 deletions(-) create mode 100644 code/staticpublisher/FilesystemPublisher.php create mode 100644 code/staticpublisher/StaticPublisher.php create mode 100644 tasks/RebuildStaticCacheTask.php create mode 100644 tests/CMSMainTest.php create mode 100644 tests/CMSMainTest.yml diff --git a/_config.php b/_config.php index 1b511515..8e26bc4b 100644 --- a/_config.php +++ b/_config.php @@ -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(); -?> \ No newline at end of file +?> diff --git a/code/AssetAdmin.php b/code/AssetAdmin.php index fffd9636..8b7ad158 100755 --- a/code/AssetAdmin.php +++ b/code/AssetAdmin.php @@ -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')) ); diff --git a/code/CMSMain.php b/code/CMSMain.php index a821efbf..890966a6 100644 --- a/code/CMSMain.php +++ b/code/CMSMain.php @@ -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 "
  • $count
  • "; + $response .= "
  • $count
  • "; } 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 '

    ' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '

    + $response .= '

    ' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '

    ' . _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.') . '

    @@ -1235,6 +1224,8 @@ HTML; . _t('CMSMain.PUBALLCONFIRM',"Please publish every page in the site, copying content stage to live",PR_LOW,'Confirmation button') .'" /> '; } + + return $response; } function restorepage() { diff --git a/code/LeftAndMain.php b/code/LeftAndMain.php index 20614ae2..e3cb06f9 100644 --- a/code/LeftAndMain.php +++ b/code/LeftAndMain.php @@ -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; diff --git a/code/sitefeatures/PageComment.php b/code/sitefeatures/PageComment.php index a96621ba..3bab07e1 100755 --- a/code/sitefeatures/PageComment.php +++ b/code/sitefeatures/PageComment.php @@ -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( diff --git a/code/sitefeatures/PageCommentInterface.php b/code/sitefeatures/PageCommentInterface.php index 17c703da..a471aa6a 100755 --- a/code/sitefeatures/PageCommentInterface.php +++ b/code/sitefeatures/PageCommentInterface.php @@ -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; diff --git a/code/staticpublisher/FilesystemPublisher.php b/code/staticpublisher/FilesystemPublisher.php new file mode 100644 index 00000000..bbb2072c --- /dev/null +++ b/code/staticpublisher/FilesystemPublisher.php @@ -0,0 +1,101 @@ +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]"); + } + } + } +} diff --git a/code/staticpublisher/StaticPublisher.php b/code/staticpublisher/StaticPublisher.php new file mode 100644 index 00000000..c170fe61 --- /dev/null +++ b/code/staticpublisher/StaticPublisher.php @@ -0,0 +1,77 @@ +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; + } +} diff --git a/javascript/PageCommentInterface.js b/javascript/PageCommentInterface.js index 14339d1b..af6465d2 100755 --- a/javascript/PageCommentInterface.js +++ b/javascript/PageCommentInterface.js @@ -69,7 +69,7 @@ PageCommentInterface.prototype = { }); } - if(response.responseText != "spamprotectionfalied"){ + if(response.responseText != "spamprotectionfailed"){ __newComment.className ="even"; // Load the response into the new
  • __newComment.innerHTML = response.responseText; diff --git a/tasks/RebuildStaticCacheTask.php b/tasks/RebuildStaticCacheTask.php new file mode 100644 index 00000000..5a31192e --- /dev/null +++ b/tasks/RebuildStaticCacheTask.php @@ -0,0 +1,52 @@ +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 "
    \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! ==";
    +	}
    +}
    diff --git a/templates/PageCommentInterface.ss b/templates/PageCommentInterface.ss
    index 4b86569c..a2378a2c 100755
    --- a/templates/PageCommentInterface.ss
    +++ b/templates/PageCommentInterface.ss
    @@ -1,7 +1,11 @@
     
    -

    <% _t('POSTCOM','Post your comment') %>

    - - $PostCommentForm + <% if CanPostComment %> +

    <% _t('POSTCOM','Post your comment') %>

    + + $PostCommentForm + <% else %> +

    You can't post comments until you have logged in<% if PostingRequiresPermission %>, and that you have an appropriate permission level<% end_if %>. Please login by clicking here.

    + <% end_if %>

    <% _t('COMMENTS','Comments') %>

    diff --git a/tests/CMSMainTest.php b/tests/CMSMainTest.php new file mode 100644 index 00000000..4a848070 --- /dev/null +++ b/tests/CMSMainTest.php @@ -0,0 +1,35 @@ + $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()) + */ + } + +} \ No newline at end of file diff --git a/tests/CMSMainTest.yml b/tests/CMSMainTest.yml new file mode 100644 index 00000000..83e7b8ab --- /dev/null +++ b/tests/CMSMainTest.yml @@ -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