diff --git a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php index f777a53..5d189af 100644 --- a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php @@ -120,70 +120,51 @@ public function processAttachments(AttachmentsInterface $response) { throw new \InvalidArgumentException('\Drupal\Core\Render\HtmlResponse instance expected.'); } - $attached = $response->getAttachments(); + // Since mergeBubbleableMetadata() needs full render arrays, we must set one + // up here. + $attached = []; + $attached['#attached'] = $response->getAttachments(); // Get the placeholders from attached and then remove them. - $placeholders = $attached['html_response_placeholders']; - unset($attached['html_response_placeholders']); + $placeholders = $attached['#attached']['html_response_placeholders']; + unset($attached['#attached']['html_response_placeholders']); - $variables = $this->processAssetLibraries($attached, $placeholders); + $variables = $this->processAssetLibraries($attached['#attached'], $placeholders); // Since we can only replace content in the HTML head section if there's a // placeholder for it, we can safely avoid processing the render array if // it's not present. if (!empty($placeholders['head'])) { // 'feed' is a special case of 'html_head_link'. We create an - // 'html_head_link' for the RSS feed. - if (!empty($attached['feed'])) { - // @todo: Determine if feed is supposed to be a single structure or an - // array of structures, and then refactor this array manipulation into - // a separate method if needed. - $feed = $attached['feed']; - - if (is_array($feed[0])) { - foreach ($feed as $item) { - $feed_link = [ - 'href' => $item[0], - 'rel' => 'alternate', - 'title' => empty($feed[1]) ? '' : $item[1], - 'type' => 'application/rss+xml', - ]; - $attached['html_head_link'][] = [$feed_link]; - } - } - else { - $feed_link = [ - 'href' => $feed[0], - 'rel' => 'alternate', - 'title' => empty($feed[1]) ? '' : $feed[1], - 'type' => 'application/rss+xml', - ]; - $attached['html_head_link'][] = $feed_link; - } + // 'html_head_link' for the RSS feed and merge it. + if (!empty($attached['#attached']['feed'])) { + // Since mergeBubbleableMetadata() requires a complete render array, we + // have to add the '#attached' key. + $attached = $this->renderer->mergeBubbleableMetadata( + $attached, + ['#attached' => $this->processFeed($attached['#attached']['feed'])] + ); } - // 'html_head_link' is a special case of 'html_head' which can be present // as a head element, but also as a Link: HTTP header depending on // settings in the render array. Processing it can add to both the // 'html_head' and 'http_header' keys of '#attached', so we must address - // it first. - if (!empty($attached['html_head_link'])) { + // it before 'html_head'. + if (!empty($attached['#attached']['html_head_link'])) { // Merge the processed 'html_head_link' into $attached so that its // 'html_head' and 'http_header' values are present for further // processing. The merge requires a full render array, so we have to add // '#attached' keys. $attached = $this->renderer->mergeBubbleableMetadata( - ['#attached' => $attached], - ['#attached' => $this->processHtmlHeadLink($attached['html_head_link'])] + $attached, + ['#attached' => $this->processHtmlHeadLink($attached['#attached']['html_head_link'])] ); - // Unwrap the '#attached' key. - $attached = $attached['#attached']; } // Now we can process 'html_head', which contains both 'feed' and // 'html_head_link'. - if (!empty($attached['html_head'])) { - $html_head = $this->processHtmlHead($attached['html_head']); + if (!empty($attached['#attached']['html_head'])) { + $html_head = $this->processHtmlHead($attached['#attached']['html_head']); // Invoke hook_html_head_alter(). $this->moduleHandler->alter('html_head', $html_head); // Store the result in $variables so it can be inserted into the @@ -196,8 +177,8 @@ public function processAttachments(AttachmentsInterface $response) { $this->renderPlaceholders($response, $placeholders, $variables); // Set the HTTP headers on the response if any bubbled. - if (!empty($attached['http_header'])) { - $this->setHeaders($response, $attached['http_header']); + if (!empty($attached['#attached']['http_header'])) { + $this->setHeaders($response, $attached['#attached']['http_header']); } return $response; @@ -289,7 +270,8 @@ protected function setHeaders(HtmlResponse $response, array $headers) { // Drupal treats the HTTP response status code like a header, even though // it really is not. if (strtolower($name) === 'status') { - $response->setStatusCode($value); + list ($code, $message) = explode(' ', $value, 2); + $response->setStatusCode($code, $message); } $response->headers->set($name, $value, $replace); } @@ -325,6 +307,11 @@ 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: + * - 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. * @@ -359,4 +346,30 @@ protected function processHtmlHeadLink(array $html_head_link) { return $attached; } + /** + * + * @param array $attached_feed + * The ['#attached']['feed'] portion of a render array. + * + * @return array + * An ['#attached']['html_head_link'] array, suitable for merging with + * another 'html_head_link' array. + */ + protected function processFeed($attached_feed) { + // @todo: Determine if feed is supposed to be a single structure or an + // array of structures, and then refactor this array manipulation into + // a separate method if needed. + $html_head_link = []; + foreach($attached_feed as $item) { + $feed_link = [ + 'rel' => 'alternate', + 'type' => 'application/rss+xml', + 'href' => $item[0], + 'title' => empty($attached_feed[1]) ? '' : $item[1], + ]; + $html_head_link[] = [$feed_link, FALSE]; + } + return ['html_head_link' => $html_head_link]; + } + } diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php index 42fe60e..373efa2 100644 --- a/core/lib/Drupal/Core/Render/theme.api.php +++ b/core/lib/Drupal/Core/Render/theme.api.php @@ -369,7 +369,7 @@ * @code * $build['#attached']['library'][] = 'core/jquery'; * $build['#attached']['drupalSettings']['foo'] = 'bar'; - * $build['#attached']['feed'][] = ['aggregator/rss', $this->t('Feed title')]; + * $build['#attached']['feed'][] = [$url, $this->t('Feed title')]; * @endcode * * See \Drupal\Core\Render\AttachmentsResponseProcessorInterface for additional diff --git a/core/modules/aggregator/src/Controller/AggregatorController.php b/core/modules/aggregator/src/Controller/AggregatorController.php index 13ff3d9..398da98 100644 --- a/core/modules/aggregator/src/Controller/AggregatorController.php +++ b/core/modules/aggregator/src/Controller/AggregatorController.php @@ -174,6 +174,7 @@ public function pageLast() { $items = $this->entityManager()->getStorage('aggregator_item')->loadAll(20); $build = $this->buildPageList($items); $build['#attached']['feed'][] = array('aggregator/rss', $this->config('system.site')->get('name') . ' ' . $this->t('aggregator')); + error_log(print_r($build, TRUE)); return $build; } diff --git a/core/modules/system/src/Tests/HttpKernel/HeadersResponseCodeRenderTest.php b/core/modules/system/src/Tests/HttpKernel/HeadersResponseCodeRenderTest.php deleted file mode 100644 index 7a81a0d..0000000 --- a/core/modules/system/src/Tests/HttpKernel/HeadersResponseCodeRenderTest.php +++ /dev/null @@ -1,37 +0,0 @@ -drupalGet('/httpkernel-test/teapot'); - $this->assertResponse(418); - $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'); - } - -} diff --git a/core/modules/system/src/Tests/Render/HtmlResponseAttachmentsTest.php b/core/modules/system/src/Tests/Render/HtmlResponseAttachmentsTest.php new file mode 100644 index 0000000..1268ab3 --- /dev/null +++ b/core/modules/system/src/Tests/Render/HtmlResponseAttachmentsTest.php @@ -0,0 +1,46 @@ +drupalGet('/render_attached_test/teapot'); + $this->assertResponse(418); + + $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'); + + $this->drupalGet('/render_attached_test/feed'); + $this->assertEqual(1, count($this->xpath('//head/link[@rel="alternate"]')), 'Link has rel="alternate"'); + $this->assertEqual(1, count($this->xpath('//head/link[@type="application/rss+xml"]')), 'Link has proper content type.'); + $this->assertEqual(1, count($this->xpath('//head/link[@href="test://url"]')), 'Link has URL.'); + $this->assertEqual(1, count($this->xpath('//head/link[@title=""]')), 'Link has title.'); + } + +} diff --git a/core/modules/system/tests/modules/httpkernel_test/src/Controller/TestController.php b/core/modules/system/tests/modules/httpkernel_test/src/Controller/TestController.php index 5730080..1699594 100644 --- a/core/modules/system/tests/modules/httpkernel_test/src/Controller/TestController.php +++ b/core/modules/system/tests/modules/httpkernel_test/src/Controller/TestController.php @@ -21,21 +21,4 @@ public function get() { return new Response(); } - /** - * Test special header and status code rendering. - * - * @return array - * A render array using features of the 'http_header' directive. - */ - public function teapot() { - $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']; - $render['#attached']['http_header'][] = ['Status', "418 I'm a teapot."]; - return $render; - } - } diff --git a/core/modules/system/tests/modules/render_attached_test/render_attached_test.info.yml b/core/modules/system/tests/modules/render_attached_test/render_attached_test.info.yml new file mode 100644 index 0000000..db4ff45 --- /dev/null +++ b/core/modules/system/tests/modules/render_attached_test/render_attached_test.info.yml @@ -0,0 +1,6 @@ +name: 'Rendering #attached test' +type: module +description: 'Support module for HtmlResponseAttachmentsTest.' +package: Testing +version: VERSION +core: 8.x 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 new file mode 100644 index 0000000..e7329ef --- /dev/null +++ b/core/modules/system/tests/modules/render_attached_test/render_attached_test.routing.yml @@ -0,0 +1,20 @@ +render_attached.teapot: + path: '/render_attached_test/teapot' + defaults: + _controller: '\Drupal\render_attached_test\Controller\TestController::teapotHeaderStatus' + requirements: + _access: 'TRUE' + +render_attached.head: + path: '/render_attached_test/head' + defaults: + _controller: '\Drupal\render_attached_test\Controller\TestController::head' + requirements: + _access: 'TRUE' + +render_attached.feed_single: + path: '/render_attached_test/feed' + defaults: + _controller: '\Drupal\render_attached_test\Controller\TestController::feed' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/httpkernel_test/src/Controller/TestController.php b/core/modules/system/tests/modules/render_attached_test/src/Controller/TestController.php similarity index 61% copy from core/modules/system/tests/modules/httpkernel_test/src/Controller/TestController.php copy to core/modules/system/tests/modules/render_attached_test/src/Controller/TestController.php index 5730080..66b1419 100644 --- a/core/modules/system/tests/modules/httpkernel_test/src/Controller/TestController.php +++ b/core/modules/system/tests/modules/render_attached_test/src/Controller/TestController.php @@ -2,39 +2,49 @@ /** * @file - * Contains \Drupal\httpkernel_test\Controller\TestController. + * Contains \Drupal\render_attached_test\Controller\TestController. */ -namespace Drupal\httpkernel_test\Controller; +namespace Drupal\render_attached_test\Controller; use Symfony\Component\HttpFoundation\Response; /** - * A test controller. + * Controller for various permutations of #attached in the render array. */ class TestController { /** - * Return an empty response. + * Test special header and status code rendering. + * + * @return array + * A render array using features of the 'http_header' directive. */ - public function get() { - return new Response(); + public function teapotHeaderStatus() { + $render = []; + $render['#attached']['http_header'][] = ['Status', "418 I'm a teapot."]; + return $render; } /** - * Test special header and status code rendering. + * Test attached HTML head rendering. * * @return array - * A render array using features of the 'http_header' directive. + * A render array using the 'html_head' directive. */ - public function teapot() { + public function head() { $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']; - $render['#attached']['http_header'][] = ['Status', "418 I'm a teapot."]; + return $render; + } + + public function feed() { + $render = []; + $render['#attached']['feed'][] = ['test://url', 'Your RSS feed.']; return $render; }