From 08ce5a7001677e9ffb86c6b22f13893a0985f872 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Sun, 15 Mar 2015 20:23:37 +0000
Subject: [PATCH] Issue #2448847 by dawehner, arlinsandbulte: [regression]
 Themes unable to implement hook_theme_registry_alter()

---
 core/core.services.yml                        |  6 +++-
 core/lib/Drupal/Core/Theme/Registry.php       | 23 +++++++++++++--
 core/lib/Drupal/Core/Theme/ThemeManager.php   | 29 +++++++++++++++----
 .../Core/Theme/ThemeManagerInterface.php      | 22 ++++++++++++++
 .../system/src/Tests/Theme/RegistryTest.php   | 21 +++++++++++++-
 .../tests/themes/test_theme/test_theme.theme  |  7 +++++
 .../Drupal/Tests/Core/Theme/RegistryTest.php  |  9 ++++++
 7 files changed, 106 insertions(+), 11 deletions(-)

diff --git a/core/core.services.yml b/core/core.services.yml
index cf9082c0d6a4..d723cd8ea9c7 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1092,7 +1092,9 @@ services:
     class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry
   theme.manager:
     class: Drupal\Core\Theme\ThemeManager
-    arguments: ['@app.root', '@theme.registry', '@theme.negotiator', '@theme.initialization', '@request_stack', '@module_handler']
+    arguments: ['@app.root', '@theme.negotiator', '@theme.initialization', '@request_stack', '@module_handler']
+    calls:
+      - [setThemeRegistry, ['@theme.registry']]
   theme.initialization:
     class: Drupal\Core\Theme\ThemeInitialization
     arguments: ['@app.root', '@theme_handler', '@state']
@@ -1101,6 +1103,8 @@ services:
     arguments: ['@app.root', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization']
     tags:
       - { name: needs_destruction }
+    calls:
+      - [setThemeManager, ['@theme.manager']]
   authentication:
     class: Drupal\Core\Authentication\AuthenticationManager
     tags:
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index a0b1f6473fab..5e450ab851e0 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -130,6 +130,13 @@ class Registry implements DestructableInterface {
    */
   protected $themeHandler;
 
+  /**
+   * The theme manager.
+   *
+   * @var \Drupal\Core\Theme\ThemeManagerInterface
+   */
+  protected $themeManager;
+
   /**
    * Constructs a \Drupal\Core\Theme\Registry object.
    *
@@ -158,6 +165,16 @@ public function __construct($root, CacheBackendInterface $cache, LockBackendInte
     $this->themeInitialization = $theme_initialization;
   }
 
+  /**
+   * Sets the theme manager.
+   *
+   * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
+   *   The theme manager.
+   */
+  public function setThemeManager(ThemeManagerInterface $theme_manager) {
+    $this->themeManager = $theme_manager;
+  }
+
   /**
    * Initializes a theme with a certain name.
    *
@@ -173,7 +190,7 @@ protected function init($theme_name = NULL) {
     }
     // Unless instantiated for a specific theme, use globals.
     if (!isset($theme_name)) {
-      $this->theme = \Drupal::theme()->getActiveTheme();
+      $this->theme = $this->themeManager->getActiveTheme();
     }
     // Instead of the active theme, a specific theme was requested.
     else {
@@ -326,9 +343,9 @@ protected function build() {
     // Finally, hooks provided by the theme itself.
     $this->processExtension($cache, $this->theme->getName(), 'theme', $this->theme->getName(), $this->theme->getPath());
 
-    // Let modules alter the registry.
+    // Let modules and themes alter the registry.
     $this->moduleHandler->alter('theme_registry', $cache);
-    // @todo Do we want to allow themes to take part?
+    $this->themeManager->alterForTheme($this->theme, 'theme_registry', $cache);
 
     // @todo Implement more reduction of the theme registry entry.
     // Optimize the registry to not have empty arrays for functions.
diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php
index a100374e95d0..6850fcb283f6 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManager.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManager.php
@@ -62,8 +62,6 @@ class ThemeManager implements ThemeManagerInterface {
    *
    * @param string $root
    *   The app root.
-   * @param \Drupal\Core\Theme\Registry $theme_registry
-   *   The theme registry.
    * @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator
    *   The theme negotiator.
    * @param \Drupal\Core\Theme\ThemeInitialization $theme_initialization
@@ -72,15 +70,27 @@ class ThemeManager implements ThemeManagerInterface {
    *   The request stack.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    */
-  public function __construct($root, Registry $theme_registry, ThemeNegotiatorInterface $theme_negotiator, ThemeInitialization $theme_initialization, RequestStack $request_stack, ModuleHandlerInterface $module_handler) {
+  public function __construct($root, ThemeNegotiatorInterface $theme_negotiator, ThemeInitialization $theme_initialization, RequestStack $request_stack, ModuleHandlerInterface $module_handler) {
     $this->root = $root;
     $this->themeNegotiator = $theme_negotiator;
-    $this->themeRegistry = $theme_registry;
     $this->themeInitialization = $theme_initialization;
     $this->requestStack = $request_stack;
     $this->moduleHandler = $module_handler;
   }
 
+  /**
+   * Sets the theme registry.
+   *
+   * @param \Drupal\Core\Theme\Registry $theme_registry
+   *   The theme registry.
+   *
+   * @return $this
+   */
+  public function setThemeRegistry(Registry $theme_registry) {
+    $this->themeRegistry = $theme_registry;
+    return $this;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -402,7 +412,7 @@ protected function initTheme(RouteMatchInterface $route_match = NULL) {
    *
    * @todo Should we cache some of these information?
    */
-  public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
+  public function alterForTheme(ActiveTheme $theme, $type, &$data, &$context1 = NULL, &$context2 = NULL) {
     // Most of the time, $type is passed as a string, so for performance,
     // normalize it to that. When passed as an array, usually the first item in
     // the array is a generic type, and additional items in the array are more
@@ -419,7 +429,6 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
     }
 
     $theme_keys = array();
-    $theme = $this->getActiveTheme();
     foreach ($theme->getBaseThemes() as $base) {
       $theme_keys[] = $base->getName();
     }
@@ -446,4 +455,12 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
+    $theme = $this->getActiveTheme();
+    $this->alterForTheme($theme, $type, $data, $context1, $context2);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
index b88d8faee217..fdc633438b65 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
@@ -69,6 +69,8 @@ public function setActiveTheme(ActiveTheme $active_theme);
   /**
    * Passes alterable variables to specific $theme_TYPE_alter() implementations.
    *
+   * It also invokes alter hooks for all base themes.
+   *
    * $theme specifies the theme name of the active theme and all its base
    * themes.
    *
@@ -122,4 +124,24 @@ public function setActiveTheme(ActiveTheme $active_theme);
    */
   public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL);
 
+  /**
+   * Provides an alter hook for a specific theme.
+   *
+   * Similar to ::alter, it also invokes the alter hooks for the base themes.
+   *
+   * @param \Drupal\Core\Theme\ActiveTheme $theme
+   *   A manually specified theme.
+   * @param string|array $type
+   *   A string describing the type of the alterable $data.
+   * @param mixed $data
+   *   The variable that will be passed to $theme_TYPE_alter() implementations
+   * @param mixed $context1
+   *   (optional) An additional variable that is passed by reference.
+   * @param mixed $context2
+   *   (optional) An additional variable that is passed by reference.
+   *
+   * @see ::alter
+   */
+  public function alterForTheme(ActiveTheme $theme, $type, &$data, &$context1 = NULL, &$context2 = NULL);
+
 }
diff --git a/core/modules/system/src/Tests/Theme/RegistryTest.php b/core/modules/system/src/Tests/Theme/RegistryTest.php
index 8b6cb0afb1df..22f0cde15ba2 100644
--- a/core/modules/system/src/Tests/Theme/RegistryTest.php
+++ b/core/modules/system/src/Tests/Theme/RegistryTest.php
@@ -23,7 +23,7 @@ class RegistryTest extends KernelTestBase {
    *
    * @var array
    */
-  public static $modules = array('theme_test');
+  public static $modules = array('theme_test', 'system');
 
   protected $profile = 'testing';
   /**
@@ -72,8 +72,11 @@ public function testMultipleSubThemes() {
     $theme_handler->install(['test_basetheme', 'test_subtheme', 'test_subsubtheme']);
 
     $registry_subsub_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subsubtheme');
+    $registry_subsub_theme->setThemeManager(\Drupal::theme());
     $registry_sub_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subtheme');
+    $registry_sub_theme->setThemeManager(\Drupal::theme());
     $registry_base_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_basetheme');
+    $registry_base_theme->setThemeManager(\Drupal::theme());
 
     $preprocess_functions = $registry_subsub_theme->get()['theme_test_template_test']['preprocess functions'];
     $this->assertIdentical([
@@ -96,4 +99,20 @@ public function testMultipleSubThemes() {
       'test_basetheme_preprocess_theme_test_template_test',
     ], $preprocess_functions);
   }
+
+  /**
+   * Tests that the theme registry can be altered by themes.
+   */
+  public function testThemeRegistryAlterByTheme() {
+
+    /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
+    $theme_handler = \Drupal::service('theme_handler');
+    $theme_handler->install(['test_theme']);
+    $theme_handler->setDefault('test_theme');
+
+    $registry = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_theme');
+    $registry->setThemeManager(\Drupal::theme());
+    $this->assertEqual('value', $registry->get()['theme_test_template_test']['variables']['additional']);
+  }
+
 }
diff --git a/core/modules/system/tests/themes/test_theme/test_theme.theme b/core/modules/system/tests/themes/test_theme/test_theme.theme
index 8c74ccdf487b..951fd583f6a3 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -94,3 +94,10 @@ function test_theme_theme_test_function_suggestions__theme_override($variables)
 function test_theme_theme_test_function_suggestions__module_override($variables) {
   return 'Theme function overridden based on new theme suggestion provided by a module.';
 }
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function test_theme_theme_registry_alter(&$registry) {
+  $registry['theme_test_template_test']['variables']['additional'] = 'value';
+}
diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
index 2365a25b64a5..4a9101d0b4df 100644
--- a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
+++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
@@ -59,6 +59,13 @@ class RegistryTest extends UnitTestCase {
    */
   protected $themeInitialization;
 
+  /**
+   * The theme manager.
+   *
+   * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $themeManager;
+
   /**
    * {@inheritdoc}
    */
@@ -70,6 +77,7 @@ protected function setUp() {
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
     $this->themeHandler = $this->getMock('Drupal\Core\Extension\ThemeHandlerInterface');
     $this->themeInitialization = $this->getMock('Drupal\Core\Theme\ThemeInitializationInterface');
+    $this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
 
     $this->setupTheme();
   }
@@ -124,6 +132,7 @@ public function testGetRegistryForModule() {
 
   protected function setupTheme($theme_name = NULL) {
     $this->registry = new TestRegistry($this->root, $this->cache, $this->lock, $this->moduleHandler, $this->themeHandler, $this->themeInitialization, $theme_name);
+    $this->registry->setThemeManager($this->themeManager);
   }
 
 }
-- 
GitLab