1<?php 2 3namespace Drupal\layout_builder\Plugin\Block; 4 5use Drupal\Core\Block\BlockBase; 6use Drupal\Core\Cache\CacheableMetadata; 7use Drupal\Core\Entity\EntityFieldManagerInterface; 8use Drupal\Core\Entity\EntityTypeManagerInterface; 9use Drupal\Core\Plugin\ContainerFactoryPluginInterface; 10use Drupal\Core\Plugin\ContextAwarePluginInterface; 11use Drupal\Core\Render\Element; 12use Drupal\Core\Session\AccountInterface; 13use Drupal\Core\StringTranslation\TranslatableMarkup; 14use Symfony\Component\DependencyInjection\ContainerInterface; 15 16/** 17 * Provides a block that renders an extra field from an entity. 18 * 19 * This block handles fields that are provided by implementations of 20 * hook_entity_extra_field_info(). 21 * 22 * @see \Drupal\layout_builder\Plugin\Block\FieldBlock 23 * This block plugin handles all other field entities not provided by 24 * hook_entity_extra_field_info(). 25 * 26 * @Block( 27 * id = "extra_field_block", 28 * deriver = "\Drupal\layout_builder\Plugin\Derivative\ExtraFieldBlockDeriver", 29 * ) 30 * 31 * @internal 32 * Plugin classes are internal. 33 */ 34class ExtraFieldBlock extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface { 35 36 /** 37 * The entity field manager. 38 * 39 * @var \Drupal\Core\Entity\EntityFieldManagerInterface 40 */ 41 protected $entityFieldManager; 42 43 /** 44 * The field name. 45 * 46 * @var string 47 */ 48 protected $fieldName; 49 50 /** 51 * The entity type manager. 52 * 53 * @var \Drupal\Core\Entity\EntityTypeManagerInterface 54 */ 55 protected $entityTypeManager; 56 57 /** 58 * Constructs a new ExtraFieldBlock. 59 * 60 * @param array $configuration 61 * A configuration array containing information about the plugin instance. 62 * @param string $plugin_id 63 * The plugin ID for the plugin instance. 64 * @param mixed $plugin_definition 65 * The plugin implementation definition. 66 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager 67 * The entity type manager. 68 * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager 69 * The entity field manager. 70 */ 71 public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) { 72 $this->entityTypeManager = $entity_type_manager; 73 $this->entityFieldManager = $entity_field_manager; 74 // Get field name from the plugin ID. 75 list (, , , $field_name) = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4); 76 assert(!empty($field_name)); 77 $this->fieldName = $field_name; 78 79 parent::__construct($configuration, $plugin_id, $plugin_definition); 80 } 81 82 /** 83 * {@inheritdoc} 84 */ 85 public function defaultConfiguration() { 86 return [ 87 'label_display' => FALSE, 88 'formatter' => [ 89 'settings' => [], 90 'third_party_settings' => [], 91 ], 92 ]; 93 } 94 95 /** 96 * {@inheritdoc} 97 */ 98 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { 99 return new static( 100 $configuration, 101 $plugin_id, 102 $plugin_definition, 103 $container->get('entity_type.manager'), 104 $container->get('entity_field.manager') 105 ); 106 } 107 108 /** 109 * Gets the entity that has the field. 110 * 111 * @return \Drupal\Core\Entity\FieldableEntityInterface 112 * The entity. 113 */ 114 protected function getEntity() { 115 return $this->getContextValue('entity'); 116 } 117 118 /** 119 * {@inheritdoc} 120 */ 121 public function build() { 122 $entity = $this->getEntity(); 123 // Add a placeholder to replace after the entity view is built. 124 // @see layout_builder_entity_view_alter(). 125 $extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle()); 126 if (!isset($extra_fields['display'][$this->fieldName])) { 127 $build = []; 128 } 129 else { 130 $build = [ 131 '#extra_field_placeholder_field_name' => $this->fieldName, 132 // Always provide a placeholder. The Layout Builder will NOT invoke 133 // hook_entity_view_alter() so extra fields will not be added to the 134 // render array. If the hook is invoked the placeholder will be 135 // replaced. 136 // @see ::replaceFieldPlaceholder() 137 '#markup' => $this->t('Placeholder for the @preview_fallback', ['@preview_fallback' => $this->getPreviewFallbackString()]), 138 ]; 139 } 140 CacheableMetadata::createFromObject($this)->applyTo($build); 141 return $build; 142 } 143 144 /** 145 * {@inheritdoc} 146 */ 147 public function getPreviewFallbackString() { 148 $entity = $this->getEntity(); 149 $extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle()); 150 return new TranslatableMarkup('"@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]); 151 } 152 153 /** 154 * Replaces all placeholders for a given field. 155 * 156 * @param array $build 157 * The built render array for the elements. 158 * @param array $built_field 159 * The render array to replace the placeholder. 160 * @param string $field_name 161 * The field name. 162 * 163 * @see ::build() 164 */ 165 public static function replaceFieldPlaceholder(array &$build, array $built_field, $field_name) { 166 foreach (Element::children($build) as $child) { 167 if (isset($build[$child]['#extra_field_placeholder_field_name']) && $build[$child]['#extra_field_placeholder_field_name'] === $field_name) { 168 $placeholder_cache = CacheableMetadata::createFromRenderArray($build[$child]); 169 $built_cache = CacheableMetadata::createFromRenderArray($built_field); 170 $merged_cache = $placeholder_cache->merge($built_cache); 171 $build[$child] = $built_field; 172 $merged_cache->applyTo($build); 173 } 174 else { 175 static::replaceFieldPlaceholder($build[$child], $built_field, $field_name); 176 } 177 } 178 } 179 180 /** 181 * {@inheritdoc} 182 */ 183 protected function blockAccess(AccountInterface $account) { 184 return $this->getEntity()->access('view', $account, TRUE); 185 } 186 187} 188