From 3f5bb7d9fc8bb1be2980db075124d3ce6044fef1 Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Wed, 8 Oct 2014 12:49:20 +0100
Subject: [PATCH] Issue #2346369 by Wim Leers: Support special '#attached'
 variable for attaching assets in preprocess functions.

---
 core/includes/theme.inc                       | 20 ++++++-----
 .../system/src/Tests/Common/RenderTest.php    | 25 ++++++++++++++
 .../modules/common_test/common_test.module    | 24 +++++++++++++
 core/modules/system/theme.api.php             | 15 ++++++++
 core/modules/views/views.module               |  3 +-
 core/themes/bartik/bartik.theme               | 13 ++-----
 core/themes/seven/seven.libraries.yml         |  2 +-
 core/themes/seven/seven.theme                 | 34 +++----------------
 8 files changed, 85 insertions(+), 51 deletions(-)

diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 03dc12192d35..b84302918545 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -372,6 +372,15 @@ function _theme($hook, $variables = array()) {
         $preprocessor_function($variables, $hook, $info);
       }
     }
+    // Allow theme preprocess functions to set $variables['#attached'] and use
+    // it like the #attached property on render arrays. In Drupal 8, this is the
+    // (only) officially supported method of attaching assets from preprocess
+    // functions. Assets attached here should be associated with the template
+    // that we're preprocessing variables for.
+    if (isset($variables['#attached'])) {
+      $preprocess_attached = ['#attached' => $variables['#attached']];
+      drupal_render($preprocess_attached, TRUE);
+    }
   }
 
   // Generate the output using either a function or a template.
@@ -1946,15 +1955,8 @@ function template_preprocess_maintenance_page(&$variables) {
   $attributes['class'] = $classes;
 
   // @see system_page_build()
-  $attached = array(
-    '#attached' => array(
-      'library' => array(
-        'core/normalize',
-        'system/maintenance',
-      ),
-    ),
-  );
-  drupal_render($attached);
+  $variables['#attached']['library'][] = 'core/normalize';
+  $variables['#attached']['library'][] = 'system/maintenance';
 }
 
 /**
diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php
index d07085cf2cfb..6c286cab0163 100644
--- a/core/modules/system/src/Tests/Common/RenderTest.php
+++ b/core/modules/system/src/Tests/Common/RenderTest.php
@@ -410,6 +410,31 @@ function testDrupalRenderThemeArguments() {
     $this->assertEqual(drupal_render($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works');
   }
 
+  /**
+   * Tests theme preprocess functions being able to attach assets.
+   */
+  function testDrupalRenderThemePreprocessAttached() {
+    \Drupal::state()->set('theme_preprocess_attached_test', TRUE);
+
+    $test_element = [
+      '#theme' => 'common_test_render_element',
+      'foo' => [
+        '#markup' => 'Kittens!',
+      ],
+    ];
+    drupal_render($test_element);
+
+    $expected_attached = [
+      'library' => [
+        'test/generic_preprocess',
+        'test/specific_preprocess',
+      ]
+    ];
+    $this->assertEqual($expected_attached, $test_element['#attached'], 'All expected assets from theme preprocess hooks attached.');
+
+    \Drupal::state()->set('theme_preprocess_attached_test', FALSE);
+  }
+
   /**
    * Tests caching of an empty render item.
    */
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index e23a842eedba..e812b617d6df 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -142,6 +142,30 @@ function theme_common_test_empty($variables) {
   return '';
 }
 
+/**
+ * Implements MODULE_preprocess().
+ *
+ * @see RenderTest::testDrupalRenderThemePreprocessAttached()
+ */
+function common_test_preprocess(&$variables, $hook) {
+  if (!\Drupal::state()->get('theme_preprocess_attached_test', FALSE)) {
+    return;
+  }
+  $variables['#attached']['library'][] = 'test/generic_preprocess';
+}
+
+/**
+ * Implements MODULE_preprocess_HOOK().
+ *
+ * @see RenderTest::testDrupalRenderThemePreprocessAttached()
+ */
+function common_test_preprocess_common_test_render_element(&$variables) {
+  if (!\Drupal::state()->get('theme_preprocess_attached_test', FALSE)) {
+    return;
+  }
+  $variables['#attached']['library'][] = 'test/specific_preprocess';
+}
+
 /**
  * Implements hook_library_info_alter().
  */
diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php
index 1a613b7d6f75..26c998aab64e 100644
--- a/core/modules/system/theme.api.php
+++ b/core/modules/system/theme.api.php
@@ -159,6 +159,21 @@
  * suggestions as input, and can change this array (adding suggestions and
  * removing them).
  *
+ * @section Assets
+ *
+ * We can distinguish between two types of assets:
+ * 1. global assets (loaded on all pages where the theme is in use): these are
+ *    defined in the theme's *.info.yml file.
+ * 2. template-specific assets (loaded on all pages where a specific template is
+ *    in use): these can be added by in preprocessing functions, using @code
+ *    $variables['#attached'] @endcode, e.g.:
+ *    @code
+ *    function seven_preprocess_menu_local_action(array &$variables) {
+ *      // We require Modernizr's touch test for button styling.
+ *      $variables['#attached']['library'][] = 'core/modernizr';
+ *    }
+ *    @endcode
+ *
  * @see hooks
  * @see callbacks
  * @see theme_render
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index f3877e03c0ef..a41e73e182b5 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -329,8 +329,7 @@ function views_preprocess_page(&$variables) {
       unset($class[$key]);
       $attributes['class'] = $class;
       $attributes['data-views-page-contextual-id'] = $variables['title_suffix']['contextual_links']['#id'];
-      $attached['#attached']['library'][] = 'views/views.contextual-links';
-      drupal_render($attached);
+      $variables['#attached']['library'][] = 'views/views.contextual-links';
     }
   }
 }
diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme
index db027bea9ba3..60f9a34d8c4d 100644
--- a/core/themes/bartik/bartik.theme
+++ b/core/themes/bartik/bartik.theme
@@ -87,16 +87,9 @@ function bartik_preprocess_maintenance_page(&$variables) {
   if (!$variables['db_is_active']) {
     $variables['site_name'] = '';
   }
-  // Normally we could attach libraries via hook_page_alter(), but when the
-  // database is inactive it's not called so we add them here.
-  $libraries = array(
-    '#attached' => array(
-      'library' => array(
-        'bartik/maintenance_page',
-      ),
-    ),
-  );
-  drupal_render($libraries);
+
+  // Bartik has custom styling for the maintenance page.
+  $variables['#attached']['library'][] = 'bartik/maintenance_page';
 
   // Set the options that apply to both page and maintenance page.
   _bartik_process_page($variables);
diff --git a/core/themes/seven/seven.libraries.yml b/core/themes/seven/seven.libraries.yml
index d7a9495d0b79..a79487161cdf 100644
--- a/core/themes/seven/seven.libraries.yml
+++ b/core/themes/seven/seven.libraries.yml
@@ -16,7 +16,7 @@ install-page:
     theme:
       css/theme/install-page.css: {}
   dependencies:
-    - system/maintenance
+    - seven/maintenance-page
 
 drupal.nav-tabs:
   version: VERSION
diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme
index ccd1c92bc1f4..88c25704bbbf 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -128,14 +128,7 @@ function seven_preprocess_menu_local_action(array &$variables) {
   $variables['link']['#options']['attributes']['class'][] = 'button--small';
 
   // We require Modernizr's touch test for button styling.
-  $libraries = array(
-    '#attached' => array(
-      'library' => array(
-        'core/modernizr',
-      ),
-    ),
-  );
-  drupal_render($libraries);
+  $variables['#attached']['library'][] = 'core/modernizr';
 }
 
 /**
@@ -158,17 +151,8 @@ function seven_preprocess_install_page(&$variables) {
   $classes[] = 'install-background';
   $attributes['class'] = $classes;
 
-  // Normally we could attach libraries via hook_page_alter(), but when the
-  // database is inactive it's not called so we add them here.
-  $libraries = array(
-    '#attached' => array(
-      'library' => array(
-        'seven/maintenance-page',
-        'seven/install-page',
-      ),
-    ),
-  );
-  drupal_render($libraries);
+  // Seven has custom styling for the install page.
+  $variables['#attached']['library'][] = 'seven/install-page';
 }
 
 /**
@@ -181,16 +165,8 @@ function seven_preprocess_maintenance_page(&$variables) {
   $classes[] = 'maintenance-background';
   $attributes['class'] = $classes;
 
-  // // Normally we could attach libraries via hook_page_alter(), but when the
-  // // database is inactive it's not called so we add them here.
-  $libraries = array(
-    '#attached' => array(
-      'library' => array(
-        'seven/maintenance-page',
-      ),
-    ),
-  );
-  drupal_render($libraries);
+  // Seven has custom styling for the maintenance page.
+  $variables['#attached']['library'][] = 'seven/maintenance-page';
 }
 
 /**
-- 
GitLab