From f4a242f15463637805c3392c3eedf6f2b714923c Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Tue, 13 Dec 2016 15:45:12 +0000
Subject: [PATCH] Issue #2669802 by chr.fritsch, Berdir, seanB, bojanz,
 dawehner, yoroy, slashrsm, tstoeckler: Add a content entity form which allows
 to set revisions

---
 core/core.libraries.yml                       |   7 +
 .../Drupal/Core/Entity/ContentEntityForm.php  | 166 +++++++++++++++++-
 core/lib/Drupal/Core/Entity/EntityType.php    |  14 ++
 .../Core/Entity/EntityTypeInterface.php       |   8 +
 .../RevisionableEntityBundleInterface.php     |  20 +++
 .../block_content.js => misc/entity-form.js}  |  10 +-
 .../block_content/block_content.libraries.yml |   9 +-
 .../block_content/src/BlockContentForm.php    | 140 +--------------
 .../src/BlockContentTranslationHandler.php    |  18 --
 .../src/BlockContentTypeInterface.php         |  11 +-
 .../block_content/src/Entity/BlockContent.php |  10 +-
 .../src/ContentTranslationHandler.php         |  10 ++
 .../src/Form/MenuLinkContentForm.php          |   2 +-
 core/modules/node/node.js                     |  31 ----
 core/modules/node/node.libraries.yml          |   4 +-
 core/modules/node/src/Entity/Node.php         |   1 +
 core/modules/node/src/Entity/NodeType.php     |   7 +
 core/modules/node/src/NodeForm.php            |  83 +--------
 .../node/src/NodeTranslationHandler.php       |  10 --
 core/modules/node/src/NodeTypeInterface.php   |   9 +-
 .../src/Kernel/EntitySerializationTest.php    |  23 ++-
 .../src/Entity/EntityTestMulRev.php           |   1 +
 .../entity_test/src/Entity/EntityTestRev.php  |  14 +-
 .../tests/src/Unit/EntityViewsDataTest.php    |  15 ++
 .../Entity/EntityDefinitionUpdateTest.php     |   5 +-
 .../Core/Entity/EntityFieldTest.php           |   7 +
 26 files changed, 321 insertions(+), 314 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Entity/RevisionableEntityBundleInterface.php
 rename core/{modules/block_content/js/block_content.js => misc/entity-form.js} (82%)

diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index ce12db5202dc..38e963ed94a9 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -203,6 +203,13 @@ drupal.dropbutton:
     - core/drupalSettings
     - core/jquery.once
 
+drupal.entity-form:
+  version: VERSION
+  js:
+    misc/entity-form.js: {}
+  dependencies:
+    - core/drupal.form
+
 drupal.form:
   version: VERSION
   js:
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
index 29b0a9b7bf7c..e131e9be4eae 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -2,6 +2,7 @@
 
 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;
@@ -21,14 +22,42 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
    */
   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;
+
   /**
    * 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) {
+  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');
   }
 
   /**
@@ -36,15 +65,54 @@ public function __construct(EntityManagerInterface $entity_manager) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('entity.manager')
+      $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}
    */
   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
@@ -54,6 +122,11 @@ public function form(array $form, FormStateInterface $form_state) {
     $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);
+    }
+
     return $form;
   }
 
@@ -76,6 +149,17 @@ public function buildEntity(array $form, FormStateInterface $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;
   }
 
@@ -283,8 +367,84 @@ public function updateFormLangcode($entity_type_id, EntityInterface $entity, arr
    */
   public function updateChangedTime(EntityInterface $entity) {
     if ($entity->getEntityType()->isSubclassOf(EntityChangedInterface::class)) {
-      $entity->setChangedTime(REQUEST_TIME);
+      $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) {
+    $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();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index 89b83cf098ae..78fb2ffbc2bd 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -174,6 +174,13 @@ class EntityType implements EntityTypeInterface {
    */
   protected $translatable = FALSE;
 
+  /**
+   * Indicates whether the revision form fields should be added to the form.
+   *
+   * @var bool
+   */
+  protected $show_revision_ui = FALSE;
+
   /**
    * The human-readable name of the type.
    *
@@ -689,6 +696,13 @@ public function getBaseTable() {
     return $this->base_table;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function showRevisionUi() {
+    return $this->isRevisionable() && $this->show_revision_ui;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index c6336c950c58..60d60b563326 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -573,6 +573,14 @@ public function getBaseTable();
    */
   public function isTranslatable();
 
+  /**
+   * Indicates whether the revision form fields should be added to the form.
+   *
+   * @return bool
+   *   TRUE if the form field should be added, FALSE otherwise.
+   */
+  public function showRevisionUi();
+
   /**
    * Indicates whether entities of this type have revision support.
    *
diff --git a/core/lib/Drupal/Core/Entity/RevisionableEntityBundleInterface.php b/core/lib/Drupal/Core/Entity/RevisionableEntityBundleInterface.php
new file mode 100644
index 000000000000..caf44f20a3f4
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/RevisionableEntityBundleInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+
+/**
+ * Provides an interface defining a revisionable entity bundle.
+ */
+interface RevisionableEntityBundleInterface extends ConfigEntityInterface {
+
+  /**
+   * Gets whether a new revision should be created by default.
+   *
+   * @return bool
+   *   TRUE if a new revision should be created by default.
+   */
+  public function shouldCreateNewRevision();
+
+}
diff --git a/core/modules/block_content/js/block_content.js b/core/misc/entity-form.js
similarity index 82%
rename from core/modules/block_content/js/block_content.js
rename to core/misc/entity-form.js
index bbc0533b2a35..f86c416e9d9f 100644
--- a/core/modules/block_content/js/block_content.js
+++ b/core/misc/entity-form.js
@@ -8,20 +8,20 @@
   'use strict';
 
   /**
-   * Sets summaries about revision and translation of block content.
+   * Sets summaries about revision and translation of entities.
    *
    * @type {Drupal~behavior}
    *
    * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behaviour block content form tabs.
+   *   Attaches summary behaviour entity form tabs.
    *
    *   Specifically, it updates summaries to the revision information and the
    *   translation options.
    */
-  Drupal.behaviors.blockContentDetailsSummaries = {
+  Drupal.behaviors.entityContentDetailsSummaries = {
     attach: function (context) {
       var $context = $(context);
-      $context.find('.block-content-form-revision-information').drupalSetSummary(function (context) {
+      $context.find('.entity-content-form-revision-information').drupalSetSummary(function (context) {
         var $revisionContext = $(context);
         var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
 
@@ -36,7 +36,7 @@
         return Drupal.t('No revision');
       });
 
-      $context.find('fieldset.block-content-translation-options').drupalSetSummary(function (context) {
+      $context.find('details.entity-translation-options').drupalSetSummary(function (context) {
         var $translationContext = $(context);
         var translate;
         var $checkbox = $translationContext.find('.js-form-item-translation-translate input');
diff --git a/core/modules/block_content/block_content.libraries.yml b/core/modules/block_content/block_content.libraries.yml
index 5a1b5b6a799c..b9549d461087 100644
--- a/core/modules/block_content/block_content.libraries.yml
+++ b/core/modules/block_content/block_content.libraries.yml
@@ -1,8 +1,7 @@
+# Deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
+# This is just for BC and not used anymore. See https://www.drupal.org/node/2669802
 drupal.block_content:
   version: VERSION
-  js:
-    js/block_content.js: {}
+  js: {}
   dependencies:
-    - core/jquery
-    - core/drupal
-    - core/drupal.form
+    - core/drupal.entity-form
diff --git a/core/modules/block_content/src/BlockContentForm.php b/core/modules/block_content/src/BlockContentForm.php
index 8815f5652699..b3f50f7a952b 100644
--- a/core/modules/block_content/src/BlockContentForm.php
+++ b/core/modules/block_content/src/BlockContentForm.php
@@ -4,38 +4,13 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Entity\ContentEntityForm;
-use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Language\LanguageManagerInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Form handler for the custom block edit forms.
  */
 class BlockContentForm extends ContentEntityForm {
 
-  /**
-   * The custom block storage.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected $blockContentStorage;
-
-  /**
-   * The custom block type storage.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected $blockContentTypeStorage;
-
-  /**
-   * The language manager.
-   *
-   * @var \Drupal\Core\Language\LanguageManagerInterface
-   */
-  protected $languageManager;
-
   /**
    * The block content entity.
    *
@@ -43,63 +18,13 @@ class BlockContentForm extends ContentEntityForm {
    */
   protected $entity;
 
-  /**
-   * Constructs a BlockContentForm object.
-   *
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
-   * @param \Drupal\Core\Entity\EntityStorageInterface $block_content_storage
-   *   The custom block storage.
-   * @param \Drupal\Core\Entity\EntityStorageInterface $block_content_type_storage
-   *   The custom block type storage.
-   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
-   *   The language manager.
-   */
-  public function __construct(EntityManagerInterface $entity_manager, EntityStorageInterface $block_content_storage, EntityStorageInterface $block_content_type_storage, LanguageManagerInterface $language_manager) {
-    parent::__construct($entity_manager);
-    $this->blockContentStorage = $block_content_storage;
-    $this->blockContentTypeStorage = $block_content_type_storage;
-    $this->languageManager = $language_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    $entity_manager = $container->get('entity.manager');
-    return new static(
-      $entity_manager,
-      $entity_manager->getStorage('block_content'),
-      $entity_manager->getStorage('block_content_type'),
-      $container->get('language_manager')
-    );
-  }
-
-  /**
-   * Overrides \Drupal\Core\Entity\EntityForm::prepareEntity().
-   *
-   * Prepares the custom block object.
-   *
-   * Fills in a few default values, and then invokes
-   * hook_block_content_prepare() on all modules.
-   */
-  protected function prepareEntity() {
-    $block = $this->entity;
-    // Set up default values, if required.
-    $block_type = $this->blockContentTypeStorage->load($block->bundle());
-    if (!$block->isNew()) {
-      $block->setRevisionLogMessage(NULL);
-    }
-    // Always use the default revision setting.
-    $block->setNewRevision($block_type->shouldCreateNewRevision());
-  }
-
   /**
    * {@inheritdoc}
    */
   public function form(array $form, FormStateInterface $form_state) {
     $block = $this->entity;
-    $account = $this->currentUser();
+
+    $form = parent::form($form, $form_state);
 
     if ($this->operation == 'edit') {
       $form['#title'] = $this->t('Edit custom block %label', array('%label' => $block->label()));
@@ -109,56 +34,7 @@ public function form(array $form, FormStateInterface $form_state) {
     // names.
     $form['#attributes']['class'][0] = 'block-' . Html::getClass($block->bundle()) . '-form';
 
-    $form['advanced'] = array(
-      '#type' => 'vertical_tabs',
-      '#weight' => 99,
-    );
-
-    // 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'] = array(
-      '#type' => 'details',
-      '#title' => $this->t('Revision information'),
-      // Open by default when "Create new revision" is checked.
-      '#open' => $block->isNewRevision(),
-      '#group' => 'advanced',
-      '#attributes' => array(
-        'class' => array('block-content-form-revision-information'),
-      ),
-      '#attached' => array(
-        'library' => array('block_content/drupal.block_content'),
-      ),
-      '#weight' => 20,
-      '#access' => $block->isNewRevision() || $account->hasPermission('administer blocks'),
-    );
-
-    $form['revision_information']['revision'] = array(
-      '#type' => 'checkbox',
-      '#title' => $this->t('Create new revision'),
-      '#default_value' => $block->isNewRevision(),
-      '#access' => $account->hasPermission('administer blocks'),
-    );
-
-    // Check the revision log checkbox when the log textarea is filled in.
-    // This must not happen if "Create new revision" is enabled by default,
-    // since the state would auto-disable the checkbox otherwise.
-    if (!$block->isNewRevision()) {
-      $form['revision_information']['revision']['#states'] = array(
-        'checked' => array(
-          'textarea[name="revision_log"]' => array('empty' => FALSE),
-        ),
-      );
-    }
-
-    $form['revision_information']['revision_log'] = array(
-      '#type' => 'textarea',
-      '#title' => $this->t('Revision log message'),
-      '#rows' => 4,
-      '#default_value' => $block->getRevisionLog(),
-      '#description' => $this->t('Briefly describe the changes you have made.'),
-    );
-
-    return parent::form($form, $form_state, $block);
+    return $form;
   }
 
   /**
@@ -167,19 +43,11 @@ public function form(array $form, FormStateInterface $form_state) {
   public function save(array $form, FormStateInterface $form_state) {
     $block = $this->entity;
 
-    // Save as a new revision if requested to do so.
-    if (!$form_state->isValueEmpty('revision')) {
-      $block->setNewRevision();
-      // If a new revision is created, save the current user as revision author.
-      $block->setRevisionCreationTime(REQUEST_TIME);
-      $block->setRevisionUserId(\Drupal::currentUser()->id());
-    }
-
     $insert = $block->isNew();
     $block->save();
     $context = array('@type' => $block->bundle(), '%info' => $block->label());
     $logger = $this->logger('block_content');
-    $block_type = $this->blockContentTypeStorage->load($block->bundle());
+    $block_type = $this->getBundleEntity();
     $t_args = array('@type' => $block_type->label(), '%info' => $block->label());
 
     if ($insert) {
diff --git a/core/modules/block_content/src/BlockContentTranslationHandler.php b/core/modules/block_content/src/BlockContentTranslationHandler.php
index 781ba1b6dc39..b446cde8c519 100644
--- a/core/modules/block_content/src/BlockContentTranslationHandler.php
+++ b/core/modules/block_content/src/BlockContentTranslationHandler.php
@@ -5,30 +5,12 @@
 use Drupal\block_content\Entity\BlockContentType;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\content_translation\ContentTranslationHandler;
-use Drupal\Core\Form\FormStateInterface;
 
 /**
  * Defines the translation handler for custom blocks.
  */
 class BlockContentTranslationHandler extends ContentTranslationHandler {
 
-  /**
-   * {@inheritdoc}
-   */
-  public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
-    parent::entityFormAlter($form, $form_state, $entity);
-    // Move the translation fieldset to a vertical tab.
-    if (isset($form['translation'])) {
-      $form['translation'] += array(
-        '#group' => 'additional_settings',
-        '#weight' => 100,
-        '#attributes' => array(
-          'class' => array('block-content-translation-options'),
-        ),
-      );
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/block_content/src/BlockContentTypeInterface.php b/core/modules/block_content/src/BlockContentTypeInterface.php
index 9229dab8a81c..da3864e1a080 100644
--- a/core/modules/block_content/src/BlockContentTypeInterface.php
+++ b/core/modules/block_content/src/BlockContentTypeInterface.php
@@ -3,11 +3,12 @@
 namespace Drupal\block_content;
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\RevisionableEntityBundleInterface;
 
 /**
  * Provides an interface defining a custom block type entity.
  */
-interface BlockContentTypeInterface extends ConfigEntityInterface {
+interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface {
 
   /**
    * Returns the description of the block type.
@@ -17,12 +18,4 @@ interface BlockContentTypeInterface extends ConfigEntityInterface {
    */
   public function getDescription();
 
-  /**
-   * Returns whether a new revision should be created by default.
-   *
-   * @return bool
-   *   TRUE if a new revision should be created by default.
-   */
-  public function shouldCreateNewRevision();
-
 }
diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php
index 13af342934b4..51ae6f679c4a 100644
--- a/core/modules/block_content/src/Entity/BlockContent.php
+++ b/core/modules/block_content/src/Entity/BlockContent.php
@@ -35,6 +35,7 @@
  *   base_table = "block_content",
  *   revision_table = "block_content_revision",
  *   data_table = "block_content_field_data",
+ *   show_revision_ui = TRUE,
  *   links = {
  *     "canonical" = "/block/{block_content}",
  *     "delete-form" = "/block/{block_content}/delete",
@@ -182,7 +183,14 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['revision_log'] = BaseFieldDefinition::create('string_long')
       ->setLabel(t('Revision log message'))
       ->setDescription(t('The log entry explaining the changes in this revision.'))
-      ->setRevisionable(TRUE);
+      ->setRevisionable(TRUE)
+      ->setDisplayOptions('form', array(
+        'type' => 'string_textarea',
+        'weight' => 25,
+        'settings' => array(
+          'rows' => 4,
+        ),
+      ));
 
     $fields['changed'] = BaseFieldDefinition::create('changed')
       ->setLabel(t('Changed'))
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 6fd225481570..799512521342 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -384,6 +384,16 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
         '#multilingual' => TRUE,
       );
 
+      if (isset($form['advanced'])) {
+        $form['content_translation'] += array(
+          '#group' => 'advanced',
+          '#weight' => 100,
+          '#attributes' => array(
+            'class' => array('entity-translation-options'),
+          ),
+        );
+      }
+
       // A new translation is enabled by default.
       $metadata = $this->manager->getTranslationMetadata($entity);
       $status = $new_translation || $metadata->isPublished();
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
index ed372a926b6c..029ca49bcf1a 100644
--- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -49,7 +49,7 @@ class MenuLinkContentForm extends ContentEntityForm {
    *   The path validator.
    */
   public function __construct(EntityManagerInterface $entity_manager, MenuParentFormSelectorInterface $menu_parent_selector, LanguageManagerInterface $language_manager, PathValidatorInterface $path_validator) {
-    parent::__construct($entity_manager, $language_manager);
+    parent::__construct($entity_manager);
     $this->menuParentSelector = $menu_parent_selector;
     $this->pathValidator = $path_validator;
   }
diff --git a/core/modules/node/node.js b/core/modules/node/node.js
index 98af6fdc16bb..086263d2a8e4 100644
--- a/core/modules/node/node.js
+++ b/core/modules/node/node.js
@@ -18,21 +18,6 @@
   Drupal.behaviors.nodeDetailsSummaries = {
     attach: function (context) {
       var $context = $(context);
-      $context.find('.node-form-revision-information').drupalSetSummary(function (context) {
-        var $revisionContext = $(context);
-        var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
-
-          // Return 'New revision' if the 'Create new revision' checkbox is
-          // checked, or if the checkbox doesn't exist, but the revision log does.
-          // For users without the "Administer content" permission the checkbox
-          // won't appear, but the revision log will if the content type is set to
-          // auto-revision.
-        if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $revisionContext.find('.js-form-item-revision-log textarea').length)) {
-          return Drupal.t('New revision');
-        }
-
-        return Drupal.t('No revision');
-      });
 
       $context.find('.node-form-author').drupalSetSummary(function (context) {
         var $authorContext = $(context);
@@ -64,22 +49,6 @@
           return Drupal.t('Not promoted');
         }
       });
-
-      $context.find('fieldset.node-translation-options').drupalSetSummary(function (context) {
-        var $translationContext = $(context);
-        var translate;
-        var $checkbox = $translationContext.find('.js-form-item-translation-translate input');
-
-        if ($checkbox.size()) {
-          translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
-        }
-        else {
-          $checkbox = $translationContext.find('.js-form-item-translation-retranslate input');
-          translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
-        }
-
-        return translate;
-      });
     }
   };
 
diff --git a/core/modules/node/node.libraries.yml b/core/modules/node/node.libraries.yml
index 22c93ac7f35d..59947a237de6 100644
--- a/core/modules/node/node.libraries.yml
+++ b/core/modules/node/node.libraries.yml
@@ -6,10 +6,8 @@ drupal.node:
   js:
     node.js: {}
   dependencies:
-    - core/jquery
-    - core/drupal
+    - core/drupal.entity-form
     - core/drupalSettings
-    - core/drupal.form
 
 drupal.node.preview:
   version: VERSION
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index f034957988ee..68afc8360bcd 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -46,6 +46,7 @@
  *   data_table = "node_field_data",
  *   revision_table = "node_revision",
  *   revision_data_table = "node_field_revision",
+ *   show_revision_ui = TRUE,
  *   translatable = TRUE,
  *   list_cache_contexts = { "user.node_grants:view" },
  *   entity_keys = {
diff --git a/core/modules/node/src/Entity/NodeType.php b/core/modules/node/src/Entity/NodeType.php
index 91d8a9095999..23f24a468bb5 100644
--- a/core/modules/node/src/Entity/NodeType.php
+++ b/core/modules/node/src/Entity/NodeType.php
@@ -205,4 +205,11 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
     $storage->resetCache(array_keys($entities));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function shouldCreateNewRevision() {
+    return $this->isNewRevision();
+  }
+
 }
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 282803009509..41939234bc7e 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -26,7 +26,7 @@ class NodeForm extends ContentEntityForm {
   protected $hasBeenPreviewed = FALSE;
 
   /**
-   * Constructs a ContentEntityForm object.
+   * Constructs a NodeForm object.
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
@@ -48,19 +48,6 @@ public static function create(ContainerInterface $container) {
     );
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function prepareEntity() {
-    /** @var \Drupal\node\NodeInterface $node */
-    $node = $this->entity;
-
-    if (!$node->isNew()) {
-      // Remove the revision log message from the original node entity.
-      $node->revision_log = NULL;
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -102,55 +89,15 @@ public function form(array $form, FormStateInterface $form_state) {
       $form['#title'] = $this->t('<em>Edit @type</em> @title', array('@type' => node_get_type_label($node), '@title' => $node->label()));
     }
 
-    $current_user = $this->currentUser();
-
     // Changed must be sent to the client, for later overwrite error checking.
     $form['changed'] = array(
       '#type' => 'hidden',
       '#default_value' => $node->getChangedTime(),
     );
 
-    $form['advanced'] = array(
-      '#type' => 'vertical_tabs',
-      '#attributes' => array('class' => array('entity-meta')),
-      '#weight' => 99,
-    );
     $form = parent::form($form, $form_state);
 
-    // Add a revision_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'] = array(
-      '#type' => 'details',
-      '#group' => 'advanced',
-      '#title' => t('Revision information'),
-      // Open by default when "Create new revision" is checked.
-      '#open' => $node->isNewRevision(),
-      '#attributes' => array(
-        'class' => array('node-form-revision-information'),
-      ),
-      '#attached' => array(
-        'library' => array('node/drupal.node'),
-      ),
-      '#weight' => 20,
-      '#optional' => TRUE,
-    );
-
-    $form['revision'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Create new revision'),
-      '#default_value' => $node->type->entity->isNewRevision(),
-      '#access' => $current_user->hasPermission('administer nodes') && !$node->isNew(),
-      '#group' => 'revision_information',
-    );
-
-    $form['revision_log'] += array(
-      '#states' => array(
-        'visible' => array(
-          ':input[name="revision"]' => array('checked' => TRUE),
-        ),
-      ),
-      '#group' => 'revision_information',
-    );
+    $form['advanced']['#attributes']['class'][] = 'entity-meta';
 
     // Node author information for administrators.
     $form['author'] = array(
@@ -303,32 +250,6 @@ protected function actions(array $form, FormStateInterface $form_state) {
     return $element;
   }
 
-  /**
-   * {@inheritdoc}
-   *
-   * Updates the node object by processing the submitted values.
-   *
-   * This function can be called by a "Next" button of a wizard to update the
-   * form state's entity with the current step's values before proceeding to the
-   * next step.
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    // Build the node object from the submitted values.
-    parent::submitForm($form, $form_state);
-    $node = $this->entity;
-
-    // Save as a new revision if requested to do so.
-    if (!$form_state->isValueEmpty('revision') && $form_state->getValue('revision') != FALSE) {
-      $node->setNewRevision();
-      // If a new revision is created, save the current user as revision author.
-      $node->setRevisionCreationTime(REQUEST_TIME);
-      $node->setRevisionUserId(\Drupal::currentUser()->id());
-    }
-    else {
-      $node->setNewRevision(FALSE);
-    }
-  }
-
   /**
    * Form submission handler for the 'preview' action.
    *
diff --git a/core/modules/node/src/NodeTranslationHandler.php b/core/modules/node/src/NodeTranslationHandler.php
index 9c9741e1c3ed..2f6d2abfd938 100644
--- a/core/modules/node/src/NodeTranslationHandler.php
+++ b/core/modules/node/src/NodeTranslationHandler.php
@@ -17,17 +17,7 @@ class NodeTranslationHandler extends ContentTranslationHandler {
   public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
     parent::entityFormAlter($form, $form_state, $entity);
 
-    // Move the translation fieldset to a vertical tab.
     if (isset($form['content_translation'])) {
-      $form['content_translation'] += array(
-        '#group' => 'advanced',
-        '#attributes' => array(
-          'class' => array('node-translation-options'),
-        ),
-      );
-
-      $form['content_translation']['#weight'] = 100;
-
       // We do not need to show these values on node forms: they inherit the
       // basic node property values.
       $form['content_translation']['status']['#access'] = FALSE;
diff --git a/core/modules/node/src/NodeTypeInterface.php b/core/modules/node/src/NodeTypeInterface.php
index c034ffbee04d..df4831e56632 100644
--- a/core/modules/node/src/NodeTypeInterface.php
+++ b/core/modules/node/src/NodeTypeInterface.php
@@ -3,11 +3,12 @@
 namespace Drupal\node;
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\RevisionableEntityBundleInterface;
 
 /**
  * Provides an interface defining a node type entity.
  */
-interface NodeTypeInterface extends ConfigEntityInterface {
+interface NodeTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface {
 
   /**
    * Determines whether the node type is locked.
@@ -22,13 +23,17 @@ public function isLocked();
    *
    * @return bool
    *   TRUE if a new revision should be created by default.
+   *
+   * @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use
+   *   Drupal\Core\Entity\RevisionableEntityBundleInterface::shouldCreateNewRevision()
+   *   instead.
    */
   public function isNewRevision();
 
   /**
    * Sets whether a new revision should be created by default.
    *
-   * @param bool $new_revision_
+   * @param bool $new_revision
    *   TRUE if a new revision should be created by default.
    */
   public function setNewRevision($new_revision);
diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
index 0f84d3d79a21..2f5c20a1cd59 100644
--- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
+++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
@@ -29,7 +29,7 @@ class EntitySerializationTest extends NormalizerTestBase {
   /**
    * The test entity.
    *
-   * @var \Drupal\Core\Entity\ContentEntityBase
+   * @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface
    */
   protected $entity;
 
@@ -76,6 +76,10 @@ protected function setUp() {
         'value' => $this->randomMachineName(),
         'format' => 'full_html',
       ),
+      'revision_log_message' => array(
+        'value' => 'Serialization revision message',
+      ),
+      'revision_user' => $this->user->id(),
     );
     $this->entity = EntityTestMulRev::create($this->values);
     $this->entity->save();
@@ -123,6 +127,20 @@ public function testNormalize() {
         array('value' => TRUE),
       ),
       'non_rev_field' => array(),
+      'revision_created' => array(
+        array('value' => $this->entity->getRevisionCreationTime()),
+      ),
+      'revision_user' => array(
+        array(
+          'target_id' => $this->user->id(),
+          'target_type' => $this->user->getEntityTypeId(),
+          'target_uuid' => $this->user->uuid(),
+          'url' => $this->user->url(),
+        ),
+      ),
+      'revision_log_message' => array(
+        array('value' => $this->values['revision_log_message']['value']),
+      ),
       'field_test_text' => array(
         array(
           'value' => $this->values['field_test_text']['value'],
@@ -192,6 +210,9 @@ public function testSerialize() {
       'revision_id' => '<revision_id><value>' . $this->entity->getRevisionId() . '</value></revision_id>',
       'default_langcode' => '<default_langcode><value>1</value></default_langcode>',
       'non_rev_field' => '<non_rev_field/>',
+      'revision_created' => '<revision_created><value>' . $this->entity->getRevisionCreationTime() . '</value></revision_created>',
+      'revision_user' => '<revision_user><target_id>' . $this->user->id() . '</target_id><target_type>' . $this->user->getEntityTypeId() . '</target_type><target_uuid>' . $this->user->uuid() . '</target_uuid><url>' . $this->user->url() . '</url></revision_user>',
+      'revision_log_message' => '<revision_log_message><value>' . $this->values['revision_log_message']['value'] . '</value></revision_log_message>',
       'field_test_text' => '<field_test_text><value>' . $this->values['field_test_text']['value'] . '</value><format>' . $this->values['field_test_text']['format'] . '</format></field_test_text>',
     );
     // Sort it in the same order as normalised.
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index 0ee28bf032dc..f01675f60256 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -27,6 +27,7 @@
  *   revision_data_table = "entity_test_mulrev_property_revision",
  *   admin_permission = "administer entity_test content",
  *   translatable = TRUE,
+ *   show_revision_ui = TRUE,
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
index 47035ea84f76..49c5aca2d553 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
@@ -3,6 +3,8 @@
 namespace Drupal\entity_test\Entity;
 
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\RevisionLogEntityTrait;
+use Drupal\Core\Entity\RevisionLogInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
 
 /**
@@ -28,6 +30,7 @@
  *   base_table = "entity_test_rev",
  *   revision_table = "entity_test_rev_revision",
  *   admin_permission = "administer entity_test content",
+ *   show_revision_ui = TRUE,
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
@@ -45,7 +48,9 @@
  *   }
  * )
  */
-class EntityTestRev extends EntityTest {
+class EntityTestRev extends EntityTest implements RevisionLogInterface {
+
+  use RevisionLogEntityTrait;
 
   /**
    * {@inheritdoc}
@@ -53,13 +58,8 @@ class EntityTestRev extends EntityTest {
   public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields = parent::baseFieldDefinitions($entity_type);
 
-    $fields['revision_id'] = BaseFieldDefinition::create('integer')
-      ->setLabel(t('Revision ID'))
-      ->setDescription(t('The version id of the test entity.'))
-      ->setReadOnly(TRUE)
-      ->setSetting('unsigned', TRUE);
+    $fields += static::revisionLogBaseFieldDefinitions($entity_type);
 
-    $fields['langcode']->setRevisionable(TRUE);
     $fields['name']->setRevisionable(TRUE);
     $fields['user_id']->setRevisionable(TRUE);
 
diff --git a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
index 60ba8e577084..acb84e5f1ef2 100644
--- a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
+++ b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
@@ -89,6 +89,12 @@ protected function setUp() {
     $typed_data_manager->expects($this->any())
         ->method('createDataDefinition')
         ->willReturn($this->getMock('Drupal\Core\TypedData\DataDefinitionInterface'));
+
+    $typed_data_manager->expects($this->any())
+      ->method('getDefinition')
+      ->with($this->equalTo('field_item:string_long'))
+      ->willReturn(array('class' => '\Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem'));
+
     $this->baseEntityType = new TestEntityType([
       'base_table' => 'entity_test',
       'id' => 'entity_test',
@@ -1107,3 +1113,12 @@ function t($string, array $args = []) {
     return strtr($string, $args);
   }
 }
+
+
+namespace Drupal\Core\Entity;
+
+if (!function_exists('t')) {
+  function t($string, array $args = []) {
+    return strtr($string, $args);
+  }
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
index 23cf0340c541..e49ee7ea92d6 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
@@ -102,6 +102,9 @@ public function testEntityTypeUpdateWithoutData() {
     $expected = array(
       'entity_test_update' => array(
         t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
+        // The revision key is now defined, so the revision field needs to be
+        // created.
+        t('The %field_name field needs to be installed.', ['%field_name' => 'Revision ID']),
       ),
     );
     $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.');
@@ -797,7 +800,7 @@ public function testBaseFieldEntityKeyUpdateWithExistingData() {
   /**
    * Check that field schema is correctly handled with long-named fields.
    */
-  function testLongNameFieldIndexes() {
+  public function testLongNameFieldIndexes() {
     $this->addLongNameBaseField();
     $entity_type_id = 'entity_test_update';
     $entity_type = $this->entityManager->getDefinition($entity_type_id);
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
index 56af88810c9b..5dc1a948d35f 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\KernelTests\Core\Entity;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\RevisionLogInterface;
 use Drupal\Core\Entity\TypedData\EntityDataDefinition;
 use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -572,6 +573,12 @@ protected function doTestDataStructureInterfaces($entity_type) {
       // Field format.
       NULL,
     );
+
+    if ($entity instanceof RevisionLogInterface) {
+      // Adding empty string for revision message.
+      $target_strings[] = '';
+    }
+
     asort($strings);
     asort($target_strings);
     $this->assertEqual(array_values($strings), array_values($target_strings), format_string('%entity_type: All contained strings found.', array('%entity_type' => $entity_type)));
-- 
GitLab