mirror of
https://github.com/silverstripe/silverstripe-reports
synced 2024-10-22 11:05:53 +02:00
API CHANGE Checking for SiteTree::$allowed_children in SiteTree->validate() (was only checked via JavaScript before).
BUGFIX Ensure that VirtualPage $allowed_children are checked on original classes to avoid allowing more than necessary (AIR-38)
This commit is contained in:
parent
d03724e116
commit
2dd96a4050
@ -16,6 +16,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
|
|||||||
* If a classname is prefixed by "*", such as "*Page", then only that
|
* If a classname is prefixed by "*", such as "*Page", then only that
|
||||||
* class is allowed - no subclasses. Otherwise, the class and all its
|
* class is allowed - no subclasses. Otherwise, the class and all its
|
||||||
* subclasses are allowed.
|
* subclasses are allowed.
|
||||||
|
* To control allowed children on root level (no parent), use {@link $can_be_root}.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -1479,6 +1480,36 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
|
|||||||
parent::onAfterDelete();
|
parent::onAfterDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
$result = parent::validate();
|
||||||
|
|
||||||
|
// Allowed children validation
|
||||||
|
$parent = $this->getParent();
|
||||||
|
if($parent && $parent->exists()) {
|
||||||
|
// No need to check for subclasses or instanceof, as allowedChildren() already
|
||||||
|
// deconstructs any inheritance trees already.
|
||||||
|
$allowed = $parent->allowedChildren();
|
||||||
|
$subject = ($this instanceof VirtualPage) ? $this->CopyContentFrom() : $this;
|
||||||
|
if($subject->ID && !in_array($subject->ClassName, $allowed)) {
|
||||||
|
|
||||||
|
$result->error(
|
||||||
|
sprintf(
|
||||||
|
_t(
|
||||||
|
'SiteTree.PageTypeNotAllowed',
|
||||||
|
'Page type "%s" not allowed as child of this parent page',
|
||||||
|
PR_MEDIUM,
|
||||||
|
'First argument is a class name'
|
||||||
|
),
|
||||||
|
$subject->i18n_singular_name()
|
||||||
|
),
|
||||||
|
'ALLOWED_CHILDREN'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns TRUE if this object has a URLSegment value that does not conflict with any other objects. This methods
|
* Returns TRUE if this object has a URLSegment value that does not conflict with any other objects. This methods
|
||||||
* checks for:
|
* checks for:
|
||||||
@ -2329,9 +2360,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
function allowedChildren() {
|
function allowedChildren() {
|
||||||
|
$allowedChildren = array();
|
||||||
$candidates = $this->stat('allowed_children');
|
$candidates = $this->stat('allowed_children');
|
||||||
if($candidates && $candidates != "none" && $candidates != "SiteTree_root") {
|
if($candidates && $candidates != "none" && $candidates != "SiteTree_root") {
|
||||||
foreach($candidates as $candidate) {
|
foreach($candidates as $candidate) {
|
||||||
|
// If a classname is prefixed by "*", such as "*Page", then only that
|
||||||
|
// class is allowed - no subclasses. Otherwise, the class and all its subclasses are allowed.
|
||||||
if(substr($candidate,0,1) == '*') {
|
if(substr($candidate,0,1) == '*') {
|
||||||
$allowedChildren[] = substr($candidate,1);
|
$allowedChildren[] = substr($candidate,1);
|
||||||
} else {
|
} else {
|
||||||
@ -2341,8 +2375,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $allowedChildren;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $allowedChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +10,14 @@ class SiteTreeTest extends SapphireTest {
|
|||||||
'SiteTree' => array('SiteTreeSubsites')
|
'SiteTree' => array('SiteTreeSubsites')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
protected $extraDataObjects = array(
|
||||||
|
'SiteTreeTest_ClassA',
|
||||||
|
'SiteTreeTest_ClassB',
|
||||||
|
'SiteTreeTest_ClassC',
|
||||||
|
'SiteTreeTest_ClassD',
|
||||||
|
'SiteTreeTest_ClassCext'
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Necessary because of monolithic Translatable design
|
* @todo Necessary because of monolithic Translatable design
|
||||||
*/
|
*/
|
||||||
@ -742,6 +750,81 @@ class SiteTreeTest extends SapphireTest {
|
|||||||
$this->assertContains('Page', $classes, 'Page types do contain subclasses');
|
$this->assertContains('Page', $classes, 'Page types do contain subclasses');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testAllowedChildren() {
|
||||||
|
$page = new SiteTree();
|
||||||
|
$this->assertContains(
|
||||||
|
'VirtualPage',
|
||||||
|
$page->allowedChildren(),
|
||||||
|
'Includes core subclasses by default'
|
||||||
|
);
|
||||||
|
|
||||||
|
$classA = new SiteTreeTest_ClassA();
|
||||||
|
$this->assertEquals(
|
||||||
|
array('SiteTreeTest_ClassB'),
|
||||||
|
$classA->allowedChildren(),
|
||||||
|
'Direct setting of allowed children'
|
||||||
|
);
|
||||||
|
|
||||||
|
$classB = new SiteTreeTest_ClassB();
|
||||||
|
$this->assertEquals(
|
||||||
|
array('SiteTreeTest_ClassC', 'SiteTreeTest_ClassCext'),
|
||||||
|
$classB->allowedChildren(),
|
||||||
|
'Includes subclasses'
|
||||||
|
);
|
||||||
|
|
||||||
|
$classD = new SiteTreeTest_ClassD();
|
||||||
|
$this->assertEquals(
|
||||||
|
array('SiteTreeTest_ClassC'),
|
||||||
|
$classD->allowedChildren(),
|
||||||
|
'Excludes subclasses if class is prefixed by an asterisk'
|
||||||
|
);
|
||||||
|
|
||||||
|
$classC = new SiteTreeTest_ClassC();
|
||||||
|
$this->assertEquals(
|
||||||
|
array(),
|
||||||
|
$classC->allowedChildren(),
|
||||||
|
'Null setting'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testAllowedChildrenValidation() {
|
||||||
|
$page = new SiteTree();
|
||||||
|
$page->write();
|
||||||
|
$classA = new SiteTreeTest_ClassA();
|
||||||
|
$classA->write();
|
||||||
|
$classB = new SiteTreeTest_ClassB();
|
||||||
|
$classB->write();
|
||||||
|
$classC = new SiteTreeTest_ClassC();
|
||||||
|
$classC->write();
|
||||||
|
$classD = new SiteTreeTest_ClassD();
|
||||||
|
$classD->write();
|
||||||
|
$classCext = new SiteTreeTest_ClassCext();
|
||||||
|
$classCext->write();
|
||||||
|
|
||||||
|
$classB->ParentID = $page->ID;
|
||||||
|
$valid = $classB->validate();
|
||||||
|
$this->assertTrue($valid->valid(), "Does allow children on unrestricted parent");
|
||||||
|
|
||||||
|
$classB->ParentID = $classA->ID;
|
||||||
|
$valid = $classB->validate();
|
||||||
|
$this->assertTrue($valid->valid(), "Does allow child specifically allowed by parent");
|
||||||
|
|
||||||
|
$classC->ParentID = $classA->ID;
|
||||||
|
$valid = $classC->validate();
|
||||||
|
$this->assertFalse($valid->valid(), "Doesnt allow child on parents specifically restricting children");
|
||||||
|
|
||||||
|
$classB->ParentID = $classC->ID;
|
||||||
|
$valid = $classB->validate();
|
||||||
|
$this->assertFalse($valid->valid(), "Doesnt allow child on parents disallowing all children");
|
||||||
|
|
||||||
|
$classB->ParentID = $classC->ID;
|
||||||
|
$valid = $classB->validate();
|
||||||
|
$this->assertFalse($valid->valid(), "Doesnt allow child on parents disallowing all children");
|
||||||
|
|
||||||
|
$classCext->ParentID = $classD->ID;
|
||||||
|
$valid = $classCext->validate();
|
||||||
|
$this->assertFalse($valid->valid(), "Doesnt allow child where only parent class is allowed on parent node, and asterisk prefixing is used");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**#@+
|
/**#@+
|
||||||
@ -774,3 +857,29 @@ class SiteTreeTest_NullHtmlCleaner extends HTMLCleaner {
|
|||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SiteTreeTest_ClassA extends Page implements TestOnly {
|
||||||
|
|
||||||
|
static $need_permission = array('ADMIN', 'CMS_ACCESS_CMSMain');
|
||||||
|
|
||||||
|
static $allowed_children = array('SiteTreeTest_ClassB');
|
||||||
|
}
|
||||||
|
|
||||||
|
class SiteTreeTest_ClassB extends Page implements TestOnly {
|
||||||
|
// Also allowed subclasses
|
||||||
|
static $allowed_children = array('SiteTreeTest_ClassC');
|
||||||
|
}
|
||||||
|
|
||||||
|
class SiteTreeTest_ClassC extends Page implements TestOnly {
|
||||||
|
static $allowed_children = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SiteTreeTest_ClassD extends Page implements TestOnly {
|
||||||
|
// Only allows this class, no children classes
|
||||||
|
static $allowed_children = array('*SiteTreeTest_ClassC');
|
||||||
|
}
|
||||||
|
|
||||||
|
class SiteTreeTest_ClassCext extends SiteTreeTest_ClassC implements TestOnly {
|
||||||
|
// Override SiteTreeTest_ClassC definitions
|
||||||
|
static $allowed_children = array('SiteTreeTest_ClassB');
|
||||||
|
}
|
@ -3,6 +3,11 @@
|
|||||||
class VirtualPageTest extends SapphireTest {
|
class VirtualPageTest extends SapphireTest {
|
||||||
static $fixture_file = 'VirtualPageTest.yml';
|
static $fixture_file = 'VirtualPageTest.yml';
|
||||||
|
|
||||||
|
protected $extraDataObjects = array(
|
||||||
|
'VirtualPageTest_ClassA',
|
||||||
|
'VirtualPageTest_ClassB',
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that, after you update the source page of a virtual page, all the virtual pages
|
* Test that, after you update the source page of a virtual page, all the virtual pages
|
||||||
* are updated
|
* are updated
|
||||||
@ -321,4 +326,49 @@ class VirtualPageTest extends SapphireTest {
|
|||||||
$vp = DataObject::get_by_id('SiteTree', $vp->ID);
|
$vp = DataObject::get_by_id('SiteTree', $vp->ID);
|
||||||
$this->assertEquals(1, $vp->HasBrokenLink);
|
$this->assertEquals(1, $vp->HasBrokenLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base functionality tested in {@link SiteTreeTest->testAllowedChildrenValidation()}.
|
||||||
|
*/
|
||||||
|
function testAllowedChildrenLimitedOnVirtualPages() {
|
||||||
|
$classA = new SiteTreeTest_ClassA();
|
||||||
|
$classA->write();
|
||||||
|
$classB = new SiteTreeTest_ClassB();
|
||||||
|
$classB->write();
|
||||||
|
$classBVirtual = new VirtualPage();
|
||||||
|
$classBVirtual->CopyContentFromID = $classB->ID;
|
||||||
|
$classBVirtual->write();
|
||||||
|
$classC = new SiteTreeTest_ClassC();
|
||||||
|
$classC->write();
|
||||||
|
$classCVirtual = new VirtualPage();
|
||||||
|
$classCVirtual->CopyContentFromID = $classC->ID;
|
||||||
|
$classCVirtual->write();
|
||||||
|
|
||||||
|
$classBVirtual->ParentID = $classA->ID;
|
||||||
|
$valid = $classBVirtual->validate();
|
||||||
|
$this->assertTrue($valid->valid(), "Does allow child linked to virtual page type allowed by parent");
|
||||||
|
|
||||||
|
$classCVirtual->ParentID = $classA->ID;
|
||||||
|
$valid = $classCVirtual->validate();
|
||||||
|
$this->assertFalse($valid->valid(), "Doesn't allow child linked to virtual page type disallowed by parent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VirtualPageTest_ClassA extends Page implements TestOnly {
|
||||||
|
|
||||||
|
static $db = array(
|
||||||
|
'MyInitiallyCopiedField' => 'Text',
|
||||||
|
'MyVirtualField' => 'Text',
|
||||||
|
'MyNonVirtualField' => 'Text',
|
||||||
|
);
|
||||||
|
|
||||||
|
static $allowed_children = array('VirtualPageTest_ClassB');
|
||||||
|
}
|
||||||
|
|
||||||
|
class VirtualPageTest_ClassB extends Page implements TestOnly {
|
||||||
|
static $allowed_children = array('VirtualPageTest_ClassC');
|
||||||
|
}
|
||||||
|
|
||||||
|
class VirtualPageTest_ClassC extends Page implements TestOnly {
|
||||||
|
static $allowed_children = array();
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user