Merge branch '2.2' into 2.3

This commit is contained in:
Robbie Averill 2019-09-25 15:00:47 -07:00
commit 5d015c7a96
7 changed files with 311 additions and 32 deletions

View File

@ -96,7 +96,55 @@ In some Browsers the SubsiteID is visible if you hover over the "Edit" link in t
### Subsite-specific themes ### Subsite-specific themes
Download a second theme from http://www.silverstripe.com/themes/ and put it in your themes folder. Open admin/subsites?flush=1 and select one of your subsites from the menu on the bottom-left. You should see a Theme dropdown in the subsite details, and it should list both your original theme and the new theme. Select the new theme in the dropdown. Now, this subsite will use a different theme from the main site. Download a second theme from http://www.silverstripe.com/themes/ and put it in your themes folder. Open
admin/subsites?flush=1 and select one of your subsites from the menu on the bottom-left. You should see a
Theme dropdown in the subsite details, and it should list both your original theme and the new theme. Select the new
theme in the dropdown. Now, this subsite will use a different theme from the main site.
#### Cascading themes
In SilverStripe 4 themes will resolve theme files by looking through a list of themes (see the documentation on
[creating your own theme](https://docs.silverstripe.org/en/4/developer_guides/templates/themes/#developing-your-own-theme)).
Subsites will inherit this configuration for the order of themes. Choosing a theme for a Subsite will set the list of
themes to that chosen theme, and all themes that are defined below the chosen theme in priority. For example, with a
theme configuration as follows:
```yaml
SilverStripe\View\SSViewer:
themes:
- '$public'
- 'my-theme'
- 'watea'
- 'starter'
- '$default'
```
Choosing `watea` in your Subsite will create a cascading config as follows:
```yaml
themes:
- 'watea'
- '$public'
- 'starter'
- '$default'
```
You may also completely define your own cascading theme lists for CMS users to choose as theme options for their
subsite:
```yaml
SilverStripe\Subsites\Service\ThemeResolver:
theme_options:
normal:
- '$public'
- 'watea'
- 'starter'
- '$default'
special:
- 'my-theme'
- 'starter'
- '$default'
```
### Limit available themes for a subsite ### Limit available themes for a subsite

View File

@ -26,6 +26,7 @@ use SilverStripe\Security\Member;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\SiteConfig\SiteConfig; use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Model\Subsite; use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\State\SubsiteState; use SilverStripe\Subsites\State\SubsiteState;
use SilverStripe\View\SSViewer; use SilverStripe\View\SSViewer;
@ -390,10 +391,11 @@ class SiteTreeSubsites extends DataExtension
*/ */
public static function contentcontrollerInit($controller) public static function contentcontrollerInit($controller)
{ {
/** @var Subsite $subsite */
$subsite = Subsite::currentSubsite(); $subsite = Subsite::currentSubsite();
if ($subsite && $subsite->Theme) { if ($subsite && $subsite->Theme) {
SSViewer::add_themes([$subsite->Theme]); SSViewer::set_themes(ThemeResolver::singleton()->getThemeList($subsite));
} }
if ($subsite && i18n::getData()->validate($subsite->Language)) { if ($subsite && i18n::getData()->validate($subsite->Language)) {

View File

@ -28,6 +28,7 @@ use SilverStripe\Security\Group;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
use SilverStripe\Security\Permission; use SilverStripe\Security\Permission;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\State\SubsiteState; use SilverStripe\Subsites\State\SubsiteState;
use SilverStripe\Versioned\Versioned; use SilverStripe\Versioned\Versioned;
use UnexpectedValueException; use UnexpectedValueException;
@ -183,7 +184,6 @@ class Subsite extends DataObject
/** /**
* Gets the subsite currently set in the session. * Gets the subsite currently set in the session.
* *
* @uses ControllerSubsites->controllerAugmentInit()
* @return DataObject The current Subsite * @return DataObject The current Subsite
*/ */
public static function currentSubsite() public static function currentSubsite()
@ -788,7 +788,7 @@ class Subsite extends DataObject
*/ */
public function allowedThemes() public function allowedThemes()
{ {
if ($themes = self::$allowed_themes) { if (($themes = self::$allowed_themes) || ($themes = ThemeResolver::singleton()->getCustomThemeOptions())) {
return ArrayLib::valuekey($themes); return ArrayLib::valuekey($themes);
} }
@ -875,7 +875,7 @@ class Subsite extends DataObject
} }
// If there are no objects, default to the current hostname // If there are no objects, default to the current hostname
return $_SERVER['HTTP_HOST']; return Director::host();
} }
/** /**

View File

@ -186,7 +186,7 @@ class SubsiteDomain extends DataObject
*/ */
public function getSubstitutedDomain() public function getSubstitutedDomain()
{ {
$currentHost = $_SERVER['HTTP_HOST']; $currentHost = Director::host();
// If there are wildcards in the primary domain (not recommended), make some // If there are wildcards in the primary domain (not recommended), make some
// educated guesses about what to replace them with: // educated guesses about what to replace them with:

View File

@ -0,0 +1,99 @@
<?php
namespace SilverStripe\Subsites\Service;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\View\SSViewer;
class ThemeResolver
{
use Injectable;
use Configurable;
/**
* Cascading definitions for themes, keyed by the name they should appear under in the CMS. For example:
*
* [
* 'theme-1' => [
* '$public',
* 'starter',
* '$default',
* ],
* 'theme-2' => [
* 'custom',
* 'watea',
* 'starter',
* '$public',
* '$default',
* ]
* ]
*
* @config
* @var null|array[]
*/
private static $theme_options;
/**
* Get the list of themes for the given sub site that can be given to SSViewer::set_themes
*
* @param Subsite $site
* @return array
*/
public function getThemeList(Subsite $site)
{
$themes = array_values(SSViewer::get_themes());
$siteTheme = $site->Theme;
if (!$siteTheme) {
return $themes;
}
$customOptions = $this->config()->get('theme_options');
if ($customOptions && isset($customOptions[$siteTheme])) {
return $customOptions[$siteTheme];
}
// Ensure themes don't cascade "up" the list
$index = array_search($siteTheme, $themes);
if ($index > 0) {
// 4.0 didn't have support for themes in the public webroot
$constant = SSViewer::class . '::PUBLIC_THEME';
$publicConstantDefined = defined($constant);
// Check if the default is public themes
$publicDefault = $publicConstantDefined && $themes[0] === SSViewer::PUBLIC_THEME;
// Take only those that appear after theme chosen (non-inclusive)
$themes = array_slice($themes, $index + 1);
// Add back in public
if ($publicDefault) {
array_unshift($themes, SSViewer::PUBLIC_THEME);
}
}
// Add our theme
array_unshift($themes, $siteTheme);
return $themes;
}
/**
* Get a list of custom cascading theme definitions if available
*
* @return null|array
*/
public function getCustomThemeOptions()
{
$config = $this->config()->get('theme_options');
if (!$config) {
return null;
}
return array_keys($config);
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace SilverStripe\Subsites\Tests\Service;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\View\SSViewer;
class ThemeResolverTest extends SapphireTest
{
protected $themeList = [
'$public',
'custom',
'main',
'backup',
SSViewer::DEFAULT_THEME,
];
protected function setUp()
{
parent::setUp();
// Setup known theme config
Config::modify()->set(SSViewer::class, 'themes', $this->themeList);
}
public function testSubsiteWithoutThemeReturnsDefaultThemeList()
{
$subsite = new Subsite();
$resolver = new ThemeResolver();
$this->assertSame($this->themeList, $resolver->getThemeList($subsite));
}
public function testSubsiteWithCustomThemePrependsToList()
{
$subsite = new Subsite();
$subsite->Theme = 'subsite';
$resolver = new ThemeResolver();
$expected = array_merge(['subsite'], $this->themeList);
$this->assertSame($expected, $resolver->getThemeList($subsite));
}
public function testSubsiteWithCustomThemeDoesNotCascadeUpTheList()
{
$subsite = new Subsite();
$subsite->Theme = 'main';
$resolver = new ThemeResolver();
$expected = [
'main', // 'main' is moved to the top
'$public', // $public is preserved
// Anything above 'main' is removed
'backup',
SSViewer::DEFAULT_THEME,
];
$this->assertSame($expected, $resolver->getThemeList($subsite));
}
/**
* @dataProvider customThemeDefinitionsAreRespectedProvider
*/
public function testCustomThemeDefinitionsAreRespected($themeOptions, $siteTheme, $expected)
{
Config::modify()->set(ThemeResolver::class, 'theme_options', $themeOptions);
$subsite = new Subsite();
$subsite->Theme = $siteTheme;
$resolver = new ThemeResolver();
$this->assertSame($expected, $resolver->getThemeList($subsite));
}
public function customThemeDefinitionsAreRespectedProvider()
{
return [
// Simple
[
['test' => $expected = [
'subsite',
'backup',
'$public',
SSViewer::DEFAULT_THEME,
]],
'test',
$expected
],
// Many options
[
[
'aye' => [
'aye',
'thing',
SSViewer::DEFAULT_THEME,
],
'bee' => $expected = [
'subsite',
'backup',
'$public',
SSViewer::DEFAULT_THEME,
],
'sea' => [
'mer',
'ocean',
SSViewer::DEFAULT_THEME,
],
],
'bee',
$expected
],
// Conflicting with root definitions
[
['main' => $expected = [
'subsite',
'backup',
'$public',
SSViewer::DEFAULT_THEME,
]],
'main',
$expected
],
// Declaring a theme specifically should still work
[
['test' => [
'subsite',
'backup',
'$public',
SSViewer::DEFAULT_THEME,
]],
'other',
array_merge(['other'], $this->themeList)
],
];
}
}

View File

@ -9,6 +9,8 @@ use SilverStripe\CMS\Forms\SiteTreeURLSegmentField;
use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ErrorPage\ErrorPage; use SilverStripe\ErrorPage\ErrorPage;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
@ -16,6 +18,7 @@ use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Extensions\SiteTreeSubsites; use SilverStripe\Subsites\Extensions\SiteTreeSubsites;
use SilverStripe\Subsites\Model\Subsite; use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Pages\SubsitesVirtualPage; use SilverStripe\Subsites\Pages\SubsitesVirtualPage;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassA; use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassA;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassB; use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassB;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestErrorPage; use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestErrorPage;
@ -399,41 +402,25 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest
]; ];
} }
public function testIfSubsiteThemeIsSetToThemeList() public function testThemeResolverIsUsedForSettingThemeList()
{ {
$defaultThemes = ['default']; $firstResolver = $this->createMock(ThemeResolver::class);
SSViewer::set_themes($defaultThemes); $firstResolver->expects($this->never())->method('getThemeList');
Injector::inst()->registerService($firstResolver, ThemeResolver::class);
$subsitePage = $this->objFromFixture(Page::class, 'home'); $subsitePage = $this->objFromFixture(Page::class, 'home');
Subsite::changeSubsite($subsitePage->SubsiteID); Subsite::changeSubsite($subsitePage->SubsiteID);
$controller = ModelAsController::controller_for($subsitePage); $controller = ModelAsController::controller_for($subsitePage);
SiteTree::singleton()->extend('contentcontrollerInit', $controller); SiteTree::singleton()->extend('contentcontrollerInit', $controller);
$this->assertEquals( $secondResolver = $this->createMock(ThemeResolver::class);
SSViewer::get_themes(), $secondResolver->expects($this->once())->method('getThemeList');
$defaultThemes, Injector::inst()->registerService($secondResolver, ThemeResolver::class);
'Themes should not be modified when Subsite has no theme defined'
);
$pageWithTheme = $this->objFromFixture(Page::class, 'subsite1_home'); $subsitePage = $this->objFromFixture(Page::class, 'subsite1_home');
Subsite::changeSubsite($pageWithTheme->SubsiteID); Subsite::changeSubsite($subsitePage->SubsiteID);
$controller = ModelAsController::controller_for($pageWithTheme); $controller = ModelAsController::controller_for($subsitePage);
SiteTree::singleton()->extend('contentcontrollerInit', $controller); SiteTree::singleton()->extend('contentcontrollerInit', $controller);
$subsiteTheme = $pageWithTheme->Subsite()->Theme;
$allThemes = SSViewer::get_themes();
$this->assertContains(
$subsiteTheme,
$allThemes,
'Themes should be modified when Subsite has theme defined'
);
$this->assertEquals(
$subsiteTheme,
array_shift($allThemes),
'Subsite theme should be prepeded to theme list'
);
} }
public function provideAlternateAbsoluteLink() public function provideAlternateAbsoluteLink()