EntityFormController.php 12.2 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Entity\EntityFormController.
6
7
 */

8
namespace Drupal\Core\Entity;
9

10
use Drupal\Core\Form\FormBase;
11
use Drupal\Core\Extension\ModuleHandlerInterface;
12

13
14
15
/**
 * Base class for entity form controllers.
 */
16
class EntityFormController extends FormBase implements EntityFormControllerInterface {
17
18
19
20
21
22
23
24
25
26
27

  /**
   * The name of the current operation.
   *
   * Subclasses may use this to implement different behaviors depending on its
   * value.
   *
   * @var string
   */
  protected $operation;

28
29
30
31
32
33
34
  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

35
36
37
38
39
40
41
  /**
   * The entity being used by this form.
   *
   * @var \Drupal\Core\Entity\EntityInterface
   */
  protected $entity;

42
43
  /**
   * {@inheritdoc}
44
45
46
47
48
49
   */
  public function setOperation($operation) {
    // If NULL is passed, do not overwrite the operation.
    if ($operation) {
      $this->operation = $operation;
    }
50
    return $this;
51
52
53
  }

  /**
54
   * {@inheritdoc}
55
   */
56
  public function getBaseFormID() {
57
58
59
60
    // Assign ENTITYTYPE_form as base form ID to invoke corresponding
    // hook_form_alter(), #validate, #submit, and #theme callbacks, but only if
    // it is different from the actual form ID, since callbacks would be invoked
    // twice otherwise.
61
    $base_form_id = $this->entity->getEntityTypeId() . '_form';
62
    if ($base_form_id == $this->getFormId()) {
63
      $base_form_id = NULL;
64
65
    }
    return $base_form_id;
66
  }
67

68
69
70
  /**
   * {@inheritdoc}
   */
71
  public function getFormId() {
72
    $entity_type = $this->entity->getEntityTypeId();
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
    $bundle = $this->entity->bundle();
    $form_id = $entity_type;
    if ($bundle != $entity_type) {
      $form_id = $bundle . '_' . $form_id;
    }
    if ($this->operation != 'default') {
      $form_id = $form_id . '_' . $this->operation;
    }
    return $form_id . '_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, array &$form_state) {
    // During the initial form build, add this controller to the form state and
    // allow for initial preparation before form building and processing.
    if (!isset($form_state['controller'])) {
      $this->init($form_state);
92
93
94
    }

    // Retrieve the form array using the possibly updated entity in form state.
95
    $form = $this->form($form, $form_state);
96
97
98
99
100
101
102
103
104
105

    // Retrieve and add the form actions array.
    $actions = $this->actionsElement($form, $form_state);
    if (!empty($actions)) {
      $form['actions'] = $actions;
    }

    return $form;
  }

106
107
108
109
110
111
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, array &$form_state) {
  }

112
113
114
  /**
   * Initialize the form state and the entity before the first form build.
   */
115
  protected function init(array &$form_state) {
116
117
118
    // Add the controller to the form state so it can be easily accessed by
    // module-provided form handlers there.
    $form_state['controller'] = $this;
119

120
    // Prepare the entity to be presented in the entity form.
121
    $this->prepareEntity();
122

123
124
    // Invoke the prepare form hooks.
    $this->prepareInvokeAll('entity_prepare_form', $form_state);
125
    $this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state);
126
127
128
129
130
  }

  /**
   * Returns the actual form array to be built.
   *
131
   * @see \Drupal\Core\Entity\EntityFormController::build()
132
   */
133
134
  public function form(array $form, array &$form_state) {
    $entity = $this->entity;
135

136
    // Add a process callback.
137
    $form['#process'][] = array($this, 'processForm');
138

139
140
141
142
143
144
    if (!isset($form['langcode'])) {
      // If the form did not specify otherwise, default to keeping the existing
      // language of the entity or defaulting to the site default language for
      // new entities.
      $form['langcode'] = array(
        '#type' => 'value',
145
        '#value' => !$entity->isNew() ? $entity->language()->id : language_default()->id,
146
147
      );
    }
148
149
150
    return $form;
  }

151
152
153
154
155
156
  /**
   * Process callback: assigns weights and hides extra fields.
   *
   * @see \Drupal\Core\Entity\EntityFormController::form()
   */
  public function processForm($element, $form_state, $form) {
157
158
159
160
    // If the form is cached, process callbacks may not have a valid reference
    // to the entity object, hence we must restore it.
    $this->entity = $form_state['controller']->getEntity();

161
162
163
    return $element;
  }

164
165
166
167
168
169
170
  /**
   * Returns the action form element for the current entity form.
   */
  protected function actionsElement(array $form, array &$form_state) {
    $element = $this->actions($form, $form_state);

    // We cannot delete an entity that has not been created yet.
171
    if ($this->entity->isNew()) {
172
173
174
175
176
177
178
179
      unset($element['delete']);
    }
    elseif (isset($element['delete'])) {
      // Move the delete action as last one, unless weights are explicitly
      // provided.
      $delete = $element['delete'];
      unset($element['delete']);
      $element['delete'] = $delete;
180
181
182
183
184
185
      $element['delete']['#button_type'] = 'danger';
    }

    if (isset($element['submit'])) {
      // Give the primary submit button a #button_type of primary.
      $element['submit']['#button_type'] = 'primary';
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    }

    $count = 0;
    foreach (element_children($element) as $action) {
      $element[$action] += array(
        '#type' => 'submit',
        '#weight' => ++$count * 5,
      );
    }

    if (!empty($element)) {
      $element['#type'] = 'actions';
    }

    return $element;
  }

  /**
   * Returns an array of supported actions for the current entity form.
   */
  protected function actions(array $form, array &$form_state) {
    return array(
      // @todo Rename the action key from submit to save.
      'submit' => array(
210
        '#value' => $this->t('Save'),
211
212
213
214
215
216
217
218
219
        '#validate' => array(
          array($this, 'validate'),
        ),
        '#submit' => array(
          array($this, 'submit'),
          array($this, 'save'),
        ),
      ),
      'delete' => array(
220
        '#value' => $this->t('Delete'),
221
222
223
224
225
226
227
228
229
230
231
        // No need to validate the form when deleting the entity.
        '#submit' => array(
          array($this, 'delete'),
        ),
      ),
      // @todo Consider introducing a 'preview' action here, since it is used by
      // many entity types.
    );
  }

  /**
232
   * {@inheritdoc}
233
234
   */
  public function validate(array $form, array &$form_state) {
235
    $this->updateFormLangcode($form_state);
236
237
238
239
240
241
242
    // @todo Remove this.
    // Execute legacy global validation handlers.
    unset($form_state['validate_handlers']);
    form_execute_handlers('validate', $form, $form_state);
  }

  /**
243
   * {@inheritdoc}
244
245
246
247
248
249
250
251
252
253
254
255
256
   *
   * This is the default entity object builder function. It is called before any
   * other submit handler to build the new entity object to be passed to the
   * following submit handlers. At this point of the form workflow the entity is
   * validated and the form state can be updated, this way the subsequently
   * invoked handlers can retrieve a regular entity object to act on.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param array $form_state
   *   A reference to a keyed array containing the current state of the form.
   */
  public function submit(array $form, array &$form_state) {
257
258
    // Remove button and internal Form API values from submitted values.
    form_state_values_clean($form_state);
259
260
    $this->entity = $this->buildEntity($form, $form_state);
    return $this->entity;
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
  }

  /**
   * Form submission handler for the 'save' action.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param array $form_state
   *   A reference to a keyed array containing the current state of the form.
   */
  public function save(array $form, array &$form_state) {
    // @todo Perform common save operations.
  }

  /**
   * Form submission handler for the 'delete' action.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param array $form_state
   *   A reference to a keyed array containing the current state of the form.
   */
  public function delete(array $form, array &$form_state) {
284
285
286
287
288
289
290
291
292
    if ($this->entity->hasLinkTemplate('delete-form')) {
      $form_state['redirect_route'] = $this->entity->urlInfo('delete-form');

      $query = $this->getRequest()->query;
      if ($query->has('destination')) {
        $form_state['redirect_route']['options']['query']['destination'] = $query->get('destination');
        $query->remove('destination');
      }
    }
293
294
295
  }

  /**
296
   * {@inheritdoc}
297
   */
298
  public function getFormLangcode(array &$form_state) {
299
    return $this->entity->language()->id;
300
301
  }

302
  /**
303
   * {@inheritdoc}
304
   */
305
  public function isDefaultFormLangcode(array $form_state) {
306
307
    // The entity is not translatable, this is always the default language.
    return TRUE;
308
309
310
  }

  /**
311
   * Updates the form language to reflect any change to the entity language.
312
313
   *
   * @param array $form_state
314
   *   A reference to a keyed array containing the current state of the form.
315
   */
316
  protected function updateFormLangcode(array &$form_state) {
317
318
319
320
    // Update the form language as it might have changed.
    if (isset($form_state['values']['langcode']) && $this->isDefaultFormLangcode($form_state)) {
      $form_state['langcode'] = $form_state['values']['langcode'];
    }
321
  }
322

323
  /**
324
   * {@inheritdoc}
325
326
   */
  public function buildEntity(array $form, array &$form_state) {
327
    $entity = clone $this->entity;
328
329
330
331
    // If you submit a form, the form state comes from caching, which forces
    // the controller to be the one before caching. Ensure to have the
    // controller of the current request.
    $form_state['controller'] = $this;
332

333
    $this->copyFormValuesToEntity($entity, $form, $form_state);
334
335
336
337
338

    // Invoke all specified builders for copying form values to entity
    // properties.
    if (isset($form['#entity_builders'])) {
      foreach ($form['#entity_builders'] as $function) {
339
        call_user_func_array($function, array($entity->getEntityTypeId(), $entity, &$form, &$form_state));
340
341
342
      }
    }

343
344
345
    return $entity;
  }

346
347
348
349
350
351
352
353
  /**
   * Copies top-level form values to entity properties
   *
   * This should not change existing entity properties that are not being edited
   * by this form.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity the current form should operate upon.
354
355
   * @param array $form
   *   A nested array of form elements comprising the form.
356
357
358
   * @param array $form_state
   *   An associative array containing the current state of the form.
   */
359
  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, array &$form_state) {
360
361
362
363
364
365
366
367
    // @todo: This relies on a method that only exists for config and content
    //   entities, in a different way. Consider moving this logic to a config
    //   entity specific implementation.
    foreach ($form_state['values'] as $key => $value) {
      $entity->set($key, $value);
    }
  }

368
  /**
369
   * {@inheritdoc}
370
   */
371
372
  public function getEntity() {
    return $this->entity;
373
374
  }

375
  /**
376
   * {@inheritdoc}
377
   */
378
379
380
  public function setEntity(EntityInterface $entity) {
    $this->entity = $entity;
    return $this;
381
382
383
384
385
  }

  /**
   * Prepares the entity object before the form is built first.
   */
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
  protected function prepareEntity() {}

  /**
   * Invokes the specified prepare hook variant.
   *
   * @param string $hook
   *   The hook variant name.
   * @param array $form_state
   *   An associative array containing the current state of the form.
   */
  protected function prepareInvokeAll($hook, array &$form_state) {
    $implementations = $this->moduleHandler->getImplementations($hook);
    foreach ($implementations as $module) {
      $function = $module . '_' . $hook;
      if (function_exists($function)) {
        // Ensure we pass an updated translation object and form display at
        // each invocation, since they depend on form state which is alterable.
403
        $args = array($this->entity, $this->operation, &$form_state);
404
405
406
        call_user_func_array($function, $args);
      }
    }
407
408
409
  }

  /**
410
   * {@inheritdoc}
411
412
413
414
   */
  public function getOperation() {
    return $this->operation;
  }
415
416
417
418
419
420
421
422
423

  /**
   * {@inheritdoc}
   */
  public function setModuleHandler(ModuleHandlerInterface $module_handler) {
    $this->moduleHandler = $module_handler;
    return $this;
  }

424
}