You are here

BlockFieldFormatter.php in Block field 8

File

src/Plugin/Field/FieldFormatter/BlockFieldFormatter.php
View source
<?php

namespace Drupal\block_field\Plugin\Field\FieldFormatter;

use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Security\TrustedCallbackInterface;

/**
 * Plugin implementation of the 'block_field' formatter.
 *
 * @FieldFormatter(
 *   id = "block_field",
 *   label = @Translation("Block field"),
 *   field_types = {
 *     "block_field"
 *   }
 * )
 */
class BlockFieldFormatter extends FormatterBase implements TrustedCallbackInterface {

  /**
   * The Drupal context repository.
   *
   * @var \Drupal\context\Entity\ContextRepositoryInterface
   */
  protected $contextRepository;

  /**
   * The plugin context handler.
   *
   * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
   */
  protected $contextHandler;

  /**
   * Drupal\Core\Session\AccountProxy definition.
   *
   * @var \Drupal\Core\Session\AccountProxy
   */
  protected $currentUser;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Constructs a StringFormatter instance.
   *
   * @param string $plugin_id
   *   The plugin_id for the formatter.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The definition of the field to which the formatter is associated.
   * @param array $settings
   *   The formatter settings.
   * @param string $label
   *   The formatter label display setting.
   * @param string $view_mode
   *   The view mode.
   * @param array $third_party_settings
   *   Any third party settings settings.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   */
  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, ContextRepositoryInterface $contextRepository, ContextHandlerInterface $contextHandler, AccountProxyInterface $current_user, ModuleHandlerInterface $module_handler) {
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
    $this->contextRepository = $contextRepository;
    $this->contextHandler = $contextHandler;
    $this->currentUser = $current_user;
    $this->moduleHandler = $module_handler;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['label'], $configuration['view_mode'], $configuration['third_party_settings'], $container
      ->get('context.repository'), $container
      ->get('context.handler'), $container
      ->get('current_user'), $container
      ->get('module_handler'));
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];
    foreach ($items as $delta => $item) {

      /** @var \Drupal\block_field\BlockFieldItemInterface $item */
      $block_instance = $item
        ->getBlock();

      // Inject runtime contexts.
      if ($block_instance instanceof ContextAwarePluginInterface) {
        try {
          $contexts = $this->contextRepository
            ->getRuntimeContexts($block_instance
            ->getContextMapping());
          $this->contextHandler
            ->applyContextMapping($block_instance, $contexts);
        } catch (ContextException $e) {
          continue;
        }
      }

      // Make sure the block exists and is accessible.
      if (!$block_instance) {
        continue;
      }
      $access = $block_instance
        ->access($this->currentUser
        ->getAccount(), TRUE);
      CacheableMetadata::createFromRenderArray($elements)
        ->addCacheableDependency($access)
        ->applyTo($elements);
      if (!$access
        ->isAllowed()) {
        continue;
      }

      // See \Drupal\block\BlockViewBuilder::buildPreRenderableBlock
      // See template_preprocess_block()
      $base_id = $block_instance
        ->getBaseId();
      $elements[$delta] = [
        '#theme' => 'block',
        '#attributes' => [],
        '#configuration' => $block_instance
          ->getConfiguration(),
        '#plugin_id' => $block_instance
          ->getPluginId(),
        '#base_plugin_id' => $base_id,
        '#derivative_plugin_id' => $block_instance
          ->getDerivativeId(),
        '#id' => $block_instance
          ->getMachineNameSuggestion(),
        '#pre_render' => [
          [
            $this,
            'preRender',
          ],
        ],
        '#block' => $block_instance,
      ];
      CacheableMetadata::createFromRenderArray($elements[$delta])
        ->addCacheableDependency($block_instance)
        ->applyTo($elements[$delta]);

      // If an alter hook wants to modify the block contents, it can append
      // another #pre_render hook.
      $this->moduleHandler
        ->alter([
        'block_view',
        "block_view_{$base_id}",
      ], $elements[$delta], $block_instance);

      // Allow altering of cacheability metadata or setting #create_placeholder.
      $this->moduleHandler
        ->alter([
        'block_build',
        "block_build_{$base_id}",
      ], $elements[$delta], $block_instance);
    }
    return $elements;
  }

  /**
   * The #pre_render callback for building a block.
   *
   * Renders the content using the provided block plugin, and then:
   * - if there is no content, aborts rendering, and makes sure the block won't
   *   be rendered.
   * - if there is content, moves the contextual links from the block content to
   *   the block itself.
   *
   * @see \Drupal\block\BlockViewBuilder::preRender
   */
  public function preRender($build) {
    $content = $build['#block']
      ->build();

    // Remove the block entity from the render array, to ensure that blocks
    // can be rendered without the block config entity.
    unset($build['#block']);
    if ($content !== NULL && !Element::isEmpty($content)) {

      // Place the $content returned by the block plugin into a 'content' child
      // element, as a way to allow the plugin to have complete control of its
      // properties and rendering (for instance, its own #theme) without
      // conflicting with the properties used above, or alternate ones used by
      // alternate block rendering approaches in contrib (for instance, Panels).
      // However, the use of a child element is an implementation detail of this
      // particular block rendering approach. Semantically, the content returned
      // by the plugin "is the" block, and in particular, #attributes and
      // #contextual_links is information about the *entire* block. Therefore,
      // we must move these properties from $content and merge them into the
      // top-level element.
      foreach ([
        '#attributes',
        '#contextual_links',
      ] as $property) {
        if (isset($content[$property])) {
          if (empty($build[$property])) {
            $build[$property] = [];
          }
          $build[$property] += $content[$property];
          unset($content[$property]);
        }
      }
      $build['content'] = $content;
    }
    else {

      // Abort rendering: render as the empty string and ensure this block is
      // render cached, so we can avoid the work of having to repeatedly
      // determine whether the block is empty. For instance, modifying or adding
      // entities could cause the block to no longer be empty.
      $build = [
        '#markup' => '',
        '#cache' => $build['#cache'],
      ];
    }

    // If $content is not empty, then it contains cacheability metadata, and
    // we must merge it with the existing cacheability metadata. This allows
    // blocks to be empty, yet still bubble cacheability metadata, to indicate
    // why they are empty.
    if (!empty($content)) {
      CacheableMetadata::createFromRenderArray($build)
        ->merge(CacheableMetadata::createFromRenderArray($content))
        ->applyTo($build);
    }
    return $build;
  }
  public static function trustedCallbacks() {
    return [
      'preRender',
    ];
  }

}

Classes

Namesort descending Description
BlockFieldFormatter Plugin implementation of the 'block_field' formatter.