diff --git a/code/batchactions/CMSBatchActions.php b/code/batchactions/CMSBatchActions.php index 762d6df9..856882e7 100644 --- a/code/batchactions/CMSBatchActions.php +++ b/code/batchactions/CMSBatchActions.php @@ -22,21 +22,49 @@ class CMSBatchAction_Publish extends CMSBatchAction { } /** - * Un-publish items batch action. + * Unpublish items batch action. * * @package cms * @subpackage batchaction */ class CMSBatchAction_Unpublish extends CMSBatchAction { public function getActionTitle() { - return _t('CMSBatchActions.UNPUBLISH_PAGES', 'Un-publish'); + return _t('CMSBatchActions.UNPUBLISH_PAGES', 'Unpublish'); } public function run(SS_List $pages) { return $this->batchaction($pages, 'doUnpublish', - _t('CMSBatchActions.UNPUBLISHED_PAGES', 'Un-published %d pages') + _t('CMSBatchActions.UNPUBLISHED_PAGES', 'Unpublished %d pages') ); } + + public function applicablePages($ids) { + return $this->applicablePagesHelper($ids, 'canDeleteFromLive', false, true); + } +} + +/** + * Archives a page, removing it from both live and stage + * + * @package cms + * @subpackage batchaction + */ +class CMSBatchAction_Archive extends CMSBatchAction { + + public function getActionTitle() { + return _t('CMSBatchActions.ARCHIVE', 'Archive'); + } + + public function run(SS_List $pages) { + return $this->batchaction($pages, 'doArchive', + _t('CMSBatchActions.ARCHIVED_PAGES', 'Archived %d pages') + ); + } + + public function applicablePages($ids) { + return $this->applicablePagesHelper($ids, 'canArchive', true, true); + } + } /** @@ -44,6 +72,7 @@ class CMSBatchAction_Unpublish extends CMSBatchAction { * * @package cms * @subpackage batchaction + * @deprecated since version 4.0 */ class CMSBatchAction_Delete extends CMSBatchAction { public function getActionTitle() { @@ -51,6 +80,7 @@ class CMSBatchAction_Delete extends CMSBatchAction { } public function run(SS_List $pages) { + Deprecation::notice('4.0', 'Delete is deprecated. Use Archive instead'); $status = array( 'modified'=>array(), 'deleted'=>array(), @@ -93,14 +123,15 @@ class CMSBatchAction_Delete extends CMSBatchAction { * * @package cms * @subpackage batchaction + * @deprecated since version 4.0 */ class CMSBatchAction_DeleteFromLive extends CMSBatchAction { public function getActionTitle() { return _t('CMSBatchActions.DELETE_PAGES', 'Delete from published site'); } - public function run(SS_List $pages) { + Deprecation::notice('4.0', 'Delete From Live is deprecated. Use Unpublish instead'); $status = array( 'modified'=>array(), 'deleted'=>array() diff --git a/code/controllers/CMSMain.php b/code/controllers/CMSMain.php index 34e63645..01fc2620 100644 --- a/code/controllers/CMSMain.php +++ b/code/controllers/CMSMain.php @@ -36,6 +36,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr private static $page_length = 15; private static $allowed_actions = array( + 'archive', 'buildbrokenlinks', 'deleteitems', 'DeleteItemsForm', @@ -56,6 +57,14 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr 'ListViewForm', 'childfilter', ); + + /** + * Enable legacy batch actions. + * @deprecated since version 4.0 + * @var array + * @config + */ + private static $enabled_legacy_actions = array(); public function init() { // set reading lang @@ -88,8 +97,24 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr CMSBatchActionHandler::register('publish', 'CMSBatchAction_Publish'); CMSBatchActionHandler::register('unpublish', 'CMSBatchAction_Unpublish'); - CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete'); - CMSBatchActionHandler::register('deletefromlive', 'CMSBatchAction_DeleteFromLive'); + + + // Check legacy actions + $legacy = $this->config()->enabled_legacy_actions; + + // Delete from live is unnecessary since we have unpublish which does the same thing + if(in_array('CMSBatchAction_DeleteFromLive', $legacy)) { + Deprecation::notice('4.0', 'Delete From Live is deprecated. Use Un-publish instead'); + CMSBatchActionHandler::register('deletefromlive', 'CMSBatchAction_DeleteFromLive'); + } + + // Delete action + if(in_array('CMSBatchAction_Delete', $legacy)) { + Deprecation::notice('4.0', 'Delete from Stage is deprecated. Use Archive instead.'); + CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete'); + } else { + CMSBatchActionHandler::register('archive', 'CMSBatchAction_Archive'); + } } public function index($request) { @@ -1059,13 +1084,13 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr * @see deletefromlive() */ public function delete($data, $form) { + Deprecation::notice('4.0', 'Delete from stage is deprecated. Use archive instead'); $id = $data['ID']; $record = DataObject::get_by_id("SiteTree", $id); if($record && !$record->canDelete()) return Security::permissionFailure(); if(!$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); - // save ID and delete record - $recordID = $record->ID; + // Delete record $record->delete(); $this->response->addHeader( @@ -1077,6 +1102,34 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr return $this->getResponseNegotiator()->respond($this->getRequest()); } + /** + * Delete this page from both live and stage + * + * @param type $data + * @param type $form + */ + public function archive($data, $form) { + $id = $data['ID']; + $record = DataObject::get_by_id("SiteTree", $id); + if(!$record || !$record->exists()) { + throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); + } + if(!$record->canArchive()) { + return Security::permissionFailure(); + } + + // Archive record + $record->doArchive(); + + $this->response->addHeader( + 'X-Status', + rawurlencode(sprintf(_t('CMSMain.ARCHIVEDPAGE',"Archived page '%s'"), $record->Title)) + ); + + // Even if the record has been deleted from stage and live, it can be viewed in "archive mode" + return $this->getResponseNegotiator()->respond($this->getRequest()); + } + public function publish($data, $form) { $data['publish'] = '1'; diff --git a/code/controllers/CMSSiteTreeFilter.php b/code/controllers/CMSSiteTreeFilter.php index de673d52..35859784 100644 --- a/code/controllers/CMSSiteTreeFilter.php +++ b/code/controllers/CMSSiteTreeFilter.php @@ -286,7 +286,7 @@ class CMSSiteTreeFilter_DeletedPages extends CMSSiteTreeFilter { protected $numChildrenMethod = 'numHistoricalChildren'; static public function title() { - return _t('CMSSiteTreeFilter_DeletedPages.Title', "All pages, including deleted"); + return _t('CMSSiteTreeFilter_DeletedPages.Title', "All pages, including archived"); } public function getFilteredPages() { @@ -305,7 +305,7 @@ class CMSSiteTreeFilter_DeletedPages extends CMSSiteTreeFilter { class CMSSiteTreeFilter_ChangedPages extends CMSSiteTreeFilter { static public function title() { - return _t('CMSSiteTreeFilter_ChangedPages.Title', "Changed pages"); + return _t('CMSSiteTreeFilter_ChangedPages.Title', "Modified pages"); } public function getFilteredPages() { @@ -354,7 +354,7 @@ class CMSSiteTreeFilter_StatusRemovedFromDraftPages extends CMSSiteTreeFilter { class CMSSiteTreeFilter_StatusDraftPages extends CMSSiteTreeFilter { static public function title() { - return _t('CMSSiteTreeFilter_StatusDraftPages.Title', 'Draft unpublished pages'); + return _t('CMSSiteTreeFilter_StatusDraftPages.Title', 'Draft pages'); } /** @@ -393,7 +393,7 @@ class CMSSiteTreeFilter_StatusDeletedPages extends CMSSiteTreeFilter { protected $numChildrenMethod = 'numHistoricalChildren'; static public function title() { - return _t('CMSSiteTreeFilter_StatusDeletedPages.Title', 'Deleted pages'); + return _t('CMSSiteTreeFilter_StatusDeletedPages.Title', 'Archived pages'); } /** diff --git a/code/controllers/ContentController.php b/code/controllers/ContentController.php index 6a29567f..c4e2c1f2 100755 --- a/code/controllers/ContentController.php +++ b/code/controllers/ContentController.php @@ -119,7 +119,7 @@ class ContentController extends Controller { || (Versioned::current_stage() && Versioned::current_stage() != 'Live') ) ) { - if(!$this->dataRecord->canViewStage(Versioned::current_archived_date() ? 'Stage' : Versioned::current_stage())) { + if(!$this->dataRecord->canView()) { Session::clear('currentStage'); Session::clear('archiveDate'); diff --git a/code/model/ErrorPage.php b/code/model/ErrorPage.php index 85cac0c5..d22f80a0 100644 --- a/code/model/ErrorPage.php +++ b/code/model/ErrorPage.php @@ -260,6 +260,7 @@ class ErrorPage extends Page { $this->response->addHeader('X-Status', rawurlencode($fileErrorText)); return $this->httpError(405); } + return true; } /** diff --git a/code/model/SiteTree.php b/code/model/SiteTree.php index 137e97dc..7653155a 100755 --- a/code/model/SiteTree.php +++ b/code/model/SiteTree.php @@ -509,7 +509,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid */ public function RelativeLink($action = null) { if($this->ParentID && self::config()->nested_urls) { - $base = $this->Parent()->RelativeLink($this->URLSegment); + $parent = $this->Parent(); + // If page is removed select parent from version history (for archive page view) + if((!$parent || !$parent->exists()) && $this->IsDeletedFromStage) { + $parent = Versioned::get_latest_version('SiteTree', $this->ParentID); + } + $base = $parent->RelativeLink($this->URLSegment); } elseif(!$action && $this->URLSegment == RootURLController::get_homepage_link()) { // Unset base for root-level homepages. // Note: Homepages with action parameters (or $action === true) @@ -2298,20 +2303,50 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid if($this->canDelete() && $this->canDeleteFromLive()) { // "delete from live" $majorActions->push( - FormAction::create('deletefromlive',_t('CMSMain.DELETEFP','Delete'))->addExtraClass('ss-ui-action-destructive') + FormAction::create('deletefromlive',_t('CMSMain.DELETEFP','Delete')) + ->addExtraClass('ss-ui-action-destructive') ); } } else { + // Determine if we should force a restore to root (where once it was a subpage) + $restoreToRoot = $this->isParentArchived(); + // "restore" + $title = $restoreToRoot + ? _t('CMSMain.RESTORE_TO_ROOT','Restore draft at top level') + : _t('CMSMain.RESTORE','Restore draft'); + $description = $restoreToRoot + ? _t('CMSMain.RESTORE_TO_ROOT_DESC','Restore the archived version to draft as a top level page') + : _t('CMSMain.RESTORE_DESC', 'Restore the archived version to draft'); $majorActions->push( - FormAction::create('restore',_t('CMSMain.RESTORE','Restore'))->setAttribute('data-icon', 'decline') + FormAction::create('restore', $title) + ->setDescription($description) + ->setAttribute('data-to-root', $restoreToRoot) + ->setAttribute('data-icon', 'decline') ); } } else { - if($this->canDelete()) { - // "delete" + // Detect use of legacy actions + // {@see CMSMain::enabled_legacy_actions} + $legacy = CMSMain::config()->enabled_legacy_actions; + if(in_array('CMSBatchAction_Delete', $legacy)) { + Deprecation::notice('4.0', 'Delete from Stage is deprecated. Use Archive instead.'); + if($this->canDelete()) { + // delete + $moreOptions->push( + FormAction::create('delete',_t('CMSMain.DELETE','Delete draft')) + ->addExtraClass('delete ss-ui-action-destructive') + ); + } + } elseif($this->canArchive()) { + // "archive" $moreOptions->push( - FormAction::create('delete',_t('CMSMain.DELETE','Delete draft'))->addExtraClass('delete ss-ui-action-destructive') + FormAction::create('archive',_t('CMSMain.ARCHIVE','Archive')) + ->setDescription(_t( + 'SiteTree.BUTTONARCHIVEDESC', + 'Unpublish and send to archive' + )) + ->addExtraClass('delete ss-ui-action-destructive') ); } @@ -2353,6 +2388,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid * * @uses SiteTreeExtension->onBeforePublish() * @uses SiteTreeExtension->onAfterPublish() + * @return bool True if published */ public function doPublish() { if (!$this->canPublish()) return false; @@ -2461,6 +2497,22 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid } $this->invokeWithExtensions('onAfterRevertToLive', $this); + return true; + } + + /** + * Determine if this page references a parent which is archived, and not available in stage + * + * @return bool True if there is an archived parent + */ + protected function isParentArchived() { + if($parentID = $this->ParentID) { + $parentPage = Versioned::get_latest_version("SiteTree", $parentID); + if(!$parentPage || $parentPage->IsDeletedFromStage) { + return true; + } + } + return false; } /** @@ -2469,6 +2521,11 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid * @return self */ public function doRestoreToStage() { + // Ensure that the parent page is restored, otherwise restore to root + if($this->isParentArchived()) { + $this->ParentID = 0; + } + // if no record can be found on draft stage (meaning it has been "deleted from draft" before), // create an empty record if(!DB::prepared_query("SELECT \"ID\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($this->ID))->value()) { @@ -2496,6 +2553,50 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid return $result; } + /** + * Removes the page from both live and stage + * + * @return bool Success + */ + public function doArchive() { + if($this->doUnpublish()) { + $this->delete(); + return true; + } + return false; + } + + /** + * Check if the current user is allowed to archive this page. + * If extended, ensure that both canDelete and canDeleteFromLive are extended also + * + * @param Member $member + * @return bool + */ + public function canArchive($member = null) { + if(!$member) { + $member = Member::currentUser(); + } + + // Standard mechanism for accepting permission changes from extensions + $extended = $this->extendedCan('canArchive', $member); + if($extended !== null) { + return $extended; + } + + // Check if this page can be deleted + if(!$this->canDelete($member)) { + return false; + } + + // If published, check if we can delete from live + if($this->ExistsOnLive && !$this->canDeleteFromLive($member)) { + return false; + } + + return true; + } + /** * Synonym of {@link doUnpublish} */ @@ -2689,9 +2790,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid 'title' => _t('SiteTree.REMOVEDFROMDRAFTHELP', 'Page is published, but has been deleted from draft'), ); } else { - $flags['deletedonlive'] = array( - 'text' => _t('SiteTree.DELETEDPAGESHORT', 'Deleted'), - 'title' => _t('SiteTree.DELETEDPAGEHELP', 'Page is no longer published'), + $flags['archived'] = array( + 'text' => _t('SiteTree.ARCHIVEDPAGESHORT', 'Archived'), + 'title' => _t('SiteTree.ARCHIVEDPAGEHELP', 'Page is removed from draft and live'), ); } } else if($this->IsAddedToStage) { diff --git a/javascript/CMSMain.EditForm.js b/javascript/CMSMain.EditForm.js index fecce745..b0545851 100644 --- a/javascript/CMSMain.EditForm.js +++ b/javascript/CMSMain.EditForm.js @@ -276,6 +276,116 @@ } }); + /** + * Class: .cms-edit-form .Actions #Form_EditForm_action_archive + * + * Informing the user about the archive action while requiring confirmation + */ + $('.cms-edit-form .Actions #Form_EditForm_action_archive').entwine({ + + /** + * Function: onclick + * + * Parameters: + * (Event) e + */ + onclick: function(e) { + var form = this.parents('form:first'), version = form.find(':input[name=Version]').val(), message = ''; + message = ss.i18n.sprintf( + ss.i18n._t('CMSMain.Archive'), + version + ); + if(confirm(message)) { + return this._super(e); + } else { + return false; + } + } + }); + + /** + * Class: .cms-edit-form .Actions #Form_EditForm_action_restore + * + * Informing the user about the archive action while requiring confirmation + */ + $('.cms-edit-form .Actions #Form_EditForm_action_restore').entwine({ + + /** + * Function: onclick + * + * Parameters: + * (Event) e + */ + onclick: function(e) { + var form = this.parents('form:first'), + version = form.find(':input[name=Version]').val(), + message = '', + toRoot = this.data('toRoot'); + message = ss.i18n.sprintf( + ss.i18n._t(toRoot ? 'CMSMain.RestoreToRoot' : 'CMSMain.Restore'), + version + ); + if(confirm(message)) { + return this._super(e); + } else { + return false; + } + } + }); + + /** + * Class: .cms-edit-form .Actions #Form_EditForm_action_delete + * + * Informing the user about the delete from draft action while requiring confirmation + */ + $('.cms-edit-form .Actions #Form_EditForm_action_delete').entwine({ + + /** + * Function: onclick + * + * Parameters: + * (Event) e + */ + onclick: function(e) { + var form = this.parents('form:first'), version = form.find(':input[name=Version]').val(), message = ''; + message = ss.i18n.sprintf( + ss.i18n._t('CMSMain.DeleteFromDraft'), + version + ); + if(confirm(message)) { + return this._super(e); + } else { + return false; + } + } + }); + + /** + * Class: .cms-edit-form .Actions #Form_EditForm_action_unpublish + * Informing the user about the unpublish action while requiring confirmation + */ + $('.cms-edit-form .Actions #Form_EditForm_action_unpublish').entwine({ + + /** + * Function: onclick + * + * Parameters: + * (Event) e + */ + onclick: function(e) { + var form = this.parents('form:first'), version = form.find(':input[name=Version]').val(), message = ''; + message = ss.i18n.sprintf( + ss.i18n._t('CMSMain.Unpublish'), + version + ); + if(confirm(message)) { + return this._super(e); + } else { + return false; + } + } + }); + /** * Enable save buttons upon detecting changes to content. * "changed" class is added by jQuery.changetracker. diff --git a/javascript/lang/en.js b/javascript/lang/en.js index 366e3aea..5007758b 100644 --- a/javascript/lang/en.js +++ b/javascript/lang/en.js @@ -36,8 +36,13 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') { "Tree.ThisPageOnly": "This page only", "Tree.ThisPageAndSubpages": "This page and subpages", "Tree.ShowAsList": "Show children as list", - "CMSMain.ConfirmRestoreFromLive": "Do you really want to copy the published content to the draft site?", + "CMSMain.ConfirmRestoreFromLive": "Are you sure you want to revert draft to when the page was last published?", "CMSMain.RollbackToVersion": "Do you really want to roll back to version #%s of this page?", + "CMSMain.Archive": "Are you sure you want to archive this page?\n\nThe page will be unpublished and sent to the archive.", + "CMSMain.Restore": "Are you sure you want to restore this page from archive?", + "CMSMain.RestoreToRoot": "Are you sure you want to restore this page from archive?\n\nBecause the parent page is not available this will be restored to the top level.", + "CMSMain.Unpublish": "Are you sure you want to remove your page from the published site?\n\nThis page will still be available in the sitetree as draft.", + "CMSMain.DeleteFromDraft": "Are you sure you want to remove your page from the draft site?\n\nThis page will remain on the published site.", "URLSEGMENT.Edit": "Edit", "URLSEGMENT.OK": "OK", "URLSEGMENT.Cancel": "Cancel", diff --git a/javascript/lang/src/en.js b/javascript/lang/src/en.js index ee521b58..405324b5 100644 --- a/javascript/lang/src/en.js +++ b/javascript/lang/src/en.js @@ -31,8 +31,13 @@ "Tree.ThisPageOnly": "This page only", "Tree.ThisPageAndSubpages": "This page and subpages", "Tree.ShowAsList": "Show children as list", - "CMSMain.ConfirmRestoreFromLive": "Do you really want to copy the published content to the draft site?", + "CMSMain.ConfirmRestoreFromLive": "Are you sure you want to revert draft to when the page was last published?", "CMSMain.RollbackToVersion": "Do you really want to roll back to version #%s of this page?", + "CMSMain.Archive": "Are you sure you want to archive this page?\n\nThe page will be unpublished and sent to the archive.", + "CMSMain.Restore": "Are you sure you want to restore this page from archive?", + "CMSMain.RestoreToRoot": "Are you sure you want to restore this page from archive?\n\nBecause the parent page is not available this will be restored to the top level.", + "CMSMain.Unpublish": "Are you sure you want to remove your page from the published site?\n\nThis page will still be available in the sitetree as draft.", + "CMSMain.DeleteFromDraft": "Are you sure you want to remove your page from the draft site?\n\nThis page will remain on the published site.", "URLSEGMENT.Edit": "Edit", "URLSEGMENT.OK": "OK", "URLSEGMENT.Cancel": "Cancel", diff --git a/lang/en.yml b/lang/en.yml index c5e1ec3d..a40a571d 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -67,8 +67,8 @@ en: DELETE_PAGES: 'Delete from published site' PUBLISHED_PAGES: 'Published %d pages, %d failures' PUBLISH_PAGES: Publish - UNPUBLISHED_PAGES: 'Un-published %d pages' - UNPUBLISH_PAGES: Un-publish + UNPUBLISHED_PAGES: 'Unpublished %d pages' + UNPUBLISH_PAGES: Unpublish CMSMain: ACCESS: 'Access to ''{title}'' section' ACCESS_HELP: 'Allow viewing of the section containing page tree and content. View and edit permissions can be handled through page specific dropdowns, as well as the separate "Content permissions".' @@ -100,8 +100,11 @@ en: REMOVED: 'Deleted ''{title}''{description} from live site' REMOVEDPAGE: 'Removed ''{title}'' from the published site' REMOVEDPAGEFROMDRAFT: 'Removed ''%s'' from the draft site' - RESTORE: Restore + RESTORE: 'Restore draft' + RESTORE_TO_ROOT: 'Restore draft at top level' + RESTORE_TO_ROOT_DESC: 'Restore the archived version to draft as a top level page' RESTORED: 'Restored ''{title}'' successfully' + RESTORE_DESC: 'Restore the archived version to draft' ROLLBACK: 'Roll back to this version' ROLLEDBACKPUBv2: 'Rolled back to published version.' ROLLEDBACKVERSIONv2: 'Rolled back to version #%d.' @@ -152,11 +155,19 @@ en: FILTERDATETO: To FILTERLABELTEXT: Content CMSSiteTreeFilter_ChangedPages: - Title: 'Changed pages' + Title: 'Modified pages' CMSSiteTreeFilter_DeletedPages: - Title: 'All pages, including deleted' + Title: 'All pages, including archived' + CMSSIteTreeFilter_PublishedPages: + Title: 'Published pages' CMSSiteTreeFilter_Search: Title: 'All pages' + CMSSiteTreeFilter_StatusDeletedPages: + Title: 'Archived pages' + CMSSiteTreeFilter_StatusDraftPages: + Title: 'Draft pages' + CMSSiteTreeFilter_StatusRemovedFromDraftPages: + Title: 'Live but removed from draft' ContentControl: NOTEWONTBESHOWN: 'Note: this message will not be shown to your visitors' ContentController: diff --git a/tests/behat/features/publish-a-page.feature b/tests/behat/features/publish-a-page.feature index 10ca3fe6..00832f1c 100644 --- a/tests/behat/features/publish-a-page.feature +++ b/tests/behat/features/publish-a-page.feature @@ -35,7 +35,7 @@ So that only high quality changes are seen by our visitors When I click "More options" in the "#ActionMenus" element Then I should not see "Unpublish" in the "#ActionMenus_MoreOptions" element And I should see "Not published" in the "#ActionMenus_MoreOptions" element - And I should see "Delete draft" in the "#ActionMenus_MoreOptions" element + And I should see "Archive" in the "#ActionMenus_MoreOptions" element And I should see a "Save & publish" button And I should see a "Saved" button @@ -48,7 +48,7 @@ So that only high quality changes are seen by our visitors When I press the "Publish" button And I click "More options" in the "#ActionMenus" element Then I should see "Unpublish" in the "#ActionMenus_MoreOptions" element - And I should see "Delete draft" in the "#ActionMenus_MoreOptions" element + And I should see "Archive" in the "#ActionMenus_MoreOptions" element And I should see a "Published" button And I should see a "Saved" button @@ -64,7 +64,7 @@ So that only high quality changes are seen by our visitors And I click on "Hello" in the tree When I click "More options" in the "#ActionMenus" element - And I press the "Unpublish" button + And I press the "Unpublish" button, confirming the dialog Then I press the "Log out" button And I go to "/hello" @@ -80,16 +80,16 @@ So that only high quality changes are seen by our visitors And I click "More options" in the "#ActionMenus" element Then I should see "Unpublish" in the "#ActionMenus_MoreOptions" element - When I press the "Unpublish" button + When I press the "Unpublish" button, confirming the dialog And I click "More options" in the "#ActionMenus" element - Then I should see "Delete draft" in the "#ActionMenus_MoreOptions" element + Then I should see "Archive" in the "#ActionMenus_MoreOptions" element - When I press the "Delete draft" button + When I press the "Archive" button, confirming the dialog Then I should see a "Restore" button And I should not see a "Published" button And I should not see a "Save & publish" button And I should not see a "Saved" button And I should not see a "Save draft" button - When I press the "Restore" button + When I press the "Restore" button, confirming the dialog Then I should see a "Save & publish" button diff --git a/tests/behat/features/search-for-a-page.feature b/tests/behat/features/search-for-a-page.feature index 5d799160..9416ad18 100644 --- a/tests/behat/features/search-for-a-page.feature +++ b/tests/behat/features/search-for-a-page.feature @@ -46,7 +46,7 @@ Feature: Search for a page And the "page" "Deleted Page" is deleted When I press the "Apply Filter" button Then I should not see "Deleted Page" in the tree - When I select "All pages, including deleted" from "Pages" + When I select "All pages, including archived" from "Pages" And I press the "Apply Filter" button Then I should see "Deleted Page" in the tree @@ -56,7 +56,7 @@ Feature: Search for a page And the "page" "Deleted Page" is deleted When I press the "Apply Filter" button Then I should not see "Deleted Page" in the tree - When I select "Deleted pages" from "Pages" + When I select "Archived pages" from "Pages" And I press the "Apply Filter" button Then I should see "Deleted Page" in the tree And I should not see "About Us" in the tree @@ -66,7 +66,7 @@ Feature: Search for a page And the "page" "Draft Page" is not published When I press the "Apply Filter" button Then I should see "Draft Page" in the tree - When I select "Draft unpublished pages" from "Pages" + When I select "Draft pages" from "Pages" And I press the "Apply Filter" button Then I should see "Draft Page" in the tree And I should not see "About Us" in the tree @@ -81,7 +81,7 @@ Feature: Search for a page When I go to "/admin/pages" And I expand the "Filter" CMS Panel - When I select "Changed pages" from "Pages" + When I select "Modified pages" from "Pages" And I press the "Apply Filter" button Then I should see "About Us" in the tree And I should not see "Home" in the tree diff --git a/tests/controller/CMSBatchActionsTest.php b/tests/controller/CMSBatchActionsTest.php new file mode 100644 index 00000000..850062ab --- /dev/null +++ b/tests/controller/CMSBatchActionsTest.php @@ -0,0 +1,86 @@ +objFromFixture('Page', 'published'); + $published->doPublish(); + + // Deleted / archived page + $archived = $this->objFromFixture('Page', 'archived'); + $archived->doArchive(); + + // Unpublished + $unpublished = $this->objFromFixture('Page', 'unpublished'); + $unpublished->doPublish(); + $unpublished->doUnpublish(); + + // Modified + $modified = $this->objFromFixture('Page', 'modified'); + $modified->doPublish(); + $modified->Title = 'modified2'; + $modified->write(); + } + + /** + * Test which pages can be published via batch actions + */ + public function testBatchPublish() { + $this->logInWithPermission('ADMIN'); + $pages = Versioned::get_including_deleted('Page'); + $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); + } + + + /** + * Test which pages can be unpublished via batch actions + */ + public function testBatchUnpublish() { + $this->logInWithPermission('ADMIN'); + $pages = Versioned::get_including_deleted('Page'); + $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); + } + + /** + * Test which pages can be published via batch actions + */ + public function testBatchArchive() { + $this->logInWithPermission('ADMIN'); + $pages = Versioned::get_including_deleted('Page'); + $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); + } + +} diff --git a/tests/controller/CMSBatchActionsTest.yml b/tests/controller/CMSBatchActionsTest.yml new file mode 100644 index 00000000..bbeebb8f --- /dev/null +++ b/tests/controller/CMSBatchActionsTest.yml @@ -0,0 +1,9 @@ +Page: + published: + Title: Published + archived: + Title: archived + unpublished: + Title: unpublished + modified: + Title: modified1 \ No newline at end of file diff --git a/tests/model/SiteTreeActionsTest.php b/tests/model/SiteTreeActionsTest.php index 6fad34d1..799fc9d8 100644 --- a/tests/model/SiteTreeActionsTest.php +++ b/tests/model/SiteTreeActionsTest.php @@ -4,7 +4,7 @@ * - action_save * - action_publish * - action_unpublish - * - action_delete + * - action_archive * - action_deletefromlive * - action_rollback * - action_revert @@ -83,7 +83,7 @@ class SiteTreeActionsTest extends FunctionalTest { $this->assertNotNull($actions->dataFieldByName('action_save')); $this->assertNotNull($actions->dataFieldByName('action_publish')); $this->assertNotNull($actions->dataFieldByName('action_unpublish')); - $this->assertNotNull($actions->dataFieldByName('action_delete')); + $this->assertNotNull($actions->dataFieldByName('action_archive')); $this->assertNull($actions->dataFieldByName('action_deletefromlive')); $this->assertNull($actions->dataFieldByName('action_rollback')); $this->assertNull($actions->dataFieldByName('action_revert')); @@ -111,7 +111,7 @@ class SiteTreeActionsTest extends FunctionalTest { $this->assertNull($actions->dataFieldByName('action_save')); $this->assertNull($actions->dataFieldByName('action_publish')); $this->assertNull($actions->dataFieldByName('action_unpublish')); - $this->assertNull($actions->dataFieldByName('action_delete')); + $this->assertNull($actions->dataFieldByName('action_archive')); $this->assertNotNull($actions->dataFieldByName('action_deletefromlive')); $this->assertNull($actions->dataFieldByName('action_rollback')); $this->assertNotNull($actions->dataFieldByName('action_revert')); @@ -135,7 +135,7 @@ class SiteTreeActionsTest extends FunctionalTest { $this->assertNotNull($actions->dataFieldByName('action_save')); $this->assertNotNull($actions->dataFieldByName('action_publish')); $this->assertNotNull($actions->dataFieldByName('action_unpublish')); - $this->assertNotNull($actions->dataFieldByName('action_delete')); + $this->assertNotNull($actions->dataFieldByName('action_archive')); $this->assertNull($actions->dataFieldByName('action_deletefromlive')); $this->assertNotNull($actions->dataFieldByName('action_rollback')); $this->assertNull($actions->dataFieldByName('action_revert')); @@ -155,7 +155,7 @@ class SiteTreeActionsTest extends FunctionalTest { $this->assertNull($actions->dataFieldByName('action_save')); $this->assertNull($actions->dataFieldByName('action_publish')); $this->assertNull($actions->dataFieldByName('action_unpublish')); - $this->assertNull($actions->dataFieldByName('action_delete')); + $this->assertNull($actions->dataFieldByName('action_archive')); $this->assertNotNull($actions->dataFieldByName('action_email')); $this->assertNotNull($actions->dataFieldByName('action_rollback')); }