API CHANGE: Added SiteTree::VirtualPages() and SiteTree::DependentPages() accessors.

BUGFIX: Improved reliabilty of broken link tracking.
ENHANCEMENT: Added 'Dependent pages' tab to CMS, to show virtuals, redirectors, and backlinks that point to this page.
BUGFIX: Don't mark a page as changed on stage if the only thing that has changed is broken link metadata (from r101127)

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@111594 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2010-10-04 04:52:46 +00:00
parent 55c5d1cab7
commit d19bf168b0
6 changed files with 458 additions and 211 deletions

View File

@ -1372,6 +1372,24 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
DataObject::set_context_obj(null);
$this->syncLinkTracking();
// Check to see if we've only altered fields that shouldn't affect versioning
$fieldsIgnoredByVersioning = array('HasBrokenLink', 'Status', 'HasBrokenFile', 'ToDo');
$changedFields = array_keys($this->getChangedFields(true, 2));
// This more rigorous check is inline with the test that write()
// does to dedcide whether or not to write to the DB. We use that
// to avoid cluttering the system with a migrateVersion() call
// that doesn't get used
$oneChangedFields = array_keys($this->getChangedFields(true, 1));
if($oneChangedFields && !array_diff($changedFields, $fieldsIgnoredByVersioning)) {
// This will have the affect of preserving the versioning
$this->migrateVersion($this->Version);
}
parent::onBeforeWrite();
}
function syncLinkTracking() {
@ -1390,46 +1408,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$this->HasBrokenLink = false;
$this->HasBrokenFile = false;
$formFields = $this->getCMSFields(null);
foreach($htmlFields as $field) {
// Set LinkTracking appropriately
$links = HTTP::getLinksIn($this->$field);
if($links) foreach($links as $link) {
if(preg_match('/^([A-Za-z0-9_-]+)\/?(#.*)?$/', $link, $parts)) {
$candidatePage = DataObject::get_one("SiteTree", "\"URLSegment\" = '" . urldecode( $parts[1] ). "'", false);
if($candidatePage) {
$linkedPages[] = $candidatePage->ID;
} else {
$this->HasBrokenLink = true;
}
} else if(substr($link,0,7) == 'assets/') {
$candidateFile = File::find(Convert::raw2sql(urldecode($link)));
if($candidateFile) {
$linkedFiles[] = $candidateFile->ID;
} else {
$this->HasBrokenFile = true;
}
} else if($link == '' || $link[0] == '/') {
$this->HasBrokenLink = true;
}
}
$images = HTTP::getImagesIn($this->$field);
if($images) {
foreach($images as $image) {
$image = Director::makeRelative($image);
if(substr($image,0,7) == 'assets/') {
$candidateImage = File::find($image);
if($candidateImage) $linkedFiles[] = $candidateImage->ID;
else $this->HasBrokenFile = true;
}
}
}
$formField = $formFields->dataFieldByName($field);
$formField->setValue($this->$field);
$formField->saveInto($this);
}
$this->LinkTracking()->setByIDList($linkedPages);
$this->ImageTracking()->setByIDList($linkedFiles);
$this->extend('augmentSyncLinkTracking');
}
@ -1438,7 +1422,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$this->flushCache();
// Update any virtual pages that might need updating
$linkedPages = DataObject::get("VirtualPage", "\"CopyContentFromID\" = $this->ID");
$linkedPages = $this->VirtualPages();
if($linkedPages) foreach($linkedPages as $page) {
$page->copyFrom($page->CopyContentFrom());
$page->write();
@ -1463,32 +1447,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Need to flush cache to avoid outdated versionnumber references
$this->flushCache();
// Need to mark pages linking to this one as broken
foreach($this->BackLinkTracking() as $page) {
// $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write();
}
// Also write all VPs pointing here
$suffix = Versioned::current_stage() == 'Live' ? '_Live' : '';
// This coupling to the subsites module is frustrating, but difficult to avoid.
if(class_exists('Subsite')) {
$virtualPages = Subsite::get_from_all_subsites('VirtualPage', "\"SiteTree$suffix\".\"ID\" = \"SiteTree$suffix\".\"ID\" AND \"CopyContentFromID\" = {$this->ID}");
} else {
$virtualPages = DataObject::get('VirtualPage', "\"SiteTree$suffix\".\"ID\" = \"SiteTree$suffix\".\"ID\" AND \"CopyContentFromID\" = {$this->ID}");
}
if(class_exists('Subsite')) {
$redirectorPages = Subsite::get_from_all_subsites('RedirectorPage', "\"SiteTree$suffix\".\"ID\" = \"SiteTree$suffix\".\"ID\" AND \"LinkToID\" = {$this->ID}");
} else {
$redirectorPages = DataObject::get('RedirectorPage', "\"SiteTree$suffix\".\"ID\" = \"SiteTree$suffix\".\"ID\" AND \"LinkToID\" = {$this->ID}");
}
if($virtualPages) foreach($virtualPages as $page) {
// $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write();
}
if($redirectorPages) foreach($redirectorPages as $page) {
// Need to mark pages depending to this one as broken
$dependentPages = $this->DependentPages();
if($dependentPages) foreach($dependentPages as $page) {
// $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write();
}
@ -1618,6 +1579,76 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
}
}
/**
* Returns the pages that depend on this page.
* This includes virtual pages, pages that link to it, etc.
*
* @param $includeVirtuals Set to false to exlcude virtual pages.
*/
function DependentPages($includeVirtuals = true) {
if(is_callable('Subsite::disable_subsite_filter')) Subsite::disable_subsite_filter(true);
// Content links
$items = $this->BackLinkTracking();
if(!$items) $items = new DataObjectSet();
else foreach($items as $item) $item->DependentLinkType = 'Content link';
// Virtual pages
if($includeVirtuals) {
$virtuals = $this->VirtualPages();
if($virtuals) {
foreach($virtuals as $item) $item->DependentLinkType = 'Virtual page';
$items->merge($virtuals);
}
}
// Redirector pages
$redirectors = DataObject::get("RedirectorPage", "\"RedirectionType\" = 'Internal' AND \"LinkToID\" = $this->ID");
if($redirectors) {
foreach($redirectors as $item) $item->DependentLinkType = 'Redirector page';
$items->merge($redirectors);
}
if(is_callable('Subsite::disable_subsite_filter')) Subsite::disable_subsite_filter(false);
return $items;
}
/**
* Return the number of {@link DependentPages()}
*
* @param $includeVirtuals Set to false to exlcude virtual pages.
*/
function DependentPagesCount($includeVirtuals = true) {
$links = DB::query("SELECT COUNT(*) FROM \"SiteTree_LinkTracking\"
INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"SiteTree_LinkTracking\".\"SiteTreeID\"
WHERE \"ChildID\" = $this->ID ")->value();
if($includeVirtuals) {
$virtuals = DB::query("SELECT COUNT(*) FROM \"VirtualPage\"
INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"VirtualPage\".\"ID\"
WHERE \"CopyContentFromID\" = $this->ID")->value();
} else {
$virtuals = 0;
}
$redirectors = DB::query("SELECT COUNT(*) FROM \"RedirectorPage\"
INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"RedirectorPage\".\"ID\"
WHERE \"RedirectionType\" = 'Internal' AND \"LinkToID\" = $this->ID")->value();
return 0 + $links + $virtuals + $redirectors;
}
/**
* Return all virtual pages that link to this page
*/
function VirtualPages() {
if(!$this->ID) return null;
if(class_exists('Subsite')) {
return Subsite::get_from_all_subsites('VirtualPage', "\"CopyContentFromID\" = " . (int)$this->ID);
} else {
return DataObject::get('VirtualPage', "\"CopyContentFromID\" = " . (int)$this->ID);
}
}
/**
* Returns a FieldSet with which to create the CMS editing form.
*
@ -1637,7 +1668,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Status / message
// Create a status message for multiple parents
if($this->ID && is_numeric($this->ID)) {
$linkedPages = DataObject::get("VirtualPage", "\"CopyContentFromID\" = $this->ID");
$linkedPages = $this->VirtualPages();
}
$parentPageLinks = array();
@ -1674,52 +1705,37 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$statusMessage[] = _t('SiteTree.HASBROKENLINKS', "This page has broken links.");
}
$backLinksNote = '';
$backLinksTable = new LiteralField('BackLinksNote', '<p>' . _t('NOBACKLINKEDPAGES', 'There are no pages linked to this page.') . '</p>');
$dependentNote = '';
$dependentTable = new LiteralField('DependentNote', '<p></p>');
// Create a table for showing pages linked to this one
if($this->BackLinkTracking() && $this->BackLinkTracking()->Count() > 0) {
$backLinksNote = new LiteralField('BackLinksNote', '<p>' . _t('SiteTree.PAGESLINKING', 'The following pages link to this page:') . '</p>');
$backLinksTable = new TableListField(
'BackLinkTracking',
'SiteTree',
array(
'Title' => 'Title',
'AbsoluteLink' => 'URL'
),
'"ChildID" = ' . $this->ID,
'',
'LEFT JOIN "SiteTree_LinkTracking" ON "SiteTree"."ID" = "SiteTree_LinkTracking"."SiteTreeID"'
$dependentPagesCount = $this->DependentPagesCount();
if($dependentPagesCount) {
$dependentColumns = array(
'Title' => 'Title',
'Subsite.Title' => 'Subsite',
'AbsoluteLink' => 'URL',
'DependentLinkType' => 'Link type',
);
$backLinksTable->setFieldFormatting(array(
if(!class_exists('Subsite')) unset($dependentColumns['Subsite.Title']);
$dependentNote = new LiteralField('DependentNote', '<p>' . _t('SiteTree.DEPENDENT_NOTE', 'The following pages depend on this page. This includes virtual pages, redirector pages, and pages with content links.') . '</p>');
$dependentTable = new TableListField(
'DependentPages',
'SiteTree',
$dependentColumns
);
$dependentTable->setCustomSourceItems($this->DependentPages());
$dependentTable->setFieldFormatting(array(
'Title' => '<a href=\"admin/show/$ID\">$Title</a>',
'AbsoluteLink' => '<a href=\"$value\">$value</a>',
));
$backLinksTable->setPermissions(array(
$dependentTable->setPermissions(array(
'show',
'export'
));
}
$virtualPagesNote = new LiteralField('BackLinksNote', '<p>' . _t('SiteTree.VIRTUALPAGESLINKING', 'The following virtual pages pull from this page:') . '</p>');
$virtualPagesTable = new TableListField(
'VirtualPageTracking',
'SiteTree',
array(
'Title' => 'Title',
'AbsoluteLink' => 'URL'
),
'"CopyContentFromID" = ' . $this->ID,
''
);
$virtualPagesTable->setFieldFormatting(array(
'Title' => '<a href=\"admin/show/$ID\">$Title</a>'
));
$virtualPagesTable->setPermissions(array(
'show',
'export'
));
// Lay out the fields
$fields = new FieldSet(
$rootTab = new TabSet("Root",
@ -1782,11 +1798,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
new LiteralField("ToDoHelp", _t('SiteTree.TODOHELP', "<p>You can use this to keep track of work that needs to be done to the content of your site. To see all your pages with to do information, open the 'Site Reports' window on the left and select 'To Do'</p>")),
new TextareaField("ToDo", "")
),
$tabReports = new TabSet('Reports',
$tabBacklinks = new Tab('Backlinks',
$backLinksNote,
$backLinksTable
)
$tabDependent = new Tab('Dependent',
$dependentNote,
$dependentTable
),
$tabAccess = new Tab('Access',
new HeaderField('WhoCanViewHeader',_t('SiteTree.ACCESSHEADER', "Who can view this page?"), 2),
@ -1812,12 +1826,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/
$parentIDField->setFilterFunction(create_function('$node', "return \$node->ID != {$this->ID};"));
if ($virtualPagesTable->TotalCount()) {
$rootTab->push($tabVirtualPages = new Tab('VirtualPages',
$virtualPagesNote,
$virtualPagesTable
));
}
// Conditional dependent pages tab
if($dependentPagesCount) $tabDependent->setTitle(_t('SiteTree.TABDEPENDENT', "Dependent pages") . " ($dependentPagesCount)");
else $fields->removeFieldFromTab('Root', 'Dependent');
// Make page location fields read-only if the user doesn't have the appropriate permission
if(!Permission::check("SITETREE_REORGANISE")) {
@ -1864,9 +1875,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$tabMain->setTitle(_t('SiteTree.TABMAIN', "Main"));
$tabMeta->setTitle(_t('SiteTree.TABMETA', "Metadata"));
$tabBehaviour->setTitle(_t('SiteTree.TABBEHAVIOUR', "Behaviour"));
$tabReports->setTitle(_t('SiteTree.TABREPORTS', "Reports"));
$tabAccess->setTitle(_t('SiteTree.TABACCESS', "Access"));
$tabBacklinks->setTitle(_t('SiteTree.TABBACKLINKS', "BackLinks"));
if(self::$runCMSFieldsExtensions) {
$this->extend('updateCMSFields', $fields);
@ -2006,38 +2015,21 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
WHERE EXISTS (SELECT \"SiteTree\".\"Sort\" FROM \"SiteTree\" WHERE \"SiteTree_Live\".\"ID\" = \"SiteTree\".\"ID\") AND \"ParentID\" = " . sprintf('%d', $this->ParentID) );
// Publish any virtual pages that might need publishing
$linkedPages = DataObject::get("VirtualPage", "\"CopyContentFromID\" = $this->ID");
$linkedPages = $this->VirtualPages();
if($linkedPages) foreach($linkedPages as $page) {
$page->copyFrom($page->CopyContentFrom());
$page->write();
if($page->ExistsOnLive) $page->doPublish();
}
// Fix links that are different on staging vs live
$needsWriting = false;
$this->syncLinkTracking();
foreach($this->LinkTracking() as $linkedPage) {
$livePage = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $linkedPage->ID);
if($livePage && $livePage->URLSegment != $linkedPage->URLSegment) {
$needsWriting = true;
$this->rewriteLink($linkedPage->URLSegment . '/', $livePage->URLSegment . '/');
}
}
if($needsWriting) {
$this->writeToStage('Live');
}
// If this url has changed, then we need to fix pages linked to this one
if($original->URLSegment && $original->URLSegment != $this->URLSegment) {
foreach($this->BackLinkTracking() as $linkedPage) {
$linkedPageLive = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $linkedPage->ID);
if($linkedPageLive) {
$linkedPageLive->rewriteLink($original->URLSegment . '/', $this->URLSegment . '/');
$linkedPageLive->writeToStage('Live');
}
}
// Need to update pages linking to this one as no longer broken, on the live site
$origMode = Versioned::get_reading_mode();
Versioned::reading_stage('Live');
foreach($this->DependentPages(false) as $page) {
// $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write();
}
Versioned::set_reading_mode($origMode);
// Check to write CMS homepage map.
$usingStaticPublishing = false;
@ -2074,40 +2066,41 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @uses SiteTreeDecorator->onAfterUnpublish()
*/
function doUnpublish() {
if (!$this->canDeleteFromLive()) return false;
if(!$this->canDeleteFromLive()) return false;
if(!$this->ID) return false;
$this->extend('onBeforeUnpublish');
// Call delete on a cloned object so that this one doesn't lose its ID
$this->flushCache();
$clone = DataObject::get_by_id("SiteTree", $this->ID);
if (!$clone) $clone = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree_Live"."ID" = ' . $this->ID);
$clone->deleteFromStage('Live');
$origStage = Versioned::current_stage();
Versioned::reading_stage('Live');
$this->write();
// We should only unpublish virtualpages that exist on live
$virtualPages = $this->VirtualPages();
// Unpublish all virtual pages that point here
// This coupling to the subsites module is frustrating, but difficult to avoid.
if(class_exists('Subsite')) {
$virtualPages = Subsite::get_from_all_subsites('VirtualPage', "\"CopyContentFromID\" = {$this->ID}");
} else {
$virtualPages = DataObject::get('VirtualPage', "\"CopyContentFromID\" = {$this->ID}");
}
if ($virtualPages) foreach($virtualPages as $vp) $vp->doUnpublish();
// This way our ID won't be unset
$clone = clone $this;
$clone->delete();
$suffix = Versioned::current_stage() == 'Live' ? '_Live' : '';
if(class_exists('Subsite')) {
$redirectorPages = Subsite::get_from_all_subsites('RedirectorPage', "\"SiteTree$suffix\".\"ID\" = \"SiteTree$suffix\".\"ID\" AND \"LinkToID\" = {$this->ID}");
} else {
$redirectorPages = DataObject::get('RedirectorPage', "\"SiteTree$suffix\".\"ID\" = \"SiteTree$suffix\".\"ID\" AND \"LinkToID\" = {$this->ID}");
}
if($redirectorPages) foreach($redirectorPages as $page) {
// Rewrite backlinks
$dependentPages = $this->DependentPages(false);
if($dependentPages) foreach($dependentPages as $page) {
// $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write();
}
Versioned::reading_stage($origStage);
// Unpublish any published virtual pages
if ($virtualPages) foreach($virtualPages as $vp) $vp->doUnpublish();
// If we're on the draft site, then we can update the status.
// Otherwise, these lines will resurrect an inappropriate record
if(Versioned::current_stage() != 'Live') {
$this->Status = "Unpublished";
$this->write();
}
$this->extend('onAfterUnpublish');
return true;
}
@ -2130,6 +2123,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$clone = DataObject::get_by_id("SiteTree", $this->ID);
$clone->writeWithoutVersion();
// Need to update pages linking to this one as no longer broken
foreach($this->DependentPages(false) as $page) {
// $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write();
}
$this->extend('onAfterRevertToLive');
}
@ -2154,22 +2153,22 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$result = DataObject::get_by_id($this->class, $this->ID);
// Need to update pages linking to this one as no longer broken
foreach($result->DependentPages(false) as $page) {
// $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write();
}
Versioned::reading_stage($oldStage);
return $result;
}
/**
* Synonym of {@link doUnpublish}
*/
function doDeleteFromLive() {
$this->extend('onBeforeUnpublish');
$origStage = Versioned::current_stage();
Versioned::reading_stage('Live');
$this->delete();
Versioned::reading_stage($origStage);
$this->extend('onAfterUnpublish');
return true;
return $this->doUnpublish();
}
/**

View File

@ -126,13 +126,23 @@ class File extends DataObject {
if($this->Filename && $this->Name && file_exists($this->getFullPath()) && !is_dir($this->getFullPath())) {
unlink($this->getFullPath());
}
}
protected function onAfterDelete() {
parent::onAfterDelete();
if($brokenPages = $this->BackLinkTracking()) {
foreach($brokenPages as $brokenPage) {
Notifications::event("BrokenLink", $brokenPage, $brokenPage->OwnerID);
$brokenPage->HasBrokenFile = true;
$brokenPage->write();
}
$origStage = Versioned::current_stage();
// This will syncLinkTracking on draft
Versioned::reading_stage('Stage');
foreach($brokenPages as $brokenPage) $brokenPage->write();
// This will syncLinkTracking on published
Versioned::reading_stage('Live');
foreach($brokenPages as $brokenPage) $brokenPage->write();
Versioned::reading_stage($origStage);
}
}

View File

@ -36,7 +36,7 @@ class SiteTreeBacklinksTest extends SapphireTest {
$this->assertFalse($page1->BackLinkTracking()->containsIDs(array($page2->ID)), 'Assert backlink to page 2 doesn\'t exist');
// add hyperlink to page 1 on page 2
$page2->Content .= '<p><a href="page1/">Testing page 1 link</a></p>';
$page2->Content .= '<p><a href="[sitetree_link id='.$page1->ID.']">Testing page 1 link</a></p>';
$page2->write();
// load page 1
@ -74,8 +74,8 @@ class SiteTreeBacklinksTest extends SapphireTest {
$this->assertTrue($page1->BackLinkTracking()->containsIDs(array($page3->ID)), 'Assert backlink to page 3 exists');
// assert hyperlink to page 1's current url exists on page 3
$links = HTTP::getLinksIn($page3->Content);
$this->assertContains('page1/', $links, 'Assert hyperlink to page 1\'s current url exists on page 3');
$links = HTTP::getLinksIn($page3->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'page1/', $links, 'Assert hyperlink to page 1\'s current url exists on page 3');
// change url of page 1
$page1->URLSegment = 'new-url-segment';
@ -85,8 +85,8 @@ class SiteTreeBacklinksTest extends SapphireTest {
$page3 = $this->objFromFixture('Page', 'page3');
// assert hyperlink to page 1's new url exists
$links = HTTP::getLinksIn($page3->Content);
$this->assertContains('new-url-segment/', $links, 'Assert hyperlink to page 1\'s new url exists on page 3');
$links = HTTP::getLinksIn($page3->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'new-url-segment/', $links, 'Assert hyperlink to page 1\'s new url exists on page 3');
}
function testChangingUrlOnLiveSiteRewritesLink() {
@ -104,8 +104,8 @@ class SiteTreeBacklinksTest extends SapphireTest {
$this->assertTrue($page1live->BackLinkTracking()->containsIDs(array($page3live->ID)), 'Assert backlink to page 3 exists');
// assert hyperlink to page 1's current url exists on page 3
$links = HTTP::getLinksIn($page3live->Content);
$this->assertContains('page1/', $links, 'Assert hyperlink to page 1\'s current url exists on page 3');
$links = HTTP::getLinksIn($page3live->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'page1/', $links, 'Assert hyperlink to page 1\'s current url exists on page 3');
// change url of page 1
$page1live->URLSegment = 'new-url-segment';
@ -115,8 +115,9 @@ class SiteTreeBacklinksTest extends SapphireTest {
$page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID);
// assert hyperlink to page 1's new url exists
$links = HTTP::getLinksIn($page3live->Content);
$this->assertContains('new-url-segment/', $links, 'Assert hyperlink to page 1\'s new url exists on page 3');
Versioned::reading_stage('Live');
$links = HTTP::getLinksIn($page3live->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'new-url-segment/', $links, 'Assert hyperlink to page 1\'s new url exists on page 3');
}
function testPublishingPageWithModifiedUrlRewritesLink() {
@ -131,8 +132,8 @@ class SiteTreeBacklinksTest extends SapphireTest {
$page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID);
// assert hyperlink to page 1's current url exists
$links = HTTP::getLinksIn($page3live->Content);
$this->assertContains('page1/', $links, 'Assert hyperlink to page 1\'s current url exists on page 3');
$links = HTTP::getLinksIn($page3live->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'page1/', $links, 'Assert hyperlink to page 1\'s current url exists on page 3');
// rename url of page 1 on stage
$page1->URLSegment = 'new-url-segment';
@ -140,8 +141,9 @@ class SiteTreeBacklinksTest extends SapphireTest {
// assert hyperlink to page 1's current publish url exists
$page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID);
$links = HTTP::getLinksIn($page3live->Content);
$this->assertContains('page1/', $links, 'Assert hyperlink to page 1\'s current published url exists on page 3');
Versioned::reading_stage('Live');
$links = HTTP::getLinksIn($page3live->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'page1/', $links, 'Assert hyperlink to page 1\'s current published url exists on page 3');
// publish page 1
@ -149,8 +151,8 @@ class SiteTreeBacklinksTest extends SapphireTest {
// assert hyperlink to page 1's new published url exists
$page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID);
$links = HTTP::getLinksIn($page3live->Content);
$this->assertContains('new-url-segment/', $links, 'Assert hyperlink to page 1\'s new published url exists on page 3');
$links = HTTP::getLinksIn($page3live->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'new-url-segment/', $links, 'Assert hyperlink to page 1\'s new published url exists on page 3');
}
function testPublishingPageWithModifiedLinksRewritesLinks() {
@ -161,8 +163,8 @@ class SiteTreeBacklinksTest extends SapphireTest {
$this->assertTrue($page3->doPublish());
// assert hyperlink to page 1's current url exists
$links = HTTP::getLinksIn($page3->Content);
$this->assertContains('page1/', $links, 'Assert hyperlink to page 1\'s current published url exists on page 3');
$links = HTTP::getLinksIn($page3->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'page1/', $links, 'Assert hyperlink to page 1\'s current published url exists on page 3');
// change page 1 url on draft
$page1->URLSegment = 'new-url-segment';
@ -172,24 +174,25 @@ class SiteTreeBacklinksTest extends SapphireTest {
// assert page 3 on draft contains new page 1 url
$page3 = $this->objFromFixture('Page', 'page3');
$links = HTTP::getLinksIn($page3->Content);
$this->assertContains('new-url-segment/', $links, 'Assert hyperlink to page 1\'s current draft url exists on page 3');
$links = HTTP::getLinksIn($page3->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'new-url-segment/', $links, 'Assert hyperlink to page 1\'s current draft url exists on page 3');
// publish page 3
$this->assertTrue($page3->doPublish());
// assert page 3 on published site contains old page 1 url
$page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID);
$links = HTTP::getLinksIn($page3live->Content);
$this->assertContains('page1/', $links, 'Assert hyperlink to page 1\'s current published url exists on page 3');
Versioned::reading_stage('Live');
$links = HTTP::getLinksIn($page3live->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'page1/', $links, 'Assert hyperlink to page 1\'s current published url exists on page 3');
// publish page 1
$this->assertTrue($page1->doPublish());
// assert page 3 on published site contains new page 1 url
$page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID);
$links = HTTP::getLinksIn($page3live->Content);
$this->assertContains('new-url-segment/', $links, 'Assert hyperlink to page 1\'s current published url exists on page 3');
$links = HTTP::getLinksIn($page3live->obj('Content')->forTemplate());
$this->assertContains(Director::baseURL().'new-url-segment/', $links, 'Assert hyperlink to page 1\'s current published url exists on page 3');
}
function testLinkTrackingOnExtraContentFields() {
@ -202,7 +205,7 @@ class SiteTreeBacklinksTest extends SapphireTest {
$this->assertFalse($page1->BackLinkTracking()->containsIDs(array($page2->ID)), 'Assert backlink to page 2 doesn\'t exist');
// add hyperlink to page 1 on page 2
$page2->ExtraContent .= '<p><a href="page1/">Testing page 1 link</a></p>';
$page2->ExtraContent .= '<p><a href="[sitetree_link id='.$page1->ID.']">Testing page 1 link</a></p>';
$page2->write();
$page2->doPublish();
@ -216,16 +219,17 @@ class SiteTreeBacklinksTest extends SapphireTest {
// confirm that draft link on page2 has been rewritten
$page2 = $this->objFromFixture('Page', 'page2');
$this->assertEquals('<p><a href="page1-new-url/">Testing page 1 link</a></p>', $page2->ExtraContent);
$this->assertEquals('<p><a href="'.Director::baseURL().'page1-new-url/">Testing page 1 link</a></p>', $page2->obj('ExtraContent')->forTemplate());
// confirm that published link hasn't
$page2Live = Versioned::get_one_by_stage("Page", "Live", "\"SiteTree\".\"ID\" = $page2->ID");
$this->assertEquals('<p><a href="page1/">Testing page 1 link</a></p>', $page2Live->ExtraContent);
Versioned::reading_stage('Live');
$this->assertEquals('<p><a href="'.Director::baseURL().'page1/">Testing page 1 link</a></p>', $page2Live->obj('ExtraContent')->forTemplate());
// publish page1 and confirm that the link on the published page2 has now been updated
$page1->doPublish();
$page2Live = Versioned::get_one_by_stage("Page", "Live", "\"SiteTree\".\"ID\" = $page2->ID");
$this->assertEquals('<p><a href="page1-new-url/">Testing page 1 link</a></p>', $page2Live->ExtraContent);
$this->assertEquals('<p><a href="'.Director::baseURL().'page1-new-url/">Testing page 1 link</a></p>', $page2Live->obj('ExtraContent')->forTemplate());
// remove hyperlink to page 1

View File

@ -1,5 +1,6 @@
Page:
page1:
ID: 1
Title: page1
URLSegment: page1
@ -10,7 +11,7 @@ Page:
page3:
Title: page3
URLSegment: page3
Content: '<p><a href="page1/">Testing page 1 link</a></p>'
Content: '<p><a href="[sitetree_link id=1]">Testing page 1 link</a></p>'
LinkTracking: =>Page.page1

View File

@ -70,6 +70,35 @@ class SiteTreeBrokenLinksTest extends SapphireTest {
$this->assertFalse($obj->HasBrokenFile, 'Page does NOT have a broken file');
}
function testDeletingFileMarksBackedPagesAsBroken() {
// Test entry
$file = new File();
$file->Filename = 'test-file.pdf';
$file->write();
$obj = $this->objFromFixture('Page','content');
$obj->Content = '<a href="assets/test-file.pdf">link to a pdf file</a>';
$obj->write();
$this->assertTrue($obj->doPublish());
// Confirm that it isn't marked as broken to begin with
$obj->flushCache();
$obj = DataObject::get_by_id("SiteTree", $obj->ID);
$this->assertEquals(0, $obj->HasBrokenFile);
$liveObj = Versioned::get_one_by_stage("SiteTree", "Live","\"SiteTree\".\"ID\" = $obj->ID");
$this->assertEquals(0, $liveObj->HasBrokenFile);
// Delete the file
$file->delete();
// Confirm that it is marked as broken in both stage and live
$obj->flushCache();
$obj = DataObject::get_by_id("SiteTree", $obj->ID);
$this->assertEquals(1, $obj->HasBrokenFile);
$liveObj = Versioned::get_one_by_stage("SiteTree", "Live", "\"SiteTree\".\"ID\" = $obj->ID");
$this->assertEquals(1, $liveObj->HasBrokenFile);
}
function testDeletingMarksBackLinkedPagesAsBroken() {
$this->logInWithPermission('ADMIN');
@ -133,6 +162,151 @@ class SiteTreeBrokenLinksTest extends SapphireTest {
WHERE \"ID\" = $linkSrc->ID")->value());
}
function testRestoreFixesBrokenLinks() {
// Create page and virutal page
$p = new Page();
$p->Title = "source";
$p->write();
$pageID = $p->ID;
$this->assertTrue($p->doPublish());
// Content links are one kind of link to pages
$p2 = new Page();
$p2->Title = "regular link";
$p2->Content = "<a href=\"" . Director::makeRelative($p->Link()) . "\">test</a>";
$p2->write();
$this->assertTrue($p2->doPublish());
// Virtual pages are another
$vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
// Redirector links are a third
$rp = new RedirectorPage();
$rp->Title = "redirector";
$rp->LinkType = 'Internal';
$rp->LinkToID = $p->ID;
$rp->write();
$this->assertTrue($rp->doPublish());
// Confirm that there are no broken links to begin with
$this->assertFalse($p2->HasBrokenLink);
$this->assertFalse($vp->HasBrokenLink);
$this->assertFalse($rp->HasBrokenLink);
// Unpublish the source page, confirm that the page 2 and RP has a broken link on published
$p->doUnpublish();
$p2Live = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $p2->ID);
$rpLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $rp->ID);
$this->assertEquals(1, $p2Live->HasBrokenLink);
$this->assertEquals(1, $rpLive->HasBrokenLink);
// Delete the source page, confirm that the VP, RP and page 2 have broken links on draft
$p->delete();
$vp->flushCache();
$vp = DataObject::get_by_id('SiteTree', $vp->ID);
$p2->flushCache();
$p2 = DataObject::get_by_id('SiteTree', $p2->ID);
$rp->flushCache();
$rp = DataObject::get_by_id('SiteTree', $rp->ID);
$this->assertEquals(1, $p2->HasBrokenLink);
$this->assertEquals(1, $vp->HasBrokenLink);
$this->assertEquals(1, $rp->HasBrokenLink);
// Restore the page to stage, confirm that this fixes the links
$p = Versioned::get_latest_version('SiteTree', $pageID);
$p->doRestoreToStage();
$p2->flushCache();
$p2 = DataObject::get_by_id('SiteTree', $p2->ID);
$vp->flushCache();
$vp = DataObject::get_by_id('SiteTree', $vp->ID);
$rp->flushCache();
$rp = DataObject::get_by_id('SiteTree', $rp->ID);
$this->assertFalse((bool)$p2->HasBrokenLink);
$this->assertFalse((bool)$vp->HasBrokenLink);
$this->assertFalse((bool)$rp->HasBrokenLink);
// Publish and confirm that the p2 and RP broken links are fixed on published
$this->assertTrue($p->doPublish());
$p2Live = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $p2->ID);
$rpLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $rp->ID);
$this->assertFalse((bool)$p2Live->HasBrokenLink);
$this->assertFalse((bool)$rpLive->HasBrokenLink);
}
function testRevertToLiveFixesBrokenLinks() {
// Create page and virutal page
$p = new Page();
$p->Title = "source";
$p->write();
$pageID = $p->ID;
$this->assertTrue($p->doPublish());
// Content links are one kind of link to pages
$p2 = new Page();
$p2->Title = "regular link";
$p2->Content = "<a href=\"" . Director::makeRelative($p->Link()) . "\">test</a>";
$p2->write();
$this->assertTrue($p2->doPublish());
// Virtual pages are another
$vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
// Redirector links are a third
$rp = new RedirectorPage();
$rp->Title = "redirector";
$rp->LinkType = 'Internal';
$rp->LinkToID = $p->ID;
$rp->write();
$this->assertTrue($rp->doPublish());
// Confirm that there are no broken links to begin with
$this->assertFalse($p2->HasBrokenLink);
$this->assertFalse($vp->HasBrokenLink);
$this->assertFalse($rp->HasBrokenLink);
// Delete from draft and confirm that broken links are marked
$pID = $p->ID;
$p->delete();
$vp->flushCache();
$vp = DataObject::get_by_id('SiteTree', $vp->ID);
$p2->flushCache();
$p2 = DataObject::get_by_id('SiteTree', $p2->ID);
$rp->flushCache();
$rp = DataObject::get_by_id('SiteTree', $rp->ID);
$this->assertEquals(1, $p2->HasBrokenLink);
$this->assertEquals(1, $vp->HasBrokenLink);
$this->assertEquals(1, $rp->HasBrokenLink);
// Call doRevertToLive and confirm that broken links are restored
$pLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $pID);
$pLive->doRevertToLive();
$p2->flushCache();
$p2 = DataObject::get_by_id('SiteTree', $p2->ID);
$vp->flushCache();
$vp = DataObject::get_by_id('SiteTree', $vp->ID);
$rp->flushCache();
$rp = DataObject::get_by_id('SiteTree', $rp->ID);
$this->assertFalse((bool)$p2->HasBrokenLink);
$this->assertFalse((bool)$vp->HasBrokenLink);
$this->assertFalse((bool)$rp->HasBrokenLink);
// However, the page isn't marked as modified on stage
$this->assertFalse($p2->IsModifiedOnStage);
$this->assertFalse($rp->IsModifiedOnStage);
// This is something that we know to be broken
//$this->assertFalse($vp->IsModifiedOnStage);
}
}
?>

View File

@ -256,4 +256,63 @@ class VirtualPageTest extends SapphireTest {
}
}
function testUnpublishingSourcePageOfAVirtualPageAlsoUnpublishesVirtualPage() {
// Create page and virutal page
$p = new Page();
$p->Title = "source";
$p->write();
$this->assertTrue($p->doPublish());
$vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
$this->assertTrue($vp->doPublish());
// All is fine, the virtual page doesn't have a broken link
$this->assertFalse($vp->HasBrokenLink);
// Unpublish the source page, confirm that the virtual page has also been unpublished
$p->doUnpublish();
$vpLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $vp->ID);
$this->assertFalse($vpLive);
// Delete from draft, confirm that the virtual page has a broken link on the draft site
$p->delete();
$vp->flushCache();
$vp = DataObject::get_by_id('SiteTree', $vp->ID);
$this->assertEquals(1, $vp->HasBrokenLink);
}
function testDeletingFromLiveSourcePageOfAVirtualPageAlsoUnpublishesVirtualPage() {
// Create page and virutal page
$p = new Page();
$p->Title = "source";
$p->write();
$this->assertTrue($p->doPublish());
$vp = new VirtualPage();
$vp->CopyContentFromID = $p->ID;
$vp->write();
$this->assertTrue($vp->doPublish());
// All is fine, the virtual page doesn't have a broken link
$this->assertFalse($vp->HasBrokenLink);
// Delete the source page from draft, confirm that this creates a broken link
$pID = $p->ID;
$p->delete();
$vp->flushCache();
$vp = DataObject::get_by_id('SiteTree', $vp->ID);
$this->assertEquals(1, $vp->HasBrokenLink);
// Delete the source page form live, confirm that the virtual page has also been unpublished
$pLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $pID);
$this->assertTrue($pLive->doDeleteFromLive());
$vpLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $vp->ID);
$this->assertFalse($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', $vp->ID);
$this->assertEquals(1, $vp->HasBrokenLink);
}
}