6

I'm looking to create a block that renders differently, depending on the content (metadata, really) of the node in which it is embedded.

The “Title” block is a good example of what I'm trying to do, I think. When you put “Page title” in a section (from the Structure menu), it displays the title of the node being displayed.

I'd like to do something similar. The end goal is to read the workflow state data from the node in order to provide large visual clues to readers that the document they are reading is approved/unapproved/et cetera.

There are some other similar ends to which I may put this technique as well, provided I figure it out.

Here's what I have:

/**
 * Provides a 'Workflow Block'.
 *
 * @Block(
 *   id = "workflow_tag_block",
 *   admin_label = @Translation("Workflow Block"),
 * )
 */
class WorkflowTagBlock extends BlockBase implements ContainerFactoryPluginInterface {
        protected $node;

        public function __construct(array $configuration, $plugin_id, $plugin_definition, $node) {
                parent::__construct($configuration, $plugin_id, $plugin_definition);

                $this->node = $node;
        }

        public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
                return new static(
                        $configuration,
                        $plugin_id,
                        $plugin_definition,
                        $container->get /*... something here, I think. I need this to be the node I'm rendering. */
                );
        }

        /**
         * {@inheritdoc}
         */
        public function build() {
                // do things with $this->node;

                return [
                        '#markup' => $this->t('Here Be Dragons! '),
                ];
        }
}

Obviously, there's an important line I don't have complete in there. The most obvious attempt would be: $container->get('request_stack')->getCurrentRequest()->get('node'). But that returns the WorkflowTagBlock object itself, not the primary content node.

I'm aware that multiple nodes might be rendered on the screen at once (for example, in a list), so I can't just try to snag the node id from the uri or anything like that.

This is done in other places, so surely there's a way to do it. How?

4 Answers 4

6

Update your plugin definition to require the node context:

 *   context_definitions = {
 *     "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
 *   }

Then in your build() method, you can access it like this:

$node = $this->getContextValue('node');

If the block is displayed on a node page, then the node will be stored in the $node variable.

When dealing with an existing block, make sure to either replace the block with a new one once the code is updated; or add the following to the settings: of the block.

If not done, your block will return empty due to missing config.

context_mapping:
  node: '@node.node_route_context:node'
1
  • 2
    This definitely works. I just wanted to point out that, as the answer says, if you had the block created before you added the context you must remove the block or add the setting to the block. When you add the block again you should be able to add the node as context. If you don't you will likely get an error like this: Drupal\Component\Plugin\Exception\MissingValueContextException: Required contexts without a value: node in Drupal\Core\Plugin\Context\ContextHandler->applyContextMapping(). Hope this helps!
    – serverjohn
    Commented Sep 2, 2022 at 13:13
5

The Book module implements a block that needs to access to the currently viewed node, and it uses the following code, in BookNavigationBlock::build().

if ($node = $this->requestStack->getCurrentRequest()->get('node')) {
  $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
}

$this->requestStack is initialized from the following code.

  public function __construct(array $configuration, $plugin_id, $plugin_definition, RequestStack $request_stack, BookManagerInterface $book_manager, EntityStorageInterface $node_storage) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->requestStack = $request_stack;
    $this->bookManager = $book_manager;
    $this->nodeStorage = $node_storage;
  }

  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
    $configuration, 
    $plugin_id, 
    $plugin_definition, 
    $container->get('request_stack'), 
    $container->get('book.manager'), 
    $container->get('entity.manager')->getStorage('node')
    );
  }

The code is essentially like the one you are using, but it seems that in your case, a module is messing up with what $container->get('request_stack')->getCurrentRequest()->get('node') is returning.

As workaround, if you find in which cases it happens, you could override BlockBase::blockAccess() to avoid your build() method is called in those cases.

1

You can also try:

$node = \Drupal::routeMatch()->getParameter('node');
2
1

I'm not sure that creating a block directly is the most Drupalistic way to achieve this?

I would think in terms of creating a field or pseudo-field. Once you've done that, there's a number of routes (Views with contextual filter and block display, "Field as block" module, and ctools D8) to displaying the field as a block (if you even need to).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.