Problem/Motivation

This problem occurs for plugin types with YAML discovery.

In Core, there is layout.

In contrib, at least the following modules have the problem in their plugin types:

Steps to reproduce

Have two themes, one is a parent, the other is a child.

Ensure the machine name of the child is alphabetically before the machine name of the parent.

Declare a layout in the parent theme.
Override the layout in the child theme.

Result: the plugin is not overridden.

Proposed resolution

Remaining tasks

Found a solution.

Issue fork drupal-3457863

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Grimreaper created an issue. See original summary.

grimreaper’s picture

grimreaper’s picture

Assigned: grimreaper » Unassigned
Status: Active » Needs work

Provided a MR with a failing test to demonstrate the problem.

grimreaper’s picture

https://git.drupalcode.org/issue/drupal-3457863/-/jobs/1984798#L4263

---- Drupal\KernelTests\Core\Layout\LayoutPluginManagerTest ----
Status    Group      Filename          Line Function                            
--------------------------------------------------------------------------------
Fail      Other      phpunit-361.xml      0 Drupal\KernelTests\Core\Layout\Layo
    PHPUnit Test failed to complete; Error: PHPUnit 10.5.20 by Sebastian
    Bergmann and contributors.
    
    Runtime:       PHP 8.3.8
    Configuration: /builds/issue/drupal-3457863/core/phpunit.xml.dist
    
    F                                                                   1 / 1
    (100%)
    
    Time: 00:01.315, Memory: 8.00 MB
    
    There was 1 failure:
    
    1)
    Drupal\KernelTests\Core\Layout\LayoutPluginManagerTest::testPluginOverride
    Failed asserting that two strings are identical.
    --- Expected
    +++ Actual
    @@ @@
    -'Child'
    +'Parent'
    
    /builds/issue/drupal-3457863/core/tests/Drupal/KernelTests/Core/Layout/LayoutPluginManagerTest.php:40
grimreaper’s picture

Copy pasting works from @maboy in #3263526: UX: Add other widgets than select, MR 47.

/**
   * Get list of theme directories sorted by inheritance.
   *
   * @return array
   *   The sorted directories.
   */
  protected function orderTheme(): array {
    // Order theme directories to allow overriding in subtheme.
    $themes = $this->themeHandler->listInfo();
    $ordered_theme_directories = [];
    $base_theme = [];
    $theme_directories = $this->themeHandler->getThemeDirectories();

    // Create a list which includes the current theme and all its base themes.
    foreach ($themes as $key => $theme) {
      // Base theme for current theme.
      // We need to place current theme AFTER the base theme in array.
      if (isset($theme->base_themes)) {
        // Get the last base_theme.
        // To get the key after we want to insert the current theme.
        $after_key = \array_key_last($theme->base_themes);
        $index = \array_search($after_key, \array_keys($ordered_theme_directories), TRUE);

        // The current theme is a base theme.
        $before_key = NULL;
        if (\array_key_exists($key, $base_theme)) {
          $before_key = $base_theme[$key];
        }

        // The base theme is not existing yet in the array.
        if ($index == 0) {
          // Current theme is also a base theme.
          if ($before_key) {
            // Insert the current theme before its children in the array.
            $index_before = \array_search($before_key, \array_keys($ordered_theme_directories), TRUE);
            $ordered_theme_directories = \array_slice($ordered_theme_directories, 0, $index_before - 1) + [$key => $theme_directories[$key]] + $ordered_theme_directories;
          }
          else {
            // Insert the current theme at the end of the array.
            $ordered_theme_directories[$key] = $theme_directories[$key];
          }
          $base_theme = [$after_key => [$key]];
        }
        // The base theme exist in the array.
        else {
          // Insert the current theme after the base_theme.
          $ordered_theme_directories = \array_slice($ordered_theme_directories, 0, $index + 1) + [$key => $theme_directories[$key]] + $ordered_theme_directories;
        }
      }
      // No base theme,
      // Can be added first.
      else {
        $ordered_theme_directories = [$key => $theme_directories[$key]] + $ordered_theme_directories;
      }
    }

    return $ordered_theme_directories;
  }

I will try to use that for a generic fix.

pdureau credited maboy.

pdureau’s picture

grimreaper’s picture

Assigned: Unassigned » grimreaper
grimreaper’s picture

I have just put code from comment 6 into the getThemeDirectories method of the themeHandler directly.

I have changed the logic to not rely on itself anymore to avoid infinite loop. And so I had to change some variables name. I hope the logic is still ok.

At the beginning I tried a "simplified" approach based on:

    $theme_config = $this->configFactory->get('system.theme');
    $default_theme = $theme_config->get('default');
    $admin_theme = $theme_config->get('admin');

And only handle those themes but the problem is that it can be null, at least in the tests.

grimreaper’s picture

Assigned: grimreaper » Unassigned
Status: Needs work » Needs review

Test are green now. Thanks @maboy!

grimreaper’s picture

So, testing with UI Suite modules, I started with UI Styles.

No problem on the default theme which is a sub theme of ui_suite_bootstrap generated using its starterkit.

But if I go on a page which should be contextualized on ui_suite_bootstrap, it does not detect the styles overridden by its sub theme.

See attached screenshots. Also I attached the styles of the sub theme.

So maybe the envisaged solution is not ok and all plugins provided by YAML should follow SDC logic to have the provider automatically prefixed in the plugin machine name to avoid overlaps.

smustgrave’s picture

Status: Needs review » Needs work

Nice work everyone,

Believe to be very close just a couple nitpicky stuff.

grimreaper’s picture

Assigned: Unassigned » grimreaper
grimreaper’s picture

Assigned: grimreaper » Unassigned
Status: Needs work » Needs review

Review comments addressed.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Feedback appears to be addressed

grimreaper’s picture

Thanks for the RTBC but I don't know if it's not too soon. See comment 12.

At least in RTBC this will more attract attention from maintainers to get feedbacks.

pdureau’s picture

About theme hierarchy (A is the base theme of B which is the base theme of a C which is the active theme):

  • it is important to get the plugin override right: a plugin from C is overriding a plugin from A or B if same plugin ID, a plugin from B is overriding a plugin from A if same plugin ID.
  • merging and overriding theme settings with the same rules would be nice but it is not as mandatory and not in the scope of this issue, IMHO
grimreaper’s picture

Adding the parent theme in the dependencies list of the child theme has no effect.

grimreaper’s picture

Status: Reviewed & tested by the community » Needs review

The pipeline failed on one test: Drupal\Tests\ckeditor5\FunctionalJavascript\ImageUrlProvider, I don't think it is related to the change in this issue.

catch made their first commit to this issue’s fork.

grimreaper’s picture

Existing kernel test updated.

Pipeline still gets some random failure from other unrelated tests.

grimreaper’s picture

Issue tags: +Barcelona2024
catch’s picture

Status: Needs review » Reviewed & tested by the community

Talked with @grimreaper and @longwave about this issue at Drupal Barcelona. Existing test coverage looked great, found a way to move the ordering logic up a layer (actually removing it in the end which was unexpected but even better). RTBC for me now.

longwave’s picture

Status: Reviewed & tested by the community » Needs work

Given we're touching this code to add a foreach() I think we can make it even more readable?

grimreaper’s picture

Status: Needs work » Needs review

Suggestion approved, thanks!

catch’s picture

Status: Needs review » Reviewed & tested by the community

Yes that looks much better, foreach is now an overall simplification of the code compared to how it was.

  • longwave committed bd6acd2f on 10.3.x
    Issue #3457863 by grimreaper, catch, pdureau, maboy, longwave: YAML...

  • longwave committed 152f9511 on 10.4.x
    Issue #3457863 by grimreaper, catch, pdureau, maboy, longwave: YAML...

  • longwave committed 62027b5d on 11.0.x
    Issue #3457863 by grimreaper, catch, pdureau, maboy, longwave: YAML...

  • longwave committed 5e0ff5a8 on 11.x
    Issue #3457863 by grimreaper, catch, pdureau, maboy, longwave: YAML...
longwave’s picture

Version: 11.x-dev » 10.3.x-dev
Status: Reviewed & tested by the community » Fixed

Backported down to 10.3.x as this seems like a low risk bug fix and unblocks some problems in contrib.

Committed and pushed 5e0ff5a872 to 11.x and 62027b5d4d to 11.0.x and 152f951114 to 10.4.x and bd6acd2ffb to 10.3.x. Thanks!

grimreaper’s picture

Thanks a lot for the merge and the backports!

cilefen’s picture

shane birley’s picture

I was in process of upgrading a Drupal 8.x website to 10.3.x and everything was going well until yesterday when I upgraded to 10.3.6. I started getting the WSOD and this error popped up:

Uncaught PHP Exception TypeError: "Drupal\Core\Extension\ThemeHandler::addTheme(): Argument #1 ($theme) must be of type Drupal\Core\Extension\Extension, null given, called in /core/lib/Drupal/Core/Extension/ThemeHandler.php on line 74" at /core/lib/Drupal/Core/Extension/ThemeHandler.php line 84

Unsure this is 100% related but I can't find any additional references that match.

catch’s picture

There's an issue open for that error, and I do think it's a regression from here: #3478628: Fatal error: Uncaught TypeError: Drupal\Core\Extension\ThemeHandler::addTheme().

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.