diff --git a/core/core.services.yml b/core/core.services.yml index 4e3749a5e185d675de09688deefcd5f5f1487551..e73b944301bc98edb3aa367bf714518730542bb9 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1554,7 +1554,7 @@ services: Drupal\Core\Theme\ThemeInitializationInterface: '@theme.initialization' theme.registry: class: Drupal\Core\Theme\Registry - arguments: ['%app.root%', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization', '@cache.bootstrap', '@extension.list.module'] + arguments: ['%app.root%', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization', '@cache.bootstrap', '@extension.list.module', '@kernel'] tags: - { name: needs_destruction } calls: diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index e72de8be75eb29c5804502cf7d9eb0c7b4a1adc7..d4f02d7108ec931cd4cccaff78a6262d41b10136 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -10,7 +10,9 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Lock\LockBackendInterface; +use Drupal\Core\Update\UpdateKernel; use Drupal\Core\Utility\ThemeRegistry; +use Symfony\Component\HttpKernel\HttpKernelInterface; /** * Defines the theme registry service. @@ -178,10 +180,12 @@ class Registry implements DestructableInterface { * The cache backend interface to use for the runtime theme registry data. * @param \Drupal\Core\Extension\ModuleExtensionList $module_list * The module list. + * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel + * The kernel. * @param string $theme_name * (optional) The name of the theme for which to construct the registry. */ - public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, CacheBackendInterface $runtime_cache, ModuleExtensionList $module_list, $theme_name = NULL) { + public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, CacheBackendInterface $runtime_cache, ModuleExtensionList $module_list, protected HttpKernelInterface $kernel, $theme_name = NULL) { $this->root = $root; $this->cache = $cache; $this->lock = $lock; @@ -252,11 +256,30 @@ public function get() { return $cached; } } - $this->build(); - // Only persist it if all modules are loaded to ensure it is complete. - if ($this->moduleHandler->isLoaded()) { - $this->setCache(); + + // Some theme hook implementations such as the one in Views request a lot of + // information such as field schemas. These might be broken until an update + // is run, so we need to build a limited registry while on update.php. + if ($this->kernel instanceof UpdateKernel) { + $module_list = $this->moduleHandler->getModuleList(); + $filter_list = array_intersect_key($module_list, ['system' => TRUE]); + + // Call ::build() with only the system module and then revert. + $this->moduleHandler->setModuleList($filter_list); + $this->build(); + $this->moduleHandler->setModuleList($module_list); + + // We might have poisoned the cache with only info from 'system'. + $this->cache->delete("theme_registry:build:modules"); + } + else { + $this->build(); + // Only persist it if all modules are loaded to ensure it is complete. + if ($this->moduleHandler->isLoaded()) { + $this->setCache(); + } } + return $this->registry[$this->theme->getName()]; } diff --git a/core/modules/system/src/Theme/DbUpdateNegotiator.php b/core/modules/system/src/Theme/DbUpdateNegotiator.php index f765412edb72475e9b5bb6145be11fbbf5f9e78f..d80d610c3d9e0a83c2e909cdfe4281933a5538bb 100644 --- a/core/modules/system/src/Theme/DbUpdateNegotiator.php +++ b/core/modules/system/src/Theme/DbUpdateNegotiator.php @@ -5,7 +5,6 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\Core\Site\Settings; use Drupal\Core\Theme\ThemeNegotiatorInterface; /** @@ -51,7 +50,8 @@ public function applies(RouteMatchInterface $route_match) { * {@inheritdoc} */ public function determineActiveTheme(RouteMatchInterface $route_match) { - return Settings::get('maintenance_theme') ?: 'claro'; + // The update page always uses Claro to ensure stability. + return 'claro'; } } diff --git a/core/modules/system/tests/modules/update_test_broken_theme_hook/update_test_broken_theme_hook.info.yml b/core/modules/system/tests/modules/update_test_broken_theme_hook/update_test_broken_theme_hook.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..6ed5a95cc6f4552f9fb25015dfe96f030399c765 --- /dev/null +++ b/core/modules/system/tests/modules/update_test_broken_theme_hook/update_test_broken_theme_hook.info.yml @@ -0,0 +1,5 @@ +name: 'Update test with broken theme hook' +type: module +description: 'Support module for update testing.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/update_test_broken_theme_hook/update_test_broken_theme_hook.module b/core/modules/system/tests/modules/update_test_broken_theme_hook/update_test_broken_theme_hook.module new file mode 100644 index 0000000000000000000000000000000000000000..8758efca08e62349747cf607152c526bc029e796 --- /dev/null +++ b/core/modules/system/tests/modules/update_test_broken_theme_hook/update_test_broken_theme_hook.module @@ -0,0 +1,13 @@ +<?php + +/** + * @file + * Hook implementations for the update_test_broken_theme_hook module. + */ + +/** + * Implements hook_theme(). + */ +function update_test_broken_theme_hook_theme($existing, $type, $theme, $path) { + throw new \Exception('This mimics an exception caused by unstable dependencies.'); +} diff --git a/core/tests/Drupal/FunctionalTests/Update/UpdateReducedThemeRegistryTest.php b/core/tests/Drupal/FunctionalTests/Update/UpdateReducedThemeRegistryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b31d62cc3d03dc37746c209ddc5bf18d61523132 --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Update/UpdateReducedThemeRegistryTest.php @@ -0,0 +1,44 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\FunctionalTests\Update; + +use Drupal\Core\Url; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests that update.php is accessible even if there are unstable modules. + * + * @group Update + */ +class UpdateReducedThemeRegistryTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['update_test_broken_theme_hook']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * Tests that the update page can be accessed. + */ + public function testUpdatePageWithBrokenThemeHook(): void { + require_once $this->root . '/core/includes/update.inc'; + $this->writeSettings([ + 'settings' => [ + 'update_free_access' => (object) [ + 'value' => TRUE, + 'required' => TRUE, + ], + ], + ]); + $this->drupalGet(Url::fromRoute('system.db_update')); + $this->assertSession()->statusCodeEquals(200); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php b/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php index 20d1320c242d473626f65981816fe89bbdd22b11..0a672d6e3218ca71502c340d231125c4b5449205 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php @@ -76,11 +76,11 @@ public function testMultipleSubThemes() { $module_list = $this->container->get('extension.list.module'); assert($module_list instanceof ModuleExtensionList); - $registry_subsub_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $module_list, 'test_subsubtheme'); + $registry_subsub_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $module_list, \Drupal::service('kernel'), 'test_subsubtheme'); $registry_subsub_theme->setThemeManager(\Drupal::theme()); - $registry_sub_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $module_list, 'test_subtheme'); + $registry_sub_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $module_list, \Drupal::service('kernel'), 'test_subtheme',); $registry_sub_theme->setThemeManager(\Drupal::theme()); - $registry_base_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $module_list, 'test_basetheme'); + $registry_base_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $module_list, \Drupal::service('kernel'), 'test_basetheme'); $registry_base_theme->setThemeManager(\Drupal::theme()); $preprocess_functions = $registry_subsub_theme->get()['theme_test_template_test']['preprocess functions']; @@ -114,7 +114,7 @@ public function testSuggestionPreprocessFunctions() { $extension_list = $this->container->get('extension.list.module'); assert($extension_list instanceof ModuleExtensionList); - $registry_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $extension_list, 'test_theme'); + $registry_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $extension_list, \Drupal::service('kernel'), 'test_theme'); $registry_theme->setThemeManager(\Drupal::theme()); $suggestions = ['__kitten', '__flamingo']; @@ -156,7 +156,7 @@ public function testThemeRegistryAlterByTheme() { $extension_list = $this->container->get('extension.list.module'); assert($extension_list instanceof ModuleExtensionList); - $registry = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $extension_list, 'test_theme'); + $registry = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $extension_list, \Drupal::service('kernel'), 'test_theme'); $registry->setThemeManager(\Drupal::theme()); $this->assertEquals('value', $registry->get()['theme_test_template_test']['variables']['additional']); } @@ -265,7 +265,7 @@ public function testThemeTemplatesRegisteredByModules() { $extension_list = \Drupal::service('extension.list.module'); assert($extension_list instanceof ModuleExtensionList); - $registry_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $extension_list, 'test_theme'); + $registry_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), $extension_list, \Drupal::service('kernel'), 'test_theme'); $registry_theme->setThemeManager(\Drupal::theme()); $expected = [ diff --git a/core/tests/Drupal/KernelTests/Core/Theme/Stable9TemplateOverrideTest.php b/core/tests/Drupal/KernelTests/Core/Theme/Stable9TemplateOverrideTest.php index 22cd470323acef02b138a3ba730bc7a74c3e007f..7f2598e2fa3793f6b2969c7da036ef344c02a67e 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/Stable9TemplateOverrideTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/Stable9TemplateOverrideTest.php @@ -92,7 +92,7 @@ protected function installAllModules() { * Ensures that Stable 9 overrides all relevant core templates. */ public function testStable9TemplateOverrides() { - $registry = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $this->themeHandler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), \Drupal::service('extension.list.module'), 'stable9'); + $registry = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $this->themeHandler, \Drupal::service('theme.initialization'), \Drupal::service('cache.bootstrap'), \Drupal::service('extension.list.module'), \Drupal::service('kernel'), 'stable9'); $registry->setThemeManager(\Drupal::theme()); $registry_full = $registry->get(); diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php index 9a4ecb89b61f963f716efa0e71bcd17e6aac8ca7..7f6a63ab81b2c10ee34f6102eea6d4660ce96bd9 100644 --- a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Theme\ActiveTheme; use Drupal\Core\Theme\Registry; use Drupal\Tests\UnitTestCase; +use Symfony\Component\HttpKernel\HttpKernelInterface; /** * @coversDefaultClass \Drupal\Core\Theme\Registry @@ -78,6 +79,13 @@ class RegistryTest extends UnitTestCase { */ protected $moduleList; + /** + * The kernel. + * + * @var \Symfony\Component\HttpKernel\HttpKernelInterface|\PHPUnit\Framework\MockObject\MockObject + */ + protected $kernel; + /** * The list of functions that get_defined_functions() should provide. * @@ -99,7 +107,8 @@ protected function setUp(): void { $this->runtimeCache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface'); $this->themeManager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface'); $this->moduleList = $this->createMock(ModuleExtensionList::class); - $this->registry = new Registry($this->root, $this->cache, $this->lock, $this->moduleHandler, $this->themeHandler, $this->themeInitialization, $this->runtimeCache, $this->moduleList); + $this->kernel = $this->createMock(HttpKernelInterface::class); + $this->registry = new Registry($this->root, $this->cache, $this->lock, $this->moduleHandler, $this->themeHandler, $this->themeInitialization, $this->runtimeCache, $this->moduleList, $this->kernel); $this->registry->setThemeManager($this->themeManager); }