diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index 3a26493..65d2afe 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -580,6 +580,43 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path) } /** + * Completes the definition of the requested suggestion hook. + * + * @param string $hook + * The name of the suggestion hook to complete. + * @param array $cache + * The theme registry, as documented in + * \Drupal\Core\Theme\Registry::processExtension(). + */ + protected function completeSuggestion($hook, array &$cache) { + $previous_hook = $hook; + $incomplete_previous_hook = array(); + while ((!isset($cache[$previous_hook]) || isset($cache[$previous_hook]['incomplete preprocess functions'])) + && $pos = strrpos($previous_hook, '__')) { + if (isset($cache[$previous_hook]) && !$incomplete_previous_hook && isset($cache[$previous_hook]['incomplete preprocess functions'])) { + $incomplete_previous_hook = $cache[$previous_hook]; + unset($incomplete_previous_hook['incomplete preprocess functions']); + } + $previous_hook = substr($previous_hook, 0, $pos); + + // If base hook exists clone of it for the preprocess function + // without a template. + // @see https://www.drupal.org/node/2457295 + if (isset($cache[$previous_hook]) && !isset($cache[$previous_hook]['incomplete preprocess functions'])) { + $cache[$hook] = $incomplete_previous_hook + $cache[$previous_hook]; + if (isset($incomplete_previous_hook['preprocess functions'])) { + $diff = array_diff($incomplete_previous_hook['preprocess functions'], $cache[$previous_hook]['preprocess functions']); + $cache[$hook]['preprocess functions'] = array_merge($cache[$previous_hook]['preprocess functions'], $diff); + } + // If a base hook isn't set, this is the actual base hook. + if (!isset($cache[$previous_hook]['base hook'])) { + $cache[$hook]['base hook'] = $previous_hook; + } + } + } + } + + /** * Merges the preceding hook's preprocess functions into the current hook's. * * @param string $hook @@ -613,43 +650,6 @@ protected function mergePreprocessFunctions($hook, $complete_hook, array &$cache } /** - * Completes the definition of the requested suggestion hook. - * - * @param string $hook - * The name of the suggestion hook to complete. - * @param array $cache - * The theme registry, as documented in - * \Drupal\Core\Theme\Registry::processExtension(). - */ - protected function completeSuggestion($hook, array &$cache) { - $previous_hook = $hook; - // Loop until we find a preceding completed hook definition. - while ((!isset($cache[$previous_hook]) || isset($cache[$previous_hook]['incomplete preprocess functions'])) - && $pos = strrpos($previous_hook, '__')) { - $previous_hook = substr($previous_hook, 0, $pos); - - if (!isset($cache[$previous_hook])) { - continue; - } - - // If there is a incomplete hook and we are creating a new theme registry - // entry, copy it as a base for this one. - if (!isset($cache[$hook]) && isset($cache[$previous_hook]['incomplete preprocess functions'])) { - $cache[$hook] = $cache[$previous_hook]; - } - // First found completed hook has the correct set of preprocess functions. - if (!isset($cache[$previous_hook]['incomplete preprocess functions'])) { - $this->mergePreprocessFunctions($hook, $previous_hook, $cache); - - // If a base hook isn't set, this is the actual base hook. - if (!isset($cache[$previous_hook]['base hook'])) { - $cache[$hook]['base hook'] = $previous_hook; - } - } - } - } - - /** * Completes the theme registry adding discovered functions and hooks. * * @param array $cache @@ -707,7 +707,11 @@ protected function postProcessExtension(array &$cache, ActiveTheme $theme) { ksort($suggestion_level); foreach ($suggestion_level as $level => $item) { foreach ($item as $preprocessor => $hook) { - if (strpos($hook, '__')) { + if (isset($cache[$hook]['preprocess functions']) && !in_array($hook, $cache[$hook]['preprocess functions'])) { + // Add missing preprocessor to existing hook. + $cache[$hook]['preprocess functions'][] = $preprocessor; + } + elseif (!isset($cache[$hook]) && strpos($hook, '__')) { // Process non-existing hook and register it. // Look for a previously defined hook that is either a less specific // suggestion hook or the base hook. diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php index a5f9a47..f3eb364 100644 --- a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php @@ -199,29 +199,6 @@ public function setUpPostProcessExtension() { } /** - * Tests that suggestion preprocess functions are added to the registry. - * - * @param array $cache - * The incomplete theme registry. - * @param array $functions - * The preprocess functions that are defined. - * @param array $expected - * The completed theme registry. - * - * @dataProvider providerPreprocessFunctionCompletion - */ - public function testPreprocessFunctionCompletion(array $cache, array $functions, array $expected) { - - $this->setUpPostProcessExtension(); - self::$functions['user'] = $functions; - - $this->reflectionMethod->invokeArgs($this->registry, [ & $cache, $this->theme]); - - $this->assertArrayEquals($cache, $expected); - - } - - /** * Tests that preprocess functions are added when base hook is specified. */ public function testManualBaseHook() { @@ -229,30 +206,28 @@ public function testManualBaseHook() { $functions = ['cat', 'mouse', 'cheese']; self::$functions['user'] = $functions; // Define a test case registry with mock data. - $cache = [ - 'test_hook' => - ['preprocess functions' => ['cat', 'mouse'], ], - 'test_hook_manual' => - ['preprocess functions' => ['cheese'], - 'base hook' => 'test_hook', - 'incomplete preprocess functions' => TRUE, ] + $hooks = [ + 'test_hook' => + ['preprocess functions' => ['cat', 'mouse'], ], + 'test_hook_manual' => + ['preprocess functions' => ['cheese'], + 'base hook' => 'test_hook', + 'incomplete preprocess functions' => TRUE, ] ]; $expected = [ - 'test_hook' => - ['preprocess functions' => ['cat', 'mouse'], - ], - 'test_hook_manual' => - ['preprocess functions' => ['cat', 'mouse', 'cheese'], - 'base hook' => 'test_hook', - ] + 'test_hook' => + ['preprocess functions' => ['cat', 'mouse'], ], + 'test_hook_manual' => + ['preprocess functions' => ['cat', 'mouse', 'cheese'], + 'base hook' => 'test_hook', ] ]; $this->setUpPostProcessExtension(); - $this->reflectionMethod->invokeArgs($this->registry, [ & $cache, $this->theme]); + $this->reflectionMethod->invokeArgs($this->registry, [ &$hooks, $this->theme]); - $this->assertArrayEquals($cache, $expected); + $this->assertArrayEquals($hooks, $expected); } @@ -265,36 +240,35 @@ public function testManualSuggestion() { $functions = ['cat', 'mouse', 'cheese']; - $cache = [ - 'test_hook' => - ['preprocess functions' => ['cat', 'mouse'], ], - 'test_hook__suggestion' => - ['preprocess functions' => ['bread'], - 'incomplete preprocess functions' => TRUE, ], - 'test_hook__suggestion__another' => - ['preprocess functions' => ['cheese'], - 'incomplete preprocess functions' => TRUE, ] - + $hooks = [ + 'test_hook' => + ['preprocess functions' => ['cat', 'mouse'], + 'template' => 'test-hook'], + 'test_hook__suggestion' => + ['preprocess functions' => ['bread'], + 'incomplete preprocess functions' => TRUE, ], + 'test_hook__suggestion__another' => + ['preprocess functions' => ['cheese'], + 'incomplete preprocess functions' => TRUE, ] ]; $expected = [ - 'test_hook' => - ['preprocess functions' => ['cat', 'mouse'], - ], - 'test_hook__suggestion' => - ['preprocess functions' => ['cat', 'mouse', 'bread', - 'test_preprocess_test_hook__suggestion'], - 'base hook' => 'test_hook', - ], - 'test_hook__suggestion__another' => - ['preprocess functions' => - ['cat', 'mouse', 'bread', - 'test_preprocess_test_hook__suggestion', - 'cheese', - 'test_preprocess_test_hook__suggestion__another'], - 'base hook' => 'test_hook', - ] - + 'test_hook' => + ['preprocess functions' => ['cat', 'mouse'], + 'template' => 'test-hook', ], + 'test_hook__suggestion' => + ['preprocess functions' => ['cat', 'mouse', 'bread', + 'test_preprocess_test_hook__suggestion'], + 'base hook' => 'test_hook', + 'template' => 'test-hook', ], + 'test_hook__suggestion__another' => + ['preprocess functions' => + ['cat', 'mouse', 'bread', + 'test_preprocess_test_hook__suggestion', + 'cheese', + 'test_preprocess_test_hook__suggestion__another'], + 'base hook' => 'test_hook', + 'template' => 'test-hook', ] ]; $functions = ['cat', 'mouse', 'cheese', 'bread', @@ -302,266 +276,9 @@ public function testManualSuggestion() { 'test_preprocess_test_hook__suggestion__another']; self::$functions['user'] = $functions; - $this->reflectionMethod->invokeArgs($this->registry, [ & $cache, $this->theme]); + $this->reflectionMethod->invokeArgs($this->registry, [ &$hooks, $this->theme]); - $this->assertArrayEquals($cache, $expected); - } - - - /** - * Provides test data for testPreprocessFunctionCompletion(). - * - * @return array - * Test data. - */ - public function providerPreprocessFunctionCompletion() { - // A pairwise test set listing which suggestion hooks should be defined. - // We test up to three suggestions. For the third level suggestion hook - // unset hook with no preprocess is not included in the test set. - // - // Each line determines a test case. - // First array determines theme hooks that are defined: - // - First column: hook__s1 is defined in the registry. - // - Second column: hook__s1__s2 is defined in the registry. - // - Third column: hook__s1__s2__s3 is defined in the registry. - // - // Second array determines preprocess functions that are defined: - // - First column: test_preprocess_hook__s1 is defined. - // - Second column: test_preprocess_hook__s1__s2 is defined. - // - Third column: test_preprocess_hook__s1__s2__s3 is defined. - $test_definitions = [ - [[TRUE, TRUE, FALSE], [TRUE, TRUE, TRUE]], - [[TRUE, FALSE, TRUE], [FALSE, FALSE, TRUE]], - [[FALSE, TRUE, TRUE], [FALSE, FALSE, FALSE]], - [[FALSE, FALSE, TRUE], [TRUE, TRUE, TRUE]], - [[TRUE, TRUE, TRUE], [FALSE, TRUE, FALSE]], - [[FALSE, TRUE, TRUE], [TRUE, FALSE, FALSE]], - [[FALSE, FALSE, FALSE], [FALSE, FALSE, TRUE]], - [[TRUE, FALSE, TRUE], [FALSE, TRUE, FALSE]], - [[FALSE, TRUE, TRUE], [TRUE, FALSE, TRUE]], - ]; - - $expected_test_outputs = [ - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1', - 'preprocess functions' => [ - 'template_preprocess','test_preprocess_hook__s1']], - 'hook__s1__s2' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1', - 'test_preprocess_hook__s1__s2']], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1', - 'test_preprocess_hook__s1__s2', - 'test_preprocess_hook__s1__s2__s3' - ]]], - - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1', - 'preprocess functions' => [ - 'template_preprocess' ]], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2__s3', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1__s2__s3' - ]]], - - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1__s2' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2', - 'preprocess functions' => [ - 'template_preprocess' ]], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2__s3', - 'preprocess functions' => [ - 'template_preprocess', - ]]], - - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1' => [ - 'base hook' => 'hook', - 'template' => 'hook', - 'preprocess functions' => [ - 'template_preprocess', 'test_preprocess_hook__s1']], - 'hook__s1__s2' => [ - 'base hook' => 'hook', - 'template' => 'hook', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1', - 'test_preprocess_hook__s1__s2']], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2__s3', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1', - 'test_preprocess_hook__s1__s2', - 'test_preprocess_hook__s1__s2__s3' - ]]], - - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1', - 'preprocess functions' => [ - 'template_preprocess']], - 'hook__s1__s2' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1__s2']], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2__s3', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1__s2', - ]]], - - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1' => [ - 'base hook' => 'hook', - 'template' => 'hook', - 'preprocess functions' => [ - 'template_preprocess','test_preprocess_hook__s1']], - 'hook__s1__s2' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1' ]], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2__s3', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1', - ]]], - - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1__s2__s3' - ]]], - - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1', - 'preprocess functions' => [ - 'template_preprocess' ]], - 'hook__s1__s2' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1', - - 'preprocess functions' => [ - 'template_preprocess','test_preprocess_hook__s1__s2']], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2__s3', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1__s2' - ]]], - - [ 'hook' => [ - 'preprocess functions' => ['template_preprocess'], - 'template' => 'hook'], - 'hook__s1' => [ - 'base hook' => 'hook', - 'template' => 'hook', - 'preprocess functions' => [ - 'template_preprocess','test_preprocess_hook__s1']], - 'hook__s1__s2' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2', - 'preprocess functions' => [ - 'template_preprocess','test_preprocess_hook__s1']], - 'hook__s1__s2__s3' => [ - 'base hook' => 'hook', - 'template' => 'hook__s1__s2__s3', - 'preprocess functions' => [ - 'template_preprocess', - 'test_preprocess_hook__s1', - 'test_preprocess_hook__s1__s2__s3' - ]]], - - ]; - - // The theme hook and preprocess function names used in the tests. - $hook_names = ['hook__s1', 'hook__s1__s2', 'hook__s1__s2__s3']; - $preprocess_function_names = ['test_preprocess_hook__s1', - 'test_preprocess_hook__s1__s2', - 'test_preprocess_hook__s1__s2__s3']; - - $base_hook = 'hook'; - - foreach ($test_definitions as $test_index => $test_definition) { - - $test = [ - 'tested theme hooks' => [ $base_hook => - ['preprocess functions' => ['template_preprocess'], - 'template' => $base_hook]], - 'tested preprocess functions' => [], - 'expected test result' => [] - ]; - - // Add the wanted suggestion theme hooks to test data. - foreach ($test_definition[0] as $key => $hook_enabled) { - if ($hook_enabled) { - $test['tested theme hooks'][$hook_names[$key]] - = ['base hook' => $base_hook, - 'template' => $hook_names[$key], - 'incomplete preprocess functions' => TRUE]; - } - } - // Add the wanted suggestion preprocess functions to test data. - foreach ($test_definition[1] as $key => $function_enabled) { - if ($function_enabled) { - $test['tested preprocess functions'][] = $preprocess_function_names[$key]; - } - } - $test['expected test result'] = $expected_test_outputs[$test_index]; - $tests[] = $test; - } - - return $tests; + $this->assertArrayEquals($hooks, $expected); } protected function setupTheme($theme_name = NULL) { @@ -592,7 +309,6 @@ protected function listThemes() { protected function initializeTheme() { } - } }