/**
  * Performs the actual action of adding the object to the ChangeSet, once the ChangeSet ID is known
  *
  * @param DataObject $object The object to add to the ChangeSet
  * @param int $campaignID The ID of the ChangeSet to add $object to
  * @return SS_HTTPResponse
  * @throws SS_HTTPResponse_Exception
  */
 public function addToCampaign($object, $campaignID)
 {
     /** @var ChangeSet $changeSet */
     $changeSet = ChangeSet::get()->byID($campaignID);
     if (!$changeSet) {
         $this->editForm->httpError(404, _t('AddToCampaign.ErrorNotFound', 'That {Type} couldn\'t be found', '', ['Type' => 'Campaign']));
         return null;
     }
     if (!$changeSet->canEdit()) {
         $this->editForm->httpError(403, _t('AddToCampaign.ErrorCampaignPermissionDenied', 'It seems you don\'t have the necessary permissions to add {ObjectTitle} to {CampaignTitle}', '', ['ObjectTitle' => $object->Title, 'CampaignTitle' => $changeSet->Title]));
         return null;
     }
     $changeSet->addObject($object);
     if (Director::is_ajax()) {
         $response = new SS_HTTPResponse(_t('AddToCampaign.Success', 'Successfully added {ObjectTitle} to {CampaignTitle}', '', ['ObjectTitle' => $object->Title, 'CampaignTitle' => $changeSet->Title]), 200);
         $response->addHeader('Content-Type', 'text/plain; charset=utf-8');
         return $response;
     } else {
         return $this->editForm->getController()->redirectBack();
     }
 }
 /**
  * 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);
 }
 /**
  * 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);
 }
 /**
  * Test that objects are correctly published recursively
  */
 public function testRecursivePublish()
 {
     /** @var VersionedOwnershipTest_Subclass $parent */
     $parent = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
     $parentID = $parent->ID;
     $banner1 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany1_published');
     $banner2 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', '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']];
     $newBanners = [['Title' => 'Renamed Banner 1'], ['Title' => 'Related Many 3'], ['Title' => 'New Banner']];
     $parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)->byID($parentID);
     $this->assertDOSEquals($newBanners, $parentDraft->Banners());
     $parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', 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', Versioned::DRAFT)->byID($parentID);
     $this->assertDOSEquals($newBanners, $parentDraft->Banners());
     $parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', 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', 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());
 }
 /**
  * @todo Use GridFieldDetailForm once it can handle structured data and form schemas
  *
  * @param int $id
  * @return Form
  */
 public function getDetailEditForm($id)
 {
     // Get record-specific fields
     $record = null;
     if ($id) {
         $record = ChangeSet::get()->byID($id);
         if (!$record || !$record->canView()) {
             return null;
         }
     }
     if (!$record) {
         $record = ChangeSet::singleton();
     }
     $fields = $record->getCMSFields();
     // Add standard fields
     $fields->push(HiddenField::create('ID'));
     $form = Form::create($this, 'DetailEditForm', $fields, FieldList::create(FormAction::create('save', _t('CMSMain.SAVE', 'Save'))->setIcon('save'), FormAction::create('cancel', _t('LeftAndMain.CANCEL', 'Cancel'))->setUseButtonTag(true)));
     // Load into form
     if ($id && $record) {
         $form->loadDataFrom($record);
     }
     $form->getValidator()->addRequiredField('Name');
     // Configure form to respond to validation errors with form schema
     // if requested via react.
     $form->setValidationResponseCallback(function () use($form, $record) {
         $schemaId = Controller::join_links($this->Link('schema/DetailEditForm'), $record->exists() ? $record->ID : '');
         return $this->getSchemaResponse($form, $schemaId);
     });
     return $form;
 }
 public function testPublish()
 {
     $this->publishAllFixtures();
     $base = $this->objFromFixture('ChangeSetTest_Base', 'base');
     $baseID = $base->ID;
     $baseBefore = $base->Version;
     $end1 = $this->objFromFixture('ChangeSetTest_End', '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_Mid();
     $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_Base', '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_Base', Versioned::LIVE, $baseID));
     $end1Change = $changeset->Changes()->filter(['ObjectClass' => 'ChangeSetTest_End', 'ObjectID' => $end1ID])->first();
     $this->assertEquals((int) $end1Before, (int) $end1Change->VersionBefore);
     $this->assertEquals(0, (int) $end1Change->VersionAfter);
     $this->assertEquals(0, (int) Versioned::get_versionnumber_by_stage('ChangeSetTest_End', Versioned::LIVE, $end1ID));
     $midNewChange = $changeset->Changes()->filter(['ObjectClass' => 'ChangeSetTest_Mid', 'ObjectID' => $midNewID])->first();
     $this->assertEquals(0, (int) $midNewChange->VersionBefore);
     $this->assertEquals((int) $midNewAfter, (int) $midNewChange->VersionAfter);
     $this->assertEquals((int) $midNewAfter, (int) Versioned::get_versionnumber_by_stage('ChangeSetTest_Mid', Versioned::LIVE, $midNewID));
     // 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();
 }
 /**
  * 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();
 }