From 4cc7b0806d12166c72f1e1bdf829d0c14e7beaf2 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 17 Mar 2016 13:02:50 +1300 Subject: [PATCH] API Update to use new Versioned API API CMS notifications for recursive operations API Un-deprecated delete action and batch actions API remove deprecated API --- code/batchactions/CMSBatchActions.php | 52 ---- code/controllers/AssetAdmin.php | 2 +- code/controllers/CMSMain.php | 205 ++++++-------- code/controllers/SilverStripeNavigator.php | 4 +- code/model/SiteTree.php | 312 ++++----------------- code/model/SiteTreeFileExtension.php | 4 +- code/model/SiteTreeLinkTracking.php | 33 ++- code/model/VirtualPage.php | 307 ++++++++------------ tests/controller/AssetAdminTest.php | 20 +- tests/controller/AssetAdminTest.yml | 4 +- tests/controller/CMSBatchActionsTest.php | 2 + tests/model/ErrorPageFileExtensionTest.php | 2 +- tests/model/FileLinkTrackingTest.php | 8 +- tests/model/SiteTreeBacklinksTest.php | 8 +- tests/model/SiteTreeBrokenLinksTest.php | 2 +- tests/model/SiteTreeTest.php | 26 +- tests/model/VirtualPageTest.php | 251 +++++------------ 17 files changed, 397 insertions(+), 845 deletions(-) diff --git a/code/batchactions/CMSBatchActions.php b/code/batchactions/CMSBatchActions.php index fbe230e4..68cabfb9 100644 --- a/code/batchactions/CMSBatchActions.php +++ b/code/batchactions/CMSBatchActions.php @@ -119,7 +119,6 @@ class CMSBatchAction_Restore extends CMSBatchAction { * * @package cms * @subpackage batchaction - * @deprecated since version 4.0 */ class CMSBatchAction_Delete extends CMSBatchAction { public function getActionTitle() { @@ -127,7 +126,6 @@ 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(), @@ -163,53 +161,3 @@ class CMSBatchAction_Delete extends CMSBatchAction { return $this->applicablePagesHelper($ids, 'canDelete', true, false); } } - -/** - * Unpublish (delete from live site) items batch action. - * - * @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() - ); - - /** @var SiteTree $page */ - foreach($pages as $page) { - $id = $page->ID; - - // Perform the action - if($page->canUnpublish()) { - $page->doUnpublish(); - } - - // check to see if the record exists on the stage site, if it doesn't remove the tree node - $stageRecord = Versioned::get_one_by_stage( 'SiteTree', 'Stage', array( - '"SiteTree"."ID"' => $id - )); - if($stageRecord) { - $status['modified'][$stageRecord->ID] = array( - 'TreeTitle' => $stageRecord->TreeTitle, - ); - } else { - $status['deleted'][$id] = array(); - } - - } - - return $this->response(_t('CMSBatchActions.DELETED_PAGES', 'Deleted %d pages from published site, %d failures'), $status); - } - - public function applicablePages($ids) { - return $this->applicablePagesHelper($ids, 'canDelete', false, true); - } -} diff --git a/code/controllers/AssetAdmin.php b/code/controllers/AssetAdmin.php index 6a8a31cc..9d4f393a 100644 --- a/code/controllers/AssetAdmin.php +++ b/code/controllers/AssetAdmin.php @@ -63,7 +63,7 @@ class AssetAdmin extends LeftAndMain implements PermissionProvider{ public function init() { parent::init(); - Versioned::reading_stage("Stage"); + Versioned::set_stage(Versioned::DRAFT); Requirements::javascript(CMS_DIR . "/javascript/dist/AssetAdmin.js"); Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang', false, true); diff --git a/code/controllers/CMSMain.php b/code/controllers/CMSMain.php index f4717139..f9d038cc 100644 --- a/code/controllers/CMSMain.php +++ b/code/controllers/CMSMain.php @@ -58,14 +58,6 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr '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 if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) { @@ -92,25 +84,9 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr CMSBatchActionHandler::register('publish', 'CMSBatchAction_Publish'); CMSBatchActionHandler::register('unpublish', 'CMSBatchAction_Unpublish'); - - - // 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'); - CMSBatchActionHandler::register('restore', 'CMSBatchAction_Restore'); - } + CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete'); + CMSBatchActionHandler::register('archive', 'CMSBatchAction_Archive'); + CMSBatchActionHandler::register('restore', 'CMSBatchAction_Restore'); } public function index($request) { @@ -555,7 +531,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr // Then, try getting a record from the live site if(!$record) { // $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id"); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::LIVE); singleton($treeClass)->flushCache(); $record = DataObject::get_by_id($treeClass, $id); @@ -872,6 +848,11 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr /** * Save and Publish page handler + * + * @param array $data + * @param Form $form + * @return SS_HTTPResponse + * @throws SS_HTTPResponse_Exception */ public function save($data, $form) { $className = $this->stat('tree_class'); @@ -879,19 +860,35 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr // Existing or new record? $id = $data['ID']; if(substr($id,0,3) != 'new') { + /** @var SiteTree $record */ $record = DataObject::get_by_id($className, $id); - if($record && !$record->canEdit()) return Security::permissionFailure($this); - if(!$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); + // Check edit permissions + if($record && !$record->canEdit()) { + return Security::permissionFailure($this); + } + if(!$record || !$record->ID) { + throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); + } } else { - if(!singleton($this->stat('tree_class'))->canCreate()) return Security::permissionFailure($this); + if(!$className::singleton()->canCreate()) { + return Security::permissionFailure($this); + } $record = $this->getNewItem($id, false); } + // Check publishing permissions + $doPublish = !empty($data['publish']); + if($record && $doPublish && !$record->canPublish()) { + return Security::permissionFailure($this); + } + // TODO Coupling to SiteTree $record->HasBrokenLink = 0; $record->HasBrokenFile = 0; - if (!$record->ObsoleteClassName) $record->writeWithoutVersion(); + if (!$record->ObsoleteClassName) { + $record->writeWithoutVersion(); + } // Update the class instance if necessary if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) { @@ -909,10 +906,22 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr $record->write(); // If the 'Save & Publish' button was clicked, also publish the page - if (isset($data['publish']) && $data['publish'] == 1) { + if ($doPublish) { $record->doPublish(); + $message = _t( + 'CMSMain.PUBLISHED', + "Published '{title}' successfully.", + ['title' => $record->Title] + ); + } else { + $message = _t( + 'CMSMain.SAVED', + "Saved '{title}' successfully.", + ['title' => $record->Title] + ); } + $this->getResponse()->addHeader('X-Status', rawurlencode($message)); return $this->getResponseNegotiator()->respond($this->getRequest()); } @@ -967,98 +976,56 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr return $newItem; } - /** - * Delete the page from live. This means a page in draft mode might still exist. - * - * @see delete() - */ - public function deletefromlive($data, $form) { - Versioned::reading_stage('Live'); - - /** @var SiteTree $record */ - $record = DataObject::get_by_id("SiteTree", $data['ID']); - if($record && !($record->canDelete() && $record->canUnpublish())) { - return Security::permissionFailure($this); - } - - $descendantsRemoved = 0; - $recordTitle = $record->Title; - - // before deleting the records, get the descendants of this tree - if($record) { - $descendantIDs = $record->getDescendantIDList(); - - // then delete them from the live site too - $descendantsRemoved = 0; - foreach( $descendantIDs as $descID ) - /** @var SiteTree $descendant */ - if( $descendant = DataObject::get_by_id('SiteTree', $descID) ) { - $descendant->doUnpublish(); - $descendantsRemoved++; - } - - // delete the record - $record->doUnpublish(); - } - - Versioned::reading_stage('Stage'); - - if(isset($descendantsRemoved)) { - $descRemoved = ' ' . _t( - 'CMSMain.DESCREMOVED', - 'and {count} descendants', - array('count' => $descendantsRemoved) - ); - } else { - $descRemoved = ''; - } - - $this->getResponse()->addHeader( - 'X-Status', - rawurlencode( - _t( - 'CMSMain.REMOVED', - 'Deleted \'{title}\'{description} from live site', - array('title' => $recordTitle, 'description' => $descRemoved) - ) - ) - ); - - // Even if the record has been deleted from stage and live, it can be viewed in "archive mode" - return $this->getResponseNegotiator()->respond($this->getRequest()); - } - /** * Actually perform the publication step + * + * @param Versioned|DataObject $record + * @return mixed */ public function performPublish($record) { - if($record && !$record->canPublish()) return Security::permissionFailure($this); + if($record && !$record->canPublish()) { + return Security::permissionFailure($this); + } $record->doPublish(); } /** - * Reverts a page by publishing it to live. - * Use {@link restorepage()} if you want to restore a page - * which was deleted from draft without publishing. - * - * @uses SiteTree->doRevertToLive() + * Reverts a page by publishing it to live. + * Use {@link restorepage()} if you want to restore a page + * which was deleted from draft without publishing. + * + * @uses SiteTree->doRevertToLive() + * + * @param array $data + * @param Form $form + * @return SS_HTTPResponse + * @throws SS_HTTPResponse_Exception */ public function revert($data, $form) { - if(!isset($data['ID'])) return new SS_HTTPResponse("Please pass an ID in the form content", 400); + if(!isset($data['ID'])) { + throw new SS_HTTPResponse_Exception("Please pass an ID in the form content", 400); + } $id = (int) $data['ID']; $restoredPage = Versioned::get_latest_version("SiteTree", $id); - if(!$restoredPage) return new SS_HTTPResponse("SiteTree #$id not found", 400); + if(!$restoredPage) { + throw new SS_HTTPResponse_Exception("SiteTree #$id not found", 400); + } + /** @var SiteTree $record */ $record = Versioned::get_one_by_stage('SiteTree', 'Live', array( '"SiteTree_Live"."ID"' => $id )); // a user can restore a page without publication rights, as it just adds a new draft state // (this action should just be available when page has been "deleted from draft") - if($record && !$record->canEdit()) return Security::permissionFailure($this); - if(!$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); + if($record && !$record->canEdit()) { + return Security::permissionFailure($this); + } + if(!$record || !$record->ID) { + throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); + } $record->doRevertToLive(); @@ -1077,14 +1044,23 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr /** * Delete the current page from draft stage. + * * @see deletefromlive() + * + * @param array $data + * @param Form $form + * @return SS_HTTPResponse + * @throws SS_HTTPResponse_Exception */ 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); + if($record && !$record->canDelete()) { + return Security::permissionFailure(); + } + if(!$record || !$record->ID) { + throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); + } // Delete record $record->delete(); @@ -1103,9 +1079,12 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr * * @param array $data * @param Form $form + * @return SS_HTTPResponse + * @throws SS_HTTPResponse_Exception */ public function archive($data, $form) { $id = $data['ID']; + /** @var SiteTree $record */ $record = DataObject::get_by_id("SiteTree", $id); if(!$record || !$record->exists()) { throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); @@ -1167,10 +1146,9 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr /** * Rolls a site back to a given version ID * - * @param array - * @param Form - * - * @return html + * @param array $data + * @param Form $form + * @return SS_HTTPResponse */ public function doRollback($data, $form) { $this->extend('onBeforeRollback', $data['ID']); @@ -1178,8 +1156,11 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr $id = (isset($data['ID'])) ? (int) $data['ID'] : null; $version = (isset($data['Version'])) ? (int) $data['Version'] : null; + /** @var DataObject|Versioned $record */ $record = DataObject::get_by_id($this->stat('tree_class'), $id); - if($record && !$record->canEdit()) return Security::permissionFailure($this); + if($record && !$record->canEdit()) { + return Security::permissionFailure($this); + } if($version) { $record->doRollbackTo($version); diff --git a/code/controllers/SilverStripeNavigator.php b/code/controllers/SilverStripeNavigator.php index 4d799d04..472bd18e 100644 --- a/code/controllers/SilverStripeNavigator.php +++ b/code/controllers/SilverStripeNavigator.php @@ -297,7 +297,7 @@ class SilverStripeNavigatorItem_StageLink extends SilverStripeNavigatorItem { public function isActive() { return ( - Versioned::current_stage() == 'Stage' + Versioned::get_stage() == 'Stage' && !(ClassInfo::exists('SiteTreeFutureState') && SiteTreeFutureState::get_future_datetime()) && !$this->isArchived() ); @@ -350,7 +350,7 @@ class SilverStripeNavigatorItem_LiveLink extends SilverStripeNavigatorItem { public function isActive() { return ( - (!Versioned::current_stage() || Versioned::current_stage() == 'Live') + (!Versioned::get_stage() || Versioned::get_stage() == 'Live') && !$this->isArchived() ); } diff --git a/code/model/SiteTree.php b/code/model/SiteTree.php index 9c6f1f2c..b9e0cd78 100755 --- a/code/model/SiteTree.php +++ b/code/model/SiteTree.php @@ -132,6 +132,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid "EditorGroups" => "Group", ); + private static $has_many = array( + "VirtualPages" => "VirtualPage.CopyContentFrom" + ); + + private static $owned_by = array( + "VirtualPages" + ); + private static $casting = array( "Breadcrumbs" => "HTMLText", "LastEdited" => "SS_Datetime", @@ -181,7 +189,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid private static $extensions = array( "Hierarchy", - "Versioned('Stage', 'Live')", + "Versioned", "SiteTreeLinkTracking" ); @@ -233,77 +241,6 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid protected $_cache_statusFlags = null; - /** - * Determines if the system should avoid orphaned pages - * by deleting all children when the their parent is deleted (TRUE), - * or rather preserve this data even if its not reachable through any navigation path (FALSE). - * - * @deprecated 4.0 Use the "SiteTree.enforce_strict_hierarchy" config setting instead - * @param boolean - */ - static public function set_enforce_strict_hierarchy($to) { - Deprecation::notice('4.0', 'Use the "SiteTree.enforce_strict_hierarchy" config setting instead'); - Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', $to); - } - - /** - * @deprecated 4.0 Use the "SiteTree.enforce_strict_hierarchy" config setting instead - * @return boolean - */ - static public function get_enforce_strict_hierarchy() { - Deprecation::notice('4.0', 'Use the "SiteTree.enforce_strict_hierarchy" config setting instead'); - return Config::inst()->get('SiteTree', 'enforce_strict_hierarchy'); - } - - /** - * Returns TRUE if nested URLs (e.g. page/sub-page/) are currently enabled on this site. - * - * @deprecated 4.0 Use the "SiteTree.nested_urls" config setting instead - * @return bool - */ - static public function nested_urls() { - Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead'); - return Config::inst()->get('SiteTree', 'nested_urls'); - } - - /** - * @deprecated 4.0 Use the "SiteTree.nested_urls" config setting instead - */ - static public function enable_nested_urls() { - Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead'); - Config::inst()->update('SiteTree', 'nested_urls', true); - } - - /** - * @deprecated 4.0 Use the "SiteTree.nested_urls" config setting instead - */ - static public function disable_nested_urls() { - Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead'); - Config::inst()->update('SiteTree', 'nested_urls', false); - } - - /** - * Set the (re)creation of default pages on /dev/build - * - * @deprecated 4.0 Use the "SiteTree.create_default_pages" config setting instead - * @param bool $option - */ - static public function set_create_default_pages($option = true) { - Deprecation::notice('4.0', 'Use the "SiteTree.create_default_pages" config setting instead'); - Config::inst()->update('SiteTree', 'create_default_pages', $option); - } - - /** - * Return true if default pages should be created on /dev/build. - * - * @deprecated 4.0 Use the "SiteTree.create_default_pages" config setting instead - * @return bool - */ - static public function get_create_default_pages() { - Deprecation::notice('4.0', 'Use the "SiteTree.create_default_pages" config setting instead'); - return Config::inst()->get('SiteTree', 'create_default_pages'); - } - /** * Fetches the {@link SiteTree} object that maps to a link. * @@ -439,7 +376,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid !($page = DataObject::get_by_id('SiteTree', $arguments['id'])) // Get the current page by ID. && !($page = Versioned::get_latest_version('SiteTree', $arguments['id'])) // Attempt link to old version. ) { - return; // There were no suitable matches at all. + return null; // There were no suitable matches at all. } $link = Convert::raw2att($page->Link()); @@ -538,9 +475,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid * @return string */ public function getAbsoluteLiveLink($includeStageEqualsLive = true) { - $oldStage = Versioned::current_stage(); - Versioned::reading_stage('Live'); - $live = Versioned::get_one_by_stage('SiteTree', 'Live', array( + $oldStage = Versioned::get_stage(); + Versioned::set_stage(Versioned::LIVE); + $live = Versioned::get_one_by_stage('SiteTree', Versioned::LIVE, array( '"SiteTree"."ID"' => $this->ID )); if($live) { @@ -550,7 +487,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid $link = null; } - Versioned::reading_stage($oldStage); + Versioned::set_stage($oldStage); return $link; } @@ -1067,22 +1004,6 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid } } - /** - * @deprecated - */ - public function canDeleteFromLive($member = null) { - Deprecation::notice('4.0', 'Use canUnpublish'); - - // Deprecated extension - $extended = $this->extendedCan('canDeleteFromLive', $member); - if($extended !== null) { - Deprecation::notice('4.0', 'Use canUnpublish in your extension instead'); - return $extended; - } - - return $this->canUnpublish($member); - } - /** * Stub method to get the site config, unless the current class can provide an alternate. * @@ -1531,26 +1452,6 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid $this->extend('augmentSyncLinkTracking'); } - public function onAfterWrite() { - // Need to flush cache to avoid outdated versionnumber references - $this->flushCache(); - - $linkedPages = $this->VirtualPages(); - if($linkedPages) { - // The only way after a write() call to determine if it was triggered by a writeWithoutVersion(), - // which we have to pass on to the virtual page writes as well. - $previous = ($this->Version > 1) ? Versioned::get_version($this->class, $this->ID, $this->Version-1) : null; - $withoutVersion = $this->getExtensionInstance('Versioned')->_nextWriteWithoutVersion; - foreach($linkedPages as $page) { - $page->copyFrom($page->CopyContentFrom()); - if($withoutVersion) $page->writeWithoutVersion(); - else $page->write(); - } - } - - parent::onAfterWrite(); - } - public function onBeforeDelete() { parent::onBeforeDelete(); @@ -1693,7 +1594,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid * @return string */ public function getStageURLSegment() { - $stageRecord = Versioned::get_one_by_stage('SiteTree', 'Stage', array( + $stageRecord = Versioned::get_one_by_stage('SiteTree', Versioned::DRAFT, array( '"SiteTree"."ID"' => $this->ID )); return ($stageRecord) ? $stageRecord->URLSegment : null; @@ -1705,7 +1606,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid * @return string */ public function getLiveURLSegment() { - $liveRecord = Versioned::get_one_by_stage('SiteTree', 'Live', array( + $liveRecord = Versioned::get_one_by_stage('SiteTree', Versioned::LIVE, array( '"SiteTree"."ID"' => $this->ID )); return ($liveRecord) ? $liveRecord->URLSegment : null; @@ -1720,7 +1621,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid */ public function rewriteFileLinks() { // Skip live stage - if(\Versioned::current_stage() === \Versioned::get_live_stage()) { + if(\Versioned::get_stage() === \Versioned::LIVE) { return; } @@ -1814,26 +1715,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid * @return DataList */ public function VirtualPages() { + $pages = parent::VirtualPages(); - // Ignore new records - if(!$this->ID) return null; - - // Check subsite virtual pages - // @todo Refactor out subsite module specific code - if(class_exists('Subsite')) { - return Subsite::get_from_all_subsites('VirtualPage', array( - '"VirtualPage"."CopyContentFromID"' => $this->ID - )); + // Disable subsite filter for these pages + if($pages instanceof DataList) { + return $pages->setDataQueryParam('Subsite.filter', false); + } else { + return $pages; } - - // Check existing virtualpages - if(class_exists('VirtualPage')) { - return VirtualPage::get()->where(array( - '"VirtualPage"."CopyContentFromID"' => $this->ID - )); - } - - return null; } /** @@ -2213,7 +2102,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus noborder'); // Render page information into the "more-options" drop-up, on the top. - $live = Versioned::get_one_by_stage('SiteTree', 'Live', array( + $live = Versioned::get_one_by_stage('SiteTree', Versioned::LIVE, array( '"SiteTree"."ID"' => $this->ID )); $moreOptions->push( @@ -2226,7 +2115,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid ); // "readonly"/viewing version that isn't the current version of the record - $stageOrLiveRecord = Versioned::get_one_by_stage($this->class, Versioned::current_stage(), array( + $stageOrLiveRecord = Versioned::get_one_by_stage($this->class, Versioned::get_stage(), array( '"SiteTree"."ID"' => $this->ID )); if($stageOrLiveRecord && $stageOrLiveRecord->Version != $this->Version) { @@ -2292,19 +2181,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid ); } } else { - // 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()) { + if($this->canDelete()) { + // delete + $moreOptions->push( + FormAction::create('delete',_t('CMSMain.DELETE','Delete draft')) + ->addExtraClass('delete ss-ui-action-destructive') + ); + } + if($this->canArchive()) { // "archive" $moreOptions->push( FormAction::create('archive',_t('CMSMain.ARCHIVE','Archive')) @@ -2349,123 +2233,29 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid return $actions; } - /** - * Publish this page. - * - * @uses SiteTreeExtension->onBeforePublish() - * @uses SiteTreeExtension->onAfterPublish() - * @return bool True if published - */ - public function doPublish() { - if (!$this->canPublish()) return false; - - $original = Versioned::get_one_by_stage("SiteTree", "Live", array( - '"SiteTree"."ID"' => $this->ID - )); - if(!$original) $original = new SiteTree(); - - // Handle activities undertaken by extensions - $this->invokeWithExtensions('onBeforePublish', $original); - //$this->PublishedByID = Member::currentUser()->ID; - $this->write(); - $this->publish("Stage", "Live"); - + public function onAfterPublish() { + // Force live sort order to match stage sort order DB::prepared_query('UPDATE "SiteTree_Live" SET "Sort" = (SELECT "SiteTree"."Sort" FROM "SiteTree" WHERE "SiteTree_Live"."ID" = "SiteTree"."ID") WHERE EXISTS (SELECT "SiteTree"."Sort" FROM "SiteTree" WHERE "SiteTree_Live"."ID" = "SiteTree"."ID") AND "ParentID" = ?', array($this->ParentID) ); - - // Publish any virtual pages that might need publishing - $linkedPages = $this->VirtualPages(); - if($linkedPages) foreach($linkedPages as $page) { - $page->copyFrom($page->CopyContentFrom()); - $page->write(); - if($page->getExistsOnLive()) $page->doPublish(); - } - - // Need to update pages linking to this one as no longer broken, on the live site - $origMode = Versioned::get_reading_mode(); - Versioned::reading_stage('Live'); - foreach($this->DependentPages(false) as $page) { - // $page->write() calls syncLinkTracking, which does all the hard work for us. - $page->write(); - } - Versioned::set_reading_mode($origMode); - - // Handle activities undertaken by extensions - $this->invokeWithExtensions('onAfterPublish', $original); - - return true; } /** - * Unpublish this page - remove it from the live site - * - * Overrides {@see Versioned::doUnpublish()} - * - * @uses SiteTreeExtension->onBeforeUnpublish() - * @uses SiteTreeExtension->onAfterUnpublish() + * Update draft dependant pages */ - public function doUnpublish() { - if(!$this->canUnpublish()) return false; - if(!$this->ID) return false; - - $this->invokeWithExtensions('onBeforeUnpublish', $this); - - $origStage = Versioned::current_stage(); - Versioned::reading_stage('Live'); - - // We should only unpublish virtualpages that exist on live - $virtualPages = $this->VirtualPages(); - - // This way our ID won't be unset - $clone = clone $this; - $clone->delete(); - - // Rewrite backlinks - $dependentPages = $this->DependentPages(false); - if($dependentPages) foreach($dependentPages as $page) { - // $page->write() calls syncLinkTracking, which does all the hard work for us. - $page->write(); - } - Versioned::reading_stage($origStage); - - // Unpublish any published virtual pages - if ($virtualPages) foreach($virtualPages as $vp) $vp->doUnpublish(); - - // If we're on the draft site, then we can update the status. - // Otherwise, these lines will resurrect an inappropriate record - if(DB::prepared_query("SELECT \"ID\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($this->ID))->value() - && Versioned::current_stage() != 'Live') { - $this->write(); - } - - $this->invokeWithExtensions('onAfterUnpublish', $this); - - return true; - } - - /** - * Revert the draft changes: replace the draft content with the content on live - */ - public function doRevertToLive() { - $this->invokeWithExtensions('onBeforeRevertToLive', $this); - - $this->publish("Live", "Stage", false); - - // Use a clone to get the updates made by $this->publish - $clone = DataObject::get_by_id("SiteTree", $this->ID); - $clone->writeWithoutVersion(); + public function onAfterRevertToLive() { + // Use an alias to get the updates made by $this->publish + /** @var SiteTree $stageSelf */ + $stageSelf = Versioned::get_by_stage('SiteTree', Versioned::DRAFT)->byID($this->ID); + $stageSelf->writeWithoutVersion(); // Need to update pages linking to this one as no longer broken - foreach($this->DependentPages(false) as $page) { - // $page->write() calls syncLinkTracking, which does all the hard work for us. - $page->write(); + foreach($stageSelf->DependentPages() as $page) { + /** @var SiteTree $page */ + $page->writeWithoutVersion(); } - - $this->invokeWithExtensions('onAfterRevertToLive', $this); - return true; } /** @@ -2505,8 +2295,8 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('SiteTree', false); } - $oldStage = Versioned::current_stage(); - Versioned::reading_stage('Stage'); + $oldStage = Versioned::get_stage(); + Versioned::set_stage(Versioned::DRAFT); $this->forceChange(); $this->write(); @@ -2518,21 +2308,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid $page->write(); } - Versioned::reading_stage($oldStage); + Versioned::set_stage($oldStage); $this->invokeWithExtensions('onAfterRestoreToStage', $this); return $result; } - /** - * @deprecated - */ - public function doDeleteFromLive() { - Deprecation::notice("4.0", "Use doUnpublish instead"); - return $this->doUnpublish(); - } - /** * Check if this page is new - that is, if it has yet to have been written to the database. * diff --git a/code/model/SiteTreeFileExtension.php b/code/model/SiteTreeFileExtension.php index cee594a0..8fb5cade 100644 --- a/code/model/SiteTreeFileExtension.php +++ b/code/model/SiteTreeFileExtension.php @@ -109,7 +109,7 @@ class SiteTreeFileExtension extends DataExtension { */ public function onAfterDelete() { // Skip live stage - if(\Versioned::current_stage() === \Versioned::get_live_stage()) { + if(\Versioned::get_stage() === Versioned::LIVE) { return; } @@ -143,7 +143,7 @@ class SiteTreeFileExtension extends DataExtension { */ public function updateLinks() { // Skip live stage - if(\Versioned::current_stage() === \Versioned::get_live_stage()) { + if(\Versioned::get_stage() === \Versioned::LIVE) { return; } diff --git a/code/model/SiteTreeLinkTracking.php b/code/model/SiteTreeLinkTracking.php index 3ec69c08..3ce7ab60 100644 --- a/code/model/SiteTreeLinkTracking.php +++ b/code/model/SiteTreeLinkTracking.php @@ -28,12 +28,39 @@ */ class SiteTreeLinkTracking extends DataExtension { - public $parser; + /** + * @var SiteTreeLinkTracking_Parser + */ + protected $parser; + /** + * Inject parser for each page + * + * @var array + * @config + */ private static $dependencies = array( - 'parser' => '%$SiteTreeLinkTracking_Parser' + 'Parser' => '%$SiteTreeLinkTracking_Parser' ); + /** + * Parser for link tracking + * + * @return SiteTreeLinkTracking_Parser + */ + public function getParser() { + return $this->parser; + } + + /** + * @param SiteTreeLinkTracking_Parser $parser + * @return $this + */ + public function setParser($parser) { + $this->parser = $parser; + return $this; + } + private static $db = array( "HasBrokenFile" => "Boolean", "HasBrokenLink" => "Boolean" @@ -178,7 +205,7 @@ class SiteTreeLinkTracking extends DataExtension { */ public function augmentSyncLinkTracking() { // Skip live tracking - if(\Versioned::current_stage() == \Versioned::get_live_stage()) { + if(\Versioned::get_stage() == \Versioned::LIVE) { return; } diff --git a/code/model/VirtualPage.php b/code/model/VirtualPage.php index cc30b113..0dbcb816 100644 --- a/code/model/VirtualPage.php +++ b/code/model/VirtualPage.php @@ -3,6 +3,9 @@ * Virtual Page creates an instance of a page, with the same fields that the original page had, but readonly. * This allows you can have a page in mulitple places in the site structure, with different children without duplicating the content * Note: This Only duplicates $db fields and not the $has_one etc.. + * + * @method SiteTree CopyContentFrom() + * * @package cms */ class VirtualPage extends Page { @@ -18,8 +21,10 @@ class VirtualPage extends Page { private static $non_virtual_fields = array( "ID", "ClassName", + "ObsoleteClassName", "SecurityTypeID", "OwnerID", + "ParentID", "URLSegment", "Sort", "Status", @@ -31,6 +36,8 @@ class VirtualPage extends Page { "Expiry", "CanViewType", "CanEditType", + "CopyContentFromID", + "HasBrokenLink", ); /** @@ -46,57 +53,43 @@ class VirtualPage extends Page { "CopyContentFrom" => "SiteTree", ); + private static $owns = array( + "CopyContentFrom", + ); + private static $db = array( "VersionID" => "Int", ); /** * Generates the array of fields required for the page type. + * + * @return array */ public function getVirtualFields() { - $nonVirtualFields = array_merge(self::config()->non_virtual_fields, self::config()->initially_copied_fields); + // Check if copied page exists $record = $this->CopyContentFrom(); + if(!$record || !$record->exists()) { + return array(); + } - $virtualFields = array(); - foreach($record->db() as $field => $type) { - if(!in_array($field, $nonVirtualFields)) { - $virtualFields[] = $field; - } - } - return $virtualFields; + // Diff db with non-virtual fields + $fields = array_keys($record->db()); + $nonVirtualFields = $this->getNonVirtualisedFields(); + return array_diff($fields, $nonVirtualFields); } /** - * Returns the linked page, or failing that, a new object. + * List of fields or properties to never virtualise * - * Always returns a non-empty object - * - * @return SiteTree + * @return array */ - public function CopyContentFrom() { - $copyContentFromID = $this->CopyContentFromID; - if(!$copyContentFromID) { - return new SiteTree(); - } - - if(!isset($this->components['CopyContentFrom'])) { - $this->components['CopyContentFrom'] = DataObject::get_by_id("SiteTree", $copyContentFromID); - - // Don't let VirtualPages point to other VirtualPages - if($this->components['CopyContentFrom'] instanceof VirtualPage) { - $this->components['CopyContentFrom'] = null; - } - - // has_one component semantics incidate than an empty object should be returned - if(!$this->components['CopyContentFrom']) { - $this->components['CopyContentFrom'] = new SiteTree(); - } - } - - return $this->components['CopyContentFrom'] ? $this->components['CopyContentFrom'] : new SiteTree(); + public function getNonVirtualisedFields() { + return array_merge($this->config()->non_virtual_fields, $this->config()->initially_copied_fields); } public function setCopyContentFromID($val) { + // Sanity check to prevent pages virtualising other virtual pages if($val && DataObject::get_by_id('SiteTree', $val) instanceof VirtualPage) { $val = 0; } @@ -104,7 +97,11 @@ class VirtualPage extends Page { } public function ContentSource() { - return $this->CopyContentFrom(); + $copied = $this->CopyContentFrom(); + if($copied && $copied->exists()) { + return $copied; + } + return $this; } /** @@ -116,15 +113,18 @@ class VirtualPage extends Page { */ public function MetaTags($includeTitle = true) { $tags = parent::MetaTags($includeTitle); - if ($this->CopyContentFrom()->ID) { - $tags .= "CopyContentFrom()->Link()}\" />\n"; + $copied = $this->CopyContentFrom(); + if ($copied && $copied->exists()) { + $link = Convert::raw2att($copied->Link()); + $tags .= "\n"; } return $tags; } public function allowedChildren() { - if($this->CopyContentFrom()) { - return $this->CopyContentFrom()->allowedChildren(); + $copy = $this->CopyContentFrom(); + if($copy && $copy->exists()) { + return $copy->allowedChildren(); } return array(); } @@ -237,6 +237,35 @@ class VirtualPage extends Page { return $fields; } + public function onBeforeWrite() { + parent::onBeforeWrite(); + $this->refreshFromCopied(); + } + + /** + * Copy any fields from the copied record to bootstrap /backup + */ + protected function refreshFromCopied() { + // Skip if copied record isn't available + $source = $this->CopyContentFrom(); + if(!$source || !$source->exists()) { + return; + } + + // We also want to copy certain, but only if we're copying the source page for the first + // time. After this point, the user is free to customise these for the virtual page themselves. + if($this->isChanged('CopyContentFromID', 2) && $this->CopyContentFromID) { + foreach (self::config()->initially_copied_fields as $fieldName) { + $this->$fieldName = $source->$fieldName; + } + } + + // Copy fields to the original record in case the class type changes + foreach($this->getVirtualFields() as $virtualField) { + $this->$virtualField = $source->$virtualField; + } + } + public function getSettingsFields() { $fields = parent::getSettingsFields(); if(!$this->CopyContentFrom()->exists()) { @@ -257,106 +286,12 @@ class VirtualPage extends Page { return $fields; } - /** - * We have to change it to copy all the content from the original page first. - */ - public function onBeforeWrite() { - $performCopyFrom = null; - - // Determine if we need to copy values. - if( - $this->extension_instances['Versioned']->migratingVersion - && Versioned::current_stage() == 'Live' - && $this->CopyContentFromID - ) { - // On publication to live, copy from published source. - $performCopyFrom = true; - - $stageSourceVersion = DB::prepared_query( - 'SELECT "Version" FROM "SiteTree" WHERE "ID" = ?', - array($this->CopyContentFromID) - )->value(); - $liveSourceVersion = DB::prepared_query( - 'SELECT "Version" FROM "SiteTree_Live" WHERE "ID" = ?', - array($this->CopyContentFromID) - )->value(); - - // We're going to create a new VP record in SiteTree_versions because the published - // version might not exist, unless we're publishing the latest version - if($stageSourceVersion != $liveSourceVersion) { - $this->extension_instances['Versioned']->migratingVersion = null; - } - } else { - // On regular write, copy from draft source. This is only executed when the source page changes. - $performCopyFrom = $this->isChanged('CopyContentFromID', 2) && $this->CopyContentFromID != 0; - } - - if($performCopyFrom && $this instanceof VirtualPage) { - // This flush is needed because the get_one cache doesn't respect site version :-( - singleton('SiteTree')->flushCache(); - // @todo Update get_one to support parameterised queries - $source = DataObject::get_by_id("SiteTree", $this->CopyContentFromID); - // Leave the updating of image tracking until after write, in case its a new record - $this->copyFrom($source, false); - } - - parent::onBeforeWrite(); - } - - public function onAfterWrite() { - parent::onAfterWrite(); - - // Don't do this stuff when we're publishing - if(!$this->extension_instances['Versioned']->migratingVersion) { - if( - $this->isChanged('CopyContentFromID') - && $this->CopyContentFromID != 0 - && $this instanceof VirtualPage - ) { - $this->updateImageTracking(); - } - } - - // Check if page type has changed to a non-virtual page. - // Caution: Relies on the fact that the current instance is still of the old page type. - if($this->isChanged('ClassName', 2)) { - $changed = $this->getChangedFields(); - $classBefore = $changed['ClassName']['before']; - $classAfter = $changed['ClassName']['after']; - if($classBefore != $classAfter) { - // Remove all database rows for the old page type to avoid inconsistent data retrieval. - // TODO This should apply to all page type changes, not only on VirtualPage - but needs - // more comprehensive testing as its a destructive operation - $removedTables = array_diff(ClassInfo::dataClassesFor($classBefore), ClassInfo::dataClassesFor($classAfter)); - if($removedTables) foreach($removedTables as $removedTable) { - // Note: *_versions records are left intact - foreach(array('', 'Live') as $stage) { - if($stage) $removedTable = "{$removedTable}_{$stage}"; - DB::prepared_query("DELETE FROM \"$removedTable\" WHERE \"ID\" = ?", array($this->ID)); - } - } - - // Also publish the change immediately to avoid inconsistent behaviour between - // a non-virtual draft and a virtual live record (e.g. republishing the original record - // shouldn't republish the - now unrelated - changes on the ex-VirtualPage draft). - // Copies all stage fields to live as well. - // @todo Update get_one to support parameterised queries - $source = DataObject::get_by_id("SiteTree", $this->CopyContentFromID); - $this->copyFrom($source); - $this->publish('Stage', 'Live'); - - // Change reference on instance (as well as removing the underlying database tables) - $this->CopyContentFromID = 0; - } - } - } - public function validate() { $result = parent::validate(); // "Can be root" validation $orig = $this->CopyContentFrom(); - if(!$orig->stat('can_be_root') && !$this->ParentID) { + if($orig && $orig->exists() && !$orig->stat('can_be_root') && !$this->ParentID) { $result->error( _t( 'VirtualPage.PageTypNotAllowedOnRoot', @@ -370,30 +305,6 @@ class VirtualPage extends Page { return $result; } - /** - * Ensure we have an up-to-date version of everything. - * - * @param DataObject $source - * @param bool $updateImageTracking - */ - public function copyFrom($source, $updateImageTracking = true) { - if($source) { - foreach($this->getVirtualFields() as $virtualField) { - $this->$virtualField = $source->$virtualField; - } - - // We also want to copy certain, but only if we're copying the source page for the first - // time. After this point, the user is free to customise these for the virtual page themselves. - if($this->isChanged('CopyContentFromID', 2) && $this->CopyContentFromID != 0) { - foreach(self::config()->initially_copied_fields as $fieldName) { - $this->$fieldName = $source->$fieldName; - } - } - - if($updateImageTracking) $this->updateImageTracking(); - } - } - public function updateImageTracking() { // Doesn't work on unsaved records if(!$this->ID) return; @@ -425,11 +336,41 @@ class VirtualPage extends Page { return $this->$funcName(); } else if(parent::hasField($field) || ($field === 'ID' && !$this->exists())) { return $this->getField($field); - } else { - return $this->copyContentFrom()->$field; + } elseif(($copy = $this->CopyContentFrom()) && $copy->exists()) { + return $copy->$field; } } + public function getField($field) { + if($this->isFieldVirtualised($field)) { + return $this->CopyContentFrom()->getField($field); + } + return parent::getField($field); + } + + /** + * Check if given field is virtualised + * + * @param string $field + * @return bool + */ + public function isFieldVirtualised($field) { + // Don't defer if field is non-virtualised + $ignore = $this->getNonVirtualisedFields(); + if(in_array($field, $ignore)) { + return false; + } + + // Don't defer if no virtual page + $copied = $this->CopyContentFrom(); + if(!$copied || !$copied->exists()) { + return false; + } + + // Check if copied object has this field + return $copied->hasField($field); + } + /** * Pass unrecognized method calls on to the original data object * @@ -452,9 +393,11 @@ class VirtualPage extends Page { public function hasField($field) { if(parent::hasField($field)) { return true; + } + $copy = $this->CopyContentFrom(); + return $copy && $copy->exists() && $copy->hasField($field); } - return $this->CopyContentFrom()->hasField($field); - } + /** * Overwrite to also check for method on the original data object * @@ -464,8 +407,13 @@ class VirtualPage extends Page { public function hasMethod($method) { if(parent::hasMethod($method)) { return true; - } - return $this->CopyContentFrom()->hasMethod($method); + } + // Don't call property setters on copied page + if(stripos($method, 'set') === 0) { + return false; + } + $copy = $this->CopyContentFrom(); + return $copy && $copy->exists() && $copy->hasMethod($method); } /** @@ -476,9 +424,11 @@ class VirtualPage extends Page { * @return string */ public function castingHelper($field) { - return $this - ->CopyContentFrom() - ->castingHelper($field); + $copy = $this->CopyContentFrom(); + if($copy && $copy->exists() && ($helper = $copy->castingHelper($field))) { + return $helper; + } + return parent::castingHelper($field); } } @@ -493,15 +443,6 @@ class VirtualPage_Controller extends Page_Controller { 'loadcontentall' => 'ADMIN', ); - /** - * Reloads the content if the version is different ;-) - */ - public function reloadContent() { - $this->failover->copyFrom($this->failover->CopyContentFrom()); - $this->failover->write(); - return; - } - public function getViewer($action) { $originalClass = get_class($this->CopyContentFrom()); if ($originalClass == 'SiteTree') $name = 'Page_Controller'; @@ -517,26 +458,10 @@ class VirtualPage_Controller extends Page_Controller { * We can't load the content without an ID or record to copy it from. */ public function init(){ - if(isset($this->record) && $this->record->ID){ - if($this->record->VersionID != $this->failover->CopyContentFrom()->Version){ - $this->reloadContent(); - $this->VersionID = $this->failover->CopyContentFrom()->VersionID; - } - } parent::init(); $this->__call('init', array()); } - public function loadcontentall() { - $pages = DataObject::get("VirtualPage"); - foreach($pages as $page) { - $page->copyFrom($page->CopyContentFrom()); - $page->write(); - $page->publish("Stage", "Live"); - echo "
  • Published $page->URLSegment"; - } - } - /** * Also check the original object's original controller for the method * diff --git a/tests/controller/AssetAdminTest.php b/tests/controller/AssetAdminTest.php index 27344365..0eca811e 100644 --- a/tests/controller/AssetAdminTest.php +++ b/tests/controller/AssetAdminTest.php @@ -10,22 +10,22 @@ class AssetAdminTest extends SapphireTest { public function setUp() { parent::setUp(); + AssetStoreTest_SpyStore::activate('AssetAdminTest'); + if(!file_exists(ASSETS_PATH)) mkdir(ASSETS_PATH); // Create a test folders for each of the fixture references - $folderIDs = $this->allFixtureIDs('Folder'); - foreach($folderIDs as $folderID) { - $folder = DataObject::get_by_id('Folder', $folderID); - if(!file_exists(BASE_PATH."/$folder->Filename")) mkdir(BASE_PATH."/$folder->Filename"); + foreach(File::get()->filter('ClassName', 'Folder') as $folder) { + /** @var Folder $folder */ + $folder->doPublish(); } // Create a test files for each of the fixture references - $fileIDs = $this->allFixtureIDs('File'); - foreach($fileIDs as $fileID) { - $file = DataObject::get_by_id('File', $fileID); - $fh = fopen(BASE_PATH."/$file->Filename", "w"); - fwrite($fh, str_repeat('x',1000000)); - fclose($fh); + $content = str_repeat('x',1000000); + foreach(File::get()->exclude('ClassName', 'Folder') as $file) { + /** @var File $file */ + $file->setFromString($content, $file->generateFilename()); + $file->doPublish(); } } diff --git a/tests/controller/AssetAdminTest.yml b/tests/controller/AssetAdminTest.yml index dd3172aa..8529be9f 100644 --- a/tests/controller/AssetAdminTest.yml +++ b/tests/controller/AssetAdminTest.yml @@ -4,9 +4,9 @@ Folder: File: file1: Title: File1 - Filename: assets/AssetAdminTest/file1.txt + Name: file1.txt ParentID: =>Folder.folder1 file2: Title: File2 - Filename: assets/AssetAdminTest/file2.txt + Name: file2.txt ParentID: =>Folder.folder1 diff --git a/tests/controller/CMSBatchActionsTest.php b/tests/controller/CMSBatchActionsTest.php index beaa1ec8..0908ed1b 100644 --- a/tests/controller/CMSBatchActionsTest.php +++ b/tests/controller/CMSBatchActionsTest.php @@ -11,6 +11,8 @@ class CMSBatchActionsTest extends SapphireTest { public function setUp() { parent::setUp(); + $this->logInWithPermission('ADMIN'); + // published page $published = $this->objFromFixture('SiteTree', 'published'); $published->doPublish(); diff --git a/tests/model/ErrorPageFileExtensionTest.php b/tests/model/ErrorPageFileExtensionTest.php index 73e7cd06..8d77fedb 100644 --- a/tests/model/ErrorPageFileExtensionTest.php +++ b/tests/model/ErrorPageFileExtensionTest.php @@ -9,7 +9,7 @@ class ErrorPageFileExtensionTest extends SapphireTest { public function setUp() { parent::setUp(); $this->versionedMode = Versioned::get_reading_mode(); - Versioned::reading_stage('Stage'); + Versioned::set_stage(Versioned::DRAFT); AssetStoreTest_SpyStore::activate('ErrorPageFileExtensionTest'); $file = new File(); $file->setFromString('dummy', 'dummy.txt'); diff --git a/tests/model/FileLinkTrackingTest.php b/tests/model/FileLinkTrackingTest.php index 9a3913ad..63e09b79 100644 --- a/tests/model/FileLinkTrackingTest.php +++ b/tests/model/FileLinkTrackingTest.php @@ -9,7 +9,7 @@ class FileLinkTrackingTest extends SapphireTest { public function setUp() { parent::setUp(); - Versioned::reading_stage('Stage'); + Versioned::set_stage(Versioned::DRAFT); AssetStoreTest_SpyStore::activate('FileLinkTrackingTest'); $this->logInWithPermission('ADMIN'); @@ -112,18 +112,20 @@ class FileLinkTrackingTest extends SapphireTest { $file->write(); // Verify that the draft virtual pages have the correct content + $svp = Versioned::get_by_stage('VirtualPage', Versioned::DRAFT)->byID($svp->ID); $this->assertContains( 'ID))->value() + $svp->Content ); // Publishing both file and page will update the live record $file->doPublish(); $page->doPublish(); + $svp = Versioned::get_by_stage('VirtualPage', Versioned::LIVE)->byID($svp->ID); $this->assertContains( 'ID))->value() + $svp->Content ); } diff --git a/tests/model/SiteTreeBacklinksTest.php b/tests/model/SiteTreeBacklinksTest.php index cc6c9e17..79d76811 100644 --- a/tests/model/SiteTreeBacklinksTest.php +++ b/tests/model/SiteTreeBacklinksTest.php @@ -108,7 +108,7 @@ class SiteTreeBacklinksTest extends SapphireTest { $page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID); // assert hyperlink to page 1's new url exists - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::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'); } @@ -136,7 +136,7 @@ class SiteTreeBacklinksTest extends SapphireTest { // assert hyperlink to page 1's current publish url exists $page3live = Versioned::get_one_by_stage('Page', 'Live', '"SiteTree"."ID" = ' . $page3->ID); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::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'); @@ -179,7 +179,7 @@ class SiteTreeBacklinksTest extends SapphireTest { // 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'); + Versioned::set_stage(Versioned::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'); @@ -223,7 +223,7 @@ class SiteTreeBacklinksTest extends SapphireTest { // confirm that published link hasn't $page2Live = Versioned::get_one_by_stage("Page", "Live", "\"SiteTree\".\"ID\" = $page2->ID"); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::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 diff --git a/tests/model/SiteTreeBrokenLinksTest.php b/tests/model/SiteTreeBrokenLinksTest.php index 3a6c9feb..02693033 100644 --- a/tests/model/SiteTreeBrokenLinksTest.php +++ b/tests/model/SiteTreeBrokenLinksTest.php @@ -11,7 +11,7 @@ class SiteTreeBrokenLinksTest extends SapphireTest { public function setUp() { parent::setUp(); - Versioned::reading_stage('Stage'); + Versioned::set_stage(Versioned::DRAFT); AssetStoreTest_SpyStore::activate('SiteTreeBrokenLinksTest'); $this->logInWithPermission('ADMIN'); } diff --git a/tests/model/SiteTreeTest.php b/tests/model/SiteTreeTest.php index 880a264c..67839fc8 100644 --- a/tests/model/SiteTreeTest.php +++ b/tests/model/SiteTreeTest.php @@ -141,7 +141,7 @@ class SiteTreeTest extends SapphireTest { $s->write(); $oldMode = Versioned::get_reading_mode(); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::LIVE); $checkSiteTree = DataObject::get_one("SiteTree", array( '"SiteTree"."URLSegment"' => 'get-one-test-page' @@ -264,12 +264,12 @@ class SiteTreeTest extends SapphireTest { // Check that if we restore while on the live site that the content still gets pushed to // stage - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::LIVE); $deletedPage = Versioned::get_latest_version('SiteTree', $page2ID); $deletedPage->doRestoreToStage(); $this->assertFalse((bool)Versioned::get_one_by_stage("Page", "Live", "\"SiteTree\".\"ID\" = " . $page2ID)); - Versioned::reading_stage('Stage'); + Versioned::set_stage(Versioned::DRAFT); $requeriedPage = DataObject::get_by_id("Page", $page2ID); $this->assertEquals('Products', $requeriedPage->Title); $this->assertEquals('Page', $requeriedPage->class); @@ -383,12 +383,12 @@ class SiteTreeTest extends SapphireTest { $parentPage->doUnpublish(); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::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'); + Versioned::set_stage(Versioned::DRAFT); Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', true); } @@ -406,11 +406,11 @@ class SiteTreeTest extends SapphireTest { $parentPage = $this->objFromFixture('Page', 'about'); $parentPage->doUnpublish(); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::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'); + Versioned::set_stage(Versioned::DRAFT); Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', true); } @@ -427,11 +427,11 @@ class SiteTreeTest extends SapphireTest { $parentPage = $this->objFromFixture('Page', 'about'); $parentPage->doUnpublish(); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::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'); + Versioned::set_stage(Versioned::DRAFT); } /** @@ -1115,7 +1115,7 @@ class SiteTreeTest extends SapphireTest { $member->Groups()->add($group); // both pages are viewable in stage - Versioned::reading_stage('Stage'); + Versioned::set_stage(Versioned::DRAFT); $about = $this->objFromFixture('Page', 'about'); $staff = $this->objFromFixture('Page', 'staff'); $this->assertFalse($about->isOrphaned()); @@ -1127,16 +1127,16 @@ class SiteTreeTest extends SapphireTest { $staff->publish('Stage', 'Live'); $this->assertFalse($staff->isOrphaned()); $this->assertTrue($staff->canView($member)); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::LIVE); $staff = $this->objFromFixture('Page', 'staff'); // Live copy of page $this->assertTrue($staff->isOrphaned()); // because parent isn't published $this->assertFalse($staff->canView($member)); // Publishing the parent page should restore visibility - Versioned::reading_stage('Stage'); + Versioned::set_stage(Versioned::DRAFT); $about = $this->objFromFixture('Page', 'about'); $about->publish('Stage', 'Live'); - Versioned::reading_stage('Live'); + Versioned::set_stage(Versioned::LIVE); $staff = $this->objFromFixture('Page', 'staff'); $this->assertFalse($staff->isOrphaned()); $this->assertTrue($staff->canView($member)); diff --git a/tests/model/VirtualPageTest.php b/tests/model/VirtualPageTest.php index 04168e05..c507ca8c 100644 --- a/tests/model/VirtualPageTest.php +++ b/tests/model/VirtualPageTest.php @@ -30,28 +30,9 @@ class VirtualPageTest extends FunctionalTest { // Ensure we always have permission to save/publish $this->logInWithPermission("ADMIN"); - $this->origInitiallyCopiedFields = VirtualPage::config()->initially_copied_fields; - Config::inst()->remove('VirtualPage', 'initially_copied_fields'); - VirtualPage::config()->initially_copied_fields = array_merge( - $this->origInitiallyCopiedFields, - array('MyInitiallyCopiedField') - ); - - $this->origNonVirtualField = VirtualPage::config()->non_virtual_fields; - Config::inst()->remove('VirtualPage', 'non_virtual_fields'); - VirtualPage::config()->non_virtual_fields = array_merge( - $this->origNonVirtualField, - array('MyNonVirtualField', 'MySharedNonVirtualField') - ); - } - - public function tearDown() { - parent::tearDown(); - - Config::inst()->remove('VirtualPage', 'initially_copied_fields'); - Config::inst()->remove('VirtualPage', 'non_virtual_fields'); - VirtualPage::config()->initially_copied_fields = $this->origInitiallyCopiedFields; - VirtualPage::config()->non_virtual_fields = $this->origNonVirtualField; + // Add extra fields + Config::inst()->update('VirtualPage', 'initially_copied_fields', array('MyInitiallyCopiedField')); + Config::inst()->update('VirtualPage', 'non_virtual_fields', array('MyNonVirtualField', 'MySharedNonVirtualField')); } /** @@ -98,7 +79,7 @@ class VirtualPageTest extends FunctionalTest { $master->doPublish(); - Versioned::reading_stage("Live"); + Versioned::set_stage(Versioned::LIVE); $vp1 = DataObject::get_by_id("VirtualPage", $this->idFromFixture('VirtualPage', 'vp1')); $vp2 = DataObject::get_by_id("VirtualPage", $this->idFromFixture('VirtualPage', 'vp2')); @@ -111,7 +92,7 @@ class VirtualPageTest extends FunctionalTest { $this->assertEquals("New menutitle", $vp2->MenuTitle); $this->assertEquals("

    New content

    ", $vp1->Content); $this->assertEquals("

    New content

    ", $vp2->Content); - Versioned::reading_stage("Stage"); + Versioned::set_stage(Versioned::DRAFT); } /** @@ -145,23 +126,36 @@ class VirtualPageTest extends FunctionalTest { $p->write(); $p->doPublish(); - // Don't publish this change - published page will still say 'published content' - $p->Content = "draft content"; - $p->write(); - + // Virtual page has this content $vp = new VirtualPage(); $vp->CopyContentFromID = $p->ID; $vp->write(); $vp->doPublish(); + // Don't publish this change - published page will still say 'published content' + $p->Content = "draft content"; + $p->write(); + // The draft content of the virtual page should say 'draft content' - $this->assertEquals('draft content', - DB::query('SELECT "Content" from "SiteTree" WHERE "ID" = ' . $vp->ID)->value()); + /** @var VirtualPage $vpDraft */ + $vpDraft = Versioned::get_by_stage("VirtualPage", Versioned::DRAFT)->byID($vp->ID); + $this->assertEquals('draft content', $vpDraft->CopyContentFrom()->Content); + $this->assertEquals('draft content', $vpDraft->Content); // The published content of the virtual page should say 'published content' - $this->assertEquals('published content', - DB::query('SELECT "Content" from "SiteTree_Live" WHERE "ID" = ' . $vp->ID)->value()); + /** @var VirtualPage $vpLive */ + $vpLive = Versioned::get_by_stage("VirtualPage", Versioned::LIVE)->byID($vp->ID); + $this->assertEquals('published content', $vpLive->CopyContentFrom()->Content); + $this->assertEquals('published content', $vpLive->Content); + + // Publishing the virtualpage should, however, trigger publishing of the live page + $vpDraft->doPublish(); + + // Everything is published live + $vpLive = Versioned::get_by_stage("VirtualPage", Versioned::LIVE)->byID($vp->ID); + $this->assertEquals('draft content', $vpLive->CopyContentFrom()->Content); + $this->assertEquals('draft content', $vpLive->Content); } public function testCantPublishVirtualPagesBeforeTheirSource() { @@ -191,22 +185,36 @@ class VirtualPageTest extends FunctionalTest { $p->Content = "test content"; $p->write(); $p->doPublish(); + $pID = $p->ID; $vp = new VirtualPage(); $vp->CopyContentFromID = $p->ID; $vp->write(); - - // Delete the source page $this->assertTrue($vp->canPublish()); - $this->assertTrue($p->doUnpublish()); + $this->assertTrue($vp->doPublish()); + + // Delete the source page semi-manually, without triggering + // the cascade publish back to the virtual page. + Versioned::set_stage(Versioned::LIVE); + $livePage = Versioned::get_by_stage('SiteTree', Versioned::LIVE)->byID($pID); + $livePage->delete(); + Versioned::set_stage(Versioned::DRAFT); // Confirm that we can unpublish, but not publish + $this->assertFalse($p->IsPublished(), 'Copied page has orphaned the virtual page on live'); + $this->assertTrue($vp->isPublished(), 'Virtual page remains on live'); $this->assertTrue($vp->canUnpublish()); $this->assertFalse($vp->canPublish()); // Confirm that the action really works $this->assertTrue($vp->doUnpublish()); - $this->assertNull(DB::query("SELECT \"ID\" FROM \"SiteTree_Live\" WHERE \"ID\" = $vp->ID")->value()); + $this->assertEquals( + 0, + DB::prepared_query( + "SELECT count(*) FROM \"SiteTree_Live\" WHERE \"ID\" = ?", + array($vp->ID) + )->value() + ); } public function testCanEdit() { @@ -259,7 +267,6 @@ class VirtualPageTest extends FunctionalTest { // VP is still orange after we publish $p->doPublish(); - $this->fixVersionNumberCache($vp); $this->assertTrue($vp->getIsAddedToStage()); // A new VP created after P's initial construction @@ -272,77 +279,28 @@ class VirtualPageTest extends FunctionalTest { $p->Content = "new content"; $p->write(); $p->doPublish(); - $this->fixVersionNumberCache($vp2); $this->assertTrue($vp2->getIsAddedToStage()); // VP is now published $vp->doPublish(); - $this->fixVersionNumberCache($vp); $this->assertTrue($vp->getExistsOnLive()); $this->assertFalse($vp->getIsModifiedOnStage()); - // P edited, VP and P both go green + // P edited, P goes green. Change set interface should indicate to the user that the owned page has + // modifications, although the virtual page record itself will not appear as having pending changes. $p->Content = "third content"; $p->write(); - $this->fixVersionNumberCache($vp, $p); $this->assertTrue($p->getIsModifiedOnStage()); - $this->assertTrue($vp->getIsModifiedOnStage()); + $this->assertFalse($vp->getIsModifiedOnStage()); // Publish, VP goes black $p->doPublish(); - $this->fixVersionNumberCache($vp); $this->assertTrue($vp->getExistsOnLive()); $this->assertFalse($vp->getIsModifiedOnStage()); } - public function testVirtualPagesCreateVersionRecords() { - $source = $this->objFromFixture('Page', 'master'); - $source->Title = "T0"; - $source->write(); - $source->doPublish(); - - // Creating a new VP to ensure that Version #s are out of alignment - $vp = new VirtualPage(); - $vp->CopyContentFromID = $source->ID; - $vp->write(); - - $source->Title = "T1"; - $source->write(); - $source->Title = "T2"; - $source->write(); - - $this->assertEquals($vp->ID, DB::query("SELECT \"RecordID\" FROM \"SiteTree_versions\" - WHERE \"RecordID\" = $vp->ID AND \"Title\" = 'T1'")->value()); - $this->assertEquals($vp->ID, DB::query("SELECT \"RecordID\" FROM \"SiteTree_versions\" - WHERE \"RecordID\" = $vp->ID AND \"Title\" = 'T2'")->value()); - $this->assertEquals($vp->ID, DB::query("SELECT \"RecordID\" FROM \"SiteTree_versions\" - WHERE \"RecordID\" = $vp->ID AND \"Version\" = $vp->Version")->value()); - - $vp->doPublish(); - - // Check that the published content is copied from the published page, with a legal - // version - $liveVersion = DB::query("SELECT \"Version\" FROM \"SiteTree_Live\" WHERE \"ID\" = $vp->ID")->value(); - - $this->assertEquals("T0", DB::query("SELECT \"Title\" FROM \"SiteTree_Live\" - WHERE \"ID\" = $vp->ID")->value()); - - // SiteTree_Live.Version should reference a legal entry in SiteTree_versions for the - // virtual page - $this->assertEquals("T0", DB::query("SELECT \"Title\" FROM \"SiteTree_versions\" - WHERE \"RecordID\" = $vp->ID AND \"Version\" = $liveVersion")->value()); - } - - public function fixVersionNumberCache($page) { - $pages = func_get_args(); - foreach($pages as $p) { - Versioned::prepopulate_versionnumber_cache('SiteTree', 'Stage', array($p->ID)); - Versioned::prepopulate_versionnumber_cache('SiteTree', 'Live', array($p->ID)); - } - } - public function testUnpublishingSourcePageOfAVirtualPageAlsoUnpublishesVirtualPage() { // Create page and virutal page $p = new Page(); @@ -459,7 +417,6 @@ class VirtualPageTest extends FunctionalTest { $virtual->CopyContentFromID = $original->ID; $virtual->write(); - $virtual->copyFrom($original); // Using getField() to avoid side effects from an overloaded __get() $this->assertEquals( 'original', @@ -478,7 +435,6 @@ class VirtualPageTest extends FunctionalTest { $original->MyInitiallyCopiedField = 'changed'; $original->write(); - $virtual->copyFrom($original); $this->assertEquals( 'original', $virtual->MyInitiallyCopiedField, @@ -486,53 +442,6 @@ class VirtualPageTest extends FunctionalTest { ); } - public function testWriteWithoutVersion() { - $original = new SiteTree(); - $original->write(); - // Create a second version (different behaviour), - // as SiteTree->onAfterWrite() checks for Version == 1 - $original->Title = 'prepare'; - $original->write(); - $originalVersion = $original->Version; - - $virtual = new VirtualPage(); - $virtual->CopyContentFromID = $original->ID; - $virtual->write(); - // Create a second version, see above. - $virtual->Title = 'prepare'; - $virtual->write(); - $virtualVersion = $virtual->Version; - - $virtual->Title = 'changed 1'; - $virtual->writeWithoutVersion(); - $this->assertEquals( - $virtual->Version, - $virtualVersion, - 'writeWithoutVersion() on VirtualPage doesnt increment version' - ); - - $original->Title = 'changed 2'; - $original->writeWithoutVersion(); - - DataObject::flush_and_destroy_cache(); - $virtual = DataObject::get_by_id('VirtualPage', $virtual->ID, false); - $this->assertEquals( - $virtual->Version, - $virtualVersion, - 'writeWithoutVersion() on original page doesnt increment version on related VirtualPage' - ); - - $original->Title = 'changed 3'; - $original->write(); - DataObject::flush_and_destroy_cache(); - $virtual = DataObject::get_by_id('VirtualPage', $virtual->ID, false); - $this->assertGreaterThan( - $virtualVersion, - $virtual->Version, - 'write() on original page does increment version on related VirtualPage' - ); - } - public function testCanBeRoot() { $page = new SiteTree(); $page->ParentID = 0; @@ -562,50 +471,16 @@ class VirtualPageTest extends FunctionalTest { if(!$isDetected) $this->fail('Fails validation with $can_be_root=false'); } - public function testPageTypeChangeDoesntKeepOrphanedVirtualPageRecord() { - $page = new SiteTree(); - $page->write(); - $page->publish('Stage', 'Live'); - - $virtual = new VirtualPageTest_VirtualPageSub(); - $virtual->CopyContentFromID = $page->ID; - $virtual->write(); - $virtual->publish('Stage', 'Live'); - - $nonVirtual = $virtual; - $nonVirtual->ClassName = 'VirtualPageTest_ClassA'; - $nonVirtual->write(); // not publishing - - $this->assertNotNull( - DB::query(sprintf('SELECT "ID" FROM "SiteTree" WHERE "ID" = %d', $nonVirtual->ID))->value(), - "Shared base database table entry exists after type change" - ); - $this->assertNull( - DB::query(sprintf('SELECT "ID" FROM "VirtualPage" WHERE "ID" = %d', $nonVirtual->ID))->value(), - "Base database table entry no longer exists after type change" - ); - $this->assertNull( - DB::query(sprintf('SELECT "ID" FROM "VirtualPageTest_VirtualPageSub" WHERE "ID" = %d', $nonVirtual->ID))->value(), - "Sub database table entry no longer exists after type change" - ); - $this->assertNull( - DB::query(sprintf('SELECT "ID" FROM "VirtualPage_Live" WHERE "ID" = %d', $nonVirtual->ID))->value(), - "Base live database table entry no longer exists after type change" - ); - $this->assertNull( - DB::query(sprintf('SELECT "ID" FROM "VirtualPageTest_VirtualPageSub_Live" WHERE "ID" = %d', $nonVirtual->ID))->value(), - "Sub live database table entry no longer exists after type change" - ); - } - public function testPageTypeChangePropagatesToLive() { $page = new SiteTree(); + $page->Title = 'published title'; $page->MySharedNonVirtualField = 'original'; $page->write(); $page->publish('Stage', 'Live'); $virtual = new VirtualPageTest_VirtualPageSub(); $virtual->CopyContentFromID = $page->ID; + $virtual->MySharedNonVirtualField = 'virtual published field'; $virtual->write(); $virtual->publish('Stage', 'Live'); @@ -614,28 +489,38 @@ class VirtualPageTest extends FunctionalTest { // but we want to test that it gets copied on class name change instead $page->write(); + $nonVirtual = $virtual; $nonVirtual->ClassName = 'VirtualPageTest_ClassA'; $nonVirtual->MySharedNonVirtualField = 'changed on new type'; $nonVirtual->write(); // not publishing the page type change here - $this->assertEquals('original', $nonVirtual->Title, + // Stage record is changed to the new type and no longer acts as a virtual page + $nonVirtualStage = Versioned::get_one_by_stage('SiteTree', 'Stage', '"SiteTree"."ID" = ' . $nonVirtual->ID, false); + $this->assertNotNull($nonVirtualStage); + $this->assertEquals('VirtualPageTest_ClassA', $nonVirtualStage->ClassName); + $this->assertEquals('changed on new type', $nonVirtualStage->MySharedNonVirtualField); + $this->assertEquals('original', $nonVirtualStage->Title, 'Copies virtual fields from original draft into new instance on type change ' ); - $nonVirtualLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree_Live"."ID" = ' . $nonVirtual->ID); - $this->assertNotNull($nonVirtualLive); - $this->assertEquals('VirtualPageTest_ClassA', $nonVirtualLive->ClassName); - $this->assertEquals('changed on new type', $nonVirtualLive->MySharedNonVirtualField); + // Virtual page on live keeps working as it should + $virtualLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree_Live"."ID" = ' . $virtual->ID, false); + $this->assertNotNull($virtualLive); + $this->assertEquals('VirtualPageTest_VirtualPageSub', $virtualLive->ClassName); + $this->assertEquals('virtual published field', $virtualLive->MySharedNonVirtualField); + $this->assertEquals('published title', $virtualLive->Title); + // Change live page + $page->Title = 'title changed on original'; $page->MySharedNonVirtualField = 'changed only on original'; $page->write(); $page->publish('Stage', 'Live'); - $nonVirtualLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree_Live"."ID" = ' . $nonVirtual->ID, false); - $this->assertEquals('changed on new type', $nonVirtualLive->MySharedNonVirtualField, - 'No field copying from previous original after page type changed' - ); + // Virtual page only notices changes to virtualised fields (Title) + $virtualLive = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree_Live"."ID" = ' . $virtual->ID, false); + $this->assertEquals('virtual published field', $virtualLive->MySharedNonVirtualField); + $this->assertEquals('title changed on original', $virtualLive->Title); } public function testVirtualPageFindsCorrectCasting() {