Updated: Comment 0

Problem/Motivation

For all kind of elements in the HTML header we want to have a clean API to add them. Some examples are feed links and arbitrary meta tags.
Currently there are APIs like drupal_add_feed and drupal_add_html_head

Proposed resolution

Add new value objects like FeedLinkElement, of MetaElement, which can be added to the introduced page object. This will make it really easy for modules like
metatag to deal with the new page flow in Drupal 8

Remaining tasks

User interface changes

API changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner’s picture

Status: Active » Needs review
FileSize
6.65 KB

This is just the first step.

Status: Needs review » Needs work

The last submitted patch, 1: metatag-2218117-1.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
7.46 KB
827 bytes

Maybe now.

Status: Needs review » Needs work

The last submitted patch, 3: metatag-2218117-3.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
8.01 KB
719 bytes

Okay agreed, let's also render it.

Status: Needs review » Needs work

The last submitted patch, 5: metatag-2218117-5.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
19.27 KB
13.06 KB

We need support to be able to alter metatags properly.

Crell’s picture

+++ b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
@@ -82,7 +82,36 @@ public function preparePage(HtmlPage $page, &$page_array) {
+    // Display the html.html.twig's default mobile metatags for responsive design.

Display the who's what? html.html.twig? What's that?

Otherwise this looks almost exactly like what we pulled out of the HtmlPage issue, so I'm obviously +1 on bringing it back. :-)

Wim Leers’s picture

Title: Bring back metatag support for the htmlpage object. » Bring back metatag support for the HtmlPage object
dawehner’s picture

Display the who's what? html.html.twig? What's that?

There is a template html since Drupal 7 which basically prints out the html of the page without the main body area.Note: This is also just moving the comment from within the preprocess function.

Crell’s picture

Status: Needs review » Reviewed & tested by the community

Ahhh... Without context it looked like a service name. :-)

dawehner’s picture

HAHA you are so far away from theming :)

sun’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/includes/common.inc
    @@ -321,44 +316,23 @@ function drupal_add_html_head($data = NULL, $key = NULL) {
    -      'charset' => 'utf-8',
    ...
    -    // Security: This always has to be output first.
    -    '#weight' => -1000,
    
    +++ b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
    @@ -82,7 +82,36 @@ public function preparePage(HtmlPage $page, &$page_array) {
    +    $page->addMetatag(new Metatag(NULL, array(
    +      'name' => 'system_meta_content_type',
    +      'charset' => 'utf-8',
    +    )));
    

    'name' => 'charset',
    'charset' => 'utf-8',

    The 'name' is not supposed to be output. Did you test this?

    I can't seem to find where the negative weight / high priority is applied? The charset meta tag still needs to come first for security reasons, AFAIK.

  2. +++ b/core/includes/common.inc
    @@ -321,44 +316,23 @@ function drupal_add_html_head($data = NULL, $key = NULL) {
    + *   If TRUE render the HEAD elements, otherwise eturn just the elements.
    

    Typo in "eturn"

  3. +++ b/core/lib/Drupal/Core/Page/HeadElement.php
    @@ -72,6 +72,13 @@ public function setAttribute($key, $value) {
    +   * Gets all the attributes.
    +   */
    +  public function &getAttributes() {
    

    What does this @return?

    An array of raw attributes or an array of objects of a certain type?

  4. +++ b/core/lib/Drupal/Core/Page/HtmlPage.php
    @@ -69,6 +83,52 @@ public function __construct($content = '', array $cache_info = array(), $title =
    +   * @return self
    ...
    +   * @return self
    
    +++ b/core/lib/Drupal/Core/Page/Metatag.php
    @@ -0,0 +1,83 @@
    +   * @return self
    ...
    +   * @return self
    

    $this

  5. +++ b/core/lib/Drupal/Core/Page/HtmlPage.php
    @@ -69,6 +83,52 @@ public function __construct($content = '', array $cache_info = array(), $title =
    +  public function &links() {
    ...
    +  public function &metatags() {
    

    Can we rename these to have a 'get' method prefix like the other methods?

  6. +++ b/core/lib/Drupal/Core/Page/Link.php
    @@ -0,0 +1,40 @@
    +/**
    +* @file
    +* Contains \Drupal\Core\Page\Link.
    +*/
    ...
    +/**
    +* Defines a link html HEAD element, which is defined by the href of the link.
    +*/
    

    Bogus phpDoc indentation.

  7. +++ b/core/lib/Drupal/Core/Page/Link.php
    @@ -0,0 +1,40 @@
    +  * @param string $rel
    +  *   (optional) The link relationship. This is usually an IANA or
    +  *   Microformat-defined string. Defaults to an empty string
    ...
    + public function __construct($href, $rel = '', array $attributes = array()) {
    

    Why is 'rel' optional?

    According to http://www.w3.org/TR/html5/document-metadata.html#the-link-element both 'href' and 'rel' are required...?

    Shouldn't $rel come first in the signature?

  8. +++ b/core/lib/Drupal/Core/Page/Metatag.php
    @@ -0,0 +1,83 @@
    +class Metatag extends HeadElement {
    

    Why is this class named "MetaTag", while the other class is named "Link" (as opposed to "LinkTag") ?

  9. +++ b/core/lib/Drupal/Core/Page/Metatag.php
    @@ -0,0 +1,83 @@
    +  public function __construct($content = '', array $attributes = array()) {
    +    $this->attributes = $attributes + array(
    +      'content' => $content,
    +    );
    +  }
    

    This signature and processing looks a bit weird to me.

    Why are we enforcing a 'content' attribute onto each meta tag, if we mix it into attributes anyway?

  10. +++ b/core/lib/Drupal/Core/Page/Metatag.php
    @@ -0,0 +1,83 @@
    +  public function setName($name) {
    ...
    +  public function setContent($content) {
    ...
    +  public function getName() {
    +    return $this->attributes['name'];
    +  }
    ...
    +  public function getContent() {
    +    return $this->attributes['content'];
    +  }
    

    These two do not necessarily exist.

    Shouldn't the MetaTag class much rather have generic set($name, $value) and get($name) methods for arbitrary meta element attributes?

  11. +++ b/core/modules/system/tests/modules/system_module_test/lib/Drupal/system_module_test/EventSubscriber/HtmlPageSubscriber.php
    @@ -0,0 +1,45 @@
    +  public static function getSubscribedEvents() {
    +    // Execute between
    +    // \Drupal\Core\EventSubscriber\HtmlViewSubscriber::onHtmlFragment and
    +    // \Drupal\Core\EventSubscriber\HtmlViewSubscriber::onHtmlPage and
    +    $events[KernelEvents::VIEW][] = array('onHtmlPage', 60);
    

    This looks very fragile... what if there are 50 modules that need to add, change, or remove meta tags and links?

Crell’s picture

Re point 1, I don't think meta tags support fully arbitrary attributes. Dave Reid already reviewed the current class in the previous issue and had no further issues beyond what we already took care of. We're aiming for the common case, which is name and content and "sometimes other stuff".

Re point 11, then there's 50 modules that would need to register listeners. Which is no different than today; 50 modules would need to call drupal_add_html_head() from within hook_page_alter() or somewhere. It's no less fragile. And I've never in my life seen a site with 50 modules messing with header tags at once. I don't even think I've seen as many as a half-dozen.

dawehner’s picture

This looks very fragile... what if there are 50 modules that need to add, change, or remove meta tags and links?

Well, note: we always have the full double range available, if really needed.

Crell’s picture

Daniel, are you going to have time to respond to sun's other comments in #13?

dawehner’s picture

Status: Needs work » Needs review
FileSize
21.72 KB
9.76 KB

Thank you for your review!

I can't seem to find where the negative weight / high priority is applied? The charset meta tag still needs to come first for security reasons, AFAIK.

There is no explicit weight in here, but the code flow basically ensures that.

There was indeed no test for that, let's ensure the behavior.

Shouldn't $rel come first in the signature?

I don't really see why. All the w3 documents talk about HREF first. It as a developer would also expect that tbh.

A link element must have a rel attribute.

Great lookup!

Why is this class named "MetaTag", while the other class is named "Link" (as opposed to "LinkTag") ?

I do agree that we should be more consistent. Do you have a preference of MetaTag vs. MetaElement (this is what the standard talks about).

Why are we enforcing a 'content' attribute onto each meta tag, if we mix it into attributes anyway?

Kind of for DX, as you most of the time want to specify a content.

Shouldn't the MetaTag class much rather have generic set($name, $value) and get($name) methods for arbitrary meta element attributes?

I wonder whether there are any usecases for meta/link?

Crell’s picture

Looking at the code again... shouldn't the meta and links methods be on HtmlFragment, not HtmlPage? Sam, Mark, and I have been discussing the next step and it involves an HtmlFragmentInterface that has just the getters, including those for sideband data (meta, links, assets, etc.) We may as well move the logic up there now, since it will be there later.

dawehner’s picture

I do get the usecase for assets but can you please explain why meta links are useful at all on all fragments and not just html page fragments?

Crell’s picture

The NodeController::page() controller method specifies link tags for that page specifically. That gets translated to an HtmlFragment before being passed up.

A block that shows a view may also link to an RSS feed, which then gets added as a link, too.

All fragments need to be able to carry out of band data that will then get dissected and reassembled into a single HtmlPage object, with all of that out of band data together.

dawehner’s picture

Well, the question I do have though at what point do we stop. I for example have a usecase to pass along the http status code, but is that really something which belongs here?

Crell’s picture

For now let's just go with the methods where we had them in the original issue before they were removed. When we introduce the interface we can rejigger exactly what goes where. (There will likely be multiple interfaces, but I want to get this moving forward.)

Incremental progress.

dawehner’s picture

So we don't want to add more methods for now? Then let's get the current patch in.

EclipseGc’s picture

FileSize
21.71 KB

rerolled against head.

Eclipse

EclipseGc’s picture

FileSize
21.92 KB
3.37 KB

Ok, I have moved the methods as Crell asked for in #18. HtmlFragment is a parent class to HtmlPage so the interdiff is ridiculously straight forward.

Eclipse

The last submitted patch, 24: 2218117-24.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 25: 2218117-25.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
21.92 KB

This could be a random fail, let's reupload the patch.

Let's keep the "random" test failure alive for other people.

Crell’s picture

Status: Needs review » Reviewed & tested by the community

Huzzah! Thanks, Kris!

EclipseGc’s picture

I see, testbot just hates me...

Eclipse

catch’s picture

Status: Reviewed & tested by the community » Needs work

drupal_pre_render_html() and other uses are not converted. We should at least open a follow-up?

We have feed icon support in the theme layer that consults drupal_get_feeds(). That's ignored here and it's going to result in different behaviour depending which mechanism is used.

alexpott’s picture

This issue needs a issue summary and a change record.

catch’s picture

-function hook_html_head_alter(&$head_elements) {
-  foreach ($head_elements as $key => $element) {
-    if (isset($element['#attributes']['rel']) && $element['#attributes']['rel'] == 'canonical') {
-      // I want a custom canonical URL.
-      $head_elements[$key]['#attributes']['href'] = mymodule_canonical_url();
-    }
-  }
-}

This hook is removed but HTML HEAD elements can (and still are in core) added via this mechanism. Those elements won't be alterable either via the event or this hook now.

EclipseGc’s picture

Catch & I discussed this in -wscci and came to the conclusion that #33 is actually already taken care of, so that just leaves 31 & 32.

Eclipse

Crell’s picture

We discussed this in IRC between catch, dawehner, EclipseGc, and myself. The plan for dealing with feeds is thus:

1) Add a FeedLink extends LinkTag class.

2) Tweak template_preprocess_html to bridge drupal_add_feed the same way we bridge the other head methods.

3) Add an HtmlPage::getFeeds() method (only on that object, not fragment) that returns just those link objects that instanceof FeedLink. This can then be used by the theme layer for generating the RSS icons at the bottom of the page rather than calling drupal_get_feeds().

4) Add the same bridge code to HtmlControllerBase::createHtmlFragment to let feeds get added with the right object.

5) Commit this thing.

6) Factor the render->HtmlFragment code out of HtmlControllerBase to a service in a follow-up (to avoid creep): #2256365: Factor render->fragment code out to a service

7) Factor the read interface of HtmlFragment out to an interface of name TBD, follow-up: #2256373: Factor HtmlFragment out to an interface

dawehner’s picture

Status: Needs work » Needs review
FileSize
31.25 KB
10.14 KB

We are getting there.

Status: Needs review » Needs work

The last submitted patch, 36: htmlfragment-2218117-36.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
31.67 KB
2.34 KB

meh

tim.plunkett’s picture

Status: Needs review » Needs work

The substantive changes here are good, just some weird artifacts left over. Note this still needs an issue summary.

  1. +++ b/core/core.services.yml
    @@ -602,7 +602,7 @@ services:
    -    arguments: ['@content_negotiation', '@title_resolver', '@html_page_renderer', '@html_fragment_renderer', '@string_translation']
    +    arguments: ['@content_negotiation', '@title_resolver', '@html_page_renderer', '@html_fragment_renderer', '@url_generator']
    

    Is this a mistake after #2257745: Switch t() to $this->t() in ExceptionController?

  2. +++ b/core/lib/Drupal/Core/Controller/ExceptionController.php
    @@ -26,6 +28,8 @@
    +  // @TODO The trait is just wrong for such a basic functionality!
    

    ...

  3. +++ b/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php
    @@ -88,6 +88,11 @@ public function onHtmlPage(GetResponseForControllerResultEvent $event) {
    +      list($version, ) = explode('.', \Drupal::VERSION);
    
    +++ b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
    @@ -85,7 +85,36 @@ public function preparePage(HtmlPage $page, &$page_array) {
    +    list($version, ) = explode('.', \Drupal::VERSION);
    

    Do we want to use the 3rd param for explode, like explode('.', \Drupal::VERSION, 2);? Also, do we even need that comma in list()?

  4. +++ b/core/lib/Drupal/Core/Page/HtmlFragment.php
    @@ -62,6 +76,52 @@ public function __construct($content = '', array $cache_info = array()) {
    +   *   The invoked object.
    ...
    +   *   The invoked object.
    

    We've been leaving these out now

The last submitted patch, 38: metatag-2218117-38.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
32.43 KB
5.2 KB

Thank you for the review tim!

Status: Needs review » Needs work

The last submitted patch, 41: metatag-2218117-41.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
37.42 KB
5.58 KB

Test html output via a regex, sigh/

dawehner’s picture

FileSize
37.46 KB
4.33 KB

Currently working on a change notice.

dawehner’s picture

FileSize
37.43 KB
764 bytes

Bring it up faster next time, paris!

dawehner’s picture

dawehner’s picture

FileSize
37.32 KB
10.92 KB

I am so powerful: just created the metaelement.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Thanks! Would be great to quickly fix the following minor issues prior to commit:

  1. +++ b/core/lib/Drupal/Core/Page/LinkElement.php
    @@ -0,0 +1,40 @@
    +/**
    +* @file
    +* Contains \Drupal\Core\Page\LinkElement.
    +*/
    

    Bogus indentation/formatting of phpDoc comment.

  2. +++ b/core/lib/Drupal/Core/Page/MetaElement.php
    @@ -0,0 +1,83 @@
    +   * @return self
    +   *   The invoked object.
    ...
    +   * @return self
    +   *   The invoked object.
    

    $this, not self. We can skip the description.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Page/HtmlFragment.php
@@ -22,6 +22,20 @@
+  /**
+   * An array of Meta elements.
+   *
+   * @var array
+   */
+  protected $metatags = array();

@@ -62,6 +76,50 @@ public function __construct($content = '', array $cache_info = array()) {
+  /**
+   * Adds a Meta tag to the page.
+   *
+   * @param \Drupal\Core\Page\MetaElement $tag
+   *   A meta element to add.
+   *
+   * @return $this
+   */
+  public function addMetatag(MetaElement $tag) {
+    $this->metatags[] = $tag;
+    return $this;
+  }
+
+  /**
+   * Returns an array of all enqueued meta tags.
+   *
+   * @return \Drupal\Core\Page\MetaElement[]
+   */
+  public function &getMetatags() {
+    return $this->metatags;
+  }

If we're changing to use MetaElement then what should these functions & parameters be called?

alexpott’s picture

And we still need an issue summary

webchick’s picture

Status: Reviewed & tested by the community » Needs work
dawehner’s picture

Issue summary: View changes
dawehner’s picture

Status: Needs work » Needs review
FileSize
37.44 KB
12.89 KB

Here is the recent version of the patch.

Crell’s picture

Status: Needs review » Reviewed & tested by the community

Assuming the bot is OK...

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 53: metaelement-2218117-53.patch, failed testing.

dawehner’s picture

FileSize
37.44 KB

Seems like a random fail.

dawehner’s picture

Status: Needs work » Needs review
Issue tags: -Needs issue summary update, -Needs change record

.

EclipseGc’s picture

Status: Needs review » Reviewed & tested by the community
sun’s picture

+1 Looks great to me now.

catch’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/includes/theme.inc
@@ -2111,7 +2108,27 @@ function template_preprocess_page(&$variables) {
+    foreach ($page->getLinkElements() as $link) {

Minor, but this could be $page->getFeedLinks() no?

I'm not sure about putting the links/feeds onto the page object in preprocess, seems like it'd be better in a step just before preprocess, but not sure what step that should be... - maybe a followup? I realise this is a bc shim but feels quite far off actually removing that yet.

dawehner’s picture

Status: Needs work » Needs review
FileSize
37.82 KB
3.41 KB
  • Renamed the function
  • Moved the drupal_get_feeds() call from the preprocess function to default html fragment renderer
Crell’s picture

Status: Needs review » Reviewed & tested by the community

#61 makes sense.

I don't think the BC shim is better/worse in either place, so, meh.

dawehner’s picture

Btw. this is not BC at the moment, because for example /node relies on it. Just experienced that during development.

catch’s picture

Yes good point, shouldn't call something backwards compatibility if it's the only thing keeping an entire subsystem working.

I'm OK with this now but we need an explicit follow-up/plan to make it actually possible to remove the old deprecated stuff - that's not linked from the summary/related issues at all.

Crell’s picture

I updated the existing follow-up at #2256365: Factor render->fragment code out to a service to clarify that it should be a complete render->fragment converter, including #attached. That should be sufficient for #64.

webchick’s picture

Assigned: Unassigned » catch

This smells catch-ish.

  • Commit 7a8d0cd on 8.x by catch:
    Issue #2218117 by dawehner, EclipseGc: Bring back metatag support for...
catch’s picture

Status: Reviewed & tested by the community » Fixed
+++ b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
@@ -87,6 +87,15 @@ public function preparePage(HtmlPage $page, &$page_array) {
+    // Sadly there are modules which still call drupal_add_feed directly, like

One more thing, this comment isn't describing the problem, the issue is in drupal_render_collect_attached() and that needing to be connected to HtmlFragment, rather than legacy calls to drupal_add_feed(). Views doesn't call drupal_add_feed() any more, it uses #attached['drupal_add_feed'].

I updated the comment on commit since otherwise this patch could end up breaking the record for most times RTBC without actually being RTBC, if it hasn't already.

+    // @todo: collect feed links from #attached rather than a static once
+    // http://drupal.org/node/2256365 is completed.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.