Newer
Older
<?php
namespace Drupal\Core\Entity;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
Dries Buytaert
committed
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
Alex Pott
committed
* Entity form variant for content entity types.
*
* @see \Drupal\Core\ContentEntityBase
*/
Alex Pott
committed
class ContentEntityForm extends EntityForm implements ContentEntityFormInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The entity being used by this form.
*
* @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface
*/
protected $entity;
/**
* The entity type bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
Alex Pott
committed
* Constructs a ContentEntityForm object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
*/
public function __construct(EntityManagerInterface $entity_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
$this->entityManager = $entity_manager;
$this->entityTypeBundleInfo = $entity_type_bundle_info ?: \Drupal::service('entity_type.bundle.info');
$this->time = $time ?: \Drupal::service('datetime.time');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time')
);
}
/**
* {@inheritdoc}
*/
protected function prepareEntity() {
parent::prepareEntity();
// Hide the current revision log message in UI.
if ($this->showRevisionUi() && !$this->entity->isNew()) {
$this->entity->setRevisionLogMessage(NULL);
}
}
/**
* Returns the bundle entity of the entity, or NULL if there is none.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The bundle entity.
*/
protected function getBundleEntity() {
if ($bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType()) {
return $this->entityTypeManager->getStorage($bundle_entity_type)->load($this->entity->bundle());
}
return NULL;
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
public function form(array $form, FormStateInterface $form_state) {
if ($this->showRevisionUi()) {
// Advanced tab must be the first, because other fields rely on that.
if (!isset($form['advanced'])) {
$form['advanced'] = [
'#type' => 'vertical_tabs',
'#weight' => 99,
];
}
}
$form = parent::form($form, $form_state);
// Content entity forms do not use the parent's #after_build callback
// because they only need to rebuild the entity in the validation and the
// submit handler because Field API uses its own #after_build callback for
// its widgets.
unset($form['#after_build']);
$this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state);
// Allow modules to act before and after form language is updated.
Alex Pott
committed
$form['#entity_builders']['update_form_langcode'] = '::updateFormLangcode';
if ($this->showRevisionUi()) {
$this->addRevisionableFormFields($form);
}
return $form;
}
catch
committed
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
// Update the changed timestamp of the entity.
$this->updateChangedTime($this->entity);
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = parent::buildEntity($form, $form_state);
// Mark the entity as requiring validation.
$entity->setValidationRequired(!$form_state->getTemporaryValue('entity_validated'));
// Save as a new revision if requested to do so.
if ($this->showRevisionUi() && !$form_state->isValueEmpty('revision')) {
$entity->setNewRevision();
if ($entity instanceof RevisionLogInterface) {
// If a new revision is created, save the current user as
// revision author.
$entity->setRevisionUserId($this->currentUser()->id());
$entity->setRevisionCreationTime($this->time->getRequestTime());
}
}
return $entity;
}
/**
* {@inheritdoc}
Alex Pott
committed
*
* Button-level validation handlers are highly discouraged for entity forms,
* as they will prevent entity validation from running. If the entity is going
* to be saved during the form submission, this method should be manually
* invoked from the button-level validation handler, otherwise an exception
* will be thrown.
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->buildEntity($form, $form_state);
$violations = $entity->validate();
// Remove violations of inaccessible fields and not edited fields.
$violations
->filterByFieldAccess($this->currentUser())
->filterByFields(array_diff(array_keys($entity->getFieldDefinitions()), $this->getEditedFieldNames($form_state)));
$this->flagViolations($violations, $form, $form_state);
// The entity was validated.
$entity->setValidationRequired(FALSE);
$form_state->setTemporaryValue('entity_validated', TRUE);
Alex Pott
committed
return $entity;
}
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/**
* Gets the names of all fields edited in the form.
*
* If the entity form customly adds some fields to the form (i.e. without
* using the form display), it needs to add its fields here and override
* flagViolations() for displaying the violations.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return string[]
* An array of field names.
*/
protected function getEditedFieldNames(FormStateInterface $form_state) {
return array_keys($this->getFormDisplay($form_state)->getComponents());
}
/**
* Flags violations for the current form.
*
* If the entity form customly adds some fields to the form (i.e. without
* using the form display), it needs to add its fields to array returned by
* getEditedFieldNames() and overwrite this method in order to show any
* violations for those fields; e.g.:
* @code
* foreach ($violations->getByField('name') as $violation) {
* $form_state->setErrorByName('name', $violation->getMessage());
* }
* parent::flagViolations($violations, $form, $form_state);
* @endcode
*
* @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
* The violations to flag.
* @param array $form
* A nested array of form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
// Flag entity level violations.
foreach ($violations->getEntityViolations() as $violation) {
/** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
$form_state->setErrorByName('', $violation->getMessage());
}
// Let the form display flag violations of its fields.
$this->getFormDisplay($form_state)->flagWidgetsErrorsFromViolations($violations, $form, $form_state);
}
/**
* Initializes the form state and the entity before the first form build.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
Dries Buytaert
committed
protected function init(FormStateInterface $form_state) {
// Ensure we act on the translation object corresponding to the current form
// language.
$this->initFormLangcodes($form_state);
$langcode = $this->getFormLangcode($form_state);
$this->entity = $this->entity->hasTranslation($langcode) ? $this->entity->getTranslation($langcode) : $this->entity->addTranslation($langcode);
$form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation());
$this->setFormDisplay($form_display, $form_state);
parent::init($form_state);
}
/**
* Initializes form language code values.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function initFormLangcodes(FormStateInterface $form_state) {
// Store the entity default language to allow checking whether the form is
// dealing with the original entity or a translation.
if (!$form_state->has('entity_default_langcode')) {
$form_state->set('entity_default_langcode', $this->entity->getUntranslated()->language()->getId());
}
// This value might have been explicitly populated to work with a particular
// entity translation. If not we fall back to the most proper language based
// on contextual information.
Angie Byron
committed
if (!$form_state->has('langcode')) {
// Imply a 'view' operation to ensure users edit entities in the same
// language they are displayed. This allows to keep contextual editing
// working also for multilingual entities.
$form_state->set('langcode', $this->entityManager->getTranslationFromContext($this->entity)->language()->getId());
}
/**
* {@inheritdoc}
*/
public function getFormLangcode(FormStateInterface $form_state) {
$this->initFormLangcodes($form_state);
Angie Byron
committed
return $form_state->get('langcode');
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
public function isDefaultFormLangcode(FormStateInterface $form_state) {
$this->initFormLangcodes($form_state);
return $form_state->get('langcode') == $form_state->get('entity_default_langcode');
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
// First, extract values from widgets.
$extracted = $this->getFormDisplay($form_state)->extractFormValues($entity, $form, $form_state);
// Then extract the values of fields that are not rendered through widgets,
// by simply copying from top-level form values. This leaves the fields
// that are not being edited within this form untouched.
Alex Pott
committed
foreach ($form_state->getValues() as $name => $values) {
if ($entity->hasField($name) && !isset($extracted[$name])) {
$entity->set($name, $values);
}
}
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
public function getFormDisplay(FormStateInterface $form_state) {
Angie Byron
committed
return $form_state->get('form_display');
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state) {
Angie Byron
committed
$form_state->set('form_display', $form_display);
return $this;
Alex Pott
committed
/**
* Updates the form language to reflect any change to the entity language.
*
* There are use cases for modules to act both before and after form language
* being updated, thus the update is performed through an entity builder
* callback, which allows to support both cases.
*
* @param string $entity_type_id
* The entity type identifier.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity updated with the submitted values.
* @param array $form
* The complete form array.
Alex Pott
committed
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @see \Drupal\Core\Entity\ContentEntityForm::form()
Alex Pott
committed
*/
public function updateFormLangcode($entity_type_id, EntityInterface $entity, array $form, FormStateInterface $form_state) {
Alex Pott
committed
// Update the form language as it might have changed.
if ($this->isDefaultFormLangcode($form_state)) {
$langcode = $entity->language()->getId();
$form_state->set('langcode', $langcode);
Alex Pott
committed
}
}
catch
committed
/**
* Updates the changed time of the entity.
*
* Applies only if the entity implements the EntityChangedInterface.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity updated with the submitted values.
*/
public function updateChangedTime(EntityInterface $entity) {
if ($entity->getEntityType()->isSubclassOf(EntityChangedInterface::class)) {
$entity->setChangedTime($this->time->getRequestTime());
catch
committed
}
}
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
/**
* Add revision form fields if the entity enabled the UI.
*
* @param array $form
* An associative array containing the structure of the form.
*/
protected function addRevisionableFormFields(array &$form) {
$entity_type = $this->entity->getEntityType();
$new_revision_default = $this->getNewRevisionDefault();
// Add a log field if the "Create new revision" option is checked, or if the
// current user has the ability to check that option.
$form['revision_information'] = [
'#type' => 'details',
'#title' => $this->t('Revision information'),
// Open by default when "Create new revision" is checked.
'#open' => $new_revision_default,
'#group' => 'advanced',
'#weight' => 20,
'#access' => $new_revision_default || $this->entity->get($entity_type->getKey('revision'))->access('update'),
'#optional' => TRUE,
'#attributes' => [
'class' => ['entity-content-form-revision-information'],
],
'#attached' => [
'library' => ['core/drupal.entity-form'],
],
];
$form['revision'] = [
'#type' => 'checkbox',
'#title' => $this->t('Create new revision'),
'#default_value' => $new_revision_default,
'#access' => !$this->entity->isNew() && $this->entity->get($entity_type->getKey('revision'))->access('update'),
'#group' => 'revision_information',
];
if (isset($form['revision_log'])) {
$form['revision_log'] += [
'#group' => 'revision_information',
'#states' => [
'visible' => [
':input[name="revision"]' => ['checked' => TRUE],
],
],
];
}
}
/**
* Should new revisions created on default.
*
* @return bool
* New revision on default.
*/
protected function getNewRevisionDefault() {
$new_revision_default = FALSE;
$bundle_entity = $this->getBundleEntity();
if ($bundle_entity instanceof RevisionableEntityBundleInterface) {
// Always use the default revision setting.
$new_revision_default = $bundle_entity->shouldCreateNewRevision();
}
return $new_revision_default;
}
/**
* Checks whether the revision form fields should be added to the form.
*
* @return bool
* TRUE if the form field should be added, FALSE otherwise.
*/
protected function showRevisionUi() {
return $this->entity->getEntityType()->showRevisionUi();
}