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