API CHANGE Deprecated DataObjectDecorator->augmentBeforeWrite(), use DataObjectDecorator->onBeforeWrite()

API CHANGE Deprecated DataObjectDecorator->augmentPopulateDefaults(), use DataObjectDecorator->populateDefaults()
API CHANGE Deprecated DataObjectDecorator->augmentDefaultRecords(), use DataObjectDecorator->requireDefaultRecords()
API CHANGE Deprecated DataObjectDecorator->alternateCan(), use DataObjectDecorator->can()
API CHANGE Deprecated DataObjectDecorator->alternateCanAddChildren(), use DataObjectDecorator->canAddChildren()
API CHANGE Deprecated DataObjectDecorator->alternateCanView(), use DataObjectDecorator->canView()
API CHANGE Deprecated DataObjectDecorator->alternateCanDelete(), use DataObjectDecorator->canDelete()
API CHANGE Deprecated DataObjectDecorator->alternateCanEdit(), use DataObjectDecorator->canEdit()
API CHANGE Deprecated DataObjectDecorator->alternateCanCreate(), use DataObjectDecorator->canCreate()
ENHANCEMENT Added DataObject->onAfterDelete() and DataObjectDecorator->onAfterDelete()
ENHANCEMENT Added stub methods to DataObjectDecorator for documentation purposes

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@65453 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-11-07 12:18:35 +00:00
parent cfde8adaee
commit 137b76e271
5 changed files with 252 additions and 51 deletions

View File

@ -562,12 +562,18 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* database. Don't forget to call parent::onBeforeWrite(), though!
*
* This called after {@link $this->validate()}, so you can be sure that your data is valid.
*
* @uses DataObjectDecorator->onBeforeWrite()
*/
protected function onBeforeWrite() {
$this->brokenOnWrite = false;
// DEPRECATED 2.3: use onBeforeWrite()
$dummy = null;
$this->extend('augmentBeforeWrite', $dummy);
$dummy = null;
$this->extend('onBeforeWrite', $dummy);
}
/**
@ -575,10 +581,16 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* You can overload this to act upon changes made to the data after it is written.
* $this->changed will have a record
* database. Don't forget to call parent::onAfterWrite(), though!
*
* @uses DataObjectDecorator->onAfterWrite()
*/
protected function onAfterWrite() {
// DEPRECATED 2.3: use onAfterWrite()
$dummy = null;
$this->extend('augmentAfterWrite', $dummy);
$dummy = null;
$this->extend('onAfterWrite', $dummy);
}
/**
@ -591,9 +603,18 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* Event handler called before deleting from the database.
* You can overload this to clean up or otherwise process data before delete this
* record. Don't forget to call parent::onBeforeDelete(), though!
*
* @uses DataObjectDecorator->onBeforeDelete()
*/
protected function onBeforeDelete() {
$this->brokenOnDelete = false;
$dummy = null;
$this->extend('onBeforeDelete', $dummy);
}
protected function onAfterDelete() {
$this->extend('onAfterDelete');
}
/**
@ -606,6 +627,8 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* Load the default values in from the self::$defaults array.
* Will traverse the defaults of the current class and all its parent classes.
* Called by the constructor when creating new records.
*
* @uses DataObjectDecorator->populateDefaults()
*/
public function populateDefaults() {
$classes = array_reverse(ClassInfo::ancestry($this));
@ -630,7 +653,10 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
}
}
// DEPRECATED 2.3: use populateDefaults()
$this->extend('augmentPopulateDefaults');
$this->extend('populateDefaults');
}
/**
@ -641,6 +667,8 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* - Extensions such as Versioned will ammend the database-write to ensure that a version is saved.
* - Calls to {@link DataObjectLog} can be used to see everything that's been changed.
*
* @uses DataObjectDecorator->augmentWrite()
*
* @param boolean $showDebug Show debugging information
* @param boolean $forceInsert Run INSERT command rather than UPDATE, even if record already exists
* @param boolean $forceWrite Write to database even if there are no changes
@ -768,6 +796,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
}
$this->extend('augmentWrite', $manipulation);
// New records have their insert into the base data table done first, so that they can pass the
// generated ID on to the rest of the manipulation
if(isset($isNewRecord) && $isNewRecord && isset($manipulation[$baseTable])) {
@ -838,6 +867,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* Delete this data object.
* $this->onBeforeDelete() gets called.
* Note that in Versioned objects, both Stage and Live will be deleted.
* @uses DataObjectDecorator->augmentSQL()
*/
public function delete() {
$this->brokenOnDelete = true;
@ -845,6 +875,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
if($this->brokenOnDelete) {
user_error("$this->class has a broken onBeforeDelete() function. Make sure that you call parent::onBeforeDelete().", E_USER_ERROR);
}
foreach($this->getClassAncestry() as $ancestor) {
if(self::has_own_table($ancestor)) {
$sql = new SQLQuery();
@ -856,6 +887,8 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
}
}
$this->onAfterDelete();
$this->OldID = $this->ID;
$this->ID = 0;
@ -2076,12 +2109,13 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
/**
* Like {@link buildSQL}, but applies the extension modifications.
*
* @uses DataObjectDecorator->augmentSQL()
*
* @param string $filter A filter to be inserted into the WHERE clause.
* @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, self::$default_sort will be used.
* @param string|array $limit A limit expression to be inserted into the LIMIT clause.
* @param string $join A single join clause. This can be used for filtering, only 1 instance of each DataObject will be returned.
* @param string $having A filter to be inserted into the HAVING clause.
*
* @return SQLQuery Query built
*/
public function extendedSQL($filter = "", $sort = "", $limit = "", $join = "", $having = ""){
@ -2229,9 +2263,10 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
/**
* Does the hard work for get_one()
*
* @uses DataObjectDecorator->augmentSQL()
*
* @param string $filter A filter to be inserted into the WHERE clause
* @param string $orderby A sort expression to be inserted into the ORDER BY clause.
*
* @return DataObject The first item matching the query
*/
public function instance_get_one($filter, $orderby = null) {
@ -2329,6 +2364,8 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
/**
* Check the database schema and update it as necessary.
*
* @uses DataObjectDecorator->augmentDatabase()
*/
public function requireTable() {
// Only build the table if we've actually got fields
@ -2373,6 +2410,8 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
* database is built, after the database tables have all been created. Overload
* this to add default records when the database is built, but make sure you
* call parent::requireDefaultRecords().
*
* @uses DataObjectDecorator->requireDefaultRecords()
*/
public function requireDefaultRecords() {
$defaultRecords = $this->stat('default_records');
@ -2389,8 +2428,11 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
}
}
// Let any extentions make their own database default data
// DEPRECATED 2.3: Use requireDefaultRecords()
$this->extend('augmentDefaultRecords', $dummy);
// Let any extentions make their own database default data
$this->extend('requireDefaultRecords', $dummy);
}
/**
@ -2476,8 +2518,8 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
if(!$fields) $fields = array_keys($this->summaryFields());
// we need to make sure the format is unified before
// augumenting fields, so decorators can apply consistent checks
// but also after augumenting fields, because the decorator
// augmenting fields, so decorators can apply consistent checks
// but also after augmenting fields, because the decorator
// might use the shorthand notation as well
// rewrite array, if it is using shorthand syntax

View File

@ -81,6 +81,35 @@ abstract class DataObjectDecorator extends Extension {
function augmentWrite(&$manipulation) {
}
function onBeforeWrite() {
}
function onAfterWrite() {
}
function onBeforeDelete() {
}
function onAfterDelete() {
}
function requireDefaultRecords() {
}
function populateDefaults() {
}
function can($member) {
}
function canEdit($member) {
}
function canDelete($member) {
}
function canCreate($member) {
}
/**
* Define extra database fields
@ -108,7 +137,6 @@ abstract class DataObjectDecorator extends Extension {
return array();
}
/**
* This function is used to provide modifications to the form in the CMS
* by the decorator. By default, no changes are made.
@ -135,6 +163,9 @@ abstract class DataObjectDecorator extends Extension {
function updateFormFields(FieldSet &$fields) {
}
function updateCMSActions(FieldSet &$actions) {
}
/**
* this function is used to provide modifications to the summary fields in CMS
* by the decorator

View File

@ -503,13 +503,13 @@ class SiteTree extends DataObject {
/**
* This function should return true if the current user can add children
* to this page.
*
* It can be overloaded to customise the security model for an
* to this page. It can be overloaded to customise the security model for an
* application.
*
* Returns true if the member is allowed to do the given action.
*
* @uses DataObjectDecorator->can()
*
* @param string $perm The permission to be checked, such as 'View'.
* @param Member $member The member whose permissions need checking.
* Defaults to the currently logged in user.
@ -520,9 +520,8 @@ class SiteTree extends DataObject {
* @todo Check we get a endless recursion if we use parent::can()
*/
function can($perm, $member = null) {
if(!isset($member)) {
$member = Member::currentUser();
}
if(!isset($member)) $member = Member::currentUser();
if(Permission::checkMember($member, "ADMIN")) return true;
if(method_exists($this, 'can' . ucfirst($perm))) {
@ -530,13 +529,14 @@ class SiteTree extends DataObject {
return $this->$method($member);
}
$args = array($perm, $member, true);
$this->extend('alternateCan', $args);
if($args[2] == false) return false;
// DEPRECATED 2.3: Use can()
$results = $this->extend('alternateCan', $member);
if($results && is_array($results)) if(!min($results)) return false;
$results = $this->extend('can', $member);
if($results && is_array($results)) if(!min($results)) return false;
return true;
//return parent::can($perm, $member);
}
@ -550,7 +550,7 @@ class SiteTree extends DataObject {
* - canEdit() is not granted
* - There are no classes defined in {@link $allowed_children}
*
* @uses alternateCanAddChildren()
* @uses DataObjectDecorator->canAddChildren()
* @uses canEdit()
* @uses $allowed_children
*
@ -562,9 +562,12 @@ class SiteTree extends DataObject {
}
if(Permission::checkMember($member, "ADMIN")) return true;
$args = array($member, true);
$this->extend('alternateCanAddChildren', $args);
if($args[1] == false) return false;
// DEPRECATED 2.3: use canAddChildren() instead
$results = $this->extend('alternateCanAddChildren', $member);
if($results && is_array($results)) if(!min($results)) return false;
$results = $this->extend('canAddChildren', $member);
if($results && is_array($results)) if(!min($results)) return false;
return $this->canEdit() && $this->stat('allowed_children') != 'none';
}
@ -576,12 +579,12 @@ class SiteTree extends DataObject {
* application.
*
* Denies permission if any of the following conditions is TRUE:
* - alternateCanView() on any decorator returns FALSE
* - canView() on any decorator returns FALSE
* - "CanViewType" directive is set to "Inherit" and any parent page return false for canView()
* - "CanViewType" directive is set to "LoggedInUsers" and no user is logged in
* - "CanViewType" directive is set to "OnlyTheseUsers" and user is not in the given groups
*
* @uses alternateCanView()
* @uses DataObjectDecorator->canView()
* @uses ViewerGroups()
*
* @return boolean True if the current user can view this page.
@ -592,10 +595,13 @@ class SiteTree extends DataObject {
// admin override
if(Permission::checkMember($member, "ADMIN")) return true;
// DEPRECATED 2.3: use canView() instead
$results = $this->extend('alternateCanView', $member);
if($results && is_array($results)) if(!min($results)) return false;
// decorated access checks
$args = array($member, true);
$this->extend('alternateCanView', $args);
if($args[1] == false) return false;
$results = $this->extend('canView', $member);
if($results && is_array($results)) if(!min($results)) return false;
// check for empty spec
if(
@ -629,12 +635,11 @@ class SiteTree extends DataObject {
* application.
*
* Denies permission if any of the following conditions is TRUE:
* - alternateCanDelete() returns FALSE on any decorator
* - canDelete() returns FALSE on any decorator
* - canEdit() returns FALSE
* - any descendant page returns FALSE for canDelete()
*
* @todo Check if all children can be deleted as well
* @uses alternateCanDelete()
* @uses canDelete()
* @uses canEdit()
*
* @param Member $member
@ -645,9 +650,13 @@ class SiteTree extends DataObject {
if(Permission::checkMember($member, "ADMIN")) return true;
$args = array($member, true);
$this->extend('alternateCanDelete', $args);
if($args[1] == false) return false;
// DEPRECATED 2.3: use canDelete() instead
$results = $this->extend('alternateCanDelete', $member);
if($results && is_array($results)) if(!min($results)) return false;
// decorated access checks
$results = $this->extend('canDelete', $member);
if($results && is_array($results)) if(!min($results)) return false;
// if page can't be edited, don't grant delete permissions
if(!$this->canEdit()) return false;
@ -667,12 +676,11 @@ class SiteTree extends DataObject {
* application.
*
* Denies permission if any of the following conditions is TRUE:
* - alternateCanCreate() returns FALSE on any decorator
* - canCreate() returns FALSE on any decorator
* - $can_create is set to FALSE and the site is not in "dev mode"
*
* Use {@link canAddChildren()} to control behaviour of creating children under this page.
*
* @uses alternateCanCreate()
* @uses $can_create
*
* @param Member $member
@ -683,9 +691,13 @@ class SiteTree extends DataObject {
if(Permission::checkMember($member, "ADMIN")) return true;
$args = array($member, true);
$this->extend('alternateCanCreate', $args);
if($args[1] == false) return false;
// DEPRECATED 2.3: use canCreate() instead
$results = $this->extend('alternateCanCreate', $member);
if($results && is_array($results)) if(!min($results)) return false;
// decorated permission checks
$results = $this->extend('canCreate', $member);
if($results && is_array($results)) if(!min($results)) return false;
return $this->stat('can_create') != false || Director::isDev();
}
@ -697,13 +709,12 @@ class SiteTree extends DataObject {
* application.
*
* Denies permission if any of the following conditions is TRUE:
* - alternateCanEdit() on any decorator returns FALSE
* - canEdit() on any decorator returns FALSE
* - canView() return false
* - "CanEditType" directive is set to "Inherit" and any parent page return false for canEdit()
* - "CanEditType" directive is set to "LoggedInUsers" and no user is logged in or doesn't have the CMS_Access_CMSMAIN permission code
* - "CanEditType" directive is set to "OnlyTheseUsers" and user is not in the given groups
*
* @uses alternateCanEdit()
* @uses canView()
* @uses EditorGroups()
*
@ -711,13 +722,17 @@ class SiteTree extends DataObject {
* @return boolean True if the current user can edit this page.
*/
public function canEdit($member = null) {
if(Permission::checkMember($member, "ADMIN")) return true;
if(!$member) $member = Member::currentUser();
if(Permission::checkMember($member, "ADMIN")) return true;
// DEPRECATED 2.3: use canEdit() instead
$results = $this->extend('alternateCanEdit', $member);
if($results && is_array($results)) if(!min($results)) return false;
// decorated access checks
$args = array($member, true);
$this->extend('alternateCanEdit', $args);
if($args[1] == false) return false;
$results = $this->extend('canEdit', $member);
if($results && is_array($results)) if(!min($results)) return false;
// if page can't be viewed, don't grant edit permissions
if(!$this->canView()) return false;
@ -754,20 +769,27 @@ class SiteTree extends DataObject {
* application.
*
* Denies permission if any of the following conditions is TRUE:
* - alternateCanPublish() on any decorator returns FALSE
* - canPublish() on any decorator returns FALSE
* - canEdit() returns FALSE
*
* @uses alternateCanPublish()
* @uses DataObjectDecorator->canPublish()
*
* @param Member $member
* @return boolean True if the current user can publish this page.
*/
public function canPublish($member = null) {
if(!$member) $member = Member::currentUser();
if(Permission::checkMember($member, "ADMIN")) return true;
// DEPRECATED 2.3: use canPublish() instead
$results = $this->extend('alternateCanPublish', $member);
if($results && is_array($results)) if(!min($results)) return false;
// If we have a result, then that means at least one decorator specified alternateCanPublish
// Allow the permission check only if *all* voting decorators allow it.
if(!$member) $member = Member::currentUser();
$results = $this->extend('alternateCanPublish', $member);
if($results && is_array($results)) return min($results);
$results = $this->extend('canPublish', $member);
if($results && is_array($results)) if(!min($results)) return false;
// Normal case
return $this->canEdit();
@ -830,8 +852,11 @@ class SiteTree extends DataObject {
}
$tags .= "<meta http-equiv=\"Content-Language\" content=\"". Translatable::current_lang() ."\"/>\n";
// DEPRECATED 2.3: Use MetaTags
$this->extend('updateMetaTags', $tags);
$this->extend('MetaTags', $tags);
return $tags;
}

View File

@ -1,6 +1,6 @@
<?php
class DataObjectDecoratorTest extends SapphireTest {
static $fixture_file = 'sapphire/tests/DataObjectTest.yml';
static $fixture_file = 'sapphire/tests/DataObjectDecoratorTest.yml';
function testOneToManyAssociationWithDecorator() {
// Fails in RestfulServerTest
@ -31,6 +31,31 @@ class DataObjectDecoratorTest extends SapphireTest {
$contact->delete();
}
function testPermissionDecoration() {
// testing behaviour in isolation, too many sideeffects and other checks
// in SiteTree->can*() methods to test one single feature reliably with them
$obj = $this->objFromFixture('DataObjectDecoratorTest_MyObject', 'object1');
$websiteuser = $this->objFromFixture('Member', 'websiteuser');
$admin = $this->objFromFixture('Member', 'admin');
$this->assertFalse(
$obj->canOne($websiteuser),
'Both decorators return true, but original method returns false'
);
$this->assertFalse(
$obj->canTwo($websiteuser),
'One decorator returns false, original returns true, but decorator takes precedence'
);
$this->assertTrue(
$obj->canThree($admin),
'Undefined decorator methods returning NULL dont influence the original method'
);
}
}
class DataObjectDecoratorTest_Member extends DataObject implements TestOnly {
@ -55,6 +80,7 @@ class DataObjectDecoratorTest_ContactRole extends DataObjectDecorator implements
)
);
}
}
class DataObjectDecoratorTest_RelatedObject extends DataObject implements TestOnly {
@ -71,4 +97,68 @@ class DataObjectDecoratorTest_RelatedObject extends DataObject implements TestOn
}
DataObject::add_extension('DataObjectDecoratorTest_Member', 'DataObjectDecoratorTest_ContactRole');
class DataObjectDecoratorTest_MyObject extends DataObject implements TestOnly {
static $db = array(
'Title' => 'Varchar',
);
function canOne($member = null) {
// decorated access checks
$results = $this->extend('canOne', $member);
if($results && is_array($results)) if(!min($results)) return false;
return false;
}
function canTwo($member = null) {
// decorated access checks
$results = $this->extend('canTwo', $member);
if($results && is_array($results)) if(!min($results)) return false;
return true;
}
function canThree($member = null) {
// decorated access checks
$results = $this->extend('canThree', $member);
if($results && is_array($results)) if(!min($results)) return false;
return true;
}
}
class DataObjectDecoratorTest_Ext1 extends DataObjectDecorator implements TestOnly {
function canOne($member = null) {
return true;
}
function canTwo($member = null) {
return false;
}
function canThree($member = null) {
}
}
class DataObjectDecoratorTest_Ext2 extends DataObjectDecorator implements TestOnly {
function canOne($member = null) {
return true;
}
function canTwo($member = null) {
return true;
}
function canThree($member = null) {
}
}
DataObject::add_extension('DataObjectDecoratorTest_MyObject', 'DataObjectDecoratorTest_Ext1');
DataObject::add_extension('DataObjectDecoratorTest_MyObject', 'DataObjectDecoratorTest_Ext2');
?>

View File

@ -11,7 +11,6 @@ PageComment:
comment4:
Name: Bob
Comment: Second comment by Bob
Page:
home:
Title: Home
@ -22,4 +21,18 @@ Page:
Comments: =>PageComment.comment3,=>PageComment.comment4
page2:
Title: Second Page
Permission:
adminpermission:
Code: ADMIN
Group:
admingroup:
Permissions: =>Permission.adminpermission
Member:
admin:
Email: admin@test.com
Groups: =>Group.admingroup
websiteuser:
Email: websiteuser@test.com
DataObjectDecoratorTest_MyObject:
object1:
Title: Object 1