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