Merge pull request #1919 from open-sausages/pulls/4.0/virtualpage-cascade-delete

API Virtual pages now respect cascade_deletes on source page
This commit is contained in:
Chris Joe 2017-08-09 16:35:24 +12:00 committed by GitHub
commit 9e86a2bcd4
4 changed files with 73 additions and 130 deletions

View File

@ -193,13 +193,17 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi
); );
private static $has_many = array( private static $has_many = array(
"VirtualPages" => "SilverStripe\\CMS\\Model\\VirtualPage.CopyContentFrom" "VirtualPages" => VirtualPage::class . '.CopyContentFrom'
); );
private static $owned_by = array( private static $owned_by = array(
"VirtualPages" "VirtualPages"
); );
private static $cascade_deletes = [
'VirtualPages',
];
private static $casting = array( private static $casting = array(
"Breadcrumbs" => "HTMLFragment", "Breadcrumbs" => "HTMLFragment",
"LastEdited" => "Datetime", "LastEdited" => "Datetime",

View File

@ -66,7 +66,7 @@ class VirtualPage extends Page
); );
private static $has_one = array( private static $has_one = array(
"CopyContentFrom" => "SilverStripe\\CMS\\Model\\SiteTree", "CopyContentFrom" => SiteTree::class,
); );
private static $owns = array( private static $owns = array(

View File

@ -37,6 +37,7 @@ class SiteTreeBrokenLinksTest extends SapphireTest
public function testBrokenLinksBetweenPages() public function testBrokenLinksBetweenPages()
{ {
/** @var Page $obj */
$obj = $this->objFromFixture('Page', 'content'); $obj = $this->objFromFixture('Page', 'content');
$obj->Content = '<a href="[sitetree_link,id=3423423]">this is a broken link</a>'; $obj->Content = '<a href="[sitetree_link,id=3423423]">this is a broken link</a>';
@ -50,6 +51,7 @@ class SiteTreeBrokenLinksTest extends SapphireTest
public function testBrokenAnchorBetweenPages() public function testBrokenAnchorBetweenPages()
{ {
/** @var Page $obj */
$obj = $this->objFromFixture('Page', 'content'); $obj = $this->objFromFixture('Page', 'content');
$target = $this->objFromFixture('Page', 'about'); $target = $this->objFromFixture('Page', 'about');
@ -141,7 +143,6 @@ class SiteTreeBrokenLinksTest extends SapphireTest
$this->assertEquals(0, (int)$linkSrc->HasBrokenLink); $this->assertEquals(0, (int)$linkSrc->HasBrokenLink);
// Delete page from draft // Delete page from draft
$linkDestID = $linkDest->ID;
$linkDest->delete(); $linkDest->delete();
// Confirm draft has broken link // Confirm draft has broken link
@ -156,10 +157,12 @@ class SiteTreeBrokenLinksTest extends SapphireTest
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
// Set up two draft pages with a link from content -> about // Set up two draft pages with a link from content -> about
/** @var Page $linkDest */
$linkDest = $this->objFromFixture('Page', 'about'); $linkDest = $this->objFromFixture('Page', 'about');
// Ensure that it's not on the published site // Ensure that it's not on the published site
$linkDest->doUnpublish(); $linkDest->doUnpublish();
/** @var Page $linkSrc */
$linkSrc = $this->objFromFixture('Page', 'content'); $linkSrc = $this->objFromFixture('Page', 'content');
$linkSrc->Content = "<p><a href=\"[sitetree_link,id=$linkDest->ID]\">about us</a></p>"; $linkSrc->Content = "<p><a href=\"[sitetree_link,id=$linkDest->ID]\">about us</a></p>";
$linkSrc->write(); $linkSrc->write();
@ -191,11 +194,6 @@ class SiteTreeBrokenLinksTest extends SapphireTest
$p2->write(); $p2->write();
$this->assertTrue($p2->publishRecursive()); $this->assertTrue($p2->publishRecursive());
// Virtual pages are another
$vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
// Redirector links are a third // Redirector links are a third
$rp = new RedirectorPage(); $rp = new RedirectorPage();
$rp->Title = "redirector"; $rp->Title = "redirector";
@ -206,7 +204,6 @@ class SiteTreeBrokenLinksTest extends SapphireTest
// Confirm that there are no broken links to begin with // Confirm that there are no broken links to begin with
$this->assertFalse($p2->HasBrokenLink); $this->assertFalse($p2->HasBrokenLink);
$this->assertFalse($vp->HasBrokenLink);
$this->assertFalse($rp->HasBrokenLink); $this->assertFalse($rp->HasBrokenLink);
// Unpublishing doesn't affect broken state on live (draft is source of truth) // Unpublishing doesn't affect broken state on live (draft is source of truth)
@ -218,14 +215,11 @@ class SiteTreeBrokenLinksTest extends SapphireTest
// Delete the source page, confirm that the VP, RP and page 2 have broken links on draft // Delete the source page, confirm that the VP, RP and page 2 have broken links on draft
$p->delete(); $p->delete();
$vp->flushCache();
$vp = DataObject::get_by_id(SiteTree::class, $vp->ID);
$p2->flushCache(); $p2->flushCache();
$p2 = DataObject::get_by_id(SiteTree::class, $p2->ID); $p2 = DataObject::get_by_id(SiteTree::class, $p2->ID);
$rp->flushCache(); $rp->flushCache();
$rp = DataObject::get_by_id(SiteTree::class, $rp->ID); $rp = DataObject::get_by_id(SiteTree::class, $rp->ID);
$this->assertEquals(1, $p2->HasBrokenLink); $this->assertEquals(1, $p2->HasBrokenLink);
$this->assertEquals(1, $vp->HasBrokenLink);
$this->assertEquals(1, $rp->HasBrokenLink); $this->assertEquals(1, $rp->HasBrokenLink);
// Restore the page to stage, confirm that this fixes the links // Restore the page to stage, confirm that this fixes the links
@ -235,12 +229,9 @@ class SiteTreeBrokenLinksTest extends SapphireTest
$p2->flushCache(); $p2->flushCache();
$p2 = DataObject::get_by_id(SiteTree::class, $p2->ID); $p2 = DataObject::get_by_id(SiteTree::class, $p2->ID);
$vp->flushCache();
$vp = DataObject::get_by_id(SiteTree::class, $vp->ID);
$rp->flushCache(); $rp->flushCache();
$rp = DataObject::get_by_id(SiteTree::class, $rp->ID); $rp = DataObject::get_by_id(SiteTree::class, $rp->ID);
$this->assertFalse((bool)$p2->HasBrokenLink); $this->assertFalse((bool)$p2->HasBrokenLink);
$this->assertFalse((bool)$vp->HasBrokenLink);
$this->assertFalse((bool)$rp->HasBrokenLink); $this->assertFalse((bool)$rp->HasBrokenLink);
// Publish and confirm that the p2 and RP broken links are fixed on published // Publish and confirm that the p2 and RP broken links are fixed on published
@ -254,68 +245,57 @@ class SiteTreeBrokenLinksTest extends SapphireTest
public function testRevertToLiveFixesBrokenLinks() public function testRevertToLiveFixesBrokenLinks()
{ {
// Create page and virutal page // Create page and virutal page
$p = new Page(); $page = new Page();
$p->Title = "source"; $page->Title = "source";
$p->write(); $page->write();
$pageID = $p->ID; $pageID = $page->ID;
$this->assertTrue($p->publishRecursive()); $this->assertTrue($page->publishRecursive());
// Content links are one kind of link to pages // Content links are one kind of link to pages
$p2 = new Page(); $page2 = new Page();
$p2->Title = "regular link"; $page2->Title = "regular link";
$p2->Content = "<a href=\"[sitetree_link,id=$p->ID]\">test</a>"; $page2->Content = "<a href=\"[sitetree_link,id={$pageID}]\">test</a>";
$p2->write(); $page2->write();
$this->assertTrue($p2->publishRecursive()); $this->assertTrue($page2->publishRecursive());
// Virtual pages are another
$vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
// Redirector links are a third // Redirector links are a third
$rp = new RedirectorPage(); $redirectorPage = new RedirectorPage();
$rp->Title = "redirector"; $redirectorPage->Title = "redirector";
$rp->LinkType = 'Internal'; $redirectorPage->LinkType = 'Internal';
$rp->LinkToID = $p->ID; $redirectorPage->LinkToID = $page->ID;
$rp->write(); $redirectorPage->write();
$this->assertTrue($rp->publishRecursive()); $this->assertTrue($redirectorPage->publishRecursive());
// Confirm that there are no broken links to begin with // Confirm that there are no broken links to begin with
$this->assertFalse($p2->HasBrokenLink); $this->assertFalse($page2->HasBrokenLink);
$this->assertFalse($vp->HasBrokenLink); $this->assertFalse($redirectorPage->HasBrokenLink);
$this->assertFalse($rp->HasBrokenLink);
// Delete from draft and confirm that broken links are marked // Delete from draft and confirm that broken links are marked
$pID = $p->ID; $page->delete();
$p->delete();
$vp->flushCache(); $page2->flushCache();
$vp = DataObject::get_by_id(SiteTree::class, $vp->ID); $page2 = DataObject::get_by_id(SiteTree::class, $page2->ID);
$p2->flushCache(); $redirectorPage->flushCache();
$p2 = DataObject::get_by_id(SiteTree::class, $p2->ID); $redirectorPage = DataObject::get_by_id(SiteTree::class, $redirectorPage->ID);
$rp->flushCache(); $this->assertEquals(1, $page2->HasBrokenLink);
$rp = DataObject::get_by_id(SiteTree::class, $rp->ID); $this->assertEquals(1, $redirectorPage->HasBrokenLink);
$this->assertEquals(1, $p2->HasBrokenLink);
$this->assertEquals(1, $vp->HasBrokenLink);
$this->assertEquals(1, $rp->HasBrokenLink);
// Call doRevertToLive and confirm that broken links are restored // Call doRevertToLive and confirm that broken links are restored
$pLive = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $pID); /** @var Page $pageLive */
$pLive->doRevertToLive(); $pageLive = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $pageID);
$pageLive->doRevertToLive();
$p2->flushCache(); $page2->flushCache();
$p2 = DataObject::get_by_id(SiteTree::class, $p2->ID); $page2 = DataObject::get_by_id(SiteTree::class, $page2->ID);
$vp->flushCache(); $redirectorPage->flushCache();
$vp = DataObject::get_by_id(SiteTree::class, $vp->ID); $redirectorPage = DataObject::get_by_id(SiteTree::class, $redirectorPage->ID);
$rp->flushCache(); $this->assertFalse((bool)$page2->HasBrokenLink);
$rp = DataObject::get_by_id(SiteTree::class, $rp->ID); $this->assertFalse((bool)$redirectorPage->HasBrokenLink);
$this->assertFalse((bool)$p2->HasBrokenLink);
$this->assertFalse((bool)$vp->HasBrokenLink);
$this->assertFalse((bool)$rp->HasBrokenLink);
} }
public function testBrokenAnchorLinksInAPage() public function testBrokenAnchorLinksInAPage()
{ {
/** @var Page $obj */
$obj = $this->objFromFixture('Page', 'content'); $obj = $this->objFromFixture('Page', 'content');
$origContent = $obj->Content; $origContent = $obj->Content;

View File

@ -1,18 +1,16 @@
<?php <?php
use SilverStripe\ORM\DataObject;
use SilverStripe\Versioned\Versioned;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\ValidationException;
use SilverStripe\ORM\FieldType\DBVarchar;
use SilverStripe\ORM\DataExtension;
use SilverStripe\CMS\Model\VirtualPage;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\CMS\Model\RedirectorPage;
use SilverStripe\CMS\Controllers\ModelAsController; use SilverStripe\CMS\Controllers\ModelAsController;
use SilverStripe\CMS\Model\RedirectorPage;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\CMS\Model\VirtualPage;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\FunctionalTest; use SilverStripe\Dev\FunctionalTest;
use SilverStripe\Dev\TestOnly; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\ValidationException;
use SilverStripe\Security\Member;
use SilverStripe\Versioned\Versioned;
class VirtualPageTest extends FunctionalTest class VirtualPageTest extends FunctionalTest
{ {
@ -200,51 +198,12 @@ class VirtualPageTest extends FunctionalTest
$this->assertTrue($vp->canPublish()); $this->assertTrue($vp->canPublish());
} }
public function testCanDeleteOrphanedVirtualPagesFromLive()
{
// An unpublished source page
$p = new Page();
$p->Content = "test content";
$p->write();
$p->publishRecursive();
$pID = $p->ID;
$vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
$this->assertTrue($vp->canPublish());
$this->assertTrue($vp->publishRecursive());
// Delete the source page semi-manually, without triggering
// the cascade publish back to the virtual page.
Versioned::set_stage(Versioned::LIVE);
$livePage = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)->byID($pID);
$livePage->delete();
Versioned::set_stage(Versioned::DRAFT);
// Confirm that we can unpublish, but not publish
$this->assertFalse($p->IsPublished(), 'Copied page has orphaned the virtual page on live');
$this->assertTrue($vp->isPublished(), 'Virtual page remains on live');
$this->assertTrue($vp->canUnpublish());
$this->assertFalse($vp->canPublish());
// Confirm that the action really works
$this->assertTrue($vp->doUnpublish());
$this->assertEquals(
0,
DB::prepared_query(
"SELECT count(*) FROM \"SiteTree_Live\" WHERE \"ID\" = ?",
array($vp->ID)
)->value()
);
}
public function testCanEdit() public function testCanEdit()
{ {
$parentPage = $this->objFromFixture('Page', 'master3'); $parentPage = $this->objFromFixture('Page', 'master3');
$virtualPage = $this->objFromFixture(VirtualPage::class, 'vp3'); $virtualPage = $this->objFromFixture(VirtualPage::class, 'vp3');
$bob = $this->objFromFixture('SilverStripe\\Security\\Member', 'bob'); $bob = $this->objFromFixture(Member::class, 'bob');
$andrew = $this->objFromFixture('SilverStripe\\Security\\Member', 'andrew'); $andrew = $this->objFromFixture(Member::class, 'andrew');
// Bob can edit the mirrored page, but he shouldn't be able to edit the virtual page. // Bob can edit the mirrored page, but he shouldn't be able to edit the virtual page.
$this->logInAs($bob); $this->logInAs($bob);
@ -259,12 +218,13 @@ class VirtualPageTest extends FunctionalTest
public function testCanView() public function testCanView()
{ {
/** @var Page $parentPage */
$parentPage = $this->objFromFixture('Page', 'master3'); $parentPage = $this->objFromFixture('Page', 'master3');
$parentPage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); $parentPage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
$virtualPage = $this->objFromFixture(VirtualPage::class, 'vp3'); $virtualPage = $this->objFromFixture(VirtualPage::class, 'vp3');
$virtualPage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); $virtualPage->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
$cindy = $this->objFromFixture('SilverStripe\\Security\\Member', 'cindy'); $cindy = $this->objFromFixture(Member::class, 'cindy');
$alice = $this->objFromFixture('SilverStripe\\Security\\Member', 'alice'); $alice = $this->objFromFixture(Member::class, 'alice');
// Cindy can see both pages // Cindy can see both pages
$this->logInAs($cindy); $this->logInAs($cindy);
@ -336,6 +296,7 @@ class VirtualPageTest extends FunctionalTest
$vp = new VirtualPage(); $vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID; $vp->CopyContentFromID = $p->ID;
$vp->write(); $vp->write();
$vpID = $vp->ID;
$this->assertTrue($vp->publishRecursive()); $this->assertTrue($vp->publishRecursive());
// All is fine, the virtual page doesn't have a broken link // All is fine, the virtual page doesn't have a broken link
@ -346,17 +307,17 @@ class VirtualPageTest extends FunctionalTest
// The draft VP still has the CopyContentFromID link // The draft VP still has the CopyContentFromID link
$vp->flushCache(); $vp->flushCache();
$vp = DataObject::get_by_id(SiteTree::class, $vp->ID); $vp = DataObject::get_by_id(SiteTree::class, $vpID);
$this->assertEquals($p->ID, $vp->CopyContentFromID); $this->assertEquals($p->ID, $vp->CopyContentFromID);
$vpLive = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, '"SiteTree"."ID" = ' . $vp->ID); $vpLive = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, '"SiteTree"."ID" = ' . $vpID);
$this->assertNull($vpLive); $this->assertNull($vpLive);
// Delete from draft, confirm that the virtual page has a broken link on the draft site // Delete from draft, ensure virtual page deletion cascades
$p->delete(); $p->delete();
$vp->flushCache(); $vp->flushCache();
$vp = DataObject::get_by_id(SiteTree::class, $vp->ID); $vp = DataObject::get_by_id(SiteTree::class, $vpID);
$this->assertEquals(1, $vp->HasBrokenLink); $this->assertNull($vp);
} }
public function testDeletingFromLiveSourcePageOfAVirtualPageAlsoUnpublishesVirtualPage() public function testDeletingFromLiveSourcePageOfAVirtualPageAlsoUnpublishesVirtualPage()
@ -369,29 +330,27 @@ class VirtualPageTest extends FunctionalTest
$vp = new VirtualPage(); $vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID; $vp->CopyContentFromID = $p->ID;
$vp->write(); $vp->write();
$vpID = $vp->ID;
$this->assertTrue($vp->publishRecursive()); $this->assertTrue($vp->publishRecursive());
// All is fine, the virtual page doesn't have a broken link // All is fine, the virtual page doesn't have a broken link
$this->assertFalse($vp->HasBrokenLink); $this->assertFalse($vp->HasBrokenLink);
// Delete the source page from draft, confirm that this creates a broken link // Delete the source page from draft, cascades to virtual page
$pID = $p->ID; $pID = $p->ID;
$p->delete(); $p->delete();
$vp->flushCache(); $vp->flushCache();
$vp = DataObject::get_by_id(SiteTree::class, $vp->ID); $vpDraft = Versioned::get_by_stage(SiteTree::class, Versioned::DRAFT)
$this->assertEquals(1, $vp->HasBrokenLink); ->byID($pID);
$this->assertNull($vpDraft);
// Delete the source page form live, confirm that the virtual page has also been unpublished // Delete the source page form live, confirm that the virtual page has also been unpublished
$pLive = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, '"SiteTree"."ID" = ' . $pID); $pLive = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)
->byID($pID);
$this->assertTrue($pLive->doUnpublish()); $this->assertTrue($pLive->doUnpublish());
$vpLive = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, '"SiteTree"."ID" = ' . $vp->ID); $vpLive = Versioned::get_by_stage(SiteTree::class, Versioned::LIVE)
->byID($vpID);
$this->assertNull($vpLive); $this->assertNull($vpLive);
// Delete from draft, confirm that the virtual page has a broken link on the draft site
$pLive->delete();
$vp->flushCache();
$vp = DataObject::get_by_id(SiteTree::class, $vp->ID);
$this->assertEquals(1, $vp->HasBrokenLink);
} }
/** /**