Skip to content
Snippets Groups Projects
Select Git revision
  • e839a9726a0f9abb4e6776b56eabdce546a961ce
  • 11.x default protected
  • 10.5.x protected
  • 11.2.x protected
  • 10.6.x protected
  • 11.1.x protected
  • 10.4.x protected
  • 11.0.x protected
  • 10.3.x protected
  • 7.x protected
  • 10.2.x protected
  • 10.1.x protected
  • 9.5.x protected
  • 10.0.x protected
  • 9.4.x protected
  • 9.3.x protected
  • 9.2.x protected
  • 9.1.x protected
  • 8.9.x protected
  • 9.0.x protected
  • 8.8.x protected
  • 10.5.1 protected
  • 11.2.2 protected
  • 11.2.1 protected
  • 11.2.0 protected
  • 10.5.0 protected
  • 11.2.0-rc2 protected
  • 10.5.0-rc1 protected
  • 11.2.0-rc1 protected
  • 10.4.8 protected
  • 11.1.8 protected
  • 10.5.0-beta1 protected
  • 11.2.0-beta1 protected
  • 11.2.0-alpha1 protected
  • 10.4.7 protected
  • 11.1.7 protected
  • 10.4.6 protected
  • 11.1.6 protected
  • 10.3.14 protected
  • 10.4.5 protected
  • 11.0.13 protected
41 results

ContentEntityForm.php

Blame
  • Nathaniel Catchpole's avatar
    Issue #2894261 by alexpott, Berdir, Sam152, deepakaryan1988, larowlan,...
    catch authored
    Issue #2894261 by alexpott, Berdir, Sam152, deepakaryan1988, larowlan, andypost: Deprecated service entity.manager needs to be replaced with entity_type.manager in ContentEntityForm
    
    (cherry picked from commit 077e2307)
    f78fb96a
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    ContentEntityForm.php 16.77 KiB
    <?php
    
    namespace Drupal\Core\Entity;
    
    use Drupal\Component\Datetime\TimeInterface;
    use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
    use Drupal\Core\Entity\Entity\EntityFormDisplay;
    use Drupal\Core\Form\FormStateInterface;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    
    /**
     * Entity form variant for content entity types.
     *
     * @see \Drupal\Core\ContentEntityBase
     */
    class ContentEntityForm extends EntityForm implements ContentEntityFormInterface {
    
      /**
       * 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;
    
      /**
       * The entity repository service.
       *
       * @var \Drupal\Core\Entity\EntityRepositoryInterface
       */
      protected $entityRepository;
    
      /**
       * Constructs a ContentEntityForm object.
       *
       * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
       *   The entity repository service.
       * @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(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
        if ($entity_repository instanceof EntityManagerInterface) {
          @trigger_error('Passing the entity.manager service to ContentEntityForm::__construct() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Pass the entity.repository service instead. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
          $this->entityManager = $entity_repository;
        }
        $this->entityRepository = $entity_repository;
        $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.repository'),
          $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 instanceof RevisionLogInterface) {
          $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}
       */
      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.
        $form['#entity_builders']['update_form_langcode'] = '::updateFormLangcode';
    
        if ($this->showRevisionUi()) {
          $this->addRevisionableFormFields($form);
        }
    
        $form['footer'] = [
          '#type' => 'container',
          '#weight' => 99,
          '#attributes' => [
            'class' => ['entity-content-form-footer'],
          ],
          '#optional' => TRUE,
        ];
    
        return $form;
      }
    
      /**
       * {@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}
       *
       * 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.
        $violations->filterByFieldAccess($this->currentUser());
    
        // In case a field-level submit button is clicked, for example the 'Add
        // another item' button for multi-value fields or the 'Upload' button for a
        // File or an Image field, make sure that we only keep violations for that
        // specific field.
        $edited_fields = [];
        if ($limit_validation_errors = $form_state->getLimitValidationErrors()) {
          foreach ($limit_validation_errors as $section) {
            $field_name = reset($section);
            if ($entity->hasField($field_name)) {
              $edited_fields[] = $field_name;
            }
          }
          $edited_fields = array_unique($edited_fields);
        }
        else {
          $edited_fields = $this->getEditedFieldNames($form_state);
        }
    
        // Remove violations for fields that are not edited.
        $violations->filterByFields(array_diff(array_keys($entity->getFieldDefinitions()), $edited_fields));
    
        $this->flagViolations($violations, $form, $form_state);
    
        // The entity was validated.
        $entity->setValidationRequired(FALSE);
        $form_state->setTemporaryValue('entity_validated', TRUE);
    
        return $entity;
      }
    
      /**
       * 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(str_replace('.', '][', $violation->getPropertyPath()), $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.
       */
      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.
        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->entityRepository->getTranslationFromContext($this->entity)->language()->getId());
        }
      }
    
      /**
       * {@inheritdoc}
       */
      public function getFormLangcode(FormStateInterface $form_state) {
        $this->initFormLangcodes($form_state);
        return $form_state->get('langcode');
      }
    
      /**
       * {@inheritdoc}
       */
      public function isDefaultFormLangcode(FormStateInterface $form_state) {
        $this->initFormLangcodes($form_state);
        return $form_state->get('langcode') == $form_state->get('entity_default_langcode');
      }
    
      /**
       * {@inheritdoc}
       */
      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.
        foreach ($form_state->getValues() as $name => $values) {
          if ($entity->hasField($name) && !isset($extracted[$name])) {
            $entity->set($name, $values);
          }
        }
      }
    
      /**
       * {@inheritdoc}
       */
      public function getFormDisplay(FormStateInterface $form_state) {
        return $form_state->get('form_display');
      }
    
      /**
       * {@inheritdoc}
       */
      public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state) {
        $form_state->set('form_display', $form_display);
        return $this;
      }
    
      /**
       * 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.
       * @param \Drupal\Core\Form\FormStateInterface $form_state
       *   The current state of the form.
       *
       * @see \Drupal\Core\Entity\ContentEntityForm::form()
       */
      public function updateFormLangcode($entity_type_id, EntityInterface $entity, array $form, FormStateInterface $form_state) {
        $langcode = $entity->language()->getId();
        $form_state->set('langcode', $langcode);
    
        // If this is the original entity language, also update the default
        // langcode.
        if ($langcode == $entity->getUntranslated()->language()->getId()) {
          $form_state->set('entity_default_langcode', $langcode);
        }
      }
    
      /**
       * 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 instanceof EntityChangedInterface) {
          $entity->setChangedTime($this->time->getRequestTime());
        }
      }
    
      /**
       * 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) {
        /** @var ContentEntityTypeInterface $entity_type */
        $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',
        ];
        // Get log message field's key from definition.
        $log_message_field = $entity_type->getRevisionMetadataKey('revision_log_message');
        if ($log_message_field && isset($form[$log_message_field])) {
          $form[$log_message_field] += [
            '#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();
      }
    
    }