Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Entity / EntityForm.php
1 <?php
2
3 namespace Drupal\Core\Entity;
4
5 use Drupal\Core\Form\FormBase;
6 use Drupal\Core\Extension\ModuleHandlerInterface;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\Core\Render\Element;
9 use Drupal\Core\Routing\RouteMatchInterface;
10
11 /**
12  * Base class for entity forms.
13  *
14  * @ingroup entity_api
15  */
16 class EntityForm extends FormBase implements EntityFormInterface {
17
18   /**
19    * The name of the current operation.
20    *
21    * Subclasses may use this to implement different behaviors depending on its
22    * value.
23    *
24    * @var string
25    */
26   protected $operation;
27
28   /**
29    * The module handler service.
30    *
31    * @var \Drupal\Core\Extension\ModuleHandlerInterface
32    */
33   protected $moduleHandler;
34
35   /**
36    * The entity manager.
37    *
38    * @var \Drupal\Core\Entity\EntityManagerInterface
39    *
40    * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
41    *
42    * @see https://www.drupal.org/node/2549139
43    */
44   protected $entityManager;
45
46   /**
47    * The entity type manager.
48    *
49    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
50    */
51   protected $entityTypeManager;
52
53   /**
54    * The entity being used by this form.
55    *
56    * @var \Drupal\Core\Entity\EntityInterface
57    */
58   protected $entity;
59
60   /**
61    * {@inheritdoc}
62    */
63   public function setOperation($operation) {
64     // If NULL is passed, do not overwrite the operation.
65     if ($operation) {
66       $this->operation = $operation;
67     }
68     return $this;
69   }
70
71   /**
72    * {@inheritdoc}
73    */
74   public function getBaseFormId() {
75     // Assign ENTITYTYPE_form as base form ID to invoke corresponding
76     // hook_form_alter(), #validate, #submit, and #theme callbacks, but only if
77     // it is different from the actual form ID, since callbacks would be invoked
78     // twice otherwise.
79     $base_form_id = $this->entity->getEntityTypeId() . '_form';
80     if ($base_form_id == $this->getFormId()) {
81       $base_form_id = NULL;
82     }
83     return $base_form_id;
84   }
85
86   /**
87    * {@inheritdoc}
88    */
89   public function getFormId() {
90     $form_id = $this->entity->getEntityTypeId();
91     if ($this->entity->getEntityType()->hasKey('bundle')) {
92       $form_id .= '_' . $this->entity->bundle();
93     }
94     if ($this->operation != 'default') {
95       $form_id = $form_id . '_' . $this->operation;
96     }
97     return $form_id . '_form';
98   }
99
100   /**
101    * {@inheritdoc}
102    */
103   public function buildForm(array $form, FormStateInterface $form_state) {
104     // During the initial form build, add this form object to the form state and
105     // allow for initial preparation before form building and processing.
106     if (!$form_state->has('entity_form_initialized')) {
107       $this->init($form_state);
108     }
109
110     // Ensure that edit forms have the correct cacheability metadata so they can
111     // be cached.
112     if (!$this->entity->isNew()) {
113       \Drupal::service('renderer')->addCacheableDependency($form, $this->entity);
114     }
115
116     // Retrieve the form array using the possibly updated entity in form state.
117     $form = $this->form($form, $form_state);
118
119     // Retrieve and add the form actions array.
120     $actions = $this->actionsElement($form, $form_state);
121     if (!empty($actions)) {
122       $form['actions'] = $actions;
123     }
124
125     return $form;
126   }
127
128   /**
129    * Initialize the form state and the entity before the first form build.
130    */
131   protected function init(FormStateInterface $form_state) {
132     // Flag that this form has been initialized.
133     $form_state->set('entity_form_initialized', TRUE);
134
135     // Prepare the entity to be presented in the entity form.
136     $this->prepareEntity();
137
138     // Invoke the prepare form hooks.
139     $this->prepareInvokeAll('entity_prepare_form', $form_state);
140     $this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state);
141   }
142
143   /**
144    * Gets the actual form array to be built.
145    *
146    * @see \Drupal\Core\Entity\EntityForm::processForm()
147    * @see \Drupal\Core\Entity\EntityForm::afterBuild()
148    */
149   public function form(array $form, FormStateInterface $form_state) {
150     // Add #process and #after_build callbacks.
151     $form['#process'][] = '::processForm';
152     $form['#after_build'][] = '::afterBuild';
153
154     return $form;
155   }
156
157   /**
158    * Process callback: assigns weights and hides extra fields.
159    *
160    * @see \Drupal\Core\Entity\EntityForm::form()
161    */
162   public function processForm($element, FormStateInterface $form_state, $form) {
163     // If the form is cached, process callbacks may not have a valid reference
164     // to the entity object, hence we must restore it.
165     $this->entity = $form_state->getFormObject()->getEntity();
166
167     return $element;
168   }
169
170   /**
171    * Form element #after_build callback: Updates the entity with submitted data.
172    *
173    * Updates the internal $this->entity object with submitted values when the
174    * form is being rebuilt (e.g. submitted via AJAX), so that subsequent
175    * processing (e.g. AJAX callbacks) can rely on it.
176    */
177   public function afterBuild(array $element, FormStateInterface $form_state) {
178     // Rebuild the entity if #after_build is being called as part of a form
179     // rebuild, i.e. if we are processing input.
180     if ($form_state->isProcessingInput()) {
181       $this->entity = $this->buildEntity($element, $form_state);
182     }
183
184     return $element;
185   }
186
187   /**
188    * Returns the action form element for the current entity form.
189    */
190   protected function actionsElement(array $form, FormStateInterface $form_state) {
191     $element = $this->actions($form, $form_state);
192
193     if (isset($element['delete'])) {
194       // Move the delete action as last one, unless weights are explicitly
195       // provided.
196       $delete = $element['delete'];
197       unset($element['delete']);
198       $element['delete'] = $delete;
199       $element['delete']['#button_type'] = 'danger';
200     }
201
202     if (isset($element['submit'])) {
203       // Give the primary submit button a #button_type of primary.
204       $element['submit']['#button_type'] = 'primary';
205     }
206
207     $count = 0;
208     foreach (Element::children($element) as $action) {
209       $element[$action] += [
210         '#weight' => ++$count * 5,
211       ];
212     }
213
214     if (!empty($element)) {
215       $element['#type'] = 'actions';
216     }
217
218     return $element;
219   }
220
221   /**
222    * Returns an array of supported actions for the current entity form.
223    *
224    * This function generates a list of Form API elements which represent
225    * actions supported by the current entity form.
226    *
227    * @param array $form
228    *   An associative array containing the structure of the form.
229    * @param \Drupal\Core\Form\FormStateInterface $form_state
230    *   The current state of the form.
231    *
232    * @return array
233    *   An array of supported Form API action elements keyed by name.
234    *
235    * @todo Consider introducing a 'preview' action here, since it is used by
236    *   many entity types.
237    */
238   protected function actions(array $form, FormStateInterface $form_state) {
239     // @todo Consider renaming the action key from submit to save. The impacts
240     //   are hard to predict. For example, see
241     //   \Drupal\language\Element\LanguageConfiguration::processLanguageConfiguration().
242     $actions['submit'] = [
243       '#type' => 'submit',
244       '#value' => $this->t('Save'),
245       '#submit' => ['::submitForm', '::save'],
246     ];
247
248     if (!$this->entity->isNew() && $this->entity->hasLinkTemplate('delete-form')) {
249       $route_info = $this->entity->urlInfo('delete-form');
250       if ($this->getRequest()->query->has('destination')) {
251         $query = $route_info->getOption('query');
252         $query['destination'] = $this->getRequest()->query->get('destination');
253         $route_info->setOption('query', $query);
254       }
255       $actions['delete'] = [
256         '#type' => 'link',
257         '#title' => $this->t('Delete'),
258         '#access' => $this->entity->access('delete'),
259         '#attributes' => [
260           'class' => ['button', 'button--danger'],
261         ],
262       ];
263       $actions['delete']['#url'] = $route_info;
264     }
265
266     return $actions;
267   }
268
269   /**
270    * {@inheritdoc}
271    *
272    * This is the default entity object builder function. It is called before any
273    * other submit handler to build the new entity object to be used by the
274    * following submit handlers. At this point of the form workflow the entity is
275    * validated and the form state can be updated, this way the subsequently
276    * invoked handlers can retrieve a regular entity object to act on. Generally
277    * this method should not be overridden unless the entity requires the same
278    * preparation for two actions, see \Drupal\comment\CommentForm for an example
279    * with the save and preview actions.
280    *
281    * @param array $form
282    *   An associative array containing the structure of the form.
283    * @param \Drupal\Core\Form\FormStateInterface $form_state
284    *   The current state of the form.
285    */
286   public function submitForm(array &$form, FormStateInterface $form_state) {
287     // Remove button and internal Form API values from submitted values.
288     $form_state->cleanValues();
289     $this->entity = $this->buildEntity($form, $form_state);
290   }
291
292   /**
293    * {@inheritdoc}
294    */
295   public function save(array $form, FormStateInterface $form_state) {
296     return $this->entity->save();
297   }
298
299   /**
300    * {@inheritdoc}
301    */
302   public function buildEntity(array $form, FormStateInterface $form_state) {
303     $entity = clone $this->entity;
304     $this->copyFormValuesToEntity($entity, $form, $form_state);
305
306     // Invoke all specified builders for copying form values to entity
307     // properties.
308     if (isset($form['#entity_builders'])) {
309       foreach ($form['#entity_builders'] as $function) {
310         call_user_func_array($form_state->prepareCallback($function), [$entity->getEntityTypeId(), $entity, &$form, &$form_state]);
311       }
312     }
313
314     return $entity;
315   }
316
317   /**
318    * Copies top-level form values to entity properties
319    *
320    * This should not change existing entity properties that are not being edited
321    * by this form.
322    *
323    * @param \Drupal\Core\Entity\EntityInterface $entity
324    *   The entity the current form should operate upon.
325    * @param array $form
326    *   A nested array of form elements comprising the form.
327    * @param \Drupal\Core\Form\FormStateInterface $form_state
328    *   The current state of the form.
329    */
330   protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
331     $values = $form_state->getValues();
332
333     if ($this->entity instanceof EntityWithPluginCollectionInterface) {
334       // Do not manually update values represented by plugin collections.
335       $values = array_diff_key($values, $this->entity->getPluginCollections());
336     }
337
338     // @todo: This relies on a method that only exists for config and content
339     //   entities, in a different way. Consider moving this logic to a config
340     //   entity specific implementation.
341     foreach ($values as $key => $value) {
342       $entity->set($key, $value);
343     }
344   }
345
346   /**
347    * {@inheritdoc}
348    */
349   public function getEntity() {
350     return $this->entity;
351   }
352
353   /**
354    * {@inheritdoc}
355    */
356   public function setEntity(EntityInterface $entity) {
357     $this->entity = $entity;
358     return $this;
359   }
360
361   /**
362    * {@inheritdoc}
363    */
364   public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
365     if ($route_match->getRawParameter($entity_type_id) !== NULL) {
366       $entity = $route_match->getParameter($entity_type_id);
367     }
368     else {
369       $values = [];
370       // If the entity has bundles, fetch it from the route match.
371       $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
372       if ($bundle_key = $entity_type->getKey('bundle')) {
373         if (($bundle_entity_type_id = $entity_type->getBundleEntityType()) && $route_match->getRawParameter($bundle_entity_type_id)) {
374           $values[$bundle_key] = $route_match->getParameter($bundle_entity_type_id)->id();
375         }
376         elseif ($route_match->getRawParameter($bundle_key)) {
377           $values[$bundle_key] = $route_match->getParameter($bundle_key);
378         }
379       }
380
381       $entity = $this->entityTypeManager->getStorage($entity_type_id)->create($values);
382     }
383
384     return $entity;
385   }
386
387   /**
388    * Prepares the entity object before the form is built first.
389    */
390   protected function prepareEntity() {}
391
392   /**
393    * Invokes the specified prepare hook variant.
394    *
395    * @param string $hook
396    *   The hook variant name.
397    * @param \Drupal\Core\Form\FormStateInterface $form_state
398    *   The current state of the form.
399    */
400   protected function prepareInvokeAll($hook, FormStateInterface $form_state) {
401     $implementations = $this->moduleHandler->getImplementations($hook);
402     foreach ($implementations as $module) {
403       $function = $module . '_' . $hook;
404       if (function_exists($function)) {
405         // Ensure we pass an updated translation object and form display at
406         // each invocation, since they depend on form state which is alterable.
407         $args = [$this->entity, $this->operation, &$form_state];
408         call_user_func_array($function, $args);
409       }
410     }
411   }
412
413   /**
414    * {@inheritdoc}
415    */
416   public function getOperation() {
417     return $this->operation;
418   }
419
420   /**
421    * {@inheritdoc}
422    */
423   public function setModuleHandler(ModuleHandlerInterface $module_handler) {
424     $this->moduleHandler = $module_handler;
425     return $this;
426   }
427
428   /**
429    * {@inheritdoc}
430    */
431   public function setEntityManager(EntityManagerInterface $entity_manager) {
432     $this->entityManager = $entity_manager;
433     return $this;
434   }
435
436   /**
437    * {@inheritdoc}
438    */
439   public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
440     $this->entityTypeManager = $entity_type_manager;
441     return $this;
442   }
443
444 }