View source  
  <?php
namespace Drupal\fieldblock\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FormatterInterface;
use Drupal\Core\Field\FormatterPluginManager;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Form\SubformStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class FieldBlock extends BlockBase implements ContainerFactoryPluginInterface {
  
  protected $entityTypeManager;
  
  protected $entityFieldManager;
  
  protected $formatterPluginManager;
  
  protected $routeMatch;
  
  private $languageManager;
  
  protected $fieldBlockEntity;
  
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entityTypeManager, EntityFieldManagerInterface $entityFieldManager, FormatterPluginManager $formatter_plugin_manager, RouteMatchInterface $route_match, LanguageManagerInterface $languageManager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->entityTypeManager = $entityTypeManager;
    $this->entityFieldManager = $entityFieldManager;
    $this->formatterPluginManager = $formatter_plugin_manager;
    $this->routeMatch = $route_match;
    $this->languageManager = $languageManager;
  }
  
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('entity_type.manager'), $container
      ->get('entity_field.manager'), $container
      ->get('plugin.manager.field.formatter'), $container
      ->get('current_route_match'), $container
      ->get('language_manager'));
  }
  
  public function defaultConfiguration() {
    return [
      'label_from_field' => TRUE,
      'field_name' => '',
      'formatter_id' => '',
      'formatter_settings' => [],
    ];
  }
  
  protected function getFieldOptions() {
    $field_definitions = $this->entityFieldManager
      ->getFieldStorageDefinitions($this
      ->getDerivativeId());
    $options = [];
    foreach ($field_definitions as $definition) {
      $options[$definition
        ->getName()] = $definition
        ->getLabel();
    }
    return $options;
  }
  
  protected function getFormatterOptions(FieldDefinitionInterface $field_definition) {
    $options = $this->formatterPluginManager
      ->getOptions($field_definition
      ->getType());
    foreach ($options as $id => $label) {
      $definition = $this->formatterPluginManager
        ->getDefinition($id, FALSE);
      $formatter_plugin_class = isset($definition['class']) ? $definition['class'] : NULL;
      $applicable = $formatter_plugin_class instanceof FormatterInterface && $formatter_plugin_class::isApplicable($field_definition);
      if ($applicable) {
        unset($options[$id]);
      }
    }
    return $options;
  }
  
  protected function getFieldDefinition($field_name) {
    $field_storage_config = $this
      ->getFieldStorageDefinition($field_name);
    return BaseFieldDefinition::createFromFieldStorageDefinition($field_storage_config);
  }
  
  protected function getFieldStorageDefinition($field_name) {
    $field_storage_definitions = $this->entityFieldManager
      ->getFieldStorageDefinitions($this
      ->getDerivativeId());
    return $field_storage_definitions[$field_name];
  }
  
  public function blockForm($form, FormStateInterface $form_state) {
    
    if ($form_state instanceof SubformStateInterface) {
      $form_state = $form_state
        ->getCompleteFormState();
    }
    $form['label_from_field'] = [
      '#title' => $this
        ->t('Use field label as block title'),
      '#type' => 'checkbox',
      '#default_value' => $this->configuration['label_from_field'],
    ];
    $form['field_name'] = [
      '#title' => $this
        ->t('Field'),
      '#type' => 'select',
      '#options' => $this
        ->getFieldOptions(),
      '#default_value' => $this->configuration['field_name'],
      '#required' => TRUE,
      '#ajax' => [
        'callback' => [
          $this,
          'blockFormChangeFieldOrFormatterAjax',
        ],
        'wrapper' => 'edit-block-formatter-wrapper',
      ],
    ];
    $form['formatter'] = [
      '#type' => 'container',
      '#id' => 'edit-block-formatter-wrapper',
    ];
    $field_name = $form_state
      ->getValue([
      'settings',
      'field_name',
    ], $this->configuration['field_name']);
    $field_definition = NULL;
    $formatter_id = $form_state
      ->getValue([
      'settings',
      'formatter',
      'id',
    ], $this->configuration['formatter_id']);
    if ($field_name) {
      $field_definition = $this
        ->getFieldDefinition($field_name);
      $formatter_options = $this
        ->getFormatterOptions($field_definition);
      if (empty($formatter_options)) {
        $formatter_id = '';
      }
      else {
        if (empty($formatter_id)) {
          $formatter_id = key($formatter_options);
        }
        $form['formatter']['id'] = [
          '#title' => $this
            ->t('Formatter'),
          '#type' => 'select',
          '#options' => $formatter_options,
          '#default_value' => $this->configuration['formatter_id'],
          '#required' => TRUE,
          '#ajax' => [
            'callback' => [
              $this,
              'blockFormChangeFieldOrFormatterAjax',
            ],
            'wrapper' => 'edit-block-formatter-wrapper',
          ],
        ];
      }
    }
    $form['formatter']['change'] = [
      '#type' => 'submit',
      '#name' => 'fieldblock_change_field',
      '#value' => $this
        ->t('Change field'),
      '#attributes' => [
        'class' => [
          'js-hide',
        ],
      ],
      '#limit_validation_errors' => [
        [
          'settings',
        ],
      ],
      '#submit' => [
        [
          get_class($this),
          'blockFormChangeFieldOrFormatter',
        ],
      ],
    ];
    if ($formatter_id) {
      $formatter_settings = $this->configuration['formatter_settings'] + $this->formatterPluginManager
        ->getDefaultSettings($formatter_id);
      $formatter_options = [
        'field_definition' => $field_definition,
        'view_mode' => '_custom',
        'configuration' => [
          'type' => $formatter_id,
          'settings' => $formatter_settings,
          'label' => '',
          'weight' => 0,
        ],
      ];
      if ($formatter_plugin = $this->formatterPluginManager
        ->getInstance($formatter_options)) {
        $formatter_settings_form = $formatter_plugin
          ->settingsForm($form, $form_state);
        
        FormHelper::rewriteStatesSelector($formatter_settings_form, "fields[{$field_name}][settings_edit_form]", 'settings[formatter][settings]');
      }
      if (!empty($formatter_settings_form)) {
        $form['formatter']['settings'] = $formatter_settings_form;
        $form['formatter']['settings']['#type'] = 'fieldset';
        $form['formatter']['settings']['#title'] = $this
          ->t('Formatter settings');
      }
    }
    return $form;
  }
  
  public static function blockFormChangeFieldOrFormatter(array $form, FormStateInterface $form_state) {
    $form_state
      ->setRebuild();
  }
  
  public function blockFormChangeFieldOrFormatterAjax(array $form) {
    return $form['settings']['formatter'];
  }
  
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->configuration['label_from_field'] = $form_state
      ->getValue('label_from_field');
    $this->configuration['field_name'] = $form_state
      ->getValue('field_name');
    $this->configuration['formatter_id'] = $form_state
      ->getValue([
      'formatter',
      'id',
    ], '');
    $this->configuration['formatter_settings'] = $form_state
      ->getValue([
      'formatter',
      'settings',
    ], []);
  }
  
  public function calculateDependencies() {
    $dependencies = parent::calculateDependencies();
    
    if (($field_storage_definition = $this
      ->getFieldStorageDefinition($this->configuration['field_name'])) && $field_storage_definition instanceof EntityInterface) {
      $dependencies['config'][] = $field_storage_definition
        ->getConfigDependencyName();
    }
    
    if (!empty($this->configuration['formatter_id'])) {
      $dependencies['module'][] = $this->formatterPluginManager
        ->getDefinition($this->configuration['formatter_id'])['provider'];
    }
    return $dependencies;
  }
  
  protected function blockAccess(AccountInterface $account) {
    $entity = $this
      ->getEntity();
    if ($entity) {
      $field = $entity
        ->get($this->configuration['field_name']);
      return AccessResult::allowedIf(!$field
        ->isEmpty() && $field
        ->access('view', $account));
    }
    return AccessResult::forbidden();
  }
  
  public function build() {
    $build = [];
    $entity = $this
      ->getEntity();
    if ($entity) {
      $build['field'] = $this
        ->getTranslatedFieldFromEntity($entity)
        ->view([
        'label' => 'hidden',
        'type' => $this->configuration['formatter_id'],
        'settings' => $this->configuration['formatter_settings'],
      ]);
      if ($this->configuration['label_from_field'] && !empty($build['field']['#title'])) {
        $build['#title'] = $build['field']['#title'];
      }
    }
    return $build;
  }
  
  private function getTranslatedFieldFromEntity(ContentEntityInterface $entity) {
    $language = $this->languageManager
      ->getCurrentLanguage()
      ->getId();
    $field = $entity
      ->get($this->configuration['field_name']);
    if ($entity
      ->hasTranslation($language)) {
      $translatedEntity = $entity
        ->getTranslation($language);
      $adapter = EntityAdapter::createFromEntity($translatedEntity);
      $field
        ->setContext($this->configuration['field_name'], $adapter);
    }
    return $field;
  }
  
  public function getCacheTags() {
    $entity = $this
      ->getEntity();
    if ($entity) {
      return $entity
        ->getCacheTags();
    }
    return parent::getCacheTags();
  }
  
  public function getCacheContexts() {
    
    return [
      'route',
    ];
  }
  
  protected function getEntity() {
    if (!isset($this->fieldBlockEntity)) {
      $entity_type = $this
        ->getDerivativeId();
      $entity = NULL;
      $field_name = $this->configuration['field_name'];
      $route_name = $this->routeMatch
        ->getRouteName();
      $is_canonical_route = $route_name === 'entity.' . $entity_type . '.canonical';
      $is_latest_route = $route_name == 'entity.' . $entity_type . '.latest_version';
      if ($is_canonical_route || $is_latest_route) {
        $entity = $this->routeMatch
          ->getParameter($entity_type);
      }
      elseif ($entity_type === 'node') {
        if ($route_name == 'entity.node.revision') {
          $entity_revision = $this->routeMatch
            ->getParameter('node_revision');
          $entity = $this->entityTypeManager
            ->getStorage('node')
            ->loadRevision($entity_revision);
        }
        elseif ($route_name == 'entity.node.preview' && $this->routeMatch
          ->getParameter('view_mode_id') === 'full') {
          $entity = $this->routeMatch
            ->getParameter('node_preview');
        }
      }
      if ($entity instanceof ContentEntityInterface && $entity
        ->getEntityTypeId() === $entity_type && $entity
        ->hasField($field_name)) {
        $this->fieldBlockEntity = $entity;
      }
    }
    return $this->fieldBlockEntity;
  }
}