From f909aad7b44df946f3943d580fc18132fb7d0795 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 7 Aug 2012 17:37:44 +0200 Subject: [PATCH 1/8] API CHANGE Removed Subsite_Template subclass, as it unnecessarily duplicates the Subsite->duplicate() logic in Subsite_Template->createInstance(). It also arbitrarily limits duplication of subsites to templates. --- code/SiteTreeSubsites.php | 29 +------------ code/Subsite.php | 68 +++---------------------------- code/SubsiteAdmin.php | 47 +++++++-------------- lang/en_US.php | 8 ++-- tests/SiteTreeSubsitesTest.php | 8 ++-- tests/SubsiteAdminTest.php | 10 ++--- tests/SubsiteTest.php | 21 ++++------ tests/SubsiteTest.yml | 38 ++++++++--------- tests/SubsitesVirtualPageTest.php | 18 ++++---- 9 files changed, 69 insertions(+), 178 deletions(-) diff --git a/code/SiteTreeSubsites.php b/code/SiteTreeSubsites.php index e9e5e5d..45f77b8 100644 --- a/code/SiteTreeSubsites.php +++ b/code/SiteTreeSubsites.php @@ -4,29 +4,7 @@ * Extension for the SiteTree object to add subsites support */ class SiteTreeSubsites extends SiteTreeDecorator { - static $template_variables = array( - '((Company Name))' => 'Title' - ); - - static $template_fields = array( - "URLSegment", - "Title", - "MenuTitle", - "Content", - "MetaTitle", - "MetaDescription", - "MetaKeywords", - ); - /** - * Set the fields that will be copied from the template. - * Note that ParentID and Sort are implied. - */ - static function set_template_fields($fieldList) { - self::$template_fields = $fieldList; - } - - function extraStatics() { if(!method_exists('DataObjectDecorator', 'load_extra_statics') && $this->owner->class != 'SiteTree') return null; return array( @@ -224,9 +202,8 @@ class SiteTreeSubsites extends SiteTreeDecorator { /** * Create a duplicate of this page and save it to another subsite * @param $subsiteID int|Subsite The Subsite to copy to, or its ID - * @param $isTemplate boolean If this is true, then the current page will be treated as the template, and MasterPageID will be set */ - public function duplicateToSubsite($subsiteID = null, $isTemplate = true) { + public function duplicateToSubsite($subsiteID = null) { if(is_object($subsiteID)) { $subsite = $subsiteID; $subsiteID = $subsite->ID; @@ -237,9 +214,7 @@ class SiteTreeSubsites extends SiteTreeDecorator { $page->CheckedPublicationDifferences = $page->AddedToStage = true; $subsiteID = ($subsiteID ? $subsiteID : Subsite::currentSubsiteID()); $page->SubsiteID = $subsiteID; - - if($isTemplate) $page->MasterPageID = $this->owner->ID; - + $page->MasterPageID = $this->owner->ID; $page->write(); return $page; diff --git a/code/Subsite.php b/code/Subsite.php index 7d5e3fa..617a40e 100644 --- a/code/Subsite.php +++ b/code/Subsite.php @@ -429,13 +429,13 @@ JS; * Duplicate this subsite */ function duplicate() { - $newTemplate = parent::duplicate(); + $duplicate = parent::duplicate(); $oldSubsiteID = Session::get('SubsiteID'); self::changeSubsite($this->ID); /* - * Copy data from this template to the given subsite. Does this using an iterative depth-first search. + * Copy data from this object to the given subsite. Does this using an iterative depth-first search. * This will make sure that the new parents on the new subsite are correct, and there are no funny * issues with having to check whether or not the new parents have been added to the site tree * when a page, etc, is duplicated @@ -443,12 +443,11 @@ JS; $stack = array(array(0,0)); while(count($stack) > 0) { list($sourceParentID, $destParentID) = array_pop($stack); - $children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", ''); if($children) { foreach($children as $child) { - $childClone = $child->duplicateToSubsite($newTemplate, false); + $childClone = $child->duplicateToSubsite($duplicate, false); $childClone->ParentID = $destParentID; $childClone->writeToStage('Stage'); $childClone->publish('Stage', 'Live'); @@ -459,7 +458,7 @@ JS; self::changeSubsite($oldSubsiteID); - return $newTemplate; + return $duplicate; } @@ -467,7 +466,7 @@ JS; * Return the subsites that the current user can access. * Look for one of the given permission codes on the site. * - * Sites and Templates will only be included if they have a Title + * Sites will only be included if they have a Title * * @param $permCode array|string Either a single permission code or an array of permission codes. * @param $includeMainSite If true, the main site will be included if appropriate. @@ -491,8 +490,6 @@ JS; return self::$_cache_accessible_sites[$cacheKey]; } - $templateClassList = "'" . implode("', '", ClassInfo::subclassesFor("Subsite_Template")) . "'"; - $subsites = DataObject::get( 'Subsite', "\"Subsite\".\"Title\" != ''", @@ -634,58 +631,3 @@ JS; self::$_cache_accessible_sites = array(); } } - -/** - * An instance of subsite that can be duplicated to provide a quick way to create new subsites. - * - * @package subsites - */ -class Subsite_Template extends Subsite { - /** - * Create an instance of this template, with the given title & domain - */ - function createInstance($title, $domain = null) { - $intranet = Object::create('Subsite'); - $intranet->Title = $title; - $intranet->TemplateID = $this->ID; - $intranet->write(); - - if($domain) { - $intranetDomain = Object::create('SubsiteDomain'); - $intranetDomain->SubsiteID = $intranet->ID; - $intranetDomain->Domain = $domain; - $intranetDomain->write(); - } - - $oldSubsiteID = Session::get('SubsiteID'); - self::changeSubsite($this->ID); - - /* - * Copy site content from this template to the given subsite. Does this using an iterative depth-first search. - * This will make sure that the new parents on the new subsite are correct, and there are no funny - * issues with having to check whether or not the new parents have been added to the site tree - * when a page, etc, is duplicated - */ - $stack = array(array(0,0)); - while(count($stack) > 0) { - list($sourceParentID, $destParentID) = array_pop($stack); - - $children = Versioned::get_by_stage('SiteTree', 'Live', "\"ParentID\" = $sourceParentID", ''); - - if($children) { - foreach($children as $child) { - $childClone = $child->duplicateToSubsite($intranet); - $childClone->ParentID = $destParentID; - $childClone->writeToStage('Stage'); - $childClone->publish('Stage', 'Live'); - array_push($stack, array($child->ID, $childClone->ID)); - } - } - } - - self::changeSubsite($oldSubsiteID); - - return $intranet; - } -} -?> diff --git a/code/SubsiteAdmin.php b/code/SubsiteAdmin.php index e1ffa29..3234609 100644 --- a/code/SubsiteAdmin.php +++ b/code/SubsiteAdmin.php @@ -18,49 +18,30 @@ class SubsiteAdmin_CollectionController extends ModelAdmin_CollectionController function AddForm() { $form = parent::AddForm(); - $templates = DataObject::get('Subsite_Template', '', 'Title'); - $templateArray = array('' => "(No template)"); - if($templates) { - $templateArray = $templateArray + $templates->map('ID', 'Title'); + $subsites = DataObject::get('Subsite', '', 'Title'); + $subsiteMap = array('' => "(No template)", -1 => '(Main Site)'); + if($subsites) { + $subsiteMap = $subsiteMap + $subsites->map('ID', 'Title'); } $form->Fields()->addFieldsToTab('Root.Configuration', array( - new DropdownField('Type', 'Type', array( - 'subsite' => 'New site', - 'template' => 'New template', - )), - new DropdownField('TemplateID', 'Copy structure from:', $templateArray) + new DropdownField('TemplateID', 'Copy structure from:', $subsiteMap) )); return $form; } function doCreate($data, $form, $request) { - if(isset($data['TemplateID']) && $data['TemplateID']) { - $template = DataObject::get_by_id('Subsite_Template', $data['TemplateID']); + if(isset($data['TemplateID']) && $data['TemplateID'] == -1) { + // Copy from main site. Hacky as it relies on ID=0 + // in order to query the main site (which is technically not contained in a Subsite record) + $mainsite = new Subsite(); + $subsite = $mainsite->duplicate(); + } elseif(isset($data['TemplateID']) && $data['TemplateID']) { + $template = DataObject::get_by_id('Subsite', $data['TemplateID']); + $subsite = $template->duplicate(); } else { - $template = null; - } - - // Create subsite from existing template - switch($data['Type']) { - case 'template': - if($template) $subsite = $template->duplicate(); - else { - $subsite = new Subsite_Template(); - $subsite->write(); - } - break; - - case 'subsite': - default: - if($template) $subsite = $template->createInstance($data['Title']); - else { - $subsite = new Subsite(); - $subsite->Title = $data['Title']; - $subsite->write(); - } - break; + $subsite = new Subsite(); } $form->dataFieldByName('Domains')->setExtraData(array( diff --git a/lang/en_US.php b/lang/en_US.php index ea3d7b9..8c30acf 100644 --- a/lang/en_US.php +++ b/lang/en_US.php @@ -53,13 +53,13 @@ $lang['en_US']['SubsiteDomain']['SINGULARNAME'] = array( 50, 'Singular name of the object, used in dropdowns and to generally identify a single object in the interface' ); -$lang['en_US']['Subsite_Template']['PLURALNAME'] = array( - 'Subsite Templats', +$lang['en_US']['Subsite']['PLURALNAME'] = array( + 'Subsites', 50, 'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface' ); -$lang['en_US']['Subsite_Template']['SINGULARNAME'] = array( - 'Subsite Template', +$lang['en_US']['Subsite']['SINGULARNAME'] = array( + 'Subsite', 50, 'Singular name of the object, used in dropdowns and to generally identify a single object in the interface' ); diff --git a/tests/SiteTreeSubsitesTest.php b/tests/SiteTreeSubsitesTest.php index d38a536..0c126a3 100644 --- a/tests/SiteTreeSubsitesTest.php +++ b/tests/SiteTreeSubsitesTest.php @@ -10,8 +10,8 @@ class SiteTreeSubsitesTest extends SapphireTest { ); function testPagesInDifferentSubsitesCanShareURLSegment() { - $subsiteMain = $this->objFromFixture('Subsite_Template', 'main'); - $subsite1 = $this->objFromFixture('Subsite_Template', 'subsite1'); + $subsiteMain = $this->objFromFixture('Subsite', 'main'); + $subsite1 = $this->objFromFixture('Subsite', 'subsite1'); $pageMain = new SiteTree(); $pageMain->URLSegment = 'testpage'; @@ -90,8 +90,8 @@ class SiteTreeSubsitesTest extends SapphireTest { $mainpage = $this->objFromFixture('SiteTree', 'home'); $subsite1page = $this->objFromFixture('SiteTree', 'subsite1_home'); $subsite2page = $this->objFromFixture('SiteTree', 'subsite2_home'); - $subsite1 = $this->objFromFixture('Subsite_Template', 'subsite1'); - $subsite2 = $this->objFromFixture('Subsite_Template', 'subsite2'); + $subsite1 = $this->objFromFixture('Subsite', 'subsite1'); + $subsite2 = $this->objFromFixture('Subsite', 'subsite2'); // Cant pass member as arguments to canEdit() because of GroupSubsites Session::set("loggedInAs", $admin->ID); diff --git a/tests/SubsiteAdminTest.php b/tests/SubsiteAdminTest.php index 2988540..fb3f695 100644 --- a/tests/SubsiteAdminTest.php +++ b/tests/SubsiteAdminTest.php @@ -64,12 +64,12 @@ class SubsiteAdminTest extends SapphireTest { $form = $cont->AddSubsiteForm(); $source = $form->dataFieldByName('TemplateID')->getSource(); - $templateIDs = $this->allFixtureIDs('Subsite_Template'); + $templateIDs = $this->allFixtureIDs('Subsite'); foreach($templateIDs as $templateID) { $this->assertArrayHasKey($templateID, $source); } - $templateObj = $this->objFromFixture('Subsite_Template','main'); + $templateObj = $this->objFromFixture('Subsite','main'); $this->assertEquals($templateObj->Title, $source[$templateObj->ID], "Template dropdown isn't listing Title values"); $response = $form->testSubmission('addintranet', array( @@ -104,9 +104,9 @@ class SubsiteAdminTest extends SapphireTest { $this->assertTrue($subsite->adminSearchFields() instanceof FieldSet); $this->assertArrayHasKey(0, $ids, "Main site accessible"); - $this->assertArrayHasKey($this->idFromFixture('Subsite_Template','main'), $ids, "Site with no groups inaccesible"); - $this->assertArrayHasKey($this->idFromFixture('Subsite_Template','subsite1'), $ids, "Subsite1 Template inaccessible"); - $this->assertArrayHasKey($this->idFromFixture('Subsite_Template','subsite2'), $ids, "Subsite2 Template inaccessible"); + $this->assertArrayHasKey($this->idFromFixture('Subsite','main'), $ids, "Site with no groups inaccesible"); + $this->assertArrayHasKey($this->idFromFixture('Subsite','subsite1'), $ids, "Subsite1 Template inaccessible"); + $this->assertArrayHasKey($this->idFromFixture('Subsite','subsite2'), $ids, "Subsite2 Template inaccessible"); } diff --git a/tests/SubsiteTest.php b/tests/SubsiteTest.php index 45d404d..3a93e5b 100644 --- a/tests/SubsiteTest.php +++ b/tests/SubsiteTest.php @@ -24,12 +24,12 @@ class SubsiteTest extends SapphireTest { Subsite::$write_hostmap = false; // Create the instance - $template = $this->objFromFixture('Subsite_Template', 'main'); + $template = $this->objFromFixture('Subsite', 'main'); // Test that changeSubsite is working Subsite::changeSubsite($template->ID); - - $tmplHome = DataObject::get_one('SiteTree', "\"URLSegment\" = 'home'"); + $tmplStaff = $this->objFromFixture('Page','staff'); + $tmplHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'"); // Publish all the pages in the template, testing that DataObject::get only returns pages from the chosen subsite $pages = DataObject::get("SiteTree"); @@ -38,20 +38,16 @@ class SubsiteTest extends SapphireTest { $this->assertEquals($template->ID, $page->SubsiteID); $page->publish('Stage', 'Live'); } - + // Create a new site - $subsite = $template->createInstance('My Site', 'something.test.com'); + $subsite = $template->duplicate(); // Check title - $this->assertEquals($subsite->Title, 'My Site'); + $this->assertEquals($subsite->Title, $template->Title); - // Check that domain generation is working - $this->assertEquals('something.test.com', $subsite->domain()); - // Another test that changeSubsite is working $subsite->activate(); - - $siteHome = DataObject::get_one('SiteTree', "\"URLSegment\" = 'home'"); + $siteHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'"); $this->assertNotNull($siteHome); $this->assertEquals($subsite->ID, $siteHome->SubsiteID, 'createInstance() copies existing pages retaining the same URLSegment' @@ -59,8 +55,7 @@ class SubsiteTest extends SapphireTest { $this->assertEquals($siteHome->MasterPageID, $tmplHome->ID, 'Check master page value'); // Check linking of child pages - $tmplStaff = $this->objFromFixture('SiteTree','staff'); - $siteStaff = DataObject::get_one('SiteTree', "\"URLSegment\" = '" . Convert::raw2sql($tmplStaff->URLSegment) . "'"); + $siteStaff = DataObject::get_one('Page', "\"URLSegment\" = '" . Convert::raw2sql($tmplStaff->URLSegment) . "'"); $this->assertEquals($siteStaff->MasterPageID, $tmplStaff->ID); Subsite::changeSubsite(0); diff --git a/tests/SubsiteTest.yml b/tests/SubsiteTest.yml index b2bd257..017cfd8 100644 --- a/tests/SubsiteTest.yml +++ b/tests/SubsiteTest.yml @@ -1,11 +1,10 @@ -Subsite_Template: +Subsite: main: Title: Template subsite1: Title: Subsite1 Template subsite2: Title: Subsite2 Template -Subsite: domaintest1: Title: Test 1 domaintest2: @@ -14,10 +13,10 @@ Subsite: Title: Test 3 SubsiteDomain: subsite1: - SubsiteID: =>Subsite_Template.subsite1 + SubsiteID: =>Subsite.subsite1 Domain: subsite1.* subsite2: - SubsiteID: =>Subsite_Template.subsite2 + SubsiteID: =>Subsite.subsite2 Domain: subsite2.* dt1a: SubsiteID: =>Subsite.domaintest1 @@ -37,40 +36,39 @@ SubsiteDomain: SubsiteID: =>Subsite.domaintest3 Domain: three.* IsPrimary: 1 - -SiteTree: +Page: home: Title: Home - SubsiteID: =>Subsite_Template.main + SubsiteID: =>Subsite.main about: Title: About - SubsiteID: =>Subsite_Template.main + SubsiteID: =>Subsite.main linky: Title: Linky MetaTitle: Linky - SubsiteID: =>Subsite_Template.main + SubsiteID: =>Subsite.main staff: Title: Staff - ParentID: =>SiteTree.about - SubsiteID: =>Subsite_Template.main + ParentID: =>Page.about + SubsiteID: =>Subsite.main contact: Title: Contact Us - SubsiteID: =>Subsite_Template.main + SubsiteID: =>Subsite.main importantpage: Title: Important Page - SubsiteID: =>Subsite_Template.main + SubsiteID: =>Subsite.main subsite1_home: Title: Home (Subsite 1) - SubsiteID: =>Subsite_Template.subsite1 + SubsiteID: =>Subsite.subsite1 subsite1_contactus: Title: Contact Us (Subsite 1) - SubsiteID: =>Subsite_Template.subsite1 + SubsiteID: =>Subsite.subsite1 subsite2_home: Title: Home (Subsite 2) - SubsiteID: =>Subsite_Template.subsite2 + SubsiteID: =>Subsite.subsite2 subsite2_contactus: Title: Contact Us (Subsite 2) - SubsiteID: =>Subsite_Template.subsite2 + SubsiteID: =>Subsite.subsite2 Group: admin: @@ -81,17 +79,17 @@ Group: Title: subsite1_group Code: subsite1_group AccessAllSubsites: 0 - Subsites: =>Subsite_Template.subsite1 + Subsites: =>Subsite.subsite1 subsite2_group: Title: subsite2_group Code: subsite2_group AccessAllSubsites: 0 - Subsites: =>Subsite_Template.subsite2 + Subsites: =>Subsite.subsite2 subsite1admins: Title: subsite1admins Code: subsite1admins AccessAllSubsites: 0 - Subsites: =>Subsite_Template.subsite1 + Subsites: =>Subsite.subsite1 allsubsitesauthors: Title: allsubsitesauthors Code: allsubsitesauthors diff --git a/tests/SubsitesVirtualPageTest.php b/tests/SubsitesVirtualPageTest.php index 1e7bfbb..9b632fe 100644 --- a/tests/SubsitesVirtualPageTest.php +++ b/tests/SubsitesVirtualPageTest.php @@ -30,7 +30,7 @@ class SubsitesVirtualPageTest extends SapphireTest { function testVirtualPageFromAnotherSubsite() { Subsite::$write_hostmap = false; - $subsite = $this->objFromFixture('Subsite_Template', 'subsite2'); + $subsite = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($subsite->ID); Subsite::$disable_subsite_filter = false; @@ -55,7 +55,7 @@ class SubsitesVirtualPageTest extends SapphireTest { function testCustomMetadata() { Subsite::$write_hostmap = false; - $subsite = $this->objFromFixture('Subsite_Template', 'main'); + $subsite = $this->objFromFixture('Subsite', 'main'); Subsite::changeSubsite($subsite->ID); @@ -172,19 +172,19 @@ class SubsitesVirtualPageTest extends SapphireTest { StaticPublisher::$disable_realtime = true; // Go to main site, get parent page - $subsite = $this->objFromFixture('Subsite_Template', 'main'); + $subsite = $this->objFromFixture('Subsite', 'main'); Subsite::changeSubsite($subsite->ID); $page = $this->objFromFixture('SiteTree', 'importantpage'); // Create two SVPs on other subsites - $subsite = $this->objFromFixture('Subsite_Template', 'subsite1'); + $subsite = $this->objFromFixture('Subsite', 'subsite1'); Subsite::changeSubsite($subsite->ID); $vp1 = new SubsitesVirtualPage(); $vp1->CopyContentFromID = $page->ID; $vp1->write(); $vp1->doPublish(); - $subsite = $this->objFromFixture('Subsite_Template', 'subsite2'); + $subsite = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($subsite->ID); $vp2 = new SubsitesVirtualPage(); $vp2->CopyContentFromID = $page->ID; @@ -192,7 +192,7 @@ class SubsitesVirtualPageTest extends SapphireTest { $vp2->doPublish(); // Switch back to main site, unpublish source - $subsite = $this->objFromFixture('Subsite_Template', 'main'); + $subsite = $this->objFromFixture('Subsite', 'main'); Subsite::changeSubsite($subsite->ID); $page = $this->objFromFixture('SiteTree', 'importantpage'); $page->doUnpublish(); @@ -201,7 +201,7 @@ class SubsitesVirtualPageTest extends SapphireTest { $onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp1->ID); $this->assertFalse($onLive, 'SVP has been removed from live'); - $subsite = $this->objFromFixture('Subsite_Template', 'subsite2'); + $subsite = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($vp2->SubsiteID); $onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp2->ID); $this->assertFalse($onLive, 'SVP has been removed from live'); @@ -213,8 +213,8 @@ class SubsitesVirtualPageTest extends SapphireTest { */ function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite() { Subsite::$write_hostmap = false; - $subsite1 = $this->objFromFixture('Subsite_Template', 'subsite1'); - $subsite2 = $this->objFromFixture('Subsite_Template', 'subsite2'); + $subsite1 = $this->objFromFixture('Subsite', 'subsite1'); + $subsite2 = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($subsite1->ID); $subsite1Page = $this->objFromFixture('SiteTree', 'subsite1_contactus'); From c952db1cb1fe1bdbc138e75851c060dd8e01e722 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 10 Aug 2012 11:13:43 +0200 Subject: [PATCH 2/8] SubsiteCopyPagesTask --- code/tasks/SubsiteCopyPagesTask.php | 67 +++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 code/tasks/SubsiteCopyPagesTask.php diff --git a/code/tasks/SubsiteCopyPagesTask.php b/code/tasks/SubsiteCopyPagesTask.php new file mode 100644 index 0000000..8448bec --- /dev/null +++ b/code/tasks/SubsiteCopyPagesTask.php @@ -0,0 +1,67 @@ + to= + */ +class SubsiteCopyPagesTask extends BuildTask { + + protected $title = 'Copy pages to different subsite'; + + protected $description = ''; + + function run($request) { + $subsiteFromId = $request->getVar('from'); + if(!is_numeric($subsiteFromId)) throw new InvalidArgumentException('Missing "from" parameter'); + $subsiteFrom = DataObject::get_by_id('Subsite', $subsiteFromId); + if(!$subsiteFrom) throw new InvalidArgumentException('Subsite not found'); + + $subsiteToId = $request->getVar('to'); + if(!is_numeric($subsiteToId)) throw new InvalidArgumentException('Missing "to" parameter'); + $subsiteTo = DataObject::get_by_id('Subsite', $subsiteToId); + if(!$subsiteTo) throw new InvalidArgumentException('Subsite not found'); + + $useVirtualPages = (bool)$request->getVar('virtual'); + + Subsite::changeSubsite($subsiteFrom); + + // Copy data from this template to the given subsite. Does this using an iterative depth-first search. + // This will make sure that the new parents on the new subsite are correct, and there are no funny + // issues with having to check whether or not the new parents have been added to the site tree + // when a page, etc, is duplicated + $stack = array(array(0,0)); + while(count($stack) > 0) { + list($sourceParentID, $destParentID) = array_pop($stack); + + $children = Versioned::get_by_stage('SiteTree', 'Live', "\"ParentID\" = $sourceParentID", ''); + + if($children) { + foreach($children as $child) { + if($useVirtualPages) { + $childClone = new SubsitesVirtualPage(); + $childClone->writeToStage('Stage'); + $childClone->CopyContentFromID = $child->ID; + $childClone->SubsiteID = $subsiteTo->ID; + } else { + $childClone = $child->duplicateToSubsite($subsiteTo->ID, true); + } + + $childClone->ParentID = $destParentID; + $childClone->writeToStage('Stage'); + $childClone->publish('Stage', 'Live'); + array_push($stack, array($child->ID, $childClone->ID)); + + $this->log(sprintf('Copied "%s" (#%d, %s)', $child->Title, $child->ID, $child->Link())); + } + } + + unset($children); + } + } + + function log($msg) { + echo $msg . "\n"; + } +} \ No newline at end of file From 25f83daf0ec9a61e85c68ef8b76474d5a7b5c42a Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 10 Aug 2012 11:23:19 +0200 Subject: [PATCH 3/8] NEW Copy page to different subsite, select MasterPageID This is also the only UI-facing way to set a master page after the initial copy action when creating a new subsite. Shows "edit" link when master page is already set. --- code/LeftAndMainSubsites.php | 15 ++++++--- code/SiteTreeSubsites.php | 64 +++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/code/LeftAndMainSubsites.php b/code/LeftAndMainSubsites.php index 73c0bda..a1cf504 100644 --- a/code/LeftAndMainSubsites.php +++ b/code/LeftAndMainSubsites.php @@ -6,6 +6,8 @@ */ class LeftAndMainSubsites extends Extension { + static $allowed_actions = array('CopyToSubsite'); + function init() { Requirements::css('subsites/css/LeftAndMain_Subsites.css'); Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js'); @@ -21,7 +23,7 @@ class LeftAndMainSubsites extends Extension { Subsite::changeSubsite($_REQUEST['SubsiteID']); } } - + /** * Set the title of the CMS tree */ @@ -164,8 +166,11 @@ class LeftAndMainSubsites extends Extension { FormResponse::status_message('Saved, please update related pages.', 'good'); } } -} - - -?> + function copytosubsite($data, $form) { + $page = DataObject::get_by_id('SiteTree', $data['ID']); + $subsite = DataObject::get_by_id('Subsite', $data['CopyToSubsiteID']); + $newPage = $page->duplicateToSubsite($subsite->ID, true); + return $this->owner->redirect(Controller::join_links($this->owner->Link('show'), $newPage->ID)); + } +} \ No newline at end of file diff --git a/code/SiteTreeSubsites.php b/code/SiteTreeSubsites.php index 45f77b8..14d1e60 100644 --- a/code/SiteTreeSubsites.php +++ b/code/SiteTreeSubsites.php @@ -66,7 +66,69 @@ class SiteTreeSubsites extends SiteTreeDecorator { } function updateCMSFields(&$fields) { - if($this->owner->MasterPageID) $fields->insertFirst(new HeaderField('This page\'s content is copied from a master page: ' . $this->owner->MasterPage()->Title, 2)); + $subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain"); + $subsitesMap = array(); + if($subsites && $subsites->Count()) { + $subsitesMap = $subsites->toDropdownMap('ID', 'Title'); + unset($subsitesMap[$this->owner->SubsiteID]); + } + + // Master page notice + if($this->owner->MasterPageID) { + $masterPage = $this->owner->MasterPage(); + $masterNoteField = new LiteralField( + 'MasterLink', + sprintf( + _t( + 'SiteTreeSubsites.MasterLinkNote', + '

This page\'s content is copied from the %s master page (edit)

' + ), + $masterPage->AbsoluteLink(), + $masterPage->Title, + Controller::join_links( + singleton('CMSMain')->Link('show'), + $masterPage->ID + ) + ) + ); + $fields->addFieldToTab('Root.Content.Main',$masterNoteField); + } + + // Master page edit field (only allowed from default subsite to avoid inconsistent relationships) + $isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite; + if($isDefaultSubsite && $subsitesMap) { + $fields->addFieldToTab( + 'Root.Content.Main', + new DropdownField( + "CopyToSubsiteID", + _t('SiteTreeSubsites.CopyToSubsite', "Copy page to subsite"), + $subsitesMap, + '' + ) + ); + $fields->addFieldToTab( + 'Root.Content.Main', + $copyAction = new InlineFormAction( + "copytosubsite", + _t('SiteTreeSubsites.CopyAction', "Copy") + ) + ); + $copyAction->includeDefaultJS(false); + } else { + $defaultSubsite = DataObject::get_one('Subsite', '"DefaultSite" = 1'); + if($defaultSubsite) { + $fields->addFieldToTab('Root.Content.Main', + $masterPageField = new SubsitesTreeDropdownField( + "MasterPageID", + _t('VirtualPage.MasterPage', "Master page"), + "SiteTree", + "ID", + "MenuTitle" + ) + ); + $masterPageField->setSubsiteID($defaultSubsite->ID); + } + } // replace readonly link prefix $subsite = $this->owner->Subsite(); From 18bcda48d356de98486a02f79892d79756b48836 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 14 Aug 2012 18:11:37 +0200 Subject: [PATCH 4/8] BUG Allow usage of SubsiteTreeDropdownField when SubsiteID is set in PHP, not through CopyContentFromID dropdown --- javascript/SubsitesTreeDropdownField.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/SubsitesTreeDropdownField.js b/javascript/SubsitesTreeDropdownField.js index 1b41163..55234ea 100644 --- a/javascript/SubsitesTreeDropdownField.js +++ b/javascript/SubsitesTreeDropdownField.js @@ -3,6 +3,8 @@ SubsitesTreeDropdownField.prototype = { subsiteID: function() { var subsiteSel = $$('#CopyContentFromID_SubsiteID select')[0]; + if(!subsiteSel) return; + subsiteSel.onchange = (function() { this.createTreeNode(true); this.ajaxGetTree((function(response) { From 172752a9f41c64bf2c519887b5e3d0e7f0d3d2b7 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 15 Aug 2012 11:11:22 +0200 Subject: [PATCH 5/8] NEW Access to non-public subsites for logged-in users Also added caching subsite domain mapping --- README.md | 21 +++++++++++++++++ code/LeftAndMainSubsites.php | 2 +- code/Subsite.php | 45 +++++++++++++++++++++++++----------- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 424b2c1..ef948a7 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,27 @@ Not all themes might be suitable or adapted for all subsites. You can optionally :::php Subsite::set_allowed_themes(array('blackcandy','mytheme')); +### Public display of a subsite + +By default, each subsite is available to the public (= not logged-in), +provided a correct host mapping is set up. A subsite can be marked as non-public +in its settings, in which case it only shows if a user with CMS permissions is logged in. +This is useful to create and check subsites on a live system before publishing them. + +Please note that you need to filter for this manually in your own queries: + + $publicSubsites = DataObject::get( + 'Subsite', + Subsite::$check_is_public ? '"IsPublic"=1' : ''; + ); + +To ensure the logged-in status of a member is carried across to subdomains, +you also need to configure PHP session cookies to be set +for all subdomains: + + // Example matching subsite1.example.org and www.example.org + Session::set_cookie_domain('.example.org'); + ## Screenshots ![](docs/en/_images/subsites-module-adminscreenshot-new.png) diff --git a/code/LeftAndMainSubsites.php b/code/LeftAndMainSubsites.php index a1cf504..0545221 100644 --- a/code/LeftAndMainSubsites.php +++ b/code/LeftAndMainSubsites.php @@ -53,7 +53,7 @@ class LeftAndMainSubsites extends Extension { case "CMSMain": // If there's a default site then main site has no meaning - $showMainSite = !DataObject::get_one('Subsite',"\"DefaultSite\"=1 AND \"IsPublic\"=1"); + $showMainSite = !DataObject::get_one('Subsite',"\"DefaultSite\"=1"); $subsites = Subsite::accessible_sites($accessPerm, $showMainSite); break; diff --git a/code/Subsite.php b/code/Subsite.php index 617a40e..72c96ed 100644 --- a/code/Subsite.php +++ b/code/Subsite.php @@ -76,6 +76,8 @@ class Subsite extends DataObject implements PermissionProvider { */ private static $_cache_accessible_sites = array(); + private static $_cache_subsite_for_domain = array(); + /** * @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites. * Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder @@ -90,6 +92,11 @@ class Subsite extends DataObject implements PermissionProvider { */ static $strict_subdomain_matching = false; + /** + * @var boolean Respects the IsPublic flag when retrieving subsites + */ + static $check_is_public = true; + static function set_allowed_domains($domain){ user_error('Subsite::set_allowed_domains() is deprecated; it is no longer necessary ' . 'because users can now enter any domain name', E_USER_NOTICE); @@ -334,15 +341,22 @@ JS; * @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used. * @return int Subsite ID */ - static function getSubsiteIDForDomain($host = null, $returnMainIfNotFound = true) { + static function getSubsiteIDForDomain($host = null, $checkPermissions = true) { if($host == null) $host = $_SERVER['HTTP_HOST']; - - if(!Subsite::$strict_subdomain_matching) $host = preg_replace('/^www\./', '', $host); - $SQL_host = Convert::raw2sql($host); - $matchingDomains = DataObject::get("SubsiteDomain", "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')", - "\"IsPrimary\" DESC", "INNER JOIN \"Subsite\" ON \"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND - \"Subsite\".\"IsPublic\"=1"); + if(!Subsite::$strict_subdomain_matching) $host = preg_replace('/^www\./', '', $host); + + $cacheKey = implode('_', array($host, Member::currentUserID(), Subsite::$check_is_public)); + if(isset(self::$_cache_subsite_for_domain[$cacheKey])) return self::$_cache_subsite_for_domain[$cacheKey]; + + $SQL_host = Convert::raw2sql($host); + $joinFilter = self::$check_is_public ? "AND \"Subsite\".\"IsPublic\"=1" : ''; + $matchingDomains = DataObject::get( + "SubsiteDomain", + "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')", + "\"IsPrimary\" DESC", + "INNER JOIN \"Subsite\" ON \"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" $joinFilter" + ); if($matchingDomains) { $subsiteIDs = array_unique($matchingDomains->column('SubsiteID')); @@ -355,16 +369,18 @@ JS; )); } - return $subsiteIDs[0]; + $subsiteID = $subsiteIDs[0]; + } else if($default = DataObject::get_one('Subsite', "\"DefaultSite\" = 1")) { + // Check for a 'default' subsite + $subsiteID = $default->ID; + } else { + // Default subsite id = 0, the main site + $subsiteID = 0; } - // Check for a 'default' subsite - if ($default = DataObject::get_one('Subsite', "\"DefaultSite\" = 1")) { - return $default->ID; - } + self::$_cache_subsite_for_domain[$cacheKey] = $subsiteID; - // Default subsite id = 0, the main site - return 0; + return $subsiteID; } function getMembersByPermission($permissionCodes = array('ADMIN')){ @@ -629,5 +645,6 @@ JS; */ static function on_db_reset() { self::$_cache_accessible_sites = array(); + self::$_cache_subsite_for_domain = array(); } } From 601e8d6c687152a29d829dd8bb0522e3a231caf2 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 3 Jan 2013 14:15:41 +0100 Subject: [PATCH 6/8] Fixed fixture class references --- tests/SiteTreeSubsitesTest.php | 10 +++++----- tests/SubsitesVirtualPageTest.php | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/SiteTreeSubsitesTest.php b/tests/SiteTreeSubsitesTest.php index 0c126a3..d966892 100644 --- a/tests/SiteTreeSubsitesTest.php +++ b/tests/SiteTreeSubsitesTest.php @@ -60,8 +60,8 @@ class SiteTreeSubsitesTest extends SapphireTest { function testRelatedPages() { $this->assertTrue(singleton('RelatedPageLink')->getCMSFields() instanceof FieldSet); - $importantpage = $this->objFromFixture('SiteTree', 'importantpage'); - $contact = $this->objFromFixture('SiteTree', 'contact'); + $importantpage = $this->objFromFixture('Page', 'importantpage'); + $contact = $this->objFromFixture('Page', 'contact'); $link = new RelatedPageLink(); $link->MasterPageID = $importantpage->ID; @@ -87,9 +87,9 @@ class SiteTreeSubsitesTest extends SapphireTest { $admin = $this->objFromFixture('Member', 'admin'); $subsite1member = $this->objFromFixture('Member', 'subsite1member'); $subsite2member = $this->objFromFixture('Member', 'subsite2member'); - $mainpage = $this->objFromFixture('SiteTree', 'home'); - $subsite1page = $this->objFromFixture('SiteTree', 'subsite1_home'); - $subsite2page = $this->objFromFixture('SiteTree', 'subsite2_home'); + $mainpage = $this->objFromFixture('Page', 'home'); + $subsite1page = $this->objFromFixture('Page', 'subsite1_home'); + $subsite2page = $this->objFromFixture('Page', 'subsite2_home'); $subsite1 = $this->objFromFixture('Subsite', 'subsite1'); $subsite2 = $this->objFromFixture('Subsite', 'subsite2'); diff --git a/tests/SubsitesVirtualPageTest.php b/tests/SubsitesVirtualPageTest.php index 9b632fe..2db2f54 100644 --- a/tests/SubsitesVirtualPageTest.php +++ b/tests/SubsitesVirtualPageTest.php @@ -35,7 +35,7 @@ class SubsitesVirtualPageTest extends SapphireTest { Subsite::changeSubsite($subsite->ID); Subsite::$disable_subsite_filter = false; - $linky = $this->objFromFixture('SiteTree', 'linky'); + $linky = $this->objFromFixture('Page', 'linky'); $svp = new SubsitesVirtualPage(); $svp->CopyContentFromID = $linky->ID; @@ -59,7 +59,7 @@ class SubsitesVirtualPageTest extends SapphireTest { Subsite::changeSubsite($subsite->ID); - $orig = $this->objFromFixture('SiteTree', 'linky'); + $orig = $this->objFromFixture('Page', 'linky'); $svp = new SubsitesVirtualPage(); $svp->CopyContentFromID = $orig->ID; @@ -174,7 +174,7 @@ class SubsitesVirtualPageTest extends SapphireTest { // Go to main site, get parent page $subsite = $this->objFromFixture('Subsite', 'main'); Subsite::changeSubsite($subsite->ID); - $page = $this->objFromFixture('SiteTree', 'importantpage'); + $page = $this->objFromFixture('Page', 'importantpage'); // Create two SVPs on other subsites $subsite = $this->objFromFixture('Subsite', 'subsite1'); @@ -194,7 +194,7 @@ class SubsitesVirtualPageTest extends SapphireTest { // Switch back to main site, unpublish source $subsite = $this->objFromFixture('Subsite', 'main'); Subsite::changeSubsite($subsite->ID); - $page = $this->objFromFixture('SiteTree', 'importantpage'); + $page = $this->objFromFixture('Page', 'importantpage'); $page->doUnpublish(); Subsite::changeSubsite($vp1->SubsiteID); @@ -217,7 +217,7 @@ class SubsitesVirtualPageTest extends SapphireTest { $subsite2 = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($subsite1->ID); - $subsite1Page = $this->objFromFixture('SiteTree', 'subsite1_contactus'); + $subsite1Page = $this->objFromFixture('Page', 'subsite1_contactus'); $subsite1Page->URLSegment = 'contact-us'; $subsite1Page->write(); From dfeb52de877e1354cc370905f009a104a16c1a38 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 3 Jan 2013 14:46:56 +0100 Subject: [PATCH 7/8] Fix test failures caused by subsite filtering on fixture retrieval --- tests/BaseSubsiteTest.php | 13 +++++++++++++ tests/FileSubsitesTest.php | 2 +- tests/GroupSubsitesTest.php | 2 +- tests/LeftAndMainSubsitesTest.php | 11 +++++++++++ tests/SiteConfigSubsitesTest.php | 2 +- tests/SiteTreeSubsitesTest.php | 2 +- tests/SubsiteAdminTest.php | 2 +- tests/SubsiteTest.php | 2 +- tests/SubsitesVirtualPageTest.php | 3 ++- 9 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 tests/BaseSubsiteTest.php diff --git a/tests/BaseSubsiteTest.php b/tests/BaseSubsiteTest.php new file mode 100644 index 0000000..5eb58cb --- /dev/null +++ b/tests/BaseSubsiteTest.php @@ -0,0 +1,13 @@ +objFromFixture("Member","admin"); diff --git a/tests/SiteConfigSubsitesTest.php b/tests/SiteConfigSubsitesTest.php index 0727081..075eff1 100644 --- a/tests/SiteConfigSubsitesTest.php +++ b/tests/SiteConfigSubsitesTest.php @@ -1,6 +1,6 @@ Date: Thu, 3 Jan 2013 14:55:25 +0100 Subject: [PATCH 8/8] Added composer.json --- composer.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..44ad5eb --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "silverstripe/subsites", + "description": "Run multiple sites from a single SilverStripe install.", + "type": "silverstripe-module", + "keywords": ["silverstripe", "subsites", "multisite"], + "authors": [ + { + "name": "Sam Minnee", + "email": "sam@silverstripe.com" + } + ], + "require": + { + "silverstripe/framework": "~2.3", + "silverstripe/cms": "~2.3" + } +}