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