diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php index 6dd83f7..422356a 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php +++ b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php @@ -9,8 +9,6 @@ use Drupal\Core\Cache\CacheCollectorInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Theme\ThemeManagerInterface; /** * Discovers available asset libraries in Drupal. @@ -79,7 +77,24 @@ public function getLibrariesByExtension($extension) { */ public function getLibraryByName($extension, $name) { $extension = $this->getLibrariesByExtension($extension); - return isset($extension[$name]) ? $extension[$name] : FALSE; + if (isset($extension[$name])) { + // Handle libraries that are marked for override or removal. + if (isset($extension[$name]['override'])) { + if ($extension[$name]['override']) { + list($new_extension, $new_name) = explode('/', $extension[$name]['override']); + $extension[$name] = $this->getLibraryByName($new_extension, $new_name); + } + else { + unset($extension[$name]); + return FALSE; + } + } + return $extension[$name]; + } + else { + return FALSE; + } + } /** diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php index 042359f..5c97820 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php +++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php @@ -88,6 +88,7 @@ public function buildByExtension($extension) { } $libraries = $this->parseLibraryInfo($extension, $path); + $this->applyLibrariesOverrides($libraries, $extension, $path); foreach ($libraries as $id => &$library) { if (!isset($library['js']) && !isset($library['css']) && !isset($library['drupalSettings'])) { @@ -314,6 +315,59 @@ protected function parseLibraryInfo($extension, $path) { } /** + * Apply libraries overrides specified for the current active theme. + * + * @param array $libraries + * The library definition. + * @param string $extension + * The extension in which this library was defined. + * @param string $path + * The root path of the extension. + * + * @return array + * The modified library definition. + */ + protected function applyLibrariesOverrides(&$libraries, $extension, $path) { + $libraries_overrides = $this->themeManager->getActiveTheme()->getLibrariesOverride(); + $theme_path = $this->themeManager->getActiveTheme()->getPath(); + foreach ($libraries as $name => $library) { + // Process libraries overrides. + foreach ($libraries_overrides as $item => $override) { + // Active theme defines an override for this library. + if ($item === "$extension/$name") { + // The whole library. + if ($override) { + $libraries[$name]['override'] = $override; + } + else { + $libraries[$name]['override'] = FALSE; + } + } + else if (strpos($item, "$extension/$name/") !== FALSE) { + // An asset within the library. + list(, , $component, $value) = explode('/', $item, 4); + if ($component == 'css') { + // SMACSS-category should be incorporated into the component name. + list($category, $value) = explode('/', $value, 2); + $parents = [$component, $category, $value]; + $new_parents = [$component, $category, '/' . $theme_path . '/' . $override]; + } + else { + $parents = [$component, $value]; + $new_parents = [$component, '/' . $theme_path . '/' . $override]; + } + // Remove previous component to be overridden. + NestedArray::unsetValue($libraries[$name], $parents); + if ($override) { + // Replace with an override if specified. + NestedArray::setValue($libraries[$name], $new_parents, []); + } + } + } + } + } + + /** * Wraps drupal_get_path(). */ protected function drupalGetPath($type, $name) { diff --git a/core/lib/Drupal/Core/Theme/ActiveTheme.php b/core/lib/Drupal/Core/Theme/ActiveTheme.php index aace6f9..d1970e7 100644 --- a/core/lib/Drupal/Core/Theme/ActiveTheme.php +++ b/core/lib/Drupal/Core/Theme/ActiveTheme.php @@ -74,6 +74,13 @@ class ActiveTheme { protected $libraries; /** + * The libraries or library components overridden by the theme. + * + * @var array + */ + protected $librariesOverride; + + /** * Constructs an ActiveTheme object. * * @param array $values @@ -88,6 +95,7 @@ public function __construct(array $values) { 'libraries' => [], 'extension' => 'html.twig', 'base_themes' => [], + 'libraries_override' => [], ]; $this->name = $values['name']; @@ -98,6 +106,7 @@ public function __construct(array $values) { $this->libraries = $values['libraries']; $this->extension = $values['extension']; $this->baseThemes = $values['base_themes']; + $this->librariesOverride = $values['libraries_override']; } /** @@ -177,4 +186,10 @@ public function getBaseThemes() { return $this->baseThemes; } + /** + * Returns the libraries or library components overridden by the active theme. + */ + public function getLibrariesOverride() { + return $this->librariesOverride; + } } diff --git a/core/lib/Drupal/Core/Theme/ThemeInitialization.php b/core/lib/Drupal/Core/Theme/ThemeInitialization.php index a77271e..6697d9b 100644 --- a/core/lib/Drupal/Core/Theme/ThemeInitialization.php +++ b/core/lib/Drupal/Core/Theme/ThemeInitialization.php @@ -185,6 +185,25 @@ public function getActiveTheme(Extension $theme, array $base_themes = []) { } } + // Prepare libraries overrides from this theme and ancestor themes. + $values['libraries_override'] = array(); + + // Grab libraries-overrides from base theme. + foreach ($base_themes as $base) { + if (!empty($base->info['libraries-override'])) { + foreach ($base->info['libraries-override'] as $library => $override) { + $values['libraries_override'][$library] = $override; + } + } + } + + // Add libraries-overrides declared by this theme. + if (!empty($theme->info['libraries-override'])) { + foreach ($theme->info['libraries-override'] as $library => $override) { + $values['libraries_override'][$library] = $override; + } + } + // Do basically the same as the above for libraries $values['libraries'] = array(); diff --git a/core/modules/system/src/Tests/Asset/LibraryDiscoveryIntegrationTest.php b/core/modules/system/src/Tests/Asset/LibraryDiscoveryIntegrationTest.php index 2ddccea..989f56e 100644 --- a/core/modules/system/src/Tests/Asset/LibraryDiscoveryIntegrationTest.php +++ b/core/modules/system/src/Tests/Asset/LibraryDiscoveryIntegrationTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\Asset; +use Drupal\Component\Utility\SafeMarkup; use Drupal\simpletest\KernelTestBase; /** @@ -42,4 +43,62 @@ public function testElementInfoByTheme() { $this->assertTrue($library_discovery->getLibraryByName('test_theme', 'kitten')); } + + /** + * Tests that libraries-overrides are applied to library definitions. + */ + public function testLibrariesOverride() { + /** @var \Drupal\Core\Theme\ThemeInitializationInterface $theme_initializer */ + $theme_initializer = $this->container->get('theme.initialization'); + + /** @var \Drupal\Core\Theme\ThemeManagerInterface $theme_manager */ + $theme_manager = $this->container->get('theme.manager'); + + /** @var \Drupal\Core\Render\ElementInfoManagerInterface $element_info */ + $library_discovery = $this->container->get('library.discovery'); + + $theme_manager->setActiveTheme($theme_initializer->getActiveThemeByName('test_theme')); + + // Assert that entire library was correctly overridden. + $this->assertEqual($library_discovery->getLibraryByName('core', 'drupal.collapse'), $library_discovery->getLibraryByName('test_theme', 'collapse'), 'Entire library correctly overridden.'); + + // Assert that library component was correctly overridden. + $library = $library_discovery->getLibraryByName('classy', 'base'); + $this->assertAssetInLibraryComponent($library['css'], 'base', 'core/modules/system/tests/themes/test_theme/css/test_theme_layout.css'); + + // Assert that entire library was correctly removed. + $this->assertFalse($library_discovery->getLibraryByName('core', 'drupal.progress'), 'Entire library correctly removed.'); + + // Assert that library component was correctly removed. + $library = $library_discovery->getLibraryByName('core', 'drupal.dialog'); + $this->assertNoAssetInLibraryComponent($library['css'], 'drupal.dialog', 'core/misc/dialog.theme.css'); + + } + + protected function assertAssetInLibraryComponent($component, $library_name, $asset, $message = NULL) { + if (!isset($message)) { + $message = SafeMarkup::format('Asset @asset found in library @library', ['@asset' => $asset, '@library' => $library_name]); + } + foreach ($component as $definition) { + if ($asset == $definition['data']) { + $this->pass($message); + return; + } + } + $this->fail($message); + } + + protected function assertNoAssetInLibraryComponent($component, $library_name, $asset, $message = NULL) { + if (!isset($message)) { + $message = SafeMarkup::format('Asset @asset not found in library @library', ['@asset' => $asset, '@library' => $library_name]); + } + foreach ($component as $definition) { + if ($asset == $definition['data']) { + $this->fail($message); + return; + } + } + $this->pass($message); + } + } diff --git a/core/modules/system/tests/themes/test_theme/test_theme.info.yml b/core/modules/system/tests/themes/test_theme/test_theme.info.yml index 7ddcb2d..d776107 100644 --- a/core/modules/system/tests/themes/test_theme/test_theme.info.yml +++ b/core/modules/system/tests/themes/test_theme/test_theme.info.yml @@ -20,3 +20,13 @@ regions: content: Content left: Left right: Right + +libraries-override: + # Replace an entire library. + 'core/drupal.collapse': 'test_theme/collapse' + # Remove an entire library. + 'core/drupal.progress': FALSE + # Replace one particular library asset with another. + 'classy/base/css/theme/css/layout.css': 'css/test_theme_layout.css' + # Remove one particular asset. + 'core/drupal.dialog/css/theme/misc/dialog.theme.css': FALSE diff --git a/core/modules/system/tests/themes/test_theme/test_theme.libraries.yml b/core/modules/system/tests/themes/test_theme/test_theme.libraries.yml new file mode 100644 index 0000000..69d07fb --- /dev/null +++ b/core/modules/system/tests/themes/test_theme/test_theme.libraries.yml @@ -0,0 +1,7 @@ +collapse: + js: + js/collapse.js: { } + + css: + base: + css/collapse.css: { } diff --git a/core/themes/bartik/bartik.info.yml b/core/themes/bartik/bartik.info.yml index 3c02894..1af3c21 100644 --- a/core/themes/bartik/bartik.info.yml +++ b/core/themes/bartik/bartik.info.yml @@ -5,10 +5,10 @@ description: 'A flexible, recolorable theme with many regions and a responsive, package: Core version: VERSION core: 8.x -stylesheets-remove: - - '@classy/css/layout.css' libraries: - bartik/global-styling +libraries-override: + classy/base/css/theme/css/layout.css: false ckeditor_stylesheets: - css/base/elements.css - css/components/captions.css @@ -35,4 +35,3 @@ regions: footer_third: 'Footer third' footer_fourth: 'Footer fourth' footer_fifth: 'Footer fifth' -