diff --git a/Assets/File.php b/Assets/File.php index 8c719c43f..92d13160a 100644 --- a/Assets/File.php +++ b/Assets/File.php @@ -815,10 +815,10 @@ class File extends DataObject implements ShortcodeHandler, AssetContainer, Thumb * @todo Solve this via triggered publishing / ownership in the future */ public function onBeforePublish() { - // Relies on Parent() returning the stage record - $parent = $this->Parent(); - if($parent && $parent->exists()) { - $parent->publishRecursive(); + // Publish all parents from the root up + /** @var Folder $parent */ + foreach($this->getAncestors()->reverse() as $parent) { + $parent->publishSingle(); } } diff --git a/Dev/SapphireTest.php b/Dev/SapphireTest.php index 804ecb671..8292a74f6 100644 --- a/Dev/SapphireTest.php +++ b/Dev/SapphireTest.php @@ -689,6 +689,49 @@ class SapphireTest extends PHPUnit_Framework_TestCase { ); } } + /** + * Asserts that no items in a given list appear in the given dataobject list + * + * @param SS_List|array $matches The patterns to match. Each pattern is a map of key-value pairs. You can + * either pass a single pattern or an array of patterns. + * @param SS_List $dataObjectSet The {@link SS_List} to test. + * + * Examples + * -------- + * Check that $members doesn't have an entry with Email = sam@example.com: + * $this->assertNotDOSContains(array('Email' => '...@example.com'), $members); + * + * Check that $members doesn't have entries with Email = sam@example.com and with + * Email = ingo@example.com: + * $this->assertNotDOSContains(array( + * array('Email' => '...@example.com'), + * array('Email' => 'i...@example.com'), + * ), $members); + */ + public function assertNotDOSContains($matches, $dataObjectSet) { + $extracted = array(); + foreach($dataObjectSet as $object) { + /** @var DataObject $object */ + $extracted[] = $object->toMap(); + } + + $matched = []; + foreach($matches as $match) { + foreach($extracted as $i => $item) { + if($this->dataObjectArrayMatch($item, $match)) { + $matched[] = $extracted[$i]; + break; + } + } + + // We couldn't find a match - assertion failed + $this->assertEmpty( + $matched, + "Failed asserting that the SS_List dosn't contain a set of objects. " + . "Found objects were: " . var_export($matched, true) + ); + } + } /** * Assert that the given {@link SS_List} includes only DataObjects matching the given diff --git a/Forms/ReadonlyField.php b/Forms/ReadonlyField.php index 656f0f80a..bbf5a9d7e 100644 --- a/Forms/ReadonlyField.php +++ b/Forms/ReadonlyField.php @@ -61,6 +61,13 @@ class ReadonlyField extends FormField { return parent::castingHelper($field); } + public function getSchemaStateDefaults() { + $values = parent::getSchemaStateDefaults(); + // Suppress `('none')` from appearing in react as a literal + $values['value'] = $this->dataValue(); + return $values; + } + /** * If $dontEscape is true the returned value will be plain text diff --git a/ORM/Versioning/ChangeSet.php b/ORM/Versioning/ChangeSet.php index ecd9078cd..b3438c4cb 100644 --- a/ORM/Versioning/ChangeSet.php +++ b/ORM/Versioning/ChangeSet.php @@ -26,6 +26,7 @@ use LogicException; * @method Member Owner() * @property string $Name * @property string $State + * @property bool $IsInferred */ class ChangeSet extends DataObject { @@ -47,6 +48,7 @@ class ChangeSet extends DataObject { private static $db = array( 'Name' => 'Varchar', 'State' => "Enum('open,published,reverted','open')", + 'IsInferred' => 'Boolean(0)' // True if created automatically ); private static $has_many = array( @@ -91,6 +93,7 @@ class ChangeSet extends DataObject { * Publish this changeset, then closes it. * * @throws Exception + * @return bool True if successful */ public function publish() { // Logical checks prior to publish @@ -114,9 +117,19 @@ class ChangeSet extends DataObject { $change->publish(); } + // Once this changeset is published, unlink any objects linking to + // records in this changeset as unlinked (set RelationID to 0). + // This is done as a safer alternative to deleting records on live that + // are deleted on stage. + foreach($this->Changes() as $change) { + /** @var ChangeSetItem $change */ + $change->unlinkDisownedObjects(); + } + $this->State = static::STATE_PUBLISHED; $this->write(); }); + return true; } /** @@ -377,7 +390,11 @@ class ChangeSet extends DataObject { public function getCMSFields() { $fields = new FieldList(new TabSet('Root')); - $fields->addFieldToTab('Root.Main', TextField::create('Name', $this->fieldLabel('Name'))); + if ($this->IsInferred) { + $fields->addFieldToTab('Root.Main', ReadonlyField::create('Name', $this->fieldLabel('Name'))); + } else { + $fields->addFieldToTab('Root.Main', TextField::create('Name', $this->fieldLabel('Name'))); + } if ($this->isInDB()) { $fields->addFieldToTab('Root.Main', ReadonlyField::create('State', $this->fieldLabel('State'))); } diff --git a/ORM/Versioning/ChangeSetItem.php b/ORM/Versioning/ChangeSetItem.php index e965f619b..7e323ce5f 100644 --- a/ORM/Versioning/ChangeSetItem.php +++ b/ORM/Versioning/ChangeSetItem.php @@ -245,6 +245,17 @@ class ChangeSetItem extends DataObject implements Thumbnail { $this->write(); } + /** + * Once this item (and all owned objects) are published, unlink + * all disowned objects + */ + public function unlinkDisownedObjects() { + $object = $this->getObjectInStage(Versioned::DRAFT); + if ($object) { + $object->unlinkDisownedObjects(Versioned::DRAFT, Versioned::LIVE); + } + } + /** Reverts this item, then close it. **/ public function revert() { user_error('Not implemented', E_USER_ERROR); diff --git a/ORM/Versioning/Versioned.php b/ORM/Versioning/Versioned.php index cf00180fa..c551b8e7f 100644 --- a/ORM/Versioning/Versioned.php +++ b/ORM/Versioning/Versioned.php @@ -17,6 +17,7 @@ use SilverStripe\ORM\DB; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataExtension; +use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\ORM\Queries\SQLUpdate; use SilverStripe\Security\Member; @@ -1456,22 +1457,20 @@ class Versioned extends DataExtension implements TemplateGlobalProvider { * @return bool */ public function publishRecursive() { - $owner = $this->owner; - if(!$owner->publishSingle()) { - return false; - } - - // Publish owned objects - foreach ($owner->findOwned(false) as $object) { - /** @var Versioned|DataObject $object */ - $object->publishRecursive(); - } - - // Unlink any objects disowned as a result of this action - // I.e. objects which aren't owned anymore by this record, but are by the old live record - $owner->unlinkDisownedObjects(Versioned::DRAFT, Versioned::LIVE); - - return true; + // Create a new changeset for this item and publish it + $changeset = ChangeSet::create(); + $changeset->IsInferred = true; + $changeset->Name = _t( + 'Versioned.INFERRED_TITLE', + "Generated by publish of '{title}' at {created}", + [ + 'title' => $this->owner->Title, + 'created' => DBDatetime::now()->Nice() + ] + ); + $changeset->write(); + $changeset->addObject($this->owner); + return $changeset->publish(); } /** diff --git a/admin/code/CampaignAdmin.php b/admin/code/CampaignAdmin.php index f0c7355c1..27c2fb013 100644 --- a/admin/code/CampaignAdmin.php +++ b/admin/code/CampaignAdmin.php @@ -268,6 +268,7 @@ JSON; 'Created' => $changeSet->Created, 'LastEdited' => $changeSet->LastEdited, 'State' => $changeSet->State, + 'IsInferred' => $changeSet->IsInferred, 'canEdit' => $changeSet->canEdit(), 'canPublish' => false, '_embedded' => ['items' => []] diff --git a/tests/model/ChangeSetItemTest.php b/tests/model/ChangeSetItemTest.php index 35c13be6c..b9b421be6 100644 --- a/tests/model/ChangeSetItemTest.php +++ b/tests/model/ChangeSetItemTest.php @@ -3,15 +3,18 @@ use SilverStripe\ORM\DataObject; use SilverStripe\ORM\Versioning\ChangeSetItem; use SilverStripe\Dev\SapphireTest; +use SilverStripe\ORM\Versioning\Versioned; - +/** + * @mixin Versioned + */ class ChangeSetItemTest_Versioned extends DataObject { private static $db = [ 'Foo' => 'Int' ]; private static $extensions = [ - "SilverStripe\\ORM\\Versioning\\Versioned" + Versioned::class ]; function canEdit($member = null) { return true; } @@ -24,10 +27,11 @@ class ChangeSetItemTest_Versioned extends DataObject { class ChangeSetItemTest extends SapphireTest { protected $extraDataObjects = [ - 'ChangeSetItemTest_Versioned' + ChangeSetItemTest_Versioned::class ]; - function testChangeType() { + public function testChangeType() { + $this->logInWithPermission('ADMIN'); $object = new ChangeSetItemTest_Versioned(['Foo' => 1]); $object->write(); @@ -79,7 +83,8 @@ class ChangeSetItemTest extends SapphireTest { ); } - function testGetForObject() { + public function testGetForObject() { + $this->logInWithPermission('ADMIN'); $object = new ChangeSetItemTest_Versioned(['Foo' => 1]); $object->write(); diff --git a/tests/model/ChangeSetTest.php b/tests/model/ChangeSetTest.php index f53c6d459..ad1b5f3b7 100644 --- a/tests/model/ChangeSetTest.php +++ b/tests/model/ChangeSetTest.php @@ -10,8 +10,6 @@ use SilverStripe\Dev\SapphireTest; use SilverStripe\Control\Session; - - /** * Provides a set of targettable permissions for tested models * @@ -59,7 +57,7 @@ class ChangeSetTest_Base extends DataObject implements TestOnly { ]; private static $has_many = [ - 'Mids' => 'ChangeSetTest_Mid', + 'Mids' => ChangeSetTest_Mid::class, ]; private static $owns = [ @@ -82,8 +80,8 @@ class ChangeSetTest_Mid extends DataObject implements TestOnly { ]; private static $has_one = [ - 'Base' => 'ChangeSetTest_Base', - 'End' => 'ChangeSetTest_End', + 'Base' => ChangeSetTest_Base::class, + 'End' => ChangeSetTest_End::class, ]; private static $owns = [ @@ -128,10 +126,10 @@ class ChangeSetTest extends SapphireTest { protected static $fixture_file = 'ChangeSetTest.yml'; protected $extraDataObjects = [ - 'ChangeSetTest_Base', - 'ChangeSetTest_Mid', - 'ChangeSetTest_End', - 'ChangeSetTest_EndChild', + ChangeSetTest_Base::class, + ChangeSetTest_Mid::class, + ChangeSetTest_End::class, + ChangeSetTest_EndChild::class, ]; /** @@ -191,8 +189,8 @@ class ChangeSetTest extends SapphireTest { $cs = new ChangeSet(); $cs->write(); - $cs->addObject($this->objFromFixture('ChangeSetTest_End', 'end1')); - $cs->addObject($this->objFromFixture('ChangeSetTest_EndChild', 'endchild1')); + $cs->addObject($this->objFromFixture(ChangeSetTest_End::class, 'end1')); + $cs->addObject($this->objFromFixture(ChangeSetTest_EndChild::class, 'endchild1')); $this->assertChangeSetLooksLike($cs, [ 'ChangeSetTest_End.end1' => ChangeSetItem::EXPLICITLY, @@ -206,7 +204,7 @@ class ChangeSetTest extends SapphireTest { $cs = new ChangeSet(); $cs->write(); - $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); $cs->addObject($base); $cs->sync(); @@ -226,7 +224,7 @@ class ChangeSetTest extends SapphireTest { $cs = new ChangeSet(); $cs->write(); - $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); $cs->addObject($base); $cs->sync(); @@ -235,7 +233,7 @@ class ChangeSetTest extends SapphireTest { 'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY ]); - $end = $this->objFromFixture('ChangeSetTest_End', 'end1'); + $end = $this->objFromFixture(ChangeSetTest_End::class, 'end1'); $end->Baz = 3; $end->write(); @@ -257,7 +255,7 @@ class ChangeSetTest extends SapphireTest { $this->assertDOSEquals([ [ 'Added' => ChangeSetItem::EXPLICITLY, - 'ObjectClass' => 'ChangeSetTest_Base', + 'ObjectClass' => ChangeSetTest_Base::class, 'ObjectID' => $base->ID, 'ChangeSetID' => $cs->ID ] @@ -273,7 +271,7 @@ class ChangeSetTest extends SapphireTest { $cs = new ChangeSet(); $cs->write(); - $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); $cs->addObject($base); $cs->sync(); @@ -282,7 +280,7 @@ class ChangeSetTest extends SapphireTest { ]); $this->assertTrue($cs->isSynced()); - $end = $this->objFromFixture('ChangeSetTest_End', 'end1'); + $end = $this->objFromFixture(ChangeSetTest_End::class, 'end1'); $end->Baz = 3; $end->write(); $this->assertFalse($cs->isSynced()); @@ -301,7 +299,7 @@ class ChangeSetTest extends SapphireTest { $this->logInWithPermission('ADMIN'); $changeSet = new ChangeSet(); $changeSet->write(); - $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); $changeSet->addObject($base); $changeSet->sync(); $this->assertEquals(5, $changeSet->Changes()->count()); @@ -333,7 +331,7 @@ class ChangeSetTest extends SapphireTest { $this->logInWithPermission('ADMIN'); $changeSet = new ChangeSet(); $changeSet->write(); - $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); $changeSet->addObject($base); $changeSet->sync(); $this->assertEquals(5, $changeSet->Changes()->count()); @@ -362,7 +360,7 @@ class ChangeSetTest extends SapphireTest { $this->logInWithPermission('ADMIN'); $changeSet = new ChangeSet(); $changeSet->write(); - $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); $changeSet->addObject($base); $changeSet->sync(); $this->assertEquals(5, $changeSet->Changes()->count()); @@ -381,7 +379,7 @@ class ChangeSetTest extends SapphireTest { $this->logInWithPermission('ADMIN'); $changeSet = new ChangeSet(); $changeSet->write(); - $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); $changeSet->addObject($base); $changeSet->sync(); $this->assertEquals(5, $changeSet->Changes()->count()); @@ -398,10 +396,10 @@ class ChangeSetTest extends SapphireTest { public function testPublish() { $this->publishAllFixtures(); - $base = $this->objFromFixture('ChangeSetTest_Base', 'base'); + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); $baseID = $base->ID; $baseBefore = $base->Version; - $end1 = $this->objFromFixture('ChangeSetTest_End', 'end1'); + $end1 = $this->objFromFixture(ChangeSetTest_End::class, 'end1'); $end1ID = $end1->ID; $end1Before = $end1->Version; @@ -436,7 +434,7 @@ class ChangeSetTest extends SapphireTest { // Check each item has the correct before/after version applied $baseChange = $changeset->Changes()->filter([ - 'ObjectClass' => 'ChangeSetTest_Base', + 'ObjectClass' => ChangeSetTest_Base::class, 'ObjectID' => $baseID, ])->first(); $this->assertEquals((int)$baseBefore, (int)$baseChange->VersionBefore); @@ -444,29 +442,29 @@ class ChangeSetTest extends SapphireTest { $this->assertEquals((int)$baseChange->VersionBefore + 1, (int)$baseChange->VersionAfter); $this->assertEquals( (int)$baseChange->VersionAfter, - (int)Versioned::get_versionnumber_by_stage('ChangeSetTest_Base', Versioned::LIVE, $baseID) + (int)Versioned::get_versionnumber_by_stage(ChangeSetTest_Base::class, Versioned::LIVE, $baseID) ); $end1Change = $changeset->Changes()->filter([ - 'ObjectClass' => 'ChangeSetTest_End', + 'ObjectClass' => ChangeSetTest_End::class, 'ObjectID' => $end1ID, ])->first(); $this->assertEquals((int)$end1Before, (int)$end1Change->VersionBefore); $this->assertEquals(0, (int)$end1Change->VersionAfter); $this->assertEquals( 0, - (int)Versioned::get_versionnumber_by_stage('ChangeSetTest_End', Versioned::LIVE, $end1ID) + (int)Versioned::get_versionnumber_by_stage(ChangeSetTest_End::class, Versioned::LIVE, $end1ID) ); $midNewChange = $changeset->Changes()->filter([ - 'ObjectClass' => 'ChangeSetTest_Mid', + 'ObjectClass' => ChangeSetTest_Mid::class, 'ObjectID' => $midNewID, ])->first(); $this->assertEquals(0, (int)$midNewChange->VersionBefore); $this->assertEquals((int)$midNewAfter, (int)$midNewChange->VersionAfter); $this->assertEquals( (int)$midNewAfter, - (int)Versioned::get_versionnumber_by_stage('ChangeSetTest_Mid', Versioned::LIVE, $midNewID) + (int)Versioned::get_versionnumber_by_stage(ChangeSetTest_Mid::class, Versioned::LIVE, $midNewID) ); // Test trying to re-publish is blocked @@ -477,4 +475,47 @@ class ChangeSetTest extends SapphireTest { $changeset->publish(); } + /** + * Ensure that related objects are disassociated on live + */ + public function testUnlinkDisassociated() { + $this->publishAllFixtures(); + /** @var ChangeSetTest_Base $base */ + $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); + /** @var ChangeSetTest_Mid $mid1 $mid2 */ + $mid1 = $this->objFromFixture(ChangeSetTest_Mid::class, 'mid1'); + $mid2 = $this->objFromFixture(ChangeSetTest_Mid::class, 'mid2'); + + // Remove mid1 from stage + $this->assertEquals($base->ID, $mid1->BaseID); + $this->assertEquals($base->ID, $mid2->BaseID); + $mid1->deleteFromStage(Versioned::DRAFT); + + // Publishing recursively should unlinkd this object + $changeset = new ChangeSet(); + $changeset->write(); + $changeset->addObject($base); + + // Assert changeset only contains root object + $this->assertChangeSetLooksLike($changeset, [ + 'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY + ]); + + $changeset->publish(); + + // mid1 on live exists, but has BaseID set to zero + $mid1Live = Versioned::get_by_stage(ChangeSetTest_Mid::class, Versioned::LIVE) + ->byID($mid1->ID); + $this->assertNotNull($mid1Live); + $this->assertEquals($mid1->ID, $mid1Live->ID); + $this->assertEquals(0, $mid1Live->BaseID); + + // mid2 on live exists and retains BaseID + $mid2Live = Versioned::get_by_stage(ChangeSetTest_Mid::class, Versioned::LIVE) + ->byID($mid2->ID); + $this->assertNotNull($mid2Live); + $this->assertEquals($mid2->ID, $mid2Live->ID); + $this->assertEquals($base->ID, $mid2Live->BaseID); + } + } diff --git a/tests/model/VersionedOwnershipTest.php b/tests/model/VersionedOwnershipTest.php index 17aef21d7..664373437 100644 --- a/tests/model/VersionedOwnershipTest.php +++ b/tests/model/VersionedOwnershipTest.php @@ -1,12 +1,15 @@ 'Related Many 1'], - ['Title' => 'Related Many 2'], // Will be deleted + ['Title' => 'Related Many 2'], // Will be unlinked (but not deleted) // `Related Many 3` isn't published ]; $newBanners = [ @@ -284,6 +287,8 @@ class VersionedOwnershipTest extends SapphireTest { $this->assertDOSEquals($oldLiveBanners, $parentLive->Banners()); // On publishing of owner, all children should now be updated + $now = DBDatetime::now(); + DBDatetime::set_mock_now($now); // Lock 'now' to predictable time $parent->publishRecursive(); // Now check each object has the correct state @@ -299,6 +304,46 @@ class VersionedOwnershipTest extends SapphireTest { $banner2Live = Versioned::get_by_stage('VersionedOwnershipTest_RelatedMany', Versioned::LIVE) ->byID($banner2ID); $this->assertEmpty($banner2Live->PageID); + + // Test that a changeset was created + /** @var ChangeSet $changeset */ + $changeset = ChangeSet::get()->sort('"ChangeSet"."ID" DESC')->first(); + $this->assertNotEmpty($changeset); + + // Test that this changeset is inferred + $this->assertTrue((bool)$changeset->IsInferred); + $this->assertEquals( + "Generated by publish of 'Subclass 1' at ".$now->Nice(), + $changeset->getTitle() + ); + + // Test that this changeset contains all items + $this->assertDOSContains( + [ + [ + 'ObjectID' => $parent->ID, + 'ObjectClass' => $parent->baseClass(), + 'Added' => ChangeSetItem::EXPLICITLY + ], + [ + 'ObjectID' => $banner1->ID, + 'ObjectClass' => $banner1->baseClass(), + 'Added' => ChangeSetItem::IMPLICITLY + ], + [ + 'ObjectID' => $banner4->ID, + 'ObjectClass' => $banner4->baseClass(), + 'Added' => ChangeSetItem::IMPLICITLY + ] + ], + $changeset->Changes() + ); + + // Objects that are unlinked should not need to be a part of the changeset + $this->assertNotDOSContains( + [[ 'ObjectID' => $banner2ID, 'ObjectClass' => $banner2->baseClass() ]], + $changeset->Changes() + ); } /** @@ -595,7 +640,7 @@ class VersionedOwnershipTest extends SapphireTest { */ class VersionedOwnershipTest_Object extends DataObject implements TestOnly { private static $extensions = array( - 'SilverStripe\\ORM\\Versioning\\Versioned', + Versioned::class, ); private static $db = array( @@ -637,7 +682,7 @@ class VersionedOwnershipTest_Subclass extends VersionedOwnershipTest_Object impl */ class VersionedOwnershipTest_Related extends DataObject implements TestOnly { private static $extensions = array( - 'SilverStripe\\ORM\\Versioning\\Versioned', + Versioned::class, ); private static $db = array( @@ -669,7 +714,7 @@ class VersionedOwnershipTest_Related extends DataObject implements TestOnly { */ class VersionedOwnershipTest_RelatedMany extends DataObject implements TestOnly { private static $extensions = array( - 'SilverStripe\\ORM\\Versioning\\Versioned', + Versioned::class, ); private static $db = array( @@ -691,7 +736,7 @@ class VersionedOwnershipTest_RelatedMany extends DataObject implements TestOnly class VersionedOwnershipTest_Attachment extends DataObject implements TestOnly { private static $extensions = array( - 'SilverStripe\\ORM\\Versioning\\Versioned', + Versioned::class, ); private static $db = array( @@ -714,7 +759,7 @@ class VersionedOwnershipTest_Attachment extends DataObject implements TestOnly { */ class VersionedOwnershipTest_Page extends DataObject implements TestOnly { private static $extensions = array( - 'SilverStripe\\ORM\\Versioning\\Versioned', + Versioned::class, ); private static $db = array( @@ -749,7 +794,7 @@ class VersionedOwnershipTest_Page extends DataObject implements TestOnly { */ class VersionedOwnershipTest_Banner extends DataObject implements TestOnly { private static $extensions = array( - 'SilverStripe\\ORM\\Versioning\\Versioned', + Versioned::class, ); private static $db = array( @@ -773,7 +818,7 @@ class VersionedOwnershipTest_Banner extends DataObject implements TestOnly { */ class VersionedOwnershipTest_CustomRelation extends DataObject implements TestOnly { private static $extensions = array( - 'SilverStripe\\ORM\\Versioning\\Versioned', + Versioned::class, ); private static $db = array( @@ -803,7 +848,7 @@ class VersionedOwnershipTest_CustomRelation extends DataObject implements TestOn */ class VersionedOwnershipTest_Image extends DataObject implements TestOnly { private static $extensions = array( - 'SilverStripe\\ORM\\Versioning\\Versioned', + Versioned::class, ); private static $db = array(