diff --git a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
index df18779..0b001c7 100644
--- a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
+++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
@@ -161,7 +161,7 @@ public function processAttachments(AttachmentsInterface $response) {
['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'html_response_attachment_placeholders', 'placeholders', 'drupalSettings']
);
if (!empty($unsupported_types)) {
- throw new \LogicException(sprintf('You are not allowed to use %s in #attached', implode(', ', $unsupported_types)));
+ throw new \LogicException(sprintf('You are not allowed to use %s in #attached.', implode(', ', $unsupported_types)));
}
// Get the placeholders from attached and then remove them.
@@ -212,7 +212,7 @@ public function processAttachments(AttachmentsInterface $response) {
// Now replace the attachment placeholders.
$this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables);
- // Set the HTTP headers on the response if any bubbled.
+ // Set the HTTP headers and status code on the response if any bubbled.
if (!empty($attached['http_header'])) {
$this->setHeaders($response, $attached['http_header']);
}
@@ -318,6 +318,9 @@ protected function processAssetLibraries(array $attached, array $placeholders) {
/**
* Renders HTML response attachment placeholders.
*
+ * This is the last step where all of the attachments are placed into the
+ * response object's contents.
+ *
* @param \Drupal\Core\Render\HtmlResponse $response
* The HTML response to update.
* @param array $placeholders
@@ -343,7 +346,13 @@ protected function renderHtmlResponseAttachmentPlaceholders(HtmlResponse $respon
* @param \Drupal\Core\Render\HtmlResponse $response
* The HTML response to update.
* @param array $headers
- * The headers to set.
+ * The headers to set, as an array. The items in this array should be as
+ * follows:
+ * - The header name.
+ * - The header value.
+ * - (optional) Whether to replace a current value with the new one, or add
+ * it to the others. If the value is not replaced, it will be appended,
+ * resulting in a header like this: 'Header: value1,value2'
*/
protected function setHeaders(HtmlResponse $response, array $headers) {
foreach ($headers as $values) {
@@ -392,14 +401,13 @@ protected function processHtmlHead(array $html_head) {
* depending on options in the render array. Processing it can add to both the
* html_head and http_header sections.
*
- * Each head link is specified by a two-element array:
+ * @param array $html_head_link
+ * The 'html_head_link' value of a render array. Each head link is specified
+ * by a two-element array:
* - An array specifying the attributes of the link.
* - A boolean specifying whether the link should also be a Link: HTTP
* header.
*
- * @param array $html_head_link
- * The 'html_head_link' value of a render array.
- *
* @return array
* An ['#attached'] section of a render array. This allows us to easily
* merge the results with other render arrays. The array could contain the
@@ -432,6 +440,10 @@ protected function processHtmlHeadLink(array $html_head_link) {
}
/**
+ * Transform a 'feed' attachment into an 'html_head_link' attachment.
+ *
+ * The RSS feed is a special case of 'html_head_link', so we just turn it into
+ * one.
*
* @param array $attached_feed
* The ['#attached']['feed'] portion of a render array.
@@ -446,7 +458,7 @@ protected function processFeed($attached_feed) {
$feed_link = [
'href' => $item[0],
'rel' => 'alternate',
- 'title' => empty($attached_feed[1]) ? '' : $item[1],
+ 'title' => empty($item[1]) ? '' : $item[1],
'type' => 'application/rss+xml',
];
$html_head_link[] = [$feed_link, FALSE];
diff --git a/core/modules/system/src/Tests/Render/HtmlResponseAttachmentsTest.php b/core/modules/system/src/Tests/Render/HtmlResponseAttachmentsTest.php
index d8bcddb..d95b3fb 100644
--- a/core/modules/system/src/Tests/Render/HtmlResponseAttachmentsTest.php
+++ b/core/modules/system/src/Tests/Render/HtmlResponseAttachmentsTest.php
@@ -36,37 +36,31 @@ public function testAttachments() {
$this->assertResponse(418);
$this->assertHeader('X-Drupal-Cache', 'HIT');
- // Test ['#attached']['html_head'] with various replacement rules.
- $this->drupalGet('/render_attached_test/head');
- $this->assertHeader('X-Test-Teapot', 'Teapot Mode Active');
- $this->assertHeader('X-Test-Teapot-Replace', 'Teapot replaced');
- $this->assertHeader('X-Test-Teapot-No-Replace', 'This value is not replaced,This one is added');
+ // Test ['#attached']['http_header'] with various replacement rules.
+ $this->drupalGet('/render_attached_test/header');
+ $this->assertTeapotHeaders();
$this->assertHeader('X-Drupal-Cache', 'MISS');
// Repeat for the cache.
- $this->drupalGet('/render_attached_test/head');
+ $this->drupalGet('/render_attached_test/header');
$this->assertHeader('X-Drupal-Cache', 'HIT');
// Test ['#attached']['feed'].
$this->drupalGet('/render_attached_test/feed');
- // Discover the DOM element for the feed link.
- $link = $this->xpath('//head/link[@href="test://url"]');
- $this->assertEqual(1, count($link), 'Link has URL.');
- // Reconcile the other attributes.
- $link_attributes = [
- 'href' => 'test://url',
- 'rel' => 'alternate',
- 'type' => 'application/rss+xml',
- 'title' => '',
- ];
- $link = reset($link);
- foreach ($link->attributes() as $attribute => $value) {
- $this->assertEqual($value, $link_attributes[$attribute]);
- }
+ $this->assertHeader('X-Drupal-Cache', 'MISS');
+ $this->assertFeed();
// Repeat for the cache.
$this->drupalGet('/render_attached_test/feed');
$this->assertHeader('X-Drupal-Cache', 'HIT');
- // Now repeat the process using drupal_process_attached().
+ // Test ['#attached']['html_head'].
+ $this->drupalGet('/render_attached_test/head');
+ $this->assertHeader('X-Drupal-Cache', 'MISS');
+ $this->assertHead();
+ // Repeat for the cache.
+ $this->drupalGet('/render_attached_test/head');
+ $this->assertHeader('X-Drupal-Cache', 'HIT');
+
+ // Now repeat all of the preceeding using drupal_process_attached().
// Test ['#attached']['http_header] = ['Status', $code].
$this->drupalGet('/render_attached_test/teapot_dpa');
$this->assertResponse(418);
@@ -76,29 +70,29 @@ public function testAttachments() {
$this->assertResponse(418);
$this->assertHeader('X-Drupal-Cache', 'HIT');
- // Test ['#attached']['html_head'] with various replacement rules.
- $this->drupalGet('/render_attached_test/head_dpa');
- $this->assertHeader('X-Test-Teapot', 'Teapot Mode Active');
- $this->assertHeader('X-Test-Teapot-Replace', 'Teapot replaced');
- $this->assertHeader('X-Test-Teapot-No-Replace', 'This value is not replaced,This one is added');
+ // Test ['#attached']['http_header'] with various replacement rules.
+ $this->drupalGet('/render_attached_test/header_dpa');
$this->assertHeader('X-Drupal-Cache', 'MISS');
+ $this->assertTeapotHeaders();
// Repeat for the cache.
- $this->drupalGet('/render_attached_test/head_dpa');
+ $this->drupalGet('/render_attached_test/header_dpa');
$this->assertHeader('X-Drupal-Cache', 'HIT');
// Test ['#attached']['feed'].
$this->drupalGet('/render_attached_test/feed_dpa');
- // Discover the DOM element for the feed link.
- $link = $this->xpath('//head/link[@href="test://url"]');
- $this->assertEqual(1, count($link), 'Link has URL.');
- // Reconcile the other attributes.
- $link = reset($link);
- foreach ($link->attributes() as $attribute => $value) {
- $this->assertEqual($value, $link_attributes[$attribute]);
- }
+ $this->assertHeader('X-Drupal-Cache', 'MISS');
+ $this->assertFeed();
// Repeat for the cache.
$this->drupalGet('/render_attached_test/feed_dpa');
$this->assertHeader('X-Drupal-Cache', 'HIT');
+
+ // Test ['#attached']['html_head'].
+ $this->drupalGet('/render_attached_test/head_dpa');
+ $this->assertHeader('X-Drupal-Cache', 'MISS');
+ $this->assertHead();
+ // Repeat for the cache.
+ $this->drupalGet('/render_attached_test/head_dpa');
+ $this->assertHeader('X-Drupal-Cache', 'HIT');
}
/**
@@ -112,15 +106,60 @@ public function testSmartCache() {
$this->drupalGet('');
// Make sure our block is visible.
$this->assertText('Headers handled by drupal_process_attached().');
- // The header added in the block build should be present.
- $this->assertHeader('X-DPA-Cache-Test', 'HIT');
+ // Test that all our attached items are present.
+ $this->assertFeed();
+ $this->assertHead();
+ $this->assertResponse(418);
+ $this->assertTeapotHeaders();
// Reload the page, to test caching.
$this->drupalGet('');
// Make sure our block is visible.
$this->assertText('Headers handled by drupal_process_attached().');
// The header should be present again.
- $this->assertHeader('X-DPA-Cache-Test', 'HIT');
+ $this->assertHeader('X-Test-Teapot', 'Teapot Mode Active');
+ }
+
+ /**
+ * Helper function to make assertions about added HTTP headers.
+ */
+ protected function assertTeapotHeaders() {
+ $this->assertHeader('X-Test-Teapot', 'Teapot Mode Active');
+ $this->assertHeader('X-Test-Teapot-Replace', 'Teapot replaced');
+ $this->assertHeader('X-Test-Teapot-No-Replace', 'This value is not replaced,This one is added');
+ }
+
+ /**
+ * Helper function to make assertions about the presence of an RSS feed.
+ */
+ protected function assertFeed() {
+ // Discover the DOM element for the feed link.
+ $test_meta = $this->xpath('//head/link[@href="test://url"]');
+ $this->assertEqual(1, count($test_meta), 'Link has URL.');
+ // Reconcile the other attributes.
+ $test_meta_attributes = [
+ 'href' => 'test://url',
+ 'rel' => 'alternate',
+ 'type' => 'application/rss+xml',
+ 'title' => 'Your RSS feed.',
+ ];
+ $test_meta = reset($test_meta);
+ foreach ($test_meta->attributes() as $attribute => $value) {
+ $this->assertEqual($value, $test_meta_attributes[$attribute]);
+ }
+ }
+
+ /**
+ * Helper function to make assertions about HTML head elements.
+ */
+ protected function assertHead() {
+ // Discover the DOM element for the meta link.
+ $test_meta = $this->xpath('//head/meta[@test-attribute="testy"]');
+ $this->assertEqual(1, count($test_meta), 'There\'s only one test attribute.');
+ // Grab the only DOM element.
+ $test_meta = reset($test_meta);
+ $test_meta_attributes = $test_meta->attributes();
+ $this->assertEqual($test_meta_attributes['test-attribute'], 'testy');
}
}
diff --git a/core/modules/system/tests/modules/render_attached_test/render_attached_test.routing.yml b/core/modules/system/tests/modules/render_attached_test/render_attached_test.routing.yml
index 40f8229..d3d578b 100644
--- a/core/modules/system/tests/modules/render_attached_test/render_attached_test.routing.yml
+++ b/core/modules/system/tests/modules/render_attached_test/render_attached_test.routing.yml
@@ -5,6 +5,13 @@ render_attached.teapot:
requirements:
_access: 'TRUE'
+render_attached.header:
+ path: '/render_attached_test/header'
+ defaults:
+ _controller: '\Drupal\render_attached_test\Controller\TestController::header'
+ requirements:
+ _access: 'TRUE'
+
render_attached.head:
path: '/render_attached_test/head'
defaults:
@@ -26,6 +33,13 @@ render_attached.teapot_dpa:
requirements:
_access: 'TRUE'
+render_attached.header_dpa:
+ path: '/render_attached_test/header_dpa'
+ defaults:
+ _controller: '\Drupal\render_attached_test\Controller\TestController::headerDpa'
+ requirements:
+ _access: 'TRUE'
+
render_attached.head_dpa:
path: '/render_attached_test/head_dpa'
defaults:
diff --git a/core/modules/system/tests/modules/render_attached_test/src/Controller/TestController.php b/core/modules/system/tests/modules/render_attached_test/src/Controller/TestController.php
index 01898ef..3d158b3 100644
--- a/core/modules/system/tests/modules/render_attached_test/src/Controller/TestController.php
+++ b/core/modules/system/tests/modules/render_attached_test/src/Controller/TestController.php
@@ -28,9 +28,9 @@ public function teapotHeaderStatus() {
* Test attached HTML head rendering.
*
* @return array
- * A render array using the 'html_head' directive.
+ * A render array using the 'http_head' directive.
*/
- public function head() {
+ public function header() {
$render = [];
$render['#attached']['http_header'][] = ['X-Test-Teapot-Replace', 'This value gets replaced'];
$render['#attached']['http_header'][] = ['X-Test-Teapot-Replace', 'Teapot replaced', TRUE];
@@ -41,6 +41,28 @@ public function head() {
}
/**
+ * Test attached HTML head rendering.
+ *
+ * @return array
+ * A render array using the 'html_head' directive.
+ */
+ public function head() {
+ $head = [
+ [
+ '#tag' => 'meta',
+ '#attributes' => [
+ 'test-attribute' => 'testy',
+ ],
+ ],
+ 'test_head_attribute',
+ ];
+
+ $render = [];
+ $render['#attached']['html_head'][] = $head;
+ return $render;
+ }
+
+ /**
* Test attached feed rendering.
*
* @return array
@@ -59,9 +81,18 @@ public function feed() {
* A generic render array.
*/
public function teapotHeaderStatusDpa() {
- $render = [];
- $render['#attached']['http_header'][] = ['Status', "418 I'm a teapot."];
- \drupal_process_attached($render);
+ \drupal_process_attached($this->teapotHeaderStatus());
+ return ['#markup' => "I'm some markup here to fool the kernel into rendering this page."];
+ }
+
+ /**
+ * Test attached HTML head rendering as a side-effect.
+ *
+ * @return array
+ * A render array using the 'http_header' directive.
+ */
+ public function headerDpa() {
+ \drupal_process_attached($this->header());
return ['#markup' => "I'm some markup here to fool the kernel into rendering this page."];
}
@@ -72,13 +103,7 @@ public function teapotHeaderStatusDpa() {
* A render array using the 'html_head' directive.
*/
public function headDpa() {
- $render = [];
- $render['#attached']['http_header'][] = ['X-Test-Teapot-Replace', 'This value gets replaced'];
- $render['#attached']['http_header'][] = ['X-Test-Teapot-Replace', 'Teapot replaced', TRUE];
- $render['#attached']['http_header'][] = ['X-Test-Teapot-No-Replace', 'This value is not replaced'];
- $render['#attached']['http_header'][] = ['X-Test-Teapot-No-Replace', 'This one is added', FALSE];
- $render['#attached']['http_header'][] = ['X-Test-Teapot', 'Teapot Mode Active'];
- \drupal_process_attached($render);
+ \drupal_process_attached($this->head());
return ['#markup' => "I'm some markup here to fool the kernel into rendering this page."];
}
@@ -89,9 +114,7 @@ public function headDpa() {
* A render array using the 'feed' directive.
*/
public function feedDpa() {
- $render = [];
- $render['#attached']['feed'][] = ['test://url', 'Your RSS feed.'];
- \drupal_process_attached($render);
+ \drupal_process_attached($this->feed());
return ['#markup' => "I'm some markup here to fool the kernel into rendering this page."];
}
diff --git a/core/modules/system/tests/modules/render_attached_test/src/Plugin/Block/DrupalProcessAttachedBlock.php b/core/modules/system/tests/modules/render_attached_test/src/Plugin/Block/DrupalProcessAttachedBlock.php
index 9ab5400..3e7b342 100644
--- a/core/modules/system/tests/modules/render_attached_test/src/Plugin/Block/DrupalProcessAttachedBlock.php
+++ b/core/modules/system/tests/modules/render_attached_test/src/Plugin/Block/DrupalProcessAttachedBlock.php
@@ -7,8 +7,10 @@
namespace Drupal\render_attached_test\Plugin\Block;
+use Drupal\render_attached_test\Controller\TestController;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
+use Drupal\Core\Render\BubbleableMetadata;
/**
* A block we can use to test caching of #attached headers.
@@ -26,8 +28,16 @@ class DrupalProcessAttachedBlock extends BlockBase {
* {@inheritdoc}
*/
public function build() {
- // Use drupal_process_attached() to attach an HTTP header.
- \drupal_process_attached(['#attached' => ['http_header' => [['X-DPA-Cache-Test', 'HIT']]]]);
+ // Grab test attachment fixtures from
+ // Drupal\render_attached_test\Controller\TestController.
+ $controller = new TestController();
+ $attached = BubbleableMetadata::mergeAttachments($controller->feed(), $controller->head());
+ $attached = BubbleableMetadata::mergeAttachments($attached, $controller->header());
+ $attached = BubbleableMetadata::mergeAttachments($attached, $controller->teapotHeaderStatus());
+
+ // Use drupal_process_attached() to attach all the #attached stuff.
+ drupal_process_attached($attached);
+
// Return some arbitrary markup so the block doesn't disappear.
return ['#markup' => 'Headers handled by drupal_process_attached().'];
}