diff --git a/tests/RedirectorPageTest.php b/tests/RedirectorPageTest.php new file mode 100644 index 00000000..c24edd2c --- /dev/null +++ b/tests/RedirectorPageTest.php @@ -0,0 +1,53 @@ +assertEquals("http://www.google.com", $this->objFromFixture('RedirectorPage','goodexternal')->Link()); + $this->assertEquals(Director::baseURL() . "redirection-dest/", $this->objFromFixture('RedirectorPage','goodinternal')->redirectionLink()); + $this->assertEquals(Director::baseURL() . "redirection-dest/", $this->objFromFixture('RedirectorPage','goodinternal')->Link()); + } + + function testEmptyRedirectors() { + /* If a redirector page is misconfigured, then its link method will just return the usual URLSegment-generated value */ + $page1 = $this->objFromFixture('RedirectorPage','badexternal'); + $this->assertEquals(Director::baseURL() . 'bad-external/', $page1->Link()); + + /* An error message will be shown if you visit it */ + $content = $this->get(Director::makeRelative($page1->Link()))->getBody(); + $this->assertContains('message-setupWithoutRedirect', $content); + + /* This also applies for internal links */ + $page2 = $this->objFromFixture('RedirectorPage','badinternal'); + $this->assertEquals(Director::baseURL() . 'bad-internal/', $page2->Link()); + $content = $this->get(Director::makeRelative($page2->Link()))->getBody(); + $this->assertContains('message-setupWithoutRedirect', $content); + } + + function testReflexiveAndTransitiveInternalRedirectors() { + /* Reflexive redirectors are those that point to themselves. They should behave the same as an empty redirector */ + $page = $this->objFromFixture('RedirectorPage','reflexive'); + $this->assertEquals(Director::baseURL() . 'reflexive/', $page->Link()); + $content = $this->get(Director::makeRelative($page->Link()))->getBody(); + $this->assertContains('message-setupWithoutRedirect', $content); + + /* Transitive redirectors are those that point to another redirector page. They should send people to the URLSegment + * of the destination page - the middle-stop, so to speak. That should redirect to the final destination */ + $page = $this->objFromFixture('RedirectorPage','transitive'); + $this->assertEquals(Director::baseURL() . 'good-internal/', $page->Link()); + + $this->autoFollowRedirection = false; + $response = $this->get(Director::makeRelative($page->Link())); + $this->assertEquals(Director::baseURL() . "redirection-dest/", $response->getHeader("Location")); + } + + function testExternalURLGetsPrefixIfNotSet() { + $page = $this->objFromFixture('RedirectorPage', 'externalnoprefix'); + $this->assertEquals($page->ExternalURL, 'http://google.com', 'onBeforeWrite has prefixed with http'); + $page->write(); + $this->assertEquals($page->ExternalURL, 'http://google.com', 'onBeforeWrite will not double prefix if written again!'); + } +} \ No newline at end of file diff --git a/tests/RedirectorPageTest.yml b/tests/RedirectorPageTest.yml new file mode 100644 index 00000000..70d0129f --- /dev/null +++ b/tests/RedirectorPageTest.yml @@ -0,0 +1,39 @@ +Page: + dest: + Title: Redirection Dest + URLSegment: redirection-dest + +RedirectorPage: + goodexternal: + Title: Good External + URLSegment: good-external + RedirectionType: External + ExternalURL: http://www.google.com + goodinternal: + Title: Good Internal + URLSegment: good-internal + RedirectionType: Internal + LinkTo: =>Page.dest + badexternal: + Title: Bad External + RedirectionType: External + URLSegment: bad-external + externalnoprefix: + Title: External no prefix + RedirectionType: External + URLSegment: external-no-prefix + ExternalURL: google.com + badinternal: + Title: Bad Internal + RedirectionType: Internal + URLSegment: bad-internal + reflexive: + Title: Reflexive + RedirectionType: Internal + LinkTo: =>RedirectorPage.reflexive + URLSegment: reflexive + transitive: + Title: Transitive + RedirectionType: Internal + LinkTo: =>RedirectorPage.goodinternal + URLSegment: transitive diff --git a/tests/SiteTreeActionsTest.php b/tests/SiteTreeActionsTest.php new file mode 100644 index 00000000..1e458b80 --- /dev/null +++ b/tests/SiteTreeActionsTest.php @@ -0,0 +1,186 @@ +objFromFixture('Member', 'cmsreadonlyeditor'); + $this->session()->inst_set('loggedInAs', $readonlyEditor->ID); + + $page = new SiteTreeActionsTest_Page(); + $page->CanEditType = 'LoggedInUsers'; + $page->write(); + $page->doPublish(); + + $actionsArr = $page->getCMSActions()->column('Name'); + + $this->assertNotContains('action_save',$actionsArr); + $this->assertNotContains('action_publish',$actionsArr); + $this->assertNotContains('action_unpublish',$actionsArr); + $this->assertNotContains('action_delete',$actionsArr); + $this->assertNotContains('action_deletefromlive',$actionsArr); + $this->assertNotContains('action_rollback',$actionsArr); + $this->assertNotContains('action_revert',$actionsArr); + } + + function testActionsNoDeletePublishedRecord() { + if(class_exists('SiteTreeCMSWorkflow')) return true; + + $this->logInWithPermission('ADMIN'); + + $page = new SiteTreeActionsTest_Page(); + $page->CanEditType = 'LoggedInUsers'; + $page->write(); + $pageID = $page->ID; + $page->doPublish(); + $page->deleteFromStage('Stage'); + + // Get the live version of the page + $page = Versioned::get_one_by_stage("SiteTree", "Live", "\"SiteTree\".\"ID\" = $pageID"); + $this->assertType("SiteTree", $page); + + // Check that someone without the right permission can't delete the page + $editor = $this->objFromFixture('Member', 'cmsnodeleteeditor'); + $this->session()->inst_set('loggedInAs', $editor->ID); + + $actionsArr = $page->getCMSActions()->column('Name'); + $this->assertNotContains('action_deletefromlive',$actionsArr); + + // Check that someone with the right permission can delete the page + $this->objFromFixture('Member', 'cmseditor')->logIn(); + $actionsArr = $page->getCMSActions()->column('Name'); + $this->assertContains('action_deletefromlive',$actionsArr); + } + + function testActionsPublishedRecord() { + if(class_exists('SiteTreeCMSWorkflow')) return true; + + $author = $this->objFromFixture('Member', 'cmseditor'); + $this->session()->inst_set('loggedInAs', $author->ID); + + $page = new Page(); + $page->CanEditType = 'LoggedInUsers'; + $page->write(); + $page->doPublish(); + + $actionsArr = $page->getCMSActions()->column('Name'); + + $this->assertContains('action_save',$actionsArr); + $this->assertContains('action_publish',$actionsArr); + $this->assertContains('action_unpublish',$actionsArr); + $this->assertContains('action_delete',$actionsArr); + $this->assertNotContains('action_deletefromlive',$actionsArr); + $this->assertNotContains('action_rollback',$actionsArr); + $this->assertNotContains('action_revert',$actionsArr); + } + + function testActionsDeletedFromStageRecord() { + if(class_exists('SiteTreeCMSWorkflow')) return true; + + $author = $this->objFromFixture('Member', 'cmseditor'); + $this->session()->inst_set('loggedInAs', $author->ID); + + $page = new Page(); + $page->CanEditType = 'LoggedInUsers'; + $page->write(); + $pageID = $page->ID; + $page->doPublish(); + $page->deleteFromStage('Stage'); + + // Get the live version of the page + $page = Versioned::get_one_by_stage("SiteTree", "Live", "\"SiteTree\".\"ID\" = $pageID"); + $this->assertType('SiteTree', $page); + + $actionsArr = $page->getCMSActions()->column('Name'); + + $this->assertNotContains('action_save',$actionsArr); + $this->assertNotContains('action_publish',$actionsArr); + $this->assertNotContains('action_unpublish',$actionsArr); + $this->assertNotContains('action_delete',$actionsArr); + $this->assertContains('action_deletefromlive',$actionsArr); + $this->assertNotContains('action_rollback',$actionsArr); + $this->assertContains('action_revert',$actionsArr); + } + + function testActionsChangedOnStageRecord() { + if(class_exists('SiteTreeCMSWorkflow')) return true; + + $author = $this->objFromFixture('Member', 'cmseditor'); + $this->session()->inst_set('loggedInAs', $author->ID); + + $page = new Page(); + $page->CanEditType = 'LoggedInUsers'; + $page->write(); + $page->doPublish(); + $page->Content = 'Changed on Stage'; + $page->write(); + $page->flushCache(); + + $actionsArr = $page->getCMSActions()->column('Name'); + + $this->assertContains('action_save',$actionsArr); + $this->assertContains('action_publish',$actionsArr); + $this->assertContains('action_unpublish',$actionsArr); + $this->assertContains('action_delete',$actionsArr); + $this->assertNotContains('action_deletefromlive',$actionsArr); + $this->assertContains('action_rollback',$actionsArr); + $this->assertNotContains('action_revert',$actionsArr); + } + + function testActionsViewingOldVersion() { + $p = new Page(); + $p->Content = 'test page first version'; + $p->write(); + $p->Content = 'new content'; + $p->write(); + + // Looking at the old version, the ability to rollback to that version is available + $version = DB::query('SELECT "Version" FROM "SiteTree_versions" WHERE "Content" = \'test page first version\'')->value(); + $old = Versioned::get_version('Page', $p->ID, $version); + $actions = $old->getCMSActions()->column('Name'); + $this->assertNotContains('action_save', $actions); + $this->assertNotContains('action_publish', $actions); + $this->assertNotContains('action_unpublish', $actions); + $this->assertNotContains('action_delete', $actions); + $this->assertContains('action_email', $actions); + $this->assertContains('action_rollback', $actions); + } + +} + +class SiteTreeActionsTest_Page extends Page implements TestOnly { + function canEdit($member = null) { + return Permission::checkMember($member, 'SiteTreeActionsTest_Page_CANEDIT'); + } + + function canDelete($member = null) { + return Permission::checkMember($member, 'SiteTreeActionsTest_Page_CANDELETE'); + } +} diff --git a/tests/SiteTreeActionsTest.yml b/tests/SiteTreeActionsTest.yml new file mode 100644 index 00000000..b22da76a --- /dev/null +++ b/tests/SiteTreeActionsTest.yml @@ -0,0 +1,33 @@ +Permission: + cmsmain1: + Code: CMS_ACCESS_CMSMain + cmsmain2: + Code: CMS_ACCESS_CMSMain + cmsmain3: + Code: CMS_ACCESS_CMSMain + candelete: + Code: SiteTreeActionsTest_Page_CANDELETE + canedit1: + Code: SiteTreeActionsTest_Page_CANEDIT + canedit2: + Code: SiteTreeActionsTest_Page_CANEDIT +Group: + cmseditors: + Title: CMS Editors + Permissions: =>Permission.cmsmain1,=>Permission.canedit1,=>Permission.candelete + cmsreadonly: + Title: CMS Readonly + Permissions: =>Permission.cmsmain2 + cmsnodelete: + Title: CMS No Delete + Permissions: =>Permission.cmsmain3,=>Permission.canedit2 +Member: + cmseditor: + Email: cmseditor@test.com + Groups: =>Group.cmseditors + cmsreadonlyeditor: + Email: cmsreadonlyeditor@test.com + Groups: =>Group.cmsreadonly + cmsnodeleteeditor: + Email: cmsnodeleteeditor@test.com + Groups: =>Group.cmsnodelete \ No newline at end of file diff --git a/tests/SiteTreeBacklinksTest.php b/tests/SiteTreeBacklinksTest.php new file mode 100644 index 00000000..dd39f770 --- /dev/null +++ b/tests/SiteTreeBacklinksTest.php @@ -0,0 +1,258 @@ + array('SiteTreeBacklinksTest_DOD'), + ); + + static function set_up_once() { + SiteTreeTest::set_up_once(); + + parent::set_up_once(); + } + + static function tear_down_once() { + SiteTreeTest::tear_down_once(); + + parent::tear_down_once(); + } + + function setUp() { + parent::setUp(); + + // Log in as admin so that we don't run into permission issues. That's not what we're + // testing here. + $this->logInWithPermission('ADMIN'); + } + + function testSavingPageWithLinkAddsBacklink() { + // load page 1 + $page1 = $this->objFromFixture('Page', 'page1'); + + // assert backlink to page 2 doesn't exist + $page2 = $this->objFromFixture('Page', 'page2'); + $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 .= '

Testing page 1 link

'; + $page2->write(); + + // load page 1 + $page1 = $this->objFromFixture('Page', 'page1'); + + // assert backlink to page 2 exists + $this->assertTrue($page1->BackLinkTracking()->containsIDs(array($page2->ID)), 'Assert backlink to page 2 exists'); + } + + function testRemovingLinkFromPageRemovesBacklink() { + // load page 1 + $page1 = $this->objFromFixture('Page', 'page1'); + + // assert backlink to page 3 exits + $page3 = $this->objFromFixture('Page', 'page3'); + $this->assertTrue($page1->BackLinkTracking()->containsIDs(array($page3->ID)), 'Assert backlink to page 3 exists'); + + // remove hyperlink to page 1 + $page3->Content = '

No links anymore!

'; + $page3->write(); + + // load page 1 + $page1 = $this->objFromFixture('Page', 'page1'); + + // assert backlink to page 3 exists + $this->assertFalse($page1->BackLinkTracking()->containsIDs(array($page3->ID)), 'Assert backlink to page 3 doesn\'t exist'); + } + + function testChangingUrlOnDraftSiteRewritesLink() { + // load page 1 + $page1 = $this->objFromFixture('Page', 'page1'); + + // assert backlink to page 3 exists + $page3 = $this->objFromFixture('Page', 'page3'); + $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->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'; + $page1->write(); + + // load page 3 + $page3 = $this->objFromFixture('Page', 'page3'); + + // assert hyperlink to page 1's new url exists + $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() { + // publish page 1 & 3 + $page1 = $this->objFromFixture('Page', 'page1'); + $page3 = $this->objFromFixture('Page', 'page3'); + $this->assertTrue($page1->doPublish()); + $this->assertTrue($page3->doPublish()); + + // load pages from live + $page1live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page1->ID); + $page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID); + + // assert backlink to page 3 exists + $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->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'; + $page1live->writeToStage('Live'); + + // load page 3 from live + $page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID); + + // assert hyperlink to page 1's new url exists + 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() { + // publish page 1 & 3 + $page1 = $this->objFromFixture('Page', 'page1'); + $page3 = $this->objFromFixture('Page', 'page3'); + + $this->assertTrue($page1->doPublish()); + $this->assertTrue($page3->doPublish()); + + // load page 3 from live + $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->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'; + $page1->write(); + + // assert hyperlink to page 1's current publish url exists + $page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID); + 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 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->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() { + // publish page 1 & 3 + $page1 = $this->objFromFixture('Page', 'page1'); + $page3 = $this->objFromFixture('Page', 'page3'); + $this->assertTrue($page1->doPublish()); + $this->assertTrue($page3->doPublish()); + + // assert hyperlink to page 1's current url exists + $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'; + + // save page 1 + $page1->write(); + + // assert page 3 on draft contains new page 1 url + $page3 = $this->objFromFixture('Page', 'page3'); + $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); + 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->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() { + $page1 = $this->objFromFixture('Page', 'page1'); + $page2 = $this->objFromFixture('Page', 'page2'); + $page1->doPublish(); + $page2->doPublish(); + + // assert backlink to page 2 doesn't exist + $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 .= '

Testing page 1 link

'; + $page2->write(); + $page2->doPublish(); + + // assert backlink to page 2 exists + $this->assertTrue($page1->BackLinkTracking()->containsIDs(array($page2->ID)), 'Assert backlink to page 2 exists'); + + // update page1 url + $page1 = $this->objFromFixture('Page', 'page1'); + $page1->URLSegment = "page1-new-url"; + $page1->write(); + + // confirm that draft link on page2 has been rewritten + $page2 = $this->objFromFixture('Page', 'page2'); + $this->assertEquals('

Testing page 1 link

', $page2->obj('ExtraContent')->forTemplate()); + + // confirm that published link hasn't + $page2Live = Versioned::get_one_by_stage("Page", "Live", "\"SiteTree\".\"ID\" = $page2->ID"); + Versioned::reading_stage('Live'); + $this->assertEquals('

Testing page 1 link

', $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('

Testing page 1 link

', $page2Live->obj('ExtraContent')->forTemplate()); + + + // remove hyperlink to page 1 + $page2->ExtraContent = '

No links anymore!

'; + $page2->write(); + + // assert backlink to page 2 no longer exists + $this->assertFalse($page1->BackLinkTracking()->containsIDs(array($page2->ID)), 'Assert backlink to page 2 has been removed'); + } + +} + +class SiteTreeBacklinksTest_DOD extends DataObjectDecorator implements TestOnly { + function extraStatics() { + return array( + 'db' => array( + 'ExtraContent' => 'HTMLText', + ), + ); + } + + function updateCMSFields(&$fields) { + $fields->addFieldToTab("Root.Content.Main", new HTMLEditorField("ExtraContent")); + } +} +?> diff --git a/tests/SiteTreeBacklinksTest.yml b/tests/SiteTreeBacklinksTest.yml new file mode 100644 index 00000000..67b1b4be --- /dev/null +++ b/tests/SiteTreeBacklinksTest.yml @@ -0,0 +1,17 @@ +Page: + page1: + ID: 1 + Title: page1 + URLSegment: page1 + + page2: + Title: page2 + URLSegment: page2 + + page3: + Title: page3 + URLSegment: page3 + Content: '

Testing page 1 link

' + LinkTracking: =>Page.page1 + + diff --git a/tests/SiteTreeBrokenLinksTest.php b/tests/SiteTreeBrokenLinksTest.php new file mode 100644 index 00000000..a9e069cd --- /dev/null +++ b/tests/SiteTreeBrokenLinksTest.php @@ -0,0 +1,313 @@ +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'); + } + + 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'); + } + + 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'); + } + + function testBrokenAssetLinks() { + $obj = $this->objFromFixture('Page','content'); + + $obj->Content = 'this is a broken link to a pdf file'; + $obj->syncLinkTracking(); + $this->assertTrue($obj->HasBrokenFile, 'Page has a broken file'); + + $obj->Content = 'this is not a broken file link'; + $obj->syncLinkTracking(); + $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 = 'link to a pdf file'; + $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'); + + // Set up two published pages with a link from content -> about + $linkDest = $this->objFromFixture('Page','about'); + $linkDest->doPublish(); + + $linkSrc = $this->objFromFixture('Page','content'); + $linkSrc->Content = "

ID]\">about us

"; + $linkSrc->write(); + + $linkSrc->doPublish(); + + // Confirm no broken link + $this->assertEquals(0, (int)$linkSrc->HasBrokenLink); + $this->assertEquals(0, DB::query("SELECT \"HasBrokenLink\" FROM \"SiteTree_Live\" + WHERE \"ID\" = $linkSrc->ID")->value()); + + // Delete page from draft + $linkDestID = $linkDest->ID; + $linkDest->delete(); + + // Confirm draft has broken link, and published doesn't + $linkSrc->flushCache(); + $linkSrc = $this->objFromFixture('Page', 'content'); + + $this->assertEquals(1, (int)$linkSrc->HasBrokenLink); + $this->assertEquals(0, DB::query("SELECT \"HasBrokenLink\" FROM \"SiteTree_Live\" + WHERE \"ID\" = $linkSrc->ID")->value()); + + // Delete from live + $linkDest = Versioned::get_one_by_stage("SiteTree", "Live", "\"SiteTree\".\"ID\" = $linkDestID"); + $linkDest->doDeleteFromLive(); + + // Confirm both draft and published have broken link + $linkSrc->flushCache(); + $linkSrc = $this->objFromFixture('Page', 'content'); + + $this->assertEquals(1, (int)$linkSrc->HasBrokenLink); + $this->assertEquals(1, DB::query("SELECT \"HasBrokenLink\" FROM \"SiteTree_Live\" + WHERE \"ID\" = $linkSrc->ID")->value()); + } + + function testPublishingSourceBeforeDestHasBrokenLink() { + $this->logInWithPermission('ADMIN'); + + // Set up two draft pages with a link from content -> about + $linkDest = $this->objFromFixture('Page','about'); + // Ensure that it's not on the published site + $linkDest->doDeleteFromLive(); + + $linkSrc = $this->objFromFixture('Page','content'); + $linkSrc->Content = "

ID]\">about us

"; + $linkSrc->write(); + + // Publish the source of the link, while the dest is still unpublished. + $linkSrc->doPublish(); + + // Verify that the link isn't broken on draft but is broken on published + $this->assertEquals(0, (int)$linkSrc->HasBrokenLink); + $this->assertEquals(1, DB::query("SELECT \"HasBrokenLink\" FROM \"SiteTree_Live\" + 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 = "ID]\">test"; + $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 = "ID]\">test"; + $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); + + } +} + +?> \ No newline at end of file diff --git a/tests/SiteTreeBrokenLinksTest.yml b/tests/SiteTreeBrokenLinksTest.yml new file mode 100644 index 00000000..8f6a53f9 --- /dev/null +++ b/tests/SiteTreeBrokenLinksTest.yml @@ -0,0 +1,27 @@ +Page: + content: + Title: ContentPage + Content: This is some happy content. + about: + Title: About + URLSegment: about + Content: about us here + brokenInternalRedirector: + RedirectionType: Internal + Title: RedirectorPageToBrokenInteralPage + LinkToID: 0 + workingInternalRedirector: + RedirectionType: Internal + Title: RedirectorPageToBrokenInteralPage + LinkTo: =>Page.content + +File: + privacypolicy: + Name: privacypolicy.pdf + Title: privacypolicy.pdf + Filename: assets/privacypolicy.pdf + +ErrorPage: + 404: + Title: Page not Found + ErrorCode: 404 \ No newline at end of file diff --git a/tests/SiteTreePermissionsTest.php b/tests/SiteTreePermissionsTest.php new file mode 100755 index 00000000..3584cb6b --- /dev/null +++ b/tests/SiteTreePermissionsTest.php @@ -0,0 +1,445 @@ + array('SiteTreeSubsites') + ); + + static function set_up_once() { + SiteTreeTest::set_up_once(); + + parent::set_up_once(); + } + + static function tear_down_once() { + SiteTreeTest::tear_down_once(); + + parent::tear_down_once(); + } + + function setUp() { + parent::setUp(); + + $this->useDraftSite(); + + // we're testing HTTP status codes before being redirected to login forms + $this->autoFollowRedirection = false; + } + + + function testAccessingStageWithBlankStage() { + $this->useDraftSite(false); + $this->autoFollowRedirection = false; + + $page = $this->objFromFixture('Page', 'draftOnlyPage'); + + if($member = Member::currentUser()) { + $member->logOut(); + } + + $response = $this->get($page->URLSegment . '?stage=Live'); + $this->assertEquals($response->getStatusCode(), '404'); + + $response = $this->get($page->URLSegment . '?stage='); + $this->assertEquals($response->getStatusCode(), '404'); + + // should be prompted for a login + $response = $this->get($page->URLSegment . '?stage=Stage'); + $this->assertEquals($response->getStatusCode(), '302'); + + $this->logInWithPermission('ADMIN'); + + $response = $this->get($page->URLSegment . '?stage=Live'); + $this->assertEquals($response->getStatusCode(), '404'); + + $response = $this->get($page->URLSegment . '?stage=Stage'); + $this->assertEquals($response->getStatusCode(), '200'); + + $response = $this->get($page->URLSegment . '?stage='); + $this->assertEquals($response->getStatusCode(), '404'); + } + + function testPermissionCheckingWorksOnDeletedPages() { + // Set up fixture - a published page deleted from draft + $this->logInWithPermission("ADMIN"); + $page = $this->objFromFixture('Page','restrictedEditOnlySubadminGroup'); + $pageID = $page->ID; + $this->assertTrue($page->doPublish()); + $page->delete(); + + // Re-fetch the page from the live site + $page = Versioned::get_one_by_stage('SiteTree', 'Live', "\"SiteTree\".\"ID\" = $pageID"); + + // subadmin has edit rights on that page + $member = $this->objFromFixture('Member','subadmin'); + $member->logIn(); + + // Test can_edit_multiple + $this->assertEquals( + array($pageID => true), + SiteTree::can_edit_multiple(array($pageID), $member->ID) + ); + + // Test canEdit + $member->logIn(); + $this->assertTrue($page->canEdit()); + } + + function testPermissionCheckingWorksOnUnpublishedPages() { + // Set up fixture - an unpublished page + $this->logInWithPermission("ADMIN"); + $page = $this->objFromFixture('Page','restrictedEditOnlySubadminGroup'); + $pageID = $page->ID; + $page->doUnpublish(); + + // subadmin has edit rights on that page + $member = $this->objFromFixture('Member','subadmin'); + $member->logIn(); + + // Test can_edit_multiple + $this->assertEquals( + array($pageID => true), + SiteTree::can_edit_multiple(array($pageID), $member->ID) + ); + + // Test canEdit + $member->logIn(); + $this->assertTrue($page->canEdit()); + } + + function testCanEditOnPageDeletedFromStageAndLiveReturnsFalse() { + // Find a page that exists and delete it from both stage and published + $this->logInWithPermission("ADMIN"); + $page = $this->objFromFixture('Page','restrictedEditOnlySubadminGroup'); + $pageID = $page->ID; + $page->doUnpublish(); + $page->delete(); + + // We'll need to resurrect the page from the version cache to test this case + $page = Versioned::get_latest_version('SiteTree', $pageID); + + // subadmin had edit rights on that page, but now it's gone + $member = $this->objFromFixture('Member','subadmin'); + $member->logIn(); + + $this->assertFalse($page->canEdit()); + } + + function testCanViewStage() { + $page = $this->objFromFixture('Page', 'standardpage'); + $editor = $this->objFromFixture('Member', 'editor'); + $websiteuser = $this->objFromFixture('Member', 'websiteuser'); + + $this->assertTrue($page->canViewStage('Live', $websiteuser)); + $this->assertFalse($page->canViewStage('Stage', $websiteuser)); + + $this->assertTrue($page->canViewStage('Live', $editor)); + $this->assertTrue($page->canViewStage('Stage', $editor)); + } + + function testAccessTabOnlyDisplaysWithGrantAccessPermissions() { + $page = $this->objFromFixture('Page', 'standardpage'); + + $subadminuser = $this->objFromFixture('Member', 'subadmin'); + $this->session()->inst_set('loggedInAs', $subadminuser->ID); + $fields = $page->getCMSFields(); + $this->assertFalse( + $fields->dataFieldByName('CanViewType')->isReadonly(), + 'Users with SITETREE_GRANT_ACCESS permission can change "view" permissions in cms fields' + ); + $this->assertFalse( + $fields->dataFieldByName('CanEditType')->isReadonly(), + 'Users with SITETREE_GRANT_ACCESS permission can change "edit" permissions in cms fields' + ); + + $editoruser = $this->objFromFixture('Member', 'editor'); + $this->session()->inst_set('loggedInAs', $editoruser->ID); + $fields = $page->getCMSFields(); + $this->assertTrue( + $fields->dataFieldByName('CanViewType')->isReadonly(), + 'Users without SITETREE_GRANT_ACCESS permission cannot change "view" permissions in cms fields' + ); + $this->assertTrue( + $fields->dataFieldByName('CanEditType')->isReadonly(), + 'Users without SITETREE_GRANT_ACCESS permission cannot change "edit" permissions in cms fields' + ); + + $this->session()->inst_set('loggedInAs', null); + } + + function testRestrictedViewLoggedInUsers() { + $page = $this->objFromFixture('Page', 'restrictedViewLoggedInUsers'); + + // unauthenticated users + $this->assertFalse( + $page->canView(FALSE), + 'Unauthenticated members cant view a page marked as "Viewable for any logged in users"' + ); + $this->session()->inst_set('loggedInAs', null); + $response = $this->get($page->RelativeLink()); + $this->assertEquals( + $response->getStatusCode(), + 302, + 'Unauthenticated members cant view a page marked as "Viewable for any logged in users"' + ); + + // website users + $websiteuser = $this->objFromFixture('Member', 'websiteuser'); + $this->assertTrue( + $page->canView($websiteuser), + 'Authenticated members can view a page marked as "Viewable for any logged in users" even if they dont have access to the CMS' + ); + $this->session()->inst_set('loggedInAs', $websiteuser->ID); + $response = $this->get($page->RelativeLink()); + $this->assertEquals( + $response->getStatusCode(), + 200, + 'Authenticated members can view a page marked as "Viewable for any logged in users" even if they dont have access to the CMS' + ); + $this->session()->inst_set('loggedInAs', null); + } + + function testRestrictedViewOnlyTheseUsers() { + $page = $this->objFromFixture('Page', 'restrictedViewOnlyWebsiteUsers'); + + // unauthenticcated users + $this->assertFalse( + $page->canView(FALSE), + 'Unauthenticated members cant view a page marked as "Viewable by these groups"' + ); + $this->session()->inst_set('loggedInAs', null); + $response = $this->get($page->RelativeLink()); + $this->assertEquals( + $response->getStatusCode(), + 302, + 'Unauthenticated members cant view a page marked as "Viewable by these groups"' + ); + + // subadmin users + $subadminuser = $this->objFromFixture('Member', 'subadmin'); + $this->assertFalse( + $page->canView($subadminuser), + 'Authenticated members cant view a page marked as "Viewable by these groups" if theyre not in the listed groups' + ); + $this->session()->inst_set('loggedInAs', $subadminuser->ID); + $response = $this->get($page->RelativeLink()); + $this->assertEquals( + $response->getStatusCode(), + 403, + 'Authenticated members cant view a page marked as "Viewable by these groups" if theyre not in the listed groups' + ); + $this->session()->inst_set('loggedInAs', null); + + // website users + $websiteuser = $this->objFromFixture('Member', 'websiteuser'); + $this->assertTrue( + $page->canView($websiteuser), + 'Authenticated members can view a page marked as "Viewable by these groups" if theyre in the listed groups' + ); + $this->session()->inst_set('loggedInAs', $websiteuser->ID); + $response = $this->get($page->RelativeLink()); + $this->assertEquals( + $response->getStatusCode(), + 200, + 'Authenticated members can view a page marked as "Viewable by these groups" if theyre in the listed groups' + ); + $this->session()->inst_set('loggedInAs', null); + } + + function testRestrictedEditLoggedInUsers() { + $page = $this->objFromFixture('Page', 'restrictedEditLoggedInUsers'); + + // unauthenticcated users + $this->assertFalse( + $page->canEdit(FALSE), + 'Unauthenticated members cant edit a page marked as "Editable by logged in users"' + ); + + // website users + $websiteuser = $this->objFromFixture('Member', 'websiteuser'); + $websiteuser->logIn(); + $this->assertFalse( + $page->canEdit($websiteuser), + 'Authenticated members cant edit a page marked as "Editable by logged in users" if they dont have cms permissions' + ); + + // subadmin users + $subadminuser = $this->objFromFixture('Member', 'subadmin'); + $this->assertTrue( + $page->canEdit($subadminuser), + 'Authenticated members can edit a page marked as "Editable by logged in users" if they have cms permissions and belong to any of these groups' + ); + } + + function testRestrictedEditOnlySubadminGroup() { + $page = $this->objFromFixture('Page', 'restrictedEditOnlySubadminGroup'); + + // unauthenticated users + $this->assertFalse( + $page->canEdit(FALSE), + 'Unauthenticated members cant edit a page marked as "Editable by these groups"' + ); + + // subadmin users + $subadminuser = $this->objFromFixture('Member', 'subadmin'); + $this->assertTrue( + $page->canEdit($subadminuser), + 'Authenticated members can view a page marked as "Editable by these groups" if theyre in the listed groups' + ); + + // website users + $websiteuser = $this->objFromFixture('Member', 'websiteuser'); + $this->assertFalse( + $page->canEdit($websiteuser), + 'Authenticated members cant edit a page marked as "Editable by these groups" if theyre not in the listed groups' + ); + } + + function testRestrictedViewInheritance() { + $parentPage = $this->objFromFixture('Page', 'parent_restrictedViewOnlySubadminGroup'); + $childPage = $this->objFromFixture('Page', 'child_restrictedViewOnlySubadminGroup'); + + // unauthenticated users + $this->assertFalse( + $childPage->canView(FALSE), + 'Unauthenticated members cant view a page marked as "Viewable by these groups" by inherited permission' + ); + $this->session()->inst_set('loggedInAs', null); + $response = $this->get($childPage->RelativeLink()); + $this->assertEquals( + $response->getStatusCode(), + 302, + 'Unauthenticated members cant view a page marked as "Viewable by these groups" by inherited permission' + ); + + // subadmin users + $subadminuser = $this->objFromFixture('Member', 'subadmin'); + $this->assertTrue( + $childPage->canView($subadminuser), + 'Authenticated members can view a page marked as "Viewable by these groups" if theyre in the listed groups by inherited permission' + ); + $this->session()->inst_set('loggedInAs', $subadminuser->ID); + $response = $this->get($childPage->RelativeLink()); + $this->assertEquals( + $response->getStatusCode(), + 200, + 'Authenticated members can view a page marked as "Viewable by these groups" if theyre in the listed groups by inherited permission' + ); + $this->session()->inst_set('loggedInAs', null); + } + + function testRestrictedEditInheritance() { + $parentPage = $this->objFromFixture('Page', 'parent_restrictedEditOnlySubadminGroup'); + $childPage = $this->objFromFixture('Page', 'child_restrictedEditOnlySubadminGroup'); + + // unauthenticated users + $this->assertFalse( + $childPage->canEdit(FALSE), + 'Unauthenticated members cant edit a page marked as "Editable by these groups" by inherited permission' + ); + + // subadmin users + $subadminuser = $this->objFromFixture('Member', 'subadmin'); + $this->assertTrue( + $childPage->canEdit($subadminuser), + 'Authenticated members can edit a page marked as "Editable by these groups" if theyre in the listed groups by inherited permission' + ); + } + + function testDeleteRestrictedChild() { + $parentPage = $this->objFromFixture('Page', 'deleteTestParentPage'); + $childPage = $this->objFromFixture('Page', 'deleteTestChildPage'); + + // unauthenticated users + $this->assertFalse( + $parentPage->canDelete(FALSE), + 'Unauthenticated members cant delete a page if it doesnt have delete permissions on any of its descendants' + ); + $this->assertFalse( + $childPage->canDelete(FALSE), + 'Unauthenticated members cant delete a child page marked as "Editable by these groups"' + ); + } + + function testRestrictedEditLoggedInUsersDeletedFromStage() { + $page = $this->objFromFixture('Page', 'restrictedEditLoggedInUsers'); + $pageID = $page->ID; + + $this->logInWithPermission("ADMIN"); + + $page->doPublish(); + $page->deleteFromStage('Stage'); + + // Get the live version of the page + $page = Versioned::get_one_by_stage("SiteTree", "Live", "\"SiteTree\".\"ID\" = $pageID"); + $this->assertTrue(is_object($page), 'Versioned::get_one_by_stage() is returning an object'); + + // subadmin users + $subadminuser = $this->objFromFixture('Member', 'subadmin'); + $this->assertTrue( + $page->canEdit($subadminuser), + 'Authenticated members can edit a page that was deleted from stage and marked as "Editable by logged in users" if they have cms permissions and belong to any of these groups' + ); + } + + function testInheritCanViewFromSiteConfig() { + $page = $this->objFromFixture('Page', 'inheritWithNoParent'); + $siteconfig = $this->objFromFixture('SiteConfig', 'default'); + $editor = $this->objFromFixture('Member', 'editor'); + $editorGroup = $this->objFromFixture('Group', 'editorgroup'); + + $siteconfig->CanViewType = 'Anyone'; + $siteconfig->write(); + $this->assertTrue($page->canView(FALSE), 'Anyone can view a page when set to inherit from the SiteConfig, and SiteConfig has canView set to LoggedInUsers'); + + $siteconfig->CanViewType = 'LoggedInUsers'; + $siteconfig->write(); + $this->assertFalse($page->canView(FALSE), 'Anonymous can\'t view a page when set to inherit from the SiteConfig, and SiteConfig has canView set to LoggedInUsers'); + + $siteconfig->CanViewType = 'LoggedInUsers'; + $siteconfig->write(); + $this->assertTrue($page->canView($editor), 'Users can view a page when set to inherit from the SiteConfig, and SiteConfig has canView set to LoggedInUsers'); + + $siteconfig->CanViewType = 'OnlyTheseUsers'; + $siteconfig->ViewerGroups()->add($editorGroup); + $siteconfig->ViewerGroups()->write(); + $siteconfig->write(); + $this->assertTrue($page->canView($editor), 'Editors can view a page when set to inherit from the SiteConfig, and SiteConfig has canView set to OnlyTheseUsers'); + $this->assertFalse($page->canView(FALSE), 'Anonymous can\'t view a page when set to inherit from the SiteConfig, and SiteConfig has canView set to OnlyTheseUsers'); + } + + function testInheritCanEditFromSiteConfig() { + $page = $this->objFromFixture('Page', 'inheritWithNoParent'); + $siteconfig = $this->objFromFixture('SiteConfig', 'default'); + $editor = $this->objFromFixture('Member', 'editor'); + $user = $this->objFromFixture('Member', 'websiteuser'); + $editorGroup = $this->objFromFixture('Group', 'editorgroup'); + + $siteconfig->CanEditType = 'LoggedInUsers'; + $siteconfig->write(); + + $this->assertFalse($page->canEdit(FALSE), 'Anonymous can\'t edit a page when set to inherit from the SiteConfig, and SiteConfig has canEdit set to LoggedInUsers'); + $this->session()->inst_set('loggedInAs', $editor->ID); + $this->assertTrue($page->canEdit(), 'Users can edit a page when set to inherit from the SiteConfig, and SiteConfig has canEdit set to LoggedInUsers'); + + $siteconfig->CanEditType = 'OnlyTheseUsers'; + $siteconfig->EditorGroups()->add($editorGroup); + $siteconfig->EditorGroups()->write(); + $siteconfig->write(); + $this->assertTrue($page->canEdit($editor), 'Editors can edit a page when set to inherit from the SiteConfig, and SiteConfig has canEdit set to OnlyTheseUsers'); + $this->session()->inst_set('loggedInAs', null); + $this->assertFalse($page->canEdit(FALSE), 'Anonymous can\'t edit a page when set to inherit from the SiteConfig, and SiteConfig has canEdit set to OnlyTheseUsers'); + $this->session()->inst_set('loggedInAs', $user->ID); + $this->assertFalse($page->canEdit($user), 'Website user can\'t edit a page when set to inherit from the SiteConfig, and SiteConfig has canEdit set to OnlyTheseUsers'); + } + +} +?> \ No newline at end of file diff --git a/tests/SiteTreePermissionsTest.yml b/tests/SiteTreePermissionsTest.yml new file mode 100644 index 00000000..709d8aa6 --- /dev/null +++ b/tests/SiteTreePermissionsTest.yml @@ -0,0 +1,88 @@ +SiteConfig: + default: + Title: My test site + Tagline: There is no doubt this is a great test site + CanViewType: Anyone + CanEditType: LoggedInUsers +Permission: + cmsmain1: + Code: CMS_ACCESS_CMSMain + cmsmain2: + Code: CMS_ACCESS_CMSMain + grantaccess: + Code: SITETREE_GRANT_ACCESS +Group: + subadmingroup: + Title: Create, edit and delete pages + Code: subadmingroup + Permissions: =>Permission.cmsmain1,=>Permission.grantaccess + editorgroup: + Title: Edit existing pages + Code: editorgroup + Permissions: =>Permission.cmsmain2 + websiteusers: + Title: View certain restricted pages +Member: + subadmin: + Email: subadmin@test.com + Password: test + Groups: =>Group.subadmingroup + editor: + Email: editor@test.com + Password: test + Groups: =>Group.editorgroup + websiteuser: + Email: websiteuser@test.com + Password: test + Groups: =>Group.websiteusers +Page: + standardpage: + URLSegment: standardpage + restrictedViewLoggedInUsers: + CanViewType: LoggedInUsers + URLSegment: restrictedViewLoggedInUsers + restrictedViewOnlyWebsiteUsers: + CanViewType: OnlyTheseUsers + ViewerGroups: =>Group.websiteusers + URLSegment: restrictedViewOnlyWebsiteUsers + restrictedViewOnlySubadminGroup: + CanViewType: OnlyTheseUsers + ViewerGroups: =>Group.subadmingroup + URLSegment: restrictedViewOnlySubadminGroup + restrictedEditLoggedInUsers: + CanEditType: LoggedInUsers + URLSegment: restrictedEditLoggedInUsers + restrictedEditOnlySubadminGroup: + CanEditType: OnlyTheseUsers + EditorGroups: =>Group.subadmingroup + URLSegment: restrictedEditOnlySubadminGroup + inheritWithNoParent: + CanEditType: Inherit + CanViewType: Inherit + URLSegment: inheritWithNoParent + parent_restrictedViewOnlySubadminGroup: + CanViewType: OnlyTheseUsers + ViewerGroups: =>Group.subadmingroup + URLSegment: parent-restrictedViewOnlySubadminGroup + child_restrictedViewOnlySubadminGroup: + CanViewType: Inherit + Parent: =>Page.parent_restrictedViewOnlySubadminGroup + URLSegment: child-restrictedViewOnlySubadminGroup + parent_restrictedEditOnlySubadminGroup: + CanEditType: OnlyTheseUsers + EditorGroups: =>Group.subadmingroup + URLSegment: parent-restrictedEditOnlySubadminGroup + child_restrictedEditOnlySubadminGroup: + CanEditType: Inherit + Parent: =>Page.parent_restrictedEditOnlySubadminGroup + URLSegment: child-restrictedEditOnlySubadminGroup + deleteTestParentPage: + CanEditType: Inherit + URLSegment: deleteTestParentPage + deleteTestChildPage: + CanEditType: OnlyTheseUsers + EditorGroups: =>Group.subadmingroup + URLSegment: deleteTestChildPage + draftOnlyPage: + CanViewType: Anyone + URLSegment: draft-only \ No newline at end of file diff --git a/tests/SiteTreeTest.php b/tests/SiteTreeTest.php new file mode 100755 index 00000000..44e5c41d --- /dev/null +++ b/tests/SiteTreeTest.php @@ -0,0 +1,758 @@ + array('SiteTreeSubsites') + ); + + /** + * @todo Necessary because of monolithic Translatable design + */ + static protected $origTranslatableSettings = array(); + + static function set_up_once() { + // needs to recreate the database schema with language properties + self::kill_temp_db(); + + // store old defaults + self::$origTranslatableSettings['has_extension'] = singleton('SiteTree')->hasExtension('Translatable'); + self::$origTranslatableSettings['default_locale'] = Translatable::default_locale(); + + // overwrite locale + Translatable::set_default_locale("en_US"); + + // refresh the decorated statics - different fields in $db with Translatable enabled + if(self::$origTranslatableSettings['has_extension']) { + Object::remove_extension('SiteTree', 'Translatable'); + Object::remove_extension('SiteConfig', 'Translatable'); + } + + // recreate database with new settings + $dbname = self::create_temp_db(); + DB::set_alternative_database_name($dbname); + + parent::set_up_once(); + } + + static function tear_down_once() { + if(self::$origTranslatableSettings['has_extension']) { + Object::add_extension('SiteTree', 'Translatable'); + Object::add_extension('SiteConfig', 'Translatable'); + } + + + Translatable::set_default_locale(self::$origTranslatableSettings['default_locale']); + Translatable::set_current_locale(self::$origTranslatableSettings['default_locale']); + + self::kill_temp_db(); + self::create_temp_db(); + + parent::tear_down_once(); + } + + function testCreateDefaultpages() { + $remove = DataObject::get('SiteTree'); + if($remove) foreach($remove as $page) $page->delete(); + // Make sure the table is empty + $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 0); + + // Disable the creation + SiteTree::set_create_default_pages(false); + singleton('SiteTree')->requireDefaultRecords(); + + // The table should still be empty + $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 0); + + // Enable the creation + SiteTree::set_create_default_pages(true); + singleton('SiteTree')->requireDefaultRecords(); + + // The table should now have three rows (home, about-us, contact-us) + $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 3); + } + + /** + * Test generation of the URLSegment values. + * - Turns things into lowercase-hyphen-format + * - Generates from Title by default, unless URLSegment is explicitly set + * - Resolves duplicates by appending a number + * - renames classes with a class name conflict + */ + function testURLGeneration() { + $expectedURLs = array( + 'home' => 'home', + 'staff' => 'my-staff', + 'about' => 'about-us', + 'staffduplicate' => 'my-staff-2', + 'product1' => '1-1-test-product', + 'product2' => 'another-product', + 'product3' => 'another-product-2', + 'product4' => 'another-product-3', + 'object' => 'object', + 'controller' => 'controller-2', + 'numericonly' => '1930', + ); + + foreach($expectedURLs as $fixture => $urlSegment) { + $obj = $this->objFromFixture('Page', $fixture); + $this->assertEquals($urlSegment, $obj->URLSegment); + } + } + + /** + * Test that publication copies data to SiteTree_Live + */ + function testPublishCopiesToLiveTable() { + $obj = $this->objFromFixture('Page','about'); + $obj->publish('Stage', 'Live'); + + $createdID = DB::query("SELECT \"ID\" FROM \"SiteTree_Live\" WHERE \"URLSegment\" = '$obj->URLSegment'")->value(); + $this->assertEquals($obj->ID, $createdID); + } + + /** + * Test that field which are set and then cleared are also transferred to the published site. + */ + function testPublishDeletedFields() { + $this->logInWithPermission('ADMIN'); + + $obj = $this->objFromFixture('Page', 'about'); + $obj->MetaTitle = "asdfasdf"; + $obj->write(); + $this->assertTrue($obj->doPublish()); + + $this->assertEquals('asdfasdf', DB::query("SELECT \"MetaTitle\" FROM \"SiteTree_Live\" WHERE \"ID\" = '$obj->ID'")->value()); + + $obj->MetaTitle = null; + $obj->write(); + $this->assertTrue($obj->doPublish()); + + $this->assertNull(DB::query("SELECT \"MetaTitle\" FROM \"SiteTree_Live\" WHERE \"ID\" = '$obj->ID'")->value()); + + } + + function testParentNodeCachedInMemory() { + $parent = new SiteTree(); + $parent->Title = 'Section Title'; + $child = new SiteTree(); + $child->Title = 'Page Title'; + $child->setParent($parent); + + $this->assertType("SiteTree", $child->Parent); + $this->assertEquals("Section Title", $child->Parent->Title); + } + + function testParentModelReturnType() { + $parent = new SiteTreeTest_PageNode(); + $child = new SiteTreeTest_PageNode(); + + $child->setParent($parent); + $this->assertType('SiteTreeTest_PageNode', $child->Parent); + } + + /** + * Confirm that DataObject::get_one() gets records from SiteTree_Live + */ + function testGetOneFromLive() { + $s = new SiteTree(); + $s->Title = "V1"; + $s->URLSegment = "get-one-test-page"; + $s->write(); + $s->publish("Stage", "Live"); + $s->Title = "V2"; + $s->write(); + + $oldMode = Versioned::get_reading_mode(); + Versioned::reading_stage('Live'); + + $checkSiteTree = DataObject::get_one("SiteTree", "\"URLSegment\" = 'get-one-test-page'"); + $this->assertEquals("V1", $checkSiteTree->Title); + + Versioned::set_reading_mode($oldMode); + } + + function testChidrenOfRootAreTopLevelPages() { + $pages = DataObject::get("SiteTree"); + foreach($pages as $page) $page->publish('Stage', 'Live'); + unset($pages); + + /* If we create a new SiteTree object with ID = 0 */ + $obj = new SiteTree(); + /* Then its children should be the top-level pages */ + $stageChildren = $obj->stageChildren()->toDropDownMap('ID','Title'); + $liveChildren = $obj->liveChildren()->toDropDownMap('ID','Title'); + $allChildren = $obj->AllChildrenIncludingDeleted()->toDropDownMap('ID','Title'); + + $this->assertContains('Home', $stageChildren); + $this->assertContains('Products', $stageChildren); + $this->assertNotContains('Staff', $stageChildren); + + $this->assertContains('Home', $liveChildren); + $this->assertContains('Products', $liveChildren); + $this->assertNotContains('Staff', $liveChildren); + + $this->assertContains('Home', $allChildren); + $this->assertContains('Products', $allChildren); + $this->assertNotContains('Staff', $allChildren); + } + + function testCanSaveBlankToHasOneRelations() { + /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */ + $page = new SiteTree(); + $parentID = $this->idFromFixture('Page', 'home'); + $page->ParentID = $parentID; + $page->write(); + $this->assertEquals($parentID, DB::query("SELECT \"ParentID\" FROM \"SiteTree\" WHERE \"ID\" = $page->ID")->value()); + + /* You should then be able to save a null/0/'' value to the relation */ + $page->ParentID = null; + $page->write(); + $this->assertEquals(0, DB::query("SELECT \"ParentID\" FROM \"SiteTree\" WHERE \"ID\" = $page->ID")->value()); + } + + function testStageStates() { + // newly created page + $createdPage = new SiteTree(); + $createdPage->write(); + $this->assertFalse($createdPage->IsDeletedFromStage); + $this->assertTrue($createdPage->IsAddedToStage); + $this->assertTrue($createdPage->IsModifiedOnStage); + + // published page + $publishedPage = new SiteTree(); + $publishedPage->write(); + $publishedPage->publish('Stage','Live'); + $this->assertFalse($publishedPage->IsDeletedFromStage); + $this->assertFalse($publishedPage->IsAddedToStage); + $this->assertFalse($publishedPage->IsModifiedOnStage); + + // published page, deleted from stage + $deletedFromDraftPage = new SiteTree(); + $deletedFromDraftPage->write(); + $deletedFromDraftPageID = $deletedFromDraftPage->ID; + $deletedFromDraftPage->publish('Stage','Live'); + $deletedFromDraftPage->deleteFromStage('Stage'); + $this->assertTrue($deletedFromDraftPage->IsDeletedFromStage); + $this->assertFalse($deletedFromDraftPage->IsAddedToStage); + $this->assertFalse($deletedFromDraftPage->IsModifiedOnStage); + + // published page, deleted from live + $deletedFromLivePage = new SiteTree(); + $deletedFromLivePage->write(); + $deletedFromLivePage->publish('Stage','Live'); + $deletedFromLivePage->deleteFromStage('Stage'); + $deletedFromLivePage->deleteFromStage('Live'); + $this->assertTrue($deletedFromLivePage->IsDeletedFromStage); + $this->assertFalse($deletedFromLivePage->IsAddedToStage); + $this->assertFalse($deletedFromLivePage->IsModifiedOnStage); + + // published page, modified + $modifiedOnDraftPage = new SiteTree(); + $modifiedOnDraftPage->write(); + $modifiedOnDraftPage->publish('Stage','Live'); + $modifiedOnDraftPage->Content = 'modified'; + $modifiedOnDraftPage->write(); + $this->assertFalse($modifiedOnDraftPage->IsDeletedFromStage); + $this->assertFalse($modifiedOnDraftPage->IsAddedToStage); + $this->assertTrue($modifiedOnDraftPage->IsModifiedOnStage); + } + + /** + * Test that a page can be completely deleted and restored to the stage site + */ + function testRestoreToStage() { + $page = $this->objFromFixture('Page', 'about'); + $pageID = $page->ID; + $page->delete(); + $this->assertTrue(!DataObject::get_by_id("Page", $pageID)); + + $deletedPage = Versioned::get_latest_version('SiteTree', $pageID); + $resultPage = $deletedPage->doRestoreToStage(); + + $requeriedPage = DataObject::get_by_id("Page", $pageID); + + $this->assertEquals($pageID, $resultPage->ID); + $this->assertEquals($pageID, $requeriedPage->ID); + $this->assertEquals('About Us', $requeriedPage->Title); + $this->assertEquals('Page', $requeriedPage->class); + + + $page2 = $this->objFromFixture('Page', 'products'); + $page2ID = $page2->ID; + $page2->doUnpublish(); + $page2->delete(); + + // Check that if we restore while on the live site that the content still gets pushed to + // stage + Versioned::reading_stage('Live'); + $deletedPage = Versioned::get_latest_version('SiteTree', $page2ID); + $deletedPage->doRestoreToStage(); + $this->assertTrue(!Versioned::get_one_by_stage("Page", "Live", "\"SiteTree\".\"ID\" = " . $page2ID)); + + Versioned::reading_stage('Stage'); + $requeriedPage = DataObject::get_by_id("Page", $page2ID); + $this->assertEquals('Products', $requeriedPage->Title); + $this->assertEquals('Page', $requeriedPage->class); + + } + + public function testGetByLink() { + $home = $this->objFromFixture('Page', 'home'); + $about = $this->objFromFixture('Page', 'about'); + $staff = $this->objFromFixture('Page', 'staff'); + $product = $this->objFromFixture('Page', 'product1'); + $notFound = $this->objFromFixture('ErrorPage', '404'); + + SiteTree::disable_nested_urls(); + + $this->assertEquals($home->ID, SiteTree::get_by_link('/', false)->ID); + $this->assertEquals($home->ID, SiteTree::get_by_link('/home/', false)->ID); + $this->assertEquals($about->ID, SiteTree::get_by_link($about->Link(), false)->ID); + $this->assertEquals($staff->ID, SiteTree::get_by_link($staff->Link(), false)->ID); + $this->assertEquals($product->ID, SiteTree::get_by_link($product->Link(), false)->ID); + $this->assertEquals($notFound->ID, SiteTree::get_by_link($notFound->Link(), false)->ID); + + SiteTree::enable_nested_urls(); + + $this->assertEquals($home->ID, SiteTree::get_by_link('/', false)->ID); + $this->assertEquals($home->ID, SiteTree::get_by_link('/home/', false)->ID); + $this->assertEquals($about->ID, SiteTree::get_by_link($about->Link(), false)->ID); + $this->assertEquals($staff->ID, SiteTree::get_by_link($staff->Link(), false)->ID); + $this->assertEquals($product->ID, SiteTree::get_by_link($product->Link(), false)->ID); + $this->assertEquals($notFound->ID, SiteTree::get_by_link($notFound->Link(), false)->ID); + + $this->assertEquals ( + $staff->ID, SiteTree::get_by_link('/my-staff/', false)->ID, 'Assert a unique URLSegment can be used for b/c.' + ); + } + + function testRelativeLink() { + $about = $this->objFromFixture('Page', 'about'); + $staff = $this->objFromFixture('Page', 'staff'); + + SiteTree::enable_nested_urls(); + + $this->assertEquals('about-us/', $about->RelativeLink(), 'Matches URLSegment on top level without parameters'); + $this->assertEquals('about-us/my-staff/', $staff->RelativeLink(), 'Matches URLSegment plus parent on second level without parameters'); + $this->assertEquals('about-us/edit', $about->RelativeLink('edit'), 'Matches URLSegment plus parameter on top level'); + $this->assertEquals('about-us/tom&jerry', $about->RelativeLink('tom&jerry'), 'Doesnt url encode parameter'); + } + + function testDeleteFromStageOperatesRecursively() { + SiteTree::set_enforce_strict_hierarchy(false); + $pageAbout = $this->objFromFixture('Page', 'about'); + $pageStaff = $this->objFromFixture('Page', 'staff'); + $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); + + $pageAbout->delete(); + + $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); + $this->assertTrue(DataObject::get_by_id('Page', $pageStaff->ID) instanceof Page); + $this->assertTrue(DataObject::get_by_id('Page', $pageStaffDuplicate->ID) instanceof Page); + SiteTree::set_enforce_strict_hierarchy(true); + } + + function testDeleteFromStageOperatesRecursivelyStrict() { + $pageAbout = $this->objFromFixture('Page', 'about'); + $pageStaff = $this->objFromFixture('Page', 'staff'); + $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); + + $pageAbout->delete(); + + $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); + $this->assertFalse(DataObject::get_by_id('Page', $pageStaff->ID)); + $this->assertFalse(DataObject::get_by_id('Page', $pageStaffDuplicate->ID)); + } + + function testDeleteFromLiveOperatesRecursively() { + SiteTree::set_enforce_strict_hierarchy(false); + $this->logInWithPermission('ADMIN'); + + $pageAbout = $this->objFromFixture('Page', 'about'); + $pageAbout->doPublish(); + $pageStaff = $this->objFromFixture('Page', 'staff'); + $pageStaff->doPublish(); + $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); + $pageStaffDuplicate->doPublish(); + + $parentPage = $this->objFromFixture('Page', 'about'); + $parentPage->doDeleteFromLive(); + + Versioned::reading_stage('Live'); + $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); + $this->assertTrue(DataObject::get_by_id('Page', $pageStaff->ID) instanceof Page); + $this->assertTrue(DataObject::get_by_id('Page', $pageStaffDuplicate->ID) instanceof Page); + Versioned::reading_stage('Stage'); + SiteTree::set_enforce_strict_hierarchy(true); + } + + function testUnpublishDoesNotDeleteChildrenWithLooseHierachyOn() { + SiteTree::set_enforce_strict_hierarchy(false); + $this->logInWithPermission('ADMIN'); + + $pageAbout = $this->objFromFixture('Page', 'about'); + $pageAbout->doPublish(); + $pageStaff = $this->objFromFixture('Page', 'staff'); + $pageStaff->doPublish(); + $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); + $pageStaffDuplicate->doPublish(); + + $parentPage = $this->objFromFixture('Page', 'about'); + $parentPage->doUnpublish(); + + Versioned::reading_stage('Live'); + $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); + $this->assertTrue(DataObject::get_by_id('Page', $pageStaff->ID) instanceof Page); + $this->assertTrue(DataObject::get_by_id('Page', $pageStaffDuplicate->ID) instanceof Page); + Versioned::reading_stage('Stage'); + SiteTree::set_enforce_strict_hierarchy(true); + } + + + function testDeleteFromLiveOperatesRecursivelyStrict() { + $this->logInWithPermission('ADMIN'); + + $pageAbout = $this->objFromFixture('Page', 'about'); + $pageAbout->doPublish(); + $pageStaff = $this->objFromFixture('Page', 'staff'); + $pageStaff->doPublish(); + $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); + $pageStaffDuplicate->doPublish(); + + $parentPage = $this->objFromFixture('Page', 'about'); + $parentPage->doDeleteFromLive(); + + Versioned::reading_stage('Live'); + $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); + $this->assertFalse(DataObject::get_by_id('Page', $pageStaff->ID)); + $this->assertFalse(DataObject::get_by_id('Page', $pageStaffDuplicate->ID)); + Versioned::reading_stage('Stage'); + } + + /** + * Simple test to confirm that querying from a particular archive date doesn't throw + * an error + */ + function testReadArchiveDate() { + Versioned::reading_archived_date('2009-07-02 14:05:07'); + + DataObject::get('SiteTree', "\"ParentID\" = 0"); + + Versioned::reading_archived_date(null); + } + + function testEditPermissions() { + $editor = $this->objFromFixture("Member", "editor"); + + $home = $this->objFromFixture("Page", "home"); + $products = $this->objFromFixture("Page", "products"); + $product1 = $this->objFromFixture("Page", "product1"); + $product4 = $this->objFromFixture("Page", "product4"); + + // Can't edit a page that is locked to admins + $this->assertFalse($home->canEdit($editor)); + + // Can edit a page that is locked to editors + $this->assertTrue($products->canEdit($editor)); + + // Can edit a child of that page that inherits + $this->assertTrue($product1->canEdit($editor)); + + // Can't edit a child of that page that has its permissions overridden + $this->assertFalse($product4->canEdit($editor)); + } + + function testEditPermissionsOnDraftVsLive() { + // Create an inherit-permission page + $page = new Page(); + $page->write(); + $page->CanEditType = "Inherit"; + $page->doPublish(); + $pageID = $page->ID; + + // Lock down the site config + $sc = $page->SiteConfig; + $sc->CanEditType = 'OnlyTheseUsers'; + $sc->EditorGroups()->add($this->idFromFixture('Group', 'admins')); + $sc->write(); + + // Confirm that Member.editor can't edit the page + $this->objFromFixture('Member','editor')->logIn(); + $this->assertFalse($page->canEdit()); + + // Change the page to be editable by Group.editors, but do not publish + $this->objFromFixture('Member','admin')->logIn(); + $page->CanEditType = 'OnlyTheseUsers'; + $page->EditorGroups()->add($this->idFromFixture('Group', 'editors')); + $page->write(); + + // Confirm that Member.editor can now edit the page + $this->objFromFixture('Member','editor')->logIn(); + $this->assertTrue($page->canEdit()); + + // Publish the changes to the page + $this->objFromFixture('Member','admin')->logIn(); + $page->doPublish(); + + // Confirm that Member.editor can still edit the page + $this->objFromFixture('Member','editor')->logIn(); + $this->assertTrue($page->canEdit()); + } + + function testCompareVersions() { + $page = new Page(); + $page->write(); + $this->assertEquals(1, $page->Version); + + $page->Content = "

This is a test

"; + $page->write(); + $this->assertEquals(2, $page->Version); + + $diff = $page->compareVersions(1, 2); + + $processedContent = trim($diff->Content); + $processedContent = preg_replace('/\s*\s*/','>',$processedContent); + $this->assertEquals("

This is a test

", $processedContent); + + } + + function testAuthorIDAndPublisherIDFilledOutOnPublish() { + // Ensure that we have a member ID who is doing all this work + $member = Member::currentUser(); + if($member) { + $memberID = $member->ID; + } else { + $memberID = $this->idFromFixture("Member", "admin"); + Session::set("loggedInAs", $memberID); + } + + // Write the page + $about = $this->objFromFixture('Page','about'); + $about->Title = "Another title"; + $about->write(); + + // Check the version created + $savedVersion = DB::query("SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_versions\" + WHERE \"RecordID\" = $about->ID ORDER BY \"Version\" DESC")->first(); + $this->assertEquals($memberID, $savedVersion['AuthorID']); + $this->assertEquals(0, $savedVersion['PublisherID']); + + // Publish the page + $about->doPublish(); + $publishedVersion = DB::query("SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_versions\" + WHERE \"RecordID\" = $about->ID ORDER BY \"Version\" DESC")->first(); + + // Check the version created + $this->assertEquals($memberID, $publishedVersion['AuthorID']); + $this->assertEquals($memberID, $publishedVersion['PublisherID']); + + } + + public function testLinkShortcodeHandler() { + $aboutPage = $this->objFromFixture('Page', 'about'); + $errorPage = $this->objFromFixture('ErrorPage', '404'); + + $parser = new ShortcodeParser(); + $parser->register('sitetree_link', array('SiteTree', 'link_shortcode_handler')); + + $aboutShortcode = sprintf('[sitetree_link id=%d]', $aboutPage->ID); + $aboutEnclosed = sprintf('[sitetree_link id=%d]Example Content[/sitetree_link]', $aboutPage->ID); + + $aboutShortcodeExpected = $aboutPage->Link(); + $aboutEnclosedExpected = sprintf('Example Content', $aboutPage->Link()); + + $this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test that simple linking works.'); + $this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed), 'Test enclosed content is linked.'); + + $aboutPage->delete(); + + $this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test that deleted pages still link.'); + $this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed)); + + $aboutShortcode = '[sitetree_link id="-1"]'; + $aboutEnclosed = '[sitetree_link id="-1"]Example Content[/sitetree_link]'; + + $aboutShortcodeExpected = $errorPage->Link(); + $aboutEnclosedExpected = sprintf('Example Content', $errorPage->Link()); + + $this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test link to 404 page if no suitable matches.'); + $this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed)); + + $this->assertEquals('', $parser->parse('[sitetree_link]'), 'Test that invalid ID attributes are not parsed.'); + $this->assertEquals('', $parser->parse('[sitetree_link id="text"]')); + $this->assertEquals('', $parser->parse('[sitetree_link]Example Content[/sitetree_link]')); + } + + public function testIsCurrent() { + $aboutPage = $this->objFromFixture('Page', 'about'); + $errorPage = $this->objFromFixture('ErrorPage', '404'); + + Director::set_current_page($aboutPage); + $this->assertTrue($aboutPage->isCurrent(), 'Assert that basic isSection checks works.'); + $this->assertFalse($errorPage->isCurrent()); + + Director::set_current_page($errorPage); + $this->assertTrue($errorPage->isCurrent(), 'Assert isSection works on error pages.'); + $this->assertFalse($aboutPage->isCurrent()); + + Director::set_current_page($aboutPage); + $this->assertTrue ( + DataObject::get_one('SiteTree', '"Title" = \'About Us\'')->isCurrent(), + 'Assert that isCurrent works on another instance with the same ID.' + ); + + Director::set_current_page($newPage = new SiteTree()); + $this->assertTrue($newPage->isCurrent(), 'Assert that isCurrent works on unsaved pages.'); + } + + public function testIsSection() { + $about = $this->objFromFixture('Page', 'about'); + $staff = $this->objFromFixture('Page', 'staff'); + $ceo = $this->objFromFixture('Page', 'ceo'); + + Director::set_current_page($about); + $this->assertTrue($about->isSection()); + $this->assertFalse($staff->isSection()); + $this->assertFalse($ceo->isSection()); + + Director::set_current_page($staff); + $this->assertTrue($about->isSection()); + $this->assertTrue($staff->isSection()); + $this->assertFalse($ceo->isSection()); + + Director::set_current_page($ceo); + $this->assertTrue($about->isSection()); + $this->assertTrue($staff->isSection()); + $this->assertTrue($ceo->isSection()); + } + + /** + * @covers SiteTree::validURLSegment + */ + public function testValidURLSegmentURLSegmentConflicts() { + $sitetree = new SiteTree(); + SiteTree::disable_nested_urls(); + + $sitetree->URLSegment = 'home'; + $this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised'); + $sitetree->URLSegment = 'home-noconflict'; + $this->assertTrue($sitetree->validURLSegment()); + + $sitetree->ParentID = $this->idFromFixture('Page', 'about'); + $sitetree->URLSegment = 'home'; + $this->assertFalse($sitetree->validURLSegment(), 'Conflicts are still recognised with a ParentID value'); + + SiteTree::enable_nested_urls(); + + $sitetree->ParentID = 0; + $sitetree->URLSegment = 'home'; + $this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised'); + + $sitetree->ParentID = $this->idFromFixture('Page', 'about'); + $this->assertTrue($sitetree->validURLSegment(), 'URLSegments can be the same across levels'); + + $sitetree->URLSegment = 'my-staff'; + $this->assertFalse($sitetree->validURLSegment(), 'Nested URLSegment conflicts are recognised'); + $sitetree->URLSegment = 'my-staff-noconflict'; + $this->assertTrue($sitetree->validURLSegment()); + } + + /** + * @covers SiteTree::validURLSegment + */ + public function testValidURLSegmentClassNameConflicts() { + $sitetree = new SiteTree(); + $sitetree->URLSegment = 'Controller'; + + $this->assertFalse($sitetree->validURLSegment(), 'Class name conflicts are recognised'); + } + + /** + * @covers SiteTree::validURLSegment + */ + public function testValidURLSegmentControllerConflicts() { + SiteTree::enable_nested_urls(); + + $sitetree = new SiteTree(); + $sitetree->ParentID = $this->idFromFixture('SiteTreeTest_Conflicted', 'parent'); + + $sitetree->URLSegment = 'index'; + $this->assertFalse($sitetree->validURLSegment(), 'index is not a valid URLSegment'); + + $sitetree->URLSegment = 'conflicted-action'; + $this->assertFalse($sitetree->validURLSegment(), 'allowed_actions conflicts are recognised'); + + $sitetree->URLSegment = 'conflicted-template'; + $this->assertFalse($sitetree->validURLSegment(), 'Action-specific template conflicts are recognised'); + + $sitetree->URLSegment = 'valid'; + $this->assertTrue($sitetree->validURLSegment(), 'Valid URLSegment values are allowed'); + } + + public function testVersionsAreCreated() { + $p = new Page(); + $p->Content = "one"; + $p->write(); + $this->assertEquals(1, $p->Version); + + // No changes don't bump version + $p->write(); + $this->assertEquals(1, $p->Version); + + $p->Content = "two"; + $p->write(); + $this->assertEquals(2, $p->Version); + + // Only change meta-data don't bump version + $p->HasBrokenLink = true; + $p->write(); + $p->HasBrokenLink = false; + $p->write(); + $this->assertEquals(2, $p->Version); + + $p->Content = "three"; + $p->write(); + $this->assertEquals(3, $p->Version); + + } + + function testPageTypeClasses() { + $classes = SiteTree::page_type_classes(); + $this->assertNotContains('SiteTree', $classes, 'Page types do not include base class'); + $this->assertContains('Page', $classes, 'Page types do contain subclasses'); + } + +} + +/**#@+ + * @ignore + */ + +class SiteTreeTest_PageNode extends Page implements TestOnly { } +class SiteTreeTest_PageNode_Controller extends Page_Controller implements TestOnly { +} + +class SiteTreeTest_Conflicted extends Page implements TestOnly { } +class SiteTreeTest_Conflicted_Controller extends Page_Controller implements TestOnly { + + public static $allowed_actions = array ( + 'conflicted-action' + ); + + public function hasActionTemplate($template) { + if($template == 'conflicted-template') { + return true; + } else { + return parent::hasActionTemplate($template); + } + } + +} + +/**#@-*/ diff --git a/tests/WidgetAreaEditorTest.php b/tests/WidgetAreaEditorTest.php new file mode 100644 index 00000000..67cb7825 --- /dev/null +++ b/tests/WidgetAreaEditorTest.php @@ -0,0 +1,471 @@ + array( + 'BottomBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidget', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + $editorSide = new WidgetAreaEditor('SideBar'); + $editorBott = new WidgetAreaEditor('BottomBar'); + $page = new WidgetAreaEditorTest_FakePage(); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + + $this->assertEquals($page->BottomBar()->Widgets()->Count(), 1); + $this->assertEquals($page->SideBar()->Widgets()->Count(), 0); + + $_REQUEST = $oldRequest; + } + + function testFillingTwoAreas() { + $oldRequest = $_REQUEST; + + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetSide', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetBottom', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + $editorSide = new WidgetAreaEditor('SideBar'); + $editorBott = new WidgetAreaEditor('BottomBar'); + $page = new WidgetAreaEditorTest_FakePage(); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + + // Make sure they both got saved + $this->assertEquals($page->BottomBar()->Widgets()->Count(), 1); + $this->assertEquals($page->SideBar()->Widgets()->Count(), 1); + + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + $this->assertEquals($sideWidgets[0]->Title(), 'MyTestWidgetSide'); + $this->assertEquals($bottWidgets[0]->Title(), 'MyTestWidgetBottom'); + + $_REQUEST = $oldRequest; + } + + function testDeletingOneWidgetFromOneArea() { + $oldRequest = $_REQUEST; + + // First get some widgets in there + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetSide', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetBottom', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + $editorSide = new WidgetAreaEditor('SideBar'); + $editorBott = new WidgetAreaEditor('BottomBar'); + $page = new WidgetAreaEditorTest_FakePage(); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + // Save again (after removing the SideBar's widget) + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + ), + 'BottomBar' => array( + $bottWidgets[0]->ID => array( + 'Title' => 'MyTestWidgetBottom', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + $this->assertEquals($page->BottomBar()->Widgets()->Count(), 1); + $this->assertEquals($bottWidgets[0]->Title(), 'MyTestWidgetBottom'); + $this->assertEquals($page->SideBar()->Widgets()->Count(), 0); + + $_REQUEST = $oldRequest; + } + + function testDeletingAWidgetFromEachArea() { + $oldRequest = $_REQUEST; + + // First get some widgets in there + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetSide', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetBottom', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + $editorSide = new WidgetAreaEditor('SideBar'); + $editorBott = new WidgetAreaEditor('BottomBar'); + $page = new WidgetAreaEditorTest_FakePage(); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + // Save again (after removing the SideBar's widget) + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + ), + 'BottomBar' => array( + ) + ) + ); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + $this->assertEquals($page->BottomBar()->Widgets()->Count(), 0); + $this->assertEquals($page->SideBar()->Widgets()->Count(), 0); + + $_REQUEST = $oldRequest; + } + + function testEditingOneWidget() { + $oldRequest = $_REQUEST; + + // First get some widgets in there + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetSide', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetBottom', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + $editorSide = new WidgetAreaEditor('SideBar'); + $editorBott = new WidgetAreaEditor('BottomBar'); + $page = new WidgetAreaEditorTest_FakePage(); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + // Save again (after removing the SideBar's widget) + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + $sideWidgets[0]->ID => array( + 'Title' => 'MyTestWidgetSide-edited', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + $bottWidgets[0]->ID => array( + 'Title' => 'MyTestWidgetBottom', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + $this->assertEquals($page->BottomBar()->Widgets()->Count(), 1); + $this->assertEquals($page->SideBar()->Widgets()->Count(), 1); + $this->assertEquals($bottWidgets[0]->Title(), 'MyTestWidgetBottom'); + $this->assertEquals($sideWidgets[0]->Title(), 'MyTestWidgetSide-edited'); + + + $_REQUEST = $oldRequest; + } + + function testEditingAWidgetFromEachArea() { + $oldRequest = $_REQUEST; + + // First get some widgets in there + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetSide', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetBottom', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + $editorSide = new WidgetAreaEditor('SideBar'); + $editorBott = new WidgetAreaEditor('BottomBar'); + $page = new WidgetAreaEditorTest_FakePage(); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + // Save again (after removing the SideBar's widget) + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + $sideWidgets[0]->ID => array( + 'Title' => 'MyTestWidgetSide-edited', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + $bottWidgets[0]->ID => array( + 'Title' => 'MyTestWidgetBottom-edited', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + $this->assertEquals($page->BottomBar()->Widgets()->Count(), 1); + $this->assertEquals($page->SideBar()->Widgets()->Count(), 1); + $this->assertEquals($bottWidgets[0]->Title(), 'MyTestWidgetBottom-edited'); + $this->assertEquals($sideWidgets[0]->Title(), 'MyTestWidgetSide-edited'); + + + $_REQUEST = $oldRequest; + } + + function testEditAWidgetFromOneAreaAndDeleteAWidgetFromAnotherArea() { + $oldRequest = $_REQUEST; + + // First get some widgets in there + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetSide', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + 'new-1' => array( + 'Title' => 'MyTestWidgetBottom', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ) + ) + ); + + $editorSide = new WidgetAreaEditor('SideBar'); + $editorBott = new WidgetAreaEditor('BottomBar'); + $page = new WidgetAreaEditorTest_FakePage(); + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + // Save again (after removing the SideBar's widget) + $_REQUEST = array( + 'Widget' => array( + 'SideBar' => array( + $sideWidgets[0]->ID => array( + 'Title' => 'MyTestWidgetSide-edited', + 'Type' => $this->widgetToTest, + 'Sort' => 0 + ) + ), + 'BottomBar' => array( + ) + ) + ); + + + $editorSide->saveInto($page); + $editorBott->saveInto($page); + + $page->write(); + $page->flushCache(); + $page->BottomBar()->flushCache(); + $page->SideBar()->flushCache(); + $sideWidgets = $page->SideBar()->Widgets()->toArray(); + $bottWidgets = $page->BottomBar()->Widgets()->toArray(); + + $this->assertEquals($page->BottomBar()->Widgets()->Count(), 0); + $this->assertEquals($page->SideBar()->Widgets()->Count(), 1); + $this->assertEquals($sideWidgets[0]->Title(), 'MyTestWidgetSide-edited'); + + + $_REQUEST = $oldRequest; + } +} + +class WidgetAreaEditorTest_FakePage extends Page implements TestOnly { + public static $has_one = array( + "SideBar" => "WidgetArea", + "BottomBar" => "WidgetArea", + ); +} + +class WidgetAreaEditorTest_TestWidget extends Widget implements TestOnly { + static $cmsTitle = "Test widget"; + static $title = "Test widget"; + static $description = "Test widget"; + static $db = array( + 'Title' => 'Varchar' + ); + public function getCMSFields() { + $fields = new FieldSet(); + $fields->push(new TextField('Title')); + return $fields; + } + function Title() { + return $this->Title ? $this->Title : self::$title; + } +} \ No newline at end of file