/** * 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', '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', 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', 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', 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)); }
/** * Helper method for applicablePages() methods. Acts as a skeleton implementation. * * @param array $ids The IDs passed to applicablePages * @param string $methodName The canXXX() method to call on each page to check if the action is applicable * @param bool $checkStagePages Set to true if you want to check stage pages * @param bool $checkLivePages Set to true if you want to check live pages (e.g, for deleted-from-draft) * @return array */ public function applicablePagesHelper($ids, $methodName, $checkStagePages = true, $checkLivePages = true) { if (!is_array($ids)) { user_error("Bad \$ids passed to applicablePagesHelper()", E_USER_WARNING); } if (!is_string($methodName)) { user_error("Bad \$methodName passed to applicablePagesHelper()", E_USER_WARNING); } $applicableIDs = array(); $managedClass = $this->managedClass; $draftPages = DataObject::get($managedClass)->byIDs($ids); // Filter out the live-only ids $onlyOnLive = array_fill_keys($ids, true); if ($checkStagePages) { foreach ($draftPages as $obj) { unset($onlyOnLive[$obj->ID]); if ($obj->{$methodName}()) { $applicableIDs[] = $obj->ID; } } } $onlyOnLive = array_keys($onlyOnLive); if ($checkLivePages && $onlyOnLive && Object::has_extension($managedClass, 'SilverStripe\\ORM\\Versioning\\Versioned')) { // Get the pages that only exist on live (deleted from stage) $livePages = Versioned::get_by_stage($managedClass, "Live")->byIDs($onlyOnLive); foreach ($livePages as $obj) { if ($obj->{$methodName}()) { $applicableIDs[] = $obj->ID; } } } return $applicableIDs; }
public function testPublishing() { /** @var 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 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); }
/** * Ensure that related objects are disassociated on live */ public function testUnlinkDisassociated() { $this->publishAllFixtures(); /** @var ChangeSetTest_Base $base */ $base = $this->objFromFixture(ChangeSetTest_Base::class, 'base'); /** @var ChangeSetTest_Mid $mid1 $mid2 */ $mid1 = $this->objFromFixture(ChangeSetTest_Mid::class, 'mid1'); $mid2 = $this->objFromFixture(ChangeSetTest_Mid::class, 'mid2'); // Remove mid1 from stage $this->assertEquals($base->ID, $mid1->BaseID); $this->assertEquals($base->ID, $mid2->BaseID); $mid1->deleteFromStage(Versioned::DRAFT); // Publishing recursively should unlinkd this object $changeset = new ChangeSet(); $changeset->write(); $changeset->addObject($base); // Assert changeset only contains root object $this->assertChangeSetLooksLike($changeset, ['ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY]); $changeset->publish(); // mid1 on live exists, but has BaseID set to zero $mid1Live = Versioned::get_by_stage(ChangeSetTest_Mid::class, Versioned::LIVE)->byID($mid1->ID); $this->assertNotNull($mid1Live); $this->assertEquals($mid1->ID, $mid1Live->ID); $this->assertEquals(0, $mid1Live->BaseID); // mid2 on live exists and retains BaseID $mid2Live = Versioned::get_by_stage(ChangeSetTest_Mid::class, Versioned::LIVE)->byID($mid2->ID); $this->assertNotNull($mid2Live); $this->assertEquals($mid2->ID, $mid2Live->ID); $this->assertEquals($base->ID, $mid2Live->BaseID); }
/** * 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 || !$object->canUnpublish($member)) { return false; } break; default: /** @var Versioned|DataObject $object */ $object = Versioned::get_by_stage($this->ObjectClass, Versioned::DRAFT)->byID($this->ObjectID); if (!$object || !$object->canPublish($member)) { return false; } break; } // If object can be published/unpublished let extensions deny return $this->can(__FUNCTION__, $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 false; }
/** * This tests for the situation described in the ticket #5596. * Writing new Page to live first creates a row in VersionedTest_DataObject table (to get the new ID), * then "changes it's mind" in Versioned and writes VersionedTest_DataObject_Live. It does not remove * the VersionedTest_DataObject record though. */ public function testWritingNewToLive() { $origReadingMode = Versioned::get_reading_mode(); Versioned::set_stage(Versioned::LIVE); $page = new VersionedTest_DataObject(); $page->Title = "testWritingNewToLive"; $page->URLSegment = "testWritingNewToLive"; $page->write(); $live = Versioned::get_by_stage('VersionedTest_DataObject', 'Live', array('"VersionedTest_DataObject_Live"."ID"' => $page->ID)); $this->assertEquals(1, $live->count()); $this->assertEquals($live->First()->Title, 'testWritingNewToLive'); $stage = Versioned::get_by_stage('VersionedTest_DataObject', 'Stage', array('"VersionedTest_DataObject"."ID"' => $page->ID)); $this->assertEquals(0, $stage->count()); Versioned::set_reading_mode($origReadingMode); }
/** * Tests for the bug #5994 - Moving folder after executing Folder::findOrMake will not set the Filenames properly */ public function testFindOrMakeFolderThenMove() { $folder1 = $this->objFromFixture('SilverStripe\\Assets\\Folder', 'folder1'); Folder::find_or_make($folder1->Filename); $folder2 = $this->objFromFixture('SilverStripe\\Assets\\Folder', 'folder2'); // Publish file1 /** @var File $file1 */ $file1 = DataObject::get_by_id('SilverStripe\\Assets\\File', $this->idFromFixture('SilverStripe\\Assets\\File', 'file1-folder1'), false); $file1->publishRecursive(); // set ParentID. This should cause updateFilesystem to be called on all children $folder1->ParentID = $folder2->ID; $folder1->write(); // Check if the file in the folder moved along /** @var File $file1Draft */ $file1Draft = Versioned::get_by_stage('SilverStripe\\Assets\\File', Versioned::DRAFT)->byID($file1->ID); $this->assertFileExists(AssetStoreTest_SpyStore::getLocalPath($file1Draft)); $this->assertEquals('FileTest-folder2/FileTest-folder1/File1.txt', $file1Draft->Filename, 'The file DataObject has updated path'); // File should be located in new folder $this->assertEquals(ASSETS_PATH . '/FolderTest/.protected/FileTest-folder2/FileTest-folder1/55b443b601/File1.txt', AssetStoreTest_SpyStore::getLocalPath($file1Draft)); // Published (live) version remains in the old location /** @var File $file1Live */ $file1Live = Versioned::get_by_stage('SilverStripe\\Assets\\File', Versioned::LIVE)->byID($file1->ID); $this->assertEquals(ASSETS_PATH . '/FolderTest/FileTest-folder1/55b443b601/File1.txt', AssetStoreTest_SpyStore::getLocalPath($file1Live)); // Publishing the draft to live should move the new file to the public store $file1Draft->publishRecursive(); $this->assertEquals(ASSETS_PATH . '/FolderTest/FileTest-folder2/FileTest-folder1/55b443b601/File1.txt', AssetStoreTest_SpyStore::getLocalPath($file1Draft)); }
/** * @return DataObject */ protected function getRecord() { return Versioned::get_by_stage(FormFactoryTest_TestObject::class, Versioned::DRAFT)->first(); }