From a98958fdf9e4186013a395431b63b1696a0f1e49 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Mon, 23 May 2016 10:41:51 +1200 Subject: [PATCH] BUG Fix issue with urlsegment being renamed in subsites --- code/SubsitesVirtualPage.php | 72 ++++------------ code/extensions/SiteTreeSubsites.php | 71 +++++++++++----- code/model/Subsite.php | 109 ++++++++++++------------ tests/BaseSubsiteTest.php | 1 + tests/SiteTreeSubsitesTest.php | 119 ++++++++++++++++++++------- tests/SubsiteTest.php | 73 ++++++++-------- tests/SubsiteTest.yml | 12 +++ tests/SubsitesVirtualPageTest.php | 78 ++++++++++-------- 8 files changed, 302 insertions(+), 233 deletions(-) diff --git a/code/SubsitesVirtualPage.php b/code/SubsitesVirtualPage.php index ccc801d..4ec527c 100644 --- a/code/SubsitesVirtualPage.php +++ b/code/SubsitesVirtualPage.php @@ -9,18 +9,18 @@ class SubsitesVirtualPage extends VirtualPage 'CustomMetaDescription' => 'Text', 'CustomExtraMeta' => 'HTMLText' ); - + public function getCMSFields() { $fields = parent::getCMSFields(); - + $subsites = DataObject::get('Subsite'); if (!$subsites) { $subsites = new ArrayList(); } else { $subsites=ArrayList::create($subsites->toArray()); } - + $subsites->push(new ArrayData(array('Title' => 'Main site', 'ID' => 0))); $fields->addFieldToTab( @@ -32,7 +32,7 @@ class SubsitesVirtualPage extends VirtualPage )->addExtraClass('subsitestreedropdownfield-chooser no-change-track'), 'CopyContentFromID' ); - + // Setup the linking to the original page. $pageSelectionField = new SubsitesTreeDropdownField( "CopyContentFromID", @@ -41,13 +41,13 @@ class SubsitesVirtualPage extends VirtualPage "ID", "MenuTitle" ); - + if (Controller::has_curr() && Controller::curr()->getRequest()) { $subsiteID = Controller::curr()->getRequest()->requestVar('CopyContentFromID_SubsiteID'); $pageSelectionField->setSubsiteID($subsiteID); } $fields->replaceField('CopyContentFromID', $pageSelectionField); - + // Create links back to the original object in the CMS if ($this->CopyContentFromID) { $editLink = "admin/pages/edit/show/$this->CopyContentFromID/?SubsiteID=" . $this->CopyContentFrom()->SubsiteID; @@ -63,8 +63,8 @@ class SubsitesVirtualPage extends VirtualPage ); $linkToContentLabelField->setAllowHTML(true); } - - + + $fields->addFieldToTab( 'Root.Main', TextField::create( @@ -97,7 +97,7 @@ class SubsitesVirtualPage extends VirtualPage )->setDescription(_t('SubsitesVirtualPage.OverrideNote')), 'ExtraMeta' ); - + return $fields; } @@ -116,7 +116,7 @@ class SubsitesVirtualPage extends VirtualPage { return ($this->CopyContentFromID) ? (int)$this->CopyContentFrom()->SubsiteID : (int)Session::get('SubsiteID'); } - + public function getVirtualFields() { $fields = parent::getVirtualFields(); @@ -125,7 +125,7 @@ class SubsitesVirtualPage extends VirtualPage unset($fields[$k]); } } - + foreach (self::$db as $field => $type) { if (in_array($field, $fields)) { unset($fields[array_search($field, $fields)]); @@ -134,7 +134,7 @@ class SubsitesVirtualPage extends VirtualPage return $fields; } - + public function syncLinkTracking() { $oldState = Subsite::$disable_subsite_filter; @@ -148,7 +148,7 @@ class SubsitesVirtualPage extends VirtualPage public function onBeforeWrite() { parent::onBeforeWrite(); - + if ($this->CustomMetaTitle) { $this->MetaTitle = $this->CustomMetaTitle; } else { @@ -170,46 +170,6 @@ class SubsitesVirtualPage extends VirtualPage $this->ExtraMeta = $this->ContentSource()->ExtraMeta ? $this->ContentSource()->ExtraMeta : $this->ExtraMeta; } } - - public function validURLSegment() - { - $isValid = parent::validURLSegment(); - - // Veto the validation rules if its false. In this case, some logic - // needs to be duplicated from parent to find out the exact reason the validation failed. - if (!$isValid) { - $IDFilter = ($this->ID) ? "AND \"SiteTree\".\"ID\" <> $this->ID" : null; - $parentFilter = null; - - if (Config::inst()->get('SiteTree', 'nested_urls')) { - if ($this->ParentID) { - $parentFilter = " AND \"SiteTree\".\"ParentID\" = $this->ParentID"; - } else { - $parentFilter = ' AND "SiteTree"."ParentID" = 0'; - } - } - - $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; - Subsite::$disable_subsite_filter = true; - $existingPage = DataObject::get_one( - 'SiteTree', - "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter", - false // disable cache, it doesn't include subsite status in the key - ); - Subsite::$disable_subsite_filter = $origDisableSubsiteFilter; - $existingPageInSubsite = DataObject::get_one( - 'SiteTree', - "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter", - false // disable cache, it doesn't include subsite status in the key - ); - - // If URL has been vetoed because of an existing page, - // be more specific and allow same URLSegments in different subsites - $isValid = !($existingPage && $existingPageInSubsite); - } - - return $isValid; - } } class SubsitesVirtualPage_Controller extends VirtualPage_Controller @@ -220,14 +180,14 @@ class SubsitesVirtualPage_Controller extends VirtualPage_Controller $this->failover->write(); return; } - + public function init() { $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; Subsite::$disable_subsite_filter = true; - + parent::init(); - + Subsite::$disable_subsite_filter = $origDisableSubsiteFilter; } } diff --git a/code/extensions/SiteTreeSubsites.php b/code/extensions/SiteTreeSubsites.php index 1ae8301..8aa6233 100644 --- a/code/extensions/SiteTreeSubsites.php +++ b/code/extensions/SiteTreeSubsites.php @@ -24,7 +24,7 @@ class SiteTreeSubsites extends DataExtension } return false; } - + /** * Update any requests to limit the results to the current site */ @@ -36,7 +36,7 @@ class SiteTreeSubsites extends DataExtension if ($dataQuery->getQueryParam('Subsite.filter') === false) { return; } - + // If you're querying by ID, ignore the sub-site - this is a bit ugly... // if(!$query->where || (strpos($query->where[0], ".\"ID\" = ") === false && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false && strpos($query->where[0], "ID = ") !== 0)) { if ($query->filtersOnID()) { @@ -60,13 +60,13 @@ class SiteTreeSubsites extends DataExtension break; } } - + public function onBeforeWrite() { if (!$this->owner->ID && !$this->owner->SubsiteID) { $this->owner->SubsiteID = Subsite::currentSubsiteID(); } - + parent::onBeforeWrite(); } @@ -107,7 +107,7 @@ class SiteTreeSubsites extends DataExtension if ($subsite && $subsite->exists()) { // Use baseurl from domain $baseLink = $subsite->absoluteBaseURL(); - + // Add parent page if enabled if($nested_urls_enabled && $this->owner->ParentID) { $baseLink = Controller::join_links( @@ -115,12 +115,12 @@ class SiteTreeSubsites extends DataExtension $this->owner->Parent()->RelativeLink(true) ); } - + $urlsegment = $fields->dataFieldByName('URLSegment'); $urlsegment->setURLPrefix($baseLink); } } - + public function alternateSiteConfig() { if (!$this->owner->SubsiteID) { @@ -136,12 +136,12 @@ class SiteTreeSubsites extends DataExtension } return $sc; } - + /** * Only allow editing of a page if the member satisfies one of the following conditions: * - Is in a group which has access to the subsite this page belongs to * - Is in a group with edit permissions on the "main site" - * + * * @return boolean */ public function canEdit($member = null) @@ -149,7 +149,7 @@ class SiteTreeSubsites extends DataExtension if (!$member) { $member = Member::currentUser(); } - + // Find the sites that this user has access to $goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true, 'all', $member)->column('ID'); @@ -169,7 +169,7 @@ class SiteTreeSubsites extends DataExtension return false; } } - + /** * @return boolean */ @@ -178,10 +178,10 @@ class SiteTreeSubsites extends DataExtension if (!$member && $member !== false) { $member = Member::currentUser(); } - + return $this->canEdit($member); } - + /** * @return boolean */ @@ -190,10 +190,10 @@ class SiteTreeSubsites extends DataExtension if (!$member && $member !== false) { $member = Member::currentUser(); } - + return $this->canEdit($member); } - + /** * @return boolean */ @@ -218,7 +218,7 @@ class SiteTreeSubsites extends DataExtension } else { $subsite = DataObject::get_by_id('Subsite', $subsiteID); } - + $oldSubsite=Subsite::currentSubsiteID(); if ($subsiteID) { Subsite::changeSubsite($subsiteID); @@ -294,7 +294,7 @@ class SiteTreeSubsites extends DataExtension // Set LinkTracking appropriately $links = HTTP::getLinksIn($this->owner->Content); $linkedPages = array(); - + if ($links) { foreach ($links as $link) { if (substr($link, 0, strlen('http://')) == 'http://') { @@ -302,7 +302,7 @@ class SiteTreeSubsites extends DataExtension if (strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) { $domain = substr($withoutHttp, 0, strpos($withoutHttp, '/')); $rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1); - + $subsiteID = Subsite::getSubsiteIDForDomain($domain); if ($subsiteID == 0) { continue; @@ -312,7 +312,7 @@ class SiteTreeSubsites extends DataExtension Subsite::disable_subsite_filter(true); $candidatePage = DataObject::get_one("SiteTree", "\"URLSegment\" = '" . Convert::raw2sql(urldecode($rest)) . "' AND \"SubsiteID\" = " . $subsiteID, false); Subsite::disable_subsite_filter($origDisableSubsiteFilter); - + if ($candidatePage) { $linkedPages[] = $candidatePage->ID; } else { @@ -322,10 +322,37 @@ class SiteTreeSubsites extends DataExtension } } } - + $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages); } - + + /** + * Ensure that valid url segments are checked within the correct subsite of the owner object, + * even if the current subsiteID is set to some other subsite. + * + * @return null|bool Either true or false, or null to not influence result + */ + public function augmentValidURLSegment() + { + // If this page is being filtered in the current subsite, then no custom validation query is required. + $subsite = Subsite::$force_subsite ?: Subsite::currentSubsiteID(); + if (empty($this->owner->SubsiteID) || $subsite == $this->owner->SubsiteID) { + return null; + } + + // Backup forced subsite + $prevForceSubsite = Subsite::$force_subsite; + Subsite::$force_subsite = $this->owner->SubsiteID; + + // Repeat validation in the correct subsite + $isValid = $this->owner->validURLSegment(); + + // Restore + Subsite::$force_subsite = $prevForceSubsite; + + return (bool)$isValid; + } + /** * Return a piece of text to keep DataObject cache keys appropriately specific */ @@ -333,7 +360,7 @@ class SiteTreeSubsites extends DataExtension { return 'subsite-'.Subsite::currentSubsiteID(); } - + /** * @param Member * @return boolean|null diff --git a/code/model/Subsite.php b/code/model/Subsite.php index 4efa5eb..be0f3a9 100644 --- a/code/model/Subsite.php +++ b/code/model/Subsite.php @@ -9,8 +9,8 @@ class Subsite extends DataObject { /** * @var $use_session_subsiteid Boolean Set to TRUE when using the CMS and FALSE - * when browsing the frontend of a website. - * + * when browsing the frontend of a website. + * * @todo Remove flag once the Subsite CMS works without session state, * similarly to the Translatable module. */ @@ -21,7 +21,7 @@ class Subsite extends DataObject * to limit DataObject::get*() calls to a specific subsite. Useful for debugging. */ public static $disable_subsite_filter = false; - + /** * Allows you to force a specific subsite ID, or comma separated list of IDs. * Only works for reading. An object cannot be written to more than 1 subsite. @@ -33,10 +33,10 @@ class Subsite extends DataObject * @var boolean */ public static $write_hostmap = true; - + /** * Memory cache of accessible sites - * + * * @array */ private static $_cache_accessible_sites = array(); @@ -54,7 +54,7 @@ class Subsite extends DataObject * are listed. */ private static $allowed_themes = array(); - + /** * @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same. * Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com') @@ -69,14 +69,14 @@ class Subsite extends DataObject /** * Set allowed themes - * + * * @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites. */ public static function set_allowed_themes($themes) { self::$allowed_themes = $themes; } - + /** * Gets the subsite currently set in the session. * @@ -99,7 +99,6 @@ class Subsite extends DataObject * * @todo Pass $request object from controller so we don't have to rely on $_GET * - * @param boolean $cache * @return int ID of the current subsite instance */ public static function currentSubsiteID() @@ -118,7 +117,7 @@ class Subsite extends DataObject return (int)$id; } - + /** * Switch to another subsite through storing the subsite identifier in the current PHP session. * Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE. @@ -151,13 +150,13 @@ class Subsite extends DataObject Permission::flush_permission_cache(); } - + /** * Get a matching subsite for the given host, or for the current HTTP_HOST. * Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string, * for example matching all subdomains on *.example.com with one subsite, * and all subdomains on *.example.org on another. - * + * * @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used. * @return int Subsite ID */ @@ -215,7 +214,7 @@ class Subsite extends DataObject } /** - * + * * @param string $className * @param string $filter * @param string $sort @@ -237,7 +236,7 @@ class Subsite extends DataObject { self::$disable_subsite_filter = $disabled; } - + /** * Flush caches on database reset */ @@ -246,7 +245,7 @@ class Subsite extends DataObject self::$_cache_accessible_sites = array(); self::$_cache_subsite_for_domain = array(); } - + /** * Return all subsites, regardless of permissions (augmented with main site). * @@ -333,13 +332,13 @@ class Subsite extends DataObject $member = DataObject::get_by_id('Member', $member); } - // Rationalise permCode argument + // Rationalise permCode argument if (is_array($permCode)) { $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'"; } else { $SQL_codes = "'" . Convert::raw2sql($permCode) . "'"; } - + // Cache handling $cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle; if (isset(self::$_cache_accessible_sites[$cacheKey])) { @@ -386,19 +385,19 @@ class Subsite extends DataObject } if (self::hasMainSitePermission($member, $permCode)) { $subsites=$subsites->toArray(); - + $mainSite = new Subsite(); $mainSite->Title = $mainSiteTitle; array_unshift($subsites, $mainSite); $subsites=ArrayList::create($subsites); } } - + self::$_cache_accessible_sites[$cacheKey] = $subsites; return $subsites; } - + /** * Write a host->domain map to subsites/host-map.php * @@ -412,14 +411,14 @@ class Subsite extends DataObject if (!self::$write_hostmap) { return; } - + if (!$file) { $file = Director::baseFolder().'/subsites/host-map.php'; } $hostmap = array(); - + $subsites = DataObject::get('Subsite'); - + if ($subsites) { foreach ($subsites as $subsite) { $domains = $subsite->Domains(); @@ -437,7 +436,7 @@ class Subsite extends DataObject } } } - + $data = "ID; - + // Count this user's groups which can access the main site $groupCount = DB::query(" SELECT COUNT(\"Permission\".\"ID\") @@ -508,7 +507,7 @@ class Subsite extends DataObject // There has to be at least one that allows access. return ($groupCount + $roleCount > 0); } - + /** * * @var array @@ -523,7 +522,7 @@ class Subsite extends DataObject // Used to hide unfinished/private subsites from public view. // If unset, will default to true 'IsPublic' => 'Boolean', - + // Comma-separated list of disallowed page types 'PageTypeBlacklist' => 'Text', ); @@ -535,7 +534,7 @@ class Subsite extends DataObject private static $has_many = array( 'Domains' => 'SubsiteDomain', ); - + /** * * @var array @@ -561,13 +560,13 @@ class Subsite extends DataObject 'Domains.Domain', 'IsPublic', ); - + /** * * @var string */ private static $default_sort = "\"Title\" ASC"; - + /** * @todo Possible security issue, don't grant edit permissions to everybody. * @return boolean @@ -579,7 +578,7 @@ class Subsite extends DataObject /** * Show the configuration fields for each subsite - * + * * @return FieldList */ public function getCMSFields() @@ -597,13 +596,13 @@ class Subsite extends DataObject '

'._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'

' ); } - + $languageSelector = new DropdownField( 'Language', $this->fieldLabel('Language'), i18n::get_common_locales() ); - + $pageTypeMap = array(); $pageTypes = SiteTree::page_type_classes(); foreach ($pageTypes as $pageType) { @@ -618,7 +617,7 @@ class Subsite extends DataObject _t('Subsite.TabTitleConfig', 'Configuration'), new HeaderField($this->getClassName() . ' configuration', 2), new TextField('Title', $this->fieldLabel('Title'), $this->Title), - + new HeaderField( _t('Subsite.DomainsHeadline', "Domains for this subsite") ), @@ -629,8 +628,8 @@ class Subsite extends DataObject new CheckboxField('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic), new DropdownField('Theme', $this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme), - - + + new LiteralField( 'PageTypeBlacklistToggle', sprintf( @@ -654,9 +653,9 @@ class Subsite extends DataObject $this->extend('updateCMSFields', $fields); return $fields; } - + /** - * + * * @param boolean $includerelations * @return array */ @@ -677,7 +676,7 @@ class Subsite extends DataObject } /** - * + * * @return array */ public function summaryFields() @@ -688,10 +687,10 @@ class Subsite extends DataObject 'IsPublic' => _t('Subsite.IsPublicHeaderField', 'Active subsite'), ); } - + /** * Return the themes that can be used with this subsite, as an array of themecode => description - * + * * @return array */ public function allowedThemes() @@ -727,7 +726,7 @@ class Subsite extends DataObject } /** - * + * * @return ValidationResult */ public function validate() @@ -749,11 +748,11 @@ class Subsite extends DataObject Subsite::writeHostMap(); parent::onAfterWrite(); } - + /** * Return the primary domain of this site. Tries to "normalize" the domain name, * by replacing potential wildcards. - * + * * @return string The full domain name of this subsite (without protocol prefix) */ public function domain() @@ -780,9 +779,9 @@ class Subsite extends DataObject ->sort('"IsPrimary" DESC') ->first(); } - + /** - * + * * @return string - The full domain name of this subsite (without protocol prefix) */ public function getPrimaryDomain() @@ -792,7 +791,7 @@ class Subsite extends DataObject /** * Get the absolute URL for this subsite - * @return string + * @return string */ public function absoluteBaseURL() { @@ -816,8 +815,8 @@ class Subsite extends DataObject /** * Javascript admin action to duplicate this subsite - * - * @return string - javascript + * + * @return string - javascript */ public function adminDuplicate() { @@ -827,7 +826,7 @@ class Subsite extends DataObject 'Created a copy of {title}', array('title' => Convert::raw2js($this->Title)) ); - + return <<ID'); @@ -843,7 +842,7 @@ JS; } /** - * + * * @param array $permissionCodes * @return DataList */ diff --git a/tests/BaseSubsiteTest.php b/tests/BaseSubsiteTest.php index 657857c..63cb8be 100644 --- a/tests/BaseSubsiteTest.php +++ b/tests/BaseSubsiteTest.php @@ -6,6 +6,7 @@ class BaseSubsiteTest extends SapphireTest parent::setUp(); Subsite::$use_session_subsiteid = true; + Subsite::$force_subsite = null; } /** diff --git a/tests/SiteTreeSubsitesTest.php b/tests/SiteTreeSubsitesTest.php index f5edef4..fde96d8 100644 --- a/tests/SiteTreeSubsitesTest.php +++ b/tests/SiteTreeSubsitesTest.php @@ -3,7 +3,7 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest { public static $fixture_file = 'subsites/tests/SubsiteTest.yml'; - + protected $extraDataObjects = array( 'SiteTreeSubsitesTest_ClassA', 'SiteTreeSubsitesTest_ClassB' @@ -12,38 +12,38 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest protected $illegalExtensions = array( 'SiteTree' => array('Translatable') ); - + public function testPagesInDifferentSubsitesCanShareURLSegment() { $subsiteMain = $this->objFromFixture('Subsite', 'main'); $subsite1 = $this->objFromFixture('Subsite', 'subsite1'); - + $pageMain = new SiteTree(); $pageMain->URLSegment = 'testpage'; $pageMain->write(); $pageMain->publish('Stage', 'Live'); - + $pageMainOther = new SiteTree(); $pageMainOther->URLSegment = 'testpage'; $pageMainOther->write(); $pageMainOther->publish('Stage', 'Live'); - + $this->assertNotEquals($pageMain->URLSegment, $pageMainOther->URLSegment, 'Pages in same subsite cant share the same URL' ); - + Subsite::changeSubsite($subsite1->ID); - + $pageSubsite1 = new SiteTree(); $pageSubsite1->URLSegment = 'testpage'; $pageSubsite1->write(); $pageSubsite1->publish('Stage', 'Live'); - + $this->assertEquals($pageMain->URLSegment, $pageSubsite1->URLSegment, 'Pages in different subsites can share the same URL' ); } - + public function testBasicSanity() { $this->assertTrue(singleton('SiteTree')->getSiteConfig() instanceof SiteConfig); @@ -52,19 +52,19 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest $this->assertTrue(singleton('SubsitesVirtualPage')->getCMSFields() instanceof FieldList); $this->assertTrue(is_array(singleton('SiteTreeSubsites')->extraStatics())); } - + public function testErrorPageLocations() { $subsite1 = $this->objFromFixture('Subsite', 'domaintest1'); - + Subsite::changeSubsite($subsite1->ID); $path = ErrorPage::get_filepath_for_errorcode(500); - + $static_path = Config::inst()->get('ErrorPage', 'static_filepath'); $expected_path = $static_path . '/error-500-'.$subsite1->domain().'.html'; $this->assertEquals($expected_path, $path); } - + public function testCanEditSiteTree() { $admin = $this->objFromFixture('Member', 'admin'); @@ -75,29 +75,29 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest $subsite2page = $this->objFromFixture('Page', 'subsite2_home'); $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); $this->assertTrue( (bool)$subsite1page->canEdit(), 'Administrators can edit all subsites' ); - + // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state Subsite::changeSubsite($subsite1); - + Session::set("loggedInAs", $subsite1member->ID); $this->assertTrue( (bool)$subsite1page->canEdit(), 'Members can edit pages on a subsite if they are in a group belonging to this subsite' ); - + Session::set("loggedInAs", $subsite2member->ID); $this->assertFalse( (bool)$subsite1page->canEdit(), 'Members cant edit pages on a subsite if they are not in a group belonging to this subsite' ); - + // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state Subsite::changeSubsite(0); $this->assertFalse( @@ -105,7 +105,7 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest 'Members cant edit pages on the main site if they are not in a group allowing this' ); } - + /** * Similar to {@link SubsitesVirtualPageTest->testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()}. */ @@ -114,7 +114,7 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest // Set up a couple of pages with the same URL on different subsites $s1 = $this->objFromFixture('Subsite', 'domaintest1'); $s2 = $this->objFromFixture('Subsite', 'domaintest2'); - + $p1 = new SiteTree(); $p1->Title = $p1->URLSegment = "test-page"; $p1->SubsiteID = $s1->ID; @@ -128,7 +128,7 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest // Check that the URLs weren't modified in our set-up $this->assertEquals($p1->URLSegment, 'test-page'); $this->assertEquals($p2->URLSegment, 'test-page'); - + // Check that if we switch between the different subsites, we receive the correct pages Subsite::changeSubsite($s1); $this->assertEquals($p1->ID, SiteTree::get_by_link('test-page')->ID); @@ -136,22 +136,22 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest Subsite::changeSubsite($s2); $this->assertEquals($p2->ID, SiteTree::get_by_link('test-page')->ID); } - + public function testPageTypesBlacklistInClassDropdown() { $editor = $this->objFromFixture('Member', 'editor'); Session::set("loggedInAs", $editor->ID); - + $s1 = $this->objFromFixture('Subsite', 'domaintest1'); $s2 = $this->objFromFixture('Subsite', 'domaintest2'); $page = singleton('SiteTree'); - + $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; $s1->write(); - + Subsite::changeSubsite($s1); $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); - + $this->assertArrayNotHasKey('ErrorPage', $settingsFields ); @@ -174,17 +174,17 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest $settingsFields ); } - + public function testPageTypesBlacklistInCMSMain() { $editor = $this->objFromFixture('Member', 'editor'); Session::set("loggedInAs", $editor->ID); - + $cmsmain = new CMSMain(); - + $s1 = $this->objFromFixture('Subsite', 'domaintest1'); $s2 = $this->objFromFixture('Subsite', 'domaintest2'); - + $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; $s1->write(); @@ -202,6 +202,65 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest $this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes); $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); } + + /** + * Tests that url segments between subsites don't conflict, but do conflict within them + */ + public function testValidateURLSegment() { + $this->logInWithPermission('ADMIN'); + // Saving existing page in the same subsite doesn't change urls + $mainHome = $this->objFromFixture('Page', 'home'); + $mainSubsiteID = $this->idFromFixture('Subsite', 'main'); + Subsite::changeSubsite($mainSubsiteID); + $mainHome->Content = '

Some new content

'; + $mainHome->write(); + $this->assertEquals('home', $mainHome->URLSegment); + $mainHome->doPublish(); + $mainHomeLive = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $mainHome->ID)); + $this->assertEquals('home', $mainHomeLive->URLSegment); + + // Saving existing page in another subsite doesn't change urls + Subsite::changeSubsite($mainSubsiteID); + $subsite1Home = $this->objFromFixture('Page', 'subsite1_home'); + $subsite1Home->Content = '

In subsite 1

'; + $subsite1Home->write(); + $this->assertEquals('home', $subsite1Home->URLSegment); + $subsite1Home->doPublish(); + $subsite1HomeLive = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $subsite1Home->ID)); + $this->assertEquals('home', $subsite1HomeLive->URLSegment); + + // Creating a new page in a subsite doesn't conflict with urls in other subsites + $subsite1ID = $this->idFromFixture('Subsite', 'subsite1'); + Subsite::changeSubsite($subsite1ID); + $subsite1NewPage = new Page(); + $subsite1NewPage->SubsiteID = $subsite1ID; + $subsite1NewPage->Title = 'Important Page (Subsite 1)'; + $subsite1NewPage->URLSegment = 'important-page'; // Also exists in main subsite + $subsite1NewPage->write(); + $this->assertEquals('important-page', $subsite1NewPage->URLSegment); + $subsite1NewPage->doPublish(); + $subsite1NewPageLive = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $subsite1NewPage->ID)); + $this->assertEquals('important-page', $subsite1NewPageLive->URLSegment); + + // Creating a new page in a subsite DOES conflict with urls in the same subsite + $subsite1NewPage2 = new Page(); + $subsite1NewPage2->SubsiteID = $subsite1ID; + $subsite1NewPage2->Title = 'Important Page (Subsite 1)'; + $subsite1NewPage2->URLSegment = 'important-page'; // Also exists in main subsite + $subsite1NewPage2->write(); + $this->assertEquals('important-page-2', $subsite1NewPage2->URLSegment); + $subsite1NewPage2->doPublish(); + $subsite1NewPage2Live = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $subsite1NewPage2->ID)); + $this->assertEquals('important-page-2', $subsite1NewPage2Live->URLSegment); + + // Original page is left un-modified + $mainSubsiteImportantPageID = $this->idFromFixture('Page', 'importantpage'); + $mainSubsiteImportantPage = Page::get()->byID($mainSubsiteImportantPageID); + $this->assertEquals('important-page', $mainSubsiteImportantPage->URLSegment); + $mainSubsiteImportantPage->Content = '

New Important Page Content

'; + $mainSubsiteImportantPage->write(); + $this->assertEquals('important-page', $mainSubsiteImportantPage->URLSegment); + } } class SiteTreeSubsitesTest_ClassA extends SiteTree implements TestOnly diff --git a/tests/SubsiteTest.php b/tests/SubsiteTest.php index 9add45f..1b0eab1 100644 --- a/tests/SubsiteTest.php +++ b/tests/SubsiteTest.php @@ -17,17 +17,17 @@ class SubsiteTest extends BaseSubsiteTest * @var array */ protected $origServer = array(); - + public function setUp() { parent::setUp(); - + Config::inst()->update('Director', 'alternate_base_url', '/'); $this->origStrictSubdomainMatching = Subsite::$strict_subdomain_matching; $this->origServer = $_SERVER; Subsite::$strict_subdomain_matching = false; } - + public function tearDown() { $_SERVER = $this->origServer; @@ -42,15 +42,16 @@ class SubsiteTest extends BaseSubsiteTest public function testSubsiteCreation() { Subsite::$write_hostmap = false; - + // Create the instance $template = $this->objFromFixture('Subsite', 'main'); - + // Test that changeSubsite is working Subsite::changeSubsite($template->ID); + $this->assertEquals($template->ID, Subsite::currentSubsiteID()); $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"); $totalPages = $pages->Count(); @@ -61,22 +62,22 @@ class SubsiteTest extends BaseSubsiteTest // Create a new site $subsite = $template->duplicate(); - + // Check title $this->assertEquals($subsite->Title, $template->Title); - + // Another test that changeSubsite is working $subsite->activate(); - + $siteHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'"); $this->assertNotEquals($siteHome, false, 'Home Page for subsite not found'); $this->assertEquals($subsite->ID, $siteHome->SubsiteID, 'createInstance() copies existing pages retaining the same URLSegment' ); - + Subsite::changeSubsite(0); } - + /** * Confirm that domain lookup is working */ @@ -89,7 +90,7 @@ class SubsiteTest extends BaseSubsiteTest foreach (DataObject::get('SubsiteDomain') as $domain) { $domain->delete(); } - + // Much more expressive than YML in this case $subsite1 = $this->createSubsiteWithDomains(array( 'one.example.org' => true, @@ -105,19 +106,19 @@ class SubsiteTest extends BaseSubsiteTest 'subdomain.unique.com' => false, '*.onmultiplesubsites.com' => false, )); - + $this->assertEquals( $subsite3->ID, Subsite::getSubsiteIDForDomain('subdomain.unique.com'), 'Full unique match' ); - + $this->assertEquals( $subsite1->ID, Subsite::getSubsiteIDForDomain('one.example.org'), 'Full match, doesn\'t complain about multiple matches within a single subsite' ); - + $failed = false; try { Subsite::getSubsiteIDForDomain('subdomain.onmultiplesubsites.com'); @@ -128,19 +129,19 @@ class SubsiteTest extends BaseSubsiteTest $failed, 'Fails on multiple matches with wildcard vs. www across multiple subsites' ); - + $this->assertEquals( $subsite1->ID, Subsite::getSubsiteIDForDomain('one.unique.com'), 'Fuzzy match suffixed with wildcard (rule "one.*")' ); - + $this->assertEquals( $subsite2->ID, Subsite::getSubsiteIDForDomain('two.mysite.com'), 'Matches correct subsite for rule' ); - + $this->assertEquals( $subsite2->ID, Subsite::getSubsiteIDForDomain('other.mysite.com'), @@ -153,7 +154,7 @@ class SubsiteTest extends BaseSubsiteTest "Doesn't match unknown subsite" ); } - + public function testStrictSubdomainMatching() { // Clear existing fixtures @@ -163,7 +164,7 @@ class SubsiteTest extends BaseSubsiteTest foreach (DataObject::get('SubsiteDomain') as $domain) { $domain->delete(); } - + // Much more expressive than YML in this case $subsite1 = $this->createSubsiteWithDomains(array( 'example.org' => true, @@ -176,7 +177,7 @@ class SubsiteTest extends BaseSubsiteTest )); Subsite::$strict_subdomain_matching = false; - + $this->assertEquals( $subsite1->ID, Subsite::getSubsiteIDForDomain('example.org'), @@ -197,9 +198,9 @@ class SubsiteTest extends BaseSubsiteTest Subsite::getSubsiteIDForDomain('www.wildcard.com'), 'Doesn\'t match www prefix without strict check, even if a wildcard subdomain is in place' ); - + Subsite::$strict_subdomain_matching = true; - + $this->assertEquals( $subsite1->ID, Subsite::getSubsiteIDForDomain('example.org'), @@ -226,7 +227,7 @@ class SubsiteTest extends BaseSubsiteTest 'Fails on multiple matches with strict checking and wildcard vs. www' ); } - + protected function createSubsiteWithDomains($domains) { $subsite = new Subsite(array( @@ -241,7 +242,7 @@ class SubsiteTest extends BaseSubsiteTest )); $domain->write(); } - + return $subsite; } @@ -267,7 +268,7 @@ class SubsiteTest extends BaseSubsiteTest $this->assertEquals($_SERVER['HTTP_HOST'], singleton('Subsite')->PrimaryDomain); $this->assertEquals('http://'.$_SERVER['HTTP_HOST'].Director::baseURL(), singleton('Subsite')->absoluteBaseURL()); } - + /** * Tests that Subsite and SubsiteDomain both respect http protocol correctly */ @@ -276,20 +277,20 @@ class SubsiteTest extends BaseSubsiteTest $subsite2 = $this->objFromFixture('Subsite', 'domaintest2'); $domain2a = $this->objFromFixture('SubsiteDomain', 'dt2a'); $domain2b = $this->objFromFixture('SubsiteDomain', 'dt2b'); - + // domaintest4 is 'https' (primary only) $subsite4 = $this->objFromFixture('Subsite', 'domaintest4'); $domain4a = $this->objFromFixture('SubsiteDomain', 'dt4a'); $domain4b = $this->objFromFixture('SubsiteDomain', 'dt4b'); // secondary domain is http only though - + // domaintest5 is 'http' $subsite5 = $this->objFromFixture('Subsite', 'domaintest5'); $domain5a = $this->objFromFixture('SubsiteDomain', 'dt5'); - + // Check protocol when current protocol is http:// $_SERVER['HTTP_HOST'] = 'www.mysite.com'; $_SERVER['HTTPS'] = ''; - + $this->assertEquals('http://two.mysite.com/', $subsite2->absoluteBaseURL()); $this->assertEquals('http://two.mysite.com/', $domain2a->absoluteBaseURL()); $this->assertEquals('http://subsite.mysite.com/', $domain2b->absoluteBaseURL()); @@ -298,11 +299,11 @@ class SubsiteTest extends BaseSubsiteTest $this->assertEquals('http://www.secondary.com/', $domain4b->absoluteBaseURL()); $this->assertEquals('http://www.tertiary.com/', $subsite5->absoluteBaseURL()); $this->assertEquals('http://www.tertiary.com/', $domain5a->absoluteBaseURL()); - + // Check protocol when current protocol is https:// $_SERVER['HTTP_HOST'] = 'www.mysite.com'; $_SERVER['HTTPS'] = 'ON'; - + $this->assertEquals('https://two.mysite.com/', $subsite2->absoluteBaseURL()); $this->assertEquals('https://two.mysite.com/', $domain2a->absoluteBaseURL()); $this->assertEquals('https://subsite.mysite.com/', $domain2b->absoluteBaseURL()); @@ -373,14 +374,14 @@ class SubsiteTest extends BaseSubsiteTest sort($member2SiteTitles); $this->assertEquals('Subsite1 Template', $member2SiteTitles[0], 'Member can get to subsite via a group role'); } - + public function testhasMainSitePermission() { $admin = $this->objFromFixture('Member', 'admin'); $subsite1member = $this->objFromFixture('Member', 'subsite1member'); $subsite1admin = $this->objFromFixture('Member', 'subsite1admin'); $allsubsitesauthor = $this->objFromFixture('Member', 'allsubsitesauthor'); - + $this->assertTrue( Subsite::hasMainSitePermission($admin), 'Default permissions granted for super-admin' @@ -425,7 +426,7 @@ class SubsiteTest extends BaseSubsiteTest $page1->write(); $page1->doPublish(); $this->assertEquals($page1->SubsiteID, $subsite1->ID); - + // duplicate $subsite2 = $subsite1->duplicate(); $subsite2->activate(); @@ -434,7 +435,7 @@ class SubsiteTest extends BaseSubsiteTest $page2->Title = 'MyNewAwesomePage'; $page2->write(); $page2->doPublish(); - + // check change & check change has not affected subiste1 $subsite1->activate(); $this->assertEquals('MyAwesomePage', DataObject::get_by_id('Page', $page1->ID)->Title); diff --git a/tests/SubsiteTest.yml b/tests/SubsiteTest.yml index f888474..4d8f0b5 100644 --- a/tests/SubsiteTest.yml +++ b/tests/SubsiteTest.yml @@ -64,40 +64,52 @@ Page: mainSubsitePage: Title: MainSubsitePage SubsiteID: 0 + URLSegment: mainsubsitepage home: Title: Home SubsiteID: =>Subsite.main + URLSegment: home about: Title: About SubsiteID: =>Subsite.main + URLSegment: about linky: Title: Linky SubsiteID: =>Subsite.main + URLSegment: linky staff: Title: Staff ParentID: =>Page.about SubsiteID: =>Subsite.main + URLSegment: staff contact: Title: Contact Us SubsiteID: =>Subsite.main + URLSegment: contact-us importantpage: Title: Important Page SubsiteID: =>Subsite.main + URLSegment: important-page subsite1_home: Title: Home (Subsite 1) SubsiteID: =>Subsite.subsite1 + URLSegment: home subsite1_contactus: Title: Contact Us (Subsite 1) SubsiteID: =>Subsite.subsite1 + URLSegment: contact-us subsite1_staff: Title: Staff SubsiteID: =>Subsite.subsite1 + URLSegment: staff subsite2_home: Title: Home (Subsite 2) SubsiteID: =>Subsite.subsite2 + URLSegment: home subsite2_contactus: Title: Contact Us (Subsite 2) SubsiteID: =>Subsite.subsite2 + URLSegment: contact-us PermissionRoleCode: roleCode1: diff --git a/tests/SubsitesVirtualPageTest.php b/tests/SubsitesVirtualPageTest.php index 92f289b..95f30e8 100644 --- a/tests/SubsitesVirtualPageTest.php +++ b/tests/SubsitesVirtualPageTest.php @@ -6,12 +6,12 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest 'subsites/tests/SubsiteTest.yml', 'subsites/tests/SubsitesVirtualPageTest.yml', ); - + public function setUp() { parent::setUp(); $this->logInWithPermission('ADMIN'); - + $fh = fopen(Director::baseFolder() . '/assets/testscript-test-file.pdf', "w"); fwrite($fh, str_repeat('x', 1000000)); fclose($fh); @@ -31,26 +31,26 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest } } } - + // Attempt to bring main:linky to subsite2:linky public function testVirtualPageFromAnotherSubsite() { Subsite::$write_hostmap = false; - + $subsite = $this->objFromFixture('Subsite', 'subsite2'); - + Subsite::changeSubsite($subsite->ID); Subsite::$disable_subsite_filter = false; - + $linky = $this->objFromFixture('Page', 'linky'); - + $svp = new SubsitesVirtualPage(); $svp->CopyContentFromID = $linky->ID; $svp->SubsiteID = $subsite->ID; $svp->URLSegment = 'linky'; - + $svp->write(); - + $this->assertEquals($svp->SubsiteID, $subsite->ID); $this->assertEquals($svp->Title, $linky->Title); } @@ -70,12 +70,12 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $svp->CopyContentFromID = $page->ID; $svp->write(); $svp->doPublish(); - + // Rename the file $file = $this->objFromFixture('File', 'file1'); $file->Name = 'renamed-test-file.pdf'; $file->write(); - + // Verify that the draft and publish virtual pages both have the corrected link $this->assertContains('ID")->value()); @@ -111,27 +111,27 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $p->doPublish(); $this->fixVersionNumberCache($vp); $this->assertTrue($vp->IsAddedToStage); - + // A new VP created after P's initial construction $vp2 = new SubsitesVirtualPage(); $vp2->CopyContentFromID = $p->ID; $vp2->write(); $this->assertTrue($vp2->IsAddedToStage); - + // Also remains orange after a republish $p->Content = "new content"; $p->write(); $p->doPublish(); $this->fixVersionNumberCache($vp2); $this->assertTrue($vp2->IsAddedToStage); - + // VP is now published $vp->doPublish(); $this->fixVersionNumberCache($vp); $this->assertTrue($vp->ExistsOnLive); $this->assertFalse($vp->IsModifiedOnStage); - + // P edited, VP and P both go green $p->Content = "third content"; $p->write(); @@ -146,7 +146,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $this->assertTrue($vp->ExistsOnLive); $this->assertFalse($vp->IsModifiedOnStage); } - + /** * This test ensures published Subsites Virtual Pages immediately reflect updates * to their published target pages. Note - this has to happen when the virtual page @@ -162,12 +162,12 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $p->writeToStage('Stage'); $p->publish('Stage', 'Live'); $this->assertTrue($p->ExistsOnLive); - + // change to subsite $subsite = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($subsite->ID); Subsite::$disable_subsite_filter = false; - + // create svp in subsite $svp = new SubsitesVirtualPage(); $svp->CopyContentFromID = $p->ID; @@ -176,34 +176,34 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $svp->publish('Stage', 'Live'); $this->assertEquals($svp->SubsiteID, $subsite->ID); $this->assertTrue($svp->ExistsOnLive); - + // change back to original subsite ("Main site") Subsite::changeSubsite(0); - + // update original page $p->Title = 'New Title'; // "save & publish" $p->writeToStage('Stage'); $p->publish('Stage', 'Live'); $this->assertNotEquals($p->SubsiteID, $subsite->ID); - + // reload SVP from database // can't use DO::get by id because caches. $svpdb = $svp->get()->byID($svp->ID); - + // ensure title changed $this->assertEquals($svpdb->Title, $p->Title); } - + public function testUnpublishingParentPageUnpublishesSubsiteVirtualPages() { Config::inst()->update('StaticPublisher', 'disable_realtime', true); - + // Go to main site, get parent page $subsite = $this->objFromFixture('Subsite', 'main'); Subsite::changeSubsite($subsite->ID); $page = $this->objFromFixture('Page', 'importantpage'); - + // Create two SVPs on other subsites $subsite = $this->objFromFixture('Subsite', 'subsite1'); Subsite::changeSubsite($subsite->ID); @@ -211,30 +211,30 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $vp1->CopyContentFromID = $page->ID; $vp1->write(); $vp1->doPublish(); - + $subsite = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($subsite->ID); $vp2 = new SubsitesVirtualPage(); $vp2->CopyContentFromID = $page->ID; $vp2->write(); $vp2->doPublish(); - + // Switch back to main site, unpublish source $subsite = $this->objFromFixture('Subsite', 'main'); Subsite::changeSubsite($subsite->ID); $page = $this->objFromFixture('Page', 'importantpage'); $page->doUnpublish(); - + Subsite::changeSubsite($vp1->SubsiteID); $onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp1->ID); $this->assertNull($onLive, 'SVP has been removed from live'); - + $subsite = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($vp2->SubsiteID); $onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp2->ID); $this->assertNull($onLive, 'SVP has been removed from live'); } - + /** * Similar to {@link SiteTreeSubsitesTest->testTwoPagesWithSameURLOnDifferentSubsites()} * and {@link SiteTreeSubsitesTest->testPagesInDifferentSubsitesCanShareURLSegment()}. @@ -245,11 +245,11 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $subsite1 = $this->objFromFixture('Subsite', 'subsite1'); $subsite2 = $this->objFromFixture('Subsite', 'subsite2'); Subsite::changeSubsite($subsite1->ID); - + $subsite1Page = $this->objFromFixture('Page', 'subsite1_staff'); $subsite1Page->URLSegment = 'staff'; $subsite1Page->write(); - + // saving on subsite1, and linking to subsite1 $subsite1Vp = new SubsitesVirtualPage(); $subsite1Vp->CopyContentFromID = $subsite1Page->ID; @@ -260,11 +260,11 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $subsite1Page->URLSegment, "Doesn't allow explicit URLSegment overrides when already existing in same subsite" ); - + //Change to subsite 2 Subsite::changeSubsite($subsite2->ID); - // saving in subsite2 (which already has a page with URLSegment 'contact-us'), + // saving in subsite2 (which already has a page with URLSegment 'contact-us'), // but linking to a page in subsite1 $subsite2Vp = new SubsitesVirtualPage(); $subsite2Vp->CopyContentFromID = $subsite1Page->ID; @@ -274,6 +274,16 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $subsite2Vp->URLSegment, $subsite1Page->URLSegment, "Does allow explicit URLSegment overrides when only existing in a different subsite" + ); + + // When changing subsites and re-saving this page, it doesn't trigger a change + Subsite::changeSubsite($subsite1->ID); + $subsite1Page->write(); + $subsite2Vp->write(); + $this->assertEquals( + $subsite2Vp->URLSegment, + $subsite1Page->URLSegment, + "SubsiteVirtualPage doesn't change urls when being written in another subsite" ); }