diff --git a/code/controllers/CMSSettingsController.php b/code/controllers/CMSSettingsController.php index 986157c2..8b2404db 100644 --- a/code/controllers/CMSSettingsController.php +++ b/code/controllers/CMSSettingsController.php @@ -41,7 +41,8 @@ class CMSSettingsController extends LeftAndMain { $this, 'EditForm', $fields, $actions )->setHTMLID('Form_EditForm'); $form->setResponseNegotiator($this->getResponseNegotiator()); - $form->addExtraClass('cms-add-form cms-content center cms-edit-form'); + $form->addExtraClass('root-form'); + $form->addExtraClass('cms-edit-form cms-panel-padded center'); // don't add data-pjax-fragment=CurrentForm, its added in the content template instead if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet'); diff --git a/code/controllers/ContentController.php b/code/controllers/ContentController.php index 6d3f514f..f2b7a45d 100644 --- a/code/controllers/ContentController.php +++ b/code/controllers/ContentController.php @@ -103,13 +103,28 @@ class ContentController extends Controller { // Check page permissions if($this->dataRecord && $this->URLSegment != 'Security' && !$this->dataRecord->canView()) { - $permissionMessage = null; + return Security::permissionFailure($this); + } - // Check if we could view the live version, offer redirect if so - if($this->canViewStage('Live')) { + // Draft/Archive security check - only CMS users should be able to look at stage/archived content + if( + $this->URLSegment != 'Security' + && !Session::get('unsecuredDraftSite') + && ( + Versioned::current_archived_date() + || (Versioned::current_stage() && Versioned::current_stage() != 'Live') + ) + ) { + if(!$this->dataRecord->canViewStage(Versioned::current_archived_date() ? 'Stage' : Versioned::current_stage())) { + $link = $this->Link(); + $message = _t( + "ContentController.DRAFT_SITE_ACCESS_RESTRICTION", + 'You must log in with your CMS password in order to view the draft or archived content. ' . + 'Click here to go back to the published site.' + ); Session::clear('currentStage'); Session::clear('archiveDate'); - + $permissionMessage = sprintf( _t( "ContentController.DRAFT_SITE_ACCESS_RESTRICTION", @@ -118,9 +133,10 @@ class ContentController extends Controller { ), Controller::join_links($this->Link(), "?stage=Live") ); + + return Security::permissionFailure($this, $permissionMessage); } - return Security::permissionFailure($this, $permissionMessage); } // Use theme from the site config @@ -215,7 +231,7 @@ class ContentController extends Controller { $response = $this->request->isMedia() ? null : ErrorPage::response_for($code); // Failover to $message if the HTML response is unavailable / inappropriate parent::httpError($code, $response ? $response : $message); - } + } /** * Get the project name diff --git a/code/model/SiteTree.php b/code/model/SiteTree.php index 898b6955..cd6443a6 100644 --- a/code/model/SiteTree.php +++ b/code/model/SiteTree.php @@ -218,7 +218,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid * Determines if the system should avoid orphaned pages * by deleting all children when the their parent is deleted (TRUE), * or rather preserve this data even if its not reachable through any navigation path (FALSE). - * + * * @deprecated 3.2 Use the "SiteTree.enforce_strict_hierarchy" config setting instead * @param boolean */ @@ -409,7 +409,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid ) { return; // There were no suitable matches at all. } - + $link = Convert::raw2att($page->Link()); if($content) { @@ -445,7 +445,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid return Director::absoluteURL($this->Link($action)); } } - + /** * Base link used for previewing. Defaults to absolute URL, * in order to account for domain changes, e.g. on multi site setups. @@ -841,8 +841,8 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid } if(!$fromLive - && !Session::get('unsecuredDraftSite') - && !Permission::checkMember($member, array('CMS_ACCESS_CMSMain', 'VIEW_DRAFT_CONTENT'))) { + && !Session::get('unsecuredDraftSite') + && !Permission::checkMember($member, array('CMS_ACCESS_CMSMain', 'VIEW_DRAFT_CONTENT'))) { // If we weren't definitely loaded from live, and we can't view non-live content, we need to // check to make sure this version is the live version and so can be viewed if (Versioned::get_versionnumber_by_stage($this->class, 'Live', $this->ID) != $this->Version) return false; @@ -876,11 +876,11 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid return false; } - + /** * Determines canView permissions for the latest version of this Page on a specific stage (see {@link Versioned}). * Usually the stage is read from {@link Versioned::current_stage()}. - * + * * @todo Implement in CMS UI. * * @param String $stage @@ -892,7 +892,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid Versioned::reading_stage($stage); $versionFromStage = DataObject::get($this->class)->byID($this->ID); - + Versioned::set_reading_mode($oldMode); return $versionFromStage ? $versionFromStage->canView($member) : false; } @@ -1335,7 +1335,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid if($this->ExtraMeta) { $tags .= $this->ExtraMeta . "\n"; } - + if(Permission::check('CMS_ACCESS_CMSMain') && in_array('CMSPreviewable', class_implements($this)) && !$this instanceof ErrorPage) { $tags .= "ID}\" />\n"; $tags .= "CMSEditLink() . "\" />\n"; @@ -1605,10 +1605,10 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid 'SiteTree', "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter" ); - + return !($existingPage); - } - + } + /** * Generate a URL segment based on the title provided. * @@ -1712,7 +1712,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid foreach($contentLinks as $item) { $item->DependentLinkType = 'Content link'; $linkList->push($item); - } + } $items->merge($linkList); } @@ -1724,9 +1724,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid foreach($virtuals as $item) { $item->DependentLinkType = 'Virtual page'; $virtualList->push($item); - } - $items->merge($virtualList); } + $items->merge($virtualList); + } } // Redirector pages @@ -1736,7 +1736,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid foreach($redirectors as $item) { $item->DependentLinkType = 'Redirector page'; $redirectorList->push($item); - } + } $items->merge($redirectorList); } @@ -1999,9 +1999,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid ->setAttribute( 'data-placeholder', _t('SiteTree.GroupPlaceholder', 'Click to select group') - ) ) ) + ) ); $visibility->setTitle($this->fieldLabel('Visibility')); @@ -2226,14 +2226,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid // Set up the initial state of the button to reflect the state of the underlying SiteTree object. if($this->stagesDiffer('Stage', 'Live')) { $publish->addExtraClass('ss-ui-alternate'); - } } - + } + $actions = new FieldList(array($majorActions, $rootTabSet)); // Hook for extensions to add/remove actions. $this->extend('updateCMSActions', $actions); - + return $actions; } @@ -2666,7 +2666,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid if(!$this->canEdit() && !$this->canAddChildren()) { if (!$this->canView()) { - $classes .= " disabled"; + $classes .= " disabled"; } else { $classes .= " edit-disabled"; } diff --git a/javascript/CMSMain.Tree.js b/javascript/CMSMain.Tree.js index e866f440..a8b28f7a 100644 --- a/javascript/CMSMain.Tree.js +++ b/javascript/CMSMain.Tree.js @@ -2,6 +2,37 @@ $.entwine('ss.tree', function($){ $('.cms-tree').entwine({ + fromDocument: { + 'oncontext_show.vakata': function(e){ + this.adjustContextClass(); + } + }, + /* + * Add and remove classes from context menus to allow for + * adjusting the display + */ + adjustContextClass: function(){ + var menus = $('#vakata-contextmenu').find("ul ul"); + + menus.each(function(i){ + var col = "1", + count = $(menus[i]).find('li').length; + + //Assign columns to menus over 10 items long + if(count > 20){ + col = "3"; + }else if(count > 10){ + col = "2"; + } + + $(menus[i]).addClass('col-' + col).removeClass('right'); + + //Remove "right" class that jstree adds on mouseenter + $(menus[i]).find('li').on("mouseenter", function (e) { + $(this).parent('ul').removeClass("right"); + }); + }); + }, getTreeConfig: function() { var self = this, config = this._super(), hints = this.getHints(); config.plugins.push('contextmenu'); @@ -101,7 +132,7 @@ ); } } - ] + ] }; return menuitems; @@ -110,6 +141,18 @@ return config; } }); + + // Scroll tree down to context of the current page + $('.cms-tree a.jstree-clicked').entwine({ + onmatch: function(){ + var self = this, + panel = self.parents('.cms-panel-content'); + + panel.animate({ + scrollTop: self.offset().top - (panel.height() / 2) + }, 'slow'); + } + }); }); }(jQuery)); diff --git a/templates/Includes/CMSSettingsController_Content.ss b/templates/Includes/CMSSettingsController_Content.ss index c10e458e..900872c0 100644 --- a/templates/Includes/CMSSettingsController_Content.ss +++ b/templates/Includes/CMSSettingsController_Content.ss @@ -1,4 +1,4 @@ -
+
<% with $EditForm %> @@ -7,10 +7,9 @@ <% include CMSBreadcrumbs %> <% end_with %>
- <% if $Fields.hasTabset %> <% with $Fields.fieldByName('Root') %> -
+
    <% loop $Tabs %> class="$extraClass"<% end_if %>>$Title @@ -28,4 +27,4 @@
-
\ No newline at end of file +
diff --git a/templates/Includes/CMSSettingsController_EditForm.ss b/templates/Includes/CMSSettingsController_EditForm.ss index 6ffc7fc9..b7f5f3ca 100644 --- a/templates/Includes/CMSSettingsController_EditForm.ss +++ b/templates/Includes/CMSSettingsController_EditForm.ss @@ -1,33 +1,29 @@ -
+ -
- <% if $Message %> -

$Message

- <% else %> - - <% end_if %> + <% if $Message %> +

$Message

+ <% else %> + + <% end_if %> -
- <% if $Legend %>$Legend<% end_if %> - <% loop $Fields %> - $FieldHolder - <% end_loop %> -
-
-
+
+ <% if $Legend %>$Legend<% end_if %> + <% loop $Fields %> + $FieldHolder + <% end_loop %> +
+
-
- <% if $Actions %> -
- <% loop $Actions %> - $Field - <% end_loop %> - <% if $Controller.LinkPreview %> - - <% _t('LeftAndMain.PreviewButton', 'Preview') %> » - - <% end_if %> -
+ <% if $Actions %> +
+ <% loop $Actions %> + $Field + <% end_loop %> + <% if $Controller.LinkPreview %> + + <% _t('LeftAndMain.PreviewButton', 'Preview') %> » + <% end_if %>
- \ No newline at end of file + <% end_if %> + diff --git a/tests/search/SearchFormTest.php b/tests/search/SearchFormTest.php index f060842b..c6d5867c 100644 --- a/tests/search/SearchFormTest.php +++ b/tests/search/SearchFormTest.php @@ -11,7 +11,7 @@ class ZZZSearchFormTest extends FunctionalTest { protected static $fixture_file = 'SearchFormTest.yml'; - + protected $mockController; public function waitUntilIndexingFinished() { @@ -37,7 +37,7 @@ class ZZZSearchFormTest extends FunctionalTest { $this->waitUntilIndexingFinished(); } - + /** * @return Boolean */ @@ -101,7 +101,7 @@ class ZZZSearchFormTest extends FunctionalTest { 'Unpublished pages are not found by searchform' ); } - + public function testPagesRestrictedToLoggedinUsersNotIncluded() { if(!$this->checkFulltextSupport()) return; @@ -109,7 +109,6 @@ class ZZZSearchFormTest extends FunctionalTest { $page = $this->objFromFixture('SiteTree', 'restrictedViewLoggedInUsers'); $page->publish('Stage', 'Live'); - $results = $sf->getResults(null, array('Search'=>'restrictedViewLoggedInUsers')); $this->assertNotContains( $page->ID, @@ -135,7 +134,6 @@ class ZZZSearchFormTest extends FunctionalTest { $page = $this->objFromFixture('SiteTree', 'restrictedViewOnlyWebsiteUsers'); $page->publish('Stage', 'Live'); - $results = $sf->getResults(null, array('Search'=>'restrictedViewOnlyWebsiteUsers')); $this->assertNotContains( $page->ID, @@ -165,16 +163,13 @@ class ZZZSearchFormTest extends FunctionalTest { } public function testInheritedRestrictedPagesNotIncluded() { - if(!$this->checkFulltextSupport()) return; - $sf = new SearchForm($this->mockController, 'SearchForm'); $parent = $this->objFromFixture('SiteTree', 'restrictedViewLoggedInUsers'); $parent->publish('Stage', 'Live'); - + $page = $this->objFromFixture('SiteTree', 'inheritRestrictedView'); $page->publish('Stage', 'Live'); - $results = $sf->getResults(null, array('Search'=>'inheritRestrictedView')); $this->assertNotContains( $page->ID, @@ -232,6 +227,10 @@ class ZZZSearchFormTest extends FunctionalTest { public function testSearchTitleAndContentWithSpecialCharacters() { if(!$this->checkFulltextSupport()) return; + if(class_exists('PostgreSQLDatabase') && DB::getConn() instanceof PostgreSQLDatabase) { + $this->markTestSkipped("PostgreSQLDatabase doesn't support entity-encoded searches"); + } + $sf = new SearchForm($this->mockController, 'SearchForm'); $pageWithSpecialChars = $this->objFromFixture('SiteTree', 'pageWithSpecialChars');