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