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