Skip to content

Commit

Permalink
FIX Improving support for cascading themes
Browse files Browse the repository at this point in the history
- Fixes an issue where themes would cascade "up" the list of themes
- Provides configuration for defining custom theme options with their own sets of cascading themes

Fixes #392
  • Loading branch information
ScopeyNZ committed Jul 12, 2019
1 parent 3afdd01 commit 2eb04ff
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 28 deletions.
4 changes: 3 additions & 1 deletion src/Extensions/SiteTreeSubsites.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use SilverStripe\Security\Security;
use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\State\SubsiteState;
use SilverStripe\View\SSViewer;

Expand Down Expand Up @@ -387,10 +388,11 @@ public function canPublish($member = null)
*/
public static function contentcontrollerInit($controller)
{
/** @var Subsite $subsite */
$subsite = Subsite::currentSubsite();

if ($subsite && $subsite->Theme) {
SSViewer::add_themes([$subsite->Theme]);
SSViewer::set_themes(ThemeResolver::singleton()->getThemeList($subsite));
}

if ($subsite && i18n::getData()->validate($subsite->Language)) {
Expand Down
4 changes: 2 additions & 2 deletions src/Model/Subsite.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\State\SubsiteState;
use SilverStripe\Versioned\Versioned;
use UnexpectedValueException;
Expand Down Expand Up @@ -182,7 +183,6 @@ public static function set_allowed_themes($themes)
/**
* Gets the subsite currently set in the session.
*
* @uses ControllerSubsites->controllerAugmentInit()
* @return DataObject The current Subsite
*/
public static function currentSubsite()
Expand Down Expand Up @@ -787,7 +787,7 @@ public function fieldLabels($includerelations = true)
*/
public function allowedThemes()
{
if ($themes = self::$allowed_themes) {
if (($themes = self::$allowed_themes) || ($themes = ThemeResolver::singleton()->getCustomThemeOptions())) {
return ArrayLib::valuekey($themes);
}

Expand Down
95 changes: 95 additions & 0 deletions src/Service/ThemeResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?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) {
// Check if the default is public themes
$publicDefault = $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);
}
}
143 changes: 143 additions & 0 deletions tests/php/Service/ThemeResolverTest.php
Original file line number Diff line number Diff line change
@@ -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 = [
SSViewer::PUBLIC_THEME,
'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
SSViewer::PUBLIC_THEME, // $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',
SSViewer::PUBLIC_THEME,
SSViewer::DEFAULT_THEME,
]],
'test',
$expected
],
// Many options
[
[
'aye' => [
'aye',
'thing',
SSViewer::DEFAULT_THEME,
],
'bee' => $expected = [
'subsite',
'backup',
SSViewer::PUBLIC_THEME,
SSViewer::DEFAULT_THEME,
],
'sea' => [
'mer',
'ocean',
SSViewer::DEFAULT_THEME,
],
],
'bee',
$expected
],
// Conflicting with root definitions
[
['main' => $expected = [
'subsite',
'backup',
SSViewer::PUBLIC_THEME,
SSViewer::DEFAULT_THEME,
]],
'main',
$expected
],
// Declaring a theme specifically should still work
[
['test' => [
'subsite',
'backup',
SSViewer::PUBLIC_THEME,
SSViewer::DEFAULT_THEME,
]],
'other',
array_merge(['other'], $this->themeList)
],
];
}
}
37 changes: 12 additions & 25 deletions tests/php/SiteTreeSubsitesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ErrorPage\ErrorPage;
use SilverStripe\Forms\FieldList;
use SilverStripe\Security\Member;
use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Subsites\Extensions\SiteTreeSubsites;
use SilverStripe\Subsites\Model\Subsite;
use SilverStripe\Subsites\Pages\SubsitesVirtualPage;
use SilverStripe\Subsites\Service\ThemeResolver;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassA;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestClassB;
use SilverStripe\Subsites\Tests\SiteTreeSubsitesTest\TestErrorPage;
Expand Down Expand Up @@ -396,41 +398,26 @@ public function duplicateToSubsiteProvider()
];
}

public function testIfSubsiteThemeIsSetToThemeList()
public function testThemeResolverIsUsedForSettingThemeList()
{
$defaultThemes = ['default'];
SSViewer::set_themes($defaultThemes);
$firstResolver = $this->createMock(ThemeResolver::class);
$firstResolver->expects($this->never())->method('getThemeList');
Injector::inst()->registerService($firstResolver, ThemeResolver::class);

$subsitePage = $this->objFromFixture(Page::class, 'home');
Subsite::changeSubsite($subsitePage->SubsiteID);
$controller = ModelAsController::controller_for($subsitePage);
SiteTree::singleton()->extend('contentcontrollerInit', $controller);

$this->assertEquals(
SSViewer::get_themes(),
$defaultThemes,
'Themes should not be modified when Subsite has no theme defined'
);
$secondResolver = $this->createMock(ThemeResolver::class);
$secondResolver->expects($this->once())->method('getThemeList');
Injector::inst()->registerService($secondResolver, ThemeResolver::class);

$pageWithTheme = $this->objFromFixture(Page::class, 'subsite1_home');
Subsite::changeSubsite($pageWithTheme->SubsiteID);
$controller = ModelAsController::controller_for($pageWithTheme);
$subsitePage = $this->objFromFixture(Page::class, 'subsite1_home');
Subsite::changeSubsite($subsitePage->SubsiteID);
$controller = ModelAsController::controller_for($subsitePage);
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()
Expand Down

0 comments on commit 2eb04ff

Please sign in to comment.