diff --git a/_config/extensions.yml b/_config/extensions.yml index 1c90a55..32731f5 100644 --- a/_config/extensions.yml +++ b/_config/extensions.yml @@ -50,6 +50,7 @@ SilverStripe\Admin\SecurityAdmin: SilverStripe\CMS\Controllers\CMSMain: extensions: + - SilverStripe\Subsites\Extensions\HintsCacheKeyExtension - SilverStripe\Subsites\Extensions\SubsiteMenuExtension SilverStripe\CMS\Controllers\CMSPagesController: diff --git a/docs/en/introduction.md b/docs/en/introduction.md index 24eabff..66bf7e6 100644 --- a/docs/en/introduction.md +++ b/docs/en/introduction.md @@ -48,3 +48,14 @@ to speak to your website administrator or hosting provider to facilitate this. You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID= to the request. + +### How do Subsite domains work with Fluent domains? + +The Subsites module and Fluent translation module both provide the concept of defining "domains" and let you +configure the host name for it. This functionality is essentially performing the same duty in both modules. + +In the "URL segment" field for CMS pages, both Subsites and Fluent will add their context to the value. If you +have a Subsite domain configured but no Fluent domain, Fluent will respect the existing domain and add its +locale context to the value. If you have a Subsite domain configured and a Fluent domain configured, Fluent will +use its own domain host name value, and the Subsite domain value will be lost. For this reason, you will need +to ensure that you use the same host name in both Subsite and Fluent domain entries. diff --git a/lang/en.yml b/lang/en.yml index 6852671..87f27e5 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -47,13 +47,14 @@ en: SilverStripe\Subsites\Model\SubsiteDomain: DOMAIN: Domain DOMAIN_DESCRIPTION: 'Hostname of this subsite (exclude protocol). Allows wildcards (*).' + ISPRIMARY_DESCRIPTION: 'Mark this as the default domain for this subsite' IS_PRIMARY: 'Is Primary Domain?' PLURALNAME: 'Subsite Domains' PLURALS: one: 'A Subsite Domain' other: '{count} Subsite Domains' PROTOCOL_AUTOMATIC: Automatic - PROTOCOL_DESCRIPTION: 'Mark this as the default domain for this subsite' + PROTOCOL_DESCRIPTION: 'When generating links to this subsite, use the selected protocol.
Selecting ''Automatic'' means subsite links will default to the current protocol.' PROTOCOL_HTTP: 'http://' PROTOCOL_HTTPS: 'https://' Protocol: Protocol diff --git a/src/Extensions/HintsCacheKeyExtension.php b/src/Extensions/HintsCacheKeyExtension.php new file mode 100644 index 0000000..74fb594 --- /dev/null +++ b/src/Extensions/HintsCacheKeyExtension.php @@ -0,0 +1,22 @@ +getSubsiteId(); + } +} diff --git a/src/Extensions/SiteTreeSubsites.php b/src/Extensions/SiteTreeSubsites.php index 728feca..ed79f98 100644 --- a/src/Extensions/SiteTreeSubsites.php +++ b/src/Extensions/SiteTreeSubsites.php @@ -3,6 +3,7 @@ namespace SilverStripe\Subsites\Extensions; use Page; +use SilverStripe\CMS\Forms\SiteTreeURLSegmentField; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Control\Controller; use SilverStripe\Control\Director; @@ -150,6 +151,7 @@ class SiteTreeSubsites extends DataExtension // replace readonly link prefix $subsite = $this->owner->Subsite(); $nested_urls_enabled = Config::inst()->get(SiteTree::class, 'nested_urls'); + /** @var Subsite $subsite */ if ($subsite && $subsite->exists()) { // Use baseurl from domain $baseLink = $subsite->absoluteBaseURL(); @@ -163,7 +165,7 @@ class SiteTreeSubsites extends DataExtension } $urlsegment = $fields->dataFieldByName('URLSegment'); - if ($urlsegment) { + if ($urlsegment && $urlsegment instanceof SiteTreeURLSegmentField) { $urlsegment->setURLPrefix($baseLink); } } diff --git a/src/Model/Subsite.php b/src/Model/Subsite.php index 0ce4032..51247ae 100644 --- a/src/Model/Subsite.php +++ b/src/Model/Subsite.php @@ -114,7 +114,7 @@ class Subsite extends DataObject private static $summary_fields = [ 'Title', 'PrimaryDomain', - 'IsPublic' + 'IsPublic.Nice' ]; /** @@ -773,7 +773,7 @@ class Subsite extends DataObject $labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site'); $labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme'); $labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language'); - $labels['IsPublic'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access'); + $labels['IsPublic.Nice'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access'); $labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist'); $labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain'); $labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain'); diff --git a/src/Model/SubsiteDomain.php b/src/Model/SubsiteDomain.php index 66432e9..83f33b5 100644 --- a/src/Model/SubsiteDomain.php +++ b/src/Model/SubsiteDomain.php @@ -111,13 +111,14 @@ class SubsiteDomain extends DataObject self::PROTOCOL_HTTPS => _t(__CLASS__ . '.PROTOCOL_HTTPS', 'https://'), self::PROTOCOL_AUTOMATIC => _t(__CLASS__ . '.PROTOCOL_AUTOMATIC', 'Automatic') ]; - $fields = new FieldList( + $fields = FieldList::create( WildcardDomainField::create('Domain', $this->fieldLabel('Domain'), null, 255) ->setDescription(_t( __CLASS__ . '.DOMAIN_DESCRIPTION', 'Hostname of this subsite (exclude protocol). Allows wildcards (*).' )), OptionsetField::create('Protocol', $this->fieldLabel('Protocol'), $protocols) + ->setValue($this->Protocol ?: self::PROTOCOL_AUTOMATIC) ->setDescription(_t( __CLASS__ . '.PROTOCOL_DESCRIPTION', 'When generating links to this subsite, use the selected protocol.
' . @@ -125,7 +126,7 @@ class SubsiteDomain extends DataObject )), CheckboxField::create('IsPrimary', $this->fieldLabel('IsPrimary')) ->setDescription(_t( - __CLASS__ . '.PROTOCOL_DESCRIPTION', + __CLASS__ . '.ISPRIMARY_DESCRIPTION', 'Mark this as the default domain for this subsite' )) ); diff --git a/src/Pages/SubsitesVirtualPage.php b/src/Pages/SubsitesVirtualPage.php index b4c3d4f..fcd39b3 100644 --- a/src/Pages/SubsitesVirtualPage.php +++ b/src/Pages/SubsitesVirtualPage.php @@ -8,7 +8,6 @@ use SilverStripe\CMS\Model\VirtualPage; use SilverStripe\Control\Controller; use SilverStripe\Core\Config\Config; use SilverStripe\Forms\DropdownField; -use SilverStripe\Forms\LabelField; use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\TextareaField; use SilverStripe\Forms\TextField; @@ -113,7 +112,7 @@ class SubsitesVirtualPage extends VirtualPage 'Root.Main', TextareaField::create( 'CustomMetaKeywords', - $this->fieldLabel('CustomMetaTitle') + $this->fieldLabel('CustomMetaKeywords') )->setDescription(_t(__CLASS__ . '.OverrideNote', 'Overrides inherited value from the source')), 'MetaKeywords' ); @@ -121,7 +120,7 @@ class SubsitesVirtualPage extends VirtualPage 'Root.Main', TextareaField::create( 'CustomMetaDescription', - $this->fieldLabel('CustomMetaTitle') + $this->fieldLabel('CustomMetaDescription') )->setDescription(_t(__CLASS__ . '.OverrideNote', 'Overrides inherited value from the source')), 'MetaDescription' ); @@ -129,7 +128,7 @@ class SubsitesVirtualPage extends VirtualPage 'Root.Main', TextField::create( 'CustomExtraMeta', - $this->fieldLabel('CustomMetaTitle') + $this->fieldLabel('CustomExtraMeta') )->setDescription(_t(__CLASS__ . '.OverrideNote', 'Overrides inherited value from the source')), 'ExtraMeta' ); @@ -226,30 +225,20 @@ class SubsitesVirtualPage extends VirtualPage // 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; + $filters = [ + 'URLSegment' => $this->URLSegment, + 'ID:not' => $this->ID, + ]; if (Config::inst()->get(SiteTree::class, 'nested_urls')) { - if ($this->ParentID) { - $parentFilter = " AND \"SiteTree\".\"ParentID\" = $this->ParentID"; - } else { - $parentFilter = ' AND "SiteTree"."ParentID" = 0'; - } + $filters['ParentID'] = $this->ParentID ?: 0; } $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; - Subsite::$disable_subsite_filter = true; - $existingPage = DataObject::get_one( - SiteTree::class, - "\"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::class, - "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter", - false // disable cache, it doesn't include subsite status in the key - ); + Subsite::disable_subsite_filter(); + $existingPage = SiteTree::get()->filter($filters)->first(); + Subsite::disable_subsite_filter($origDisableSubsiteFilter); + $existingPageInSubsite = SiteTree::get()->filter($filters)->first(); // If URL has been vetoed because of an existing page, // be more specific and allow same URLSegments in different subsites diff --git a/tests/php/SiteTreeSubsitesTest.php b/tests/php/SiteTreeSubsitesTest.php index d165336..dc54afa 100644 --- a/tests/php/SiteTreeSubsitesTest.php +++ b/tests/php/SiteTreeSubsitesTest.php @@ -5,10 +5,10 @@ namespace SilverStripe\Subsites\Tests; use Page; use SilverStripe\CMS\Controllers\CMSMain; use SilverStripe\CMS\Controllers\ModelAsController; +use SilverStripe\CMS\Forms\SiteTreeURLSegmentField; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Control\Director; use SilverStripe\Core\Config\Config; -use SilverStripe\Core\Convert; use SilverStripe\ErrorPage\ErrorPage; use SilverStripe\Forms\FieldList; use SilverStripe\Security\Member; @@ -21,6 +21,7 @@ use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassB; use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestErrorPage; use SilverStripe\Versioned\Versioned; use SilverStripe\View\SSViewer; +use TractorCow\Fluent\Extension\FluentSiteTreeExtension; class SiteTreeSubsitesTest extends BaseSubsiteTest { @@ -33,7 +34,9 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest ]; protected static $illegal_extensions = [ - SiteTree::class => ['Translatable'] // @todo implement Translatable namespace + SiteTree::class => [ + FluentSiteTreeExtension::class, + ], ]; protected function setUp() @@ -449,7 +452,7 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest /** * @dataProvider provideAlternateAbsoluteLink - * @param name $pageFixtureName + * @param string $pageFixtureName * @param string|null $action * @param string $expectedAbsoluteLink */ @@ -465,4 +468,23 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest $this->assertEquals($expectedAbsoluteLink, $result); } + + public function testURLSegmentBaseIsSetToSubsiteBaseURL() + { + // This subsite has a domain with 'one.example.org' as the primary domain + /** @var Subsite $subsite */ + $subsite = $this->objFromFixture(Subsite::class, 'domaintest1'); + Subsite::changeSubsite($subsite); + + $page = new SiteTree(); + $page->SubsiteID = $subsite->ID; + $page->write(); + $fields = $page->getCMSFields(); + + /** @var SiteTreeURLSegmentField $urlSegmentField */ + $urlSegmentField = $fields->dataFieldByName('URLSegment'); + $this->assertInstanceOf(SiteTreeURLSegmentField::class, $urlSegmentField); + + $this->assertSame('http://one.example.org/', $urlSegmentField->getURLPrefix()); + } } diff --git a/tests/php/SubsitesVirtualPageTest.php b/tests/php/SubsitesVirtualPageTest.php index 99e9646..527927f 100644 --- a/tests/php/SubsitesVirtualPageTest.php +++ b/tests/php/SubsitesVirtualPageTest.php @@ -311,4 +311,41 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest Versioned::prepopulate_versionnumber_cache(SiteTree::class, 'Live', [$p->ID]); } } + + public function testValidURLSegmentWithUniquePageAndNestedURLs() + { + SiteTree::config()->set('nested_urls', true); + + $newPage = new SubsitesVirtualPage(); + $newPage->Title = 'My new page'; + $newPage->URLSegment = 'my-new-page'; + + $this->assertTrue($newPage->validURLSegment()); + } + + public function testValidURLSegmentWithExistingPageInSubsite() + { + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + Subsite::changeSubsite($subsite1->ID); + + SiteTree::config()->set('nested_urls', false); + + $similarContactUsPage = new SubsitesVirtualPage(); + $similarContactUsPage->Title = 'Similar to Contact Us in Subsite 1'; + $similarContactUsPage->URLSegment = 'contact-us'; + + $this->assertFalse($similarContactUsPage->validURLSegment()); + } + + public function testValidURLSegmentWithExistingPageInAnotherSubsite() + { + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + Subsite::changeSubsite($subsite1->ID); + + $similarStaffPage = new SubsitesVirtualPage(); + $similarStaffPage->Title = 'Similar to Staff page in main site'; + $similarStaffPage->URLSegment = 'staff'; + + $this->assertFalse($similarStaffPage->validURLSegment()); + } }