Commit 7a8d0cd2 authored by catch's avatar catch

Issue #2218117 by dawehner, EclipseGc: Bring back metatag support for the HtmlPage object.

parent 445abea9
......@@ -472,7 +472,7 @@ services:
- { name: event_subscriber }
controller.page:
class: Drupal\Core\Controller\HtmlPageController
arguments: ['@controller_resolver', '@title_resolver']
arguments: ['@controller_resolver', '@title_resolver', '@url_generator']
controller.ajax:
class: Drupal\Core\Controller\AjaxController
arguments: ['@controller_resolver', '@ajax_response_renderer']
......@@ -598,7 +598,7 @@ services:
arguments: ['@config.manager', '@config.storage', '@config.storage.snapshot']
exception_controller:
class: Drupal\Core\Controller\ExceptionController
arguments: ['@content_negotiation', '@title_resolver', '@html_page_renderer', '@html_fragment_renderer', '@string_translation']
arguments: ['@content_negotiation', '@title_resolver', '@html_page_renderer', '@html_fragment_renderer', '@string_translation', '@url_generator']
calls:
- [setContainer, ['@service_container']]
exception_listener:
......
......@@ -236,12 +236,7 @@ function drupal_get_profile() {
* @see drupal_pre_render_html_tag()
*/
function drupal_add_html_head($data = NULL, $key = NULL) {
$stored_head = &drupal_static(__FUNCTION__);
if (!isset($stored_head)) {
// Make sure the defaults, including Content-Type, come first.
$stored_head = _drupal_default_html_head();
}
$stored_head = &drupal_static(__FUNCTION__, array());
if (isset($data) && isset($key)) {
if (!isset($data['#type'])) {
......@@ -252,45 +247,24 @@ function drupal_add_html_head($data = NULL, $key = NULL) {
return $stored_head;
}
/**
* Returns elements that are always displayed in the HEAD tag of the HTML page.
*/
function _drupal_default_html_head() {
// Add default elements. Make sure the Content-Type comes first because the
// IE browser may be vulnerable to XSS via encoding attacks from any content
// that comes before this META tag, such as a TITLE tag.
$elements['system_meta_content_type'] = array(
'#type' => 'html_tag',
'#tag' => 'meta',
'#attributes' => array(
'charset' => 'utf-8',
),
// Security: This always has to be output first.
'#weight' => -1000,
);
// Show Drupal and the major version number in the META GENERATOR tag.
// Get the major version.
list($version, ) = explode('.', \Drupal::VERSION);
$elements['system_meta_generator'] = array(
'#type' => 'html_tag',
'#tag' => 'meta',
'#attributes' => array(
'name' => 'Generator',
'content' => 'Drupal ' . $version . ' (http://drupal.org)',
),
);
// Also send the generator in the HTTP header.
$elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
return $elements;
}
/**
* Retrieves output to be displayed in the HEAD tag of the HTML page.
*
* @param bool $render
* If TRUE render the HEAD elements, otherwise return just the elements.
*
* @return string|array
* Return the rendered HTML head or the elements itself.
*/
function drupal_get_html_head() {
function drupal_get_html_head($render = TRUE) {
$elements = drupal_add_html_head();
\Drupal::moduleHandler()->alter('html_head', $elements);
return drupal_render($elements);
if ($render) {
return drupal_render($elements);
}
else {
return $elements;
}
}
/**
......@@ -307,35 +281,17 @@ function drupal_add_feed($url = NULL, $title = '') {
$stored_feed_links = &drupal_static(__FUNCTION__, array());
if (isset($url)) {
$feed_icon = array(
'#theme' => 'feed_icon',
'#url' => $url,
'#title' => $title,
);
$feed_icon['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'alternate',
'type' => 'application/rss+xml',
'title' => $title,
// Force the URL to be absolute, for consistency with other <link> tags
// output by Drupal.
'href' => url($url, array('absolute' => TRUE)),
);
$stored_feed_links[$url] = drupal_render($feed_icon);
$stored_feed_links[$url] = array('url' => $url, 'title' => $title);
}
return $stored_feed_links;
}
/**
* Gets the feed URLs for the current page.
*
* @param $delimiter
* A delimiter to split feeds by.
*/
function drupal_get_feeds($delimiter = "\n") {
function drupal_get_feeds() {
$feeds = drupal_add_feed();
return implode($feeds, $delimiter);
return $feeds;
}
/**
......
......@@ -16,6 +16,9 @@
use Drupal\Core\Language\Language;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ExtensionNameLengthException;
use Drupal\Core\Page\FeedLinkElement;
use Drupal\Core\Page\LinkElement;
use Drupal\Core\Page\MetaElement;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Template\RenderWrapper;
use Drupal\Core\Theme\ThemeSettings;
......@@ -2019,32 +2022,24 @@ function template_preprocess_html(&$variables) {
$variables['head_title_array'] = $head_title;
$variables['head_title'] = implode(' | ', $head_title);
// Display the html.html.twig's default mobile metatags for responsive design.
$elements = array(
'MobileOptimized' => array(
'#tag' => 'meta',
'#attributes' => array(
'name' => 'MobileOptimized',
'content' => 'width',
),
),
'HandheldFriendly' => array(
'#tag' => 'meta',
'#attributes' => array(
'name' => 'HandheldFriendly',
'content' => 'true',
),
),
'viewport' => array(
'#tag' => 'meta',
'#attributes' => array(
'name' => 'viewport',
'content' => 'width=device-width',
),
),
);
foreach ($elements as $name => $element) {
drupal_add_html_head($element, $name);
// @todo Remove drupal_*_html_head() and refactor accordingly.
$html_heads = drupal_get_html_head(FALSE);
uasort($html_heads, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
foreach ($html_heads as $name => $tag) {
if ($tag['#tag'] == 'link') {
$link = new LinkElement($name, isset($tag['#attributes']['content']) ? $tag['#attributes']['content'] : NULL, $tag['#attributes']);
if (!empty($tag['#noscript'])) {
$link->setNoScript();
}
$page->addLinkElement($link);
}
elseif ($tag['#tag'] == 'meta') {
$metatag = new MetaElement(NULL, $tag['#attributes']);
if (!empty($tag['#noscript'])) {
$metatag->setNoScript();
}
$page->addMetaElement($metatag);
}
}
$variables['page_top'][] = array('#markup' => $page->getBodyTop());
......@@ -2056,7 +2051,9 @@ function template_preprocess_html(&$variables) {
$variables['page_bottom'][] = array('#markup' => $footer_scripts);
// Wrap function calls in an object so they can be called when printed.
$variables['head'] = new RenderWrapper('drupal_get_html_head');
$variables['head'] = new RenderWrapper(function() use ($page) {
return implode("\n", $page->getMetaElements()) . implode("\n", $page->getLinkElements());
});
$variables['styles'] = new RenderWrapper('drupal_get_css');
$variables['scripts'] = new RenderWrapper('drupal_get_js');
}
......@@ -2111,7 +2108,19 @@ function template_preprocess_page(&$variables) {
$variables['secondary_menu'] = theme_get_setting('features.secondary_menu') ? menu_secondary_menu() : array();
$variables['action_links'] = menu_get_local_actions();
$variables['tabs'] = menu_local_tabs();
$variables['feed_icons'] = drupal_get_feeds();
// Convert drupal_get_feeds to feed links on the page object.
/** @var \Drupal\Core\Page\HtmlPage $page */
$page = $variables['page']['#page'];
// Render the feed icons.
$variables['feed_icons'] = array();
foreach ($page->getFeedLinkElements() as $link) {
$variables['feed_icons'][] = array(
'#theme' => 'feed_icon',
'#url' => $link->getAttributes()['href'],
'#title' => $link->getAttributes()['title'],
);
}
}
else {
$variables['main_menu'] = array();
......
......@@ -8,7 +8,9 @@
namespace Drupal\Core\Controller;
use Drupal\Core\Page\DefaultHtmlPageRenderer;
use Drupal\Core\Page\HtmlFragmentRendererInterface;
use Drupal\Core\Page\HtmlPageRendererInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
......@@ -68,9 +70,12 @@ class ExceptionController extends HtmlControllerBase implements ContainerAwareIn
* The page renderer.
* @param \Drupal\Core\Page\HtmlFragmentRendererInterface $fragment_renderer
* The fragment rendering service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The url generator.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
*/
public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver, HtmlPageRendererInterface $renderer, $fragment_renderer, TranslationInterface $string_translation) {
parent::__construct($title_resolver);
public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver, HtmlPageRendererInterface $renderer, HtmlFragmentRendererInterface $fragment_renderer, TranslationInterface $string_translation, UrlGeneratorInterface $url_generator) {
parent::__construct($title_resolver, $url_generator);
$this->negotiation = $negotiation;
$this->htmlPageRenderer = $renderer;
$this->fragmentRenderer = $fragment_renderer;
......
......@@ -7,7 +7,9 @@
namespace Drupal\Core\Controller;
use Drupal\Core\Page\FeedLinkElement;
use Drupal\Core\Page\HtmlFragment;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Utility\Title;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
......@@ -25,14 +27,24 @@ class HtmlControllerBase {
*/
protected $titleResolver;
/**
* The url generator.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* Constructs a new HtmlControllerBase object.
*
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The url generator.
*/
public function __construct(TitleResolverInterface $title_resolver) {
public function __construct(TitleResolverInterface $title_resolver, UrlGeneratorInterface $url_generator) {
$this->titleResolver = $title_resolver;
$this->urlGenerator = $url_generator;
}
/**
......@@ -72,6 +84,15 @@ protected function createHtmlFragment($page_content, Request $request) {
$fragment->setTitle($this->titleResolver->getTitle($request, $route), Title::PASS_THROUGH);
}
// Add feed links from the page content.
$attached = drupal_render_collect_attached($page_content, TRUE);
if (!empty($attached['drupal_add_feed'])) {
foreach ($attached['drupal_add_feed'] as $feed) {
$feed_link = new FeedLinkElement($feed[1], $this->urlGenerator->generateFromPath($feed[0]));
$fragment->addLinkElement($feed_link);
}
}
return $fragment;
}
......
......@@ -7,6 +7,7 @@
namespace Drupal\Core\Controller;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\Request;
/**
......@@ -28,9 +29,11 @@ class HtmlPageController extends HtmlControllerBase {
* The controller resolver.
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The url generator.
*/
public function __construct(ControllerResolverInterface $controller_resolver, TitleResolverInterface $title_resolver) {
parent::__construct($title_resolver);
public function __construct(ControllerResolverInterface $controller_resolver, TitleResolverInterface $title_resolver, UrlGeneratorInterface $url_generator) {
parent::__construct($title_resolver, $url_generator);
$this->controllerResolver = $controller_resolver;
}
......
......@@ -88,6 +88,11 @@ public function onHtmlPage(GetResponseForControllerResultEvent $event) {
if ($max_age = $page->getCacheMaxAge()) {
$response->headers->set('cache_max_age', $max_age);
}
// Set the generator in the HTTP header.
list($version) = explode('.', \Drupal::VERSION, 2);
$response->headers->set('X-Generator', 'Drupal ' . $version . ' (http://drupal.org)');
$event->setResponse($response);
}
}
......
......@@ -85,7 +85,45 @@ public function preparePage(HtmlPage $page, &$page_array) {
$html_attributes['lang'] = $language_interface->id;
$html_attributes['dir'] = $language_interface->direction ? 'rtl' : 'ltr';
$this->setDefaultMetaTags($page);
// @todo: collect feed links from #attached rather than a static once
// http://drupal.org/node/2256365 is completed.
foreach (drupal_get_feeds() as $feed) {
// Force the URL to be absolute, for consistency with other <link> tags
// output by Drupal.
$link = new FeedLinkElement($feed['title'], url($feed['url'], array('absolute' => TRUE)));
$page->addLinkElement($link);
}
return $page;
}
/**
* Apply the default meta tags to the page object.
*
* @param \Drupal\Core\Page\HtmlPage $page
* The html page.
*/
protected function setDefaultMetaTags(HtmlPage $page) {
// Add default elements. Make sure the Content-Type comes first because the
// IE browser may be vulnerable to XSS via encoding attacks from any content
// that comes before this META tag, such as a TITLE tag.
$page->addMetaElement(new MetaElement(NULL, array(
'name' => 'charset',
'charset' => 'utf-8',
)));
// Show Drupal and the major version number in the META GENERATOR tag.
// Get the major version.
list($version) = explode('.', \Drupal::VERSION, 2);
$page->addMetaElement(new MetaElement('Drupal ' . $version . ' (http://drupal.org)', array(
'name' => 'Generator',
)));
// Display the html.html.twig's default mobile metatags for responsive design.
$page->addMetaElement(new MetaElement(NULL, array('name' => 'MobileOptimized', 'content' => 'width')));
$page->addMetaElement(new MetaElement(NULL, array('name' => 'HandheldFriendly', 'content' => 'true')));
$page->addMetaElement(new MetaElement(NULL, array('name' => 'viewport', 'content' => 'width=device-width')));
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Page\FeedLinkElement.
*/
namespace Drupal\Core\Page;
/**
* Defines a link to a feed.
*/
class FeedLinkElement extends LinkElement {
/**
* Creates a FeedLink instance.
*
* @param string $title
* The title of the feed.
* @param string $href
* The absolute URL to the feed.
*/
public function __construct($title, $href) {
$rel = 'alternate';
$attributes['title'] = $title;
$attributes['type'] = 'application/rss+xml';
parent::__construct($href, $rel, $attributes);
}
}
......@@ -71,6 +71,16 @@ public function setAttribute($key, $value) {
return $this;
}
/**
* Gets all the attributes.
*
* @return array
* An array of all the attributes keyed by name of attribute.
*/
public function &getAttributes() {
return $this->attributes;
}
/**
* Sets if this element should be wrapped in <noscript>.
*
......
......@@ -21,6 +21,20 @@
*/
class HtmlFragment implements CacheableInterface {
/**
* An array of Link elements.
*
* @var array
*/
protected $links = array();
/**
* An array of Meta elements.
*
* @var array
*/
protected $metatags = array();
/**
* HTML content string.
*
......@@ -61,6 +75,65 @@ public function __construct($content = '', array $cache_info = array()) {
);
}
/**
* Adds a link element to the page.
*
* @param \Drupal\Core\Page\LinkElement $link
* A link element to enqueue.
*
* @return $this
*/
public function addLinkElement(LinkElement $link) {
$this->links[] = $link;
return $this;
}
/**
* Returns an array of all enqueued links.
*
* @return \Drupal\Core\Page\LinkElement[]
*/
public function &getLinkElements() {
return $this->links;
}
/**
* Returns all feed link elements.
*
* @return \Drupal\Core\Page\FeedLinkElement[]
*/
public function getFeedLinkElements() {
$feed_links = array();
foreach ($this->links as $link) {
if ($link instanceof FeedLinkElement) {
$feed_links[] = $link;
}
}
return $feed_links;
}
/**
* Adds a meta element to the page.
*
* @param \Drupal\Core\Page\MetaElement $meta
* A meta element to add.
*
* @return $this
*/
public function addMetaElement(MetaElement $meta) {
$this->metatags[] = $meta;
return $this;
}
/**
* Returns an array of all enqueued meta elements.
*
* @return \Drupal\Core\Page\MetaElement[]
*/
public function &getMetaElements() {
return $this->metatags;
}
/**
* Sets the response content.
*
......@@ -73,8 +146,7 @@ public function __construct($content = '', array $cache_info = array()) {
* @param mixed $content
* The content for this fragment.
*
* @return self
* The fragment.
* @return $this
*/
public function setContent($content) {
$this->content = $content;
......@@ -108,6 +180,8 @@ public function getContent() {
* \Drupal\Component\Utility\String::checkPlain() or
* \Drupal\Component\Utility\Xss::filterAdmin(). With this flag the string
* will be passed through unchanged.
*
* @return $this
*/
public function setTitle($title, $output = Title::CHECK_PLAIN) {
if ($output == Title::CHECK_PLAIN) {
......@@ -119,6 +193,7 @@ public function setTitle($title, $output = Title::CHECK_PLAIN) {
else {
$this->title = $title;
}
return $this;
}
/**
......
......@@ -92,7 +92,7 @@ public function getBodyAttributes() {
* @param string $content
* The top-content to set.
*
* @return self
* @return $this
* The called object.
*/
public function setBodyTop($content) {
......@@ -116,7 +116,7 @@ public function getBodyTop() {
* @param string $content
* The bottom-content to set.
*
* @return self
* @return $this
* The called object.
*/
public function setBodyBottom($content) {
......@@ -140,7 +140,7 @@ public function getBodyBottom() {
* @param int $status
* The status code to set.
*
* @return self
* @return $this
* The called object.
*/
public function setStatusCode($status) {
......@@ -163,9 +163,29 @@ public function getStatusCode() {
*
* @param array $cache_tags
* The cache tags associated with this HTML page.
*
* @return $this
* The called object.
*/
public function setCacheTags(array $cache_tags) {
$this->cache['tags'] = $cache_tags;
return $this;
}
/**
* Gets all feed links.
*
* @return \Drupal\Core\Page\FeedLinkElement[]
* A list of feed links attached to the page.
*/
public function getFeedLinkElements() {
$feed_links = array();
foreach ($this->getLinkElements() as $link) {
if ($link instanceof FeedLinkElement) {
$feed_links[] = $link;
}
}
return $feed_links;
}
}
......
<?php
/**
* @file
* Contains \Drupal\Core\Page\LinkElement.
*/
namespace Drupal\Core\Page;
/**
* Defines a link html HEAD element, which is defined by the href of the link.
*/
class LinkElement extends HeadElement {
/**
* {@inheritdoc}
*/
protected $element = 'link';
/**
* Constructs a new Link object.
*
* @param string $href
* The Link URI. The URI should already be processed to be a fully qualified
* absolute link if necessary.
* @param string $rel
* (optional) The link relationship. This is usually an IANA or
* Microformat-defined string. Defaults to an empty string
* @param array $attributes
* (optional) Additional attributes for this element. Defaults to an empty
* array.
*/
public function __construct($href, $rel = '', array $attributes = array()) {
$this->attributes = $attributes + array(
'href' => $href,
'rel' => $rel,
);
}