Merge branch '3.1'

Conflicts:
	.travis.yml
	code/controllers/ReportAdmin.php
This commit is contained in:
Andrew Short 2013-07-09 13:54:05 +10:00
commit 1710958207
14 changed files with 148 additions and 70 deletions

View File

@ -1,27 +1,24 @@
language: php language: php
php: php:
- 5.3 - 5.3
- 5.4
env: env:
- DB=MYSQL CORE_RELEASE=master - DB=MYSQL CORE_RELEASE=master
- DB=PGSQL CORE_RELEASE=master
- DB=SQLITE3 CORE_RELEASE=master
- PHPCS=1 CORE_RELEASE=master
matrix: matrix:
exclude: include:
- php: 5.4 - php: 5.3
env: DB=PGSQL CORE_RELEASE=master env: DB=PGSQL CORE_RELEASE=master
- php: 5.3
env: DB=SQLITE CORE_RELEASE=master
- php: 5.4 - php: 5.4
env: DB=SQLITE3 CORE_RELEASE=master env: DB=MYSQL CORE_RELEASE=master
- php: 5.4 - php: 5.5
env: PHPCS=1 CORE_RELEASE=master env: DB=MYSQL CORE_RELEASE=master
allow_failures: allow_failures:
- env: DB=PGSQL CORE_RELEASE=master - php: 5.5
- env: DB=SQLITE3 CORE_RELEASE=master env: DB=MYSQL CORE_RELEASE=master
- env: PHPCS=1 CORE_RELEASE=master
before_script: before_script:
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support - git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support

View File

@ -28,6 +28,7 @@ class AssetAdmin extends LeftAndMain implements PermissionProvider{
'delete', 'delete',
'AddForm', 'AddForm',
'DeleteItemsForm', 'DeleteItemsForm',
'SearchForm',
'getsubtree', 'getsubtree',
'movemarked', 'movemarked',
'removefile', 'removefile',
@ -395,7 +396,7 @@ JS
public function AddForm() { public function AddForm() {
$folder = singleton('Folder'); $folder = singleton('Folder');
$form = new Form( $form = CMSForm::create(
$this, $this,
'AddForm', 'AddForm',
new FieldList( new FieldList(
@ -407,7 +408,8 @@ JS
->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept') ->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
->setTitle(_t('AssetAdmin.ActionAdd', 'Add folder')) ->setTitle(_t('AssetAdmin.ActionAdd', 'Add folder'))
) )
); )->setHTMLID('Form_AddForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
// TODO Can't merge $FormAttributes in template at the moment // TODO Can't merge $FormAttributes in template at the moment
$form->addExtraClass('add-form cms-add-form cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses()); $form->addExtraClass('add-form cms-add-form cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses());

View File

@ -76,15 +76,16 @@ class CMSFileAddController extends LeftAndMain {
asort($exts); asort($exts);
$uploadField->Extensions = implode(', ', $exts); $uploadField->Extensions = implode(', ', $exts);
$form = new Form( $form = CMSForm::create(
$this, $this,
'getEditForm', 'EditForm',
new FieldList( new FieldList(
$uploadField, $uploadField,
new HiddenField('ID') new HiddenField('ID')
), ),
new FieldList() new FieldList()
); )->setHTMLID('Form_EditForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->addExtraClass('center cms-edit-form ' . $this->BaseCSSClasses()); $form->addExtraClass('center cms-edit-form ' . $this->BaseCSSClasses());
// Don't use AssetAdmin_EditForm, as it assumes a different panel structure // Don't use AssetAdmin_EditForm, as it assumes a different panel structure
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));

View File

@ -663,7 +663,10 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
$validator = new RequiredFields(); $validator = new RequiredFields();
} }
$form = new Form($this, "EditForm", $fields, $actions, $validator); $form = CMSForm::create(
$this, "EditForm", $fields, $actions, $validator
)->setHTMLID('Form_EditForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->loadDataFrom($record); $form->loadDataFrom($record);
$form->disableDefaultAction(); $form->disableDefaultAction();
$form->addExtraClass('cms-edit-form'); $form->addExtraClass('cms-edit-form');
@ -686,9 +689,11 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
$this->extend('updateEditForm', $form); $this->extend('updateEditForm', $form);
return $form; return $form;
} else if($id) { } else if($id) {
return new Form($this, "EditForm", new FieldList( $form = CMSForm::create( $this, "EditForm", new FieldList(
new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList() new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
); )->setHTMLID('Form_EditForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
return $form;
} }
} }
@ -788,13 +793,14 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
} }
)); ));
$listview = new Form( $listview = CMSForm::create(
$this, $this,
'ListViewForm', 'ListViewForm',
new FieldList($gridField), new FieldList($gridField),
new FieldList() new FieldList()
); )->setHTMLID('Form_ListViewForm');
$listview->setAttribute('data-pjax-fragment', 'ListViewForm'); $listview->setAttribute('data-pjax-fragment', 'ListViewForm');
$listview->setResponseNegotiator($this->getResponseNegotiator());
$this->extend('updateListView', $listview); $this->extend('updateListView', $listview);

View File

@ -106,7 +106,10 @@ class CMSPageAddController extends CMSPageEditController {
$this->extend('updatePageOptions', $fields); $this->extend('updatePageOptions', $fields);
$form = new Form($this, "AddForm", $fields, $actions); $form = CMSForm::create(
$this, "AddForm", $fields, $actions
)->setHTMLID('Form_AddForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->addExtraClass('cms-add-form stacked cms-content center cms-edit-form ' . $this->BaseCSSClasses()); $form->addExtraClass('cms-add-form stacked cms-content center cms-edit-form ' . $this->BaseCSSClasses());
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));

View File

@ -251,13 +251,13 @@ class CMSPageHistoryController extends CMSMain {
// Use <button> to allow full jQuery UI styling // Use <button> to allow full jQuery UI styling
foreach($actions->dataFields() as $action) $action->setUseButtonTag(true); foreach($actions->dataFields() as $action) $action->setUseButtonTag(true);
$form = new Form( $form = CMSForm::create(
$this, $this,
'VersionsForm', 'VersionsForm',
$fields, $fields,
$actions $actions
); )->setHTMLID('Form_VersionsForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->loadDataFrom($this->request->requestVars()); $form->loadDataFrom($this->request->requestVars());
$hiddenID->setValue($id); $hiddenID->setValue($id);
$form->unsetValidator(); $form->unsetValidator();

View File

@ -37,7 +37,10 @@ class CMSSettingsController extends LeftAndMain {
$navField->setAllowHTML(true); $navField->setAllowHTML(true);
$actions = $siteConfig->getCMSActions(); $actions = $siteConfig->getCMSActions();
$form = new Form($this, 'EditForm', $fields, $actions); $form = CMSForm::create(
$this, 'EditForm', $fields, $actions
)->setHTMLID('Form_EditForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->addExtraClass('root-form'); $form->addExtraClass('root-form');
$form->addExtraClass('cms-edit-form cms-panel-padded center'); $form->addExtraClass('cms-edit-form cms-panel-padded center');
// don't add data-pjax-fragment=CurrentForm, its added in the content template instead // don't add data-pjax-fragment=CurrentForm, its added in the content template instead

View File

@ -102,19 +102,24 @@ class ContentController extends Controller {
// Check page permissions // Check page permissions
if($this->dataRecord && $this->URLSegment != 'Security' && !$this->dataRecord->canView()) { if($this->dataRecord && $this->URLSegment != 'Security' && !$this->dataRecord->canView()) {
return Security::permissionFailure($this); $permissionMessage = null;
}
// Draft/Archive security check - only CMS users should be able to look at stage/archived content // Check if we could view the live version, offer redirect if so
if($this->URLSegment != 'Security' && !Session::get('unsecuredDraftSite') && (Versioned::current_archived_date() || (Versioned::current_stage() && Versioned::current_stage() != 'Live'))) { if($this->canViewStage('Live')) {
if(!$this->dataRecord->canViewStage(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. <a href="%s">Click here to go back to the published site.</a>');
Session::clear('currentStage'); Session::clear('currentStage');
Session::clear('archiveDate'); Session::clear('archiveDate');
return Security::permissionFailure($this, sprintf($message, Controller::join_links($link, "?stage=Live"))); $permissionMessage = sprintf(
_t(
"ContentController.DRAFT_SITE_ACCESS_RESTRICTION",
'You must log in with your CMS password in order to view the draft or archived content. '.
'<a href="%s">Click here to go back to the published site.</a>'
),
Controller::join_links($this->Link(), "?stage=Live")
);
} }
return Security::permissionFailure($this, $permissionMessage);
} }
// Use theme from the site config // Use theme from the site config

View File

@ -830,6 +830,23 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// admin override // admin override
if($member && Permission::checkMember($member, array("ADMIN", "SITETREE_VIEW_ALL"))) return true; if($member && Permission::checkMember($member, array("ADMIN", "SITETREE_VIEW_ALL"))) return true;
// make sure we were loaded off an allowed stage
// Were we definitely loaded directly off Live during our query?
$fromLive = true;
foreach (array('mode' => 'stage', 'stage' => 'live') as $param => $match) {
$fromLive = $fromLive && strtolower((string)$this->getSourceQueryParam("Versioned.$param")) == $match;
}
if(!$fromLive
&& !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;
}
// Standard mechanism for accepting permission changes from extensions // Standard mechanism for accepting permission changes from extensions
$extended = $this->extendedCan('canView', $member); $extended = $this->extendedCan('canView', $member);
if($extended !== null) return $extended; if($extended !== null) return $extended;
@ -858,27 +875,25 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return false; return false;
} }
/** /**
* Determines permissions for a specific stage (see {@link Versioned}). * 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()}. * Usually the stage is read from {@link Versioned::current_stage()}.
* Falls back to {@link canView}. *
*
* @todo Implement in CMS UI. * @todo Implement in CMS UI.
* *
* @param String $stage * @param String $stage
* @param Member $member * @param Member $member
* @return boolean * @return boolean
*/ */
public function canViewStage($stage, $member = null) { public function canViewStage($stage = 'Live', $member = null) {
if(!$member) $member = Member::currentUser(); $oldMode = Versioned::get_reading_mode();
Versioned::reading_stage($stage);
if( $versionFromStage = DataObject::get($this->class)->byID($this->ID);
strtolower($stage) == 'stage' &&
!(Permission::checkMember($member, 'CMS_ACCESS_CMSMain') || Permission::checkMember($member, 'VIEW_DRAFT_CONTENT')) Versioned::set_reading_mode($oldMode);
) return false; return $versionFromStage ? $versionFromStage->canView($member) : false;
return $this->canView($member);
} }
/** /**
@ -1599,20 +1614,20 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
} }
$votes = array_filter(
(array)$this->extend('augmentValidURLSegment'),
function($v) {return !is_null($v);}
);
if($votes) {
return min($votes);
}
$existingPage = DataObject::get_one( $existingPage = DataObject::get_one(
'SiteTree', 'SiteTree',
"\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter" "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter"
); );
if ($existingPage) {
return false;
}
$votes = $this->extend('augmentValidURLSegment');
if($votes) {
return min($votes);
}
return true; return !($existingPage);
} }
/** /**

View File

@ -10,11 +10,16 @@ class ContentControllerPermissionsTest extends FunctionalTest {
protected $autoFollowRedirection = false; protected $autoFollowRedirection = false;
public function testCanViewStage() { public function testCanViewStage() {
// Create a new page
$page = new Page(); $page = new Page();
$page->URLSegment = 'testpage'; $page->URLSegment = 'testpage';
$page->write(); $page->write();
$page->publish('Stage', 'Live'); $page->publish('Stage', 'Live');
// Add a stage-only version
$page->Content = "Version2";
$page->write();
$response = $this->get('/testpage'); $response = $this->get('/testpage');
$this->assertEquals($response->getStatusCode(), 200, 'Doesnt require login for implicit live stage'); $this->assertEquals($response->getStatusCode(), 200, 'Doesnt require login for implicit live stage');

View File

@ -18,7 +18,8 @@ class ErrorPageTest extends FunctionalTest {
$this->tmpAssetsPath = sprintf('%s/_tmp_assets_%s', TEMP_FOLDER, rand()); $this->tmpAssetsPath = sprintf('%s/_tmp_assets_%s', TEMP_FOLDER, rand());
Filesystem::makeFolder($this->tmpAssetsPath . '/ErrorPageTest'); Filesystem::makeFolder($this->tmpAssetsPath . '/ErrorPageTest');
ErrorPage::config()->static_filepath = $this->tmpAssetsPath . '/ErrorPageTest'; ErrorPage::config()->static_filepath = $this->tmpAssetsPath . '/ErrorPageTest';
$this->origEnvType = Config::inst()->get('Director', 'environment_type');
Config::inst()->update('Director', 'environment_type', 'live'); Config::inst()->update('Director', 'environment_type', 'live');
} }
@ -29,6 +30,8 @@ class ErrorPageTest extends FunctionalTest {
Filesystem::removeFolder($this->tmpAssetsPath . '/ErrorPageTest'); Filesystem::removeFolder($this->tmpAssetsPath . '/ErrorPageTest');
Filesystem::removeFolder($this->tmpAssetsPath); Filesystem::removeFolder($this->tmpAssetsPath);
Config::inst()->update('Director', 'environment_type', $this->origEnvType);
} }
public function test404ErrorPage() { public function test404ErrorPage() {

View File

@ -126,7 +126,16 @@ class SiteTreePermissionsTest extends FunctionalTest {
} }
public function testCanViewStage() { public function testCanViewStage() {
$this->useDraftSite(false); // useDraftSite deliberately disables checking the stage as part of canView
// Get page & make sure it exists on Live
$page = $this->objFromFixture('Page', 'standardpage'); $page = $this->objFromFixture('Page', 'standardpage');
$page->publish('Stage', 'Live');
// Then make sure there's a new version on Stage
$page->Title = 1;
$page->write();
$editor = $this->objFromFixture('Member', 'editor'); $editor = $this->objFromFixture('Member', 'editor');
$websiteuser = $this->objFromFixture('Member', 'websiteuser'); $websiteuser = $this->objFromFixture('Member', 'websiteuser');
@ -135,6 +144,8 @@ class SiteTreePermissionsTest extends FunctionalTest {
$this->assertTrue($page->canViewStage('Live', $editor)); $this->assertTrue($page->canViewStage('Live', $editor));
$this->assertTrue($page->canViewStage('Stage', $editor)); $this->assertTrue($page->canViewStage('Stage', $editor));
$this->useDraftSite();
} }
public function testAccessTabOnlyDisplaysWithGrantAccessPermissions() { public function testAccessTabOnlyDisplaysWithGrantAccessPermissions() {

View File

@ -4,8 +4,9 @@
* @subpackage tests * @subpackage tests
*/ */
class SiteTreeTest extends SapphireTest { class SiteTreeTest extends SapphireTest {
protected static $fixture_file = 'SiteTreeTest.yml';
protected static $fixture_file = 'SiteTreeTest.yml';
protected $illegalExtensions = array( protected $illegalExtensions = array(
'SiteTree' => array('SiteTreeSubsites') 'SiteTree' => array('SiteTreeSubsites')
); );
@ -700,6 +701,18 @@ class SiteTreeTest extends SapphireTest {
$this->assertTrue($sitetree->validURLSegment(), 'Valid URLSegment values are allowed'); $this->assertTrue($sitetree->validURLSegment(), 'Valid URLSegment values are allowed');
} }
public function testURLSegmentPrioritizesExtensionVotes() {
$sitetree = new SiteTree();
$sitetree->URLSegment = 'unique-segment';
$this->assertTrue($sitetree->validURLSegment());
SiteTree::add_extension('SiteTreeTest_Extension');
$sitetree = new SiteTree();
$sitetree->URLSegment = 'unique-segment';
$this->assertFalse($sitetree->validURLSegment());
SiteTree::remove_extension('SiteTreeTest_Extension');
}
public function testURLSegmentMultiByte() { public function testURLSegmentMultiByte() {
$origAllow = Config::inst()->get('URLSegmentFilter', 'default_allow_multibyte'); $origAllow = Config::inst()->get('URLSegmentFilter', 'default_allow_multibyte');
Config::inst()->update('URLSegmentFilter', 'default_allow_multibyte', true); Config::inst()->update('URLSegmentFilter', 'default_allow_multibyte', true);
@ -991,3 +1004,11 @@ class SiteTreeTest_StageStatusInherit extends SiteTree implements TestOnly {
return $flags; return $flags;
} }
} }
class SiteTreeTest_Extension extends DataExtension implements TestOnly {
public function augmentValidURLSegment() {
return false;
}
}

View File

@ -11,7 +11,7 @@
class ZZZSearchFormTest extends FunctionalTest { class ZZZSearchFormTest extends FunctionalTest {
protected static $fixture_file = 'SearchFormTest.yml'; protected static $fixture_file = 'SearchFormTest.yml';
protected $mockController; protected $mockController;
public function waitUntilIndexingFinished() { public function waitUntilIndexingFinished() {
@ -88,7 +88,6 @@ class ZZZSearchFormTest extends FunctionalTest {
); );
} }
/*
public function testUnpublishedPagesNotIncluded() { public function testUnpublishedPagesNotIncluded() {
if(!$this->checkFulltextSupport()) return; if(!$this->checkFulltextSupport()) return;
@ -102,14 +101,15 @@ class ZZZSearchFormTest extends FunctionalTest {
'Unpublished pages are not found by searchform' 'Unpublished pages are not found by searchform'
); );
} }
*/
public function testPagesRestrictedToLoggedinUsersNotIncluded() { public function testPagesRestrictedToLoggedinUsersNotIncluded() {
if(!$this->checkFulltextSupport()) return; if(!$this->checkFulltextSupport()) return;
$sf = new SearchForm($this->mockController, 'SearchForm'); $sf = new SearchForm($this->mockController, 'SearchForm');
$page = $this->objFromFixture('SiteTree', 'restrictedViewLoggedInUsers'); $page = $this->objFromFixture('SiteTree', 'restrictedViewLoggedInUsers');
$page->publish('Stage', 'Live');
$results = $sf->getResults(null, array('Search'=>'restrictedViewLoggedInUsers')); $results = $sf->getResults(null, array('Search'=>'restrictedViewLoggedInUsers'));
$this->assertNotContains( $this->assertNotContains(
$page->ID, $page->ID,
@ -134,6 +134,8 @@ class ZZZSearchFormTest extends FunctionalTest {
$sf = new SearchForm($this->mockController, 'SearchForm'); $sf = new SearchForm($this->mockController, 'SearchForm');
$page = $this->objFromFixture('SiteTree', 'restrictedViewOnlyWebsiteUsers'); $page = $this->objFromFixture('SiteTree', 'restrictedViewOnlyWebsiteUsers');
$page->publish('Stage', 'Live');
$results = $sf->getResults(null, array('Search'=>'restrictedViewOnlyWebsiteUsers')); $results = $sf->getResults(null, array('Search'=>'restrictedViewOnlyWebsiteUsers'));
$this->assertNotContains( $this->assertNotContains(
$page->ID, $page->ID,
@ -162,13 +164,17 @@ class ZZZSearchFormTest extends FunctionalTest {
$member->logOut(); $member->logOut();
} }
public function testInheritedRestrictedPagesNotInlucded() { public function testInheritedRestrictedPagesNotIncluded() {
if(!$this->checkFulltextSupport()) return; if(!$this->checkFulltextSupport()) return;
$sf = new SearchForm($this->mockController, 'SearchForm'); $sf = new SearchForm($this->mockController, 'SearchForm');
$parent = $this->objFromFixture('SiteTree', 'restrictedViewLoggedInUsers');
$parent->publish('Stage', 'Live');
$page = $this->objFromFixture('SiteTree', 'inheritRestrictedView'); $page = $this->objFromFixture('SiteTree', 'inheritRestrictedView');
$page->publish('Stage', 'Live');
$results = $sf->getResults(null, array('Search'=>'inheritRestrictedView')); $results = $sf->getResults(null, array('Search'=>'inheritRestrictedView'));
$this->assertNotContains( $this->assertNotContains(
$page->ID, $page->ID,