logInWithPermission('ADMIN'); } public function tearDown() { TestAssetStore::reset(); parent::tearDown(); } public function testBrokenLinksBetweenPages() { /** @var Page $obj */ $obj = $this->objFromFixture('Page', 'content'); $obj->Content = 'this is a broken link'; $obj->syncLinkTracking(); $this->assertTrue($obj->HasBrokenLink, 'Page has a broken link'); $obj->Content = 'this is not a broken link'; $obj->syncLinkTracking(); $this->assertFalse($obj->HasBrokenLink, 'Page does NOT have a broken link'); } public function testBrokenAnchorBetweenPages() { /** @var Page $obj */ $obj = $this->objFromFixture('Page', 'content'); $target = $this->objFromFixture('Page', 'about'); $obj->Content = "ID}]#no-anchor-here\">this is a broken link"; $obj->syncLinkTracking(); $this->assertTrue($obj->HasBrokenLink, 'Page has a broken link'); $obj->Content = "ID}]#yes-anchor-here\">this is not a broken link"; $obj->syncLinkTracking(); $this->assertFalse($obj->HasBrokenLink, 'Page does NOT have a broken link'); } public function testBrokenVirtualPages() { $obj = $this->objFromFixture('Page', 'content'); $vp = new VirtualPage(); $vp->CopyContentFromID = $obj->ID; $vp->syncLinkTracking(); $this->assertFalse($vp->HasBrokenLink, 'Working virtual page is NOT marked as broken'); $vp->CopyContentFromID = 12345678; $vp->syncLinkTracking(); $this->assertTrue($vp->HasBrokenLink, 'Broken virtual page IS marked as such'); } public function testBrokenInternalRedirectorPages() { $obj = $this->objFromFixture('Page', 'content'); $rp = new RedirectorPage(); $rp->RedirectionType = 'Internal'; $rp->LinkToID = $obj->ID; $rp->syncLinkTracking(); $this->assertFalse($rp->HasBrokenLink, 'Working redirector page is NOT marked as broken'); $rp->LinkToID = 12345678; $rp->syncLinkTracking(); $this->assertTrue($rp->HasBrokenLink, 'Broken redirector page IS marked as such'); } public function testDeletingFileMarksBackedPagesAsBroken() { // Test entry $file = new File(); $file->setFromString('test', 'test-file.txt'); $file->write(); /** @var Page $obj */ $obj = $this->objFromFixture('Page', 'content'); $obj->Content = sprintf( '
', $file->ID ); $obj->write(); $this->assertTrue($obj->publishRecursive()); // Confirm that it isn't marked as broken to begin with $obj = SiteTree::get()->byID($obj->ID); $this->assertEquals(0, $obj->HasBrokenFile); $liveObj = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, "\"SiteTree\".\"ID\" = $obj->ID"); $this->assertEquals(0, $liveObj->HasBrokenFile); // Delete the file $file->delete(); // Confirm that it is marked as broken in stage $obj = SiteTree::get()->byID($obj->ID); $this->assertEquals(1, $obj->HasBrokenFile); // Publishing this page marks it as broken on live too $obj->publishRecursive(); $liveObj = Versioned::get_one_by_stage(SiteTree::class, Versioned::LIVE, "\"SiteTree\".\"ID\" = $obj->ID"); $this->assertEquals(1, $liveObj->HasBrokenFile); } public function testDeletingMarksBackLinkedPagesAsBroken() { // Set up two published pages with a link from content -> about $linkDest = $this->objFromFixture('Page', 'about'); $linkSrc = $this->objFromFixture('Page', 'content'); $linkSrc->Content = ""; $linkSrc->write(); // Confirm no broken link $this->assertEquals(0, (int)$linkSrc->HasBrokenLink); // Delete page from draft $linkDest->delete(); // Confirm draft has broken link $linkSrc->flushCache(); $linkSrc = $this->objFromFixture('Page', 'content'); $this->assertEquals(1, (int)$linkSrc->HasBrokenLink); } public function testPublishingSourceBeforeDestHasBrokenLink() { $this->logInWithPermission('ADMIN'); // Set up two draft pages with a link from content -> about /** @var Page $linkDest */ $linkDest = $this->objFromFixture('Page', 'about'); // Ensure that it's not on the published site $linkDest->doUnpublish(); /** @var Page $linkSrc */ $linkSrc = $this->objFromFixture('Page', 'content'); $linkSrc->Content = ""; $linkSrc->write(); // Publish the source of the link, while the dest is still unpublished. $linkSrc->publishRecursive(); // Verify that the link is not marked as broken on draft (source of truth) $this->assertEquals(0, (int)$linkSrc->HasBrokenLink); // Live doesn't have separate broken link tracking $this->assertEquals(0, DB::query("SELECT \"HasBrokenLink\" FROM \"SiteTree_Live\" WHERE \"ID\" = $linkSrc->ID")->value()); } public function testRestoreFixesBrokenLinks() { // Create page and virtual page $p = new Page(); $p->Title = "source"; $p->write(); $pageID = $p->ID; $this->assertTrue($p->publishRecursive()); // Content links are one kind of link to pages $p2 = new Page(); $p2->Title = "regular link"; $p2->Content = "ID]\">test"; $p2->write(); $this->assertTrue($p2->publishRecursive()); // Redirector links are a third $rp = new RedirectorPage(); $rp->Title = "redirector"; $rp->LinkType = 'Internal'; $rp->LinkToID = $p->ID; $rp->write(); $this->assertTrue($rp->publishRecursive()); // Confirm that there are no broken links to begin with $this->assertFalse($p2->HasBrokenLink); $this->assertFalse($rp->HasBrokenLink); // Unpublishing doesn't affect broken state on live (draft is source of truth) $p->doUnpublish(); $p2Live = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $p2->ID); $rpLive = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $rp->ID); $this->assertEquals(0, $p2Live->HasBrokenLink); $this->assertEquals(0, $rpLive->HasBrokenLink); // Delete the source page, confirm that the VP, RP and page 2 have broken links on draft $p->delete(); $p2->flushCache(); $p2 = DataObject::get_by_id(SiteTree::class, $p2->ID); $rp->flushCache(); $rp = DataObject::get_by_id(SiteTree::class, $rp->ID); $this->assertEquals(1, $p2->HasBrokenLink); $this->assertEquals(1, $rp->HasBrokenLink); // Restore the page to stage, confirm that this fixes the links /** @var SiteTree $p */ $p = Versioned::get_latest_version(SiteTree::class, $pageID); $p->doRestoreToStage(); $p2->flushCache(); $p2 = DataObject::get_by_id(SiteTree::class, $p2->ID); $rp->flushCache(); $rp = DataObject::get_by_id(SiteTree::class, $rp->ID); $this->assertFalse((bool)$p2->HasBrokenLink); $this->assertFalse((bool)$rp->HasBrokenLink); // Publish and confirm that the p2 and RP broken links are fixed on published $this->assertTrue($p->publishRecursive()); $p2Live = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $p2->ID); $rpLive = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $rp->ID); $this->assertFalse((bool)$p2Live->HasBrokenLink); $this->assertFalse((bool)$rpLive->HasBrokenLink); } public function testRevertToLiveFixesBrokenLinks() { // Create page and virutal page $page = new Page(); $page->Title = "source"; $page->write(); $pageID = $page->ID; $this->assertTrue($page->publishRecursive()); // Content links are one kind of link to pages $page2 = new Page(); $page2->Title = "regular link"; $page2->Content = "test"; $page2->write(); $this->assertTrue($page2->publishRecursive()); // Redirector links are a third $redirectorPage = new RedirectorPage(); $redirectorPage->Title = "redirector"; $redirectorPage->LinkType = 'Internal'; $redirectorPage->LinkToID = $page->ID; $redirectorPage->write(); $this->assertTrue($redirectorPage->publishRecursive()); // Confirm that there are no broken links to begin with $this->assertFalse($page2->HasBrokenLink); $this->assertFalse($redirectorPage->HasBrokenLink); // Delete from draft and confirm that broken links are marked $page->delete(); $page2->flushCache(); $page2 = DataObject::get_by_id(SiteTree::class, $page2->ID); $redirectorPage->flushCache(); $redirectorPage = DataObject::get_by_id(SiteTree::class, $redirectorPage->ID); $this->assertEquals(1, $page2->HasBrokenLink); $this->assertEquals(1, $redirectorPage->HasBrokenLink); // Call doRevertToLive and confirm that broken links are restored /** @var Page $pageLive */ $pageLive = Versioned::get_one_by_stage(SiteTree::class, 'Live', '"SiteTree"."ID" = ' . $pageID); $pageLive->doRevertToLive(); $page2->flushCache(); $page2 = DataObject::get_by_id(SiteTree::class, $page2->ID); $redirectorPage->flushCache(); $redirectorPage = DataObject::get_by_id(SiteTree::class, $redirectorPage->ID); $this->assertFalse((bool)$page2->HasBrokenLink); $this->assertFalse((bool)$redirectorPage->HasBrokenLink); } public function testBrokenAnchorLinksInAPage() { /** @var Page $obj */ $obj = $this->objFromFixture('Page', 'content'); $origContent = $obj->Content; $obj->Content = $origContent . 'this links to a non-existent in-page anchor or skiplink'; $obj->syncLinkTracking(); $this->assertTrue($obj->HasBrokenLink, 'Page has a broken anchor/skiplink'); $obj->Content = $origContent . 'this links to an existent in-page anchor/skiplink'; $obj->syncLinkTracking(); $this->assertFalse($obj->HasBrokenLink, 'Page doesn\'t have a broken anchor or skiplink'); } }