Commit 0fb8f6ba authored by alexpott's avatar alexpott

Issue #2389735 by lauriii, iMiksu, davidhernandez, larowlan, loopduplicate,...

Issue #2389735 by lauriii, iMiksu, davidhernandez, larowlan, loopduplicate, LewisNyman, Wim Leers, dawehner, Cottser: Core and base theme CSS files in libraries override theme CSS files with the same name
parent 8063f77b
......@@ -1190,7 +1190,7 @@ services:
- [setThemeRegistry, ['@theme.registry']]
theme.initialization:
class: Drupal\Core\Theme\ThemeInitialization
arguments: ['@app.root', '@theme_handler', '@state']
arguments: ['@app.root', '@theme_handler', '@state', '@module_handler']
theme.registry:
class: Drupal\Core\Theme\Registry
arguments: ['@app.root', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization']
......
......@@ -124,26 +124,11 @@ public function getCssAssets(AttachedAssetsInterface $assets, $optimize) {
// order.
$options['weight'] += count($css) / 1000;
// Add the data to the CSS array depending on the type.
switch ($options['type']) {
case 'file':
// Local CSS files are keyed by basename; if a file with the same
// basename is added more than once, it gets overridden.
// By default, take over the filename as basename.
if (!isset($options['basename'])) {
$options['basename'] = \Drupal::service('file_system')->basename($options['data']);
}
$css[$options['basename']] = $options;
break;
default:
// External files are keyed by their full URI, so the same CSS
// file is not added twice.
// CSS files are being keyed by the full path.
$css[$options['data']] = $options;
}
}
}
}
// Allow modules and themes to alter the CSS assets.
$this->moduleHandler->alter('css', $css, $assets);
......@@ -152,22 +137,14 @@ public function getCssAssets(AttachedAssetsInterface $assets, $optimize) {
// Sort CSS items, so that they appear in the correct order.
uasort($css, 'static::sort');
// Allow themes to remove CSS files by basename.
// Allow themes to remove CSS files by CSS files full path and file name.
if ($stylesheet_remove = $theme_info->getStyleSheetsRemove()) {
foreach ($css as $key => $options) {
if (isset($options['basename']) && isset($stylesheet_remove[$options['basename']])) {
if (isset($stylesheet_remove[$key])) {
unset($css[$key]);
}
}
}
// Allow themes to conditionally override CSS files by basename.
if ($stylesheet_override = $theme_info->getStyleSheetsOverride()) {
foreach ($css as $key => $options) {
if (isset($options['basename']) && isset($stylesheet_override[$options['basename']])) {
$css[$key]['data'] = $stylesheet_override[$options['basename']];
}
}
}
if ($optimize) {
$css = \Drupal::service('asset.css.collection_optimizer')->optimize($css);
......
......@@ -66,13 +66,6 @@ class ActiveTheme {
*/
protected $styleSheetsRemove;
/**
* The stylesheets which are overridden by the theme.
*
* @var array
*/
protected $styleSheetsOverride;
/**
* The libraries provided by the theme.
*
......@@ -92,17 +85,16 @@ public function __construct(array $values) {
'engine' => 'twig',
'owner' => 'twig',
'stylesheets_remove' => [],
'stylesheets_override' => [],
'libraries' => [],
'extension' => 'html.twig',
'base_themes' => [],
];
$this->name = $values['name'];
$this->path = $values['path'];
$this->engine = $values['engine'];
$this->owner = $values['owner'];
$this->styleSheetsRemove = $values['stylesheets_remove'];
$this->styleSheetsOverride = $values['stylesheets_override'];
$this->libraries = $values['libraries'];
$this->extension = $values['extension'];
$this->baseThemes = $values['base_themes'];
......@@ -164,15 +156,6 @@ public function getLibraries() {
return $this->libraries;
}
/**
* Returns the overridden stylesheets by the theme.
*
* @return mixed
*/
public function getStyleSheetsOverride() {
return $this->styleSheetsOverride;
}
/**
* Returns the removed stylesheets by the theme.
*
......
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Theme;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\State\StateInterface;
......@@ -37,6 +38,13 @@ class ThemeInitialization implements ThemeInitializationInterface {
*/
protected $root;
/**
* The extensions that might be attaching assets.
*
* @var array
*/
protected $extensions;
/**
* Constructs a new ThemeInitialization object.
*
......@@ -46,11 +54,14 @@ class ThemeInitialization implements ThemeInitializationInterface {
* The theme handler.
* @param \Drupal\Core\State\StateInterface $state
* The state.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to use to load modules.
*/
public function __construct($root, ThemeHandlerInterface $theme_handler, StateInterface $state) {
public function __construct($root, ThemeHandlerInterface $theme_handler, StateInterface $state, ModuleHandlerInterface $module_handler) {
$this->root = $root;
$this->themeHandler = $theme_handler;
$this->state = $state;
$this->moduleHandler = $module_handler;
}
/**
......@@ -151,47 +162,26 @@ public function getActiveTheme(Extension $theme, array $base_themes = []) {
$values['name'] = $theme->getName();
// Prepare stylesheets from this theme as well as all ancestor themes.
// We work it this way so that we can have child themes override parent
// theme stylesheets easily.
// CSS file basenames to override, pointing to the final, overridden filepath.
$values['stylesheets_override'] = array();
// CSS file basenames to remove.
// We work it this way so that we can have child themes remove CSS files
// easily from parent.
$values['stylesheets_remove'] = array();
// Grab stylesheets from base theme.
foreach ($base_themes as $base) {
$base_theme_path = $base->getPath();
if (!empty($base->info['stylesheets-remove'])) {
foreach ($base->info['stylesheets-remove'] as $basename) {
$values['stylesheets_remove'][$basename] = $base_theme_path . '/' . $basename;
}
}
if (!empty($base->info['stylesheets-override'])) {
foreach ($base->info['stylesheets-override'] as $name) {
$basename = drupal_basename($name);
$values['stylesheets_override'][$basename] = $base_theme_path . '/' . $name;
foreach ($base->info['stylesheets-remove'] as $css_file) {
$css_file = $this->resolveStyleSheetPlaceholders($css_file);
$values['stylesheets_remove'][$css_file] = $css_file;
}
}
}
// Add stylesheets used by this theme.
if (!empty($theme->info['stylesheets-remove'])) {
foreach ($theme->info['stylesheets-remove'] as $basename) {
$values['stylesheets_remove'][$basename] = $theme_path . '/' . $basename;
if (isset($values['stylesheets_override'][$basename])) {
unset($values['stylesheets_override'][$basename]);
}
}
}
if (!empty($theme->info['stylesheets-override'])) {
foreach ($theme->info['stylesheets-override'] as $name) {
$basename = drupal_basename($name);
$values['stylesheets_override'][$basename] = $theme_path . '/' . $name;
if (isset($values['stylesheets_remove'][$basename])) {
unset($values['stylesheets_remove'][$basename]);
}
foreach ($theme->info['stylesheets-remove'] as $css_file) {
$css_file = $this->resolveStyleSheetPlaceholders($css_file);
$values['stylesheets_remove'][$css_file] = $css_file;
}
}
......@@ -228,4 +218,39 @@ public function getActiveTheme(Extension $theme, array $base_themes = []) {
return new ActiveTheme($values);
}
/**
* Gets all extensions.
*
* @return array
*/
protected function getExtensions() {
if (!isset($this->extensions)) {
$this->extensions = array_merge($this->moduleHandler->getModuleList(), $this->themeHandler->listInfo());
}
return $this->extensions;
}
/**
* Gets CSS file where tokens have been resolved.
*
* @param string $css_file
* CSS file which may contain tokens.
*
* @return string
* CSS file where placeholders are replaced.
*/
protected function resolveStyleSheetPlaceholders($css_file) {
$token_candidate = explode('/', $css_file)[0];
if (!preg_match('/@[A-z0-9_-]+/', $token_candidate)) {
return $css_file;
}
$token = substr($token_candidate, 1);
// Prime extensions.
$extensions = $this->getExtensions();
if (isset($extensions[$token])) {
return str_replace($token_candidate, $extensions[$token]->getPath(), $css_file);
}
}
}
......@@ -89,7 +89,7 @@ function testAddFiles() {
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('bar.css', $css), 'CSS files are correctly added.');
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/bar.css', $css), 'CSS files are correctly added.');
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/foo.js', $js), 'JavaScript files are correctly added.');
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
......@@ -470,7 +470,7 @@ function testAddJsFileWithQueryString() {
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('querystring.css?arg1=value1&arg2=value2', $css), 'CSS file with query string is correctly added.');
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2', $css), 'CSS file with query string is correctly added.');
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js), 'JavaScript file with query string is correctly added.');
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
......
......@@ -56,7 +56,7 @@ protected function setUp() {
}
/**
* Tests stylesheets-override and stylesheets-remove.
* Tests stylesheets-remove.
*/
function testStylesheets() {
$this->themeHandler->install(array('test_basetheme', 'test_subtheme'));
......@@ -65,27 +65,22 @@ function testStylesheets() {
->save();
$base = drupal_get_path('theme', 'test_basetheme');
// Unlike test_basetheme (and the original module CSS), the subtheme decides
// to put all of its CSS into a ./css subdirectory. All overrides and
// removals are expected to be based on a file's basename and should work
// nevertheless.
$sub = drupal_get_path('theme', 'test_subtheme') . '/css';
// All removals are expected to be based on a file's path and name and
// should work nevertheless.
$this->drupalGet('theme-test/info/stylesheets');
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$base/base-add.css')]")), "$base/base-add.css found");
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$base/base-override.css')]")), "$base/base-override.css found");
$this->assertIdentical(0, count($this->xpath("//link[contains(@href, 'base-remove.css')]")), "base-remove.css not found");
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$sub/sub-add.css')]")), "$sub/sub-add.css found");
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$sub/sub-override.css')]")), "$sub/sub-override.css found");
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$sub/base-add.sub-override.css')]")), "$sub/base-add.sub-override.css found");
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$sub/base-remove.sub-override.css')]")), "$sub/base-remove.sub-override.css found");
$this->assertIdentical(0, count($this->xpath("//link[contains(@href, 'sub-remove.css')]")), "sub-remove.css not found");
$this->assertIdentical(0, count($this->xpath("//link[contains(@href, 'base-add.sub-remove.css')]")), "base-add.sub-remove.css not found");
$this->assertIdentical(0, count($this->xpath("//link[contains(@href, 'base-override.sub-remove.css')]")), "base-override.sub-remove.css not found");
// Verify that CSS files with the same name are loaded from both the base theme and subtheme.
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$base/samename.css')]")), "$base/samename.css found");
$this->assertIdentical(1, count($this->xpath("//link[contains(@href, '$sub/samename.css')]")), "$sub/samename.css found");
}
/**
......
......@@ -2,9 +2,6 @@ theme_stylesheets_override_and_remove_test:
version: VERSION
css:
base:
css/base-override.css: {}
css/base-override.sub-remove.css: {}
css/base-remove.css: {}
css/base-remove.sub-override.css: {}
css/sub-override.css: {}
css/sub-remove.css: {}
......@@ -5,9 +5,5 @@ version: VERSION
core: 8.x
libraries:
- test_basetheme/global-styling
stylesheets-override:
- base-override.css
- base-override.sub-remove.css
stylesheets-remove:
- base-remove.css
- base-remove.sub-override.css
- @theme_test/css/base-remove.css
......@@ -3,5 +3,5 @@ global-styling:
css:
base:
base-add.css: {}
base-add.sub-override.css: {}
base-add.sub-remove.css: {}
samename.css: {}
......@@ -6,11 +6,6 @@ core: 8.x
base theme: test_basetheme
libraries:
- test_subtheme/global-styling
stylesheets-override:
- css/sub-override.css
- css/base-add.sub-override.css
- css/base-remove.sub-override.css
stylesheets-remove:
- sub-remove.css
- base-add.sub-remove.css
- base-override.sub-remove.css
- @theme_test/css/sub-remove.css
- @test_basetheme/base-add.sub-remove.css
......@@ -3,3 +3,4 @@ global-styling:
css:
base:
css/sub-add.css: {}
css/samename.css: {}
......@@ -15,7 +15,7 @@ version: VERSION
base theme: classy
core: 8.x
stylesheets-remove:
- system.module.css
- @system/css/system.module.css
regions:
content: Content
left: Left
......
......@@ -5,6 +5,8 @@ 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
ckeditor_stylesheets:
......
......@@ -8,12 +8,9 @@ version: VERSION
core: 8.x
libraries:
- seven/global-styling
stylesheets-override:
- css/components/vertical-tabs.css
- css/components/jquery.ui/theme.css
- css/components/dialog.theme.css
stylesheets-remove:
- dialog.css
- core/assets/vendor/jquery.ui/themes/base/dialog.css
- @classy/css/layout.css
quickedit_stylesheets:
- css/components/quickedit.css
regions:
......
......@@ -73,3 +73,21 @@ drupal.nav-tabs:
- core/drupal
- core/jquery.once
- core/jquery.intrinsic
vertical-tabs:
version: VERSION
css:
component:
css/components/vertical-tabs.css: {}
seven.jquery.ui:
version: VERSION
css:
component:
css/components/jquery.ui/theme.css: { weight: -1 }
seven.drupal.dialog:
version: VERSION
css:
theme:
css/components/dialog.theme.css: {}
......@@ -197,3 +197,21 @@ function seven_form_node_form_alter(&$form, FormStateInterface $form_state) {
$form['revision_information']['#type'] = 'container';
$form['revision_information']['#group'] = 'meta';
}
/**
* Implements hook_library_info_alter().
*/
function seven_library_info_alter(&$libraries) {
if (isset($libraries['drupal.vertical-tabs'])) {
unset($libraries['drupal.vertical-tabs']['css']['component']['misc/vertical-tabs.css']);
$libraries['drupal.vertical-tabs']['dependencies'][] = 'seven/vertical-tabs';
}
if (isset($libraries['jquery.ui'])) {
unset($libraries['jquery.ui']['css']['theme']['assets/vendor/jquery.ui/themes/base/theme.css']);
$libraries['jquery.ui']['dependencies'][] = 'seven/seven.jquery.ui';
}
if (isset($libraries['drupal.dialog'])) {
unset($libraries['drupal.dialog']['css']['theme']['misc/dialog.theme.css']);
$libraries['drupal.dialog']['dependencies'][] = 'seven/seven.drupal.dialog';
}
}
......@@ -7,4 +7,4 @@ core: 8.x
libraries:
- stark/global-styling
stylesheets-remove:
- normalize.css
- core/assets/vendor/normalize-css/normalize.css
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment