diff --git a/.travis.yml b/.travis.yml
index f217d0676..b685dbbec 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -41,7 +41,7 @@ before_script:
- composer install --prefer-dist
- "if [ \"$DB\" = \"PGSQL\" ]; then composer require silverstripe/postgresql:2.0.x-dev --prefer-dist; fi"
- "if [ \"$DB\" = \"SQLITE\" ]; then composer require silverstripe/sqlite3:2.0.x-dev --prefer-dist; fi"
- - composer require silverstripe/config:1.0.x-dev silverstripe/admin:1.0.x-dev silverstripe/assets:1.0.x-dev --prefer-dist
+ - composer require silverstripe/config:1.0.x-dev silverstripe/admin:1.0.x-dev silverstripe/assets:1.0.x-dev silverstripe/versioned:1.0.x-dev --prefer-dist
- "if [ \"$CMS_TEST\" = \"1\" ]; then composer require silverstripe/cms:4.0.x-dev silverstripe/siteconfig:4.0.x-dev silverstripe/reports:4.0.x-dev --prefer-dist; fi"
- "if [ \"$CMS_TEST\" = \"1\" ]; then php ./cms/tests/bootstrap/mysite.php; fi"
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then sh -e /etc/init.d/xvfb start; sleep 3; fi"
diff --git a/.upgrade.yml b/.upgrade.yml
index 678f3e546..229f3ab0d 100644
--- a/.upgrade.yml
+++ b/.upgrade.yml
@@ -115,14 +115,6 @@ mappings:
SilverStripe\Model\FieldType\DBVarchar: SilverStripe\ORM\FieldType\DBVarchar
SilverStripe\Model\FieldType\DBYear: SilverStripe\ORM\FieldType\DBYear
SilverStripe\Model\FieldType\SS_Datetime: SilverStripe\ORM\FieldType\DBDatetime
- ChangeSet: SilverStripe\ORM\Versioning\ChangeSet
- ChangeSetItem: SilverStripe\ORM\Versioning\ChangeSetItem
- DataDifferencer: SilverStripe\ORM\Versioning\DataDifferencer
- VersionableExtension: SilverStripe\ORM\Versioning\VersionableExtension
- Versioned: SilverStripe\ORM\Versioning\Versioned
- Versioned_Version: SilverStripe\ORM\Versioning\Versioned_Version
- VersionedGridFieldDetailForm: SilverStripe\ORM\Versioning\VersionedGridFieldDetailForm
- VersionedGridFieldItemRequest: SilverStripe\ORM\Versioning\VersionedGridFieldItemRequest
Hierarchy: SilverStripe\ORM\Hierarchy\Hierarchy
Authenticator: SilverStripe\Security\Authenticator
BasicAuth: SilverStripe\Security\BasicAuth
@@ -288,7 +280,7 @@ mappings:
RequestHandler: SilverStripe\Control\RequestHandler
RequestProcessor: SilverStripe\Control\RequestProcessor
Session: SilverStripe\Control\Session
- VersionedRequestFilter: SilverStripe\Control\VersionedRequestFilter
+ VersionedRequestFilter: SilverStripe\Versioned\VersionedRequestFilter
Email: SilverStripe\Control\Email\Email
Mailer: SilverStripe\Control\Email\Mailer
RSSFeed: SilverStripe\Control\RSS\RSSFeed
@@ -680,22 +672,22 @@ mappings:
ArrayLibTest: SilverStripe\ORM\Tests\ArrayLibTest
ArrayListTest: SilverStripe\ORM\Tests\ArrayListTest
ArrayListTest_Object: SilverStripe\ORM\Tests\ArrayListTest\TestObject
- ChangeSetItemTest_Versioned: SilverStripe\ORM\Tests\ChangeSetItemTest\VersionedObject
- ChangeSetItemTest: SilverStripe\ORM\Tests\ChangeSetItemTest
- ChangeSetTest_Permissions: SilverStripe\ORM\Tests\ChangeSetTest\Permissions
- ChangeSetTest_Base: SilverStripe\ORM\Tests\ChangeSetTest\BaseObject
- ChangeSetTest_Mid: SilverStripe\ORM\Tests\ChangeSetTest\MidObject
- ChangeSetTest_End: SilverStripe\ORM\Tests\ChangeSetTest\EndObject
- ChangeSetTest_EndChild: SilverStripe\ORM\Tests\ChangeSetTest\EndObjectChild
- ChangeSetTest: SilverStripe\ORM\Tests\ChangeSetTest
+ ChangeSetItemTest_Versioned: SilverStripe\Versioned\Tests\ChangeSetItemTest\VersionedObject
+ ChangeSetItemTest: SilverStripe\Versioned\Tests\ChangeSetItemTest
+ ChangeSetTest_Permissions: SilverStripe\Versioned\Tests\ChangeSetTest\Permissions
+ ChangeSetTest_Base: SilverStripe\Versioned\Tests\ChangeSetTest\BaseObject
+ ChangeSetTest_Mid: SilverStripe\Versioned\Tests\ChangeSetTest\MidObject
+ ChangeSetTest_End: SilverStripe\Versioned\Tests\ChangeSetTest\EndObject
+ ChangeSetTest_EndChild: SilverStripe\Versioned\Tests\ChangeSetTest\EndObjectChild
+ ChangeSetTest: SilverStripe\Versioned\Tests\ChangeSetTest
ComponentSetTest: SilverStripe\ORM\Tests\ComponentSetTest
ComponentSetTest_Player: SilverStripe\ORM\Tests\ComponentSetTest\Player
ComponentSetTest_Team: SilverStripe\ORM\Tests\ComponentSetTest\Team
DatabaseTest: SilverStripe\ORM\Tests\DatabaseTest
DatabaseTest_MyObject: SilverStripe\ORM\Tests\DatabaseTest\MyObject
- DataDifferencerTest: SilverStripe\ORM\Tests\DataDifferencerTest
- DataDifferencerTest_Object: SilverStripe\ORM\Tests\DataDifferencerTest\TestObject
- DataDifferencerTest_HasOneRelationObject: SilverStripe\ORM\Tests\DataDifferencerTest\HasOneRelationObject
+ DataDifferencerTest: SilverStripe\Versioned\Tests\DataDifferencerTest
+ DataDifferencerTest_Object: SilverStripe\Versioned\Tests\DataDifferencerTest\TestObject
+ DataDifferencerTest_HasOneRelationObject: SilverStripe\Versioned\Tests\DataDifferencerTest\HasOneRelationObject
DataExtensionTest: SilverStripe\ORM\Tests\DataExtensionTest
DataExtensionTest_Member: SilverStripe\ORM\Tests\DataExtensionTest\TestMember
DataExtensionTest_Player: SilverStripe\ORM\Tests\DataExtensionTest\Player
@@ -718,8 +710,8 @@ mappings:
DataObjectDuplicateTestClass2: SilverStripe\ORM\Tests\DataObjectDuplicationTest\Class2
DataObjectDuplicateTestClass3: SilverStripe\ORM\Tests\DataObjectDuplicationTest\Class3
DataObjectLazyLoadingTest: SilverStripe\ORM\Tests\DataObjectLazyLoadingTest
- VersionedLazy_DataObject: SilverStripe\ORM\Tests\DataObjectLazyLoadingTest\VersionedObject
- VersionedLazySub_DataObject: SilverStripe\ORM\Tests\DataObjectLazyLoadingTest\VersionedSubObject
+ VersionedLazy_DataObject: SilverStripe\Versioned\Tests\VersionedDataObjectLazyLoadingTest\VersionedObject
+ VersionedLazySub_DataObject: SilverStripe\Versioned\Tests\VersionedDataObjectLazyLoadingTest\VersionedSubObject
DataObjectSchemaGenerationTest: SilverStripe\ORM\Tests\DataObjectSchemaGenerationTest
DataObjectSchemaGenerationTest_DO: SilverStripe\ORM\Tests\DataObjectSchemaGenerationTest\TestObject
DataObjectSchemaGenerationTest_IndexDO: SilverStripe\ORM\Tests\DataObjectSchemaGenerationTest\TestIndexObject
@@ -809,9 +801,9 @@ mappings:
ManyManyThroughListTest_Object: SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObject
ManyManyThroughListTest_JoinObject: SilverStripe\ORM\Tests\ManyManyThroughListTest\JoinObject
ManyManyThroughListTest_Item: SilverStripe\ORM\Tests\ManyManyThroughListTest\Item
- ManyManyThroughListTest_VersionedObject: SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedObject
- ManyManyThroughListTest_VersionedJoinObject: SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedJoinObject
- ManyManyThroughListTest_VersionedItem: SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedItem
+ ManyManyThroughListTest_VersionedObject: SilverStripe\Versioned\Tests\VersionedManyManyThroughListTest\VersionedObject
+ ManyManyThroughListTest_VersionedJoinObject: SilverStripe\Versioned\Tests\VersionedManyManyThroughListTest\VersionedJoinObject
+ ManyManyThroughListTest_VersionedItem: SilverStripe\Versioned\Tests\VersionedManyManyThroughListTest\VersionedItem
MapTest: SilverStripe\ORM\Tests\MapTest
MySQLDatabaseTest: SilverStripe\ORM\Tests\MySQLDatabaseTest
MySQLDatabaseTest_Data: SilverStripe\ORM\Tests\MySQLDatabaseTest\Data
@@ -834,31 +826,31 @@ mappings:
UnsavedRelationListTest_DataObject: SilverStripe\ORM\Tests\UnsavedRelationListTest\TestObject
URLSegmentFilterTest: SilverStripe\ORM\Tests\URLSegmentFilterTest
ValidationExceptionTest: SilverStripe\ORM\Tests\ValidationExceptionTest
- VersionableExtensionsTest: SilverStripe\ORM\Tests\VersionableExtensionsTest
- VersionableExtensionsTest_DataObject: SilverStripe\ORM\Tests\VersionableExtensionsTest\TestObject
- VersionableExtensionsTest_Extension: SilverStripe\ORM\Tests\VersionableExtensionsTest\TestExtension
- VersionedOwnershipTest: SilverStripe\ORM\Tests\VersionedOwnershipTest
- VersionedOwnershipTest_Object: SilverStripe\ORM\Tests\VersionedOwnershipTest\TestObject
- VersionedOwnershipTest_Subclass: SilverStripe\ORM\Tests\VersionedOwnershipTest\Subclass
- VersionedOwnershipTest_Related: SilverStripe\ORM\Tests\VersionedOwnershipTest\Related
- VersionedOwnershipTest_RelatedMany: SilverStripe\ORM\Tests\VersionedOwnershipTest\RelatedMany
- VersionedOwnershipTest_Attachment: SilverStripe\ORM\Tests\VersionedOwnershipTest\Attachment
- VersionedOwnershipTest_Page: SilverStripe\ORM\Tests\VersionedOwnershipTest\TestPage
- VersionedOwnershipTest_Banner: SilverStripe\ORM\Tests\VersionedOwnershipTest\Banner
- VersionedOwnershipTest_CustomRelation: SilverStripe\ORM\Tests\VersionedOwnershipTest\CustomRelation
- VersionedOwnershipTest_Image: SilverStripe\ORM\Tests\VersionedOwnershipTest\Image
- VersionedTest: SilverStripe\ORM\Tests\VersionedTest
- VersionedTest_DataObject: SilverStripe\ORM\Tests\VersionedTest\TestObject
- VersionedTest_WithIndexes: SilverStripe\ORM\Tests\VersionedTest\WithIndexes
- VersionedTest_RelatedWithoutVersion: SilverStripe\ORM\Tests\VersionedTest\RelatedWithoutversion
- VersionedTest_Subclass: SilverStripe\ORM\Tests\VersionedTest\Subclass
- VersionedTest_AnotherSubclass: SilverStripe\ORM\Tests\VersionedTest\AnotherSubclass
- VersionedTest_UnversionedWithField: SilverStripe\ORM\Tests\VersionedTest\UnversionedWithField
- VersionedTest_SingleStage: SilverStripe\ORM\Tests\VersionedTest\SingleStage
- VersionedTest_PublicStage: SilverStripe\ORM\Tests\VersionedTest\PublicStage
- VersionedTest_PublicViaExtension: SilverStripe\ORM\Tests\VersionedTest\PublicViaExtension
- VersionedTest_PublicExtension: SilverStripe\ORM\Tests\VersionedTest\PublicExtension
- VersionedTest_CustomTable: SilverStripe\ORM\Tests\VersionedTest\CustomTable
+ VersionableExtensionsTest: SilverStripe\Versioned\Tests\VersionableExtensionsTest
+ VersionableExtensionsTest_DataObject: SilverStripe\Versioned\Tests\VersionableExtensionsTest\TestObject
+ VersionableExtensionsTest_Extension: SilverStripe\Versioned\Tests\VersionableExtensionsTest\TestExtension
+ VersionedOwnershipTest: SilverStripe\Versioned\Tests\VersionedOwnershipTest
+ VersionedOwnershipTest_Object: SilverStripe\Versioned\Tests\VersionedOwnershipTest\TestObject
+ VersionedOwnershipTest_Subclass: SilverStripe\Versioned\Tests\VersionedOwnershipTest\Subclass
+ VersionedOwnershipTest_Related: SilverStripe\Versioned\Tests\VersionedOwnershipTest\Related
+ VersionedOwnershipTest_RelatedMany: SilverStripe\Versioned\Tests\VersionedOwnershipTest\RelatedMany
+ VersionedOwnershipTest_Attachment: SilverStripe\Versioned\Tests\VersionedOwnershipTest\Attachment
+ VersionedOwnershipTest_Page: SilverStripe\Versioned\Tests\VersionedOwnershipTest\TestPage
+ VersionedOwnershipTest_Banner: SilverStripe\Versioned\Tests\VersionedOwnershipTest\Banner
+ VersionedOwnershipTest_CustomRelation: SilverStripe\Versioned\Tests\VersionedOwnershipTest\CustomRelation
+ VersionedOwnershipTest_Image: SilverStripe\Versioned\Tests\VersionedOwnershipTest\Image
+ VersionedTest: SilverStripe\Versioned\Tests\VersionedTest
+ VersionedTest_DataObject: SilverStripe\Versioned\Tests\VersionedTest\TestObject
+ VersionedTest_WithIndexes: SilverStripe\Versioned\Tests\VersionedTest\WithIndexes
+ VersionedTest_RelatedWithoutVersion: SilverStripe\Versioned\Tests\VersionedTest\RelatedWithoutversion
+ VersionedTest_Subclass: SilverStripe\Versioned\Tests\VersionedTest\Subclass
+ VersionedTest_AnotherSubclass: SilverStripe\Versioned\Tests\VersionedTest\AnotherSubclass
+ VersionedTest_UnversionedWithField: SilverStripe\Versioned\Tests\VersionedTest\UnversionedWithField
+ VersionedTest_SingleStage: SilverStripe\Versioned\Tests\VersionedTest\SingleStage
+ VersionedTest_PublicStage: SilverStripe\Versioned\Tests\VersionedTest\PublicStage
+ VersionedTest_PublicViaExtension: SilverStripe\Versioned\Tests\VersionedTest\PublicViaExtension
+ VersionedTest_PublicExtension: SilverStripe\Versioned\Tests\VersionedTest\PublicExtension
+ VersionedTest_CustomTable: SilverStripe\Versioned\Tests\VersionedTest\CustomTable
BasicAuthTest: SilverStripe\Security\Tests\BasicAuthTest
BasicAuthTest_ControllerSecuredWithPermission: SilverStripe\Security\Tests\BasicAuthTest\ControllerSecuredWithPermission
BasicAuthTest_ControllerSecuredWithoutPermission: SilverStripe\Security\Tests\BasicAuthTest\ControllerSecuredWithoutPermission
diff --git a/_config/requestprocessors.yml b/_config/requestprocessors.yml
index aef82c9ba..66846879b 100644
--- a/_config/requestprocessors.yml
+++ b/_config/requestprocessors.yml
@@ -4,10 +4,8 @@ Name: requestprocessors
SilverStripe\Core\Injector\Injector:
FlushRequestFilter:
class: SilverStripe\Control\FlushRequestFilter
- VersionedRequestFilter:
- class: SilverStripe\Control\VersionedRequestFilter
SilverStripe\Control\RequestProcessor:
properties:
filters:
- '%$FlushRequestFilter'
- - '%$VersionedRequestFilter'
+
diff --git a/_config/versioning.yml b/_config/versioning.yml
deleted file mode 100644
index 4726ffe79..000000000
--- a/_config/versioning.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-Name: versioning
----
-SilverStripe\Forms\GridField\GridFieldDetailForm:
- extensions:
- - SilverStripe\ORM\Versioning\VersionedGridFieldDetailForm
diff --git a/composer.json b/composer.json
index 67c6c1404..a2255b5f6 100644
--- a/composer.json
+++ b/composer.json
@@ -33,6 +33,7 @@
},
"require-dev": {
"phpunit/PHPUnit": "^5.7",
+ "silverstripe/versioned": "^1.0@dev",
"silverstripe/behat-extension": "^2.1.0",
"silverstripe/serve": "dev-master",
"silverstripe/testsession": "^2.0.0-alpha3",
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 5c9a0ec3a..1e039c18b 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -20,7 +20,9 @@
tests/php
+ silverstripe-admin/tests/php
silverstripe-assets/tests/php
+ versioned/tests/php
diff --git a/src/Control/Director.php b/src/Control/Director.php
index 8e995e7d7..bd832d008 100644
--- a/src/Control/Director.php
+++ b/src/Control/Director.php
@@ -6,12 +6,11 @@ use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injector;
-use SilverStripe\Dev\Debug;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\DataModel;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
use SilverStripe\View\Requirements;
use SilverStripe\View\Requirements_Backend;
use SilverStripe\View\TemplateGlobalProvider;
@@ -280,7 +279,9 @@ class Director implements TemplateGlobalProvider
// These are needed so that calling Director::test() does not muck with whoever is calling it.
// Really, it's some inappropriate coupling and should be resolved by making less use of statics.
- $oldReadingMode = Versioned::get_reading_mode();
+ if (class_exists(Versioned::class)) {
+ $oldReadingMode = Versioned::get_reading_mode();
+ }
$getVars = array();
if (!$httpMethod) {
@@ -330,7 +331,9 @@ class Director implements TemplateGlobalProvider
// These are needed so that calling Director::test() does not muck with whoever is calling it.
// Really, it's some inappropriate coupling and should be resolved by making less use of statics
- Versioned::set_reading_mode($oldReadingMode);
+ if (class_exists(Versioned::class)) {
+ Versioned::set_reading_mode($oldReadingMode);
+ }
Injector::unnest(); // Restore old CookieJar, etc
Config::unnest();
diff --git a/src/Control/VersionedRequestFilter.php b/src/Control/VersionedRequestFilter.php
deleted file mode 100644
index ff22c37c4..000000000
--- a/src/Control/VersionedRequestFilter.php
+++ /dev/null
@@ -1,57 +0,0 @@
-setSession($session);
- $dummyController->setRequest($request);
- $dummyController->pushCurrent();
-
- // Block non-authenticated users from setting the stage mode
- if (!Versioned::can_choose_site_stage($request)) {
- $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. '.
- 'Click here to go back to the published site.'
- ),
- Convert::raw2xml(Controller::join_links(Director::baseURL(), $request->getURL(), "?stage=Live"))
- );
-
- // Force output since RequestFilter::preRequest doesn't support response overriding
- $response = Security::permissionFailure($dummyController, $permissionMessage);
- $session->inst_save();
- $dummyController->popCurrent();
- // Prevent output in testing
- if (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()) {
- throw new HTTPResponse_Exception($response);
- }
- $response->output();
- die;
- }
-
- Versioned::choose_site_stage();
- $dummyController->popCurrent();
- return true;
- }
-
- public function postRequest(HTTPRequest $request, HTTPResponse $response, DataModel $model)
- {
- return true;
- }
-}
diff --git a/src/Dev/DevelopmentAdmin.php b/src/Dev/DevelopmentAdmin.php
index 9b8bd28e6..c946f8494 100644
--- a/src/Dev/DevelopmentAdmin.php
+++ b/src/Dev/DevelopmentAdmin.php
@@ -8,7 +8,7 @@ use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\Controller;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
use SilverStripe\ORM\DatabaseAdmin;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
@@ -67,7 +67,9 @@ class DevelopmentAdmin extends Controller
// Backwards compat: Default to "draft" stage, which is important
// for tasks like dev/build which call DataObject->requireDefaultRecords(),
// but also for other administrative tasks which have assumptions about the default stage.
- Versioned::set_stage(Versioned::DRAFT);
+ if (class_exists(Versioned::class)) {
+ Versioned::set_stage(Versioned::DRAFT);
+ }
}
public function index()
diff --git a/src/Dev/SapphireTest.php b/src/Dev/SapphireTest.php
index f22dda13b..e3f210aea 100644
--- a/src/Dev/SapphireTest.php
+++ b/src/Dev/SapphireTest.php
@@ -23,7 +23,7 @@ use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Resettable;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\SS_List;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataModel;
use SilverStripe\ORM\FieldType\DBDatetime;
@@ -235,7 +235,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase
Injector::nest();
$this->originalEnv = Director::get_environment_type();
- $this->originalReadingMode = Versioned::get_reading_mode();
+ if (class_exists(Versioned::class)) {
+ $this->originalReadingMode = Versioned::get_reading_mode();
+ }
// We cannot run the tests on this abstract class.
if (get_class($this) == __CLASS__) {
@@ -258,7 +260,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
$this->originalRequirements = Requirements::backend();
Member::set_password_validator(null);
Cookie::config()->update('report_errors', false);
- if (class_exists('SilverStripe\\CMS\\Controllers\\RootURLController')) {
+ if (class_exists(RootURLController::class)) {
RootURLController::reset();
}
@@ -597,7 +599,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase
}
Director::set_environment_type($this->originalEnv);
- Versioned::set_reading_mode($this->originalReadingMode);
+ if (class_exists(Versioned::class)) {
+ Versioned::set_reading_mode($this->originalReadingMode);
+ }
//unnest injector / config now that tests are over
Injector::unnest();
diff --git a/src/ORM/DataObjectSchema.php b/src/ORM/DataObjectSchema.php
index b7d8132ed..547fce44e 100644
--- a/src/ORM/DataObjectSchema.php
+++ b/src/ORM/DataObjectSchema.php
@@ -574,6 +574,14 @@ class DataObjectSchema
list($childClass, $relationName) = explode('.', $specification, 2);
}
+ // Check child class exists
+ if (!class_exists($childClass)) {
+ throw new LogicException(
+ "belongs_many_many relation {$parentClass}.{$component} points to "
+ . "{$childClass} which does not exist"
+ );
+ }
+
// We need to find the inverse component name, if not explicitly given
if (!$relationName) {
$relationName = $this->getManyManyInverseRelationship($childClass, $parentClass);
diff --git a/src/ORM/DatabaseAdmin.php b/src/ORM/DatabaseAdmin.php
index 7787eafce..e81ef6250 100644
--- a/src/ORM/DatabaseAdmin.php
+++ b/src/ORM/DatabaseAdmin.php
@@ -9,7 +9,7 @@ use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Dev\Deprecation;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
use SilverStripe\Security\Security;
use SilverStripe\Security\Permission;
diff --git a/src/ORM/Hierarchy/Hierarchy.php b/src/ORM/Hierarchy/Hierarchy.php
index 65d45589f..9c2ac73fa 100644
--- a/src/ORM/Hierarchy/Hierarchy.php
+++ b/src/ORM/Hierarchy/Hierarchy.php
@@ -12,7 +12,7 @@ use SilverStripe\ORM\ValidationResult;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataExtension;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
use Exception;
/**
@@ -685,7 +685,7 @@ class Hierarchy extends DataExtension implements Resettable
$stageChildren = $this->owner->stageChildren(true);
// Add live site content that doesn't exist on the stage site, if required.
- if ($this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
+ if ($this->owner->hasExtension(Versioned::class)) {
// Next, go through the live children. Only some of these will be listed
$liveChildren = $this->owner->liveChildren(true, true);
if ($liveChildren) {
@@ -715,7 +715,7 @@ class Hierarchy extends DataExtension implements Resettable
*/
public function AllHistoricalChildren()
{
- if (!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
+ if (!$this->owner->hasExtension(Versioned::class)) {
throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
}
@@ -736,7 +736,7 @@ class Hierarchy extends DataExtension implements Resettable
*/
public function numHistoricalChildren()
{
- if (!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
+ if (!$this->owner->hasExtension(Versioned::class)) {
throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
}
diff --git a/src/ORM/Versioning/ChangeSet.php b/src/ORM/Versioning/ChangeSet.php
deleted file mode 100644
index db51aeb06..000000000
--- a/src/ORM/Versioning/ChangeSet.php
+++ /dev/null
@@ -1,579 +0,0 @@
- 'Varchar',
- 'State' => "Enum('open,published,reverted','open')",
- 'IsInferred' => 'Boolean(0)' // True if created automatically
- );
-
- private static $has_many = array(
- 'Changes' => 'SilverStripe\ORM\Versioning\ChangeSetItem',
- );
-
- private static $defaults = array(
- 'State' => 'open'
- );
-
- private static $has_one = array(
- 'Owner' => 'SilverStripe\\Security\\Member',
- );
-
- private static $casting = array(
- 'Description' => 'Text',
- );
-
- /**
- * List of classes to set apart in description
- *
- * @config
- * @var array
- */
- private static $important_classes = array(
- 'SilverStripe\\CMS\\Model\\SiteTree',
- 'SilverStripe\\Assets\\File',
- );
-
- private static $summary_fields = [
- 'Name' => 'Title',
- 'ChangesCount' => 'Changes',
- 'Description' => 'Description',
- ];
-
- /**
- * Default permission to require for publishers.
- * Publishers must either be able to use the campaign admin, or have all admin access.
- *
- * Also used as default permission for ChangeSetItem default permission.
- *
- * @config
- * @var array
- */
- private static $required_permission = array('CMS_ACCESS_CampaignAdmin', 'CMS_ACCESS_LeftAndMain');
-
- /**
- * Publish this changeset, then closes it.
- *
- * @throws Exception
- * @return bool True if successful
- */
- public function publish()
- {
- // Logical checks prior to publish
- if ($this->State !== static::STATE_OPEN) {
- throw new BadMethodCallException(
- "ChangeSet can't be published if it has been already published or reverted."
- );
- }
- if (!$this->isSynced()) {
- throw new ValidationException(
- "ChangeSet does not include all necessary changes and cannot be published."
- );
- }
- if (!$this->canPublish()) {
- throw new LogicException("The current member does not have permission to publish this ChangeSet.");
- }
-
- DB::get_conn()->withTransaction(function () {
- foreach ($this->Changes() as $change) {
- /** @var ChangeSetItem $change */
- $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;
- }
-
- /**
- * Add a new change to this changeset. Will automatically include all owned
- * changes as those are dependencies of this item.
- *
- * @param DataObject $object
- */
- public function addObject(DataObject $object)
- {
- if (!$this->isInDB()) {
- throw new BadMethodCallException("ChangeSet must be saved before adding items");
- }
-
- if (!$object->isInDB()) {
- throw new BadMethodCallException("Items must be saved before adding to a changeset");
- }
-
- $references = [
- 'ObjectID' => $object->ID,
- 'ObjectClass' => $object->baseClass(),
- ];
-
- // Get existing item in case already added
- $item = $this->Changes()->filter($references)->first();
-
- if (!$item) {
- $item = new ChangeSetItem($references);
- $this->Changes()->add($item);
- }
-
- $item->ReferencedBy()->removeAll();
-
- $item->Added = ChangeSetItem::EXPLICITLY;
- $item->write();
-
-
- $this->sync();
- }
-
- /**
- * Remove an item from this changeset. Will automatically remove all changes
- * which own (and thus depend on) the removed item.
- *
- * @param DataObject $object
- */
- public function removeObject(DataObject $object)
- {
- $item = ChangeSetItem::get()->filter([
- 'ObjectID' => $object->ID,
- 'ObjectClass' => $object->baseClass(),
- 'ChangeSetID' => $this->ID
- ])->first();
-
- if ($item) {
- // TODO: Handle case of implicit added item being removed.
-
- $item->delete();
- }
-
- $this->sync();
- }
-
- /**
- * Build identifying string key for this object
- *
- * @param DataObject $item
- * @return string
- */
- protected function implicitKey(DataObject $item)
- {
- if ($item instanceof ChangeSetItem) {
- return $item->ObjectClass.'.'.$item->ObjectID;
- }
- return $item->baseClass().'.'.$item->ID;
- }
-
- protected function calculateImplicit()
- {
- /** @var string[][] $explicit List of all items that have been explicitly added to this ChangeSet */
- $explicit = array();
-
- /** @var string[][] $referenced List of all items that are "referenced" by items in $explicit */
- $referenced = array();
-
- /** @var string[][] $references List of which explicit items reference each thing in referenced */
- $references = array();
-
- /** @var ChangeSetItem $item */
- foreach ($this->Changes()->filter(['Added' => ChangeSetItem::EXPLICITLY]) as $item) {
- $explicitKey = $this->implicitKey($item);
- $explicit[$explicitKey] = true;
-
- foreach ($item->findReferenced() as $referee) {
- try {
- /** @var DataObject $referee */
- $key = $this->implicitKey($referee);
-
- $referenced[$key] = [
- 'ObjectID' => $referee->ID,
- 'ObjectClass' => $referee->baseClass(),
- ];
-
- $references[$key][] = $item->ID;
-
- // Skip any bad records
- } catch (UnexpectedDataException $e) {
- }
- }
- }
-
- /** @var string[][] $explicit List of all items that are either in $explicit, $referenced or both */
- $all = array_merge($referenced, $explicit);
-
- /** @var string[][] $implicit Anything that is in $all, but not in $explicit, is an implicit inclusion */
- $implicit = array_diff_key($all, $explicit);
-
- foreach ($implicit as $key => $object) {
- $implicit[$key]['ReferencedBy'] = $references[$key];
- }
-
- return $implicit;
- }
-
- /**
- * Add implicit changes that should be included in this changeset
- *
- * When an item is created or changed, all it's owned items which have
- * changes are implicitly added
- *
- * When an item is deleted, it's owner (even if that owner does not have changes)
- * is implicitly added
- */
- public function sync()
- {
- // Start a transaction (if we can)
- DB::get_conn()->withTransaction(function () {
-
- // Get the implicitly included items for this ChangeSet
- $implicit = $this->calculateImplicit();
-
- // Adjust the existing implicit ChangeSetItems for this ChangeSet
- /** @var ChangeSetItem $item */
- foreach ($this->Changes()->filter(['Added' => ChangeSetItem::IMPLICITLY]) as $item) {
- $objectKey = $this->implicitKey($item);
-
- // If a ChangeSetItem exists, but isn't in $implicit, it's no longer required, so delete it
- if (!array_key_exists($objectKey, $implicit)) {
- $item->delete();
- } // Otherwise it is required, so update ReferencedBy and remove from $implicit
- else {
- $item->ReferencedBy()->setByIDList($implicit[$objectKey]['ReferencedBy']);
- unset($implicit[$objectKey]);
- }
- }
-
- // Now $implicit is all those items that are implicitly included, but don't currently have a ChangeSetItem.
- // So create new ChangeSetItems to match
-
- foreach ($implicit as $key => $props) {
- $item = new ChangeSetItem($props);
- $item->Added = ChangeSetItem::IMPLICITLY;
- $item->ChangeSetID = $this->ID;
- $item->ReferencedBy()->setByIDList($props['ReferencedBy']);
- $item->write();
- }
- });
- }
-
- /** Verify that any objects in this changeset include all owned changes */
- public function isSynced()
- {
- $implicit = $this->calculateImplicit();
-
- // Check the existing implicit ChangeSetItems for this ChangeSet
-
- foreach ($this->Changes()->filter(['Added' => ChangeSetItem::IMPLICITLY]) as $item) {
- $objectKey = $this->implicitKey($item);
-
- // If a ChangeSetItem exists, but isn't in $implicit -> validation failure
- if (!array_key_exists($objectKey, $implicit)) {
- return false;
- }
- // Exists, remove from $implicit
- unset($implicit[$objectKey]);
- }
-
- // If there's anything left in $implicit -> validation failure
- return empty($implicit);
- }
-
- public function canView($member = null)
- {
- return $this->can(__FUNCTION__, $member);
- }
-
- public function canEdit($member = null)
- {
- return $this->can(__FUNCTION__, $member);
- }
-
- public function canCreate($member = null, $context = array())
- {
- return $this->can(__FUNCTION__, $member, $context);
- }
-
- public function canDelete($member = null)
- {
- return $this->can(__FUNCTION__, $member);
- }
-
- /**
- * Check if this item is allowed to be published
- *
- * @param Member $member
- * @return bool
- */
- public function canPublish($member = null)
- {
- foreach ($this->Changes() as $change) {
- /** @var ChangeSetItem $change */
- if (!$change->canPublish($member)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Determine if there are changes to publish
- *
- * @return bool
- */
- public function hasChanges()
- {
- // All changes must be publishable
- /** @var ChangeSetItem $change */
- foreach ($this->Changes() as $change) {
- if ($change->hasChange()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Check if this changeset (if published) can be reverted
- *
- * @param Member $member
- * @return bool
- */
- public function canRevert($member = null)
- {
- // All changes must be publishable
- foreach ($this->Changes() as $change) {
- /** @var ChangeSetItem $change */
- if (!$change->canRevert($member)) {
- return false;
- }
- }
-
- // Default permission
- return $this->can(__FUNCTION__, $member);
- }
-
- /**
- * Default permissions for this changeset
- *
- * @param string $perm
- * @param Member $member
- * @param array $context
- * @return bool
- */
- public function can($perm, $member = null, $context = array())
- {
- if (!$member) {
- $member = Member::currentUser();
- }
-
- // Allow extensions to bypass default permissions, but only if
- // each change can be individually published.
- $extended = $this->extendedCan($perm, $member, $context);
- if ($extended !== null) {
- return $extended;
- }
-
- // Default permissions
- return (bool)Permission::checkMember($member, $this->config()->required_permission);
- }
-
- public function getCMSFields()
- {
- $fields = new FieldList(new TabSet('Root'));
- 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')));
- }
-
- $this->extend('updateCMSFields', $fields);
- return $fields;
- }
-
- /**
- * Gets summary of items in changeset
- *
- * @return string
- */
- public function getDescription()
- {
- // Initialise list of items to count
- $counted = [];
- $countedOther = 0;
- foreach ($this->config()->important_classes as $type) {
- if (class_exists($type)) {
- $counted[$type] = 0;
- }
- }
-
- // Check each change item
- /** @var ChangeSetItem $change */
- foreach ($this->Changes() as $change) {
- $found = false;
- foreach ($counted as $class => $num) {
- if (is_a($change->ObjectClass, $class, true)) {
- $counted[$class]++;
- $found = true;
- break;
- }
- }
- if (!$found) {
- $countedOther++;
- }
- }
-
- // Describe set based on this output
- $counted = array_filter($counted);
-
- // Empty state
- if (empty($counted) && empty($countedOther)) {
- return '';
- }
-
- // Put all parts together
- $parts = [];
- foreach ($counted as $class => $count) {
- $parts[] = DataObject::singleton($class)->i18n_pluralise($count);
- }
-
- // Describe non-important items
- if ($countedOther) {
- if ($counted) {
- $parts[] = i18n::_t(
- 'ChangeSet.DESCRIPTION_OTHER_ITEM_PLURALS',
- 'one other item|{count} other items',
- [ 'count' => $countedOther ]
- );
- } else {
- $parts[] = i18n::_t(
- 'ChangeSet.DESCRIPTION_ITEM_PLURALS',
- 'one item|{count} items',
- [ 'count' => $countedOther ]
- );
- }
- }
-
- // Figure out how to join everything together
- if (empty($parts)) {
- return '';
- }
- if (count($parts) === 1) {
- return $parts[0];
- }
-
- // Non-comma list
- if (count($parts) === 2) {
- return _t(
- 'ChangeSet.DESCRIPTION_AND',
- '{first} and {second}',
- [
- 'first' => $parts[0],
- 'second' => $parts[1],
- ]
- );
- }
-
- // First item
- $string = _t(
- 'ChangeSet.DESCRIPTION_LIST_FIRST',
- '{item}',
- ['item' => $parts[0]]
- );
-
- // Middle items
- for ($i = 1; $i < count($parts) - 1; $i++) {
- $string = _t(
- 'ChangeSet.DESCRIPTION_LIST_MID',
- '{list}, {item}',
- [
- 'list' => $string,
- 'item' => $parts[$i]
- ]
- );
- }
-
- // Oxford comma
- $string = _t(
- 'ChangeSet.DESCRIPTION_LIST_LAST',
- '{list}, and {item}',
- [
- 'list' => $string,
- 'item' => end($parts)
- ]
- );
- return $string;
- }
-
- /**
- * Required to support count display in react gridfield column
- *
- * @return int
- */
- public function getChangesCount()
- {
- return $this->Changes()->count();
- }
-
- public function fieldLabels($includerelations = true)
- {
- $labels = parent::fieldLabels($includerelations);
- $labels['Name'] = _t('ChangeSet.NAME', 'Name');
- $labels['State'] = _t('ChangeSet.STATE', 'State');
-
- return $labels;
- }
-}
diff --git a/src/ORM/Versioning/ChangeSetItem.php b/src/ORM/Versioning/ChangeSetItem.php
deleted file mode 100644
index 907223f0a..000000000
--- a/src/ORM/Versioning/ChangeSetItem.php
+++ /dev/null
@@ -1,475 +0,0 @@
- 'Int',
- 'VersionAfter' => 'Int',
- 'Added' => "Enum('explicitly, implicitly', 'implicitly')"
- );
-
- private static $has_one = array(
- 'ChangeSet' => 'SilverStripe\ORM\Versioning\ChangeSet',
- 'Object' => 'SilverStripe\ORM\DataObject',
- );
-
- private static $many_many = array(
- 'ReferencedBy' => 'SilverStripe\ORM\Versioning\ChangeSetItem'
- );
-
- private static $belongs_many_many = array(
- 'References' => 'ChangeSetItem.ReferencedBy'
- );
-
- private static $indexes = array(
- 'ObjectUniquePerChangeSet' => array(
- 'type' => 'unique',
- 'value' => '"ObjectID", "ObjectClass", "ChangeSetID"'
- )
- );
-
- public function onBeforeWrite()
- {
- // Make sure ObjectClass refers to the base data class in the case of old or wrong code
- $this->ObjectClass = $this->getSchema()->baseDataClass($this->ObjectClass);
- parent::onBeforeWrite();
- }
-
- public function getTitle()
- {
- // Get title of modified object
- $object = $this->getObjectLatestVersion();
- if ($object) {
- return $object->getTitle();
- }
- return $this->i18n_singular_name() . ' #' . $this->ID;
- }
-
- /**
- * Get a thumbnail for this object
- *
- * @param int $width Preferred width of the thumbnail
- * @param int $height Preferred height of the thumbnail
- * @return string URL to the thumbnail, if available
- */
- public function ThumbnailURL($width, $height)
- {
- $object = $this->getObjectLatestVersion();
- if ($object instanceof Thumbnail) {
- return $object->ThumbnailURL($width, $height);
- }
- return null;
- }
-
- /**
- * Get the type of change: none, created, deleted, modified, manymany
- * @return string
- * @throws UnexpectedDataException
- */
- public function getChangeType()
- {
- if (!class_exists($this->ObjectClass)) {
- throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
- }
-
- // Get change versions
- if ($this->VersionBefore || $this->VersionAfter) {
- $draftVersion = $this->VersionAfter; // After publishing draft was written to stage
- $liveVersion = $this->VersionBefore; // The live version before the publish
- } else {
- $draftVersion = Versioned::get_versionnumber_by_stage(
- $this->ObjectClass,
- Versioned::DRAFT,
- $this->ObjectID,
- false
- );
- $liveVersion = Versioned::get_versionnumber_by_stage(
- $this->ObjectClass,
- Versioned::LIVE,
- $this->ObjectID,
- false
- );
- }
-
- // Version comparisons
- if ($draftVersion == $liveVersion) {
- return self::CHANGE_NONE;
- } elseif (!$liveVersion) {
- return self::CHANGE_CREATED;
- } elseif (!$draftVersion) {
- return self::CHANGE_DELETED;
- } else {
- return self::CHANGE_MODIFIED;
- }
- }
-
- /**
- * Find version of this object in the given stage
- *
- * @param string $stage
- * @return DataObject|Versioned
- * @throws UnexpectedDataException
- */
- protected function getObjectInStage($stage)
- {
- if (!class_exists($this->ObjectClass)) {
- throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
- }
-
- return Versioned::get_by_stage($this->ObjectClass, $stage)->byID($this->ObjectID);
- }
-
- /**
- * Find latest version of this object
- * @return DataObject|Versioned
- * @throws UnexpectedDataException
- */
- protected function getObjectLatestVersion()
- {
- if (!class_exists($this->ObjectClass)) {
- throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
- }
-
- return Versioned::get_latest_version($this->ObjectClass, $this->ObjectID);
- }
-
- /**
- * Get all implicit objects for this change
- *
- * @return SS_List
- */
- public function findReferenced()
- {
- if ($this->getChangeType() === ChangeSetItem::CHANGE_DELETED) {
- // If deleted from stage, need to look at live record
- $record = $this->getObjectInStage(Versioned::LIVE);
- if ($record) {
- return $record->findOwners(false);
- }
- } else {
- // If changed on stage, look at owned objects there
- $record = $this->getObjectInStage(Versioned::DRAFT);
- if ($record) {
- return $record->findOwned()->filterByCallback(function ($owned) {
- /** @var Versioned|DataObject $owned */
- return $owned->stagesDiffer(Versioned::DRAFT, Versioned::LIVE);
- });
- }
- }
- // Empty set
- return new ArrayList();
- }
-
- /**
- * Publish this item, then close it.
- *
- * Note: Unlike Versioned::doPublish() and Versioned::doUnpublish, this action is not recursive.
- */
- public function publish()
- {
- if (!class_exists($this->ObjectClass)) {
- throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
- }
-
- // Logical checks prior to publish
- if (!$this->canPublish()) {
- throw new Exception("The current member does not have permission to publish this ChangeSetItem.");
- }
- if ($this->VersionBefore || $this->VersionAfter) {
- throw new BadMethodCallException("This ChangeSetItem has already been published");
- }
-
- // Record state changed
- $this->VersionAfter = Versioned::get_versionnumber_by_stage(
- $this->ObjectClass,
- Versioned::DRAFT,
- $this->ObjectID,
- false
- );
- $this->VersionBefore = Versioned::get_versionnumber_by_stage(
- $this->ObjectClass,
- Versioned::LIVE,
- $this->ObjectID,
- false
- );
-
- switch ($this->getChangeType()) {
- case static::CHANGE_NONE: {
- break;
- }
- case static::CHANGE_DELETED: {
- // Non-recursive delete
- $object = $this->getObjectInStage(Versioned::LIVE);
- $object->deleteFromStage(Versioned::LIVE);
- break;
- }
- case static::CHANGE_MODIFIED:
- case static::CHANGE_CREATED: {
- // Non-recursive publish
- $object = $this->getObjectInStage(Versioned::DRAFT);
- $object->publishSingle();
- break;
- }
- }
-
- $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);
- }
-
- public function canView($member = null)
- {
- return $this->can(__FUNCTION__, $member);
- }
-
- public function canEdit($member = null)
- {
- return $this->can(__FUNCTION__, $member);
- }
-
- public function canCreate($member = null, $context = array())
- {
- return $this->can(__FUNCTION__, $member, $context);
- }
-
- public function canDelete($member = null)
- {
- return $this->can(__FUNCTION__, $member);
- }
-
- /**
- * Check if the BeforeVersion of this changeset can be restored to draft
- *
- * @param Member $member
- * @return bool
- */
- public function canRevert($member)
- {
- // Just get the best version as this object may not even exist on either stage anymore.
- /** @var Versioned|DataObject $object */
- $object = $this->getObjectLatestVersion();
- if (!$object) {
- return false;
- }
-
- // Check change type
- switch ($this->getChangeType()) {
- case static::CHANGE_CREATED: {
- // Revert creation by deleting from stage
- return $object->canDelete($member);
- }
- default: {
- // All other actions are typically editing draft stage
- return $object->canEdit($member);
- }
- }
- }
-
- /**
- * Check if this ChangeSetItem can be published
- *
- * @param Member $member
- * @return bool
- */
- public function canPublish($member = null)
- {
- // Check canMethod to invoke on object
- switch ($this->getChangeType()) {
- case static::CHANGE_DELETED: {
- /** @var Versioned|DataObject $object */
- $object = Versioned::get_by_stage($this->ObjectClass, Versioned::LIVE)->byID($this->ObjectID);
- if ($object) {
- return $object->canUnpublish($member);
- }
- break;
- }
- default: {
- /** @var Versioned|DataObject $object */
- $object = Versioned::get_by_stage($this->ObjectClass, Versioned::DRAFT)->byID($this->ObjectID);
- if ($object) {
- return $object->canPublish($member);
- }
- break;
- }
- }
-
- return true;
- }
-
- /**
- * Determine if this item has changes
- *
- * @return bool
- */
- public function hasChange()
- {
- return $this->getChangeType() !== ChangeSetItem::CHANGE_NONE;
- }
-
- /**
- * Default permissions for this ChangeSetItem
- *
- * @param string $perm
- * @param Member $member
- * @param array $context
- * @return bool
- */
- public function can($perm, $member = null, $context = array())
- {
- if (!$member) {
- $member = Member::currentUser();
- }
-
- // Allow extensions to bypass default permissions, but only if
- // each change can be individually published.
- $extended = $this->extendedCan($perm, $member, $context);
- if ($extended !== null) {
- return $extended;
- }
-
- // Default permissions
- return (bool)Permission::checkMember($member, ChangeSet::config()->required_permission);
- }
-
- /**
- * Get the ChangeSetItems that reference a passed DataObject
- *
- * @param DataObject $object
- * @return DataList
- */
- public static function get_for_object($object)
- {
- return ChangeSetItem::get()->filter([
- 'ObjectID' => $object->ID,
- 'ObjectClass' => $object->baseClass(),
- ]);
- }
-
- /**
- * Get the ChangeSetItems that reference a passed DataObject
- *
- * @param int $objectID The ID of the object
- * @param string $objectClass The class of the object (or any parent class)
- * @return DataList
- */
- public static function get_for_object_by_id($objectID, $objectClass)
- {
- return ChangeSetItem::get()->filter([
- 'ObjectID' => $objectID,
- 'ObjectClass' => static::getSchema()->baseDataClass($objectClass)
- ]);
- }
-
- /**
- * Gets the list of modes this record can be previewed in.
- *
- * {@link https://tools.ietf.org/html/draft-kelly-json-hal-07#section-5}
- *
- * @return array Map of links in acceptable HAL format
- */
- public function getPreviewLinks()
- {
- $links = [];
-
- // Preview draft
- $stage = $this->getObjectInStage(Versioned::DRAFT);
- if ($stage instanceof CMSPreviewable && $stage->canView() && ($link = $stage->PreviewLink())) {
- $links[Versioned::DRAFT] = [
- 'href' => Controller::join_links($link, '?stage=' . Versioned::DRAFT),
- 'type' => $stage->getMimeType(),
- ];
- }
-
- // Preview live
- $live = $this->getObjectInStage(Versioned::LIVE);
- if ($live instanceof CMSPreviewable && $live->canView() && ($link = $live->PreviewLink())) {
- $links[Versioned::LIVE] = [
- 'href' => Controller::join_links($link, '?stage=' . Versioned::LIVE),
- 'type' => $live->getMimeType(),
- ];
- }
-
- return $links;
- }
-
- /**
- * Get edit link for this item
- *
- * @return string
- */
- public function CMSEditLink()
- {
- $link = $this->getObjectInStage(Versioned::DRAFT);
- if ($link instanceof CMSPreviewable) {
- return $link->CMSEditLink();
- }
- return null;
- }
-}
diff --git a/src/ORM/Versioning/DataDifferencer.php b/src/ORM/Versioning/DataDifferencer.php
deleted file mode 100644
index f36550264..000000000
--- a/src/ORM/Versioning/DataDifferencer.php
+++ /dev/null
@@ -1,273 +0,0 @@
-
- * $fromRecord = Versioned::get_version('SiteTree', $pageID, $fromVersion);
- * $toRecord = Versioned::get_version('SiteTree, $pageID, $toVersion);
- * $diff = new DataDifferencer($fromRecord, $toRecord);
- *
- *
- * And then it can be used in a number of ways. You can use the ChangedFields() method in a template:
- *
- *
- * <% with Diff %>
- * <% loop ChangedFields %>
- * - $Title
- * - $Diff
- * <% end_loop %>
- * <% end_with %>
- *
- *
- *
- * Or you can get the diff'ed content as another DataObject, that you can insert into a form.
- *
- * $form->loadDataFrom($diff->diffedData());
- *
- *
- * If there are fields whose changes you aren't interested in, you can ignore them like so:
- *
- * $diff->ignoreFields('AuthorID', 'Status');
- *
- */
-class DataDifferencer extends ViewableData
-{
- protected $fromRecord;
- protected $toRecord;
-
- protected $ignoredFields = array("ID","Version","RecordID");
-
- /**
- * Construct a DataDifferencer to show the changes between $fromRecord and $toRecord.
- * If $fromRecord is null, this will represent a "creation".
- *
- * @param DataObject $fromRecord
- * @param DataObject $toRecord
- */
- public function __construct(DataObject $fromRecord = null, DataObject $toRecord = null)
- {
- $this->fromRecord = $fromRecord;
- $this->toRecord = $toRecord;
- parent::__construct();
- }
-
- /**
- * Specify some fields to ignore changes from. Repeated calls are cumulative.
- * @param array $ignoredFields An array of field names to ignore. Alternatively, pass the field names as
- * separate args.
- * @return $this
- */
- public function ignoreFields($ignoredFields)
- {
- if (!is_array($ignoredFields)) {
- $ignoredFields = func_get_args();
- }
- $this->ignoredFields = array_merge($this->ignoredFields, $ignoredFields);
-
- return $this;
- }
-
- /**
- * Get a DataObject with altered values replaced with HTML diff strings, incorporating
- * and tags.
- */
- public function diffedData()
- {
- if ($this->fromRecord) {
- $diffed = clone $this->fromRecord;
- $fields = array_keys($diffed->toMap() + $this->toRecord->toMap());
- } else {
- $diffed = clone $this->toRecord;
- $fields = array_keys($this->toRecord->toMap());
- }
-
- $hasOnes = array_merge($this->fromRecord->hasOne(), $this->toRecord->hasOne());
-
- // Loop through properties
- foreach ($fields as $field) {
- if (in_array($field, $this->ignoredFields)) {
- continue;
- }
- if (in_array($field, array_keys($hasOnes))) {
- continue;
- }
-
- // Check if a field from-value is comparable
- $toField = $this->toRecord->obj($field);
- if (!($toField instanceof DBField)) {
- continue;
- }
- $toValue = $toField->forTemplate();
-
- // Show only to value
- if (!$this->fromRecord) {
- $diffed->setField($field, "{$toValue}");
- continue;
- }
-
- // Check if a field to-value is comparable
- $fromField = $this->fromRecord->obj($field);
- if (!($fromField instanceof DBField)) {
- continue;
- }
- $fromValue = $fromField->forTemplate();
-
- // Show changes between the two, if any exist
- if ($fromValue != $toValue) {
- $diffed->setField($field, Diff::compareHTML($fromValue, $toValue));
- }
- }
-
- // Loop through has_one
- foreach ($hasOnes as $relName => $relSpec) {
- if (in_array($relName, $this->ignoredFields)) {
- continue;
- }
-
- // Create the actual column name
- $relField = "{$relName}ID";
- $toTitle = '';
- if ($this->toRecord->hasMethod($relName)) {
- $relObjTo = $this->toRecord->$relName();
- if ($relObjTo) {
- $toTitle = ($relObjTo->hasMethod('Title') || $relObjTo->hasField('Title')) ? $relObjTo->Title : '';
- } else {
- $toTitle = '';
- }
- }
-
- if (!$this->fromRecord) {
- if ($relObjTo) {
- if ($relObjTo instanceof Image) {
- // Using relation name instead of database column name, because of FileField etc.
- // TODO Use CMSThumbnail instead to limit max size, blocked by DataDifferencerTest and GC
- // not playing nice with mocked images
- $diffed->setField($relName, "" . $relObjTo->getTag() . "");
- } else {
- $diffed->setField($relField, "" . $toTitle . "");
- }
- }
- } elseif ($this->fromRecord->$relField != $this->toRecord->$relField) {
- $fromTitle = '';
- if ($this->fromRecord->hasMethod($relName)) {
- $relObjFrom = $this->fromRecord->$relName();
- if ($relObjFrom) {
- $fromTitle = ($relObjFrom->hasMethod('Title') || $relObjFrom->hasField('Title'))
- ? $relObjFrom->Title
- : '';
- } else {
- $fromTitle = '';
- }
- }
- if (isset($relObjFrom) && $relObjFrom instanceof Image) {
- // TODO Use CMSThumbnail (see above)
- $diffed->setField(
- // Using relation name instead of database column name, because of FileField etc.
- $relName,
- Diff::compareHTML($relObjFrom->getTag(), $relObjTo->getTag())
- );
- } else {
- // Set the field.
- $diffed->setField(
- $relField,
- Diff::compareHTML($fromTitle, $toTitle)
- );
- }
- }
- }
-
- return $diffed;
- }
-
- /**
- * Get a SS_List of the changed fields.
- * Each element is an array data containing
- * - Name: The field name
- * - Title: The human-readable field title
- * - Diff: An HTML diff showing the changes
- * - From: The older version of the field
- * - To: The newer version of the field
- */
- public function ChangedFields()
- {
- $changedFields = new ArrayList();
-
- if ($this->fromRecord) {
- $base = $this->fromRecord;
- $fields = array_keys($this->fromRecord->toMap());
- } else {
- $base = $this->toRecord;
- $fields = array_keys($this->toRecord->toMap());
- }
-
- foreach ($fields as $field) {
- if (in_array($field, $this->ignoredFields)) {
- continue;
- }
-
- if (!$this->fromRecord || $this->fromRecord->$field != $this->toRecord->$field) {
- // Only show HTML diffs for fields which allow HTML values in the first place
- $fieldObj = $this->toRecord->dbObject($field);
- if ($this->fromRecord) {
- $fieldDiff = Diff::compareHTML(
- $this->fromRecord->$field,
- $this->toRecord->$field,
- (!$fieldObj || $fieldObj->stat('escape_type') != 'xml')
- );
- } else {
- if ($fieldObj && $fieldObj->stat('escape_type') == 'xml') {
- $fieldDiff = "" . $this->toRecord->$field . "";
- } else {
- $fieldDiff = "" . Convert::raw2xml($this->toRecord->$field) . "";
- }
- }
- $changedFields->push(new ArrayData(array(
- 'Name' => $field,
- 'Title' => $base->fieldLabel($field),
- 'Diff' => $fieldDiff,
- 'From' => $this->fromRecord ? $this->fromRecord->$field : null,
- 'To' => $this->toRecord ? $this->toRecord->$field : null,
- )));
- }
- }
-
- return $changedFields;
- }
-
- /**
- * Get an array of the names of every fields that has changed.
- * This is simpler than {@link ChangedFields()}
- */
- public function changedFieldNames()
- {
- $diffed = clone $this->fromRecord;
- $fields = array_keys($diffed->toMap());
-
- $changedFields = array();
-
- foreach ($fields as $field) {
- if (in_array($field, $this->ignoredFields)) {
- continue;
- }
- if ($this->fromRecord->$field != $this->toRecord->$field) {
- $changedFields[] = $field;
- }
- }
-
- return $changedFields;
- }
-}
diff --git a/src/ORM/Versioning/VersionableExtension.php b/src/ORM/Versioning/VersionableExtension.php
deleted file mode 100644
index 7728ff289..000000000
--- a/src/ORM/Versioning/VersionableExtension.php
+++ /dev/null
@@ -1,27 +0,0 @@
- "Int",
- "Version" => "Int",
- "WasPublished" => "Boolean",
- "AuthorID" => "Int",
- "PublisherID" => "Int"
- );
-
- /**
- * @var array
- * @config
- */
- private static $db = array(
- 'Version' => 'Int'
- );
-
- /**
- * Used to enable or disable the prepopulation of the version number cache.
- * Defaults to true.
- *
- * @config
- * @var boolean
- */
- private static $prepopulate_versionnumber_cache = true;
-
- /**
- * Additional database indexes for the new
- * "_Versions" table. Used in {@link augmentDatabase()}.
- *
- * @var array $indexes_for_versions_table
- */
- private static $indexes_for_versions_table = array(
- 'RecordID_Version' => '("RecordID","Version")',
- 'RecordID' => true,
- 'Version' => true,
- 'AuthorID' => true,
- 'PublisherID' => true,
- );
-
-
- /**
- * An array of DataObject extensions that may require versioning for extra tables
- * The array value is a set of suffixes to form these table names, assuming a preceding '_'.
- * E.g. if Extension1 creates a new table 'Class_suffix1'
- * and Extension2 the tables 'Class_suffix2' and 'Class_suffix3':
- *
- * $versionableExtensions = array(
- * 'Extension1' => 'suffix1',
- * 'Extension2' => array('suffix2', 'suffix3'),
- * );
- *
- * This can also be manipulated by updating the current loaded config
- *
- * SiteTree:
- * versionableExtensions:
- * - Extension1:
- * - suffix1
- * - suffix2
- * - Extension2:
- * - suffix1
- * - suffix2
- *
- * or programatically:
- *
- * Config::inst()->update($this->owner->class, 'versionableExtensions',
- * array('Extension1' => 'suffix1', 'Extension2' => array('suffix2', 'suffix3')));
- *
- *
- * Your extension must implement VersionableExtension interface in order to
- * apply custom tables for versioned.
- *
- * @config
- * @var array
- */
- private static $versionableExtensions = [];
-
- /**
- * Permissions necessary to view records outside of the live stage (e.g. archive / draft stage).
- *
- * @config
- * @var array
- */
- private static $non_live_permissions = array('CMS_ACCESS_LeftAndMain', 'CMS_ACCESS_CMSMain', 'VIEW_DRAFT_CONTENT');
-
- /**
- * List of relationships on this object that are "owned" by this object.
- * Owership in the context of versioned objects is a relationship where
- * the publishing of owning objects requires the publishing of owned objects.
- *
- * E.g. A page owns a set of banners, as in order for the page to be published, all
- * banners on this page must also be published for it to be visible.
- *
- * Typically any object and its owned objects should be visible in the same edit view.
- * E.g. a page and {@see GridField} of banners.
- *
- * Page hierarchy is typically not considered an ownership relationship.
- *
- * Ownership is recursive; If A owns B and B owns C then A owns C.
- *
- * @config
- * @var array List of has_many or many_many relationships owned by this object.
- */
- private static $owns = array();
-
- /**
- * Opposing relationship to owns config; Represents the objects which
- * own the current object.
- *
- * @var array
- */
- private static $owned_by = array();
-
- /**
- * Reset static configuration variables to their default values.
- */
- public static function reset()
- {
- self::$reading_mode = '';
- Session::clear('readingMode');
- }
-
- /**
- * Amend freshly created DataQuery objects with versioned-specific
- * information.
- *
- * @param SQLSelect $query
- * @param DataQuery $dataQuery
- */
- public function augmentDataQueryCreation(SQLSelect &$query, DataQuery &$dataQuery)
- {
- $parts = explode('.', Versioned::get_reading_mode());
-
- if ($parts[0] == 'Archive') {
- $dataQuery->setQueryParam('Versioned.mode', 'archive');
- $dataQuery->setQueryParam('Versioned.date', $parts[1]);
- } elseif ($parts[0] == 'Stage' && $this->hasStages()) {
- $dataQuery->setQueryParam('Versioned.mode', 'stage');
- $dataQuery->setQueryParam('Versioned.stage', $parts[1]);
- }
- }
-
- /**
- * Construct a new Versioned object.
- *
- * @var string $mode One of "StagedVersioned" or "Versioned".
- */
- public function __construct($mode = self::STAGEDVERSIONED)
- {
- parent::__construct();
-
- // Handle deprecated behaviour
- if ($mode === 'Stage' && func_num_args() === 1) {
- Deprecation::notice("5.0", "Versioned now takes a mode as a single parameter");
- $mode = static::VERSIONED;
- } elseif (is_array($mode) || func_num_args() > 1) {
- Deprecation::notice("5.0", "Versioned now takes a mode as a single parameter");
- $mode = func_num_args() > 1 || count($mode) > 1
- ? static::STAGEDVERSIONED
- : static::VERSIONED;
- }
-
- if (!in_array($mode, array(static::STAGEDVERSIONED, static::VERSIONED))) {
- throw new InvalidArgumentException("Invalid mode: {$mode}");
- }
-
- $this->mode = $mode;
- }
-
- /**
- * Cache of version to modified dates for this objects
- *
- * @var array
- */
- protected $versionModifiedCache = array();
-
- /**
- * Get modified date for the given version
- *
- * @param int $version
- * @return string
- */
- protected function getLastEditedForVersion($version)
- {
- // Cache key
- $baseTable = $this->baseTable();
- $id = $this->owner->ID;
- $key = "{$baseTable}#{$id}/{$version}";
-
- // Check cache
- if (isset($this->versionModifiedCache[$key])) {
- return $this->versionModifiedCache[$key];
- }
-
- // Build query
- $table = "\"{$baseTable}_Versions\"";
- $query = SQLSelect::create('"LastEdited"', $table)
- ->addWhere([
- "{$table}.\"RecordID\"" => $id,
- "{$table}.\"Version\"" => $version
- ]);
- $date = $query->execute()->value();
- if ($date) {
- $this->versionModifiedCache[$key] = $date;
- }
- return $date;
- }
-
- /**
- * Updates query parameters of relations attached to versioned dataobjects
- *
- * @param array $params
- */
- public function updateInheritableQueryParams(&$params)
- {
- // Skip if versioned isn't set
- if (!isset($params['Versioned.mode'])) {
- return;
- }
-
- // Adjust query based on original selection criterea
- switch ($params['Versioned.mode']) {
- case 'all_versions': {
- // Versioned.mode === all_versions doesn't inherit very well, so default to stage
- $params['Versioned.mode'] = 'stage';
- $params['Versioned.stage'] = static::DRAFT;
- break;
- }
- case 'version': {
- // If we selected this object from a specific version, we need
- // to find the date this version was published, and ensure
- // inherited queries select from that date.
- $version = $params['Versioned.version'];
- $date = $this->getLastEditedForVersion($version);
-
- // Filter related objects at the same date as this version
- unset($params['Versioned.version']);
- if ($date) {
- $params['Versioned.mode'] = 'archive';
- $params['Versioned.date'] = $date;
- } else {
- // Fallback to default
- $params['Versioned.mode'] = 'stage';
- $params['Versioned.stage'] = static::DRAFT;
- }
- break;
- }
- }
- }
-
- /**
- * Augment the the SQLSelect that is created by the DataQuery
- *
- * See {@see augmentLazyLoadFields} for lazy-loading applied prior to this.
- *
- * @param SQLSelect $query
- * @param DataQuery $dataQuery
- * @throws InvalidArgumentException
- */
- public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
- {
- if (!$dataQuery || !$dataQuery->getQueryParam('Versioned.mode')) {
- return;
- }
-
- $baseTable = $this->baseTable();
- $versionedMode = $dataQuery->getQueryParam('Versioned.mode');
- switch ($versionedMode) {
- // Reading a specific stage (Stage or Live)
- case 'stage':
- // Check if we need to rewrite this table
- $stage = $dataQuery->getQueryParam('Versioned.stage');
- if (!$this->hasStages() || $stage === static::DRAFT) {
- break;
- }
- // Rewrite all tables to select from the live version
- foreach ($query->getFrom() as $table => $dummy) {
- if (!$this->isTableVersioned($table)) {
- continue;
- }
- $stageTable = $this->stageTable($table, $stage);
- $query->renameTable($table, $stageTable);
- }
- break;
-
- // Reading a specific stage, but only return items that aren't in any other stage
- case 'stage_unique':
- if (!$this->hasStages()) {
- break;
- }
-
- $stage = $dataQuery->getQueryParam('Versioned.stage');
- // Recurse to do the default stage behavior (must be first, we rely on stage renaming happening before
- // below)
- $dataQuery->setQueryParam('Versioned.mode', 'stage');
- $this->augmentSQL($query, $dataQuery);
- $dataQuery->setQueryParam('Versioned.mode', 'stage_unique');
-
- // Now exclude any ID from any other stage. Note that we double rename to avoid the regular stage rename
- // renaming all subquery references to be Versioned.stage
- foreach ([static::DRAFT, static::LIVE] as $excluding) {
- if ($excluding == $stage) {
- continue;
- }
-
- $tempName = 'ExclusionarySource_'.$excluding;
- $excludingTable = $this->baseTable($excluding);
-
- $query->addWhere('"'.$baseTable.'"."ID" NOT IN (SELECT "ID" FROM "'.$tempName.'")');
- $query->renameTable($tempName, $excludingTable);
- }
- break;
-
- // Return all version instances
- case 'archive':
- case 'all_versions':
- case 'latest_versions':
- case 'version':
- foreach ($query->getFrom() as $alias => $join) {
- if (!$this->isTableVersioned($alias)) {
- continue;
- }
-
- if ($alias != $baseTable) {
- // Make sure join includes version as well
- $query->setJoinFilter(
- $alias,
- "\"{$alias}_Versions\".\"RecordID\" = \"{$baseTable}_Versions\".\"RecordID\""
- . " AND \"{$alias}_Versions\".\"Version\" = \"{$baseTable}_Versions\".\"Version\""
- );
- }
- $query->renameTable($alias, $alias . '_Versions');
- }
-
- // Add all _Versions columns
- foreach (Config::inst()->get(static::class, 'db_for_versions_table') as $name => $type) {
- $query->selectField(sprintf('"%s_Versions"."%s"', $baseTable, $name), $name);
- }
-
- // Alias the record ID as the row ID, and ensure ID filters are aliased correctly
- $query->selectField("\"{$baseTable}_Versions\".\"RecordID\"", "ID");
- $query->replaceText("\"{$baseTable}_Versions\".\"ID\"", "\"{$baseTable}_Versions\".\"RecordID\"");
-
- // However, if doing count, undo rewrite of "ID" column
- $query->replaceText(
- "count(DISTINCT \"{$baseTable}_Versions\".\"RecordID\")",
- "count(DISTINCT \"{$baseTable}_Versions\".\"ID\")"
- );
-
- // Add additional versioning filters
- switch ($versionedMode) {
- case 'archive': {
- $date = $dataQuery->getQueryParam('Versioned.date');
- if (!$date) {
- throw new InvalidArgumentException("Invalid archive date");
- }
- // Link to the version archived on that date
- $query->addWhere([
- "\"{$baseTable}_Versions\".\"Version\" IN
- (SELECT LatestVersion FROM
- (SELECT
- \"{$baseTable}_Versions\".\"RecordID\",
- MAX(\"{$baseTable}_Versions\".\"Version\") AS LatestVersion
- FROM \"{$baseTable}_Versions\"
- WHERE \"{$baseTable}_Versions\".\"LastEdited\" <= ?
- GROUP BY \"{$baseTable}_Versions\".\"RecordID\"
- ) AS \"{$baseTable}_Versions_Latest\"
- WHERE \"{$baseTable}_Versions_Latest\".\"RecordID\" = \"{$baseTable}_Versions\".\"RecordID\"
- )" => $date
- ]);
- break;
- }
- case 'latest_versions': {
- // Return latest version instances, regardless of whether they are on a particular stage
- // This provides "show all, including deleted" functonality
- $query->addWhere(
- "\"{$baseTable}_Versions\".\"Version\" IN
- (SELECT LatestVersion FROM
- (SELECT
- \"{$baseTable}_Versions\".\"RecordID\",
- MAX(\"{$baseTable}_Versions\".\"Version\") AS LatestVersion
- FROM \"{$baseTable}_Versions\"
- GROUP BY \"{$baseTable}_Versions\".\"RecordID\"
- ) AS \"{$baseTable}_Versions_Latest\"
- WHERE \"{$baseTable}_Versions_Latest\".\"RecordID\" = \"{$baseTable}_Versions\".\"RecordID\"
- )"
- );
- break;
- }
- case 'version': {
- // If selecting a specific version, filter it here
- $version = $dataQuery->getQueryParam('Versioned.version');
- if (!$version) {
- throw new InvalidArgumentException("Invalid version");
- }
- $query->addWhere([
- "\"{$baseTable}_Versions\".\"Version\"" => $version
- ]);
- break;
- }
- case 'all_versions':
- default: {
- // If all versions are requested, ensure that records are sorted by this field
- $query->addOrderBy(sprintf('"%s_Versions"."%s"', $baseTable, 'Version'));
- break;
- }
- }
- break;
- default:
- throw new InvalidArgumentException("Bad value for query parameter Versioned.mode: "
- . $dataQuery->getQueryParam('Versioned.mode'));
- }
- }
-
- /**
- * Determine if the given versioned table is a part of the sub-tree of the current dataobject
- * This helps prevent rewriting of other tables that get joined in, in particular, many_many tables
- *
- * @param string $table
- * @return bool True if this table should be versioned
- */
- protected function isTableVersioned($table)
- {
- $schema = DataObject::getSchema();
- $tableClass = $schema->tableClass($table);
- if (empty($tableClass)) {
- return false;
- }
-
- // Check that this class belongs to the same tree
- $baseClass = $schema->baseDataClass($this->owner);
- if (!is_a($tableClass, $baseClass, true)) {
- return false;
- }
-
- // Check that this isn't a derived table
- // (e.g. _Live, or a many_many table)
- $mainTable = $schema->tableName($tableClass);
- if ($mainTable !== $table) {
- return false;
- }
-
- return true;
- }
-
- /**
- * For lazy loaded fields requiring extra sql manipulation, ie versioning.
- *
- * @param SQLSelect $query
- * @param DataQuery $dataQuery
- * @param DataObject $dataObject
- */
- public function augmentLoadLazyFields(SQLSelect &$query, DataQuery &$dataQuery = null, $dataObject)
- {
- // The VersionedMode local variable ensures that this decorator only applies to
- // queries that have originated from the Versioned object, and have the Versioned
- // metadata set on the query object. This prevents regular queries from
- // accidentally querying the *_Versions tables.
- $versionedMode = $dataObject->getSourceQueryParam('Versioned.mode');
- $modesToAllowVersioning = array('all_versions', 'latest_versions', 'archive', 'version');
- if (!empty($dataObject->Version) &&
- (!empty($versionedMode) && in_array($versionedMode, $modesToAllowVersioning))
- ) {
- // This will ensure that augmentSQL will select only the same version as the owner,
- // regardless of how this object was initially selected
- $versionColumn = $this->owner->getSchema()->sqlColumnForField($this->owner, 'Version');
- $dataQuery->where([
- $versionColumn => $dataObject->Version
- ]);
- $dataQuery->setQueryParam('Versioned.mode', 'all_versions');
- }
- }
-
- public function augmentDatabase()
- {
- $owner = $this->owner;
- $class = get_class($owner);
- $schema = $owner->getSchema();
- $baseTable = $this->baseTable();
- $classTable = $schema->tableName($owner);
-
- $isRootClass = $class === $owner->baseClass();
-
- // Build a list of suffixes whose tables need versioning
- $allSuffixes = array();
- $versionableExtensions = $owner->config()->get('versionableExtensions');
- if (count($versionableExtensions)) {
- foreach ($versionableExtensions as $versionableExtension => $suffixes) {
- if ($owner->hasExtension($versionableExtension)) {
- foreach ((array)$suffixes as $suffix) {
- $allSuffixes[$suffix] = $versionableExtension;
- }
- }
- }
- }
-
- // Add the default table with an empty suffix to the list (table name = class name)
- $allSuffixes[''] = null;
-
- foreach ($allSuffixes as $suffix => $extension) {
- // Check tables for this build
- if ($suffix) {
- $suffixBaseTable = "{$baseTable}_{$suffix}";
- $suffixTable = "{$classTable}_{$suffix}";
- } else {
- $suffixBaseTable = $baseTable;
- $suffixTable = $classTable;
- }
-
- $fields = $schema->databaseFields($class, false);
- unset($fields['ID']);
- if ($fields) {
- $options = Config::inst()->get($class, 'create_table_options');
- $indexes = $owner->databaseIndexes();
- $extensionClass = $allSuffixes[$suffix];
- if ($suffix && ($extension = $owner->getExtensionInstance($extensionClass))) {
- if (!$extension instanceof VersionableExtension) {
- throw new LogicException(
- "Extension {$extensionClass} must implement VersionableExtension"
- );
- }
- // Allow versionable extension to customise table fields and indexes
- $extension->setOwner($owner);
- if ($extension->isVersionedTable($suffixTable)) {
- $extension->updateVersionableFields($suffix, $fields, $indexes);
- }
- $extension->clearOwner();
- }
-
- // Build _Live table
- if ($this->hasStages()) {
- $liveTable = $this->stageTable($suffixTable, static::LIVE);
- DB::require_table($liveTable, $fields, $indexes, false, $options);
- }
-
- // Build _Versions table
- //Unique indexes will not work on versioned tables, so we'll convert them to standard indexes:
- $nonUniqueIndexes = $this->uniqueToIndex($indexes);
- if ($isRootClass) {
- // Create table for all versions
- $versionFields = array_merge(
- Config::inst()->get(static::class, 'db_for_versions_table'),
- (array)$fields
- );
- $versionIndexes = array_merge(
- Config::inst()->get(static::class, 'indexes_for_versions_table'),
- (array)$nonUniqueIndexes
- );
- } else {
- // Create fields for any tables of subclasses
- $versionFields = array_merge(
- array(
- "RecordID" => "Int",
- "Version" => "Int",
- ),
- (array)$fields
- );
- $versionIndexes = array_merge(
- array(
- 'RecordID_Version' => array('type' => 'unique', 'value' => '"RecordID","Version"'),
- 'RecordID' => true,
- 'Version' => true,
- ),
- (array)$nonUniqueIndexes
- );
- }
-
- // Cleanup any orphans
- $this->cleanupVersionedOrphans("{$suffixBaseTable}_Versions", "{$suffixTable}_Versions");
-
- // Build versions table
- DB::require_table("{$suffixTable}_Versions", $versionFields, $versionIndexes, true, $options);
- } else {
- DB::dont_require_table("{$suffixTable}_Versions");
- if ($this->hasStages()) {
- $liveTable = $this->stageTable($suffixTable, static::LIVE);
- DB::dont_require_table($liveTable);
- }
- }
- }
- }
-
- /**
- * Cleanup orphaned records in the _Versions table
- *
- * @param string $baseTable base table to use as authoritative source of records
- * @param string $childTable Sub-table to clean orphans from
- */
- protected function cleanupVersionedOrphans($baseTable, $childTable)
- {
- // Skip if child table doesn't exist
- if (!DB::get_schema()->hasTable($childTable)) {
- return;
- }
- // Skip if tables are the same
- if ($childTable === $baseTable) {
- return;
- }
-
- // Select all orphaned version records
- $orphanedQuery = SQLSelect::create()
- ->selectField("\"{$childTable}\".\"ID\"")
- ->setFrom("\"{$childTable}\"");
-
- // If we have a parent table limit orphaned records
- // to only those that exist in this
- if (DB::get_schema()->hasTable($baseTable)) {
- $orphanedQuery
- ->addLeftJoin(
- $baseTable,
- "\"{$childTable}\".\"RecordID\" = \"{$baseTable}\".\"RecordID\"
- AND \"{$childTable}\".\"Version\" = \"{$baseTable}\".\"Version\""
- )
- ->addWhere("\"{$baseTable}\".\"ID\" IS NULL");
- }
-
- $count = $orphanedQuery->count();
- if ($count > 0) {
- DB::alteration_message("Removing {$count} orphaned versioned records", "deleted");
- $ids = $orphanedQuery->execute()->column();
- foreach ($ids as $id) {
- DB::prepared_query("DELETE FROM \"{$childTable}\" WHERE \"ID\" = ?", array($id));
- }
- }
- }
-
- /**
- * Helper for augmentDatabase() to find unique indexes and convert them to non-unique
- *
- * @param array $indexes The indexes to convert
- * @return array $indexes
- */
- private function uniqueToIndex($indexes)
- {
- $unique_regex = '/unique/i';
- $results = array();
- foreach ($indexes as $key => $index) {
- $results[$key] = $index;
-
- // support string descriptors
- if (is_string($index)) {
- if (preg_match($unique_regex, $index)) {
- $results[$key] = preg_replace($unique_regex, 'index', $index);
- }
- } // canonical, array-based descriptors
- elseif (is_array($index)) {
- if (strtolower($index['type']) == 'unique') {
- $results[$key]['type'] = 'index';
- }
- }
- }
- return $results;
- }
-
- /**
- * Generates a ($table)_version DB manipulation and injects it into the current $manipulation
- *
- * @param array $manipulation Source manipulation data
- * @param string $class Class
- * @param string $table Table Table for this class
- * @param int $recordID ID of record to version
- */
- protected function augmentWriteVersioned(&$manipulation, $class, $table, $recordID)
- {
- $schema = DataObject::getSchema();
- $baseDataClass = $schema->baseDataClass($class);
- $baseDataTable = $schema->tableName($baseDataClass);
-
- // Set up a new entry in (table)_Versions
- $newManipulation = array(
- "command" => "insert",
- "fields" => isset($manipulation[$table]['fields']) ? $manipulation[$table]['fields'] : [],
- "class" => $class,
- );
-
- // Add any extra, unchanged fields to the version record.
- $data = DB::prepared_query("SELECT * FROM \"{$table}\" WHERE \"ID\" = ?", array($recordID))->record();
-
- if ($data) {
- $fields = $schema->databaseFields($class, false);
- if (is_array($fields)) {
- $data = array_intersect_key($data, $fields);
-
- foreach ($data as $k => $v) {
- // If the value is not set at all in the manipulation currently, use the existing value from the database
- if (!array_key_exists($k, $newManipulation['fields'])) {
- $newManipulation['fields'][$k] = $v;
- }
- }
- }
- }
-
- // Ensure that the ID is instead written to the RecordID field
- $newManipulation['fields']['RecordID'] = $recordID;
- unset($newManipulation['fields']['ID']);
-
- // Generate next version ID to use
- $nextVersion = 0;
- if ($recordID) {
- $nextVersion = DB::prepared_query(
- "SELECT MAX(\"Version\") + 1
- FROM \"{$baseDataTable}_Versions\" WHERE \"RecordID\" = ?",
- array($recordID)
- )->value();
- }
- $nextVersion = $nextVersion ?: 1;
-
- if ($class === $baseDataClass) {
- // Write AuthorID for baseclass
- $userID = (Member::currentUser()) ? Member::currentUser()->ID : 0;
- $newManipulation['fields']['AuthorID'] = $userID;
-
- // Update main table version if not previously known
- $manipulation[$table]['fields']['Version'] = $nextVersion;
- }
-
- // Update _Versions table manipulation
- $newManipulation['fields']['Version'] = $nextVersion;
- $manipulation["{$table}_Versions"] = $newManipulation;
- }
-
- /**
- * Rewrite the given manipulation to update the selected (non-default) stage
- *
- * @param array $manipulation Source manipulation data
- * @param string $table Name of table
- * @param int $recordID ID of record to version
- */
- protected function augmentWriteStaged(&$manipulation, $table, $recordID)
- {
- // If the record has already been inserted in the (table), get rid of it.
- if ($manipulation[$table]['command'] == 'insert') {
- DB::prepared_query(
- "DELETE FROM \"{$table}\" WHERE \"ID\" = ?",
- array($recordID)
- );
- }
-
- $newTable = $this->stageTable($table, Versioned::get_stage());
- $manipulation[$newTable] = $manipulation[$table];
- }
-
-
- public function augmentWrite(&$manipulation)
- {
- // get Version number from base data table on write
- $version = null;
- $owner = $this->owner;
- $baseDataTable = DataObject::getSchema()->baseDataTable($owner);
- if (isset($manipulation[$baseDataTable]['fields'])) {
- if ($this->migratingVersion) {
- $manipulation[$baseDataTable]['fields']['Version'] = $this->migratingVersion;
- }
- if (isset($manipulation[$baseDataTable]['fields']['Version'])) {
- $version = $manipulation[$baseDataTable]['fields']['Version'];
- }
- }
-
- // Update all tables
- $tables = array_keys($manipulation);
- foreach ($tables as $table) {
- // Make sure that the augmented write is being applied to a table that can be versioned
- $class = isset($manipulation[$table]['class']) ? $manipulation[$table]['class'] : null;
- if (!$class || !$this->canBeVersioned($class)) {
- unset($manipulation[$table]);
- continue;
- }
-
- // Get ID field
- $id = $manipulation[$table]['id']
- ? $manipulation[$table]['id']
- : $manipulation[$table]['fields']['ID'];
- if (!$id) {
- user_error("Couldn't find ID in " . var_export($manipulation[$table], true), E_USER_ERROR);
- }
-
- if ($version < 0 || $this->_nextWriteWithoutVersion) {
- // Putting a Version of -1 is a signal to leave the version table alone, despite their being no version
- unset($manipulation[$table]['fields']['Version']);
- } elseif (empty($version)) {
- // If we haven't got a version #, then we're creating a new version.
- // Otherwise, we're just copying a version to another table
- $this->augmentWriteVersioned($manipulation, $class, $table, $id);
- }
-
- // Remove "Version" column from subclasses of baseDataClass
- if (!$this->hasVersionField($table)) {
- unset($manipulation[$table]['fields']['Version']);
- }
-
- // Grab a version number - it should be the same across all tables.
- if (isset($manipulation[$table]['fields']['Version'])) {
- $thisVersion = $manipulation[$table]['fields']['Version'];
- }
-
- // If we're editing Live, then write to (table)_Live as well as (table)
- if ($this->hasStages() && static::get_stage() === static::LIVE) {
- $this->augmentWriteStaged($manipulation, $table, $id);
- }
- }
-
- // Clear the migration flag
- if ($this->migratingVersion) {
- $this->migrateVersion(null);
- }
-
- // Add the new version # back into the data object, for accessing
- // after this write
- if (isset($thisVersion)) {
- $owner->Version = str_replace("'", "", $thisVersion);
- }
- }
-
- /**
- * Perform a write without affecting the version table.
- * On objects without versioning.
- *
- * @return int The ID of the record
- */
- public function writeWithoutVersion()
- {
- $this->_nextWriteWithoutVersion = true;
-
- return $this->owner->write();
- }
-
- /**
- *
- */
- public function onAfterWrite()
- {
- $this->_nextWriteWithoutVersion = false;
- }
-
- /**
- * If a write was skipped, then we need to ensure that we don't leave a
- * migrateVersion() value lying around for the next write.
- */
- public function onAfterSkippedWrite()
- {
- $this->migrateVersion(null);
- }
-
- /**
- * Find all objects owned by the current object.
- * Note that objects will only be searched in the same stage as the given record.
- *
- * @param bool $recursive True if recursive
- * @param ArrayList $list Optional list to add items to
- * @return ArrayList list of objects
- */
- public function findOwned($recursive = true, $list = null)
- {
- // Find objects in these relationships
- return $this->findRelatedObjects('owns', $recursive, $list);
- }
-
- /**
- * Find objects which own this object.
- * Note that objects will only be searched in the same stage as the given record.
- *
- * @param bool $recursive True if recursive
- * @param ArrayList $list Optional list to add items to
- * @return ArrayList list of objects
- */
- public function findOwners($recursive = true, $list = null)
- {
- if (!$list) {
- $list = new ArrayList();
- }
-
- // Build reverse lookup for ownership
- // @todo - Cache this more intelligently
- $rules = $this->lookupReverseOwners();
-
- // Hand off to recursive method
- return $this->findOwnersRecursive($recursive, $list, $rules);
- }
-
- /**
- * Find objects which own this object.
- * Note that objects will only be searched in the same stage as the given record.
- *
- * @param bool $recursive True if recursive
- * @param ArrayList $list List to add items to
- * @param array $lookup List of reverse lookup rules for owned objects
- * @return ArrayList list of objects
- */
- public function findOwnersRecursive($recursive, $list, $lookup)
- {
- // First pass: find objects that are explicitly owned_by (e.g. custom relationships)
- $owners = $this->findRelatedObjects('owned_by', false);
-
- // Second pass: Find owners via reverse lookup list
- foreach ($lookup as $ownedClass => $classLookups) {
- // Skip owners of other objects
- if (!is_a($this->owner, $ownedClass)) {
- continue;
- }
- foreach ($classLookups as $classLookup) {
- // Merge new owners into this object's owners
- $ownerClass = $classLookup['class'];
- $ownerRelation = $classLookup['relation'];
- $result = $this->owner->inferReciprocalComponent($ownerClass, $ownerRelation);
- $this->mergeRelatedObjects($owners, $result);
- }
- }
-
- // Merge all objects into the main list
- $newItems = $this->mergeRelatedObjects($list, $owners);
-
- // If recursing, iterate over all newly added items
- if ($recursive) {
- foreach ($newItems as $item) {
- /** @var Versioned|DataObject $item */
- $item->findOwnersRecursive(true, $list, $lookup);
- }
- }
-
- return $list;
- }
-
- /**
- * Find a list of classes, each of which with a list of methods to invoke
- * to lookup owners.
- *
- * @return array
- */
- protected function lookupReverseOwners()
- {
- // Find all classes with 'owns' config
- $lookup = array();
- foreach (ClassInfo::subclassesFor('SilverStripe\ORM\DataObject') as $class) {
- // Ensure this class is versioned
- if (!Object::has_extension($class, static::class)) {
- continue;
- }
-
- // Check owned objects for this class
- $owns = Config::inst()->get($class, 'owns', Config::UNINHERITED);
- if (empty($owns)) {
- continue;
- }
-
- $instance = DataObject::singleton($class);
- foreach ($owns as $owned) {
- // Find owned class
- $ownedClass = $instance->getRelationClass($owned);
- // Skip custom methods that don't have db relationsm
- if (!$ownedClass) {
- continue;
- }
- if ($ownedClass === DataObject::class) {
- throw new LogicException(sprintf(
- "Relation %s on class %s cannot be owned as it is polymorphic",
- $owned,
- $class
- ));
- }
-
- // Add lookup for owned class
- if (!isset($lookup[$ownedClass])) {
- $lookup[$ownedClass] = array();
- }
- $lookup[$ownedClass][] = [
- 'class' => $class,
- 'relation' => $owned
- ];
- }
- }
- return $lookup;
- }
-
-
- /**
- * Find objects in the given relationships, merging them into the given list
- *
- * @param array $source Config property to extract relationships from
- * @param bool $recursive True if recursive
- * @param ArrayList $list Optional list to add items to
- * @return ArrayList The list
- */
- public function findRelatedObjects($source, $recursive = true, $list = null)
- {
- if (!$list) {
- $list = new ArrayList();
- }
-
- // Skip search for unsaved records
- $owner = $this->owner;
- if (!$owner->isInDB()) {
- return $list;
- }
-
- $relationships = $owner->config()->get($source);
- foreach ($relationships as $relationship) {
- // Warn if invalid config
- if (!$owner->hasMethod($relationship)) {
- trigger_error(sprintf(
- "Invalid %s config value \"%s\" on object on class \"%s\"",
- $source,
- $relationship,
- $owner->class
- ), E_USER_WARNING);
- continue;
- }
-
- // Inspect value of this relationship
- $items = $owner->{$relationship}();
-
- // Merge any new item
- $newItems = $this->mergeRelatedObjects($list, $items);
-
- // Recurse if necessary
- if ($recursive) {
- foreach ($newItems as $item) {
- /** @var Versioned|DataObject $item */
- $item->findRelatedObjects($source, true, $list);
- }
- }
- }
- return $list;
- }
-
- /**
- * Helper method to merge owned/owning items into a list.
- * Items already present in the list will be skipped.
- *
- * @param ArrayList $list Items to merge into
- * @param mixed $items List of new items to merge
- * @return ArrayList List of all newly added items that did not already exist in $list
- */
- protected function mergeRelatedObjects($list, $items)
- {
- $added = new ArrayList();
- if (!$items) {
- return $added;
- }
- if ($items instanceof DataObject) {
- $items = array($items);
- }
-
- /** @var Versioned|DataObject $item */
- foreach ($items as $item) {
- $this->mergeRelatedObject($list, $added, $item);
- }
- return $added;
- }
-
- /**
- * This function should return true if the current user can publish this record.
- * It can be overloaded to customise the security model for an application.
- *
- * Denies permission if any of the following conditions is true:
- * - canPublish() on any extension returns false
- * - canEdit() returns false
- *
- * @param Member $member
- * @return bool True if the current user can publish this record.
- */
- public function canPublish($member = null)
- {
- // Skip if invoked by extendedCan()
- if (func_num_args() > 4) {
- return null;
- }
-
- if (!$member) {
- $member = Member::currentUser();
- }
-
- if (Permission::checkMember($member, "ADMIN")) {
- return true;
- }
-
- // Standard mechanism for accepting permission changes from extensions
- $owner = $this->owner;
- $extended = $owner->extendedCan('canPublish', $member);
- if ($extended !== null) {
- return $extended;
- }
-
- // Default to relying on edit permission
- return $owner->canEdit($member);
- }
-
- /**
- * Check if the current user can delete this record from live
- *
- * @param null $member
- * @return mixed
- */
- public function canUnpublish($member = null)
- {
- // Skip if invoked by extendedCan()
- if (func_num_args() > 4) {
- return null;
- }
-
- if (!$member) {
- $member = Member::currentUser();
- }
-
- if (Permission::checkMember($member, "ADMIN")) {
- return true;
- }
-
- // Standard mechanism for accepting permission changes from extensions
- $owner = $this->owner;
- $extended = $owner->extendedCan('canUnpublish', $member);
- if ($extended !== null) {
- return $extended;
- }
-
- // Default to relying on canPublish
- return $owner->canPublish($member);
- }
-
- /**
- * Check if the current user is allowed to archive this record.
- * If extended, ensure that both canDelete and canUnpublish are extended also
- *
- * @param Member $member
- * @return bool
- */
- public function canArchive($member = null)
- {
- // Skip if invoked by extendedCan()
- if (func_num_args() > 4) {
- return null;
- }
-
- if (!$member) {
- $member = Member::currentUser();
- }
-
- // Standard mechanism for accepting permission changes from extensions
- $owner = $this->owner;
- $extended = $owner->extendedCan('canArchive', $member);
- if ($extended !== null) {
- return $extended;
- }
-
- // Admin permissions allow
- if (Permission::checkMember($member, "ADMIN")) {
- return true;
- }
-
- // Check if this record can be deleted from stage
- if (!$owner->canDelete($member)) {
- return false;
- }
-
- // Check if we can delete from live
- if (!$owner->canUnpublish($member)) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Check if the user can revert this record to live
- *
- * @param Member $member
- * @return bool
- */
- public function canRevertToLive($member = null)
- {
- $owner = $this->owner;
-
- // Skip if invoked by extendedCan()
- if (func_num_args() > 4) {
- return null;
- }
-
- // Can't revert if not on live
- if (!$owner->isPublished()) {
- return false;
- }
-
- if (!$member) {
- $member = Member::currentUser();
- }
-
- if (Permission::checkMember($member, "ADMIN")) {
- return true;
- }
-
- // Standard mechanism for accepting permission changes from extensions
- $extended = $owner->extendedCan('canRevertToLive', $member);
- if ($extended !== null) {
- return $extended;
- }
-
- // Default to canEdit
- return $owner->canEdit($member);
- }
-
- /**
- * Extend permissions to include additional security for objects that are not published to live.
- *
- * @param Member $member
- * @return bool|null
- */
- public function canView($member = null)
- {
- // Invoke default version-gnostic canView
- if ($this->owner->canViewVersioned($member) === false) {
- return false;
- }
- return null;
- }
-
- /**
- * Determine if there are any additional restrictions on this object for the given reading version.
- *
- * Override this in a subclass to customise any additional effect that Versioned applies to canView.
- *
- * This is expected to be called by canView, and thus is only responsible for denying access if
- * the default canView would otherwise ALLOW access. Thus it should not be called in isolation
- * as an authoritative permission check.
- *
- * This has the following extension points:
- * - canViewDraft is invoked if Mode = stage and Stage = stage
- * - canViewArchived is invoked if Mode = archive
- *
- * @param Member $member
- * @return bool False is returned if the current viewing mode denies visibility
- */
- public function canViewVersioned($member = null)
- {
- // Bypass when live stage
- $owner = $this->owner;
- $mode = $owner->getSourceQueryParam("Versioned.mode");
- $stage = $owner->getSourceQueryParam("Versioned.stage");
- if ($mode === 'stage' && $stage === static::LIVE) {
- return true;
- }
-
- // Bypass if site is unsecured
- if (Session::get('unsecuredDraftSite')) {
- return true;
- }
-
- // Bypass if record doesn't have a live stage
- if (!$this->hasStages()) {
- return true;
- }
-
- // 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
- $latestVersion = Versioned::get_versionnumber_by_stage($owner->class, static::LIVE, $owner->ID);
- if ($latestVersion == $owner->Version) {
- // Even if this is loaded from a non-live stage, this is the live version
- return true;
- }
-
- // Extend versioned behaviour
- $extended = $owner->extendedCan('canViewNonLive', $member);
- if ($extended !== null) {
- return (bool)$extended;
- }
-
- // Fall back to default permission check
- $permissions = Config::inst()->get($owner->class, 'non_live_permissions');
- $check = Permission::checkMember($member, $permissions);
- return (bool)$check;
- }
-
- /**
- * Determines canView permissions for the latest version of this object on a specific stage.
- * Usually the stage is read from {@link Versioned::current_stage()}.
- *
- * This method should be invoked by user code to check if a record is visible in the given stage.
- *
- * This method should not be called via ->extend('canViewStage'), but rather should be
- * overridden in the extended class.
- *
- * @param string $stage
- * @param Member $member
- * @return bool
- */
- public function canViewStage($stage = 'Live', $member = null)
- {
- $oldMode = Versioned::get_reading_mode();
- Versioned::set_stage($stage);
-
- $owner = $this->owner;
- $versionFromStage = DataObject::get($owner->class)->byID($owner->ID);
-
- Versioned::set_reading_mode($oldMode);
- return $versionFromStage ? $versionFromStage->canView($member) : false;
- }
-
- /**
- * Determine if a class is supporting the Versioned extensions (e.g.
- * $table_Versions does exists).
- *
- * @param string $class Class name
- * @return boolean
- */
- public function canBeVersioned($class)
- {
- return ClassInfo::exists($class)
- && is_subclass_of($class, DataObject::class)
- && DataObject::getSchema()->classHasTable($class);
- }
-
- /**
- * Check if a certain table has the 'Version' field.
- *
- * @param string $table Table name
- *
- * @return boolean Returns false if the field isn't in the table, true otherwise
- */
- public function hasVersionField($table)
- {
- // Base table has version field
- $class = DataObject::getSchema()->tableClass($table);
- return $class === DataObject::getSchema()->baseDataClass($class);
- }
-
- /**
- * @param string $table
- *
- * @return string
- */
- public function extendWithSuffix($table)
- {
- $owner = $this->owner;
- $versionableExtensions = $owner->config()->get('versionableExtensions');
-
- if (count($versionableExtensions)) {
- foreach ($versionableExtensions as $versionableExtension => $suffixes) {
- if ($owner->hasExtension($versionableExtension)) {
- $ext = $owner->getExtensionInstance($versionableExtension);
- $ext->setOwner($owner);
- $table = $ext->extendWithSuffix($table);
- $ext->clearOwner();
- }
- }
- }
-
- return $table;
- }
-
- /**
- * Determines if the current draft version is the same as live or rather, that there are no outstanding draft changes
- *
- * @return bool
- */
- public function latestPublished()
- {
- // Get the root data object class - this will have the version field
- $owner = $this->owner;
- $draftTable = $this->baseTable();
- $liveTable = $this->stageTable($draftTable, static::LIVE);
-
- return DB::prepared_query(
- "SELECT \"$draftTable\".\"Version\" = \"$liveTable\".\"Version\" FROM \"$draftTable\"
- INNER JOIN \"$liveTable\" ON \"$draftTable\".\"ID\" = \"$liveTable\".\"ID\"
- WHERE \"$draftTable\".\"ID\" = ?",
- array($owner->ID)
- )->value();
- }
-
- /**
- * @deprecated 4.0..5.0
- */
- public function doPublish()
- {
- Deprecation::notice('5.0', 'Use publishRecursive instead');
- return $this->owner->publishRecursive();
- }
-
- /**
- * Publish this object and all owned objects to Live
- *
- * @return bool
- */
- public function publishRecursive()
- {
- // 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();
- }
-
- /**
- * Publishes this object to Live, but doesn't publish owned objects.
- *
- * @return bool True if publish was successful
- */
- public function publishSingle()
- {
- $owner = $this->owner;
- if (!$owner->canPublish()) {
- return false;
- }
-
- $owner->invokeWithExtensions('onBeforePublish');
- $owner->write();
- $owner->copyVersionToStage(static::DRAFT, static::LIVE);
- $owner->invokeWithExtensions('onAfterPublish');
- return true;
- }
-
- /**
- * Set foreign keys of has_many objects to 0 where those objects were
- * disowned as a result of a partial publish / unpublish.
- * I.e. this object and its owned objects were recently written to $targetStage,
- * but deleted objects were not.
- *
- * Note that this operation does not create any new Versions
- *
- * @param string $sourceStage Objects in this stage will not be unlinked.
- * @param string $targetStage Objects which exist in this stage but not $sourceStage
- * will be unlinked.
- */
- public function unlinkDisownedObjects($sourceStage, $targetStage)
- {
- $owner = $this->owner;
-
- // after publishing, objects which used to be owned need to be
- // dis-connected from this object (set ForeignKeyID = 0)
- $owns = $owner->config()->get('owns');
- $hasMany = $owner->config()->get('has_many');
- if (empty($owns) || empty($hasMany)) {
- return;
- }
-
- $schema = DataObject::getSchema();
- $ownedHasMany = array_intersect($owns, array_keys($hasMany));
- foreach ($ownedHasMany as $relationship) {
- // Find metadata on relationship
- $joinClass = $schema->hasManyComponent(get_class($owner), $relationship);
- $joinField = $schema->getRemoteJoinField(get_class($owner), $relationship, 'has_many', $polymorphic);
- $idField = $polymorphic ? "{$joinField}ID" : $joinField;
- $joinTable = DataObject::getSchema()->tableForField($joinClass, $idField);
-
- // Generate update query which will unlink disowned objects
- $targetTable = $this->stageTable($joinTable, $targetStage);
- $disowned = new SQLUpdate("\"{$targetTable}\"");
- $disowned->assign("\"{$idField}\"", 0);
- $disowned->addWhere(array(
- "\"{$targetTable}\".\"{$idField}\"" => $owner->ID
- ));
-
- // Build exclusion list (items to owned objects we need to keep)
- $sourceTable = $this->stageTable($joinTable, $sourceStage);
- $owned = new SQLSelect("\"{$sourceTable}\".\"ID\"", "\"{$sourceTable}\"");
- $owned->addWhere(array(
- "\"{$sourceTable}\".\"{$idField}\"" => $owner->ID
- ));
-
- // Apply class condition if querying on polymorphic has_one
- if ($polymorphic) {
- $disowned->assign("\"{$joinField}Class\"", null);
- $disowned->addWhere(array(
- "\"{$targetTable}\".\"{$joinField}Class\"" => get_class($owner)
- ));
- $owned->addWhere(array(
- "\"{$sourceTable}\".\"{$joinField}Class\"" => get_class($owner)
- ));
- }
-
- // Merge queries and perform unlink
- $ownedSQL = $owned->sql($ownedParams);
- $disowned->addWhere(array(
- "\"{$targetTable}\".\"ID\" NOT IN ({$ownedSQL})" => $ownedParams
- ));
-
- $owner->extend('updateDisownershipQuery', $disowned, $sourceStage, $targetStage, $relationship);
-
- $disowned->execute();
- }
- }
-
- /**
- * Removes the record from both live and stage
- *
- * @return bool Success
- */
- public function doArchive()
- {
- $owner = $this->owner;
- if (!$owner->canArchive()) {
- return false;
- }
-
- $owner->invokeWithExtensions('onBeforeArchive', $this);
- $owner->doUnpublish();
- $owner->deleteFromStage(static::DRAFT);
- $owner->invokeWithExtensions('onAfterArchive', $this);
-
- return true;
- }
-
- /**
- * Removes this record from the live site
- *
- * @return bool Flag whether the unpublish was successful
- */
- public function doUnpublish()
- {
- $owner = $this->owner;
- if (!$owner->canUnpublish()) {
- return false;
- }
-
- // Skip if this record isn't saved
- if (!$owner->isInDB()) {
- return false;
- }
-
- // Skip if this record isn't on live
- if (!$owner->isPublished()) {
- return false;
- }
-
- $owner->invokeWithExtensions('onBeforeUnpublish');
-
- $origReadingMode = static::get_reading_mode();
- static::set_stage(static::LIVE);
-
- // This way our ID won't be unset
- $clone = clone $owner;
- $clone->delete();
-
- static::set_reading_mode($origReadingMode);
-
- $owner->invokeWithExtensions('onAfterUnpublish');
- return true;
- }
-
- /**
- * Trigger unpublish of owning objects
- */
- public function onAfterUnpublish()
- {
- $owner = $this->owner;
-
- // Any objects which owned (and thus relied on the unpublished object) are now unpublished automatically.
- foreach ($owner->findOwners(false) as $object) {
- /** @var Versioned|DataObject $object */
- $object->doUnpublish();
- }
- }
-
-
- /**
- * Revert the draft changes: replace the draft content with the content on live
- *
- * @return bool True if the revert was successful
- */
- public function doRevertToLive()
- {
- $owner = $this->owner;
- if (!$owner->canRevertToLive()) {
- return false;
- }
-
- $owner->invokeWithExtensions('onBeforeRevertToLive');
- $owner->copyVersionToStage(static::LIVE, static::DRAFT, false);
- $owner->invokeWithExtensions('onAfterRevertToLive');
- return true;
- }
-
- /**
- * Trigger revert of all owned objects to stage
- */
- public function onAfterRevertToLive()
- {
- $owner = $this->owner;
- /** @var Versioned|DataObject $liveOwner */
- $liveOwner = static::get_by_stage(get_class($owner), static::LIVE)
- ->byID($owner->ID);
-
- // Revert any owned objects from the live stage only
- foreach ($liveOwner->findOwned(false) as $object) {
- /** @var Versioned|DataObject $object */
- $object->doRevertToLive();
- }
-
- // 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 draft record
- $owner->unlinkDisownedObjects(Versioned::LIVE, Versioned::DRAFT);
- }
-
- /**
- * @deprecated 4.0..5.0
- */
- public function publish($fromStage, $toStage, $createNewVersion = false)
- {
- Deprecation::notice('5.0', 'Use copyVersionToStage instead');
- $this->owner->copyVersionToStage($fromStage, $toStage, $createNewVersion);
- }
-
- /**
- * Move a database record from one stage to the other.
- *
- * @param int|string $fromStage Place to copy from. Can be either a stage name or a version number.
- * @param string $toStage Place to copy to. Must be a stage name.
- * @param bool $createNewVersion Set this to true to create a new version number.
- * By default, the existing version number will be copied over. Note if copying
- * to the live stage, the draft stage will also be updated with the new version.
- */
- public function copyVersionToStage($fromStage, $toStage, $createNewVersion = false)
- {
- $owner = $this->owner;
- $owner->invokeWithExtensions('onBeforeVersionedPublish', $fromStage, $toStage, $createNewVersion);
-
- $baseClass = $owner->baseClass();
- $baseTable = $owner->baseTable();
-
- /** @var Versioned|DataObject $from */
- if (is_numeric($fromStage)) {
- $from = Versioned::get_version($baseClass, $owner->ID, $fromStage);
- } else {
- $owner->flushCache();
- $from = Versioned::get_one_by_stage($baseClass, $fromStage, array(
- "\"{$baseTable}\".\"ID\" = ?" => $owner->ID
- ));
- }
- if (!$from) {
- throw new InvalidArgumentException("Can't find {$baseClass}#{$owner->ID} in stage {$fromStage}");
- }
-
- $from->forceChange();
- if ($createNewVersion) {
- // Clear version to be automatically created on write
- $from->Version = null;
- } else {
- $from->migrateVersion($from->Version);
-
- // Mark this version as having been published at some stage
- $publisherID = isset(Member::currentUser()->ID) ? Member::currentUser()->ID : 0;
- $extTable = $this->extendWithSuffix($baseTable);
- DB::prepared_query(
- "UPDATE \"{$extTable}_Versions\"
- SET \"WasPublished\" = ?, \"PublisherID\" = ?
- WHERE \"RecordID\" = ? AND \"Version\" = ?",
- array(1, $publisherID, $from->ID, $from->Version)
- );
- }
-
- // Change to new stage, write, and revert state
- $oldMode = Versioned::get_reading_mode();
- Versioned::set_stage($toStage);
-
- // Migrate stage prior to write
- $from->setSourceQueryParam('Versioned.mode', 'stage');
- $from->setSourceQueryParam('Versioned.stage', $toStage);
-
- $conn = DB::get_conn();
- if (method_exists($conn, 'allowPrimaryKeyEditing')) {
- $conn->allowPrimaryKeyEditing($baseTable, true);
- $from->write();
- $conn->allowPrimaryKeyEditing($baseTable, false);
- } else {
- $from->write();
- }
-
- $from->destroy();
-
- Versioned::set_reading_mode($oldMode);
-
- $owner->invokeWithExtensions('onAfterVersionedPublish', $fromStage, $toStage, $createNewVersion);
- }
-
- /**
- * Set the migrating version.
- *
- * @param string $version The version.
- */
- public function migrateVersion($version)
- {
- $this->migratingVersion = $version;
- }
-
- /**
- * Compare two stages to see if they're different.
- *
- * Only checks the version numbers, not the actual content.
- *
- * @param string $stage1 The first stage to check.
- * @param string $stage2
- * @return bool
- */
- public function stagesDiffer($stage1, $stage2)
- {
- $table1 = $this->baseTable($stage1);
- $table2 = $this->baseTable($stage2);
- $id = $this->owner->ID ?: $this->owner->OldID;
- if (!$id) {
- return true;
- }
-
- // We test for equality - if one of the versions doesn't exist, this
- // will be false.
-
- // TODO: DB Abstraction: if statement here:
- $stagesAreEqual = DB::prepared_query(
- "SELECT CASE WHEN \"$table1\".\"Version\"=\"$table2\".\"Version\" THEN 1 ELSE 0 END
- FROM \"$table1\" INNER JOIN \"$table2\" ON \"$table1\".\"ID\" = \"$table2\".\"ID\"
- AND \"$table1\".\"ID\" = ?",
- array($id)
- )->value();
-
- return !$stagesAreEqual;
- }
-
- /**
- * @param string $filter
- * @param string $sort
- * @param string $limit
- * @param string $join Deprecated, use leftJoin($table, $joinClause) instead
- * @param string $having
- * @return ArrayList
- */
- public function Versions($filter = "", $sort = "", $limit = "", $join = "", $having = "")
- {
- return $this->allVersions($filter, $sort, $limit, $join, $having);
- }
-
- /**
- * Return a list of all the versions available.
- *
- * @param string $filter
- * @param string $sort
- * @param string $limit
- * @param string $join @deprecated use leftJoin($table, $joinClause) instead
- * @param string $having @deprecated
- * @return ArrayList
- */
- public function allVersions($filter = "", $sort = "", $limit = "", $join = "", $having = "")
- {
- // Make sure the table names are not postfixed (e.g. _Live)
- $oldMode = static::get_reading_mode();
- static::set_stage(static::DRAFT);
-
- $owner = $this->owner;
- $list = DataObject::get(get_class($owner), $filter, $sort, $join, $limit);
- if ($having) {
- // @todo - This method doesn't exist on DataList
- $list->having($having);
- }
-
- $query = $list->dataQuery()->query();
-
- $baseTable = null;
- foreach ($query->getFrom() as $table => $tableJoin) {
- if (is_string($tableJoin) && $tableJoin[0] == '"') {
- $baseTable = str_replace('"', '', $tableJoin);
- } elseif (is_string($tableJoin) && substr($tableJoin, 0, 5) != 'INNER') {
- $query->setFrom(array(
- $table => "LEFT JOIN \"$table\" ON \"$table\".\"RecordID\"=\"{$baseTable}_Versions\".\"RecordID\""
- . " AND \"$table\".\"Version\" = \"{$baseTable}_Versions\".\"Version\""
- ));
- }
- $query->renameTable($table, $table . '_Versions');
- }
-
- // Add all _Versions columns
- foreach (Config::inst()->get(static::class, 'db_for_versions_table') as $name => $type) {
- $query->selectField(sprintf('"%s_Versions"."%s"', $baseTable, $name), $name);
- }
-
- $query->addWhere(array(
- "\"{$baseTable}_Versions\".\"RecordID\" = ?" => $owner->ID
- ));
- $query->setOrderBy(($sort) ? $sort
- : "\"{$baseTable}_Versions\".\"LastEdited\" DESC, \"{$baseTable}_Versions\".\"Version\" DESC");
-
- $records = $query->execute();
- $versions = new ArrayList();
-
- foreach ($records as $record) {
- $versions->push(new Versioned_Version($record));
- }
-
- Versioned::set_reading_mode($oldMode);
- return $versions;
- }
-
- /**
- * Compare two version, and return the diff between them.
- *
- * @param string $from The version to compare from.
- * @param string $to The version to compare to.
- *
- * @return DataObject
- */
- public function compareVersions($from, $to)
- {
- $owner = $this->owner;
- $fromRecord = Versioned::get_version($owner->class, $owner->ID, $from);
- $toRecord = Versioned::get_version($owner->class, $owner->ID, $to);
-
- $diff = new DataDifferencer($fromRecord, $toRecord);
-
- return $diff->diffedData();
- }
-
- /**
- * Return the base table - the class that directly extends DataObject.
- *
- * Protected so it doesn't conflict with DataObject::baseTable()
- *
- * @param string $stage
- * @return string
- */
- protected function baseTable($stage = null)
- {
- $baseTable = $this->owner->baseTable();
- return $this->stageTable($baseTable, $stage);
- }
-
- /**
- * Given a table and stage determine the table name.
- *
- * Note: Stages this asset does not exist in will default to the draft table.
- *
- * @param string $table Main table
- * @param string $stage
- * @return string Staged table name
- */
- public function stageTable($table, $stage)
- {
- if ($this->hasStages() && $stage === static::LIVE) {
- return "{$table}_{$stage}";
- }
- return $table;
- }
-
- //-----------------------------------------------------------------------------------------------//
-
-
- /**
- * Determine if the current user is able to set the given site stage / archive
- *
- * @param HTTPRequest $request
- * @return bool
- */
- public static function can_choose_site_stage($request)
- {
- // Request is allowed if stage isn't being modified
- if ((!$request->getVar('stage') || $request->getVar('stage') === static::LIVE)
- && !$request->getVar('archiveDate')
- ) {
- return true;
- }
-
- // Check permissions with member ID in session.
- $member = Member::currentUser();
- $permissions = Config::inst()->get(get_called_class(), 'non_live_permissions');
- return $member && Permission::checkMember($member, $permissions);
- }
-
- /**
- * Choose the stage the site is currently on.
- *
- * If $_GET['stage'] is set, then it will use that stage, and store it in
- * the session.
- *
- * if $_GET['archiveDate'] is set, it will use that date, and store it in
- * the session.
- *
- * If neither of these are set, it checks the session, otherwise the stage
- * is set to 'Live'.
- */
- public static function choose_site_stage()
- {
- // Check any pre-existing session mode
- $preexistingMode = Session::get('readingMode');
-
- // Determine the reading mode
- if (isset($_GET['stage'])) {
- $stage = ucfirst(strtolower($_GET['stage']));
- if (!in_array($stage, array(static::DRAFT, static::LIVE))) {
- $stage = static::LIVE;
- }
- $mode = 'Stage.' . $stage;
- } elseif (isset($_GET['archiveDate']) && strtotime($_GET['archiveDate'])) {
- $mode = 'Archive.' . $_GET['archiveDate'];
- } elseif ($preexistingMode) {
- $mode = $preexistingMode;
- } else {
- $mode = static::DEFAULT_MODE;
- }
-
- // Save reading mode
- Versioned::set_reading_mode($mode);
-
- // Try not to store the mode in the session if not needed
- if (($preexistingMode && $preexistingMode !== $mode)
- || (!$preexistingMode && $mode !== static::DEFAULT_MODE)
- ) {
- Session::set('readingMode', $mode);
- }
-
- if (!headers_sent() && !Director::is_cli()) {
- if (Versioned::get_stage() == 'Live') {
- // clear the cookie if it's set
- if (Cookie::get('bypassStaticCache')) {
- Cookie::force_expiry('bypassStaticCache', null, null, false, true /* httponly */);
- }
- } else {
- // set the cookie if it's cleared
- if (!Cookie::get('bypassStaticCache')) {
- Cookie::set('bypassStaticCache', '1', 0, null, null, false, true /* httponly */);
- }
- }
- }
- }
-
- /**
- * Set the current reading mode.
- *
- * @param string $mode
- */
- public static function set_reading_mode($mode)
- {
- self::$reading_mode = $mode;
- }
-
- /**
- * Get the current reading mode.
- *
- * @return string
- */
- public static function get_reading_mode()
- {
- return self::$reading_mode;
- }
-
- /**
- * Get the current reading stage.
- *
- * @return string
- */
- public static function get_stage()
- {
- $parts = explode('.', Versioned::get_reading_mode());
-
- if ($parts[0] == 'Stage') {
- return $parts[1];
- }
- return null;
- }
-
- /**
- * Get the current archive date.
- *
- * @return string
- */
- public static function current_archived_date()
- {
- $parts = explode('.', Versioned::get_reading_mode());
- if ($parts[0] == 'Archive') {
- return $parts[1];
- }
- return null;
- }
-
- /**
- * Set the reading stage.
- *
- * @param string $stage New reading stage.
- * @throws InvalidArgumentException
- */
- public static function set_stage($stage)
- {
- if (!in_array($stage, [static::LIVE, static::DRAFT])) {
- throw new \InvalidArgumentException("Invalid stage name \"{$stage}\"");
- }
- static::set_reading_mode('Stage.' . $stage);
- }
-
- /**
- * Set the reading archive date.
- *
- * @param string $date New reading archived date.
- */
- public static function reading_archived_date($date)
- {
- Versioned::set_reading_mode('Archive.' . $date);
- }
-
-
- /**
- * Get a singleton instance of a class in the given stage.
- *
- * @param string $class The name of the class.
- * @param string $stage The name of the stage.
- * @param string $filter A filter to be inserted into the WHERE clause.
- * @param boolean $cache Use caching.
- * @param string $sort A sort expression to be inserted into the ORDER BY clause.
- *
- * @return DataObject
- */
- public static function get_one_by_stage($class, $stage, $filter = '', $cache = true, $sort = '')
- {
- // TODO: No identity cache operating
- $items = static::get_by_stage($class, $stage, $filter, $sort, null, 1);
-
- return $items->first();
- }
-
- /**
- * Gets the current version number of a specific record.
- *
- * @param string $class
- * @param string $stage
- * @param int $id
- * @param boolean $cache
- *
- * @return int
- */
- public static function get_versionnumber_by_stage($class, $stage, $id, $cache = true)
- {
- $baseClass = DataObject::getSchema()->baseDataClass($class);
- $stageTable = DataObject::getSchema()->tableName($baseClass);
- if ($stage === static::LIVE) {
- $stageTable .= "_{$stage}";
- }
-
- // cached call
- if ($cache && isset(self::$cache_versionnumber[$baseClass][$stage][$id])) {
- return self::$cache_versionnumber[$baseClass][$stage][$id];
- }
-
- // get version as performance-optimized SQL query (gets called for each record in the sitetree)
- $version = DB::prepared_query(
- "SELECT \"Version\" FROM \"$stageTable\" WHERE \"ID\" = ?",
- array($id)
- )->value();
-
- // cache value (if required)
- if ($cache) {
- if (!isset(self::$cache_versionnumber[$baseClass])) {
- self::$cache_versionnumber[$baseClass] = array();
- }
-
- if (!isset(self::$cache_versionnumber[$baseClass][$stage])) {
- self::$cache_versionnumber[$baseClass][$stage] = array();
- }
-
- self::$cache_versionnumber[$baseClass][$stage][$id] = $version;
- }
-
- return $version;
- }
-
- /**
- * Pre-populate the cache for Versioned::get_versionnumber_by_stage() for
- * a list of record IDs, for more efficient database querying. If $idList
- * is null, then every record will be pre-cached.
- *
- * @param string $class
- * @param string $stage
- * @param array $idList
- */
- public static function prepopulate_versionnumber_cache($class, $stage, $idList = null)
- {
- if (!Config::inst()->get(static::class, 'prepopulate_versionnumber_cache')) {
- return;
- }
- $filter = "";
- $parameters = array();
- if ($idList) {
- // Validate the ID list
- foreach ($idList as $id) {
- if (!is_numeric($id)) {
- user_error(
- "Bad ID passed to Versioned::prepopulate_versionnumber_cache() in \$idList: " . $id,
- E_USER_ERROR
- );
- }
- }
- $filter = 'WHERE "ID" IN ('.DB::placeholders($idList).')';
- $parameters = $idList;
- }
-
- /** @var Versioned|DataObject $singleton */
- $singleton = DataObject::singleton($class);
- $baseClass = $singleton->baseClass();
- $baseTable = $singleton->baseTable();
- $stageTable = $singleton->stageTable($baseTable, $stage);
-
- $versions = DB::prepared_query("SELECT \"ID\", \"Version\" FROM \"$stageTable\" $filter", $parameters)->map();
-
- foreach ($versions as $id => $version) {
- self::$cache_versionnumber[$baseClass][$stage][$id] = $version;
- }
- }
-
- /**
- * Get a set of class instances by the given stage.
- *
- * @param string $class The name of the class.
- * @param string $stage The name of the stage.
- * @param string $filter A filter to be inserted into the WHERE clause.
- * @param string $sort A sort expression to be inserted into the ORDER BY clause.
- * @param string $join Deprecated, use leftJoin($table, $joinClause) instead
- * @param int $limit A limit on the number of records returned from the database.
- * @param string $containerClass The container class for the result set (default is DataList)
- *
- * @return DataList A modified DataList designated to the specified stage
- */
- public static function get_by_stage(
- $class,
- $stage,
- $filter = '',
- $sort = '',
- $join = '',
- $limit = null,
- $containerClass = 'SilverStripe\ORM\DataList'
- ) {
- $result = DataObject::get($class, $filter, $sort, $join, $limit, $containerClass);
- return $result->setDataQueryParam(array(
- 'Versioned.mode' => 'stage',
- 'Versioned.stage' => $stage
- ));
- }
-
- /**
- * Delete this record from the given stage
- *
- * @param string $stage
- */
- public function deleteFromStage($stage)
- {
- $oldMode = Versioned::get_reading_mode();
- Versioned::set_stage($stage);
- $owner = $this->owner;
- $clone = clone $owner;
- $clone->delete();
- Versioned::set_reading_mode($oldMode);
-
- // Fix the version number cache (in case you go delete from stage and then check ExistsOnLive)
- $baseClass = $owner->baseClass();
- self::$cache_versionnumber[$baseClass][$stage][$owner->ID] = null;
- }
-
- /**
- * Write the given record to the given stage.
- * Note: If writing to live, this will write to stage as well.
- *
- * @param string $stage
- * @param boolean $forceInsert
- * @return int The ID of the record
- */
- public function writeToStage($stage, $forceInsert = false)
- {
- $oldMode = Versioned::get_reading_mode();
- Versioned::set_stage($stage);
-
- $owner = $this->owner;
- $owner->forceChange();
- $result = $owner->write(false, $forceInsert);
- Versioned::set_reading_mode($oldMode);
-
- return $result;
- }
-
- /**
- * Roll the draft version of this record to match the published record.
- * Caution: Doesn't overwrite the object properties with the rolled back version.
- *
- * {@see doRevertToLive()} to reollback to live
- *
- * @param int $version Version number
- */
- public function doRollbackTo($version)
- {
- $owner = $this->owner;
- $owner->extend('onBeforeRollback', $version);
- $owner->copyVersionToStage($version, static::DRAFT, true);
- $owner->writeWithoutVersion();
- $owner->extend('onAfterRollback', $version);
- }
-
- public function onAfterRollback($version)
- {
- // Find record at this version
- $baseClass = DataObject::getSchema()->baseDataClass($this->owner);
- /** @var Versioned|DataObject $recordVersion */
- $recordVersion = static::get_version($baseClass, $this->owner->ID, $version);
-
- // Note that unlike other publishing actions, rollback is NOT recursive;
- // The owner collects all objects and writes them back using writeToStage();
- foreach ($recordVersion->findOwned() as $object) {
- /** @var Versioned|DataObject $object */
- $object->writeToStage(static::DRAFT);
- }
- }
-
- /**
- * Return the latest version of the given record.
- *
- * @param string $class
- * @param int $id
- * @return DataObject
- */
- public static function get_latest_version($class, $id)
- {
- $baseClass = DataObject::getSchema()->baseDataClass($class);
- $list = DataList::create($baseClass)
- ->setDataQueryParam("Versioned.mode", "latest_versions");
-
- return $list->byID($id);
- }
-
- /**
- * Returns whether the current record is the latest one.
- *
- * @todo Performance - could do this directly via SQL.
- *
- * @see get_latest_version()
- * @see latestPublished
- *
- * @return boolean
- */
- public function isLatestVersion()
- {
- $owner = $this->owner;
- if (!$owner->isInDB()) {
- return false;
- }
-
- $version = static::get_latest_version($owner->class, $owner->ID);
- return ($version->Version == $owner->Version);
- }
-
- /**
- * Check if this record exists on live
- *
- * @return bool
- */
- public function isPublished()
- {
- $id = $this->owner->ID ?: $this->owner->OldID;
- if (!$id) {
- return false;
- }
-
- // Non-staged objects are considered "published" if saved
- if (!$this->hasStages()) {
- return true;
- }
-
- $table = $this->baseTable(static::LIVE);
- $result = DB::prepared_query(
- "SELECT COUNT(*) FROM \"{$table}\" WHERE \"{$table}\".\"ID\" = ?",
- array($id)
- );
- return (bool)$result->value();
- }
-
- /**
- * Check if page doesn't exist on any stage, but used to be
- *
- * @return bool
- */
- public function isArchived()
- {
- $id = $this->owner->ID ?: $this->owner->OldID;
- return $id && !$this->isOnDraft() && !$this->isPublished();
- }
-
- /**
- * Check if this record exists on the draft stage
- *
- * @return bool
- */
- public function isOnDraft()
- {
- $id = $this->owner->ID ?: $this->owner->OldID;
- if (!$id) {
- return false;
- }
-
- $table = $this->baseTable();
- $result = DB::prepared_query(
- "SELECT COUNT(*) FROM \"{$table}\" WHERE \"{$table}\".\"ID\" = ?",
- array($id)
- );
- return (bool)$result->value();
- }
-
- /**
- * Compares current draft with live version, and returns true if no draft version of this page exists but the page
- * is still published (eg, after triggering "Delete from draft site" in the CMS).
- *
- * @return bool
- */
- public function isOnLiveOnly()
- {
- return $this->isPublished() && !$this->isOnDraft();
- }
-
- /**
- * Compares current draft with live version, and returns true if no live version exists, meaning the page was never
- * published.
- *
- * @return bool
- */
- public function isOnDraftOnly()
- {
- return $this->isOnDraft() && !$this->isPublished();
- }
-
- /**
- * Compares current draft with live version, and returns true if these versions differ, meaning there have been
- * unpublished changes to the draft site.
- *
- * @return bool
- */
- public function isModifiedOnDraft()
- {
- return $this->isOnDraft() && $this->stagesDiffer(Versioned::DRAFT, Versioned::LIVE);
- }
-
- /**
- * Return the equivalent of a DataList::create() call, querying the latest
- * version of each record stored in the (class)_Versions tables.
- *
- * In particular, this will query deleted records as well as active ones.
- *
- * @param string $class
- * @param string $filter
- * @param string $sort
- * @return DataList
- */
- public static function get_including_deleted($class, $filter = "", $sort = "")
- {
- $list = DataList::create($class)
- ->where($filter)
- ->sort($sort)
- ->setDataQueryParam("Versioned.mode", "latest_versions");
-
- return $list;
- }
-
- /**
- * Return the specific version of the given id.
- *
- * Caution: The record is retrieved as a DataObject, but saving back
- * modifications via write() will create a new version, rather than
- * modifying the existing one.
- *
- * @param string $class
- * @param int $id
- * @param int $version
- *
- * @return DataObject
- */
- public static function get_version($class, $id, $version)
- {
- $baseClass = DataObject::getSchema()->baseDataClass($class);
- $list = DataList::create($baseClass)
- ->setDataQueryParam([
- "Versioned.mode" => 'version',
- "Versioned.version" => $version
- ]);
-
- return $list->byID($id);
- }
-
- /**
- * Return a list of all versions for a given id.
- *
- * @param string $class
- * @param int $id
- *
- * @return DataList
- */
- public static function get_all_versions($class, $id)
- {
- $list = DataList::create($class)
- ->filter('ID', $id)
- ->setDataQueryParam('Versioned.mode', 'all_versions');
-
- return $list;
- }
-
- /**
- * @param array $labels
- */
- public function updateFieldLabels(&$labels)
- {
- $labels['Versions'] = _t('Versioned.has_many_Versions', 'Versions', 'Past Versions of this record');
- }
-
- /**
- * @param FieldList $fields
- */
- public function updateCMSFields(FieldList $fields)
- {
- // remove the version field from the CMS as this should be left
- // entirely up to the extension (not the cms user).
- $fields->removeByName('Version');
- }
-
- /**
- * Ensure version ID is reset to 0 on duplicate
- *
- * @param DataObject $source Record this was duplicated from
- * @param bool $doWrite
- */
- public function onBeforeDuplicate($source, $doWrite)
- {
- $this->owner->Version = 0;
- }
-
- public function flushCache()
- {
- self::$cache_versionnumber = array();
- }
-
- /**
- * Return a piece of text to keep DataObject cache keys appropriately specific.
- *
- * @return string
- */
- public function cacheKeyComponent()
- {
- return 'versionedmode-'.static::get_reading_mode();
- }
-
- /**
- * Returns an array of possible stages.
- *
- * @return array
- */
- public function getVersionedStages()
- {
- if ($this->hasStages()) {
- return [static::DRAFT, static::LIVE];
- } else {
- return [static::DRAFT];
- }
- }
-
- public static function get_template_global_variables()
- {
- return array(
- 'CurrentReadingMode' => 'get_reading_mode'
- );
- }
-
- /**
- * Check if this object has stages
- *
- * @return bool True if this object is staged
- */
- public function hasStages()
- {
- return $this->mode === static::STAGEDVERSIONED;
- }
-
- /**
- * Merge single object into a list
- *
- * @param ArrayList $list Global list. Object will not be added if already added to this list.
- * @param ArrayList $added Additional list to insert into
- * @param DataObject $item Item to add
- * @return mixed
- */
- protected function mergeRelatedObject($list, $added, $item)
- {
- // Identify item
- $itemKey = get_class($item) . '/' . $item->ID;
-
- // Write if saved, versioned, and not already added
- if ($item->isInDB() && $item->has_extension(static::class) && !isset($list[$itemKey])) {
- $list[$itemKey] = $item;
- $added[$itemKey] = $item;
- }
-
- // Add joined record (from many_many through) automatically
- $joined = $item->getJoin();
- if ($joined) {
- $this->mergeRelatedObject($list, $added, $joined);
- }
- }
-}
diff --git a/src/ORM/Versioning/VersionedGridFieldDetailForm.php b/src/ORM/Versioning/VersionedGridFieldDetailForm.php
deleted file mode 100644
index 69929de85..000000000
--- a/src/ORM/Versioning/VersionedGridFieldDetailForm.php
+++ /dev/null
@@ -1,29 +0,0 @@
-has_extension('SilverStripe\ORM\Versioning\Versioned')) {
- $class = 'SilverStripe\ORM\Versioning\VersionedGridFieldItemRequest';
- }
- }
-}
diff --git a/src/ORM/Versioning/VersionedGridFieldItemRequest.php b/src/ORM/Versioning/VersionedGridFieldItemRequest.php
deleted file mode 100644
index 890bd319c..000000000
--- a/src/ORM/Versioning/VersionedGridFieldItemRequest.php
+++ /dev/null
@@ -1,199 +0,0 @@
-getRecord();
- if (!$record || !$record->has_extension(Versioned::class)) {
- return $actions;
- }
-
- // Save & Publish action
- if ($record->canPublish()) {
- // "publish", as with "save", it supports an alternate state to show when action is needed.
- $publish = FormAction::create(
- 'doPublish',
- _t('VersionedGridFieldItemRequest.BUTTONPUBLISH', 'Publish')
- )
- ->setUseButtonTag(true)
- ->addExtraClass('btn btn-primary font-icon-rocket');
-
- // Insert after save
- if ($actions->fieldByName('action_doSave')) {
- $actions->insertAfter('action_doSave', $publish);
- } else {
- $actions->push($publish);
- }
- }
-
- // Unpublish action
- $isPublished = $record->isPublished();
- if ($isPublished && $record->canUnpublish()) {
- $actions->push(
- FormAction::create(
- 'doUnpublish',
- _t('VersionedGridFieldItemRequest.BUTTONUNPUBLISH', 'Unpublish')
- )
- ->setUseButtonTag(true)
- ->setDescription(_t(
- 'VersionedGridFieldItemRequest.BUTTONUNPUBLISHDESC',
- 'Remove this record from the published site'
- ))
- ->addExtraClass('btn-secondary')
- );
- }
-
- // Archive action
- if ($record->canArchive()) {
- // Replace "delete" action
- $actions->removeByName('action_doDelete');
-
- // "archive"
- $actions->push(
- FormAction::create('doArchive', _t('VersionedGridFieldItemRequest.ARCHIVE', 'Archive'))
- ->setDescription(_t(
- 'VersionedGridFieldItemRequest.BUTTONARCHIVEDESC',
- 'Unpublish and send to archive'
- ))
- ->addExtraClass('delete btn-secondary')
- );
- }
- return $actions;
- }
-
- /**
- * Archive this versioned record
- *
- * @param array $data
- * @param Form $form
- * @return HTTPResponse
- */
- public function doArchive($data, $form)
- {
- /** @var Versioned|DataObject $record */
- $record = $this->getRecord();
- if (!$record->canArchive()) {
- return $this->httpError(403);
- }
-
- // Record name before it's deleted
- $title = $record->Title;
- $record->doArchive();
-
- $message = sprintf(
- _t('VersionedGridFieldItemRequest.Archived', 'Archived %s %s'),
- $record->i18n_singular_name(),
- Convert::raw2xml($title)
- );
- $this->setFormMessage($form, $message);
-
- //when an item is deleted, redirect to the parent controller
- $controller = $this->getToplevelController();
- $controller->getRequest()->addHeader('X-Pjax', 'Content'); // Force a content refresh
-
- return $controller->redirect($this->getBackLink(), 302); //redirect back to admin section
- }
-
- /**
- * Publish this versioned record
- *
- * @param array $data
- * @param Form $form
- * @return HTTPResponse
- */
- public function doPublish($data, $form)
- {
- /** @var Versioned|DataObject $record */
- $record = $this->getRecord();
- $isNewRecord = $record->ID == 0;
-
- // Check permission
- if (!$record->canPublish()) {
- return $this->httpError(403);
- }
-
- // Initial save and reload
- $record = $this->saveFormIntoRecord($data, $form);
- $record->publishRecursive();
- $editURL = $this->Link('edit');
- $xmlTitle = Convert::raw2xml($record->Title);
- $link = "{$xmlTitle}";
- $message = _t(
- 'VersionedGridFieldItemRequest.Published',
- 'Published {name} {link}',
- array(
- 'name' => $record->i18n_singular_name(),
- 'link' => $link
- )
- );
- $this->setFormMessage($form, $message);
-
- return $this->redirectAfterSave($isNewRecord);
- }
-
- /**
- * Delete this record from the live site
- *
- * @param array $data
- * @param Form $form
- * @return HTTPResponse
- */
- public function doUnpublish($data, $form)
- {
- /** @var Versioned|DataObject $record */
- $record = $this->getRecord();
- if (!$record->canUnpublish()) {
- return $this->httpError(403);
- }
-
- // Record name before it's deleted
- $title = $record->Title;
- $record->doUnpublish();
-
- $message = sprintf(
- _t('VersionedGridFieldItemRequest.Unpublished', 'Unpublished %s %s'),
- $record->i18n_singular_name(),
- Convert::raw2xml($title)
- );
- $this->setFormMessage($form, $message);
-
- // Redirect back to edit
- return $this->redirectAfterSave(false);
- }
-
- /**
- * @param Form $form
- * @param string $message
- */
- protected function setFormMessage($form, $message)
- {
- $form->sessionMessage($message, 'good', ValidationResult::CAST_HTML);
- $controller = $this->getToplevelController();
- if ($controller->hasMethod('getEditForm')) {
- /** @var Form $backForm */
- $backForm = $controller->getEditForm();
- $backForm->sessionMessage($message, 'good', ValidationResult::CAST_HTML);
- }
- }
-}
diff --git a/src/ORM/Versioning/Versioned_Version.php b/src/ORM/Versioning/Versioned_Version.php
deleted file mode 100644
index 3fd4cb9f0..000000000
--- a/src/ORM/Versioning/Versioned_Version.php
+++ /dev/null
@@ -1,132 +0,0 @@
-record = $record;
- $record['ID'] = $record['RecordID'];
- $className = $record['ClassName'];
-
- $this->object = ClassInfo::exists($className) ? new $className($record) : new DataObject($record);
- $this->failover = $this->object;
-
- parent::__construct();
- }
-
- /**
- * Either 'published' if published, or 'internal' if not.
- *
- * @return string
- */
- public function PublishedClass()
- {
- return $this->record['WasPublished'] ? 'published' : 'internal';
- }
-
- /**
- * Author of this DataObject
- *
- * @return Member
- */
- public function Author()
- {
- return Member::get()->byID($this->record['AuthorID']);
- }
-
- /**
- * Member object of the person who last published this record
- *
- * @return Member
- */
- public function Publisher()
- {
- if (!$this->record['WasPublished']) {
- return null;
- }
-
- return Member::get()->byID($this->record['PublisherID']);
- }
-
- /**
- * True if this record is published via publish() method
- *
- * @return boolean
- */
- public function Published()
- {
- return !empty($this->record['WasPublished']);
- }
-
- /**
- * Traverses to a field referenced by relationships between data objects, returning the value
- * The path to the related field is specified with dot separated syntax (eg: Parent.Child.Child.FieldName)
- *
- * @param $fieldName string
- * @return string | null - will return null on a missing value
- */
- public function relField($fieldName)
- {
- $component = $this;
-
- // We're dealing with relations here so we traverse the dot syntax
- if (strpos($fieldName, '.') !== false) {
- $relations = explode('.', $fieldName);
- $fieldName = array_pop($relations);
- foreach ($relations as $relation) {
- // Inspect $component for element $relation
- if ($component->hasMethod($relation)) {
- // Check nested method
- $component = $component->$relation();
- } elseif ($component instanceof SS_List) {
- // Select adjacent relation from DataList
- $component = $component->relation($relation);
- } elseif ($component instanceof DataObject
- && ($dbObject = $component->dbObject($relation))
- ) {
- // Select db object
- $component = $dbObject;
- } else {
- user_error("$relation is not a relation/field on " . get_class($component), E_USER_ERROR);
- }
- }
- }
-
- // Bail if the component is null
- if (!$component) {
- return null;
- }
- if ($component->hasMethod($fieldName)) {
- return $component->$fieldName();
- }
- return $component->$fieldName;
- }
-}
diff --git a/tests/php/Core/ObjectTest.php b/tests/php/Core/ObjectTest.php
index aa9c49f69..b6151c33f 100644
--- a/tests/php/Core/ObjectTest.php
+++ b/tests/php/Core/ObjectTest.php
@@ -17,6 +17,7 @@ use SilverStripe\Core\Tests\ObjectTest\MySubObject;
use SilverStripe\Core\Tests\ObjectTest\TestExtension;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Control\Controller;
+use SilverStripe\Versioned\Versioned;
/**
* @todo tests for addStaticVars()
@@ -432,18 +433,18 @@ class ObjectTest extends SapphireTest
{
// Simple case
$this->assertEquals(
- array('SilverStripe\\ORM\\Versioning\\Versioned',array('Stage', 'Live')),
- Object::parse_class_spec("SilverStripe\\ORM\\Versioning\\Versioned('Stage','Live')")
+ array(Versioned::class,array('Stage', 'Live')),
+ Object::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage','Live')")
);
// String with commas
$this->assertEquals(
- array('SilverStripe\\ORM\\Versioning\\Versioned',array('Stage,Live', 'Stage')),
- Object::parse_class_spec("SilverStripe\\ORM\\Versioning\\Versioned('Stage,Live','Stage')")
+ array(Versioned::class,array('Stage,Live', 'Stage')),
+ Object::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage,Live','Stage')")
);
// String with quotes
$this->assertEquals(
- array('SilverStripe\\ORM\\Versioning\\Versioned',array('Stage\'Stage,Live\'Live', 'Live')),
- Object::parse_class_spec("SilverStripe\\ORM\\Versioning\\Versioned('Stage\'Stage,Live\'Live','Live')")
+ array(Versioned::class,array('Stage\'Stage,Live\'Live', 'Live')),
+ Object::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage\\'Stage,Live\\'Live','Live')")
);
// True, false and null values
diff --git a/tests/php/Forms/FormFactoryTest.php b/tests/php/Forms/FormFactoryTest.php
index 1db1613be..228e4639c 100644
--- a/tests/php/Forms/FormFactoryTest.php
+++ b/tests/php/Forms/FormFactoryTest.php
@@ -9,7 +9,7 @@ use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\TextField;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
class FormFactoryTest extends SapphireTest
{
@@ -19,6 +19,25 @@ class FormFactoryTest extends SapphireTest
protected static $fixture_file = 'FormFactoryTest.yml';
+ protected function getExtraDataObjects()
+ {
+ // Prevent setup breaking if versioned module absent
+ if (class_exists(Versioned::class)) {
+ return parent::getExtraDataObjects();
+ }
+ return [];
+ }
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ // Note: Soft support for versioned module optionality
+ if (!class_exists(Versioned::class)) {
+ $this->markTestSkipped('FormFactoryTest requires the Versioned extension');
+ }
+ }
+
/**
* Test versioned form
*/
@@ -34,9 +53,7 @@ class FormFactoryTest extends SapphireTest
// Check preview link
- /**
- * @var LiteralField $previewLink
-*/
+ /** @var LiteralField $previewLink */
$previewLink = $form->Fields()->fieldByName('PreviewLink');
$this->assertInstanceOf(LiteralField::class, $previewLink);
$this->assertEquals(
diff --git a/tests/php/Forms/FormFactoryTest/ControllerExtension.php b/tests/php/Forms/FormFactoryTest/ControllerExtension.php
index 718b3cb04..c0fc749b2 100644
--- a/tests/php/Forms/FormFactoryTest/ControllerExtension.php
+++ b/tests/php/Forms/FormFactoryTest/ControllerExtension.php
@@ -8,7 +8,7 @@ use SilverStripe\Core\Extension;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\LiteralField;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
/**
* Provides versionable extensions to a controller / scaffolder
diff --git a/tests/php/Forms/FormFactoryTest/TestController.php b/tests/php/Forms/FormFactoryTest/TestController.php
index d775f5667..affe7928c 100644
--- a/tests/php/Forms/FormFactoryTest/TestController.php
+++ b/tests/php/Forms/FormFactoryTest/TestController.php
@@ -5,7 +5,7 @@ namespace SilverStripe\Forms\Tests\FormFactoryTest;
use SilverStripe\Control\Controller;
use SilverStripe\Forms\Form;
use SilverStripe\ORM\DataObject;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
/**
* Edit controller for this form
diff --git a/tests/php/Forms/FormFactoryTest/TestObject.php b/tests/php/Forms/FormFactoryTest/TestObject.php
index e15fa6238..92db59ba3 100644
--- a/tests/php/Forms/FormFactoryTest/TestObject.php
+++ b/tests/php/Forms/FormFactoryTest/TestObject.php
@@ -4,7 +4,7 @@ namespace SilverStripe\Forms\Tests\FormFactoryTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
/**
* @mixin Versioned
diff --git a/tests/php/ORM/ChangeSetItemTest.php b/tests/php/ORM/ChangeSetItemTest.php
deleted file mode 100644
index d63e5680d..000000000
--- a/tests/php/ORM/ChangeSetItemTest.php
+++ /dev/null
@@ -1,102 +0,0 @@
-logInWithPermission('ADMIN');
- $object = new ChangeSetItemTest\VersionedObject(['Foo' => 1]);
- $object->write();
-
- $item = new ChangeSetItem(
- [
- 'ObjectID' => $object->ID,
- 'ObjectClass' => $object->baseClass(),
- ]
- );
-
- $this->assertEquals(
- ChangeSetItem::CHANGE_CREATED,
- $item->ChangeType,
- 'New objects that aren\'t yet published should return created'
- );
-
- $object->publishRecursive();
-
- $this->assertEquals(
- ChangeSetItem::CHANGE_NONE,
- $item->ChangeType,
- 'Objects that have just been published should return no change'
- );
-
- $object->Foo += 1;
- $object->write();
-
- $this->assertEquals(
- ChangeSetItem::CHANGE_MODIFIED,
- $item->ChangeType,
- 'Object that have unpublished changes written to draft should show as modified'
- );
-
- $object->publishRecursive();
-
- $this->assertEquals(
- ChangeSetItem::CHANGE_NONE,
- $item->ChangeType,
- 'Objects that have just been published should return no change'
- );
-
- // We need to use a copy, because ID is set to 0 by delete, causing the following unpublish to fail
- $objectCopy = clone $object;
- $objectCopy->delete();
-
- $this->assertEquals(
- ChangeSetItem::CHANGE_DELETED,
- $item->ChangeType,
- 'Objects that have been deleted from draft (but not yet unpublished) should show as deleted'
- );
-
- $object->doUnpublish();
-
- $this->assertEquals(
- ChangeSetItem::CHANGE_NONE,
- $item->ChangeType,
- 'Objects that have been deleted and then unpublished should return no change'
- );
- }
-
- public function testGetForObject()
- {
- $this->logInWithPermission('ADMIN');
- $object = new ChangeSetItemTest\VersionedObject(['Foo' => 1]);
- $object->write();
-
- $item = new ChangeSetItem(
- [
- 'ObjectID' => $object->ID,
- 'ObjectClass' => $object->baseClass(),
- ]
- );
- $item->write();
-
- $this->assertEquals(
- ChangeSetItemTest\VersionedObject::get()->byID($object->ID)->toMap(),
- ChangeSetItem::get_for_object($object)->first()->Object()->toMap()
- );
-
- $this->assertEquals(
- ChangeSetItemTest\VersionedObject::get()->byID($object->ID)->toMap(),
- ChangeSetItem::get_for_object_by_id($object->ID, $object->ClassName)->first()->Object()->toMap()
- );
- }
-}
diff --git a/tests/php/ORM/ChangeSetItemTest/VersionedObject.php b/tests/php/ORM/ChangeSetItemTest/VersionedObject.php
deleted file mode 100644
index a417393cf..000000000
--- a/tests/php/ORM/ChangeSetItemTest/VersionedObject.php
+++ /dev/null
@@ -1,28 +0,0 @@
- 'Int'
- ];
-
- private static $extensions = [
- Versioned::class
- ];
-
- function canEdit($member = null)
- {
- return true;
- }
-}
diff --git a/tests/php/ORM/ChangeSetTest.php b/tests/php/ORM/ChangeSetTest.php
deleted file mode 100644
index 3d01f9b6d..000000000
--- a/tests/php/ORM/ChangeSetTest.php
+++ /dev/null
@@ -1,511 +0,0 @@
-logInWithPermission('ADMIN');
- foreach ($this->fixtureFactory->getFixtures() as $class => $fixtures) {
- foreach ($fixtures as $handle => $id) {
- /**
- * @var Versioned|DataObject $object
-*/
- $object = $this->objFromFixture($class, $handle);
- $object->publishSingle();
- }
- }
- }
-
- /**
- * Check that the changeset includes the given items
- *
- * @param ChangeSet $cs
- * @param array $match Array of object fixture keys with change type values
- */
- protected function assertChangeSetLooksLike($cs, $match)
- {
- $items = $cs->Changes()->toArray();
-
- foreach ($match as $key => $mode) {
- list($class, $identifier) = explode('.', $key);
- $object = $this->objFromFixture($class, $identifier);
-
- foreach ($items as $i => $item) {
- if ($item->ObjectClass == $object->baseClass()
- && $item->ObjectID == $object->ID
- && $item->Added == $mode
- ) {
- unset($items[$i]);
- continue 2;
- }
- }
-
- throw new PHPUnit_Framework_ExpectationFailedException(
- 'Change set didn\'t include expected item',
- new ComparisonFailure(array('Class' => $class, 'ID' => $object->ID, 'Added' => $mode), null, "$key => $mode", '')
- );
- }
-
- if (count($items)) {
- $extra = [];
- foreach ($items as $item) {
- $extra[] = ['Class' => $item->ObjectClass, 'ID' => $item->ObjectID, 'Added' => $item->Added, 'ChangeType' => $item->getChangeType()];
- }
- throw new PHPUnit_Framework_ExpectationFailedException(
- 'Change set included items that weren\'t expected',
- new ComparisonFailure(array(), $extra, '', print_r($extra, true))
- );
- }
- }
-
- public function testAddObject()
- {
- $cs = new ChangeSet();
- $cs->write();
-
- $cs->addObject($this->objFromFixture(ChangeSetTest\EndObject::class, 'end1'));
- $cs->addObject($this->objFromFixture(ChangeSetTest\EndObjectChild::class, 'endchild1'));
-
- $this->assertChangeSetLooksLike(
- $cs,
- [
- ChangeSetTest\EndObject::class.'.end1' => ChangeSetItem::EXPLICITLY,
- ChangeSetTest\EndObjectChild::class.'.endchild1' => ChangeSetItem::EXPLICITLY
- ]
- );
- }
-
- public function testDescription()
- {
- $cs = new ChangeSet();
- $cs->write();
- $cs->addObject($this->objFromFixture(ChangeSetTest\EndObject::class, 'end1'));
- $this->assertEquals('one item', $cs->getDescription());
-
- $cs->addObject($this->objFromFixture(ChangeSetTest\EndObjectChild::class, 'endchild1'));
- $this->assertEquals('2 items', $cs->getDescription());
- }
-
- public function testRepeatedSyncIsNOP()
- {
- $this->publishAllFixtures();
-
- $cs = new ChangeSet();
- $cs->write();
-
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
- $cs->addObject($base);
-
- $cs->sync();
- $this->assertChangeSetLooksLike(
- $cs,
- [
- ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
- ]
- );
-
- $cs->sync();
- $this->assertChangeSetLooksLike(
- $cs,
- [
- ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
- ]
- );
- }
-
- public function testSync()
- {
- $this->publishAllFixtures();
-
- $cs = new ChangeSet();
- $cs->write();
-
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
-
- $cs->addObject($base);
- $cs->sync();
-
- $this->assertChangeSetLooksLike(
- $cs,
- [
- ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
- ]
- );
-
- $end = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
- $end->Baz = 3;
- $end->write();
-
- $cs->sync();
-
- $this->assertChangeSetLooksLike(
- $cs,
- [
- ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY,
- ChangeSetTest\EndObject::class.'.end1' => ChangeSetItem::IMPLICITLY
- ]
- );
-
- $baseItem = ChangeSetItem::get_for_object($base)->first();
- $endItem = ChangeSetItem::get_for_object($end)->first();
-
- $this->assertEquals(
- [$baseItem->ID],
- $endItem->ReferencedBy()->column("ID")
- );
-
- $this->assertDOSEquals(
- [
- [
- 'Added' => ChangeSetItem::EXPLICITLY,
- 'ObjectClass' => ChangeSetTest\BaseObject::class,
- 'ObjectID' => $base->ID,
- 'ChangeSetID' => $cs->ID
- ]
- ],
- $endItem->ReferencedBy()
- );
- }
-
- /**
- * Test that sync includes implicit items
- */
- public function testIsSynced()
- {
- $this->publishAllFixtures();
-
- $cs = new ChangeSet();
- $cs->write();
-
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
- $cs->addObject($base);
-
- $cs->sync();
- $this->assertChangeSetLooksLike(
- $cs,
- [
- ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
- ]
- );
- $this->assertTrue($cs->isSynced());
-
- $end = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
- $end->Baz = 3;
- $end->write();
- $this->assertFalse($cs->isSynced());
-
- $cs->sync();
-
- $this->assertChangeSetLooksLike(
- $cs,
- [
- ChangeSetTest\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY,
- ChangeSetTest\EndObject::class.'.end1' => ChangeSetItem::IMPLICITLY
- ]
- );
- $this->assertTrue($cs->isSynced());
- }
-
- public function testCanPublish()
- {
- // Create changeset containing all items (unpublished)
- $this->logInWithPermission('ADMIN');
- $changeSet = new ChangeSet();
- $changeSet->write();
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
- $changeSet->addObject($base);
- $changeSet->sync();
- $this->assertEquals(5, $changeSet->Changes()->count());
-
- // Test un-authenticated user cannot publish
- Session::clear("loggedInAs");
- $this->assertFalse($changeSet->canPublish());
-
- // campaign admin only permission doesn't grant publishing rights
- $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
- $this->assertFalse($changeSet->canPublish());
-
- // With model publish permissions only publish is allowed
- $this->logInWithPermission('PERM_canPublish');
- $this->assertTrue($changeSet->canPublish());
-
- // Test user with the necessary minimum permissions can login
- $this->logInWithPermission(
- [
- 'CMS_ACCESS_CampaignAdmin',
- 'PERM_canPublish'
- ]
- );
- $this->assertTrue($changeSet->canPublish());
- }
-
- public function testHasChanges()
- {
- // Create changeset containing all items (unpublished)
- Versioned::set_stage(Versioned::DRAFT);
- $this->logInWithPermission('ADMIN');
- $changeSet = new ChangeSet();
- $changeSet->write();
- $base = new ChangeSetTest\BaseObject();
- $base->Foo = 1;
- $base->write();
- $changeSet->addObject($base);
-
- // New changeset with changes can be published
- $this->assertTrue($changeSet->canPublish());
- $this->assertTrue($changeSet->hasChanges());
-
- // Writing the record to live dissolves the changes in this changeset
- $base->publishSingle();
- $this->assertTrue($changeSet->canPublish());
- $this->assertFalse($changeSet->hasChanges());
-
- // Changeset can be safely published without error
- $changeSet->publish();
- }
-
- public function testCanRevert()
- {
- $this->markTestSkipped("Requires ChangeSet::revert to be implemented first");
- }
-
- public function testCanEdit()
- {
- // Create changeset containing all items (unpublished)
- $this->logInWithPermission('ADMIN');
- $changeSet = new ChangeSet();
- $changeSet->write();
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
- $changeSet->addObject($base);
- $changeSet->sync();
- $this->assertEquals(5, $changeSet->Changes()->count());
-
- // Check canEdit
- Session::clear("loggedInAs");
- $this->assertFalse($changeSet->canEdit());
- $this->logInWithPermission('SomeWrongPermission');
- $this->assertFalse($changeSet->canEdit());
- $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
- $this->assertTrue($changeSet->canEdit());
- }
-
- public function testCanCreate()
- {
- // Check canCreate
- Session::clear("loggedInAs");
- $this->assertFalse(ChangeSet::singleton()->canCreate());
- $this->logInWithPermission('SomeWrongPermission');
- $this->assertFalse(ChangeSet::singleton()->canCreate());
- $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
- $this->assertTrue(ChangeSet::singleton()->canCreate());
- }
-
- public function testCanDelete()
- {
- // Create changeset containing all items (unpublished)
- $this->logInWithPermission('ADMIN');
- $changeSet = new ChangeSet();
- $changeSet->write();
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
- $changeSet->addObject($base);
- $changeSet->sync();
- $this->assertEquals(5, $changeSet->Changes()->count());
-
- // Check canDelete
- Session::clear("loggedInAs");
- $this->assertFalse($changeSet->canDelete());
- $this->logInWithPermission('SomeWrongPermission');
- $this->assertFalse($changeSet->canDelete());
- $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
- $this->assertTrue($changeSet->canDelete());
- }
-
- public function testCanView()
- {
- // Create changeset containing all items (unpublished)
- $this->logInWithPermission('ADMIN');
- $changeSet = new ChangeSet();
- $changeSet->write();
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
- $changeSet->addObject($base);
- $changeSet->sync();
- $this->assertEquals(5, $changeSet->Changes()->count());
-
- // Check canView
- Session::clear("loggedInAs");
- $this->assertFalse($changeSet->canView());
- $this->logInWithPermission('SomeWrongPermission');
- $this->assertFalse($changeSet->canView());
- $this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
- $this->assertTrue($changeSet->canView());
- }
-
- public function testPublish()
- {
- $this->publishAllFixtures();
-
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
- $baseID = $base->ID;
- $baseBefore = $base->Version;
- $end1 = $this->objFromFixture(ChangeSetTest\EndObject::class, 'end1');
- $end1ID = $end1->ID;
- $end1Before = $end1->Version;
-
- // Create a new changest
- $changeset = new ChangeSet();
- $changeset->write();
- $changeset->addObject($base);
- $changeset->addObject($end1);
-
- // Make a lot of changes
- // - ChangeSetTest_Base.base modified
- // - ChangeSetTest_End.end1 deleted
- // - new ChangeSetTest_Mid added
- $base->Foo = 343;
- $base->write();
- $baseAfter = $base->Version;
- $midNew = new ChangeSetTest\MidObject();
- $midNew->Bar = 39;
- $midNew->write();
- $midNewID = $midNew->ID;
- $midNewAfter = $midNew->Version;
- $end1->delete();
-
- $changeset->addObject($midNew);
-
- // Publish
- $this->logInWithPermission('ADMIN');
- $this->assertTrue($changeset->canPublish());
- $this->assertTrue($changeset->isSynced());
- $changeset->publish();
- $this->assertEquals(ChangeSet::STATE_PUBLISHED, $changeset->State);
-
- // Check each item has the correct before/after version applied
- $baseChange = $changeset->Changes()->filter(
- [
- 'ObjectClass' => ChangeSetTest\BaseObject::class,
- 'ObjectID' => $baseID,
- ]
- )->first();
- $this->assertEquals((int)$baseBefore, (int)$baseChange->VersionBefore);
- $this->assertEquals((int)$baseAfter, (int)$baseChange->VersionAfter);
- $this->assertEquals((int)$baseChange->VersionBefore + 1, (int)$baseChange->VersionAfter);
- $this->assertEquals(
- (int)$baseChange->VersionAfter,
- (int)Versioned::get_versionnumber_by_stage(ChangeSetTest\BaseObject::class, Versioned::LIVE, $baseID)
- );
-
- $end1Change = $changeset->Changes()->filter(
- [
- 'ObjectClass' => ChangeSetTest\EndObject::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\EndObject::class, Versioned::LIVE, $end1ID)
- );
-
- $midNewChange = $changeset->Changes()->filter(
- [
- 'ObjectClass' => ChangeSetTest\MidObject::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\MidObject::class, Versioned::LIVE, $midNewID)
- );
-
- // Test trying to re-publish is blocked
- $this->setExpectedException(
- 'BadMethodCallException',
- "ChangeSet can't be published if it has been already published or reverted."
- );
- $changeset->publish();
- }
-
- /**
- * Ensure that related objects are disassociated on live
- */
- public function testUnlinkDisassociated()
- {
- $this->publishAllFixtures();
- /**
- * @var BaseObject $base
-*/
- $base = $this->objFromFixture(ChangeSetTest\BaseObject::class, 'base');
- /**
- * @var MidObject $mid1 $mid2
-*/
- $mid1 = $this->objFromFixture(ChangeSetTest\MidObject::class, 'mid1');
- $mid2 = $this->objFromFixture(ChangeSetTest\MidObject::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\BaseObject::class.'.base' => ChangeSetItem::EXPLICITLY
- ]
- );
-
- $changeset->publish();
-
- // mid1 on live exists, but has BaseID set to zero
- $mid1Live = Versioned::get_by_stage(ChangeSetTest\MidObject::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\MidObject::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/php/ORM/ChangeSetTest.yml b/tests/php/ORM/ChangeSetTest.yml
deleted file mode 100644
index 3d48d691a..000000000
--- a/tests/php/ORM/ChangeSetTest.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-SilverStripe\ORM\Tests\ChangeSetTest\BaseObject:
- base:
- Foo: 1
-SilverStripe\ORM\Tests\ChangeSetTest\EndObject:
- end1:
- Baz: 1
- end2:
- Baz: 2
-SilverStripe\ORM\Tests\ChangeSetTest\EndObjectChild:
- endchild1:
- Baz: 3
- Qux: 3
-SilverStripe\ORM\Tests\ChangeSetTest\MidObject:
- mid1:
- Bar: 1
- Base: =>SilverStripe\ORM\Tests\ChangeSetTest\BaseObject.base
- End: =>SilverStripe\ORM\Tests\ChangeSetTest\EndObject.end1
- mid2:
- Bar: 2
- Base: =>SilverStripe\ORM\Tests\ChangeSetTest\BaseObject.base
- End: =>SilverStripe\ORM\Tests\ChangeSetTest\EndObject.end2
diff --git a/tests/php/ORM/ChangeSetTest/BaseObject.php b/tests/php/ORM/ChangeSetTest/BaseObject.php
deleted file mode 100644
index b9e3da272..000000000
--- a/tests/php/ORM/ChangeSetTest/BaseObject.php
+++ /dev/null
@@ -1,33 +0,0 @@
- 'Int',
- ];
-
- private static $has_many = [
- 'Mids' => MidObject::class,
- ];
-
- private static $owns = [
- 'Mids',
- ];
-
- private static $extensions = [
- Versioned::class,
- ];
-}
diff --git a/tests/php/ORM/ChangeSetTest/EndObject.php b/tests/php/ORM/ChangeSetTest/EndObject.php
deleted file mode 100644
index b7326644a..000000000
--- a/tests/php/ORM/ChangeSetTest/EndObject.php
+++ /dev/null
@@ -1,25 +0,0 @@
- 'Int',
- ];
-
- private static $extensions = [
- Versioned::class,
- ];
-}
diff --git a/tests/php/ORM/ChangeSetTest/EndObjectChild.php b/tests/php/ORM/ChangeSetTest/EndObjectChild.php
deleted file mode 100644
index 7043da3d2..000000000
--- a/tests/php/ORM/ChangeSetTest/EndObjectChild.php
+++ /dev/null
@@ -1,18 +0,0 @@
- 'Int',
- ];
-}
diff --git a/tests/php/ORM/ChangeSetTest/MidObject.php b/tests/php/ORM/ChangeSetTest/MidObject.php
deleted file mode 100644
index b549070d1..000000000
--- a/tests/php/ORM/ChangeSetTest/MidObject.php
+++ /dev/null
@@ -1,34 +0,0 @@
- 'Int',
- ];
-
- private static $has_one = [
- 'Base' => BaseObject::class,
- 'End' => EndObject::class,
- ];
-
- private static $owns = [
- 'End',
- ];
-
- private static $extensions = [
- Versioned::class,
- ];
-}
diff --git a/tests/php/ORM/ChangeSetTest/Permissions.php b/tests/php/ORM/ChangeSetTest/Permissions.php
deleted file mode 100644
index 2d94cd426..000000000
--- a/tests/php/ORM/ChangeSetTest/Permissions.php
+++ /dev/null
@@ -1,50 +0,0 @@
-can(__FUNCTION__, $member);
- }
-
- public function canDelete($member = null)
- {
- return $this->can(__FUNCTION__, $member);
- }
-
- public function canCreate($member = null, $context = array())
- {
- return $this->can(__FUNCTION__, $member, $context);
- }
-
- public function canPublish($member = null, $context = array())
- {
- return $this->can(__FUNCTION__, $member, $context);
- }
-
- public function canUnpublish($member = null, $context = array())
- {
- return $this->can(__FUNCTION__, $member, $context);
- }
-
- public function can($perm, $member = null, $context = array())
- {
- $perms = [
- "PERM_{$perm}",
- 'CAN_ALL',
- ];
- return Permission::checkMember($member, $perms);
- }
-}
diff --git a/tests/php/ORM/DBClassNameTest/TestObject.php b/tests/php/ORM/DBClassNameTest/TestObject.php
index 78dd0b28c..be58bd5f0 100644
--- a/tests/php/ORM/DBClassNameTest/TestObject.php
+++ b/tests/php/ORM/DBClassNameTest/TestObject.php
@@ -4,16 +4,11 @@ namespace SilverStripe\ORM\Tests\DBClassNameTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
-use SilverStripe\ORM\Versioning\Versioned;
class TestObject extends DataObject implements TestOnly
{
private static $table_name = 'DBClassNameTest_Object';
- private static $extensions = array(
- Versioned::class
- );
-
private static $db = array(
'DefaultClass' => 'DBClassName',
'AnyClass' => 'DBClassName(\'SilverStripe\\ORM\\DataObject\')',
diff --git a/tests/php/ORM/DataDifferencerTest.php b/tests/php/ORM/DataDifferencerTest.php
deleted file mode 100644
index 352c2fe8b..000000000
--- a/tests/php/ORM/DataDifferencerTest.php
+++ /dev/null
@@ -1,106 +0,0 @@
-exclude('ClassName', Folder::class);
- foreach ($files as $file) {
- $fromPath = __DIR__ . '/DataDifferencerTest/images/' . $file->Name;
- $destPath = TestAssetStore::getLocalPath($file); // Only correct for test asset store
- Filesystem::makeFolder(dirname($destPath));
- copy($fromPath, $destPath);
- }
- }
-
- protected function tearDown()
- {
- TestAssetStore::reset();
- parent::tearDown();
- }
-
- public function testArrayValues()
- {
- $obj1 = $this->objFromFixture(DataDifferencerTest\TestObject::class, 'obj1');
- $beforeVersion = $obj1->Version;
- // create a new version
- $obj1->Choices = 'a';
- $obj1->write();
- $afterVersion = $obj1->Version;
- $obj1v1 = Versioned::get_version(DataDifferencerTest\TestObject::class, $obj1->ID, $beforeVersion);
- $obj1v2 = Versioned::get_version(DataDifferencerTest\TestObject::class, $obj1->ID, $afterVersion);
- $differ = new DataDifferencer($obj1v1, $obj1v2);
- $obj1Diff = $differ->diffedData();
- // TODO Using getter would split up field again, bug only caused by simulating
- // an array-based value in the first place.
- $this->assertContains('aa,b', str_replace(' ', '', $obj1Diff->getField('Choices')));
- }
-
- public function testHasOnes()
- {
- /**
- * @var DataDifferencerTest\TestObject $obj1
-*/
- $obj1 = $this->objFromFixture(DataDifferencerTest\TestObject::class, 'obj1');
- $image1 = $this->objFromFixture(Image::class, 'image1');
- $image2 = $this->objFromFixture(Image::class, 'image2');
- $relobj2 = $this->objFromFixture(DataDifferencerTest\HasOneRelationObject::class, 'relobj2');
-
- // create a new version
- $beforeVersion = $obj1->Version;
- $obj1->ImageID = $image2->ID;
- $obj1->HasOneRelationID = $relobj2->ID;
- $obj1->write();
- $afterVersion = $obj1->Version;
- $this->assertNotEquals($beforeVersion, $afterVersion);
- /**
- * @var DataDifferencerTest\TestObject $obj1v1
-*/
- $obj1v1 = Versioned::get_version(DataDifferencerTest\TestObject::class, $obj1->ID, $beforeVersion);
- /**
- * @var DataDifferencerTest\TestObject $obj1v2
-*/
- $obj1v2 = Versioned::get_version(DataDifferencerTest\TestObject::class, $obj1->ID, $afterVersion);
- $differ = new DataDifferencer($obj1v1, $obj1v2);
- $obj1Diff = $differ->diffedData();
-
- /**
- * @skipUpgrade
-*/
- $this->assertContains($image1->Name, $obj1Diff->getField('Image'));
- /**
- * @skipUpgrade
-*/
- $this->assertContains($image2->Name, $obj1Diff->getField('Image'));
- $this->assertContains(
- 'obj2obj1',
- str_replace(' ', '', $obj1Diff->getField('HasOneRelationID'))
- );
- }
-}
diff --git a/tests/php/ORM/DataDifferencerTest.yml b/tests/php/ORM/DataDifferencerTest.yml
deleted file mode 100644
index bfb10607a..000000000
--- a/tests/php/ORM/DataDifferencerTest.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-SilverStripe\Assets\Image:
- image1:
- FileFilename: test-image.png
- FileHash: 444065542b5dd5187166d8e1cd684e0d724c5a97
- Name: test-image.png
- image2:
- FileFilename: test.image.with.dots.png
- FileHash: 46affab7043cfd9f1ded919dd24affd08e926eca
- Name: test.image.with.dots.png
-SilverStripe\ORM\Tests\DataDifferencerTest\HasOneRelationObject:
- relobj1:
- Title: obj1
- relobj2:
- Title: obj2
-SilverStripe\ORM\Tests\DataDifferencerTest\TestObject:
- obj1:
- Choices: a,b
- Image: =>SilverStripe\Assets\Image.image1
- HasOneRelation: =>SilverStripe\ORM\Tests\DataDifferencerTest\HasOneRelationObject.relobj1
diff --git a/tests/php/ORM/DataDifferencerTest/HasOneRelationObject.php b/tests/php/ORM/DataDifferencerTest/HasOneRelationObject.php
deleted file mode 100644
index 8d2c57660..000000000
--- a/tests/php/ORM/DataDifferencerTest/HasOneRelationObject.php
+++ /dev/null
@@ -1,19 +0,0 @@
- 'Varchar'
- );
-
- private static $has_many = array(
- 'Objects' => TestObject::class
- );
-}
diff --git a/tests/php/ORM/DataDifferencerTest/TestObject.php b/tests/php/ORM/DataDifferencerTest/TestObject.php
deleted file mode 100644
index f7d9f0717..000000000
--- a/tests/php/ORM/DataDifferencerTest/TestObject.php
+++ /dev/null
@@ -1,46 +0,0 @@
- "Varchar",
- );
-
- private static $has_one = array(
- 'Image' => Image::class,
- 'HasOneRelation' => HasOneRelationObject::class
- );
-
- public function getCMSFields()
- {
- $fields = parent::getCMSFields();
- $choices = array(
- 'a' => 'a',
- 'b' => 'b',
- 'c' => 'c',
- );
- $listField = new ListboxField('Choices', 'Choices', $choices);
- $fields->push($listField);
-
- return $fields;
- }
-}
diff --git a/tests/php/ORM/DataDifferencerTest/images/test-image-high-quality.jpg b/tests/php/ORM/DataDifferencerTest/images/test-image-high-quality.jpg
deleted file mode 100644
index f90315784..000000000
Binary files a/tests/php/ORM/DataDifferencerTest/images/test-image-high-quality.jpg and /dev/null differ
diff --git a/tests/php/ORM/DataDifferencerTest/images/test-image-low-quality.jpg b/tests/php/ORM/DataDifferencerTest/images/test-image-low-quality.jpg
deleted file mode 100644
index ccc8346ab..000000000
Binary files a/tests/php/ORM/DataDifferencerTest/images/test-image-low-quality.jpg and /dev/null differ
diff --git a/tests/php/ORM/DataDifferencerTest/images/test-image.png b/tests/php/ORM/DataDifferencerTest/images/test-image.png
deleted file mode 100644
index abe33e5f5..000000000
Binary files a/tests/php/ORM/DataDifferencerTest/images/test-image.png and /dev/null differ
diff --git a/tests/php/ORM/DataDifferencerTest/images/test.image.with.dots.png b/tests/php/ORM/DataDifferencerTest/images/test.image.with.dots.png
deleted file mode 100644
index 21df58520..000000000
Binary files a/tests/php/ORM/DataDifferencerTest/images/test.image.with.dots.png and /dev/null differ
diff --git a/tests/php/ORM/DataObjectLazyLoadingTest.php b/tests/php/ORM/DataObjectLazyLoadingTest.php
index fdfeed5d4..4410961e5 100644
--- a/tests/php/ORM/DataObjectLazyLoadingTest.php
+++ b/tests/php/ORM/DataObjectLazyLoadingTest.php
@@ -5,32 +5,21 @@ namespace SilverStripe\ORM\Tests;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DataObject;
-use SilverStripe\ORM\Tests\DataObjectLazyLoadingTest\VersionedObject;
-use SilverStripe\ORM\Tests\DataObjectLazyLoadingTest\VersionedSubObject;
use SilverStripe\ORM\Tests\DataObjectTest\SubTeam;
use SilverStripe\ORM\Tests\DataObjectTest\Team;
-use SilverStripe\ORM\Tests\VersionedTest\Subclass;
-use SilverStripe\ORM\Versioning\Versioned;
use SilverStripe\Dev\SapphireTest;
class DataObjectLazyLoadingTest extends SapphireTest
{
-
protected static $fixture_file = array(
'DataObjectTest.yml',
- 'VersionedTest.yml'
);
protected function getExtraDataObjects()
{
return array_merge(
DataObjectTest::$extra_data_objects,
- ManyManyListTest::$extra_data_objects,
- VersionedTest::$extra_data_objects,
- [
- VersionedObject::class,
- VersionedSubObject::class,
- ]
+ ManyManyListTest::$extra_data_objects
);
}
@@ -302,154 +291,4 @@ class DataObjectLazyLoadingTest extends SapphireTest
$this->assertArrayNotHasKey('SubclassDatabaseField_Lazy', $subteam1Lazy->toMap());
$this->assertArrayHasKey('SubclassDatabaseField', $subteam1Lazy->toMap());
}
-
- public function testLazyLoadedFieldsOnVersionedRecords()
- {
- // Save another record, sanity check that we're getting the right one
- $obj2 = new Subclass();
- $obj2->Name = "test2";
- $obj2->ExtraField = "foo2";
- $obj2->write();
-
- // Save the actual inspected record
- $obj1 = new Subclass();
- $obj1->Name = "test";
- $obj1->ExtraField = "foo";
- $obj1->write();
- $version1 = $obj1->Version;
- $obj1->Name = "test2";
- $obj1->ExtraField = "baz";
- $obj1->write();
- $version2 = $obj1->Version;
-
-
- $reloaded = Versioned::get_version(VersionedTest\Subclass::class, $obj1->ID, $version1);
- $this->assertEquals($reloaded->Name, 'test');
- $this->assertEquals($reloaded->ExtraField, 'foo');
-
- $reloaded = Versioned::get_version(VersionedTest\Subclass::class, $obj1->ID, $version2);
- $this->assertEquals($reloaded->Name, 'test2');
- $this->assertEquals($reloaded->ExtraField, 'baz');
-
- $reloaded = Versioned::get_latest_version(VersionedTest\Subclass::class, $obj1->ID);
- $this->assertEquals($reloaded->Version, $version2);
- $this->assertEquals($reloaded->Name, 'test2');
- $this->assertEquals($reloaded->ExtraField, 'baz');
-
- $allVersions = Versioned::get_all_versions(VersionedTest\Subclass::class, $obj1->ID);
- $this->assertEquals(2, $allVersions->count());
- $this->assertEquals($allVersions->first()->Version, $version1);
- $this->assertEquals($allVersions->first()->Name, 'test');
- $this->assertEquals($allVersions->first()->ExtraField, 'foo');
- $this->assertEquals($allVersions->last()->Version, $version2);
- $this->assertEquals($allVersions->last()->Name, 'test2');
- $this->assertEquals($allVersions->last()->ExtraField, 'baz');
-
- $obj1->delete();
- }
-
- public function testLazyLoadedFieldsDoNotReferenceVersionsTable()
- {
- // Save another record, sanity check that we're getting the right one
- $obj2 = new Subclass();
- $obj2->Name = "test2";
- $obj2->ExtraField = "foo2";
- $obj2->write();
-
- $obj1 = new VersionedSubObject();
- $obj1->PageName = "old-value";
- $obj1->ExtraField = "old-value";
- $obj1ID = $obj1->write();
- $obj1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
-
- $obj1 = VersionedSubObject::get()->byID($obj1ID);
- $this->assertEquals(
- 'old-value',
- $obj1->PageName,
- "Correct value on base table when fetching base class"
- );
- $this->assertEquals(
- 'old-value',
- $obj1->ExtraField,
- "Correct value on sub table when fetching base class"
- );
-
- $obj1 = VersionedObject::get()->byID($obj1ID);
- $this->assertEquals(
- 'old-value',
- $obj1->PageName,
- "Correct value on base table when fetching sub class"
- );
- $this->assertEquals(
- 'old-value',
- $obj1->ExtraField,
- "Correct value on sub table when fetching sub class"
- );
-
- // Force inconsistent state to test behaviour (shouldn't select from *_versions)
- DB::query(
- sprintf(
- "UPDATE \"VersionedLazy_DataObject_Versions\" SET \"PageName\" = 'versioned-value' " .
- "WHERE \"RecordID\" = %d",
- $obj1ID
- )
- );
- DB::query(
- sprintf(
- "UPDATE \"VersionedLazySub_DataObject_Versions\" SET \"ExtraField\" = 'versioned-value' " .
- "WHERE \"RecordID\" = %d",
- $obj1ID
- )
- );
-
- $obj1 = VersionedSubObject::get()->byID($obj1ID);
- $this->assertEquals(
- 'old-value',
- $obj1->PageName,
- "Correct value on base table when fetching base class"
- );
- $this->assertEquals(
- 'old-value',
- $obj1->ExtraField,
- "Correct value on sub table when fetching base class"
- );
- $obj1 = VersionedObject::get()->byID($obj1ID);
- $this->assertEquals(
- 'old-value',
- $obj1->PageName,
- "Correct value on base table when fetching sub class"
- );
- $this->assertEquals(
- 'old-value',
- $obj1->ExtraField,
- "Correct value on sub table when fetching sub class"
- );
-
- // Update live table only to test behaviour (shouldn't select from *_versions or stage)
- DB::query(
- sprintf(
- 'UPDATE "VersionedLazy_DataObject_Live" SET "PageName" = \'live-value\' WHERE "ID" = %d',
- $obj1ID
- )
- );
- DB::query(
- sprintf(
- 'UPDATE "VersionedLazySub_DataObject_Live" SET "ExtraField" = \'live-value\' WHERE "ID" = %d',
- $obj1ID
- )
- );
-
- Versioned::set_stage(Versioned::LIVE);
- $obj1 = VersionedObject::get()->byID($obj1ID);
- $this->assertEquals(
- 'live-value',
- $obj1->PageName,
- "Correct value from base table when fetching base class on live stage"
- );
- $this->assertEquals(
- 'live-value',
- $obj1->ExtraField,
- "Correct value from sub table when fetching base class on live stage"
- );
- }
}
diff --git a/tests/php/ORM/DataObjectLazyLoadingTest/VersionedObject.php b/tests/php/ORM/DataObjectLazyLoadingTest/VersionedObject.php
deleted file mode 100644
index fc76f14a8..000000000
--- a/tests/php/ORM/DataObjectLazyLoadingTest/VersionedObject.php
+++ /dev/null
@@ -1,23 +0,0 @@
- "Varchar"
- ];
-
- private static $extensions = [
- Versioned::class
- ];
-}
diff --git a/tests/php/ORM/DataObjectLazyLoadingTest/VersionedSubObject.php b/tests/php/ORM/DataObjectLazyLoadingTest/VersionedSubObject.php
deleted file mode 100644
index e6b182a37..000000000
--- a/tests/php/ORM/DataObjectLazyLoadingTest/VersionedSubObject.php
+++ /dev/null
@@ -1,20 +0,0 @@
- "Varchar",
- );
- private static $extensions = array(
- Versioned::class
- );
-}
diff --git a/tests/php/ORM/HierarchyTest.php b/tests/php/ORM/HierarchyTest.php
index 185652927..16886e046 100644
--- a/tests/php/ORM/HierarchyTest.php
+++ b/tests/php/ORM/HierarchyTest.php
@@ -3,14 +3,13 @@
namespace SilverStripe\ORM\Tests;
use SilverStripe\ORM\ValidationException;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\CSSContentParser;
use SilverStripe\Dev\SapphireTest;
class HierarchyTest extends SapphireTest
{
-
protected static $fixture_file = 'HierarchyTest.yml';
protected $extraDataObjects = array(
@@ -19,6 +18,25 @@ class HierarchyTest extends SapphireTest
HierarchyTest\HideTestSubObject::class,
);
+ protected function getExtraDataObjects()
+ {
+ // Prevent setup breaking if versioned module absent
+ if (class_exists(Versioned::class)) {
+ return parent::getExtraDataObjects();
+ }
+ return [];
+ }
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ // Note: Soft support for versioned module optionality
+ if (!class_exists(Versioned::class)) {
+ $this->markTestSkipped('HierarchyTest requires the Versioned extension');
+ }
+ }
+
/**
* Test the Hierarchy prevents infinite loops.
*/
diff --git a/tests/php/ORM/HierarchyTest/HideTestObject.php b/tests/php/ORM/HierarchyTest/HideTestObject.php
index ac25d04e2..a12cfc484 100644
--- a/tests/php/ORM/HierarchyTest/HideTestObject.php
+++ b/tests/php/ORM/HierarchyTest/HideTestObject.php
@@ -5,7 +5,7 @@ namespace SilverStripe\ORM\Tests\HierarchyTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Hierarchy\Hierarchy;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
/**
* @mixin Versioned
diff --git a/tests/php/ORM/HierarchyTest/HideTestSubObject.php b/tests/php/ORM/HierarchyTest/HideTestSubObject.php
index ac6a42996..f31d93303 100644
--- a/tests/php/ORM/HierarchyTest/HideTestSubObject.php
+++ b/tests/php/ORM/HierarchyTest/HideTestSubObject.php
@@ -3,7 +3,7 @@
namespace SilverStripe\ORM\Tests\HierarchyTest;
use SilverStripe\ORM\Hierarchy\Hierarchy;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
/**
* @mixin Versioned
diff --git a/tests/php/ORM/HierarchyTest/TestObject.php b/tests/php/ORM/HierarchyTest/TestObject.php
index 816289916..ae62a01ba 100644
--- a/tests/php/ORM/HierarchyTest/TestObject.php
+++ b/tests/php/ORM/HierarchyTest/TestObject.php
@@ -5,7 +5,7 @@ namespace SilverStripe\ORM\Tests\HierarchyTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Hierarchy\Hierarchy;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
/**
* @mixin Versioned
diff --git a/tests/php/ORM/ManyManyThroughListTest.php b/tests/php/ORM/ManyManyThroughListTest.php
index 6c33b04c1..a7d9a2611 100644
--- a/tests/php/ORM/ManyManyThroughListTest.php
+++ b/tests/php/ORM/ManyManyThroughListTest.php
@@ -5,7 +5,6 @@ namespace SilverStripe\ORM\Tests;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\ManyManyThroughList;
-use SilverStripe\ORM\Versioning\Versioned;
use InvalidArgumentException;
class ManyManyThroughListTest extends SapphireTest
@@ -15,10 +14,7 @@ class ManyManyThroughListTest extends SapphireTest
protected $extraDataObjects = [
ManyManyThroughListTest\Item::class,
ManyManyThroughListTest\JoinObject::class,
- ManyManyThroughListTest\TestObject::class,
- ManyManyThroughListTest\VersionedItem::class,
- ManyManyThroughListTest\VersionedJoinObject::class,
- ManyManyThroughListTest\VersionedObject::class,
+ ManyManyThroughListTest\TestObject::class
];
protected function setUp()
@@ -35,9 +31,7 @@ class ManyManyThroughListTest extends SapphireTest
public function testSelectJoin()
{
- /**
- * @var \SilverStripe\ORM\Tests\ManyManyThroughListTest\ManyManyThroughListTest_Object $parent
-*/
+ /** @var ManyManyThroughListTest\TestObject $parent */
$parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
$this->assertDOSEquals(
[
@@ -102,9 +96,7 @@ class ManyManyThroughListTest extends SapphireTest
public function testAdd()
{
- /**
- * @var \SilverStripe\ORM\Tests\ManyManyThroughListTest\ManyManyThroughListTest_Object $parent
-*/
+ /** @var ManyManyThroughListTest\TestObject $parent */
$parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
$newItem = new ManyManyThroughListTest\Item();
$newItem->Title = 'my new item';
@@ -128,9 +120,7 @@ class ManyManyThroughListTest extends SapphireTest
public function testRemove()
{
- /**
- * @var \SilverStripe\ORM\Tests\ManyManyThroughListTest\ManyManyThroughListTest_Object $parent
-*/
+ /** @var ManyManyThroughListTest\TestObject $parent */
$parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
$this->assertDOSEquals(
[
@@ -147,73 +137,6 @@ class ManyManyThroughListTest extends SapphireTest
);
}
- public function testPublishing()
- {
- /**
- * @var \SilverStripe\ORM\Tests\ManyManyThroughListTest\ManyManyThroughListTest_VersionedObject $draftParent
-*/
- $draftParent = $this->objFromFixture(ManyManyThroughListTest\VersionedObject::class, 'parent1');
- $draftParent->publishRecursive();
-
- // Modify draft stage
- $item1 = $draftParent->Items()->filter(['Title' => 'versioned item 1'])->first();
- $item1->Title = 'new versioned item 1';
- $item1->getJoin()->Title = 'new versioned join 1';
- $item1->write(false, false, false, true); // Write joined components
- $draftParent->Title = 'new versioned title';
- $draftParent->write();
-
- // Check owned objects on stage
- $draftOwnedObjects = $draftParent->findOwned(true);
- $this->assertDOSEquals(
- [
- ['Title' => 'new versioned join 1'],
- ['Title' => 'versioned join 2'],
- ['Title' => 'new versioned item 1'],
- ['Title' => 'versioned item 2'],
- ],
- $draftOwnedObjects
- );
-
- // Check live record is still old values
- // This tests that both the join table and many_many tables
- // inherit the necessary query parameters from the parent object.
- /**
- * @var \SilverStripe\ORM\Tests\ManyManyThroughListTest\ManyManyThroughListTest_VersionedObject $liveParent
-*/
- $liveParent = Versioned::get_by_stage(
- ManyManyThroughListTest\VersionedObject::class,
- Versioned::LIVE
- )->byID($draftParent->ID);
- $liveOwnedObjects = $liveParent->findOwned(true);
- $this->assertDOSEquals(
- [
- ['Title' => 'versioned join 1'],
- ['Title' => 'versioned join 2'],
- ['Title' => 'versioned item 1'],
- ['Title' => 'versioned item 2'],
- ],
- $liveOwnedObjects
- );
-
- // Publish draft changes
- $draftParent->publishRecursive();
- $liveParent = Versioned::get_by_stage(
- ManyManyThroughListTest\VersionedObject::class,
- Versioned::LIVE
- )->byID($draftParent->ID);
- $liveOwnedObjects = $liveParent->findOwned(true);
- $this->assertDOSEquals(
- [
- ['Title' => 'new versioned join 1'],
- ['Title' => 'versioned join 2'],
- ['Title' => 'new versioned item 1'],
- ['Title' => 'versioned item 2'],
- ],
- $liveOwnedObjects
- );
- }
-
/**
* Test validation
*/
diff --git a/tests/php/ORM/ManyManyThroughListTest.yml b/tests/php/ORM/ManyManyThroughListTest.yml
index 4a3573853..9a359c667 100644
--- a/tests/php/ORM/ManyManyThroughListTest.yml
+++ b/tests/php/ORM/ManyManyThroughListTest.yml
@@ -17,20 +17,3 @@ SilverStripe\ORM\Tests\ManyManyThroughListTest\JoinObject:
Sort: 2
Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObject.parent1
Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Item.child2
-SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedObject:
- parent1:
- Title: 'versioned object'
-SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedItem:
- child1:
- Title: 'versioned item 1'
- child2:
- Title: 'versioned item 2'
-SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedJoinObject:
- join1:
- Title: 'versioned join 1'
- Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedObject.parent1
- Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedItem.child1
- join2:
- Title: 'versioned join 2'
- Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedObject.parent1
- Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\VersionedItem.child2
diff --git a/tests/php/ORM/ManyManyThroughListTest/VersionedItem.php b/tests/php/ORM/ManyManyThroughListTest/VersionedItem.php
deleted file mode 100644
index 1ff9e08de..000000000
--- a/tests/php/ORM/ManyManyThroughListTest/VersionedItem.php
+++ /dev/null
@@ -1,30 +0,0 @@
- 'Varchar'
- ];
-
- private static $extensions = [
- Versioned::class
- ];
-
- private static $belongs_many_many = [
- 'Objects' => 'SilverStripe\\ORM\\Tests\\ManyManyThroughListTest\\VersionedObject.Items'
- ];
-}
diff --git a/tests/php/ORM/ManyManyThroughListTest/VersionedJoinObject.php b/tests/php/ORM/ManyManyThroughListTest/VersionedJoinObject.php
deleted file mode 100644
index 37034ef58..000000000
--- a/tests/php/ORM/ManyManyThroughListTest/VersionedJoinObject.php
+++ /dev/null
@@ -1,31 +0,0 @@
- 'Varchar'
- ];
-
- private static $extensions = [
- Versioned::class
- ];
-
- private static $has_one = [
- 'Parent' => VersionedObject::class,
- 'Child' => VersionedItem::class,
- ];
-}
diff --git a/tests/php/ORM/ManyManyThroughListTest/VersionedObject.php b/tests/php/ORM/ManyManyThroughListTest/VersionedObject.php
deleted file mode 100644
index 6a85fce1b..000000000
--- a/tests/php/ORM/ManyManyThroughListTest/VersionedObject.php
+++ /dev/null
@@ -1,40 +0,0 @@
- 'Varchar',
- ];
-
- private static $extensions = [
- Versioned::class,
- ];
-
- private static $owns = [
- 'Items', // Should automatically own both mapping and child records
- ];
-
- private static $many_many = [
- 'Items' => [
- 'through' => VersionedJoinObject::class,
- 'from' => 'Parent',
- 'to' => 'Child',
- ],
- ];
-}
diff --git a/tests/php/ORM/VersionableExtensionsFixtures.yml b/tests/php/ORM/VersionableExtensionsFixtures.yml
deleted file mode 100644
index c59124f2e..000000000
--- a/tests/php/ORM/VersionableExtensionsFixtures.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-SilverStripe\ORM\Tests\VersionableExtensionsTest\TestObject:
- object:
- Title: "Test"
diff --git a/tests/php/ORM/VersionableExtensionsTest.php b/tests/php/ORM/VersionableExtensionsTest.php
deleted file mode 100644
index 5a738168d..000000000
--- a/tests/php/ORM/VersionableExtensionsTest.php
+++ /dev/null
@@ -1,30 +0,0 @@
-assertContains($tableName, array_keys($tables), 'Contains table: '.$tableName);
- }
- }
-}
diff --git a/tests/php/ORM/VersionableExtensionsTest/TestExtension.php b/tests/php/ORM/VersionableExtensionsTest/TestExtension.php
deleted file mode 100644
index 4f75fc101..000000000
--- a/tests/php/ORM/VersionableExtensionsTest/TestExtension.php
+++ /dev/null
@@ -1,28 +0,0 @@
- 'Varchar'
- );
-
- private static $extensions = [
- Versioned::class,
- TestExtension::class,
- ];
-
- private static $versionableExtensions = [
- TestExtension::class => ['test1', 'test2', 'test3']
- ];
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest.php b/tests/php/ORM/VersionedOwnershipTest.php
deleted file mode 100644
index 1eea5d069..000000000
--- a/tests/php/ORM/VersionedOwnershipTest.php
+++ /dev/null
@@ -1,646 +0,0 @@
-getFixtureFactory()->getFixtures() as $class => $fixtures) {
- foreach ($fixtures as $name => $id) {
- if (stripos($name, '_published') !== false) {
- /** @var Versioned|DataObject $object */
- $object = DataObject::get($class)->byID($id);
- $object->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- }
- }
- }
- }
-
- /**
- * Virtual "sleep" that doesn't actually slow execution, only advances DBDateTime::now()
- *
- * @param int $minutes
- */
- protected function sleep($minutes)
- {
- $now = DBDatetime::now();
- $date = DateTime::createFromFormat('Y-m-d H:i:s', $now->getValue());
- $date->modify("+{$minutes} minutes");
- DBDatetime::set_mock_now($date->format('Y-m-d H:i:s'));
- }
-
- /**
- * Test basic findOwned() in stage mode
- */
- public function testFindOwned()
- {
- /** @var VersionedOwnershipTest\Subclass $subclass1 */
- $subclass1 = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass1_published');
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 1'],
- ['Title' => 'Attachment 1'],
- ['Title' => 'Attachment 2'],
- ['Title' => 'Attachment 5'],
- ['Title' => 'Related Many 1'],
- ['Title' => 'Related Many 2'],
- ['Title' => 'Related Many 3'],
- ],
- $subclass1->findOwned()
- );
-
- // Non-recursive search
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 1'],
- ['Title' => 'Related Many 1'],
- ['Title' => 'Related Many 2'],
- ['Title' => 'Related Many 3'],
- ],
- $subclass1->findOwned(false)
- );
-
- /** @var VersionedOwnershipTest\Subclass $subclass2 */
- $subclass2 = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2'],
- ['Title' => 'Attachment 3'],
- ['Title' => 'Attachment 4'],
- ['Title' => 'Attachment 5'],
- ['Title' => 'Related Many 4'],
- ],
- $subclass2->findOwned()
- );
-
- // Non-recursive search
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2'],
- ['Title' => 'Related Many 4'],
- ],
- $subclass2->findOwned(false)
- );
-
- /** @var VersionedOwnershipTest\Related $related1 */
- $related1 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related1');
- $this->assertDOSEquals(
- [
- ['Title' => 'Attachment 1'],
- ['Title' => 'Attachment 2'],
- ['Title' => 'Attachment 5'],
- ],
- $related1->findOwned()
- );
-
- /** @var VersionedOwnershipTest\Related $related2 */
- $related2 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related2_published');
- $this->assertDOSEquals(
- [
- ['Title' => 'Attachment 3'],
- ['Title' => 'Attachment 4'],
- ['Title' => 'Attachment 5'],
- ],
- $related2->findOwned()
- );
- }
-
- /**
- * Test findOwners
- */
- public function testFindOwners()
- {
- /** @var VersionedOwnershipTest\Attachment $attachment1 */
- $attachment1 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment1');
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 1'],
- ['Title' => 'Subclass 1'],
- ],
- $attachment1->findOwners()
- );
-
- // Non-recursive search
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 1'],
- ],
- $attachment1->findOwners(false)
- );
-
- /** @var VersionedOwnershipTest\Attachment $attachment5 */
- $attachment5 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment5_published');
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 1'],
- ['Title' => 'Related 2'],
- ['Title' => 'Subclass 1'],
- ['Title' => 'Subclass 2'],
- ],
- $attachment5->findOwners()
- );
-
- // Non-recursive
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 1'],
- ['Title' => 'Related 2'],
- ],
- $attachment5->findOwners(false)
- );
-
- /** @var VersionedOwnershipTest\Related $related1 */
- $related1 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related1');
- $this->assertDOSEquals(
- [
- ['Title' => 'Subclass 1'],
- ],
- $related1->findOwners()
- );
- }
-
- /**
- * Test findOwners on Live stage
- */
- public function testFindOwnersLive()
- {
- // Modify a few records on stage
- $related2 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related2_published');
- $related2->Title .= ' Modified';
- $related2->write();
- $attachment3 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment3_published');
- $attachment3->Title .= ' Modified';
- $attachment3->write();
- $attachment4 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment4_published');
- $attachment4->delete();
- $subclass2ID = $this->idFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
-
- // Check that stage record is ok
- /** @var VersionedOwnershipTest\Subclass $subclass2Stage */
- $subclass2Stage = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, 'Stage')->byID($subclass2ID);
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2 Modified'],
- ['Title' => 'Attachment 3 Modified'],
- ['Title' => 'Attachment 5'],
- ['Title' => 'Related Many 4'],
- ],
- $subclass2Stage->findOwned()
- );
-
- // Non-recursive
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2 Modified'],
- ['Title' => 'Related Many 4'],
- ],
- $subclass2Stage->findOwned(false)
- );
-
- // Live records are unchanged
- /** @var VersionedOwnershipTest\Subclass $subclass2Live */
- $subclass2Live = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, 'Live')->byID($subclass2ID);
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2'],
- ['Title' => 'Attachment 3'],
- ['Title' => 'Attachment 4'],
- ['Title' => 'Attachment 5'],
- ['Title' => 'Related Many 4'],
- ],
- $subclass2Live->findOwned()
- );
-
- // Test non-recursive
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2'],
- ['Title' => 'Related Many 4'],
- ],
- $subclass2Live->findOwned(false)
- );
- }
-
- /**
- * Test that objects are correctly published recursively
- */
- public function testRecursivePublish()
- {
- /** @var VersionedOwnershipTest\Subclass $parent */
- $parent = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass1_published');
- $parentID = $parent->ID;
- $banner1 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany1_published');
- $banner2 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany2_published');
- $banner2ID = $banner2->ID;
-
- // Modify, Add, and Delete banners on stage
- $banner1->Title = 'Renamed Banner 1';
- $banner1->write();
-
- $banner2->delete();
-
- $banner4 = new VersionedOwnershipTest\RelatedMany();
- $banner4->Title = 'New Banner';
- $parent->Banners()->add($banner4);
-
- // Check state of objects before publish
- $oldLiveBanners = [
- ['Title' => 'Related Many 1'],
- ['Title' => 'Related Many 2'], // Will be unlinked (but not deleted)
- // `Related Many 3` isn't published
- ];
- $newBanners = [
- ['Title' => 'Renamed Banner 1'], // Renamed
- ['Title' => 'Related Many 3'], // Published without changes
- ['Title' => 'New Banner'], // Created
- ];
- $parentDraft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
- ->byID($parentID);
- $this->assertDOSEquals($newBanners, $parentDraft->Banners());
- $parentLive = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::LIVE)
- ->byID($parentID);
- $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
- $parentDraft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
- ->byID($parentID);
- $this->assertDOSEquals($newBanners, $parentDraft->Banners());
- $parentLive = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::LIVE)
- ->byID($parentID);
- $this->assertDOSEquals($newBanners, $parentLive->Banners());
-
- // Check that the deleted banner hasn't actually been deleted from the live stage,
- // but in fact has been unlinked.
- $banner2Live = Versioned::get_by_stage(VersionedOwnershipTest\RelatedMany::class, 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()
- );
- }
-
- /**
- * Test that owning objects get unpublished as needed
- */
- public function testRecursiveUnpublish()
- {
- // Unsaved objects can't be unpublished
- $unsaved = new VersionedOwnershipTest\Subclass();
- $this->assertFalse($unsaved->doUnpublish());
-
- // Draft-only objects can't be unpublished
- /** @var VersionedOwnershipTest\RelatedMany $banner3Unpublished */
- $banner3Unpublished = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany3');
- $this->assertFalse($banner3Unpublished->doUnpublish());
-
- // First test: mid-level unpublish; We expect that owners should be unpublished, but not
- // owned objects, nor other siblings shared by the same owner.
- $related2 = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related2_published');
- /** @var VersionedOwnershipTest\Attachment $attachment3 */
- $attachment3 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment3_published');
- /** @var VersionedOwnershipTest\RelatedMany $relatedMany4 */
- $relatedMany4 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany4_published');
- /** @var VersionedOwnershipTest\Related $related2 */
- $this->assertTrue($related2->doUnpublish());
- $subclass2 = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
-
- /** @var VersionedOwnershipTest\Subclass $subclass2 */
- $this->assertFalse($subclass2->isPublished()); // Owner IS unpublished
- $this->assertTrue($attachment3->isPublished()); // Owned object is NOT unpublished
- $this->assertTrue($relatedMany4->isPublished()); // Owned object by owner is NOT unpublished
-
- // Second test: multi-level unpublish should recursively cascade down all owning objects
- // Publish related2 again
- $subclass2->publishRecursive();
- $this->assertTrue($subclass2->isPublished());
- $this->assertTrue($related2->isPublished());
- $this->assertTrue($attachment3->isPublished());
-
- // Unpublish leaf node
- $this->assertTrue($attachment3->doUnpublish());
-
- // Now all owning objects (only) are unpublished
- $this->assertFalse($attachment3->isPublished()); // Unpublished because we just unpublished it
- $this->assertFalse($related2->isPublished()); // Unpublished because it owns attachment3
- $this->assertFalse($subclass2->isPublished()); // Unpublished ecause it owns related2
- $this->assertTrue($relatedMany4->isPublished()); // Stays live because recursion only affects owners not owned.
- }
-
- public function testRecursiveArchive()
- {
- // When archiving an object, any published owners should be unpublished at the same time
- // but NOT achived
-
- /** @var VersionedOwnershipTest\Attachment $attachment3 */
- $attachment3 = $this->objFromFixture(VersionedOwnershipTest\Attachment::class, 'attachment3_published');
- $attachment3ID = $attachment3->ID;
- $this->assertTrue($attachment3->doArchive());
-
- // This object is on neither stage nor live
- $stageAttachment = Versioned::get_by_stage(VersionedOwnershipTest\Attachment::class, Versioned::DRAFT)
- ->byID($attachment3ID);
- $liveAttachment = Versioned::get_by_stage(VersionedOwnershipTest\Attachment::class, Versioned::LIVE)
- ->byID($attachment3ID);
- $this->assertEmpty($stageAttachment);
- $this->assertEmpty($liveAttachment);
-
- // Owning object is unpublished only
- /** @var VersionedOwnershipTest\Related $stageOwner */
- $stageOwner = $this->objFromFixture(VersionedOwnershipTest\Related::class, 'related2_published');
- $this->assertTrue($stageOwner->isOnDraft());
- $this->assertFalse($stageOwner->isPublished());
-
- // Bottom level owning object is also unpublished
- /** @var VersionedOwnershipTest\Subclass $stageTopOwner */
- $stageTopOwner = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
- $this->assertTrue($stageTopOwner->isOnDraft());
- $this->assertFalse($stageTopOwner->isPublished());
- }
-
- public function testRecursiveRevertToLive()
- {
- /** @var VersionedOwnershipTest\Subclass $parent */
- $parent = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass1_published');
- $parentID = $parent->ID;
- $banner1 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany1_published');
- $banner2 = $this->objFromFixture(VersionedOwnershipTest\RelatedMany::class, 'relatedmany2_published');
- $banner2ID = $banner2->ID;
-
- // Modify, Add, and Delete banners on stage
- $banner1->Title = 'Renamed Banner 1';
- $banner1->write();
-
- $banner2->delete();
-
- $banner4 = new VersionedOwnershipTest\RelatedMany();
- $banner4->Title = 'New Banner';
- $banner4->write();
- $parent->Banners()->add($banner4);
-
- // Check state of objects before publish
- $liveBanners = [
- ['Title' => 'Related Many 1'],
- ['Title' => 'Related Many 2'],
- ];
- $modifiedBanners = [
- ['Title' => 'Renamed Banner 1'], // Renamed
- ['Title' => 'Related Many 3'], // Published without changes
- ['Title' => 'New Banner'], // Created
- ];
- $parentDraft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
- ->byID($parentID);
- $this->assertDOSEquals($modifiedBanners, $parentDraft->Banners());
- $parentLive = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::LIVE)
- ->byID($parentID);
- $this->assertDOSEquals($liveBanners, $parentLive->Banners());
-
- // When reverting parent, all records should be put back on stage
- $this->assertTrue($parent->doRevertToLive());
-
- // Now check each object has the correct state
- $parentDraft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
- ->byID($parentID);
- $this->assertDOSEquals($liveBanners, $parentDraft->Banners());
- $parentLive = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::LIVE)
- ->byID($parentID);
- $this->assertDOSEquals($liveBanners, $parentLive->Banners());
-
- // Check that the newly created banner, even though it still exist, has been
- // unlinked from the reverted draft record
- /** @var VersionedOwnershipTest\RelatedMany $banner4Draft */
- $banner4Draft = Versioned::get_by_stage(VersionedOwnershipTest\RelatedMany::class, Versioned::DRAFT)
- ->byID($banner4->ID);
- $this->assertTrue($banner4Draft->isOnDraft());
- $this->assertFalse($banner4Draft->isPublished());
- $this->assertEmpty($banner4Draft->PageID);
- }
-
- /**
- * Test that rolling back to a single version works recursively
- */
- public function testRecursiveRollback()
- {
- /** @var VersionedOwnershipTest\Subclass $subclass2 */
- $this->sleep(1);
- $subclass2 = $this->objFromFixture(VersionedOwnershipTest\Subclass::class, 'subclass2_published');
-
- // Create a few new versions
- $versions = [];
- for ($version = 1; $version <= 3; $version++) {
- // Write owned objects
- $this->sleep(1);
- foreach ($subclass2->findOwned(true) as $obj) {
- $obj->Title .= " - v{$version}";
- $obj->write();
- }
- // Write parent
- $this->sleep(1);
- $subclass2->Title .= " - v{$version}";
- $subclass2->write();
- $versions[$version] = $subclass2->Version;
- }
-
-
- // Check reverting to first version
- $this->sleep(1);
- $subclass2->doRollbackTo($versions[1]);
- /** @var VersionedOwnershipTest\Subclass $subclass2Draft */
- $subclass2Draft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
- ->byID($subclass2->ID);
- $this->assertEquals('Subclass 2 - v1', $subclass2Draft->Title);
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2 - v1'],
- ['Title' => 'Attachment 3 - v1'],
- ['Title' => 'Attachment 4 - v1'],
- ['Title' => 'Attachment 5 - v1'],
- ['Title' => 'Related Many 4 - v1'],
- ],
- $subclass2Draft->findOwned(true)
- );
-
- // Check rolling forward to a later version
- $this->sleep(1);
- $subclass2->doRollbackTo($versions[3]);
- /** @var VersionedOwnershipTest\Subclass $subclass2Draft */
- $subclass2Draft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
- ->byID($subclass2->ID);
- $this->assertEquals('Subclass 2 - v1 - v2 - v3', $subclass2Draft->Title);
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2 - v1 - v2 - v3'],
- ['Title' => 'Attachment 3 - v1 - v2 - v3'],
- ['Title' => 'Attachment 4 - v1 - v2 - v3'],
- ['Title' => 'Attachment 5 - v1 - v2 - v3'],
- ['Title' => 'Related Many 4 - v1 - v2 - v3'],
- ],
- $subclass2Draft->findOwned(true)
- );
-
- // And rolling back one version
- $this->sleep(1);
- $subclass2->doRollbackTo($versions[2]);
- /** @var VersionedOwnershipTest\Subclass $subclass2Draft */
- $subclass2Draft = Versioned::get_by_stage(VersionedOwnershipTest\Subclass::class, Versioned::DRAFT)
- ->byID($subclass2->ID);
- $this->assertEquals('Subclass 2 - v1 - v2', $subclass2Draft->Title);
- $this->assertDOSEquals(
- [
- ['Title' => 'Related 2 - v1 - v2'],
- ['Title' => 'Attachment 3 - v1 - v2'],
- ['Title' => 'Attachment 4 - v1 - v2'],
- ['Title' => 'Attachment 5 - v1 - v2'],
- ['Title' => 'Related Many 4 - v1 - v2'],
- ],
- $subclass2Draft->findOwned(true)
- );
- }
-
- /**
- * Test that you can find owners without owned_by being defined explicitly
- */
- public function testInferedOwners()
- {
- // Make sure findOwned() works
- /** @var VersionedOwnershipTest\TestPage $page1 */
- $page1 = $this->objFromFixture(VersionedOwnershipTest\TestPage::class, 'page1_published');
- /** @var VersionedOwnershipTest\TestPage $page2 */
- $page2 = $this->objFromFixture(VersionedOwnershipTest\TestPage::class, 'page2_published');
- $this->assertDOSEquals(
- [
- ['Title' => 'Banner 1'],
- ['Title' => 'Image 1'],
- ['Title' => 'Custom 1'],
- ],
- $page1->findOwned()
- );
- $this->assertDOSEquals(
- [
- ['Title' => 'Banner 2'],
- ['Title' => 'Banner 3'],
- ['Title' => 'Image 1'],
- ['Title' => 'Image 2'],
- ['Title' => 'Custom 2'],
- ],
- $page2->findOwned()
- );
-
- // Check that findOwners works
- /** @var VersionedOwnershipTest\Image $image1 */
- $image1 = $this->objFromFixture(VersionedOwnershipTest\Image::class, 'image1_published');
- /** @var VersionedOwnershipTest\Image $image2 */
- $image2 = $this->objFromFixture(VersionedOwnershipTest\Image::class, 'image2_published');
-
- $this->assertDOSEquals(
- [
- ['Title' => 'Banner 1'],
- ['Title' => 'Banner 2'],
- ['Title' => 'Page 1'],
- ['Title' => 'Page 2'],
- ],
- $image1->findOwners()
- );
- $this->assertDOSEquals(
- [
- ['Title' => 'Banner 1'],
- ['Title' => 'Banner 2'],
- ],
- $image1->findOwners(false)
- );
- $this->assertDOSEquals(
- [
- ['Title' => 'Banner 3'],
- ['Title' => 'Page 2'],
- ],
- $image2->findOwners()
- );
- $this->assertDOSEquals(
- [
- ['Title' => 'Banner 3'],
- ],
- $image2->findOwners(false)
- );
-
- // Test custom relation can findOwners()
- /** @var VersionedOwnershipTest\CustomRelation $custom1 */
- $custom1 = $this->objFromFixture(VersionedOwnershipTest\CustomRelation::class, 'custom1_published');
- $this->assertDOSEquals(
- [['Title' => 'Page 1']],
- $custom1->findOwners()
- );
- }
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest.yml b/tests/php/ORM/VersionedOwnershipTest.yml
deleted file mode 100644
index b5d87c123..000000000
--- a/tests/php/ORM/VersionedOwnershipTest.yml
+++ /dev/null
@@ -1,84 +0,0 @@
-SilverStripe\ORM\Tests\VersionedOwnershipTest\Attachment:
- attachment1:
- Title: 'Attachment 1'
- attachment2:
- Title: 'Attachment 2'
- attachment3_published:
- Title: 'Attachment 3'
- attachment4_published:
- Title: 'Attachment 4'
- attachment5_published:
- Title: 'Attachment 5'
-
-SilverStripe\ORM\Tests\VersionedOwnershipTest\Related:
- related1:
- Title: 'Related 1'
- Attachments:
- - =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Attachment.attachment1
- - =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Attachment.attachment2
- - =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Attachment.attachment5_published
- related2_published:
- Title: 'Related 2'
- Attachments:
- - =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Attachment.attachment3_published
- - =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Attachment.attachment4_published
- - =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Attachment.attachment5_published
-
-SilverStripe\ORM\Tests\VersionedOwnershipTest\Subclass:
- subclass1_published:
- Title: 'Subclass 1'
- Related: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Related.related1
- subclass2_published:
- Title: 'Subclass 2'
- Related: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Related.related2_published
-
-SilverStripe\ORM\Tests\VersionedOwnershipTest\RelatedMany:
- relatedmany1_published:
- Title: 'Related Many 1'
- Page: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Subclass.subclass1_published
- relatedmany2_published:
- Title: 'Related Many 2'
- Page: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Subclass.subclass1_published
- relatedmany3:
- Title: 'Related Many 3'
- Page: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Subclass.subclass1_published
- relatedmany4_published:
- Title: 'Related Many 4'
- Page: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Subclass.subclass2_published
-
-SilverStripe\ORM\Tests\VersionedOwnershipTest\TestObject:
- object1:
- Title: 'Object 1'
-
-SilverStripe\ORM\Tests\VersionedOwnershipTest\Image:
- image1_published:
- Title: 'Image 1'
- image2_published:
- Title: 'Image 2'
-
-SilverStripe\ORM\Tests\VersionedOwnershipTest\Banner:
- banner1_published:
- Title: 'Banner 1'
- Image: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Image.image1_published
- banner2_published:
- Title: 'Banner 2'
- Image: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Image.image1_published
- banner3_published:
- Title: 'Banner 3'
- Image: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Image.image2_published
-
-SilverStripe\ORM\Tests\VersionedOwnershipTest\TestPage:
- page1_published:
- Title: 'Page 1'
- Banners: =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Banner.banner1_published
- page2_published:
- Title: 'Page 2'
- Banners:
- - =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Banner.banner2_published
- - =>SilverStripe\ORM\Tests\VersionedOwnershipTest\Banner.banner3_published
-
-SilverStripe\ORM\Tests\VersionedOwnershipTest\CustomRelation:
- custom1_published:
- Title: 'Custom 1'
- custom2_published:
- Title: 'Custom 2'
diff --git a/tests/php/ORM/VersionedOwnershipTest/Attachment.php b/tests/php/ORM/VersionedOwnershipTest/Attachment.php
deleted file mode 100644
index f3075c563..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/Attachment.php
+++ /dev/null
@@ -1,32 +0,0 @@
- 'Varchar(255)',
- );
-
- private static $belongs_many_many = array(
- 'AttachedTo' => 'SilverStripe\\ORM\\Tests\\VersionedOwnershipTest\\Related.Attachments'
- );
-
- private static $owned_by = array(
- 'AttachedTo'
- );
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest/Banner.php b/tests/php/ORM/VersionedOwnershipTest/Banner.php
deleted file mode 100644
index 4db0eee41..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/Banner.php
+++ /dev/null
@@ -1,34 +0,0 @@
- 'Varchar(255)',
- );
-
- private static $has_one = array(
- 'Image' => VersionedOwnershipTest\Image::class,
- );
-
- private static $owns = array(
- 'Image',
- );
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest/CustomRelation.php b/tests/php/ORM/VersionedOwnershipTest/CustomRelation.php
deleted file mode 100644
index 5fcd08c51..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/CustomRelation.php
+++ /dev/null
@@ -1,41 +0,0 @@
- 'Varchar(255)',
- );
-
- private static $owned_by = array(
- 'Pages'
- );
-
- /**
- * All pages with the same number. E.g. 'Page 1' owns 'Custom 1'
- *
- * @return DataList
- */
- public function Pages()
- {
- $title = str_replace('Custom', 'Page', $this->Title);
- return TestPage::get()->filter('Title', $title);
- }
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest/Image.php b/tests/php/ORM/VersionedOwnershipTest/Image.php
deleted file mode 100644
index 302c95b57..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/Image.php
+++ /dev/null
@@ -1,25 +0,0 @@
- 'Varchar(255)',
- );
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest/Related.php b/tests/php/ORM/VersionedOwnershipTest/Related.php
deleted file mode 100644
index 9f7553c4d..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/Related.php
+++ /dev/null
@@ -1,44 +0,0 @@
- 'Varchar(255)',
- );
-
- private static $has_many = array(
- 'Parents' => 'SilverStripe\\ORM\\Tests\\VersionedOwnershipTest\\Subclass.Related',
- );
-
- private static $owned_by = array(
- 'Parents',
- );
-
- private static $many_many = array(
- // Note : Currently unversioned, take care
- 'Attachments' => Attachment::class,
- );
-
- private static $owns = array(
- 'Attachments',
- );
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest/RelatedMany.php b/tests/php/ORM/VersionedOwnershipTest/RelatedMany.php
deleted file mode 100644
index 4d607adcc..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/RelatedMany.php
+++ /dev/null
@@ -1,33 +0,0 @@
- 'Varchar(255)',
- );
-
- private static $has_one = array(
- 'Page' => Subclass::class,
- );
-
- private static $owned_by = array(
- 'Page'
- );
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest/Subclass.php b/tests/php/ORM/VersionedOwnershipTest/Subclass.php
deleted file mode 100644
index cbc195959..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/Subclass.php
+++ /dev/null
@@ -1,33 +0,0 @@
- 'Text',
- );
-
- private static $has_one = array(
- 'Related' => Related::class,
- );
-
- private static $has_many = array(
- 'Banners' => RelatedMany::class,
- );
-
- private static $table_name = 'VersionedOwnershipTest_Subclass';
-
- private static $owns = array(
- 'Related',
- 'Banners',
- );
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest/TestObject.php b/tests/php/ORM/VersionedOwnershipTest/TestObject.php
deleted file mode 100644
index 6bbf0bb99..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/TestObject.php
+++ /dev/null
@@ -1,24 +0,0 @@
- 'Varchar(255)',
- 'Content' => 'Text',
- );
-}
diff --git a/tests/php/ORM/VersionedOwnershipTest/TestPage.php b/tests/php/ORM/VersionedOwnershipTest/TestPage.php
deleted file mode 100644
index fa68d3856..000000000
--- a/tests/php/ORM/VersionedOwnershipTest/TestPage.php
+++ /dev/null
@@ -1,46 +0,0 @@
- 'Varchar(255)',
- );
-
- private static $many_many = array(
- 'Banners' => Banner::class,
- );
-
- private static $owns = array(
- 'Banners',
- 'Custom'
- );
-
- /**
- * All custom objects with the same number. E.g. 'Page 1' owns 'Custom 1'
- *
- * @return DataList
- */
- public function Custom()
- {
- $title = str_replace('Page', 'Custom', $this->Title);
- return CustomRelation::get()->filter('Title', $title);
- }
-}
diff --git a/tests/php/ORM/VersionedTest.php b/tests/php/ORM/VersionedTest.php
deleted file mode 100644
index 80cdbc438..000000000
--- a/tests/php/ORM/VersionedTest.php
+++ /dev/null
@@ -1,1332 +0,0 @@
-
- array('value' => true, 'message' => 'Unique indexes are unique in main table'),
- 'VersionedTest_WithIndexes_Versions' =>
- array('value' => false, 'message' => 'Unique indexes are no longer unique in _Versions table'),
- 'VersionedTest_WithIndexes_Live' =>
- array('value' => true, 'message' => 'Unique indexes are unique in _Live table'),
- );
-
- // Test each table's performance
- foreach ($tableExpectations as $tableName => $expectation) {
- $indexes = DB::get_schema()->indexList($tableName);
-
- // Check for presence of all unique indexes
- $indexColumns = array_map(
- function ($index) {
- return $index['value'];
- },
- $indexes
- );
- sort($indexColumns);
- $expectedColumns = array('"UniqA"', '"UniqS"');
- $this->assertEquals(
- array_values($expectedColumns),
- array_values(array_intersect($indexColumns, $expectedColumns)),
- "$tableName has both indexes"
- );
-
- // Check unique -> non-unique conversion
- foreach ($indexes as $indexKey => $indexSpec) {
- if (in_array($indexSpec['value'], $expectedColumns)) {
- $isUnique = $indexSpec['type'] === 'unique';
- $this->assertEquals($isUnique, $expectation['value'], $expectation['message']);
- }
- }
- }
- }
-
- public function testDeletingOrphanedVersions()
- {
- $obj = new VersionedTest\Subclass();
- $obj->ExtraField = 'Foo'; // ensure that child version table gets written
- $obj->write();
- $obj->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
-
- $obj->ExtraField = 'Bar'; // ensure that child version table gets written
- $obj->write();
- $obj->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
-
- $versions = DB::query(
- "SELECT COUNT(*) FROM \"VersionedTest_Subclass_Versions\""
- . " WHERE \"RecordID\" = '$obj->ID'"
- )->value();
-
- $this->assertGreaterThan(0, $versions, 'At least 1 version exists in the history of the page');
-
- // Force orphaning of all versions created earlier, only on parent record.
- // The child versiones table should still have the correct relationship
- DB::query("DELETE FROM \"VersionedTest_DataObject_Versions\" WHERE \"RecordID\" = $obj->ID");
-
- // insert a record with no primary key (ID)
- DB::query("INSERT INTO \"VersionedTest_DataObject_Versions\" (\"RecordID\") VALUES ($obj->ID)");
-
- // run the script which should clean that up
- $obj->augmentDatabase();
-
- $versions = DB::query(
- "SELECT COUNT(*) FROM \"VersionedTest_Subclass_Versions\""
- . " WHERE \"RecordID\" = '$obj->ID'"
- )->value();
- $this->assertEquals(0, $versions, 'Orphaned versions on child tables are removed');
-
- // test that it doesn't delete records that we need
- $obj->write();
- $obj->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
-
- $count = DB::query(
- "SELECT COUNT(*) FROM \"VersionedTest_Subclass_Versions\""
- . " WHERE \"RecordID\" = '$obj->ID'"
- )->value();
- $obj->augmentDatabase();
-
- $count2 = DB::query(
- "SELECT COUNT(*) FROM \"VersionedTest_Subclass_Versions\""
- . " WHERE \"RecordID\" = '$obj->ID'"
- )->value();
-
- $this->assertEquals($count, $count2);
- }
-
- public function testCustomTable()
- {
- $obj = new VersionedTest\CustomTable();
- $obj->Title = 'my object';
- $obj->write();
- $id = $obj->ID;
- $obj->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $obj->Title = 'new title';
- $obj->write();
-
- $liveRecord = Versioned::get_by_stage(VersionedTest\CustomTable::class, Versioned::LIVE)->byID($id);
- $draftRecord = Versioned::get_by_stage(VersionedTest\CustomTable::class, Versioned::DRAFT)->byID($id);
-
- $this->assertEquals('my object', $liveRecord->Title);
- $this->assertEquals('new title', $draftRecord->Title);
- }
-
- /**
- * Test that publishing from invalid stage will throw exception
- */
- public function testInvalidPublish()
- {
- $obj = new VersionedTest\Subclass();
- $obj->ExtraField = 'Foo'; // ensure that child version table gets written
- $obj->write();
- $class = VersionedTest\TestObject::class;
- $this->setExpectedException(
- 'InvalidArgumentException',
- "Can't find {$class}#{$obj->ID} in stage Live"
- );
-
- // Fail publishing from live to stage
- $obj->copyVersionToStage(Versioned::LIVE, Versioned::DRAFT);
- }
-
- public function testDuplicate()
- {
- $obj1 = new VersionedTest\Subclass();
- $obj1->ExtraField = 'Foo';
- $obj1->write(); // version 1
- $obj1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $obj1->ExtraField = 'Foo2';
- $obj1->write(); // version 2
-
- // Make duplicate
- $obj2 = $obj1->duplicate();
-
- // Check records differ
- $this->assertNotEquals($obj1->ID, $obj2->ID);
- $this->assertEquals(2, $obj1->Version);
- $this->assertEquals(1, $obj2->Version);
- }
-
- public function testForceChangeUpdatesVersion()
- {
- $obj = new VersionedTest\TestObject();
- $obj->Name = "test";
- $obj->write();
-
- $oldVersion = $obj->Version;
- $obj->forceChange();
- $obj->write();
-
- $this->assertTrue(
- ($obj->Version > $oldVersion),
- "A object Version is increased when just calling forceChange() without any other changes"
- );
- }
-
- /**
- * Test Versioned::get_including_deleted()
- */
- public function testGetIncludingDeleted()
- {
- // Get all ids of pages
- $allPageIDs = DataObject::get(
- VersionedTest\TestObject::class,
- "\"ParentID\" = 0",
- "\"VersionedTest_DataObject\".\"ID\" ASC"
- )->column('ID');
-
- // Modify a page, ensuring that the Version ID and Record ID will differ,
- // and then subsequently delete it
- $targetPage = $this->objFromFixture(VersionedTest\TestObject::class, 'page3');
- $targetPage->Content = 'To be deleted';
- $targetPage->write();
- $targetPage->delete();
-
- // Get all items, ignoring deleted
- $remainingPages = DataObject::get(
- VersionedTest\TestObject::class,
- "\"ParentID\" = 0",
- "\"VersionedTest_DataObject\".\"ID\" ASC"
- );
- // Check that page 3 has gone
- $this->assertNotNull($remainingPages);
- $this->assertEquals(array("Page 1", "Page 2", "Subclass Page 1"), $remainingPages->column('Title'));
-
- // Get all including deleted
- $allPages = Versioned::get_including_deleted(
- VersionedTest\TestObject::class,
- "\"ParentID\" = 0",
- "\"VersionedTest_DataObject\".\"ID\" ASC"
- );
- // Check that page 3 is still there
- $this->assertEquals(array("Page 1", "Page 2", "Page 3", "Subclass Page 1"), $allPages->column('Title'));
-
- // Check that the returned pages have the correct IDs
- $this->assertEquals($allPageIDs, $allPages->column('ID'));
-
- // Check that this still works if we switch to reading the other stage
- Versioned::set_stage(Versioned::LIVE);
- $allPages = Versioned::get_including_deleted(
- VersionedTest\TestObject::class,
- "\"ParentID\" = 0",
- "\"VersionedTest_DataObject\".\"ID\" ASC"
- );
- $this->assertEquals(array("Page 1", "Page 2", "Page 3", "Subclass Page 1"), $allPages->column('Title'));
-
- // Check that the returned pages still have the correct IDs
- $this->assertEquals($allPageIDs, $allPages->column('ID'));
- }
-
- public function testVersionedFieldsAdded()
- {
- $obj = new VersionedTest\TestObject();
- // Check that the Version column is added as a full-fledged column
- $this->assertInstanceOf('SilverStripe\\ORM\\FieldType\\DBInt', $obj->dbObject('Version'));
-
- $obj2 = new VersionedTest\Subclass();
- // Check that the Version column is added as a full-fledged column
- $this->assertInstanceOf('SilverStripe\\ORM\\FieldType\\DBInt', $obj2->dbObject('Version'));
- }
-
- public function testVersionedFieldsNotInCMS()
- {
- $obj = new VersionedTest\TestObject();
-
- // the version field in cms causes issues with Versioned::augmentWrite()
- $this->assertNull($obj->getCMSFields()->dataFieldByName('Version'));
- }
-
- public function testPublishCreateNewVersion()
- {
- /** @var VersionedTest\TestObject $page1 */
- $page1 = $this->objFromFixture(VersionedTest\TestObject::class, 'page1');
- $page1->Content = 'orig';
- $page1->write();
- $firstVersion = $page1->Version;
- $page1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE, false);
- $this->assertEquals(
- $firstVersion,
- $page1->Version,
- 'publish() with $createNewVersion=FALSE does not create a new version'
- );
-
- $page1->Content = 'changed';
- $page1->write();
- $secondVersion = $page1->Version;
- $this->assertTrue($firstVersion < $secondVersion, 'write creates new version');
-
- $page1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE, true);
- $thirdVersion = Versioned::get_latest_version(VersionedTest\TestObject::class, $page1->ID)->Version;
- $liveVersion = Versioned::get_versionnumber_by_stage(VersionedTest\TestObject::class, 'Live', $page1->ID);
- $stageVersion = Versioned::get_versionnumber_by_stage(VersionedTest\TestObject::class, 'Stage', $page1->ID);
- $this->assertTrue(
- $secondVersion < $thirdVersion,
- 'publish() with $createNewVersion=TRUE creates a new version'
- );
- $this->assertEquals(
- $liveVersion,
- $thirdVersion,
- 'publish() with $createNewVersion=TRUE publishes to live'
- );
- $this->assertEquals(
- $stageVersion,
- $thirdVersion,
- 'publish() with $createNewVersion=TRUE also updates draft'
- );
- }
-
- public function testRollbackTo()
- {
- $page1 = $this->objFromFixture(VersionedTest\AnotherSubclass::class, 'subclass1');
- $page1->Content = 'orig';
- $page1->write();
- $page1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $origVersion = $page1->Version;
-
- $page1->Content = 'changed';
- $page1->write();
- $page1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $changedVersion = $page1->Version;
-
- $page1->doRollbackTo($origVersion);
- $page1 = Versioned::get_one_by_stage(
- VersionedTest\TestObject::class,
- 'Stage',
- array(
- '"VersionedTest_DataObject"."ID" = ?' => $page1->ID
- )
- );
-
- $this->assertTrue($page1->Version == $changedVersion + 1, 'Create a new higher version number');
- $this->assertEquals('orig', $page1->Content, 'Copies the content from the old version');
-
- // check db entries
- $version = DB::prepared_query(
- "SELECT MAX(\"Version\") FROM \"VersionedTest_DataObject_Versions\" WHERE \"RecordID\" = ?",
- array($page1->ID)
- )->value();
- $this->assertEquals($page1->Version, $version, 'Correct entry in VersionedTest_DataObject_Versions');
-
- $version = DB::prepared_query(
- "SELECT MAX(\"Version\") FROM \"VersionedTest_AnotherSubclass_Versions\" WHERE \"RecordID\" = ?",
- array($page1->ID)
- )->value();
- $this->assertEquals($page1->Version, $version, 'Correct entry in VersionedTest_AnotherSubclass_Versions');
- }
-
- public function testDeleteFromStage()
- {
- $page1 = $this->objFromFixture(VersionedTest\TestObject::class, 'page1');
- $pageID = $page1->ID;
-
- $page1->Content = 'orig';
- $page1->write();
- $page1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
-
- $this->assertEquals(
- 1,
- DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value()
- );
- $this->assertEquals(
- 1,
- DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value()
- );
-
- $page1->deleteFromStage('Live');
-
- // Confirm that deleteFromStage() doesn't manipulate the original record
- $this->assertEquals($pageID, $page1->ID);
-
- $this->assertEquals(
- 1,
- DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value()
- );
- $this->assertEquals(
- 0,
- DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value()
- );
-
- $page1->delete();
-
- $this->assertEquals(0, $page1->ID);
- $this->assertEquals(
- 0,
- DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject" WHERE "ID" = '.$pageID)->value()
- );
- $this->assertEquals(
- 0,
- DB::query('SELECT COUNT(*) FROM "VersionedTest_DataObject_Live" WHERE "ID" = '.$pageID)->value()
- );
- }
-
- public function testWritingNewToStage()
- {
- $origReadingMode = Versioned::get_reading_mode();
-
- Versioned::set_stage(Versioned::DRAFT);
- $page = new VersionedTest\TestObject();
- $page->Title = "testWritingNewToStage";
- $page->URLSegment = "testWritingNewToStage";
- $page->write();
-
- $live = Versioned::get_by_stage(
- VersionedTest\TestObject::class,
- 'Live',
- array(
- '"VersionedTest_DataObject_Live"."ID"' => $page->ID
- )
- );
- $this->assertEquals(0, $live->count());
-
- $stage = Versioned::get_by_stage(
- VersionedTest\TestObject::class,
- 'Stage',
- array(
- '"VersionedTest_DataObject"."ID"' => $page->ID
- )
- );
- $this->assertEquals(1, $stage->count());
- $this->assertEquals($stage->First()->Title, 'testWritingNewToStage');
-
- Versioned::set_reading_mode($origReadingMode);
- }
-
- /**
- * Writing a page to live should update both draft and live tables
- */
- public function testWritingNewToLive()
- {
- $origReadingMode = Versioned::get_reading_mode();
-
- Versioned::set_stage(Versioned::LIVE);
- $page = new VersionedTest\TestObject();
- $page->Title = "testWritingNewToLive";
- $page->URLSegment = "testWritingNewToLive";
- $page->write();
-
- $live = Versioned::get_by_stage(
- VersionedTest\TestObject::class,
- 'Live',
- array(
- '"VersionedTest_DataObject_Live"."ID"' => $page->ID
- )
- );
- $this->assertEquals(1, $live->count());
- $liveRecord = $live->First();
- $this->assertEquals($liveRecord->Title, 'testWritingNewToLive');
-
- $stage = Versioned::get_by_stage(
- VersionedTest\TestObject::class,
- 'Stage',
- array(
- '"VersionedTest_DataObject"."ID"' => $page->ID
- )
- );
- $this->assertEquals(1, $stage->count());
- $stageRecord = $stage->first();
- $this->assertEquals($stageRecord->Title, 'testWritingNewToLive');
-
- // Both records have the same version
- $this->assertEquals($liveRecord->Version, $stageRecord->Version);
-
- Versioned::set_reading_mode($origReadingMode);
- }
-
- /**
- * Tests DataObject::hasOwnTableDatabaseField
- */
- public function testHasOwnTableDatabaseFieldWithVersioned()
- {
- $schema = DataObject::getSchema();
-
- $this->assertNull(
- $schema->fieldSpec(DataObject::class, 'Version', DataObjectSchema::UNINHERITED),
- 'Plain models have no version field.'
- );
- $this->assertEquals(
- 'Int',
- $schema->fieldSpec(VersionedTest\TestObject::class, 'Version', DataObjectSchema::UNINHERITED),
- 'The versioned ext adds an Int version field.'
- );
- $this->assertNull(
- $schema->fieldSpec(VersionedTest\Subclass::class, 'Version', DataObjectSchema::UNINHERITED),
- 'Sub-classes of a versioned model don\'t have a Version field.'
- );
- $this->assertNull(
- $schema->fieldSpec(VersionedTest\AnotherSubclass::class, 'Version', DataObjectSchema::UNINHERITED),
- 'Sub-classes of a versioned model don\'t have a Version field.'
- );
- $this->assertEquals(
- 'Varchar(255)',
- $schema->fieldSpec(VersionedTest\UnversionedWithField::class, 'Version', DataObjectSchema::UNINHERITED),
- 'Models w/o Versioned can have their own Version field.'
- );
- }
-
- /**
- * Test that SQLSelect::queriedTables() applies the version-suffixes properly.
- */
- public function testQueriedTables()
- {
- Versioned::set_stage(Versioned::LIVE);
-
- $this->assertEquals(
- array(
- 'VersionedTest_DataObject_Live',
- 'VersionedTest_Subclass_Live',
- ),
- DataObject::get(VersionedTest\Subclass::class)->dataQuery()->query()->queriedTables()
- );
- }
-
- /**
- * Virtual "sleep" that doesn't actually slow execution, only advances DBDateTime::now()
- *
- * @param int $minutes
- */
- protected function sleep($minutes)
- {
- $now = DBDatetime::now();
- $date = DateTime::createFromFormat('Y-m-d H:i:s', $now->getValue());
- $date->modify("+{$minutes} minutes");
- DBDatetime::set_mock_now($date->format('Y-m-d H:i:s'));
- }
-
- /**
- * Tests records selected by specific version
- */
- public function testGetVersion()
- {
- // Create a few initial versions to ensure this version
- // doesn't clash with child versions
- $this->sleep(1);
- /** @var VersionedTest\TestObject $page2 */
- $page2 = $this->objFromFixture(VersionedTest\TestObject::class, 'page2');
- $page2->Title = 'dummy1';
- $page2->write();
- $this->sleep(1);
- $page2->Title = 'dummy2';
- $page2->write();
- $this->sleep(1);
- $page2->Title = 'Page 2 - v1';
- $page2->write();
- $version1Date = $page2->LastEdited;
- $version1 = $page2->Version;
-
- // Create another version where this object and some
- // child records have been modified
- $this->sleep(1);
- /** @var VersionedTest\TestObject $page2a */
- $page2a = $this->objFromFixture(VersionedTest\TestObject::class, 'page2a');
- $page2a->Title = 'Page 2a - v2';
- $page2a->write();
- $this->sleep(1);
- $page2->Title = 'Page 2 - v2';
- $page2->write();
- $version2Date = $page2->LastEdited;
- $version2 = $page2->Version;
- $this->assertGreaterThan($version1, $version2);
- $this->assertDOSEquals(
- [
- ['Title' => 'Page 2a - v2'],
- ['Title' => 'Page 2b'],
- ],
- $page2->Children()
- );
-
- // test selecting v1
- /** @var VersionedTest\TestObject $page2v1 */
- $page2v1 = Versioned::get_version(VersionedTest\TestObject::class, $page2->ID, $version1);
- $this->assertEquals('Page 2 - v1', $page2v1->Title);
-
- // When selecting v1, related records should by filtered by
- // the modified date of that version
- $archiveParms = [
- 'Versioned.mode' => 'archive',
- 'Versioned.date' => $version1Date
- ];
- $this->assertEquals($archiveParms, $page2v1->getInheritableQueryParams());
- $this->assertArraySubset($archiveParms, $page2v1->Children()->getQueryParams());
- $this->assertDOSEquals(
- [
- ['Title' => 'Page 2a'],
- ['Title' => 'Page 2b'],
- ],
- $page2v1->Children()
- );
-
- // When selecting v2, we get the same as on stage
- /** @var VersionedTest\TestObject $page2v2 */
- $page2v2 = Versioned::get_version(VersionedTest\TestObject::class, $page2->ID, $version2);
- $this->assertEquals('Page 2 - v2', $page2v2->Title);
-
- // When selecting v2, related records should by filtered by
- // the modified date of that version
- $archiveParms = [
- 'Versioned.mode' => 'archive',
- 'Versioned.date' => $version2Date
- ];
- $this->assertEquals($archiveParms, $page2v2->getInheritableQueryParams());
- $this->assertArraySubset($archiveParms, $page2v2->Children()->getQueryParams());
- $this->assertDOSEquals(
- [
- ['Title' => 'Page 2a - v2'],
- ['Title' => 'Page 2b'],
- ],
- $page2v2->Children()
- );
- }
-
- public function testGetVersionWhenClassnameChanged()
- {
- $obj = new VersionedTest\TestObject;
- $obj->Name = "test";
- $obj->write();
- $obj->Name = "test2";
- $obj->ClassName = VersionedTest\Subclass::class;
- $obj->write();
- $subclassVersion = $obj->Version;
-
- $obj->Name = "test3";
- $obj->ClassName = VersionedTest\TestObject::class;
- $obj->write();
-
- // We should be able to pass the subclass and still get the correct class back
- $obj2 = Versioned::get_version(VersionedTest\Subclass::class, $obj->ID, $subclassVersion);
- $this->assertInstanceOf(VersionedTest\Subclass::class, $obj2);
- $this->assertEquals("test2", $obj2->Name);
-
- $obj3 = Versioned::get_latest_version(VersionedTest\Subclass::class, $obj->ID);
- $this->assertEquals("test3", $obj3->Name);
- $this->assertInstanceOf(VersionedTest\TestObject::class, $obj3);
- }
-
- public function testArchiveVersion()
- {
- // In 2005 this file was created
- DBDatetime::set_mock_now('2005-01-01 00:00:00');
- $testPage = new VersionedTest\Subclass();
- $testPage->Title = 'Archived page';
- $testPage->Content = 'This is the content from 2005';
- $testPage->ExtraField = '2005';
- $testPage->write();
-
- // In 2007 we updated it
- DBDatetime::set_mock_now('2007-01-01 00:00:00');
- $testPage->Content = "It's 2007 already!";
- $testPage->ExtraField = '2007';
- $testPage->write();
-
- // In 2009 we updated it again
- DBDatetime::set_mock_now('2009-01-01 00:00:00');
- $testPage->Content = "I'm enjoying 2009";
- $testPage->ExtraField = '2009';
- $testPage->write();
-
- // End mock, back to the present day:)
- DBDatetime::clear_mock_now();
-
- // Test 1 - 2006 Content
- singleton(VersionedTest\Subclass::class)->flushCache(true);
- Versioned::set_reading_mode('Archive.2006-01-01 00:00:00');
- $testPage2006 = DataObject::get(VersionedTest\Subclass::class)->filter(array('Title' => 'Archived page'))->first();
- $this->assertInstanceOf(VersionedTest\Subclass::class, $testPage2006);
- $this->assertEquals("2005", $testPage2006->ExtraField);
- $this->assertEquals("This is the content from 2005", $testPage2006->Content);
-
- // Test 2 - 2008 Content
- singleton(VersionedTest\Subclass::class)->flushCache(true);
- Versioned::set_reading_mode('Archive.2008-01-01 00:00:00');
- $testPage2008 = DataObject::get(VersionedTest\Subclass::class)->filter(array('Title' => 'Archived page'))->first();
- $this->assertInstanceOf(VersionedTest\Subclass::class, $testPage2008);
- $this->assertEquals("2007", $testPage2008->ExtraField);
- $this->assertEquals("It's 2007 already!", $testPage2008->Content);
-
- // Test 3 - Today
- singleton(VersionedTest\Subclass::class)->flushCache(true);
- Versioned::set_reading_mode('Stage.Stage');
- $testPageCurrent = DataObject::get(VersionedTest\Subclass::class)->filter(array('Title' => 'Archived page'))
- ->first();
- $this->assertInstanceOf(VersionedTest\Subclass::class, $testPageCurrent);
- $this->assertEquals("2009", $testPageCurrent->ExtraField);
- $this->assertEquals("I'm enjoying 2009", $testPageCurrent->Content);
- }
-
- /**
- * Test that archive works on live stage
- */
- public function testArchiveLive()
- {
- Versioned::set_stage(Versioned::LIVE);
- $this->logInWithPermission('ADMIN');
- $record = new VersionedTest\TestObject();
- $record->Name = 'test object';
- // Writing in live mode should write to draft as well
- $record->write();
- $recordID = $record->ID;
- $this->assertTrue($record->isPublished());
- $this->assertTrue($record->isOnDraft());
-
- // Delete in live
- /** @var VersionedTest\TestObject $recordLive */
- $recordLive = VersionedTest\TestObject::get()->byID($recordID);
- $recordLive->doArchive();
- $this->assertFalse($recordLive->isPublished());
- $this->assertFalse($recordLive->isOnDraft());
- }
-
- /**
- * Test archive works on draft
- */
- public function testArchiveDraft()
- {
- Versioned::set_stage(Versioned::DRAFT);
- $this->logInWithPermission('ADMIN');
- $record = new VersionedTest\TestObject();
- $record->Name = 'test object';
-
- // Writing in draft mode requires publishing to effect on live
- $record->write();
- $record->publishRecursive();
- $recordID = $record->ID;
- $this->assertTrue($record->isPublished());
- $this->assertTrue($record->isOnDraft());
-
- // Delete in draft
- /** @var VersionedTest\TestObject $recordDraft */
- $recordDraft = VersionedTest\TestObject::get()->byID($recordID);
- $recordDraft->doArchive();
- $this->assertFalse($recordDraft->isPublished());
- $this->assertFalse($recordDraft->isOnDraft());
- }
-
- public function testAllVersions()
- {
- // In 2005 this file was created
- DBDatetime::set_mock_now('2005-01-01 00:00:00');
- $testPage = new VersionedTest\Subclass();
- $testPage->Title = 'Archived page';
- $testPage->Content = 'This is the content from 2005';
- $testPage->ExtraField = '2005';
- $testPage->write();
-
- // In 2007 we updated it
- DBDatetime::set_mock_now('2007-01-01 00:00:00');
- $testPage->Content = "It's 2007 already!";
- $testPage->ExtraField = '2007';
- $testPage->write();
-
- // Check both versions are returned
- $versions = Versioned::get_all_versions(VersionedTest\Subclass::class, $testPage->ID);
- $content = array();
- $extraFields = array();
- foreach ($versions as $version) {
- $content[] = $version->Content;
- $extraFields[] = $version->ExtraField;
- }
-
- $this->assertEquals($versions->Count(), 2, 'All versions returned');
- $this->assertEquals(
- $content,
- array('This is the content from 2005', "It's 2007 already!"),
- 'Version fields returned'
- );
- $this->assertEquals($extraFields, array('2005', '2007'), 'Version fields returned');
-
- // In 2009 we updated it again
- DBDatetime::set_mock_now('2009-01-01 00:00:00');
- $testPage->Content = "I'm enjoying 2009";
- $testPage->ExtraField = '2009';
- $testPage->write();
-
- // End mock, back to the present day:)
- DBDatetime::clear_mock_now();
-
- $versions = Versioned::get_all_versions(VersionedTest\Subclass::class, $testPage->ID);
- $content = array();
- $extraFields = array();
- foreach ($versions as $version) {
- $content[] = $version->Content;
- $extraFields[] = $version->ExtraField;
- }
-
- $this->assertEquals($versions->Count(), 3, 'Additional all versions returned');
- $this->assertEquals(
- $content,
- array('This is the content from 2005', "It's 2007 already!", "I'm enjoying 2009"),
- 'Additional version fields returned'
- );
- $this->assertEquals($extraFields, array('2005', '2007', '2009'), 'Additional version fields returned');
- }
-
- public function testArchiveRelatedDataWithoutVersioned()
- {
- DBDatetime::set_mock_now('2009-01-01 00:00:00');
-
- $relatedData = new VersionedTest\RelatedWithoutversion();
- $relatedData->Name = 'Related Data';
- $relatedDataId = $relatedData->write();
-
- $testData = new VersionedTest\TestObject();
- $testData->Title = 'Test';
- $testData->Content = 'Before Content';
- $testData->Related()->add($relatedData);
- $id = $testData->write();
-
- DBDatetime::set_mock_now('2010-01-01 00:00:00');
- $testData->Content = 'After Content';
- $testData->write();
-
- Versioned::reading_archived_date('2009-01-01 19:00:00');
-
- $fetchedData = VersionedTest\TestObject::get()->byId($id);
- $this->assertEquals('Before Content', $fetchedData->Content, 'We see the correct content of the older version');
-
- $relatedData = VersionedTest\RelatedWithoutversion::get()->byId($relatedDataId);
- $this->assertEquals(
- 1,
- $relatedData->Related()->count(),
- 'We have a relation, with no version table, querying it still works'
- );
- }
-
- public function testVersionedWithSingleStage()
- {
- $tables = DB::table_list();
- $this->assertContains(
- 'versionedtest_singlestage',
- array_keys($tables),
- 'Contains base table'
- );
- $this->assertContains(
- 'versionedtest_singlestage_versions',
- array_keys($tables),
- 'Contains versions table'
- );
- $this->assertNotContains(
- 'versionedtest_singlestage_live',
- array_keys($tables),
- 'Does not contain separate table with _Live suffix'
- );
- $this->assertNotContains(
- 'versionedtest_singlestage_stage',
- array_keys($tables),
- 'Does not contain separate table with _Stage suffix'
- );
-
- Versioned::set_stage(Versioned::DRAFT);
- $obj = new VersionedTest\SingleStage(array('Name' => 'MyObj'));
- $obj->write();
- $this->assertNotNull(
- VersionedTest\SingleStage::get()->byID($obj->ID),
- 'Writes to and reads from default stage if its set explicitly'
- );
-
- Versioned::set_stage(Versioned::LIVE);
- $obj = new VersionedTest\SingleStage(array('Name' => 'MyObj'));
- $obj->write();
- $this->assertNotNull(
- VersionedTest\SingleStage::get()->byID($obj->ID),
- 'Writes to and reads from default stage even if a non-matching stage is set'
- );
- }
-
- /**
- * Test that publishing processes respects lazy loaded fields
- */
- public function testLazyLoadFields()
- {
- $originalMode = Versioned::get_reading_mode();
-
- // Generate staging record and retrieve it from stage in live mode
- Versioned::set_stage(Versioned::DRAFT);
- $obj = new VersionedTest\Subclass();
- $obj->Name = 'bob';
- $obj->ExtraField = 'Field Value';
- $obj->write();
- $objID = $obj->ID;
- $filter = sprintf('"VersionedTest_DataObject"."ID" = \'%d\'', Convert::raw2sql($objID));
- Versioned::set_stage(Versioned::LIVE);
-
- // Check fields are unloaded prior to access
- $objLazy = Versioned::get_one_by_stage(VersionedTest\TestObject::class, 'Stage', $filter, false);
- $lazyFields = $objLazy->getQueriedDatabaseFields();
- $this->assertTrue(isset($lazyFields['ExtraField_Lazy']));
- $this->assertEquals(VersionedTest\Subclass::class, $lazyFields['ExtraField_Lazy']);
-
- // Check lazy loading works when viewing a Stage object in Live mode
- $this->assertEquals('Field Value', $objLazy->ExtraField);
-
- // Test that writeToStage respects lazy loaded fields
- $objLazy = Versioned::get_one_by_stage(VersionedTest\TestObject::class, 'Stage', $filter, false);
- $objLazy->writeToStage('Live');
- $objLive = Versioned::get_one_by_stage(VersionedTest\TestObject::class, 'Live', $filter, false);
- $liveLazyFields = $objLive->getQueriedDatabaseFields();
-
- // Check fields are unloaded prior to access
- $this->assertTrue(isset($liveLazyFields['ExtraField_Lazy']));
- $this->assertEquals(VersionedTest\Subclass::class, $liveLazyFields['ExtraField_Lazy']);
-
- // Check that live record has original value
- $this->assertEquals('Field Value', $objLive->ExtraField);
-
- Versioned::set_reading_mode($originalMode);
- }
-
- public function testLazyLoadFieldsRetrieval()
- {
- // Set reading mode to Stage
- Versioned::set_stage(Versioned::DRAFT);
-
- // Create object only in reading stage
- $original = new VersionedTest\Subclass();
- $original->ExtraField = 'Foo';
- $original->write();
-
- // Query for object using base class
- $query = VersionedTest\TestObject::get()->filter('ID', $original->ID);
-
- // Set reading mode to Live
- Versioned::set_stage(Versioned::LIVE);
-
- $fetched = $query->first();
- $this->assertTrue($fetched instanceof VersionedTest\Subclass);
- $this->assertEquals($original->ID, $fetched->ID); // Eager loaded
- $this->assertEquals($original->ExtraField, $fetched->ExtraField); // Lazy loaded
- }
-
- /**
- * Tests that reading mode persists between requests
- */
- public function testReadingPersistent()
- {
- $session = Injector::inst()->create('SilverStripe\\Control\\Session', array());
- $adminID = $this->logInWithPermission('ADMIN');
- $session->inst_set('loggedInAs', $adminID);
-
- // Set to stage
- Director::test('/?stage=Stage', null, $session);
- $this->assertEquals(
- 'Stage.Stage',
- $session->inst_get('readingMode'),
- 'Check querystring changes reading mode to Stage'
- );
- Director::test('/', null, $session);
- $this->assertEquals(
- 'Stage.Stage',
- $session->inst_get('readingMode'),
- 'Check that subsequent requests in the same session remain in Stage mode'
- );
-
- // Test live persists
- Director::test('/?stage=Live', null, $session);
- $this->assertEquals(
- 'Stage.Live',
- $session->inst_get('readingMode'),
- 'Check querystring changes reading mode to Live'
- );
- Director::test('/', null, $session);
- $this->assertEquals(
- 'Stage.Live',
- $session->inst_get('readingMode'),
- 'Check that subsequent requests in the same session remain in Live mode'
- );
-
- // Test that session doesn't redundantly store the default stage if it doesn't need to
- $session2 = Injector::inst()->create('SilverStripe\\Control\\Session', array());
- $session2->inst_set('loggedInAs', $adminID);
- Director::test('/', null, $session2);
- $this->assertArrayNotHasKey('readingMode', $session2->inst_changedData());
- Director::test('/?stage=Live', null, $session2);
- $this->assertArrayNotHasKey('readingMode', $session2->inst_changedData());
-
- // Test choose_site_stage
- unset($_GET['stage']);
- unset($_GET['archiveDate']);
- Session::set('readingMode', 'Stage.Stage');
- Versioned::choose_site_stage();
- $this->assertEquals('Stage.Stage', Versioned::get_reading_mode());
- Session::set('readingMode', 'Archive.2014-01-01');
- Versioned::choose_site_stage();
- $this->assertEquals('Archive.2014-01-01', Versioned::get_reading_mode());
- Session::clear('readingMode');
- Versioned::choose_site_stage();
- $this->assertEquals('Stage.Live', Versioned::get_reading_mode());
- }
-
- /**
- * Test that stage parameter is blocked by non-administrative users
- */
- public function testReadingModeSecurity()
- {
- $this->setExpectedException(HTTPResponse_Exception::class);
- $session = Injector::inst()->create(Session::class, array());
- Director::test('/?stage=Stage', null, $session);
- }
-
- /**
- * Ensures that the latest version of a record is the expected value
- *
- * @param DataObject $record
- * @param int $version
- */
- protected function assertRecordHasLatestVersion($record, $version)
- {
- $schema = DataObject::getSchema();
- foreach (ClassInfo::ancestry(get_class($record), true) as $class) {
- $table = $schema->tableName($class);
- $versionForClass = DB::prepared_query(
- $sql = "SELECT MAX(\"Version\") FROM \"{$table}_Versions\" WHERE \"RecordID\" = ?",
- array($record->ID)
- )->value();
- $this->assertEquals($version, $versionForClass, "That the table $table has the latest version $version");
- }
- }
-
- /**
- * Test that that stage a record was queried from cascades to child relations, even if the
- * global stage has changed
- */
- public function testStageCascadeOnRelations()
- {
- $origReadingMode = Versioned::get_reading_mode();
-
- // Stage record - 2 children
- Versioned::set_stage(Versioned::DRAFT);
- $draftPage = $this->objFromFixture(VersionedTest\TestObject::class, 'page2');
- $draftPage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $this->assertEquals(2, $draftPage->Children()->Count());
-
- // Live record - no children
- Versioned::set_stage(Versioned::LIVE);
- $livePage = $this->objFromFixture(VersionedTest\TestObject::class, 'page2');
- $this->assertEquals(0, $livePage->Children()->Count());
-
- // Validate that draft page still queries draft children even though global stage is live
- $this->assertEquals(2, $draftPage->Children()->Count());
-
- // Validate that live page still queries live children even though global stage is live
- Versioned::set_stage(Versioned::DRAFT);
- $this->assertEquals(0, $livePage->Children()->Count());
-
- Versioned::set_reading_mode($origReadingMode);
- }
-
- /**
- * Tests that multi-table dataobjects are correctly versioned
- */
- public function testWriteToStage()
- {
- // Test subclass with versioned extension directly added
- $record = VersionedTest\Subclass::create();
- $record->Title = "Test A";
- $record->ExtraField = "Test A";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 1);
- $record->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $this->assertRecordHasLatestVersion($record, 1);
- $record->Title = "Test A2";
- $record->ExtraField = "Test A2";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 2);
-
- // Test subclass without changes to base class
- $record = VersionedTest\Subclass::create();
- $record->ExtraField = "Test B";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 1);
- $record->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $this->assertRecordHasLatestVersion($record, 1);
- $record->ExtraField = "Test B2";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 2);
-
- // Test subclass without changes to sub class
- $record = VersionedTest\Subclass::create();
- $record->Title = "Test C";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 1);
- $record->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $this->assertRecordHasLatestVersion($record, 1);
- $record->Title = "Test C2";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 2);
-
- // Test subclass with versioned extension only added to the base clases
- $record = VersionedTest\AnotherSubclass::create();
- $record->Title = "Test A";
- $record->AnotherField = "Test A";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 1);
- $record->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $this->assertRecordHasLatestVersion($record, 1);
- $record->Title = "Test A2";
- $record->AnotherField = "Test A2";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 2);
-
-
- // Test subclass without changes to base class
- $record = VersionedTest\AnotherSubclass::create();
- $record->AnotherField = "Test B";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 1);
- $record->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $this->assertRecordHasLatestVersion($record, 1);
- $record->AnotherField = "Test B2";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 2);
-
- // Test subclass without changes to sub class
- $record = VersionedTest\AnotherSubclass::create();
- $record->Title = "Test C";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 1);
- $record->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $this->assertRecordHasLatestVersion($record, 1);
- $record->Title = "Test C2";
- $record->writeToStage("Stage");
- $this->assertRecordHasLatestVersion($record, 2);
- }
-
- public function testVersionedHandlesRenamedDataObjectFields()
- {
- Config::inst()->remove(VersionedTest\RelatedWithoutversion::class, 'db', 'Name', 'Varchar');
-
- Config::inst()->update(
- VersionedTest\RelatedWithoutversion::class,
- 'db',
- array(
- "NewField" => "Varchar",
- )
- );
-
- VersionedTest\RelatedWithoutversion::add_extension(Versioned::class);
- $this->resetDBSchema(true);
- $testData = new VersionedTest\RelatedWithoutversion();
- $testData->NewField = 'Test';
- $testData->write();
- }
-
- public function testCanView()
- {
- $public1ID = $this->idFromFixture(VersionedTest\PublicStage::class, 'public1');
- $public2ID = $this->idFromFixture(VersionedTest\PublicViaExtension::class, 'public2');
- $privateID = $this->idFromFixture(VersionedTest\TestObject::class, 'page1');
- $singleID = $this->idFromFixture(VersionedTest\SingleStage::class, 'single');
-
- // Test that all (and only) public pages are viewable in stage mode
- Session::clear("loggedInAs");
- Versioned::set_stage(Versioned::DRAFT);
- $public1 = Versioned::get_one_by_stage(VersionedTest\PublicStage::class, 'Stage', array('"ID"' => $public1ID));
- $public2 = Versioned::get_one_by_stage(VersionedTest\PublicViaExtension::class, 'Stage', array('"ID"' => $public2ID));
- $private = Versioned::get_one_by_stage(VersionedTest\TestObject::class, 'Stage', array('"ID"' => $privateID));
- // Also test an object that has just a single-stage (eg. is only versioned)
- $single = Versioned::get_one_by_stage(VersionedTest\SingleStage::class, 'Stage', array('"ID"' => $singleID));
-
-
- $this->assertTrue($public1->canView());
- $this->assertTrue($public2->canView());
- $this->assertFalse($private->canView());
- $this->assertFalse($single->canView());
-
- // Adjusting the current stage should not allow objects loaded in stage to be viewable
- Versioned::set_stage(Versioned::LIVE);
- $this->assertTrue($public1->canView());
- $this->assertTrue($public2->canView());
- $this->assertFalse($private->canView());
- $this->assertFalse($single->canView());
-
- // Writing the private page to live should be fine though
- $private->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $privateLive = Versioned::get_one_by_stage(VersionedTest\TestObject::class, 'Live', array('"ID"' => $privateID));
- $this->assertTrue($private->canView());
- $this->assertTrue($privateLive->canView());
-
- // But if the private version becomes different to the live version, it's once again disallowed
- Versioned::set_stage(Versioned::DRAFT);
- $private->Title = 'Secret Title';
- $private->write();
- $this->assertFalse($private->canView());
- $this->assertTrue($privateLive->canView());
-
- // And likewise, viewing a live page (when mode is draft) should be ok
- Versioned::set_stage(Versioned::DRAFT);
- $this->assertFalse($private->canView());
- $this->assertTrue($privateLive->canView());
-
- // Logging in as admin should allow all permissions
- $this->logInWithPermission('ADMIN');
- Versioned::set_stage(Versioned::DRAFT);
- $this->assertTrue($public1->canView());
- $this->assertTrue($public2->canView());
- $this->assertTrue($private->canView());
- $this->assertTrue($single->canView());
- }
-
- public function testCanViewStage()
- {
- $public = $this->objFromFixture(VersionedTest\PublicStage::class, 'public1');
- $private = $this->objFromFixture(VersionedTest\TestObject::class, 'page1');
- Session::clear("loggedInAs");
- Versioned::set_stage(Versioned::DRAFT);
-
- // Test that all (and only) public pages are viewable in stage mode
- // Unpublished records are not viewable in live regardless of permissions
- $this->assertTrue($public->canViewStage('Stage'));
- $this->assertFalse($private->canViewStage('Stage'));
- $this->assertFalse($public->canViewStage('Live'));
- $this->assertFalse($private->canViewStage('Live'));
-
- // Writing records to live should make both stage and live modes viewable
- $private->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $public->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
- $this->assertTrue($public->canViewStage('Stage'));
- $this->assertTrue($private->canViewStage('Stage'));
- $this->assertTrue($public->canViewStage('Live'));
- $this->assertTrue($private->canViewStage('Live'));
-
- // If the draft mode changes, the live mode remains public, although the updated
- // draft mode is secured for non-public records.
- $private->Title = 'Secret Title';
- $private->write();
- $public->Title = 'Public Title';
- $public->write();
- $this->assertTrue($public->canViewStage('Stage'));
- $this->assertFalse($private->canViewStage('Stage'));
- $this->assertTrue($public->canViewStage('Live'));
- $this->assertTrue($private->canViewStage('Live'));
- }
-
- /**
- * Values that are overwritten with null are saved to the _versions table correctly.
- */
- public function testWriteNullValueToVersion()
- {
- $record = VersionedTest\Subclass::create();
- $record->Title = "Test A";
- $record->write();
-
- $version = Versioned::get_latest_version($record->ClassName, $record->ID);
-
- $this->assertEquals(1, $version->Version);
- $this->assertEquals($record->Title, $version->Title);
-
- $record->Title = null;
- $record->write();
-
- $version = Versioned::get_latest_version($record->ClassName, $record->ID);
-
- $this->assertEquals(2, $version->Version);
- $this->assertEquals($record->Title, $version->Title);
- }
-
-
-
- public function testStageStates()
- {
- // newly created page
- $createdPage = new VersionedTest\TestObject();
- $createdPage->write();
- $this->assertTrue($createdPage->isOnDraft());
- $this->assertFalse($createdPage->isPublished());
- $this->assertTrue($createdPage->isOnDraftOnly());
- $this->assertTrue($createdPage->isModifiedOnDraft());
-
- // published page
- $publishedPage = new VersionedTest\TestObject();
- $publishedPage->write();
- $publishedPage->copyVersionToStage('Stage', 'Live');
- $this->assertTrue($publishedPage->isOnDraft());
- $this->assertTrue($publishedPage->isPublished());
- $this->assertFalse($publishedPage->isOnDraftOnly());
- $this->assertFalse($publishedPage->isOnLiveOnly());
- $this->assertFalse($publishedPage->isModifiedOnDraft());
-
- // published page, deleted from stage
- $deletedFromDraftPage = new VersionedTest\TestObject();
- $deletedFromDraftPage->write();
- $deletedFromDraftPage->copyVersionToStage('Stage', 'Live');
- $deletedFromDraftPage->deleteFromStage('Stage');
- $this->assertFalse($deletedFromDraftPage->isArchived());
- $this->assertFalse($deletedFromDraftPage->isOnDraft());
- $this->assertTrue($deletedFromDraftPage->isPublished());
- $this->assertFalse($deletedFromDraftPage->isOnDraftOnly());
- $this->assertTrue($deletedFromDraftPage->isOnLiveOnly());
- $this->assertFalse($deletedFromDraftPage->isModifiedOnDraft());
-
- // published page, deleted from live
- $deletedFromLivePage = new VersionedTest\TestObject();
- $deletedFromLivePage->write();
- $deletedFromLivePage->copyVersionToStage('Stage', 'Live');
- $deletedFromLivePage->deleteFromStage('Live');
- $this->assertFalse($deletedFromLivePage->isArchived());
- $this->assertTrue($deletedFromLivePage->isOnDraft());
- $this->assertFalse($deletedFromLivePage->isPublished());
- $this->assertTrue($deletedFromLivePage->isOnDraftOnly());
- $this->assertFalse($deletedFromLivePage->isOnLiveOnly());
- $this->assertTrue($deletedFromLivePage->isModifiedOnDraft());
-
- // published page, deleted from both stages
- $deletedFromAllStagesPage = new VersionedTest\TestObject();
- $deletedFromAllStagesPage->write();
- $deletedFromAllStagesPage->copyVersionToStage('Stage', 'Live');
- $deletedFromAllStagesPage->doArchive();
- $this->assertTrue($deletedFromAllStagesPage->isArchived());
- $this->assertFalse($deletedFromAllStagesPage->isOnDraft());
- $this->assertFalse($deletedFromAllStagesPage->isPublished());
- $this->assertFalse($deletedFromAllStagesPage->isOnDraftOnly());
- $this->assertFalse($deletedFromAllStagesPage->isOnLiveOnly());
- $this->assertFalse($deletedFromAllStagesPage->isModifiedOnDraft());
-
- // published page, modified
- $modifiedOnDraftPage = new VersionedTest\TestObject();
- $modifiedOnDraftPage->write();
- $modifiedOnDraftPage->copyVersionToStage('Stage', 'Live');
- $modifiedOnDraftPage->Content = 'modified';
- $modifiedOnDraftPage->write();
- $this->assertFalse($modifiedOnDraftPage->isArchived());
- $this->assertTrue($modifiedOnDraftPage->isOnDraft());
- $this->assertTrue($modifiedOnDraftPage->isPublished());
- $this->assertFalse($modifiedOnDraftPage->isOnDraftOnly());
- $this->assertFalse($modifiedOnDraftPage->isOnLiveOnly());
- $this->assertTrue($modifiedOnDraftPage->isModifiedOnDraft());
- }
-}
diff --git a/tests/php/ORM/VersionedTest.yml b/tests/php/ORM/VersionedTest.yml
deleted file mode 100644
index 788116ec1..000000000
--- a/tests/php/ORM/VersionedTest.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-SilverStripe\ORM\Tests\VersionedTest\TestObject:
- page1:
- Title: Page 1
- page2:
- Title: Page 2
- page3:
- Title: Page 3
- page2a:
- Parent: =>SilverStripe\ORM\Tests\VersionedTest\TestObject.page2
- Title: Page 2a
- page2b:
- Parent: =>SilverStripe\ORM\Tests\VersionedTest\TestObject.page2
- Title: Page 2b
- page3a:
- Parent: =>SilverStripe\ORM\Tests\VersionedTest\TestObject.page3
- Title: Page 3a
- page3b:
- Parent: =>SilverStripe\ORM\Tests\VersionedTest\TestObject.page3
- Title: Page 3b
-
-SilverStripe\ORM\Tests\VersionedTest\PublicStage:
- public1:
- Title: 'Some page'
-
-SilverStripe\ORM\Tests\VersionedTest\PublicViaExtension:
- public2:
- Title: 'Another page'
-
-SilverStripe\ORM\Tests\VersionedTest\AnotherSubclass:
- subclass1:
- Title: 'Subclass Page 1'
- AnotherField: 'Bob'
-
-SilverStripe\ORM\Tests\VersionedTest\SingleStage:
- single:
- Title: 'Singlestage Title'
diff --git a/tests/php/ORM/VersionedTest/AnotherSubclass.php b/tests/php/ORM/VersionedTest/AnotherSubclass.php
deleted file mode 100644
index cef682dd8..000000000
--- a/tests/php/ORM/VersionedTest/AnotherSubclass.php
+++ /dev/null
@@ -1,14 +0,0 @@
- "Varchar"
- );
-}
diff --git a/tests/php/ORM/VersionedTest/CustomTable.php b/tests/php/ORM/VersionedTest/CustomTable.php
deleted file mode 100644
index b9600c78e..000000000
--- a/tests/php/ORM/VersionedTest/CustomTable.php
+++ /dev/null
@@ -1,23 +0,0 @@
- 'Varchar'
- ];
-
- private static $table_name = 'VTCustomTable';
-
- private static $extensions = [
- Versioned::class,
- ];
-}
diff --git a/tests/php/ORM/VersionedTest/PublicExtension.php b/tests/php/ORM/VersionedTest/PublicExtension.php
deleted file mode 100644
index 239af8f00..000000000
--- a/tests/php/ORM/VersionedTest/PublicExtension.php
+++ /dev/null
@@ -1,17 +0,0 @@
- 'Varchar'
- );
-
- private static $extensions = array(
- Versioned::class
- );
-
- public function canView($member = null)
- {
- $extended = $this->extendedCan(__FUNCTION__, $member);
- if ($extended !== null) {
- return $extended;
- }
- return true;
- }
-
- public function canViewVersioned($member = null)
- {
- // All non-live modes are public
- return true;
- }
-}
diff --git a/tests/php/ORM/VersionedTest/PublicViaExtension.php b/tests/php/ORM/VersionedTest/PublicViaExtension.php
deleted file mode 100644
index 0236a631b..000000000
--- a/tests/php/ORM/VersionedTest/PublicViaExtension.php
+++ /dev/null
@@ -1,37 +0,0 @@
-extendedCan(__FUNCTION__, $member);
- if ($extended !== null) {
- return $extended;
- }
- return true;
- }
-
- private static $db = array(
- 'Title' => 'Varchar'
- );
-
- private static $extensions = array(
- Versioned::class,
- PublicExtension::class,
- );
-}
diff --git a/tests/php/ORM/VersionedTest/RelatedWithoutversion.php b/tests/php/ORM/VersionedTest/RelatedWithoutversion.php
deleted file mode 100644
index 59e51154f..000000000
--- a/tests/php/ORM/VersionedTest/RelatedWithoutversion.php
+++ /dev/null
@@ -1,19 +0,0 @@
- 'Varchar'
- );
-
- private static $belongs_many_many = array(
- 'Related' => TestObject::class
- );
-}
diff --git a/tests/php/ORM/VersionedTest/SingleStage.php b/tests/php/ORM/VersionedTest/SingleStage.php
deleted file mode 100644
index 3e1834fdb..000000000
--- a/tests/php/ORM/VersionedTest/SingleStage.php
+++ /dev/null
@@ -1,23 +0,0 @@
- 'Varchar'
- );
-
- private static $extensions = array(
- 'SilverStripe\\ORM\\Versioning\\Versioned("Versioned")'
- );
-}
diff --git a/tests/php/ORM/VersionedTest/Subclass.php b/tests/php/ORM/VersionedTest/Subclass.php
deleted file mode 100644
index 45bb47f02..000000000
--- a/tests/php/ORM/VersionedTest/Subclass.php
+++ /dev/null
@@ -1,13 +0,0 @@
- "Varchar",
- );
-}
diff --git a/tests/php/ORM/VersionedTest/TestObject.php b/tests/php/ORM/VersionedTest/TestObject.php
deleted file mode 100644
index 2c2dba73a..000000000
--- a/tests/php/ORM/VersionedTest/TestObject.php
+++ /dev/null
@@ -1,52 +0,0 @@
- "Varchar",
- 'Title' => 'Varchar',
- 'Content' => 'HTMLText',
- );
-
- private static $extensions = array(
- Versioned::class,
- );
-
- private static $has_one = array(
- 'Parent' => TestObject::class,
- );
-
- private static $has_many = array(
- 'Children' => TestObject::class,
- );
-
- private static $many_many = array(
- 'Related' => RelatedWithoutversion::class,
- );
-
-
- public function canView($member = null)
- {
- $extended = $this->extendedCan(__FUNCTION__, $member);
- if ($extended !== null) {
- return $extended;
- }
- return true;
- }
-}
diff --git a/tests/php/ORM/VersionedTest/UnversionedWithField.php b/tests/php/ORM/VersionedTest/UnversionedWithField.php
deleted file mode 100644
index 963478ed8..000000000
--- a/tests/php/ORM/VersionedTest/UnversionedWithField.php
+++ /dev/null
@@ -1,15 +0,0 @@
- 'Varchar(255)'
- ];
-}
diff --git a/tests/php/ORM/VersionedTest/WithIndexes.php b/tests/php/ORM/VersionedTest/WithIndexes.php
deleted file mode 100644
index cb95f80eb..000000000
--- a/tests/php/ORM/VersionedTest/WithIndexes.php
+++ /dev/null
@@ -1,33 +0,0 @@
- 'Int',
- 'UniqS' => 'Int',
- );
-
- private static $extensions = array(
- Versioned::class
- );
-
- private static $indexes = [
- 'UniqS_idx' => 'unique ("UniqS")',
- 'UniqA_idx' => [
- 'type' => 'unique',
- 'name' => 'UniqA_idx',
- 'value' => '"UniqA"',
- ],
- ];
-}
diff --git a/tests/php/View/SSViewerCacheBlockTest.php b/tests/php/View/SSViewerCacheBlockTest.php
index 7f11325e6..ac899791c 100644
--- a/tests/php/View/SSViewerCacheBlockTest.php
+++ b/tests/php/View/SSViewerCacheBlockTest.php
@@ -3,7 +3,7 @@
namespace SilverStripe\View\Tests;
use SilverStripe\Core\Injector\Injector;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Control\Director;
@@ -15,12 +15,21 @@ use Symfony\Component\Cache\Simple\NullCache;
class SSViewerCacheBlockTest extends SapphireTest
{
-
protected $extraDataObjects = array(
- SSViewerCacheBlockTest\TestModel::class,
- SSViewerCacheBlockTest\VersionedModel::class
+ SSViewerCacheBlockTest\TestModel::class
);
+ protected function getExtraDataObjects()
+ {
+ $classes = parent::getExtraDataObjects();
+
+ // Add extra classes if versioning is enabled
+ if (class_exists(Versioned::class)) {
+ $classes[] = SSViewerCacheBlockTest\VersionedModel::class;
+ }
+ return $classes;
+ }
+
/**
* @var SSViewerCacheBlockTest\TestModel
*/
@@ -149,6 +158,9 @@ class SSViewerCacheBlockTest extends SapphireTest
public function testVersionedCache()
{
+ if (!class_exists(Versioned::class)) {
+ $this->markTestSkipped('testVersionedCache requires Versioned extension');
+ }
$origReadingMode = Versioned::get_reading_mode();
// Run without caching in stage to prove data is uncached
diff --git a/tests/php/View/SSViewerCacheBlockTest/VersionedModel.php b/tests/php/View/SSViewerCacheBlockTest/VersionedModel.php
index 7cd25cef2..60c639847 100644
--- a/tests/php/View/SSViewerCacheBlockTest/VersionedModel.php
+++ b/tests/php/View/SSViewerCacheBlockTest/VersionedModel.php
@@ -4,7 +4,7 @@ namespace SilverStripe\View\Tests\SSViewerCacheBlockTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
-use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Versioned\Versioned;
class VersionedModel extends DataObject implements TestOnly
{