Problem/Motivation

This is split off from #2352155: Remove HtmlFragment/HtmlPage and is a blocker for that issue.

SystemMainBlock in HEAD calls drupal_set_page_content() to get the main content it should display. This is the case for historical/legacy reasons, but we need to fix it if we want:

Proposed resolution

Display variants are a generic, abstract concept: they're intended to represent different variations of displaying things. For displaying variations of pages, which always have some "main content", it needs to be possible to somehow send that main content to the variant.
In HEAD, that happens through a global static: drupal_set_page_content(). We don't want that to continue (especially because this was nigh impossible to understand), and therefore we want to be able to send the main content to a variant directly. For that purpose, I introduced \Drupal\Core\Display\PageVariantInterface, which subclasses the generic variant interface. Its sole addition: a setMainContent() method.

Now the code invoking a page display variant can send the main content and let the variant deal with it further!

In HEAD, we already have the SystemMainBlock, which is rendering the main content. Until now, that was using drupal_set_page_content(), i.e. that global static again. The Block module's page display variant already receives the main content cleanly (as per the above), so now we need to pass it cleanly to the block(s) rendering the main content as well. For that, a similar interface was introduced: \Drupal\Core\Block\MainContentBlockPluginInterface, a subclass of BlockPluginInterface, with again a sole addition: a setMainContent() method.

So now the code responsible for rendering a page variant (in #2352155: Remove HtmlFragment/HtmlPage, we'll introduce a proper way to select a page variant, but for now, we continue to use HEAD's practice of a hard-coded page variant) can always call setMainContent() on the page display variant (since page display variants must implement PageVariantInterface), which then in turn has the responsibility of rendering the main content, and in the case of blocks, we detect which block renders the main content by checking if it implements MainContentBlockPluginInterface, and if it does, we again call setMainContent(). Et voilà!

Remaining tasks

Review.

User interface changes

None.

API changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Status: Active » Needs review
FileSize
8.11 KB
tim.plunkett’s picture

Issue tags: +Page Manager
  1. +++ b/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php
    @@ -27,12 +25,15 @@
    - * \Drupal\block\Plugin\DisplayVariant\FullPageVariant
    ...
    + * - \Drupal\system\Plugin\DisplayVariant\SimplePageVariant
    ...
    + * - \Drupal\block\Plugin\DisplayVariant\DemoBlockPageVariant
    

    FullPage still exists, Simple and Demo do not.

  2. +++ b/core/lib/Drupal/Core/Display/Annotation/PageDisplayVariant.php
    @@ -0,0 +1,26 @@
    +class PageDisplayVariant extends DisplayVariant {
    

    Not used

dawehner’s picture

This itself just makes sense and is kinda exactly what we planned earlier with MainHtmlFragment.

+++ b/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php
@@ -121,6 +138,9 @@ public function build() {
+          if ($block->getPlugin() instanceof MainContentBlockPluginInterface) {
+            $block->getPlugin()->setMainContent($this->mainContent);

Maybe micro-opt but we could store the result of $block->getPlugin()

+++ b/core/lib/Drupal/Core/Display/PageVariantInterface.php
@@ -0,0 +1,30 @@
+/**
+ * Provides an interface for PageDisplayVariant plugins.
+ *
...
+ */
+interface PageVariantInterface extends VariantInterface {
+

... A little bit more help would be nice!

znerol’s picture

+++ b/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php
@@ -121,6 +138,9 @@ public function build() {
+          if ($block->getPlugin() instanceof MainContentBlockPluginInterface) {

I'm not comfortable with that. What if we pass in the block id of the main block via plugin configuration? Something like this (obviously the config also could live in a yaml).

diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 95e1bd6..2878c49 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -75,7 +75,7 @@ function _block_page_build(&$page) {
     // Create a full page display variant, which will load blocks into their
     // regions.
     $page += \Drupal::service('plugin.manager.display_variant')
-      ->createInstance('full_page')
+      ->createInstance('full_page', ['main_block' => 'system_main_block'])
       ->build();
   }
   else {
Wim Leers’s picture

D'oh; this was extracted 1:1 from the main patch; will fix both those points — thanks!

Status: Needs review » Needs work

The last submitted patch, 1: almost_rm_drupal_set_page_content-2363025-1.patch, failed testing.

catch’s picture

Component: base system » request processing system
Wim Leers’s picture

Status: Needs work » Needs review
FileSize
8.7 KB
2.64 KB

As of the above patch, we always call drupal_set_page_content() when block module is enabled. Even if we don't actually render the main content. This means that drupal_prepare_page() will assume we've rendered the main content, and not do this:

  if (!$main_content_display) {
    $page['content']['system_main'] = drupal_set_page_content();
  }

But, most tests (all of the failing ones) DON'T place SystemMainBlock. Hence the main content doesn't become visible.

So the question is: how do we make sure that FullPageVariant always displays the main content? The answer: we check if we rendered a block that does show the main content, and if we didn't, we show it manually. Much like drupal_prepare_page() does.
The only alternative is to let each of those failing tests place SystemMainBlock… which is a huge amount of work.

This moves the responsibility of rendering the main content from the mess/maze/legacy cruft that is drupal_prepare_page() (which relies on crazy hacks with static variables) into clear logic. Other page variants won't have to perform this mess.

Note: this is in fact exactly what the main patch does; see the same files as the ones in this interdiff in the main patch.

tim.plunkett’s picture

This is the same problem we had in #2295363: Blocks should be added to the page via an HtmlFragmentRenderer, the main content fallback.

Wim Leers’s picture

#2: fixed in #8!

#3:

  • will do in next reroll.
  • will add more help in a next reroll.

#4:

I'm not comfortable with that. What if we pass in the block id of the main block via plugin configuration? Something like this (obviously the config also could live in a yaml).

Why aren't you comfortable with that? What's problematic about it? In theory there could be multiple "main content" blocks. Your proposal requires us 1) to know which is the main block, 2) doesn't allow multiple main blocks, 3) doesn't allow an alternative implementation of the main block.

I don't see any benefit to what you propose, and no downside to what's in the current patch? Can you please explain a bit more what you find so problematic? :)

Status: Needs review » Needs work

The last submitted patch, 8: almost_rm_drupal_set_page_content-2363025-8.patch, failed testing.

znerol’s picture

Re #10

In theory there could be multiple "main content" blocks.

This seems to contradict the concept of "main content" (the main thing).

Your proposal requires us 1) to know which is the main block, 2) doesn't allow multiple main blocks, 3) doesn't allow an alternative implementation of the main block.

It does not have to be hard-coded at all, the configuration could easily live in a yaml.

Can you please explain a bit more what you find so problematic?

This part just looks weird to me. If the FullPageVariant knew which block handles the main content, it simply could forward the setMainContent() call to that block. The $mainContent instance variable could be removed and the loop in build() could be left as is.

The rest of the patch looks fantastic. Also I do not want to hold this back, my concerns are mere based on a gut feeling.

larowlan’s picture

Status: Needs work » Needs review
FileSize
746 bytes
8.9 KB

Fixes fails.
Reviewed this and can't find any issues.

dawehner’s picture

FileSize
1.27 KB
9.12 KB

Is there a reason why we not just simply set the main content of the $page render array? so _block_page_build() can already take it from there? Well, no teal.

Tried to improve some small bits.

Wim Leers’s picture

#13: Thanks! But we should test both with and without SystemMainBlock being placed. The main patch provides test coverage for that, so cherry-picking those hunks out of there into this patch also.

Also addressed #3.1, #3.2


#12: But then how is the system supposed to figure out which block is "the main content block"? You'd end up with something similar to this anyway, to figure that out automatically, unless you want to burden the site builder with that effort: (s)he'd have to indicate which block should contain the main content in the UI, which would be a UX regression and very brittle.
This is probably the first time I'm disagreeing with you, so I definitely want to understand your concern and understand how you'd like to see it work!

Wim Leers’s picture

Is there a reason why we not just simply set the main content of the $page render array? so _block_page_build() can already take it from there? Well, no teal.

We could do that now, I think, but the main patch will remove that: even when block module is disabled, we'll use a page display variant, so that we have a consistent rendering pipeline, that always uses a page display variant. In that patch, that default page display variant (which is used when Block module is not installed) is called SimplePageVariant (name can be changed, of course), and then the page render pipeline looks like this:

      // Select the page display variant to be used to render this main content,
      // default to the built-in "simple page".
      $event = new PageDisplayVariantSelectionEvent('simple_page');
      $this->eventDispatcher->dispatch(SystemEvents::SELECT_PAGE_DISPLAY_VARIANT, $event);

      …

      // Instantiate the page display, and give it the main content.
      $page_display = $this->displayVariantManager->createInstance($variant_id);
      if (!$page_display instanceof PageVariantInterface) {
        throw new \LogicException('Cannot render the main content for this page because the provided display variant does not implement PageVariantInterface.');
      }
      $page_display->setMainContent($main_content);

      // Generate a #type => page render array using the page display variant,
      // the page display will build the content for the various page regions.
      $page = array(
        '#type' => 'page',
      );
      $page += $page_display->build();

When Block module is enabled, it just always overrides it to its own page display variant. If Page Manager or Panels or something else is enabled, then that would be used. When using multiple, it'd be up to the event priority and whatever logic the event subscriber uses to determine which wins.

I hope that made sense.

Wim Leers’s picture

Any other concerns, or is this RTBC? :)

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

Don't see any outstanding objections.

Fabianx’s picture

RTBC + 1 - Great work!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed ffe035e and pushed to 8.0.x. Thanks!

  • alexpott committed ffe035e on 8.0.x
    Issue #2363025 by Wim Leers, dawehner, larowlan: Push usage of...

Status: Fixed » Closed (fixed)

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