diff --git a/code/batchactions/CMSBatchActions.php b/code/batchactions/CMSBatchActions.php index 165e911b..27ab4265 100644 --- a/code/batchactions/CMSBatchActions.php +++ b/code/batchactions/CMSBatchActions.php @@ -67,6 +67,51 @@ class CMSBatchAction_Archive extends CMSBatchAction { } +/** + * Batch restore of pages + */ +class CMSBatchAction_Restore extends CMSBatchAction { + + public function getActionTitle() { + return _t('CMSBatchActions.RESTORE', 'Restore'); + } + + public function run(SS_List $pages) { + // Sort pages by depth + $pageArray = $pages->toArray(); + // because of https://bugs.php.net/bug.php?id=50688 + foreach($pageArray as $page) { + $page->getPageLevel(); + } + usort($pageArray, function($a, $b) { + return $a->getPageLevel() - $b->getPageLevel(); + }); + $pages = new ArrayList($pageArray); + + // Restore + return $this->batchaction($pages, 'doRestoreToStage', + _t('CMSBatchActions.RESTORED_PAGES', 'Restored %d pages') + ); + } + + /** + * {@see SiteTree::canEdit()} + * + * @param array $ids + * @return bool + */ + public function applicablePages($ids) { + // Basic permission check based on SiteTree::canEdit + if(!Permission::check(array("ADMIN", "SITETREE_EDIT_ALL"))) { + return array(); + } + + // Get pages that exist in stage and remove them from the restore-able set + $stageIDs = Versioned::get_by_stage($this->managedClass, 'Stage')->column('ID'); + return array_values(array_diff($ids, $stageIDs)); + } +} + /** * Delete items batch action. * diff --git a/code/controllers/CMSMain.php b/code/controllers/CMSMain.php index a36008c9..b7e4e361 100644 --- a/code/controllers/CMSMain.php +++ b/code/controllers/CMSMain.php @@ -114,6 +114,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete'); } else { CMSBatchActionHandler::register('archive', 'CMSBatchAction_Archive'); + CMSBatchActionHandler::register('restore', 'CMSBatchAction_Restore'); } } diff --git a/code/model/SiteTree.php b/code/model/SiteTree.php index e79219fb..b5dc7418 100755 --- a/code/model/SiteTree.php +++ b/code/model/SiteTree.php @@ -2877,6 +2877,18 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid return isset($stack[$level-1]) ? $stack[$level-1] : null; } + /** + * Gets the depth of this page in the sitetree, where 1 is the root level + * + * @return int + */ + public function getPageLevel() { + if($this->ParentID) { + return 1 + $this->Parent()->getPageLevel(); + } + return 1; + } + /** * Return the CSS classes to apply to this node in the CMS tree. * diff --git a/tests/controller/CMSBatchActionsTest.php b/tests/controller/CMSBatchActionsTest.php index 850062ab..a241a9b4 100644 --- a/tests/controller/CMSBatchActionsTest.php +++ b/tests/controller/CMSBatchActionsTest.php @@ -12,20 +12,20 @@ class CMSBatchActionsTest extends SapphireTest { parent::setUp(); // published page - $published = $this->objFromFixture('Page', 'published'); + $published = $this->objFromFixture('SiteTree', 'published'); $published->doPublish(); // Deleted / archived page - $archived = $this->objFromFixture('Page', 'archived'); - $archived->doArchive(); + $archived = $this->objFromFixture('SiteTree', 'archived'); + $archived->doArchive(); // should archive all children // Unpublished - $unpublished = $this->objFromFixture('Page', 'unpublished'); + $unpublished = $this->objFromFixture('SiteTree', 'unpublished'); $unpublished->doPublish(); $unpublished->doUnpublish(); // Modified - $modified = $this->objFromFixture('Page', 'modified'); + $modified = $this->objFromFixture('SiteTree', 'modified'); $modified->doPublish(); $modified->Title = 'modified2'; $modified->write(); @@ -34,53 +34,128 @@ class CMSBatchActionsTest extends SapphireTest { /** * Test which pages can be published via batch actions */ - public function testBatchPublish() { + public function testBatchPublishApplicable() { $this->logInWithPermission('ADMIN'); - $pages = Versioned::get_including_deleted('Page'); + $pages = Versioned::get_including_deleted('SiteTree'); $ids = $pages->column('ID'); $action = new CMSBatchAction_Publish(); // Test applicable pages $applicable = $action->applicablePages($ids); - $this->assertContains($this->idFromFixture('Page', 'published'), $applicable); - $this->assertNotContains($this->idFromFixture('Page', 'archived'), $applicable); - $this->assertContains($this->idFromFixture('Page', 'unpublished'), $applicable); - $this->assertContains($this->idFromFixture('Page', 'modified'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'published'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'archived'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'archivedx'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'archivedy'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'unpublished'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'modified'), $applicable); } /** * Test which pages can be unpublished via batch actions */ - public function testBatchUnpublish() { + public function testBatchUnpublishApplicable() { $this->logInWithPermission('ADMIN'); - $pages = Versioned::get_including_deleted('Page'); + $pages = Versioned::get_including_deleted('SiteTree'); $ids = $pages->column('ID'); $action = new CMSBatchAction_Unpublish(); // Test applicable page $applicable = $action->applicablePages($ids); - $this->assertContains($this->idFromFixture('Page', 'published'), $applicable); - $this->assertNotContains($this->idFromFixture('Page', 'archived'), $applicable); - $this->assertNotContains($this->idFromFixture('Page', 'unpublished'), $applicable); - $this->assertContains($this->idFromFixture('Page', 'modified'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'published'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'archived'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'archivedx'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'archivedy'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'unpublished'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'modified'), $applicable); } /** - * Test which pages can be published via batch actions + * Test which pages can be archived via batch actions */ - public function testBatchArchive() { + public function testBatchArchiveApplicable() { $this->logInWithPermission('ADMIN'); - $pages = Versioned::get_including_deleted('Page'); + $pages = Versioned::get_including_deleted('SiteTree'); $ids = $pages->column('ID'); $action = new CMSBatchAction_Archive(); // Test applicable pages $applicable = $action->applicablePages($ids); - $this->assertContains($this->idFromFixture('Page', 'published'), $applicable); - $this->assertNotContains($this->idFromFixture('Page', 'archived'), $applicable); - $this->assertContains($this->idFromFixture('Page', 'unpublished'), $applicable); - $this->assertContains($this->idFromFixture('Page', 'modified'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'published'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'archived'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'unpublished'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'modified'), $applicable); + } + + /** + * Test restore batch actions + */ + public function testBatchRestoreApplicable() { + $this->logInWithPermission('ADMIN'); + $pages = Versioned::get_including_deleted('SiteTree'); + $ids = $pages->column('ID'); + $action = new CMSBatchAction_Restore(); + + // Test applicable pages + $applicable = $action->applicablePages($ids); + $this->assertNotContains($this->idFromFixture('SiteTree', 'published'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'archived'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'archivedx'), $applicable); + $this->assertContains($this->idFromFixture('SiteTree', 'archivedy'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'unpublished'), $applicable); + $this->assertNotContains($this->idFromFixture('SiteTree', 'modified'), $applicable); + } + + public function testBatchRestore() { + $this->logInWithPermission('ADMIN'); + $pages = Versioned::get_including_deleted('SiteTree'); + $action = new CMSBatchAction_Restore(); + $archivedID = $this->idFromFixture('SiteTree', 'archived'); + $archivedxID = $this->idFromFixture('SiteTree', 'archivedx'); + $archivedyID = $this->idFromFixture('SiteTree', 'archivedy'); + + // Just restore one child + $list = $pages->filter('RecordID', $archivedxID); + $this->assertEquals(1, $list->count()); + $this->assertEquals($archivedID, $list->first()->ParentID); + + // Run restore + $result = json_decode($action->run($list), true); + $this->assertEquals( + array( + $archivedxID => $archivedxID + ), + $result['success'] + ); + $archivedx = SiteTree::get()->byID($archivedxID); + $this->assertNotNull($archivedx); + $this->assertEquals(0, $archivedx->ParentID); // Restore to root because parent is unrestored + + // Restore both remaining pages + $list = $pages + ->filter('RecordID', array($archivedID, $archivedyID)) + ->sort('Title'); + $this->assertEquals(2, $list->count()); + $this->assertEquals($archivedID, $list->first()->ParentID); // archivedy + $this->assertEquals(0, $list->last()->ParentID); // archived (parent) + + // Run restore + $result = json_decode($action->run($list), true); + $this->assertEquals( + array( + // Order of archived is opposite to order items are passed in, as + // these are sorted by level first + $archivedID => $archivedID, + $archivedyID => $archivedyID + ), + $result['success'] + ); + $archived = SiteTree::get()->byID($archivedID); + $archivedy = SiteTree::get()->byID($archivedyID); + $this->assertNotNull($archived); + $this->assertNotNull($archivedy); + $this->assertEquals($archivedID, $archivedy->ParentID); // Not restored to root, but to the parent + $this->assertEquals(0, $archived->ParentID); // Root stays root } } diff --git a/tests/controller/CMSBatchActionsTest.yml b/tests/controller/CMSBatchActionsTest.yml index bbeebb8f..88ed1775 100644 --- a/tests/controller/CMSBatchActionsTest.yml +++ b/tests/controller/CMSBatchActionsTest.yml @@ -1,9 +1,15 @@ -Page: +SiteTree: published: Title: Published archived: - Title: archived + Title: 'Parent Archived' unpublished: Title: unpublished modified: - Title: modified1 \ No newline at end of file + Title: modified1 + archivedx: + Title: 'Archived: Child1' + Parent: =>SiteTree.archived + archivedy: + Title: 'Archived: Child2' + Parent: =>SiteTree.archived diff --git a/tests/model/SiteTreeTest.php b/tests/model/SiteTreeTest.php index 88049d5b..2bd4b377 100644 --- a/tests/model/SiteTreeTest.php +++ b/tests/model/SiteTreeTest.php @@ -318,6 +318,13 @@ class SiteTreeTest extends SapphireTest { $this->assertEquals('about-us/tom&jerry', $about->RelativeLink('tom&jerry'), 'Doesnt url encode parameter'); } + public function testPageLevel() { + $about = $this->objFromFixture('Page', 'about'); + $staff = $this->objFromFixture('Page', 'staff'); + $this->assertEquals(1, $about->getPageLevel()); + $this->assertEquals(2, $staff->getPageLevel()); + } + public function testAbsoluteLiveLink() { $parent = $this->objFromFixture('Page', 'about'); $child = $this->objFromFixture('Page', 'staff');