Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Entity / Entity / EntityFormDisplay.php
1 <?php
2
3 namespace Drupal\Core\Entity\Entity;
4
5 use Drupal\Core\Entity\EntityConstraintViolationListInterface;
6 use Drupal\Core\Entity\EntityDisplayPluginCollection;
7 use Drupal\Core\Entity\FieldableEntityInterface;
8 use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
9 use Drupal\Core\Entity\EntityDisplayBase;
10 use Drupal\Core\Form\FormStateInterface;
11 use Symfony\Component\Validator\ConstraintViolation;
12 use Symfony\Component\Validator\ConstraintViolationList;
13 use Symfony\Component\Validator\ConstraintViolationListInterface;
14
15 /**
16  * Configuration entity that contains widget options for all components of a
17  * entity form in a given form mode.
18  *
19  * @ConfigEntityType(
20  *   id = "entity_form_display",
21  *   label = @Translation("Entity form display"),
22  *   entity_keys = {
23  *     "id" = "id",
24  *     "status" = "status"
25  *   },
26  *   handlers = {
27  *     "access" = "\Drupal\Core\Entity\Entity\Access\EntityFormDisplayAccessControlHandler",
28  *   },
29  *   config_export = {
30  *     "id",
31  *     "targetEntityType",
32  *     "bundle",
33  *     "mode",
34  *     "content",
35  *     "hidden",
36  *   }
37  * )
38  */
39 class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayInterface {
40
41   /**
42    * {@inheritdoc}
43    */
44   protected $displayContext = 'form';
45
46   /**
47    * Returns the entity_form_display object used to build an entity form.
48    *
49    * Depending on the configuration of the form mode for the entity bundle, this
50    * can be either the display object associated with the form mode, or the
51    * 'default' display.
52    *
53    * This method should only be used internally when rendering an entity form.
54    * When assigning suggested display options for a component in a given form
55    * mode, entity_get_form_display() should be used instead, in order to avoid
56    * inadvertently modifying the output of other form modes that might happen to
57    * use the 'default' display too. Those options will then be effectively
58    * applied only if the form mode is configured to use them.
59    *
60    * hook_entity_form_display_alter() is invoked on each display, allowing 3rd
61    * party code to alter the display options held in the display before they are
62    * used to generate render arrays.
63    *
64    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
65    *   The entity for which the form is being built.
66    * @param string $form_mode
67    *   The form mode.
68    *
69    * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
70    *   The display object that should be used to build the entity form.
71    *
72    * @see entity_get_form_display()
73    * @see hook_entity_form_display_alter()
74    */
75   public static function collectRenderDisplay(FieldableEntityInterface $entity, $form_mode) {
76     $entity_type = $entity->getEntityTypeId();
77     $bundle = $entity->bundle();
78
79     // Check the existence and status of:
80     // - the display for the form mode,
81     // - the 'default' display.
82     if ($form_mode != 'default') {
83       $candidate_ids[] = $entity_type . '.' . $bundle . '.' . $form_mode;
84     }
85     $candidate_ids[] = $entity_type . '.' . $bundle . '.default';
86     $results = \Drupal::entityQuery('entity_form_display')
87       ->condition('id', $candidate_ids)
88       ->condition('status', TRUE)
89       ->execute();
90
91     // Load the first valid candidate display, if any.
92     $storage = \Drupal::entityManager()->getStorage('entity_form_display');
93     foreach ($candidate_ids as $candidate_id) {
94       if (isset($results[$candidate_id])) {
95         $display = $storage->load($candidate_id);
96         break;
97       }
98     }
99     // Else create a fresh runtime object.
100     if (empty($display)) {
101       $display = $storage->create([
102         'targetEntityType' => $entity_type,
103         'bundle' => $bundle,
104         'mode' => $form_mode,
105         'status' => TRUE,
106       ]);
107     }
108
109     // Let the display know which form mode was originally requested.
110     $display->originalMode = $form_mode;
111
112     // Let modules alter the display.
113     $display_context = [
114       'entity_type' => $entity_type,
115       'bundle' => $bundle,
116       'form_mode' => $form_mode,
117     ];
118     \Drupal::moduleHandler()->alter('entity_form_display', $display, $display_context);
119
120     return $display;
121   }
122
123   /**
124    * {@inheritdoc}
125    */
126   public function __construct(array $values, $entity_type) {
127     $this->pluginManager = \Drupal::service('plugin.manager.field.widget');
128
129     parent::__construct($values, $entity_type);
130   }
131
132   /**
133    * {@inheritdoc}
134    */
135   public function getRenderer($field_name) {
136     if (isset($this->plugins[$field_name])) {
137       return $this->plugins[$field_name];
138     }
139
140     // Instantiate the widget object from the stored display properties.
141     if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
142       $widget = $this->pluginManager->getInstance([
143         'field_definition' => $definition,
144         'form_mode' => $this->originalMode,
145         // No need to prepare, defaults have been merged in setComponent().
146         'prepare' => FALSE,
147         'configuration' => $configuration,
148       ]);
149     }
150     else {
151       $widget = NULL;
152     }
153
154     // Persist the widget object.
155     $this->plugins[$field_name] = $widget;
156     return $widget;
157   }
158
159   /**
160    * {@inheritdoc}
161    */
162   public function buildForm(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
163     // Set #parents to 'top-level' by default.
164     $form += ['#parents' => []];
165
166     // Let each widget generate the form elements.
167     foreach ($this->getComponents() as $name => $options) {
168       if ($widget = $this->getRenderer($name)) {
169         $items = $entity->get($name);
170         $items->filterEmptyItems();
171         $form[$name] = $widget->form($items, $form, $form_state);
172         $form[$name]['#access'] = $items->access('edit');
173
174         // Assign the correct weight. This duplicates the reordering done in
175         // processForm(), but is needed for other forms calling this method
176         // directly.
177         $form[$name]['#weight'] = $options['weight'];
178
179         // Associate the cache tags for the field definition & field storage
180         // definition.
181         $field_definition = $this->getFieldDefinition($name);
182         $this->renderer->addCacheableDependency($form[$name], $field_definition);
183         $this->renderer->addCacheableDependency($form[$name], $field_definition->getFieldStorageDefinition());
184       }
185     }
186
187     // Associate the cache tags for the form display.
188     $this->renderer->addCacheableDependency($form, $this);
189
190     // Add a process callback so we can assign weights and hide extra fields.
191     $form['#process'][] = [$this, 'processForm'];
192   }
193
194   /**
195    * Process callback: assigns weights and hides extra fields.
196    *
197    * @see \Drupal\Core\Entity\Entity\EntityFormDisplay::buildForm()
198    */
199   public function processForm($element, FormStateInterface $form_state, $form) {
200     // Assign the weights configured in the form display.
201     foreach ($this->getComponents() as $name => $options) {
202       if (isset($element[$name])) {
203         $element[$name]['#weight'] = $options['weight'];
204       }
205     }
206
207     // Hide extra fields.
208     $extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
209     $extra_fields = isset($extra_fields['form']) ? $extra_fields['form'] : [];
210     foreach ($extra_fields as $extra_field => $info) {
211       if (!$this->getComponent($extra_field)) {
212         $element[$extra_field]['#access'] = FALSE;
213       }
214     }
215     return $element;
216   }
217
218   /**
219    * {@inheritdoc}
220    */
221   public function extractFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
222     $extracted = [];
223     foreach ($entity as $name => $items) {
224       if ($widget = $this->getRenderer($name)) {
225         $widget->extractFormValues($items, $form, $form_state);
226         $extracted[$name] = $name;
227       }
228     }
229     return $extracted;
230   }
231
232   /**
233    * {@inheritdoc}
234    */
235   public function validateFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
236     $violations = $entity->validate();
237     $violations->filterByFieldAccess();
238
239     // Flag entity level violations.
240     foreach ($violations->getEntityViolations() as $violation) {
241       /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
242       $form_state->setError($form, $violation->getMessage());
243     }
244
245     $this->flagWidgetsErrorsFromViolations($violations, $form, $form_state);
246   }
247
248   /**
249    * {@inheritdoc}
250    */
251   public function flagWidgetsErrorsFromViolations(EntityConstraintViolationListInterface $violations, array &$form, FormStateInterface $form_state) {
252     $entity = $violations->getEntity();
253     foreach ($violations->getFieldNames() as $field_name) {
254       // Only show violations for fields that actually appear in the form, and
255       // let the widget assign the violations to the correct form elements.
256       if ($widget = $this->getRenderer($field_name)) {
257         $field_violations = $this->movePropertyPathViolationsRelativeToField($field_name, $violations->getByField($field_name));
258         $widget->flagErrors($entity->get($field_name), $field_violations, $form, $form_state);
259       }
260     }
261   }
262
263   /**
264    * Moves the property path to be relative to field level.
265    *
266    * @param string $field_name
267    *   The field name.
268    * @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations
269    *   The violations.
270    *
271    * @return \Symfony\Component\Validator\ConstraintViolationList
272    *   A new constraint violation list with the changed property path.
273    */
274   protected function movePropertyPathViolationsRelativeToField($field_name, ConstraintViolationListInterface $violations) {
275     $new_violations = new ConstraintViolationList();
276     foreach ($violations as $violation) {
277       // All the logic below is necessary to change the property path of the
278       // violations to be relative to the item list, so like title.0.value gets
279       // changed to 0.value. Sadly constraints in Symfony don't have setters so
280       // we have to create new objects.
281       /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
282       // Create a new violation object with just a different property path.
283       $violation_path = $violation->getPropertyPath();
284       $path_parts = explode('.', $violation_path);
285       if ($path_parts[0] === $field_name) {
286         unset($path_parts[0]);
287       }
288       $new_path = implode('.', $path_parts);
289
290       $constraint = NULL;
291       $cause = NULL;
292       $parameters = [];
293       $plural = NULL;
294       if ($violation instanceof ConstraintViolation) {
295         $constraint = $violation->getConstraint();
296         $cause = $violation->getCause();
297         $parameters = $violation->getParameters();
298         $plural = $violation->getPlural();
299       }
300
301       $new_violation = new ConstraintViolation(
302         $violation->getMessage(),
303         $violation->getMessageTemplate(),
304         $parameters,
305         $violation->getRoot(),
306         $new_path,
307         $violation->getInvalidValue(),
308         $plural,
309         $violation->getCode(),
310         $constraint,
311         $cause
312       );
313       $new_violations->add($new_violation);
314     }
315     return $new_violations;
316   }
317
318   /**
319    * {@inheritdoc}
320    */
321   public function getPluginCollections() {
322     $configurations = [];
323     foreach ($this->getComponents() as $field_name => $configuration) {
324       if (!empty($configuration['type']) && ($field_definition = $this->getFieldDefinition($field_name))) {
325         $configurations[$configuration['type']] = $configuration + [
326           'field_definition' => $field_definition,
327           'form_mode' => $this->mode,
328         ];
329       }
330     }
331
332     return [
333       'widgets' => new EntityDisplayPluginCollection($this->pluginManager, $configurations),
334     ];
335   }
336
337 }