1<?php 2 3namespace Drupal\field_layout; 4 5use Drupal\Core\DependencyInjection\ContainerInjectionInterface; 6use Drupal\Core\Entity\EntityFieldManagerInterface; 7use Drupal\Core\Field\FieldDefinitionInterface; 8use Drupal\field_layout\Display\EntityDisplayWithLayoutInterface; 9use Drupal\Core\Layout\LayoutPluginManagerInterface; 10use Symfony\Component\DependencyInjection\ContainerInterface; 11 12/** 13 * Builds a field layout. 14 */ 15class FieldLayoutBuilder implements ContainerInjectionInterface { 16 17 /** 18 * The layout plugin manager. 19 * 20 * @var \Drupal\Core\Layout\LayoutPluginManagerInterface 21 */ 22 protected $layoutPluginManager; 23 24 /** 25 * The entity field manager. 26 * 27 * @var \Drupal\Core\Entity\EntityFieldManagerInterface 28 */ 29 protected $entityFieldManager; 30 31 /** 32 * Constructs a new FieldLayoutBuilder. 33 * 34 * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_plugin_manager 35 * The layout plugin manager. 36 * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager 37 * The entity field manager. 38 */ 39 public function __construct(LayoutPluginManagerInterface $layout_plugin_manager, EntityFieldManagerInterface $entity_field_manager) { 40 $this->layoutPluginManager = $layout_plugin_manager; 41 $this->entityFieldManager = $entity_field_manager; 42 } 43 44 /** 45 * {@inheritdoc} 46 */ 47 public static function create(ContainerInterface $container) { 48 return new static( 49 $container->get('plugin.manager.core.layout'), 50 $container->get('entity_field.manager') 51 ); 52 } 53 54 /** 55 * Applies the layout to an entity build. 56 * 57 * @param array $build 58 * A renderable array representing the entity content or form. 59 * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display 60 * The entity display holding the display options configured for the entity 61 * components. 62 */ 63 public function buildView(array &$build, EntityDisplayWithLayoutInterface $display) { 64 $layout_definition = $this->layoutPluginManager->getDefinition($display->getLayoutId(), FALSE); 65 if ($layout_definition && $fields = $this->getFields($build, $display, 'view')) { 66 // Add the regions to the $build in the correct order. 67 $regions = array_fill_keys($layout_definition->getRegionNames(), []); 68 69 foreach ($fields as $name => $field) { 70 // If the region is controlled by the layout, move the field from the 71 // top-level of $build into a region-specific section. Custom regions 72 // could be set by other code at run-time; these should be ignored. 73 // @todo Ideally the array structure would remain unchanged, see 74 // https://www.drupal.org/node/2846393. 75 if (isset($regions[$field['region']])) { 76 $regions[$field['region']][$name] = $build[$name]; 77 unset($build[$name]); 78 } 79 } 80 // Ensure this will not conflict with any existing array elements by 81 // prefixing with an underscore. 82 $build['_field_layout'] = $display->getLayout()->build($regions); 83 } 84 } 85 86 /** 87 * Applies the layout to an entity form. 88 * 89 * @param array $build 90 * A renderable array representing the entity content or form. 91 * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display 92 * The entity display holding the display options configured for the entity 93 * components. 94 */ 95 public function buildForm(array &$build, EntityDisplayWithLayoutInterface $display) { 96 $layout_definition = $this->layoutPluginManager->getDefinition($display->getLayoutId(), FALSE); 97 if ($layout_definition && $fields = $this->getFields($build, $display, 'form')) { 98 $fill = []; 99 $fill['#process'][] = '\Drupal\Core\Render\Element\RenderElement::processGroup'; 100 $fill['#pre_render'][] = '\Drupal\Core\Render\Element\RenderElement::preRenderGroup'; 101 // Add the regions to the $build in the correct order. 102 $regions = array_fill_keys($layout_definition->getRegionNames(), $fill); 103 104 foreach ($fields as $name => $field) { 105 // As this is a form, #group can be used to relocate the fields. This 106 // avoids breaking hook_form_alter() implementations by not actually 107 // moving the field in the form structure. If a #group is already set, 108 // do not overwrite it. 109 if (isset($regions[$field['region']]) && !isset($build[$name]['#group'])) { 110 $build[$name]['#group'] = $field['region']; 111 } 112 } 113 // Ensure this will not conflict with any existing array elements by 114 // prefixing with an underscore. 115 $build['_field_layout'] = $display->getLayout()->build($regions); 116 } 117 } 118 119 /** 120 * Gets the fields that need to be processed. 121 * 122 * @param array $build 123 * A renderable array representing the entity content or form. 124 * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display 125 * The entity display holding the display options configured for the entity 126 * components. 127 * @param string $display_context 128 * The display context, either 'form' or 'view'. 129 * 130 * @return array 131 * An array of configurable fields present in the build. 132 */ 133 protected function getFields(array $build, EntityDisplayWithLayoutInterface $display, $display_context) { 134 $components = $display->getComponents(); 135 136 // Ignore any extra fields from the list of field definitions. Field 137 // definitions can have a non-configurable display, but all extra fields are 138 // always displayed. 139 $field_definitions = array_diff_key( 140 $this->entityFieldManager->getFieldDefinitions($display->getTargetEntityTypeId(), $display->getTargetBundle()), 141 $this->entityFieldManager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle()) 142 ); 143 144 $fields_to_exclude = array_filter($field_definitions, function (FieldDefinitionInterface $field_definition) use ($display_context) { 145 // Remove fields with a non-configurable display. 146 return !$field_definition->isDisplayConfigurable($display_context); 147 }); 148 $components = array_diff_key($components, $fields_to_exclude); 149 150 // Only include fields present in the build. 151 $components = array_intersect_key($components, $build); 152 153 return $components; 154 } 155 156} 157