diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index 1988a14..a17af76 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -483,4 +483,17 @@ public function getTheme($name) {
throw new \InvalidArgumentException(sprintf('The theme %s does not exist.', $name));
}
+ /**
+ * {@inheritdoc}
+ */
+ public function hasUi($name) {
+ $themes = $this->listInfo();
+ if (isset($themes[$name])) {
+ if (!empty($themes[$name]->info['hidden'])) {
+ return $name == $this->getDefault();
+ }
+ return TRUE;
+ }
+ return FALSE;
+ }
}
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
index c46f96a..cb93e40 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
@@ -208,4 +208,18 @@ public function themeExists($theme);
*/
public function getTheme($name);
+ /**
+ * Determines if a theme should be shown in the user interface.
+ *
+ * To be shown in the UI the theme has to be installed. If the theme is hidden
+ * it will not be shown unless it is the default theme.
+ *
+ * @param string $name
+ * The name of the theme to check.
+ *
+ * @return bool
+ * TRUE if the theme should be shown in the UI, FALSE if not.
+ */
+ public function hasUi($name);
+
}
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 29c6e16..f0bbdd3 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -88,8 +88,13 @@ function block_page_top(array &$page_top) {
* An array of theme names.
*/
function block_themes_installed($theme_list) {
+ $theme_info = \Drupal::service('theme_handler')->listInfo();
foreach ($theme_list as $theme) {
- block_theme_initialize($theme);
+ // Don't initialize hidden themes as they are not displayed in the block
+ // management screens.
+ if (empty($theme_info[$theme]->info['hidden'])) {
+ block_theme_initialize($theme);
+ }
}
}
diff --git a/core/modules/block/src/Controller/BlockController.php b/core/modules/block/src/Controller/BlockController.php
index 2779a9b..7f75d0b 100644
--- a/core/modules/block/src/Controller/BlockController.php
+++ b/core/modules/block/src/Controller/BlockController.php
@@ -11,6 +11,7 @@
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Controller routines for admin block routes.
@@ -53,6 +54,11 @@ public static function create(ContainerInterface $container) {
* A #type 'page' render array containing the block region demo.
*/
public function demo($theme) {
+ // Deny access if the theme is not installed or not found.
+ if (!$this->themeHandler->hasUi($theme)) {
+ throw new NotFoundHttpException();
+ }
+
$page = [
'#title' => Html::escape($this->themeHandler->getName($theme)),
'#type' => 'page',
diff --git a/core/modules/block/src/Controller/BlockListController.php b/core/modules/block/src/Controller/BlockListController.php
index 72aa445..b0c5e3f 100644
--- a/core/modules/block/src/Controller/BlockListController.php
+++ b/core/modules/block/src/Controller/BlockListController.php
@@ -8,7 +8,10 @@
namespace Drupal\block\Controller;
use Drupal\Core\Entity\Controller\EntityListController;
+use Drupal\Core\Extension\ThemeHandlerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Defines a controller to list blocks.
@@ -16,6 +19,32 @@
class BlockListController extends EntityListController {
/**
+ * The theme handler.
+ *
+ * @var \Drupal\Core\Extension\ThemeHandlerInterface
+ */
+ protected $themeHandler;
+
+ /**
+ * Constructs the BlockListController.
+ *
+ * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
+ * The theme handler.
+ */
+ public function __construct(ThemeHandlerInterface $theme_handler) {
+ $this->themeHandler = $theme_handler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('theme_handler')
+ );
+ }
+
+ /**
* Shows the block administration page.
*
* @param string|null $theme
@@ -28,6 +57,11 @@ class BlockListController extends EntityListController {
*/
public function listing($theme = NULL, Request $request = NULL) {
$theme = $theme ?: $this->config('system.theme')->get('default');
+ // Deny access if the theme is not installed or not found.
+ if (!$this->themeHandler->hasUi($theme)) {
+ throw new NotFoundHttpException();
+ }
+
return $this->entityManager()->getListBuilder('block')->render($theme, $request);
}
diff --git a/core/modules/block/src/Plugin/Derivative/ThemeLocalTask.php b/core/modules/block/src/Plugin/Derivative/ThemeLocalTask.php
index 4e337db..ed9d095 100644
--- a/core/modules/block/src/Plugin/Derivative/ThemeLocalTask.php
+++ b/core/modules/block/src/Plugin/Derivative/ThemeLocalTask.php
@@ -50,7 +50,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
$default_theme = $this->themeHandler->getDefault();
foreach ($this->themeHandler->listInfo() as $theme_name => $theme) {
- if ($theme->status) {
+ if ($this->themeHandler->hasUi($theme_name)) {
$this->derivatives[$theme_name] = $base_plugin_definition;
$this->derivatives[$theme_name]['title'] = $theme->info['name'];
$this->derivatives[$theme_name]['route_parameters'] = array('theme' => $theme_name);
diff --git a/core/modules/block/src/Tests/BlockHiddenRegionTest.php b/core/modules/block/src/Tests/BlockHiddenRegionTest.php
index a6af2ce..bb42dce 100644
--- a/core/modules/block/src/Tests/BlockHiddenRegionTest.php
+++ b/core/modules/block/src/Tests/BlockHiddenRegionTest.php
@@ -56,7 +56,9 @@ public function testBlockNotInHiddenRegion() {
// Install "block_test_theme" and set it as the default theme.
$theme = 'block_test_theme';
- \Drupal::service('theme_handler')->install(array($theme));
+ // We need to install a non-hidden theme so that there is more than one
+ // local task.
+ \Drupal::service('theme_handler')->install(array($theme, 'stark'));
$this->config('system.theme')
->set('default', $theme)
->save();
diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php
index c98b042..261c3ae 100644
--- a/core/modules/block/src/Tests/BlockTest.php
+++ b/core/modules/block/src/Tests/BlockTest.php
@@ -197,9 +197,9 @@ function testBlock() {
*/
public function testBlockThemeSelector() {
// Install all themes.
- \Drupal::service('theme_handler')->install(array('bartik', 'seven'));
+ \Drupal::service('theme_handler')->install(['bartik', 'seven', 'stark']);
$theme_settings = $this->config('system.theme');
- foreach (array('bartik', 'classy', 'seven') as $theme) {
+ foreach (['bartik', 'seven', 'stark'] as $theme) {
$this->drupalGet('admin/structure/block/list/' . $theme);
$this->assertTitle(t('Block layout') . ' | Drupal');
// Select the 'Powered by Drupal' block to be placed.
diff --git a/core/modules/block/src/Tests/BlockUiTest.php b/core/modules/block/src/Tests/BlockUiTest.php
index 25b7942..34e1c88 100644
--- a/core/modules/block/src/Tests/BlockUiTest.php
+++ b/core/modules/block/src/Tests/BlockUiTest.php
@@ -90,6 +90,10 @@ public function testBlockDemoUiPage() {
\Drupal::service('theme_handler')->install(array('test_theme'));
$this->drupalGet('admin/structure/block/demo/test_theme');
$this->assertEscaped('Test theme');
+
+ \Drupal::service('theme_handler')->install(['stable']);
+ $this->drupalGet('admin/structure/block/demo/stable');
+ $this->assertResponse(404, 'Hidden themes that are not the default theme are not supported by the block demo screen');
}
/**
@@ -136,6 +140,21 @@ function testBlockAdminUiPage() {
$this->drupalGet('admin/structure/block');
$element = $this->xpath('//tr[contains(@class, :class)]', [':class' => 'region-title-header']);
$this->assertTrue(!empty($element));
+
+ // Ensure hidden themes do not appear in the UI. Enable another non base
+ // theme and place the local tasks block.
+ $this->assertTrue(\Drupal::service('theme_handler')->themeExists('classy'), 'The classy base theme is enabled');
+ $this->drupalPlaceBlock('local_tasks_block', ['region' => 'header']);
+ \Drupal::service('theme_installer')->install(['stable']);
+ \Drupal::service('theme_installer')->install(['test_theme']);
+ $this->drupalGet('admin/structure/block');
+ $theme_handler = \Drupal::service('theme_handler');
+ $this->assertLink($theme_handler->getName('classy'));
+ $this->assertLink($theme_handler->getName('test_theme'));
+ $this->assertNoLink($theme_handler->getName('stable'));
+
+ $this->drupalGet('admin/structure/block/list/stable');
+ $this->assertResponse(404, 'Placing blocks through UI is not possible for a hidden base theme.');
}
/**
diff --git a/core/modules/block/tests/src/Unit/Menu/BlockLocalTasksTest.php b/core/modules/block/tests/src/Unit/Menu/BlockLocalTasksTest.php
index f70a57c..6320917 100644
--- a/core/modules/block/tests/src/Unit/Menu/BlockLocalTasksTest.php
+++ b/core/modules/block/tests/src/Unit/Menu/BlockLocalTasksTest.php
@@ -27,7 +27,11 @@ protected function setUp() {
$themes = array();
$themes['test_a'] = (object) array(
- 'status' => 0,
+ 'status' => 1,
+ 'info' => array(
+ 'name' => 'test_a',
+ 'hidden' => TRUE,
+ ),
);
$themes['test_b'] = (object) array(
'status' => 1,
@@ -45,6 +49,13 @@ protected function setUp() {
$theme_handler->expects($this->any())
->method('listInfo')
->will($this->returnValue($themes));
+ $theme_handler->expects($this->any())
+ ->method('hasUi')
+ ->willReturnMap([
+ ['test_a', FALSE],
+ ['test_b', TRUE],
+ ['test_c', TRUE],
+ ]);
$container = new ContainerBuilder();
$container->set('config.factory', $config_factory);
diff --git a/core/modules/block_content/src/Tests/BlockContentTypeTest.php b/core/modules/block_content/src/Tests/BlockContentTypeTest.php
index e669a72..d1405b9 100644
--- a/core/modules/block_content/src/Tests/BlockContentTypeTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentTypeTest.php
@@ -185,17 +185,15 @@ public function testsBlockContentAddTypes() {
->getStorage('block_content');
// Install all themes.
- \Drupal::service('theme_handler')->install(array('bartik', 'seven'));
- $themes = array('bartik', 'seven', 'classy');
+ \Drupal::service('theme_handler')->install(['bartik', 'seven', 'stark']);
$theme_settings = $this->config('system.theme');
- foreach ($themes as $default_theme) {
+ foreach (['bartik', 'seven', 'stark'] as $default_theme) {
// Change the default theme.
$theme_settings->set('default', $default_theme)->save();
\Drupal::service('router.builder')->rebuild();
// For each installed theme, go to its block page and test the redirects.
- $themes = array('bartik', 'classy', 'seven');
- foreach ($themes as $theme) {
+ foreach (['bartik', 'seven', 'stark'] as $theme) {
// Test that adding a block from the 'place blocks' form sends you to the
// block configure form.
$path = $theme == $default_theme ? 'admin/structure/block' : "admin/structure/block/list/$theme";
diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php
index 395c743..a782e15 100644
--- a/core/modules/system/src/Form/ThemeSettingsForm.php
+++ b/core/modules/system/src/Form/ThemeSettingsForm.php
@@ -108,13 +108,12 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme =
$themes = $this->themeHandler->listInfo();
- // Deny access if the theme is not installed or not found.
- if (!empty($theme) && (empty($themes[$theme]) || !$themes[$theme]->status)) {
- throw new NotFoundHttpException();
- }
-
// Default settings are defined in theme_get_setting() in includes/theme.inc
if ($theme) {
+ // Deny access if the theme is not installed or not found.
+ if (!$this->themeHandler->hasUi($theme)) {
+ throw new NotFoundHttpException();
+ }
$var = 'theme_' . $theme . '_settings';
$config_key = $theme . '.settings';
$themes = $this->themeHandler->listInfo();
diff --git a/core/modules/system/src/Plugin/Derivative/ThemeLocalTask.php b/core/modules/system/src/Plugin/Derivative/ThemeLocalTask.php
index 9c9de19..7193ea5 100644
--- a/core/modules/system/src/Plugin/Derivative/ThemeLocalTask.php
+++ b/core/modules/system/src/Plugin/Derivative/ThemeLocalTask.php
@@ -48,7 +48,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->themeHandler->listInfo() as $theme_name => $theme) {
- if ($theme->status) {
+ if ($this->themeHandler->hasUi($theme_name)) {
$this->derivatives[$theme_name] = $base_plugin_definition;
$this->derivatives[$theme_name]['title'] = $theme->info['name'];
$this->derivatives[$theme_name]['route_parameters'] = array('theme' => $theme_name);
diff --git a/core/modules/system/src/Tests/System/ThemeTest.php b/core/modules/system/src/Tests/System/ThemeTest.php
index 8b2836b..476a1aa 100644
--- a/core/modules/system/src/Tests/System/ThemeTest.php
+++ b/core/modules/system/src/Tests/System/ThemeTest.php
@@ -52,6 +52,9 @@ function testThemeSettings() {
$this->assertResponse(404, 'The theme settings form URL for a uninstalled theme could not be found.');
$this->drupalGet('admin/appearance/settings/' . $this->randomMachineName());
$this->assertResponse(404, 'The theme settings form URL for a non-existent theme could not be found.');
+ $this->assertTrue(\Drupal::service('theme_installer')->install(['stable']));
+ $this->drupalGet('admin/appearance/settings/stable');
+ $this->assertResponse(404, 'The theme settings form URL for a hidden theme are unavailable.');
// Specify a filesystem path to be used for the logo.
$file = current($this->drupalGetTestFiles('image'));
@@ -190,6 +193,14 @@ function testThemeSettings() {
// The logo field should only be present on the global theme settings form.
$this->assertNoFieldByName('logo_path');
$this->drupalPostForm(NULL, [], t('Save configuration'));
+
+ // Ensure only vlaid themes are listed in the local tasks.
+ $this->drupalPlaceBlock('local_tasks_block', ['region' => 'header']);
+ $this->drupalGet('admin/appearance/settings');
+ $theme_handler = \Drupal::service('theme_handler');
+ $this->assertLink($theme_handler->getName('classy'));
+ $this->assertLink($theme_handler->getName('bartik'));
+ $this->assertNoLink($theme_handler->getName('stable'));
}
/**
@@ -255,8 +266,14 @@ function testAdministrationTheme() {
* Test switching the default theme.
*/
function testSwitchDefaultTheme() {
+ /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
+ $theme_handler = \Drupal::service('theme_handler');
+ // First, Install Stark and set it as the default theme programmatically.
+ $theme_handler->install(array('stark'));
+ $theme_handler->setDefault('stark');
+
// Install Bartik and set it as the default theme.
- \Drupal::service('theme_handler')->install(array('bartik'));
+ $theme_handler->install(array('bartik'));
$this->drupalGet('admin/appearance');
$this->clickLink(t('Set as default'));
$this->assertEqual($this->config('system.theme')->get('default'), 'bartik');
@@ -266,10 +283,10 @@ function testSwitchDefaultTheme() {
$this->assertText('Bartik(' . t('active tab') . ')', 'Default local task on blocks admin page is the default theme.');
// Switch back to Stark and test again to test that the menu cache is cleared.
$this->drupalGet('admin/appearance');
- // Classy is the first 'Set as default' link.
- $this->clickLink(t('Set as default'), 0);
+ // Stark is the first 'Set as default' link.
+ $this->clickLink(t('Set as default'));
$this->drupalGet('admin/structure/block');
- $this->assertText('Classy(' . t('active tab') . ')', 'Default local task on blocks admin page has changed.');
+ $this->assertText('Stark(' . t('active tab') . ')', 'Default local task on blocks admin page has changed.');
}
/**
@@ -328,8 +345,8 @@ function testUninstallingThemes() {
// base theme of bartik.
$this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
- // Change the default theme to stark, stark is third in the list.
- $this->clickLink(t('Set as default'), 2);
+ // Change the default theme to stark, stark is second in the list.
+ $this->clickLink(t('Set as default'), 1);
// Check that bartik can be uninstalled now.
$this->assertRaw('Uninstall Bartik theme', 'A link to uninstall the Bartik theme does appear on the theme settings page.');
@@ -344,9 +361,9 @@ function testUninstallingThemes() {
// Seven is the second in the list.
$this->clickLink(t('Uninstall'));
$this->assertRaw('The Seven theme has been uninstalled');
- // Now uninstall classy.
- $this->clickLink(t('Uninstall'));
- $this->assertRaw('The Classy theme has been uninstalled');
+
+ // Check that the classy theme still can't be uninstalled as it is hidden.
+ $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
}
/**
diff --git a/core/profiles/standard/config/install/block.block.classy_page_title.yml b/core/profiles/standard/config/install/block.block.classy_page_title.yml
deleted file mode 100644
index 4236224..0000000
--- a/core/profiles/standard/config/install/block.block.classy_page_title.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-langcode: en
-status: true
-dependencies:
- theme:
- - classy
-id: classy_page_title
-theme: classy
-region: content
-weight: -50
-provider: null
-plugin: page_title_block
-settings:
- id: page_title_block
- label: 'Page title'
- provider: core
- label_display: '0'
-visibility: { }
diff --git a/core/themes/classy/classy.info.yml b/core/themes/classy/classy.info.yml
index 2b7705b..5ed9b80 100644
--- a/core/themes/classy/classy.info.yml
+++ b/core/themes/classy/classy.info.yml
@@ -5,6 +5,7 @@ package: Core
version: VERSION
core: 8.x
base theme: false
+hidden: true
libraries:
- classy/base
diff --git a/core/themes/stable/stable.info.yml b/core/themes/stable/stable.info.yml
index 687c434..960a683 100644
--- a/core/themes/stable/stable.info.yml
+++ b/core/themes/stable/stable.info.yml
@@ -5,3 +5,4 @@ package: Core
version: VERSION
core: 8.x
base theme: false
+hidden: true