From b3c15905cdb0b1fa4b6ab9c3046a9a1b949c2998 Mon Sep 17 00:00:00 2001
From: Mark Carver <mark.carver@me.com>
Date: Mon, 22 Jul 2013 21:26:31 -0500
Subject: Issue #2035055 by Mark Carver: Introduce hook_theme_prepare(), and
 hook_theme_HOOK_prepare()

---
 core/includes/theme.inc                            | 12 ++++
 .../Drupal/system/Tests/Theme/ThemePrepareTest.php | 50 ++++++++++++++++
 .../lib/Drupal/theme_test/ThemeTestController.php  | 12 ++++
 .../tests/modules/theme_test/theme_test.module     | 67 ++++++++++++++++++++++
 .../modules/theme_test/theme_test.routing.yml      | 12 ++++
 5 files changed, 153 insertions(+)
 create mode 100644 core/modules/system/lib/Drupal/system/Tests/Theme/ThemePrepareTest.php

diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 7bff76e..32c7322 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1036,6 +1036,18 @@ function theme($hook, $variables = array()) {
     'theme_hook_original' => $original_hook,
   );

+  // Invoke hook_theme_prepare().
+  $variables = NestedArray::mergeDeep($variables, Drupal::moduleHandler()->invokeAll('theme_prepare', array($hook, $variables)));
+  // Invoke hook_theme_prepare_HOOK().
+  $variables = NestedArray::mergeDeep($variables, Drupal::moduleHandler()->invokeAll('theme_prepare_' . $hook, array($variables)));
+
+  // Clone the hook, so it cannot be modified via prepare alters.
+  $hook_clone = $hook;
+  // Alter variables by invoking hook_theme_prepare_alter($variables, $hook).
+  Drupal::moduleHandler()->alter('theme_prepare', $variables, $hook_clone);
+  // Alter variables by invoking hook_theme_prepare_HOOK_alter($variables).
+  Drupal::moduleHandler()->alter('theme_prepare_' . $hook, $variables);
+
   // Invoke the variable processors, if any. The processors may specify
   // alternate suggestions for which hook's template/function to use. If the
   // hook is a suggestion of a base hook, invoke the variable processors of
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemePrepareTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemePrepareTest.php
new file mode 100644
index 0000000..ce4c2cd
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemePrepareTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Theme\ThemeTest.
+ */
+
+namespace Drupal\system\Tests\Theme;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\test_theme\ThemeClass;
+
+/**
+ * Tests low-level theme functions.
+ */
+class ThemePrepareTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('theme_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Theme Prepare API',
+      'description' => 'Test theme prepare and alter functions.',
+      'group' => 'Theme',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+    theme_enable(array('test_theme'));
+  }
+
+  function testThemePrepare() {
+    $this->drupalGet('theme-test/prepare', array('query' => array('XDEBUG_SESSION_START' => 'PHPSTORM')));
+    $this->assertText('Inserted variable1 via hook_theme_prepare().', 'Inserted variable1 via hook_theme_prepare().');
+    $this->assertText('Inserted variable2 via hook_theme_prepare_HOOK().', 'Inserted variable2 via hook_theme_prepare_HOOK().');
+  }
+
+  function testThemePrepareAlter() {
+    $this->drupalGet('theme-test/prepare-alter', array('query' => array('XDEBUG_SESSION_START' => 'PHPSTORM')));
+    $this->assertText('Inserted variable1 via hook_theme_prepare() was altered by hook_theme_prepare_alter().', 'Inserted variable1 via hook_theme_prepare() was altered by hook_theme_prepare_alter().');
+    $this->assertText('Inserted variable2 via hook_theme_prepare_HOOK() was altered by hook_theme_prepare_HOOK_alter().', 'Inserted variable2 via hook_theme_prepare_HOOK() was altered by hook_theme_prepare_HOOK_alter().');
+  }
+
+}
diff --git a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
index c790a84..acb5f77 100644
--- a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
+++ b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
@@ -31,4 +31,16 @@ function functionTemplateOverridden() {
     );
   }

+  function prepare() {
+    return array(
+      '#theme' => 'theme_test_prepare_variable',
+    );
+  }
+
+  function prepareAlter() {
+    return array(
+      '#theme' => 'theme_test_prepare_alter_variable',
+    );
+  }
+
 }
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index 51cb6d3..434edef 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -27,6 +27,12 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $items['theme_test_function_template_override'] = array(
     'variables' => array(),
   );
+  $items['theme_test_prepare_variable'] = array(
+    'variables' => array('element' => array()),
+  );
+  $items['theme_test_prepare_alter_variable'] = array(
+    'variables' => array('element' => array()),
+  );
   return $items;
 }

@@ -78,6 +84,14 @@ function theme_test_menu() {
     'theme callback' => '_theme_custom_theme',
     'route_name' => 'function_template_override',
   );
+  $items['theme-test/prepare'] = array(
+    'theme callback' => '_theme_custom_theme',
+    'route_name' => 'prepare',
+  );
+  $items['theme-test/prepare-alter'] = array(
+    'theme callback' => '_theme_custom_theme',
+    'route_name' => 'prepare_alter',
+  );
   return $items;
 }

@@ -197,3 +211,56 @@ function template_preprocess_theme_test_render_element(&$variables) {
 function theme_theme_test_render_element_children($variables) {
   return drupal_render($variables['element']);
 }
+
+function theme_theme_test_prepare_variable($variables) {
+  return drupal_render($variables['element']);
+}
+
+function theme_theme_test_prepare_alter_variable($variables) {
+  return drupal_render($variables['element']);
+}
+
+/**
+ * Tests prepare and alter hooks.
+ */
+function theme_test_theme_prepare($hook) {
+  if ($hook == 'theme_test_prepare_variable' || $hook == 'theme_test_prepare_alter_variable') {
+    return array(
+      'element' => array(
+        'variable1' => array(
+          '#markup' => '<p>Inserted variable1 via hook_theme_prepare().</p>',
+        ),
+      )
+    );
+  }
+}
+
+function theme_test_theme_prepare_theme_test_prepare_variable() {
+  return array(
+    'element' => array(
+      'variable2' => array(
+        '#markup' => '<p>Inserted variable2 via hook_theme_prepare_HOOK().</p>',
+      ),
+    ),
+  );
+}
+
+function theme_test_theme_prepare_theme_test_prepare_alter_variable() {
+  return array(
+    'element' => array(
+      'variable2' => array(
+        '#markup' => '<p>Inserted variable2 via hook_theme_prepare_HOOK().</p>',
+      ),
+    ),
+  );
+}
+
+function theme_test_theme_prepare_alter(&$variables, $hook) {
+  if ($hook == 'theme_test_prepare_alter_variable') {
+    $variables['element']['variable1']['#markup'] = '<p>Inserted variable1 via hook_theme_prepare() was altered by hook_theme_prepare_alter().</p>';
+  }
+}
+
+function theme_test_theme_prepare_theme_test_prepare_alter_variable_alter(&$variables) {
+  $variables['element']['variable2']['#markup'] = '<p>Inserted variable2 via hook_theme_prepare_HOOK() was altered by hook_theme_prepare_HOOK_alter().</p>';
+}
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
index 866171e..1e1dec5 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
+++ b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
@@ -4,3 +4,15 @@ function_template_override:
     _content: '\Drupal\theme_test\ThemeTestController::functionTemplateOverridden'
   requirements:
     _permission: 'access content'
+prepare:
+  pattern: '/theme-test/prepare'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::prepare'
+  requirements:
+    _permission: 'access content'
+prepare_alter:
+  pattern: '/theme-test/prepare-alter'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::prepareAlter'
+  requirements:
+    _permission: 'access content'
--
1.8.2

