/**
  * 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();
 }