diff --git a/core/lib/Drupal/Core/Theme/MissingThemeDependencyException.php b/core/lib/Drupal/Core/Theme/MissingThemeDependencyException.php new file mode 100644 index 0000000..c3f72af --- /dev/null +++ b/core/lib/Drupal/Core/Theme/MissingThemeDependencyException.php @@ -0,0 +1,48 @@ +theme = $theme; + } + + /** + * Gets the machine name of the missing theme. + * + * @return string + * The machine name of the theme that is missing. + */ + public function getMissingThemeName() { + return $this->theme; + } + +} diff --git a/core/lib/Drupal/Core/Theme/ThemeInitialization.php b/core/lib/Drupal/Core/Theme/ThemeInitialization.php index 95f27a9..af4b2c6 100644 --- a/core/lib/Drupal/Core/Theme/ThemeInitialization.php +++ b/core/lib/Drupal/Core/Theme/ThemeInitialization.php @@ -109,6 +109,9 @@ public function getActiveThemeByName($theme_name) { $ancestor = $theme_name; while ($ancestor && isset($themes[$ancestor]->base_theme)) { $ancestor = $themes[$ancestor]->base_theme; + if (!$this->themeHandler->themeExists($ancestor)) { + throw new MissingThemeDependencyException(sprintf('Base theme %s has not been installed.', $ancestor), $ancestor); + } $base_themes[] = $themes[$ancestor]; } diff --git a/core/lib/Drupal/Core/Theme/ThemeInitializationInterface.php b/core/lib/Drupal/Core/Theme/ThemeInitializationInterface.php index f2b1547..cd5c1be 100644 --- a/core/lib/Drupal/Core/Theme/ThemeInitializationInterface.php +++ b/core/lib/Drupal/Core/Theme/ThemeInitializationInterface.php @@ -34,6 +34,9 @@ public function initTheme($theme_name); * * @return \Drupal\Core\Theme\ActiveTheme * An active theme object instance for the given theme. + * + * @throws \Drupal\Core\Theme\MissingThemeDependencyException + * Thrown when base theme for installed theme is not installed. */ public function getActiveThemeByName($theme_name); diff --git a/core/modules/config/src/Tests/ConfigImportInstallProfileTest.php b/core/modules/config/src/Tests/ConfigImportInstallProfileTest.php index 19ef2ee..e15d3ac 100644 --- a/core/modules/config/src/Tests/ConfigImportInstallProfileTest.php +++ b/core/modules/config/src/Tests/ConfigImportInstallProfileTest.php @@ -67,6 +67,7 @@ public function testInstallProfileValidation() { $core['module']['testing_config_import'] = 0; unset($core['module']['syslog']); unset($core['theme']['stark']); + $core['theme']['stable'] = 0; $core['theme']['classy'] = 0; $sync->write('core.extension', $core); $sync->deleteAll('syslog.'); diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php index 0239a41..329acf6 100644 --- a/core/modules/system/src/Controller/DbUpdateController.php +++ b/core/modules/system/src/Controller/DbUpdateController.php @@ -10,11 +10,15 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Extension\ThemeHandlerInterface; +use Drupal\Core\Extension\ThemeInstallerInterface; use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; use Drupal\Core\Render\BareHtmlPageRendererInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Site\Settings; use Drupal\Core\State\StateInterface; +use Drupal\Core\Theme\MissingThemeDependencyException; +use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\Core\Update\UpdateRegistry; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -83,6 +87,27 @@ class DbUpdateController extends ControllerBase { protected $postUpdateRegistry; /** + * The theme manager. + * + * @var \Drupal\Core\Theme\ThemeManagerInterface + */ + protected $themeManager; + + /** + * The theme handler. + * + * @var \Drupal\Core\Extension\ThemeHandlerInterface + */ + protected $themeHandler; + + /** + * The theme installer. + * + * @var \Drupal\Core\Extension\ThemeInstallerInterface + */ + protected $themeInstaller; + + /** * Constructs a new UpdateController. * * @param string $root @@ -101,8 +126,14 @@ class DbUpdateController extends ControllerBase { * The bare HTML page renderer. * @param \Drupal\Core\Update\UpdateRegistry $post_update_registry * The post update registry. + * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager + * The theme manager. + * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler + * The theme handler. + * @param \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer + * The theme installer. */ - public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, UpdateRegistry $post_update_registry) { + public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, UpdateRegistry $post_update_registry, ThemeManagerInterface $theme_manager, ThemeHandlerInterface $theme_handler, ThemeInstallerInterface $theme_installer) { $this->root = $root; $this->keyValueExpirableFactory = $key_value_expirable_factory; $this->cache = $cache; @@ -111,6 +142,9 @@ public function __construct($root, KeyValueExpirableFactoryInterface $key_value_ $this->account = $account; $this->bareHtmlPageRenderer = $bare_html_page_renderer; $this->postUpdateRegistry = $post_update_registry; + $this->themeManager = $theme_manager; + $this->themeHandler = $theme_handler; + $this->themeInstaller = $theme_installer; } /** @@ -125,7 +159,10 @@ public static function create(ContainerInterface $container) { $container->get('module_handler'), $container->get('current_user'), $container->get('bare_html_page_renderer'), - $container->get('update.post_update_registry') + $container->get('update.post_update_registry'), + $container->get('theme.manager'), + $container->get('theme_handler'), + $container->get('theme_installer') ); } @@ -150,6 +187,8 @@ public function handle($op, Request $request) { drupal_load_updates(); update_fix_compatibility(); + // Make sure that update pages dependencies has been met. + $this->ensureActiveThemeDependencies(); if ($request->query->get('continue')) { $_SESSION['update_ignore_warnings'] = TRUE; @@ -706,4 +745,26 @@ protected function getModuleUpdates() { return $return; } + /** + * Ensures that all the dependencies for active theme has been met. + */ + protected function ensureActiveThemeDependencies() { + \Drupal::cache('bootstrap')->delete('system_list'); + \Drupal::state()->set('system.theme.data', array()); + // Ensure that at least the maintenance page has a working theme where all + // of the parent themes have been enabled. + do { + try { + $this->themeManager->getActiveTheme(); + break; + } + catch (MissingThemeDependencyException $e) { + // If this tries to install an unknown theme then it will throw an + // exception. + $theme_installed = $this->themeInstaller->install([$e->getMissingThemeName()]); + $this->themeHandler->rebuildThemeData(); + } + } while ($theme_installed); + } + } diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 69044a1..97f726f 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1810,6 +1810,9 @@ function system_update_8011() { */ function system_update_8012() { $theme_handler = \Drupal::service('theme_handler'); + if ($theme_handler->themeExists('stable')) { + return; + } // Ensure we have fresh info. $theme_handler->rebuildThemeData(); foreach ($theme_handler->listInfo() as $theme) { diff --git a/core/themes/classy/classy.info.yml b/core/themes/classy/classy.info.yml index 2b7705b..2ad47a9 100644 --- a/core/themes/classy/classy.info.yml +++ b/core/themes/classy/classy.info.yml @@ -4,7 +4,6 @@ description: 'A base theme with sensible default CSS classes added. Learn how to package: Core version: VERSION core: 8.x -base theme: false libraries: - classy/base