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 {