EntityFormController.php 13 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\Entity\Display\EntityFormDisplayInterface;
11
use Drupal\Core\Form\FormBase;
12
use Drupal\Core\Extension\ModuleHandlerInterface;
13

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

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

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

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

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

  /**
55
   * {@inheritdoc}
56
   */
57
  public function getBaseFormID() {
58 59 60 61 62
    // 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.
    $base_form_id = $this->entity->entityType() . '_form';
63
    if ($base_form_id == $this->getFormId()) {
64
      $base_form_id = NULL;
65 66
    }
    return $base_form_id;
67
  }
68

69 70 71
  /**
   * {@inheritdoc}
   */
72
  public function getFormId() {
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    $entity_type = $this->entity->entityType();
    $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);
93 94 95
    }

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

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

    return $form;
  }

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

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

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

124
    $form_display = entity_get_render_form_display($this->entity, $this->getOperation());
125
    $this->setFormDisplay($form_display, $form_state);
126 127 128 129

    // Invoke the prepare form hooks.
    $this->prepareInvokeAll('entity_prepare_form', $form_state);
    $this->prepareInvokeAll($this->entity->entityType() . '_prepare_form', $form_state);
130 131 132 133 134
  }

  /**
   * Returns the actual form array to be built.
   *
135
   * @see \Drupal\Core\Entity\EntityFormController::build()
136
   */
137 138
  public function form(array $form, array &$form_state) {
    $entity = $this->entity;
139
    // @todo Exploit the Field API to generate the default widgets for the
140 141 142
    // entity properties.
    $info = $entity->entityInfo();
    if (!empty($info['fieldable'])) {
143
      field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
144
    }
145

146 147
    // Add a process callback so we can assign weights and hide extra fields.
    $form['#process'][] = array($this, 'processForm');
148

149 150 151 152 153 154
    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',
155
        '#value' => !$entity->isNew() ? $entity->language()->id : language_default()->id,
156 157
      );
    }
158 159 160
    return $form;
  }

161 162 163 164 165 166
  /**
   * Process callback: assigns weights and hides extra fields.
   *
   * @see \Drupal\Core\Entity\EntityFormController::form()
   */
  public function processForm($element, $form_state, $form) {
167 168 169 170
    // 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();

171 172 173 174 175 176 177
    // Assign the weights configured in the form display.
    foreach ($this->getFormDisplay($form_state)->getComponents() as $name => $options) {
      if (isset($element[$name])) {
        $element[$name]['#weight'] = $options['weight'];
      }
    }

178
    // Hide or assign weights for extra fields.
179 180
    $extra_fields = field_info_extra_fields($this->entity->entityType(), $this->entity->bundle(), 'form');
    foreach ($extra_fields as $extra_field => $info) {
181 182
      $component = $this->getFormDisplay($form_state)->getComponent($extra_field);
      if (!$component) {
183 184
        $element[$extra_field]['#access'] = FALSE;
      }
185 186 187
      else {
        $element[$extra_field]['#weight'] = $component['weight'];
      }
188 189 190 191 192
    }

    return $element;
  }

193 194 195 196 197 198 199
  /**
   * 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.
200
    if ($this->entity->isNew()) {
201 202 203 204 205 206 207 208
      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;
209 210 211 212 213 214
      $element['delete']['#button_type'] = 'danger';
    }

    if (isset($element['submit'])) {
      // Give the primary submit button a #button_type of primary.
      $element['submit']['#button_type'] = 'primary';
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
    }

    $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(
239
        '#value' => $this->t('Save'),
240 241 242 243 244 245 246 247 248
        '#validate' => array(
          array($this, 'validate'),
        ),
        '#submit' => array(
          array($this, 'submit'),
          array($this, 'save'),
        ),
      ),
      'delete' => array(
249
        '#value' => $this->t('Delete'),
250 251 252 253 254 255 256 257 258 259 260
        // 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.
    );
  }

  /**
261
   * {@inheritdoc}
262 263
   */
  public function validate(array $form, array &$form_state) {
264
    $this->updateFormLangcode($form_state);
265 266 267 268 269 270 271
    // @todo Remove this.
    // Execute legacy global validation handlers.
    unset($form_state['validate_handlers']);
    form_execute_handlers('validate', $form, $form_state);
  }

  /**
272
   * {@inheritdoc}
273 274 275 276 277 278 279 280 281 282 283 284 285
   *
   * 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) {
286 287
    // Remove button and internal Form API values from submitted values.
    form_state_values_clean($form_state);
288 289
    $this->entity = $this->buildEntity($form, $form_state);
    return $this->entity;
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
  }

  /**
   * 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) {
    // @todo Perform common delete operations.
  }

  /**
317
   * {@inheritdoc}
318
   */
319
  public function getFormLangcode(array &$form_state) {
320
    return $this->entity->language()->id;
321 322
  }

323
  /**
324
   * {@inheritdoc}
325
   */
326
  public function isDefaultFormLangcode(array $form_state) {
327 328
    // The entity is not translatable, this is always the default language.
    return TRUE;
329 330 331
  }

  /**
332
   * Updates the form language to reflect any change to the entity language.
333 334
   *
   * @param array $form_state
335
   *   A reference to a keyed array containing the current state of the form.
336
   */
337
  protected function updateFormLangcode(array &$form_state) {
338 339 340 341
    // 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'];
    }
342
  }
343

344
  /**
345
   * {@inheritdoc}
346 347
   */
  public function buildEntity(array $form, array &$form_state) {
348
    $entity = clone $this->entity;
349 350 351 352
    // 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;
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371

    // Copy top-level form values to entity properties, without changing
    // existing entity properties that are not being edited by
    // this form.
    // @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);
    }

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

372 373 374 375
    return $entity;
  }

  /**
376
   * {@inheritdoc}
377
   */
378 379
  public function getEntity() {
    return $this->entity;
380 381
  }

382
  /**
383
   * {@inheritdoc}
384
   */
385 386 387
  public function setEntity(EntityInterface $entity) {
    $this->entity = $entity;
    return $this;
388 389 390 391 392
  }

  /**
   * Prepares the entity object before the form is built first.
   */
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
  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.
410
        $args = array($this->entity, $this->getFormDisplay($form_state), $this->operation, &$form_state);
411 412 413
        call_user_func_array($function, $args);
      }
    }
414 415
  }

416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
  /**
   * {@inheritdoc}
   */
  public function getFormDisplay(array $form_state) {
    return isset($form_state['form_display']) ? $form_state['form_display'] : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state) {
    $form_state['form_display'] = $form_display;
    return $this;
  }

431
  /**
432
   * {@inheritdoc}
433 434 435 436
   */
  public function getOperation() {
    return $this->operation;
  }
437 438 439 440 441 442 443 444 445

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

446
}