Merge pull request #387 from creative-commoners/pulls/2.1/fix-cross-subsite-duplication

FIX Pages now correctly duplicate children across subsites
This commit is contained in:
Guy Marriott 2018-08-24 11:03:56 +12:00 committed by GitHub
commit 039a7a8c84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 50 deletions

View File

@ -170,57 +170,89 @@ class SiteTreeSubsites extends DataExtension
} }
/** /**
* Does the basic duplication, but doesn't write anything * Does the basic duplication, but doesn't write anything this means we can subclass this easier and do more
* this means we can subclass this easier and do more complex * complex relation duplication.
* relation duplication. *
* Note that when duplicating including children, everything is written.
*
* @param Subsite|int $subsiteID
* @param bool $includeChildren
* @return SiteTree
*/ */
public function duplicateToSubsitePrep($subsiteID) public function duplicateToSubsitePrep($subsiteID, $includeChildren)
{ {
if (is_object($subsiteID)) { if (is_object($subsiteID)) {
$subsiteID = $subsiteID->ID; $subsiteID = $subsiteID->ID;
} }
$oldSubsite = SubsiteState::singleton()->getSubsiteId(); return SubsiteState::singleton()
if ($subsiteID) { ->withState(function (SubsiteState $newState) use ($subsiteID, $includeChildren) {
Subsite::changeSubsite($subsiteID); $newState->setSubsiteId($subsiteID);
} else {
$subsiteID = $oldSubsite; /** @var SiteTree $page */
} $page = $this->owner;
// doesn't write as we need to reset the SubsiteID, ParentID etc
$clone = $this->owner->duplicate(false); try {
$clone->CheckedPublicationDifferences = $clone->AddedToStage = true; // We have no idea what the ParentID should be, but it shouldn't be the same as it was since
$subsiteID = ($subsiteID ? $subsiteID : $oldSubsite); // we're now in a different subsite. As a workaround use the url-segment and subsite ID.
$clone->SubsiteID = $subsiteID; if ($page->Parent()) {
// We have no idea what the parentID should be, so as a workaround use the url-segment and subsite ID $parentSeg = $page->Parent()->URLSegment;
if ($this->owner->Parent()) {
$parentSeg = $this->owner->Parent()->URLSegment;
$newParentPage = Page::get()->filter('URLSegment', $parentSeg)->first(); $newParentPage = Page::get()->filter('URLSegment', $parentSeg)->first();
$originalParentID = $page->ParentID;
if ($newParentPage) { if ($newParentPage) {
$clone->ParentID = $newParentPage->ID; $page->ParentID = (int) $newParentPage->ID;
} else { } else {
// reset it to the top level, so the user can decide where to put it // reset it to the top level, so the user can decide where to put it
$clone->ParentID = 0; $page->ParentID = 0;
} }
} }
// MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module
$clone->MasterPageID = $this->owner->ID; // Disable query filtering by subsite during actual duplication
return $clone; $originalFilter = Subsite::$disable_subsite_filter;
Subsite::disable_subsite_filter(true);
return $includeChildren ? $page->duplicateWithChildren() : $page->duplicate(false);
} finally {
Subsite::disable_subsite_filter($originalFilter);
// Re-set the original parent ID for the current page
$page->ParentID = $originalParentID;
}
});
}
/**
* When duplicating a page, assign the current subsite ID from the state
*/
public function onBeforeDuplicate()
{
$subsiteId = SubsiteState::singleton()->getSubsiteId();
if ($subsiteId !== null) {
$this->owner->SubsiteID = $subsiteId;
}
} }
/** /**
* Create a duplicate of this page and save it to another subsite * Create a duplicate of this page and save it to another subsite
* @param $subsiteID int|Subsite The Subsite to copy to, or its ID *
* @param Subsite|int $subsiteID The Subsite to copy to, or its ID
* @param boolean $includeChildren Whether to duplicate child pages too
* @return SiteTree The duplicated page
*/ */
public function duplicateToSubsite($subsiteID = null) public function duplicateToSubsite($subsiteID = null, $includeChildren = false)
{ {
$clone = $this->owner->duplicateToSubsitePrep($subsiteID); $clone = $this->owner->duplicateToSubsitePrep($subsiteID, $includeChildren);
$clone->invokeWithExtensions('onBeforeDuplicateToSubsite', $this->owner); $clone->invokeWithExtensions('onBeforeDuplicateToSubsite', $this->owner);
if (!$includeChildren) {
// Write the new page if "include children" is false, because it is written by default when it's true.
$clone->write(); $clone->write();
}
// Deprecated: manually duplicate any configured relationships
$clone->duplicateSubsiteRelations($this->owner); $clone->duplicateSubsiteRelations($this->owner);
// new extension hooks which happens after write,
// onAfterDuplicate isn't reliable due to
// https://github.com/silverstripe/silverstripe-cms/issues/1253
$clone->invokeWithExtensions('onAfterDuplicateToSubsite', $this->owner); $clone->invokeWithExtensions('onAfterDuplicateToSubsite', $this->owner);
return $clone; return $clone;
} }
@ -231,6 +263,7 @@ class SiteTreeSubsites extends DataExtension
* It may be that some relations are not diostinct to sub site so can stay * It may be that some relations are not diostinct to sub site so can stay
* whereas others may need to be duplicated * whereas others may need to be duplicated
* *
* @param SiteTree $originalPage
*/ */
public function duplicateSubsiteRelations($originalPage) public function duplicateSubsiteRelations($originalPage)
{ {

View File

@ -359,28 +359,41 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest
$this->assertEquals('important-page', $mainSubsiteImportantPage->URLSegment); $this->assertEquals('important-page', $mainSubsiteImportantPage->URLSegment);
} }
public function testCopySubsiteWithChildren() /**
* @param bool $withChildren
* @param int $expectedChildren
* @dataProvider duplicateToSubsiteProvider
*/
public function testDuplicateToSubsite($withChildren, $expectedChildren)
{ {
$page = $this->objFromFixture('Page', 'about'); /** @var SiteTree $page */
$page = $this->objFromFixture(Page::class, 'about');
/** @var Subsite $newSubsite */
$newSubsite = $this->objFromFixture(Subsite::class, 'subsite1'); $newSubsite = $this->objFromFixture(Subsite::class, 'subsite1');
$moved = $page->duplicateToSubsite($newSubsite->ID, true); /** @var SiteTree $duplicatedPage */
$this->assertEquals($moved->SubsiteID, $newSubsite->ID, 'Ensure returned records are on new subsite'); $duplicatedPage = $page->duplicateToSubsite($newSubsite->ID, $withChildren);
$this->assertEquals( $this->assertInstanceOf(SiteTree::class, $duplicatedPage, 'A new page is returned');
$moved->AllChildren()->count(),
$page->AllChildren()->count(), $this->assertEquals($newSubsite->ID, $duplicatedPage->SubsiteID, 'Ensure returned records are on new subsite');
'All pages are copied across'
$this->assertCount(1, $page->AllChildren());
$this->assertCount(
$expectedChildren,
$duplicatedPage->AllChildren(),
'Duplicated page also duplicates children'
); );
} }
public function testCopySubsiteWithoutChildren() /**
* @return array[]
*/
public function duplicateToSubsiteProvider()
{ {
$page = $this->objFromFixture('Page', 'about'); return [
$newSubsite = $this->objFromFixture(Subsite::class, 'subsite2'); [true, 1],
[false, 0],
$moved = $page->duplicateToSubsite($newSubsite->ID, false); ];
$this->assertEquals($moved->SubsiteID, $newSubsite->ID, 'Ensure returned records are on new subsite');
$this->assertEquals($moved->AllChildren()->count(), 0, 'All pages are copied across');
} }
public function testIfSubsiteThemeIsSetToThemeList() public function testIfSubsiteThemeIsSetToThemeList()