layoutPluginManager = $layout_plugin_manager; $this->entityFieldManager = $entity_field_manager; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('plugin.manager.core.layout'), $container->get('entity_field.manager') ); } /** * Applies the layout to an entity build. * * @param array $build * A renderable array representing the entity content or form. * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display * The entity display holding the display options configured for the entity * components. */ public function buildView(array &$build, EntityDisplayWithLayoutInterface $display) { $layout_definition = $this->layoutPluginManager->getDefinition($display->getLayoutId(), FALSE); if ($layout_definition && $fields = $this->getFields($build, $display, 'view')) { // Add the regions to the $build in the correct order. $regions = array_fill_keys($layout_definition->getRegionNames(), []); foreach ($fields as $name => $field) { // If the region is controlled by the layout, move the field from the // top-level of $build into a region-specific section. Custom regions // could be set by other code at run-time; these should be ignored. // @todo Ideally the array structure would remain unchanged, see // https://www.drupal.org/node/2846393. if (isset($regions[$field['region']])) { $regions[$field['region']][$name] = $build[$name]; unset($build[$name]); } } // Ensure this will not conflict with any existing array elements by // prefixing with an underscore. $build['_field_layout'] = $display->getLayout()->build($regions); } } /** * Applies the layout to an entity form. * * @param array $build * A renderable array representing the entity content or form. * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display * The entity display holding the display options configured for the entity * components. */ public function buildForm(array &$build, EntityDisplayWithLayoutInterface $display) { $layout_definition = $this->layoutPluginManager->getDefinition($display->getLayoutId(), FALSE); if ($layout_definition && $fields = $this->getFields($build, $display, 'form')) { $fill = []; $fill['#process'][] = '\Drupal\Core\Render\Element\RenderElement::processGroup'; $fill['#pre_render'][] = '\Drupal\Core\Render\Element\RenderElement::preRenderGroup'; // Add the regions to the $build in the correct order. $regions = array_fill_keys($layout_definition->getRegionNames(), $fill); foreach ($fields as $name => $field) { // As this is a form, #group can be used to relocate the fields. This // avoids breaking hook_form_alter() implementations by not actually // moving the field in the form structure. If a #group is already set, // do not overwrite it. if (isset($regions[$field['region']]) && !isset($build[$name]['#group'])) { $build[$name]['#group'] = $field['region']; } } // Ensure this will not conflict with any existing array elements by // prefixing with an underscore. $build['_field_layout'] = $display->getLayout()->build($regions); } } /** * Gets the fields that need to be processed. * * @param array $build * A renderable array representing the entity content or form. * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display * The entity display holding the display options configured for the entity * components. * @param string $display_context * The display context, either 'form' or 'view'. * * @return array * An array of configurable fields present in the build. */ protected function getFields(array $build, EntityDisplayWithLayoutInterface $display, $display_context) { $components = $display->getComponents(); // Ignore any extra fields from the list of field definitions. Field // definitions can have a non-configurable display, but all extra fields are // always displayed. $field_definitions = array_diff_key( $this->entityFieldManager->getFieldDefinitions($display->getTargetEntityTypeId(), $display->getTargetBundle()), $this->entityFieldManager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle()) ); $fields_to_exclude = array_filter($field_definitions, function (FieldDefinitionInterface $field_definition) use ($display_context) { // Remove fields with a non-configurable display. return !$field_definition->isDisplayConfigurable($display_context); }); $components = array_diff_key($components, $fields_to_exclude); // Only include fields present in the build. $components = array_intersect_key($components, $build); return $components; } }