diff --git a/core/lib/Drupal/Core/Entity/EntityPublishedTrait.php b/core/lib/Drupal/Core/Entity/EntityPublishedTrait.php
index 871aceb365..59fddd5d0e 100644
--- a/core/lib/Drupal/Core/Entity/EntityPublishedTrait.php
+++ b/core/lib/Drupal/Core/Entity/EntityPublishedTrait.php
@@ -33,8 +33,7 @@ public static function publishedBaseFieldDefinitions(EntityTypeInterface $entity
     }
 
     return [$entity_type->getKey('published') => BaseFieldDefinition::create('boolean')
-      ->setLabel(new TranslatableMarkup('Publishing status'))
-      ->setDescription(new TranslatableMarkup('A boolean indicating the published state.'))
+      ->setLabel(new TranslatableMarkup('Published'))
       ->setRevisionable(TRUE)
       ->setTranslatable(TRUE)
       ->setDefaultValue(TRUE)];
diff --git a/core/modules/book/config/optional/core.entity_form_display.node.book.default.yml b/core/modules/book/config/optional/core.entity_form_display.node.book.default.yml
index 58aba45d36..b1db6d18a7 100644
--- a/core/modules/book/config/optional/core.entity_form_display.node.book.default.yml
+++ b/core/modules/book/config/optional/core.entity_form_display.node.book.default.yml
@@ -33,6 +33,13 @@ content:
     weight: 15
     region: content
     third_party_settings: {  }
+  status:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 120
+    region: content
+    third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
diff --git a/core/modules/book/tests/src/Functional/BookTest.php b/core/modules/book/tests/src/Functional/BookTest.php
index 66edec91e0..e52e484e73 100644
--- a/core/modules/book/tests/src/Functional/BookTest.php
+++ b/core/modules/book/tests/src/Functional/BookTest.php
@@ -741,8 +741,8 @@ public function testBookNavigationBlockOnUnpublishedBook() {
     $this->drupalPlaceBlock('book_navigation', ['block_mode' => 'book pages']);
 
     // Unpublish book node.
-    $edit = [];
-    $this->drupalPostForm('node/' . $this->book->id() . '/edit', $edit, t('Save and unpublish'));
+    $edit = ['status[value]' => FALSE];
+    $this->drupalPostForm('node/' . $this->book->id() . '/edit', $edit, t('Save'));
 
     // Test node page.
     $this->drupalGet('node/' . $this->book->id());
diff --git a/core/modules/comment/tests/src/Functional/CommentStatusFieldAccessTest.php b/core/modules/comment/tests/src/Functional/CommentStatusFieldAccessTest.php
index 504b3bacf6..e208c7125d 100644
--- a/core/modules/comment/tests/src/Functional/CommentStatusFieldAccessTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentStatusFieldAccessTest.php
@@ -86,14 +86,14 @@ public function testCommentStatusFieldAccessStatus() {
     $assert->fieldNotExists('comment[0][status]');
     $this->submitForm([
       'title[0][value]' => 'Node 1',
-    ], t('Save and publish'));
+    ], t('Save'));
     $assert->fieldExists('subject[0][value]');
     $this->drupalLogin($this->commentAdmin);
     $this->drupalGet('node/add/article');
     $assert->fieldExists('comment[0][status]');
     $this->submitForm([
       'title[0][value]' => 'Node 2',
-    ], t('Save and publish'));
+    ], t('Save'));
     $assert->fieldExists('subject[0][value]');
   }
 
diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module
index 77d822be28..39ffc94c0a 100644
--- a/core/modules/content_moderation/content_moderation.module
+++ b/core/modules/content_moderation/content_moderation.module
@@ -14,6 +14,8 @@
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -176,6 +178,27 @@ function content_moderation_node_access(NodeInterface $node, $operation, Account
 }
 
 /**
+ * Implements hook_entity_field_access().
+ */
+function content_moderation_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
+  if ($items && $operation === 'edit') {
+    /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
+    $moderation_info = Drupal::service('content_moderation.moderation_information');
+
+    $entity_type = \Drupal::entityTypeManager()->getDefinition($field_definition->getTargetEntityTypeId());
+
+    $entity = $items->getEntity();
+
+    // Deny edit access to the published field if the entity is being moderated.
+    if ($entity_type->hasKey('published') && $moderation_info->isModeratedEntity($entity) && $entity->moderation_state && $field_definition->getName() == $entity_type->getKey('published')) {
+      return AccessResult::forbidden();
+    }
+  }
+
+  return AccessResult::neutral();
+}
+
+/**
  * Implements hook_theme().
  */
 function content_moderation_theme() {
diff --git a/core/modules/content_moderation/content_moderation.module.orig b/core/modules/content_moderation/content_moderation.module.orig
new file mode 100644
index 0000000000..77d822be28
--- /dev/null
+++ b/core/modules/content_moderation/content_moderation.module.orig
@@ -0,0 +1,236 @@
+<?php
+
+/**
+ * @file
+ * Contains content_moderation.module.
+ */
+
+use Drupal\content_moderation\EntityOperations;
+use Drupal\content_moderation\EntityTypeInfo;
+use Drupal\content_moderation\ContentPreprocess;
+use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublishNode;
+use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\workflows\WorkflowInterface;
+use Drupal\node\NodeInterface;
+use Drupal\node\Plugin\Action\PublishNode;
+use Drupal\node\Plugin\Action\UnpublishNode;
+use Drupal\workflows\Entity\Workflow;
+
+/**
+ * Implements hook_help().
+ */
+function content_moderation_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    // Main module help for the content_moderation module.
+    case 'help.page.content_moderation':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Content Moderation module provides moderation for content by applying workflows to content. For more information, see the <a href=":content_moderation">online documentation for the Content Moderation module</a>.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation']) . '</p>';
+      $output .= '<h3>' . t('Uses') . '</h3>';
+      $output .= '<dl>';
+      $output .= '<dt>' . t('Configuring workflows') . '</dt>';
+      $output .= '<dd>' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '</p>';
+      $output .= '<dt>' . t('Configure Content Moderation permissions') . '</dt>';
+      $output .= '<dd>' . t('Each transition is exposed as a permission. If a user has the permission for a transition, then they can move that node from the start state to the end state') . '</p>';
+      $output .= '</dl>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_entity_base_field_info().
+ */
+function content_moderation_entity_base_field_info(EntityTypeInterface $entity_type) {
+  return \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityTypeInfo::class)
+    ->entityBaseFieldInfo($entity_type);
+}
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function content_moderation_entity_type_alter(array &$entity_types) {
+  \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityTypeInfo::class)
+    ->entityTypeAlter($entity_types);
+}
+
+/**
+ * Implements hook_entity_operation().
+ */
+function content_moderation_entity_operation(EntityInterface $entity) {
+  return \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityTypeInfo::class)
+    ->entityOperation($entity);
+}
+
+/**
+ * Implements hook_entity_presave().
+ */
+function content_moderation_entity_presave(EntityInterface $entity) {
+  return \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityOperations::class)
+    ->entityPresave($entity);
+}
+
+/**
+ * Implements hook_entity_insert().
+ */
+function content_moderation_entity_insert(EntityInterface $entity) {
+  return \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityOperations::class)
+    ->entityInsert($entity);
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function content_moderation_entity_update(EntityInterface $entity) {
+  return \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityOperations::class)
+    ->entityUpdate($entity);
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function content_moderation_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityTypeInfo::class)
+    ->formAlter($form, $form_state, $form_id);
+}
+
+/**
+ * Implements hook_preprocess_HOOK().
+ *
+ * Many default node templates rely on $page to determine whether to output the
+ * node title as part of the node content.
+ */
+function content_moderation_preprocess_node(&$variables) {
+  \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(ContentPreprocess::class)
+    ->preprocessNode($variables);
+}
+
+/**
+ * Implements hook_entity_extra_field_info().
+ */
+function content_moderation_entity_extra_field_info() {
+  return \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityTypeInfo::class)
+    ->entityExtraFieldInfo();
+}
+
+/**
+ * Implements hook_entity_view().
+ */
+function content_moderation_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
+  \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityOperations::class)
+    ->entityView($build, $entity, $display, $view_mode);
+}
+
+/**
+ * Implements hook_node_access().
+ *
+ * Nodes in particular should be viewable if unpublished and the user has
+ * the appropriate permission. This permission is therefore effectively
+ * mandatory for any user that wants to moderate things.
+ */
+function content_moderation_node_access(NodeInterface $node, $operation, AccountInterface $account) {
+  /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
+  $moderation_info = Drupal::service('content_moderation.moderation_information');
+
+  $access_result = NULL;
+  if ($operation === 'view') {
+    $access_result = (!$node->isPublished())
+      ? AccessResult::allowedIfHasPermission($account, 'view any unpublished content')
+      : AccessResult::neutral();
+
+    $access_result->addCacheableDependency($node);
+  }
+  elseif ($operation === 'update' && $moderation_info->isModeratedEntity($node) && $node->moderation_state) {
+    /** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */
+    $transition_validation = \Drupal::service('content_moderation.state_transition_validation');
+
+    $valid_transition_targets = $transition_validation->getValidTransitions($node, $account);
+    $access_result = $valid_transition_targets ? AccessResult::neutral() : AccessResult::forbidden();
+
+    $access_result->addCacheableDependency($node);
+    $access_result->addCacheableDependency($account);
+    $workflow = \Drupal::service('content_moderation.moderation_information')->getWorkflowForEntity($node);
+    $access_result->addCacheableDependency($workflow);
+    foreach ($valid_transition_targets as $valid_transition_target) {
+      $access_result->addCacheableDependency($valid_transition_target);
+    }
+  }
+
+  return $access_result;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function content_moderation_theme() {
+  return ['entity_moderation_form' => ['render element' => 'form']];
+}
+
+/**
+ * Implements hook_action_info_alter().
+ */
+function content_moderation_action_info_alter(&$definitions) {
+
+  // The publish/unpublish actions are not valid on moderated entities. So swap
+  // their implementations out for alternates that will become a no-op on a
+  // moderated node. If another module has already swapped out those classes,
+  // though, we'll be polite and do nothing.
+  if (isset($definitions['node_publish_action']['class']) && $definitions['node_publish_action']['class'] == PublishNode::class) {
+    $definitions['node_publish_action']['class'] = ModerationOptOutPublishNode::class;
+  }
+  if (isset($definitions['node_unpublish_action']['class']) && $definitions['node_unpublish_action']['class'] == UnpublishNode::class) {
+    $definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class;
+  }
+}
+
+/**
+ * Implements hook_entity_bundle_info_alter().
+ */
+function content_moderation_entity_bundle_info_alter(&$bundles) {
+  /** @var \Drupal\workflows\WorkflowInterface $workflow */
+  foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
+    /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
+    $plugin = $workflow->getTypePlugin();
+    foreach ($plugin->getEntityTypes() as $entity_type_id) {
+      foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
+        if (isset($bundles[$entity_type_id][$bundle_id])) {
+          $bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id();
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_insert().
+ */
+function content_moderation_workflow_insert(WorkflowInterface $entity) {
+  // Clear bundle cache so workflow gets added or removed from the bundle
+  // information.
+  \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
+  // Clear field cache so extra field is added or removed.
+  \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_update().
+ */
+function content_moderation_workflow_update(WorkflowInterface $entity) {
+  content_moderation_workflow_insert($entity);
+}
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php
index 7579afa4e6..f70226ebc9 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php
@@ -63,7 +63,7 @@ public function testCreatingContent() {
     // Create a new node.
     $this->drupalPostForm('node/add/moderated_content', [
       'title[0][value]' => 'non-moderated content',
-    ], t('Save and publish'));
+    ], t('Save'));
 
     $node = $this->getNodeByTitle('non-moderated content');
     if (!$node) {
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php.orig b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php.orig
new file mode 100644
index 0000000000..7579afa4e6
--- /dev/null
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTest.php.orig
@@ -0,0 +1,141 @@
+<?php
+
+namespace Drupal\Tests\content_moderation\Functional;
+
+use Drupal\Core\Url;
+use Drupal\node\Entity\Node;
+
+/**
+ * Tests general content moderation workflow for nodes.
+ *
+ * @group content_moderation
+ */
+class ModerationStateNodeTest extends ModerationStateTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->drupalLogin($this->adminUser);
+    $this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
+    $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
+  }
+
+  /**
+   * Tests creating and deleting content.
+   */
+  public function testCreatingContent() {
+    $this->drupalPostForm('node/add/moderated_content', [
+      'title[0][value]' => 'moderated content',
+    ], t('Save and Create New Draft'));
+    $node = $this->getNodeByTitle('moderated content');
+    if (!$node) {
+      $this->fail('Test node was not saved correctly.');
+    }
+    $this->assertEqual('draft', $node->moderation_state->value);
+
+    $path = 'node/' . $node->id() . '/edit';
+    // Set up published revision.
+    $this->drupalPostForm($path, [], t('Save and Publish'));
+    \Drupal::entityTypeManager()->getStorage('node')->resetCache([$node->id()]);
+    /* @var \Drupal\node\NodeInterface $node */
+    $node = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
+    $this->assertTrue($node->isPublished());
+    $this->assertEqual('published', $node->moderation_state->value);
+
+    // Verify that the state field is not shown.
+    $this->assertNoText('Published');
+
+    // Delete the node.
+    $this->drupalPostForm('node/' . $node->id() . '/delete', [], t('Delete'));
+    $this->assertText(t('The Moderated content moderated content has been deleted.'));
+
+    // Disable content moderation.
+    $this->drupalPostForm('admin/structure/types/manage/moderated_content/moderation', ['workflow' => ''], t('Save'));
+    $this->drupalGet('admin/structure/types/manage/moderated_content/moderation');
+    $this->assertOptionSelected('edit-workflow', '');
+    // Ensure the parent environment is up-to-date.
+    // @see content_moderation_workflow_insert()
+    \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
+    \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
+
+    // Create a new node.
+    $this->drupalPostForm('node/add/moderated_content', [
+      'title[0][value]' => 'non-moderated content',
+    ], t('Save and publish'));
+
+    $node = $this->getNodeByTitle('non-moderated content');
+    if (!$node) {
+      $this->fail('Non-moderated test node was not saved correctly.');
+    }
+    $this->assertEqual(NULL, $node->moderation_state->value);
+  }
+
+  /**
+   * Tests edit form destinations.
+   */
+  public function testFormSaveDestination() {
+    // Create new moderated content in draft.
+    $this->drupalPostForm('node/add/moderated_content', [
+      'title[0][value]' => 'Some moderated content',
+      'body[0][value]' => 'First version of the content.',
+    ], t('Save and Create New Draft'));
+
+    $node = $this->drupalGetNodeByTitle('Some moderated content');
+    $edit_path = sprintf('node/%d/edit', $node->id());
+
+    // After saving, we should be at the canonical URL and viewing the first
+    // revision.
+    $this->assertUrl(Url::fromRoute('entity.node.canonical', ['node' => $node->id()]));
+    $this->assertText('First version of the content.');
+
+    // Create a new draft; after saving, we should still be on the canonical
+    // URL, but viewing the second revision.
+    $this->drupalPostForm($edit_path, [
+      'body[0][value]' => 'Second version of the content.',
+    ], t('Save and Create New Draft'));
+    $this->assertUrl(Url::fromRoute('entity.node.canonical', ['node' => $node->id()]));
+    $this->assertText('Second version of the content.');
+
+    // Make a new published revision; after saving, we should be at the
+    // canonical URL.
+    $this->drupalPostForm($edit_path, [
+      'body[0][value]' => 'Third version of the content.',
+    ], t('Save and Publish'));
+    $this->assertUrl(Url::fromRoute('entity.node.canonical', ['node' => $node->id()]));
+    $this->assertText('Third version of the content.');
+
+    // Make a new forward revision; after saving, we should be on the "Latest
+    // version" tab.
+    $this->drupalPostForm($edit_path, [
+      'body[0][value]' => 'Fourth version of the content.',
+    ], t('Save and Create New Draft'));
+    $this->assertUrl(Url::fromRoute('entity.node.latest_version', ['node' => $node->id()]));
+    $this->assertText('Fourth version of the content.');
+  }
+
+  /**
+   * Tests pagers aren't broken by content_moderation.
+   */
+  public function testPagers() {
+    // Create 51 nodes to force the pager.
+    foreach (range(1, 51) as $delta) {
+      Node::create([
+        'type' => 'moderated_content',
+        'uid' => $this->adminUser->id(),
+        'title' => 'Node ' . $delta,
+        'status' => 1,
+        'moderation_state' => 'published',
+      ])->save();
+    }
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('admin/content');
+    $element = $this->cssSelect('nav.pager li.is-active a');
+    $url = $element[0]->getAttribute('href');
+    $query = [];
+    parse_str(parse_url($url, PHP_URL_QUERY), $query);
+    $this->assertEqual(0, $query['page']);
+  }
+
+}
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php
index 8391dd0aae..6b5e167403 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php
@@ -18,10 +18,10 @@ public function testNotModerated() {
     $this->assertText('The content type Not moderated has been added.');
     $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'not_moderated');
     $this->drupalGet('node/add/not_moderated');
-    $this->assertRaw('Save as unpublished');
+    $this->assertRaw('Save');
     $this->drupalPostForm(NULL, [
       'title[0][value]' => 'Test',
-    ], t('Save and publish'));
+    ], t('Save'));
     $this->assertText('Not moderated Test has been created.');
   }
 
@@ -53,7 +53,7 @@ public function testEnablingOnExistingContent() {
     $this->drupalGet('node/add/not_moderated');
     $this->drupalPostForm(NULL, [
       'title[0][value]' => 'Test',
-    ], t('Save and publish'));
+    ], t('Save'));
     $this->assertText('Not moderated Test has been created.');
 
     // Now enable moderation state, ensuring all the expected links and tabs are
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php.orig b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php.orig
new file mode 100644
index 0000000000..8391dd0aae
--- /dev/null
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateNodeTypeTest.php.orig
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\Tests\content_moderation\Functional;
+
+/**
+ * Tests moderation state node type integration.
+ *
+ * @group content_moderation
+ */
+class ModerationStateNodeTypeTest extends ModerationStateTestBase {
+
+  /**
+   * A node type without moderation state disabled.
+   */
+  public function testNotModerated() {
+    $this->drupalLogin($this->adminUser);
+    $this->createContentTypeFromUi('Not moderated', 'not_moderated');
+    $this->assertText('The content type Not moderated has been added.');
+    $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'not_moderated');
+    $this->drupalGet('node/add/not_moderated');
+    $this->assertRaw('Save as unpublished');
+    $this->drupalPostForm(NULL, [
+      'title[0][value]' => 'Test',
+    ], t('Save and publish'));
+    $this->assertText('Not moderated Test has been created.');
+  }
+
+  /**
+   * Tests enabling moderation on an existing node-type, with content.
+   */
+  public function testEnablingOnExistingContent() {
+    $editor_permissions = [
+      'administer content moderation',
+      'access administration pages',
+      'administer content types',
+      'administer nodes',
+      'view latest version',
+      'view any unpublished content',
+      'access content overview',
+      'use editorial transition create_new_draft',
+    ];
+    $publish_permissions = array_merge($editor_permissions, ['use editorial transition publish']);
+    $editor = $this->drupalCreateUser($editor_permissions);
+    $editor_with_publish = $this->drupalCreateUser($publish_permissions);
+
+    // Create a node type that is not moderated.
+    $this->drupalLogin($editor);
+    $this->createContentTypeFromUi('Not moderated', 'not_moderated');
+    $this->grantUserPermissionToCreateContentOfType($editor, 'not_moderated');
+    $this->grantUserPermissionToCreateContentOfType($editor_with_publish, 'not_moderated');
+
+    // Create content.
+    $this->drupalGet('node/add/not_moderated');
+    $this->drupalPostForm(NULL, [
+      'title[0][value]' => 'Test',
+    ], t('Save and publish'));
+    $this->assertText('Not moderated Test has been created.');
+
+    // Now enable moderation state, ensuring all the expected links and tabs are
+    // present.
+    $this->drupalGet('admin/structure/types');
+    $this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation');
+    $this->drupalGet('admin/structure/types/manage/not_moderated');
+    $this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation');
+    $this->drupalGet('admin/structure/types/manage/not_moderated/moderation');
+    $this->assertOptionSelected('edit-workflow', '');
+    $this->assertNoLink('Delete');
+    $edit['workflow'] = 'editorial';
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+
+    // And make sure it works.
+    $nodes = \Drupal::entityTypeManager()->getStorage('node')
+      ->loadByProperties(['title' => 'Test']);
+    if (empty($nodes)) {
+      $this->fail('Could not load node with title Test');
+      return;
+    }
+    $node = reset($nodes);
+    $this->drupalGet('node/' . $node->id());
+    $this->assertResponse(200);
+    $this->assertLinkByHref('node/' . $node->id() . '/edit');
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->assertResponse(200);
+    $this->assertRaw('Save and Create New Draft');
+    $this->assertNoRaw('Save and Publish');
+
+    $this->drupalLogin($editor_with_publish);
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->assertResponse(200);
+    $this->assertRaw('Save and Create New Draft');
+    $this->assertRaw('Save and Publish');
+  }
+
+}
diff --git a/core/modules/content_moderation/tests/src/Functional/NodeAccessTest.php b/core/modules/content_moderation/tests/src/Functional/NodeAccessTest.php
index f3c27146d4..c939c52c2d 100644
--- a/core/modules/content_moderation/tests/src/Functional/NodeAccessTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/NodeAccessTest.php
@@ -46,7 +46,7 @@ class NodeAccessTest extends ModerationStateTestBase {
   protected function setUp() {
     parent::setUp();
     $this->drupalLogin($this->adminUser);
-    $this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
+    $this->createContentTypeFromUi('Moderated content', 'moderated_content', FALSE);
     $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
 
     // Rebuild permissions because hook_node_grants() is implemented by the
@@ -60,8 +60,20 @@ protected function setUp() {
   public function testPageAccess() {
     $this->drupalLogin($this->adminUser);
 
+    // Access the node form before moderation is enabled, the publication state
+    // should now be visible.
+    $this->drupalGet('node/add/moderated_content');
+    $this->assertSession()->fieldExists('Published');
+
+    // Now enable the workflow.
+    $this->enableModerationThroughUi('moderated_content', 'editorial');
+
+    // Access that the status field is no longer visible.
+    $this->drupalGet('node/add/moderated_content');
+    $this->assertSession()->fieldNotExists('Published');
+
     // Create a node to test with.
-    $this->drupalPostForm('node/add/moderated_content', [
+    $this->drupalPostForm(NULL, [
       'title[0][value]' => 'moderated content',
     ], t('Save and Create New Draft'));
     $node = $this->getNodeByTitle('moderated content');
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationLanguageChangeTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationLanguageChangeTest.php
index 3236a592c3..ae326ef67b 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTranslationLanguageChangeTest.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationLanguageChangeTest.php
@@ -73,12 +73,12 @@ public function testLanguageChange() {
     $edit = [
       'title[0][value]' => 'english_title',
     ];
-    $this->drupalPostForm(NULL, $edit, t('Save and publish'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
 
     // Create a translation in French.
     $this->clickLink('Translate');
     $this->clickLink('Add');
-    $this->drupalPostForm(NULL, [], t('Save and keep published (this translation)'));
+    $this->drupalPostForm(NULL, [], t('Save (this translation)'));
     $this->clickLink('Translate');
 
     // Edit English translation.
@@ -90,7 +90,7 @@ public function testLanguageChange() {
       'files[field_image_field_0]' => $images->uri,
     ];
     $this->drupalPostForm(NULL, $edit, t('Upload'));
-    $this->drupalPostForm(NULL, ['field_image_field[0][alt]' => 'alternative_text'], t('Save and keep published (this translation)'));
+    $this->drupalPostForm(NULL, ['field_image_field[0][alt]' => 'alternative_text'], t('Save (this translation)'));
 
     // Check that the translation languages are correct.
     $node = $this->getNodeByTitle('english_title');
@@ -109,13 +109,13 @@ public function testTitleDoesNotChangesOnChangingLanguageWidgetAndTriggeringAjax
       'title[0][value]' => 'english_title',
       'test_field_only_en_fr' => 'node created',
     ];
-    $this->drupalPostForm(NULL, $edit, t('Save and publish'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
     $this->assertEqual('node created', \Drupal::state()->get('test_field_only_en_fr'));
 
     // Create a translation in French.
     $this->clickLink('Translate');
     $this->clickLink('Add');
-    $this->drupalPostForm(NULL, [], t('Save and keep published (this translation)'));
+    $this->drupalPostForm(NULL, [], t('Save (this translation)'));
     $this->clickLink('Translate');
 
     // Edit English translation.
@@ -137,7 +137,7 @@ public function testTitleDoesNotChangesOnChangingLanguageWidgetAndTriggeringAjax
       'langcode[0][value]' => 'en',
       'field_image_field[0][alt]' => 'alternative_text'
     ];
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)'));
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
 
     // Check that the translation languages are correct.
     $node = $this->getNodeByTitle('english_title');
diff --git a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceXSSTest.php b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceXSSTest.php
index 46986ec474..b161ed66d8 100644
--- a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceXSSTest.php
+++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceXSSTest.php
@@ -62,7 +62,7 @@ public function testEntityReferenceXSS() {
       'title[0][value]' => $this->randomString(),
       'entity_reference_test' => $referenced_node->id()
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save and publish');
+    $this->drupalPostForm(NULL, $edit, 'Save');
     $this->assertEscaped($referenced_node->getTitle());
 
     // Test the options_buttons type.
diff --git a/core/modules/file/src/Tests/FileFieldAnonymousSubmissionTest.php b/core/modules/file/src/Tests/FileFieldAnonymousSubmissionTest.php
index b59b1bafd3..66d87cd7e8 100644
--- a/core/modules/file/src/Tests/FileFieldAnonymousSubmissionTest.php
+++ b/core/modules/file/src/Tests/FileFieldAnonymousSubmissionTest.php
@@ -138,7 +138,7 @@ protected function doTestNodeWithFileWithoutTitle() {
       $label = 'Save';
     }
     else {
-      $label = 'Save and publish';
+      $label = 'Save';
     }
     $this->drupalPostForm(NULL, $edit, $label);
     $this->assertResponse(200);
diff --git a/core/modules/file/src/Tests/FileFieldDisplayTest.php b/core/modules/file/src/Tests/FileFieldDisplayTest.php
index 1b7e534211..db9b7dc91b 100644
--- a/core/modules/file/src/Tests/FileFieldDisplayTest.php
+++ b/core/modules/file/src/Tests/FileFieldDisplayTest.php
@@ -77,7 +77,7 @@ public function testNodeDisplay() {
 
     // Turn the "display" option off and check that the file is no longer displayed.
     $edit = [$field_name . '[0][display]' => FALSE];
-    $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
 
     $this->assertNoRaw($default_output, 'Field is hidden when "display" option is unchecked.');
 
@@ -87,7 +87,7 @@ public function testNodeDisplay() {
       $field_name . '[0][description]' => $description,
       $field_name . '[0][display]' => TRUE,
     ];
-    $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
     $this->assertText($description);
 
     // Ensure the filename in the link's title attribute is escaped.
@@ -167,7 +167,7 @@ public function testDescToggle() {
       'title[0][value]' => $title,
       'files[field_' . $field_name . '_0]' => drupal_realpath($file->uri),
     ];
-    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
     $node = $this->drupalGetNodeByTitle($title);
     $this->drupalGet('node/' . $node->id() . '/edit');
     $this->assertText(t('The description may be used as the label of the link to the file.'));
diff --git a/core/modules/file/src/Tests/FileFieldRevisionTest.php b/core/modules/file/src/Tests/FileFieldRevisionTest.php
index 7de496ff8a..7b7b4d3c7a 100644
--- a/core/modules/file/src/Tests/FileFieldRevisionTest.php
+++ b/core/modules/file/src/Tests/FileFieldRevisionTest.php
@@ -63,7 +63,7 @@ public function testRevisions() {
 
     // Save a new version of the node without any changes.
     // Check that the file is still the same as the previous revision.
-    $this->drupalPostForm('node/' . $nid . '/edit', ['revision' => '1'], t('Save and keep published'));
+    $this->drupalPostForm('node/' . $nid . '/edit', ['revision' => '1'], t('Save'));
     $node_storage->resetCache([$nid]);
     $node = $node_storage->load($nid);
     $node_file_r3 = File::load($node->{$field_name}->target_id);
diff --git a/core/modules/file/src/Tests/FileFieldTestBase.php b/core/modules/file/src/Tests/FileFieldTestBase.php
index 73cc96ad50..b7b90d7d37 100644
--- a/core/modules/file/src/Tests/FileFieldTestBase.php
+++ b/core/modules/file/src/Tests/FileFieldTestBase.php
@@ -227,7 +227,7 @@ public function uploadNodeFiles(array $files, $field_name, $nid_or_type, $new_re
         $edit[$name][] = $file_path;
       }
     }
-    $this->drupalPostForm("node/$nid/edit", $edit, t('Save and keep published'));
+    $this->drupalPostForm("node/$nid/edit", $edit, t('Save'));
 
     return $nid;
   }
@@ -243,7 +243,7 @@ public function removeNodeFile($nid, $new_revision = TRUE) {
     ];
 
     $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
   }
 
   /**
@@ -256,7 +256,7 @@ public function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE)
     ];
 
     $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
   }
 
   /**
diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/src/Tests/FileFieldValidateTest.php
index 5ddcdde3f8..422bf68282 100644
--- a/core/modules/file/src/Tests/FileFieldValidateTest.php
+++ b/core/modules/file/src/Tests/FileFieldValidateTest.php
@@ -29,7 +29,7 @@ public function testRequired() {
     // Try to post a new node without uploading a file.
     $edit = [];
     $edit['title[0][value]'] = $this->randomMachineName();
-    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
     $this->assertRaw(t('@title field is required.', ['@title' => $field->getLabel()]), 'Node save failed when required file field was empty.');
 
     // Create a new node with the uploaded file.
@@ -50,7 +50,7 @@ public function testRequired() {
     // Try to post a new node without uploading a file in the multivalue field.
     $edit = [];
     $edit['title[0][value]'] = $this->randomMachineName();
-    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
     $this->assertRaw(t('@title field is required.', ['@title' => $field->getLabel()]), 'Node save failed when required multiple value file field was empty.');
 
     // Create a new node with the uploaded file into the multivalue field.
diff --git a/core/modules/file/src/Tests/FileFieldWidgetTest.php b/core/modules/file/src/Tests/FileFieldWidgetTest.php
index ddfd8f4e79..ad47d77841 100644
--- a/core/modules/file/src/Tests/FileFieldWidgetTest.php
+++ b/core/modules/file/src/Tests/FileFieldWidgetTest.php
@@ -121,7 +121,7 @@ public function testSingleValuedWidget() {
       $this->assertTrue(isset($label[0]), 'Label for upload found.');
 
       // Save the node and ensure it does not have the file.
-      $this->drupalPostForm(NULL, [], t('Save and keep published'));
+      $this->drupalPostForm(NULL, [], t('Save'));
       $node_storage->resetCache([$nid]);
       $node = $node_storage->load($nid);
       $this->assertTrue(empty($node->{$field_name}->target_id), 'File was successfully removed from the node.');
@@ -238,8 +238,7 @@ public function testMultiValuedWidget() {
       $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), format_string('After removing all files, there is no "Remove" button displayed (JSMode=%type).', ['%type' => $type]));
 
       // Save the node and ensure it does not have any files.
-      $this->drupalPostForm(NULL, ['title[0][value]' => $this->randomMachineName()], t('Save and publish'));
-      $matches = [];
+      $this->drupalPostForm(NULL, ['title[0][value]' => $this->randomMachineName()], t('Save'));
       preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
       $nid = $matches[1];
       $node_storage->resetCache([$nid]);
@@ -368,7 +367,7 @@ public function testPrivateFileComment() {
     $edit = [
       'title[0][value]' => $this->randomMachineName(),
     ];
-    $this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/article', $edit, t('Save'));
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
 
     // Add a comment with a file.
@@ -402,7 +401,8 @@ public function testPrivateFileComment() {
 
     // Unpublishes node.
     $this->drupalLogin($this->adminUser);
-    $this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and unpublish'));
+    $edit = ['status[value]' => FALSE];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
 
     // Ensures normal user can no longer download the file.
     $this->drupalLogin($user);
diff --git a/core/modules/file/src/Tests/FilePrivateTest.php b/core/modules/file/src/Tests/FilePrivateTest.php
index c5771c6aed..6808a4782b 100644
--- a/core/modules/file/src/Tests/FilePrivateTest.php
+++ b/core/modules/file/src/Tests/FilePrivateTest.php
@@ -73,10 +73,10 @@ public function testPrivateFile() {
     // Attempt to reuse the file when editing a node.
     $edit = [];
     $edit['title[0][value]'] = $this->randomMachineName();
-    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
     $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $edit[$field_name . '[0][fids]'] = $node_file->id();
-    $this->drupalPostForm('node/' . $new_node->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $new_node->id() . '/edit', $edit, t('Save'));
     // Make sure the form submit failed - we stayed on the edit form.
     $this->assertUrl('node/' . $new_node->id() . '/edit');
     // Check that we got the expected constraint form error.
@@ -87,7 +87,7 @@ public function testPrivateFile() {
     $edit = [];
     $edit['title[0][value]'] = $this->randomMachineName();
     $edit[$field_name . '[0][fids]'] = $node_file->id();
-    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
     $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertTrue(empty($new_node), 'Node was not created.');
     $this->assertUrl('node/add/' . $type_name);
diff --git a/core/modules/file/src/Tests/FilePrivateTest.php.orig b/core/modules/file/src/Tests/FilePrivateTest.php.orig
new file mode 100644
index 0000000000..c5771c6aed
--- /dev/null
+++ b/core/modules/file/src/Tests/FilePrivateTest.php.orig
@@ -0,0 +1,228 @@
+<?php
+
+namespace Drupal\file\Tests;
+
+use Drupal\Core\Entity\Plugin\Validation\Constraint\ReferenceAccessConstraint;
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\file\Entity\File;
+use Drupal\node\Entity\NodeType;
+use Drupal\user\RoleInterface;
+
+/**
+ * Uploads a test to a private node and checks access.
+ *
+ * @group file
+ */
+class FilePrivateTest extends FileFieldTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['node_access_test', 'field_test'];
+
+  protected function setUp() {
+    parent::setUp();
+    node_access_test_add_field(NodeType::load('article'));
+    node_access_rebuild();
+    \Drupal::state()->set('node_access_test.private', TRUE);
+  }
+
+  /**
+   * Tests file access for file uploaded to a private node.
+   */
+  public function testPrivateFile() {
+    $node_storage = $this->container->get('entity.manager')->getStorage('node');
+    $type_name = 'article';
+    $field_name = strtolower($this->randomMachineName());
+    $this->createFileField($field_name, 'node', $type_name, ['uri_scheme' => 'private']);
+
+    $test_file = $this->getTestFile('text');
+    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name, TRUE, ['private' => TRUE]);
+    \Drupal::entityManager()->getStorage('node')->resetCache([$nid]);
+    /* @var \Drupal\node\NodeInterface $node */
+    $node = $node_storage->load($nid);
+    $node_file = File::load($node->{$field_name}->target_id);
+    // Ensure the file can be viewed.
+    $this->drupalGet('node/' . $node->id());
+    $this->assertRaw($node_file->getFilename(), 'File reference is displayed after attaching it');
+    // Ensure the file can be downloaded.
+    $this->drupalGet(file_create_url($node_file->getFileUri()));
+    $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
+    $this->drupalLogOut();
+    $this->drupalGet(file_create_url($node_file->getFileUri()));
+    $this->assertResponse(403, 'Confirmed that access is denied for the file without the needed permission.');
+
+    // Create a field with no view access. See
+    // field_test_entity_field_access().
+    $no_access_field_name = 'field_no_view_access';
+    $this->createFileField($no_access_field_name, 'node', $type_name, ['uri_scheme' => 'private']);
+    // Test with the field that should deny access through field access.
+    $this->drupalLogin($this->adminUser);
+    $nid = $this->uploadNodeFile($test_file, $no_access_field_name, $type_name, TRUE, ['private' => TRUE]);
+    \Drupal::entityManager()->getStorage('node')->resetCache([$nid]);
+    $node = $node_storage->load($nid);
+    $node_file = File::load($node->{$no_access_field_name}->target_id);
+
+    // Ensure the file cannot be downloaded.
+    $file_url = file_create_url($node_file->getFileUri());
+    $this->drupalGet($file_url);
+    $this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission.');
+
+    // Attempt to reuse the file when editing a node.
+    $edit = [];
+    $edit['title[0][value]'] = $this->randomMachineName();
+    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
+    $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $edit[$field_name . '[0][fids]'] = $node_file->id();
+    $this->drupalPostForm('node/' . $new_node->id() . '/edit', $edit, t('Save and keep published'));
+    // Make sure the form submit failed - we stayed on the edit form.
+    $this->assertUrl('node/' . $new_node->id() . '/edit');
+    // Check that we got the expected constraint form error.
+    $constraint = new ReferenceAccessConstraint();
+    $this->assertRaw(SafeMarkup::format($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
+    // Attempt to reuse the existing file when creating a new node, and confirm
+    // that access is still denied.
+    $edit = [];
+    $edit['title[0][value]'] = $this->randomMachineName();
+    $edit[$field_name . '[0][fids]'] = $node_file->id();
+    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
+    $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $this->assertTrue(empty($new_node), 'Node was not created.');
+    $this->assertUrl('node/add/' . $type_name);
+    $this->assertRaw(SafeMarkup::format($constraint->message, ['%type' => 'file', '%id' => $node_file->id()]));
+
+    // Now make file_test_file_download() return everything.
+    \Drupal::state()->set('file_test.allow_all', TRUE);
+    // Delete the node.
+    $node->delete();
+    // Ensure the file can still be downloaded by the owner.
+    $this->drupalGet($file_url);
+    $this->assertResponse(200, 'Confirmed that the owner still has access to the temporary file.');
+
+    // Ensure the file cannot be downloaded by an anonymous user.
+    $this->drupalLogout();
+    $this->drupalGet($file_url);
+    $this->assertResponse(403, 'Confirmed that access is denied for an anonymous user to the temporary file.');
+
+    // Ensure the file cannot be downloaded by another user.
+    $account = $this->drupalCreateUser();
+    $this->drupalLogin($account);
+    $this->drupalGet($file_url);
+    $this->assertResponse(403, 'Confirmed that access is denied for another user to the temporary file.');
+
+    // As an anonymous user, create a temporary file with no references and
+    // confirm that only the session that uploaded it may view it.
+    $this->drupalLogout();
+    user_role_change_permissions(
+      RoleInterface::ANONYMOUS_ID,
+      [
+        "create $type_name content" => TRUE,
+        'access content' => TRUE,
+      ]
+    );
+    $test_file = $this->getTestFile('text');
+    $this->drupalGet('node/add/' . $type_name);
+    $edit = ['files[' . $field_name . '_0]' => drupal_realpath($test_file->getFileUri())];
+    $this->drupalPostForm(NULL, $edit, t('Upload'));
+    /** @var \Drupal\file\FileStorageInterface $file_storage */
+    $file_storage = $this->container->get('entity.manager')->getStorage('file');
+    $files = $file_storage->loadByProperties(['uid' => 0]);
+    $this->assertEqual(1, count($files), 'Loaded one anonymous file.');
+    $file = end($files);
+    $this->assertTrue($file->isTemporary(), 'File is temporary.');
+    $usage = $this->container->get('file.usage')->listUsage($file);
+    $this->assertFalse($usage, 'No file usage found.');
+    $file_url = file_create_url($file->getFileUri());
+    $this->drupalGet($file_url);
+    $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the temporary file.');
+    // Close the prior connection and remove the session cookie.
+    $this->curlClose();
+    $this->curlCookies = [];
+    $this->cookies = [];
+    $this->drupalGet($file_url);
+    $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the temporary file.');
+
+    // As an anonymous user, create a permanent file, then remove all
+    // references to the file (so that it becomes temporary again) and confirm
+    // that only the session that uploaded it may view it.
+    $test_file = $this->getTestFile('text');
+    $this->drupalGet('node/add/' . $type_name);
+    $edit = [];
+    $edit['title[0][value]'] = $this->randomMachineName();
+    $edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $file_id = $new_node->{$field_name}->target_id;
+    $file = File::load($file_id);
+    $this->assertTrue($file->isPermanent(), 'File is permanent.');
+    // Remove the reference to this file.
+    $new_node->{$field_name} = [];
+    $new_node->save();
+    $file = File::load($file_id);
+    $this->assertTrue($file->isTemporary(), 'File is temporary.');
+    $usage = $this->container->get('file.usage')->listUsage($file);
+    $this->assertFalse($usage, 'No file usage found.');
+    $file_url = file_create_url($file->getFileUri());
+    $this->drupalGet($file_url);
+    $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the file whose references were removed.');
+    // Close the prior connection and remove the session cookie.
+    $this->curlClose();
+    $this->curlCookies = [];
+    $this->cookies = [];
+    $this->drupalGet($file_url);
+    $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the file whose references were removed.');
+
+    // As an anonymous user, create a permanent file that is referenced by a
+    // published node and confirm that all anonymous users may view it.
+    $test_file = $this->getTestFile('text');
+    $this->drupalGet('node/add/' . $type_name);
+    $edit = [];
+    $edit['title[0][value]'] = $this->randomMachineName();
+    $edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $file = File::load($new_node->{$field_name}->target_id);
+    $this->assertTrue($file->isPermanent(), 'File is permanent.');
+    $usage = $this->container->get('file.usage')->listUsage($file);
+    $this->assertTrue($usage, 'File usage found.');
+    $file_url = file_create_url($file->getFileUri());
+    $this->drupalGet($file_url);
+    $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the permanent file that is referenced by a published node.');
+    // Close the prior connection and remove the session cookie.
+    $this->curlClose();
+    $this->curlCookies = [];
+    $this->cookies = [];
+    $this->drupalGet($file_url);
+    $this->assertResponse(200, 'Confirmed that another anonymous user also has access to the permanent file that is referenced by a published node.');
+
+    // As an anonymous user, create a permanent file that is referenced by an
+    // unpublished node and confirm that no anonymous users may view it (even
+    // the session that uploaded the file) because they cannot view the
+    // unpublished node.
+    $test_file = $this->getTestFile('text');
+    $this->drupalGet('node/add/' . $type_name);
+    $edit = [];
+    $edit['title[0][value]'] = $this->randomMachineName();
+    $edit['files[' . $field_name . '_0]'] = drupal_realpath($test_file->getFileUri());
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $new_node->setPublished(FALSE);
+    $new_node->save();
+    $file = File::load($new_node->{$field_name}->target_id);
+    $this->assertTrue($file->isPermanent(), 'File is permanent.');
+    $usage = $this->container->get('file.usage')->listUsage($file);
+    $this->assertTrue($usage, 'File usage found.');
+    $file_url = file_create_url($file->getFileUri());
+    $this->drupalGet($file_url);
+    $this->assertResponse(403, 'Confirmed that the anonymous uploader cannot access the permanent file when it is referenced by an unpublished node.');
+    // Close the prior connection and remove the session cookie.
+    $this->curlClose();
+    $this->curlCookies = [];
+    $this->cookies = [];
+    $this->drupalGet($file_url);
+    $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.');
+  }
+
+}
diff --git a/core/modules/filter/tests/src/Functional/FilterHooksTest.php b/core/modules/filter/tests/src/Functional/FilterHooksTest.php
index c799460988..5ff4a8a487 100644
--- a/core/modules/filter/tests/src/Functional/FilterHooksTest.php
+++ b/core/modules/filter/tests/src/Functional/FilterHooksTest.php
@@ -60,7 +60,7 @@ public function testFilterHooks() {
     $edit['title[0][value]'] = $title;
     $edit['body[0][value]'] = $this->randomMachineName(32);
     $edit['body[0][format]'] = $format_id;
-    $this->drupalPostForm("node/add/{$type->id()}", $edit, t('Save and publish'));
+    $this->drupalPostForm("node/add/{$type->id()}", $edit, t('Save'));
     $this->assertText(t('@type @title has been created.', ['@type' => $type_name, '@title' => $title]));
 
     // Disable the text format.
diff --git a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
index 6773d32d23..965a691c9e 100644
--- a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
@@ -42,6 +42,13 @@ content:
     weight: 15
     region: content
     third_party_settings: {  }
+  status:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 120
+    region: content
+    third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
diff --git a/core/modules/forum/tests/src/Functional/ForumBlockTest.php b/core/modules/forum/tests/src/Functional/ForumBlockTest.php
index 6c412b71cd..2914e94e8a 100644
--- a/core/modules/forum/tests/src/Functional/ForumBlockTest.php
+++ b/core/modules/forum/tests/src/Functional/ForumBlockTest.php
@@ -170,7 +170,7 @@ protected function createForumTopics($count = 5) {
       ];
 
       // Create the forum topic, preselecting the forum ID via a URL parameter.
-      $this->drupalPostForm('node/add/forum', $edit, t('Save and publish'), ['query' => ['forum_id' => 1]]);
+      $this->drupalPostForm('node/add/forum', $edit, t('Save'), ['query' => ['forum_id' => 1]]);
       $topics[] = $title;
     }
 
diff --git a/core/modules/forum/tests/src/Functional/ForumIndexTest.php b/core/modules/forum/tests/src/Functional/ForumIndexTest.php
index 53c8364db0..38adb72ea8 100644
--- a/core/modules/forum/tests/src/Functional/ForumIndexTest.php
+++ b/core/modules/forum/tests/src/Functional/ForumIndexTest.php
@@ -44,7 +44,7 @@ public function testForumIndexStatus() {
     $this->drupalGet("forum/$tid");
     $this->clickLink(t('Add new @node_type', ['@node_type' => 'Forum topic']));
     $this->assertUrl('node/add/forum', ['query' => ['forum_id' => $tid]]);
-    $this->drupalPostForm(NULL, $edit, t('Save and publish'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
 
     // Check that the node exists in the database.
     $node = $this->drupalGetNodeByTitle($title);
@@ -71,7 +71,8 @@ public function testForumIndexStatus() {
 
 
     // Unpublish the node.
-    $this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and unpublish'));
+    $edit = ['status[value]' => FALSE];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
     $this->drupalGet('node/' . $node->id());
     $this->assertText(t('Access denied'), 'Unpublished node is no longer accessible.');
 
diff --git a/core/modules/image/src/Tests/ImageFieldDisplayTest.php b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
index 9c105c1434..8061526da2 100644
--- a/core/modules/image/src/Tests/ImageFieldDisplayTest.php
+++ b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
@@ -282,7 +282,7 @@ public function testImageFieldSettings() {
       $field_name . '[0][alt]' => $image['#alt'],
       $field_name . '[0][title]' => $image['#title'],
     ];
-    $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
     $default_output = str_replace("\n", NULL, $renderer->renderRoot($image));
     $this->assertRaw($default_output, 'Image displayed using user supplied alt and title attributes.');
 
@@ -292,7 +292,7 @@ public function testImageFieldSettings() {
       $field_name . '[0][alt]' => $this->randomMachineName($test_size),
       $field_name . '[0][title]' => $this->randomMachineName($test_size),
     ];
-    $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save'));
     $schema = $field->getFieldStorageDefinition()->getSchema();
     $this->assertRaw(t('Alternative text cannot be longer than %max characters but is currently %length characters long.', [
       '%max' => $schema['columns']['alt']['length'],
@@ -314,9 +314,9 @@ public function testImageFieldSettings() {
     $edit = [
       'files[' . $field_name . '_1][]' => drupal_realpath($test_image->uri),
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
     // Add the required alt text.
-    $this->drupalPostForm(NULL, [$field_name . '[1][alt]' => $alt], t('Save and keep published'));
+    $this->drupalPostForm(NULL, [$field_name . '[1][alt]' => $alt], t('Save'));
     $this->assertText(format_string('Article @title has been updated.', ['@title' => $node->getTitle()]));
 
     // Assert ImageWidget::process() calls FieldWidget::process().
diff --git a/core/modules/image/src/Tests/ImageFieldTestBase.php b/core/modules/image/src/Tests/ImageFieldTestBase.php
index eec7f7bba9..c023917e36 100644
--- a/core/modules/image/src/Tests/ImageFieldTestBase.php
+++ b/core/modules/image/src/Tests/ImageFieldTestBase.php
@@ -90,10 +90,10 @@ public function uploadNodeImage($image, $field_name, $type, $alt = '') {
       'title[0][value]' => $this->randomMachineName(),
     ];
     $edit['files[' . $field_name . '_0]'] = drupal_realpath($image->uri);
-    $this->drupalPostForm('node/add/' . $type, $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/' . $type, $edit, t('Save'));
     if ($alt) {
       // Add alt text.
-      $this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save and publish'));
+      $this->drupalPostForm(NULL, [$field_name . '[0][alt]' => $alt], t('Save'));
     }
 
     // Retrieve ID of the newly created node from the current URL.
diff --git a/core/modules/image/src/Tests/ImageFieldValidateTest.php b/core/modules/image/src/Tests/ImageFieldValidateTest.php
index 7e5ee9bae7..e429d0bb20 100644
--- a/core/modules/image/src/Tests/ImageFieldValidateTest.php
+++ b/core/modules/image/src/Tests/ImageFieldValidateTest.php
@@ -119,7 +119,7 @@ public function testRequiredAttributes() {
     $edit = [
       'title[0][value]' => $this->randomMachineName(),
     ];
-    $this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/article', $edit, t('Save'));
 
     $this->assertNoText(t('Alternative text field is required.'));
     $this->assertNoText(t('Title field is required.'));
@@ -132,7 +132,7 @@ public function testRequiredAttributes() {
     $edit = [
       'title[0][value]' => $this->randomMachineName(),
     ];
-    $this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/article', $edit, t('Save'));
 
     $this->assertNoText(t('Alternative text field is required.'));
     $this->assertNoText(t('Title field is required.'));
diff --git a/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php b/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php
index bce4ff8bd4..2007e1e266 100644
--- a/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php
+++ b/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php
@@ -85,7 +85,7 @@ public function testFragmentLink() {
     // Only enter a title in the node add form and leave the body field empty.
     $edit = ['edit-title-0-value' => 'Test inline form error with CKEditor'];
 
-    $this->submitForm($edit, 'Save and publish');
+    $this->submitForm($edit, 'Save');
 
     // Add a bottom margin to the title field to be sure the body field is not
     // visible. PhantomJS runs with a resolution of 1024x768px.
diff --git a/core/modules/language/tests/src/Functional/LanguageNegotiationUrlTest.php b/core/modules/language/tests/src/Functional/LanguageNegotiationUrlTest.php
index 8472ff77f5..07ae85d433 100644
--- a/core/modules/language/tests/src/Functional/LanguageNegotiationUrlTest.php
+++ b/core/modules/language/tests/src/Functional/LanguageNegotiationUrlTest.php
@@ -70,7 +70,7 @@ public function testDomain() {
       'title[0][value]' => 'Test',
       'path[0][alias]' => '/eng/test',
     ];
-    $this->drupalPostForm('node/add/article', $nodeValues, $this->t('Save and publish'));
+    $this->drupalPostForm('node/add/article', $nodeValues, $this->t('Save'));
     $this->assertSession()->statusCodeEquals(200);
   }
 
diff --git a/core/modules/menu_ui/src/Tests/MenuNodeTest.php b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
index 3f1bba06ee..99900955fb 100644
--- a/core/modules/menu_ui/src/Tests/MenuNodeTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
@@ -134,8 +134,7 @@ public function testMenuNodeFormWidget() {
     $this->drupalGet('test-page');
     $this->assertNoLink($node_title);
 
-    // Use not only the save button, but also the two special buttons:
-    // 'Save and publish' as well as 'Save and keep published'.
+    // Make sure the menu links only appear when the node is published.
     // These buttons just appear for 'administer nodes' users.
     $admin_user = $this->drupalCreateUser([
       'access administration pages',
@@ -146,21 +145,20 @@ public function testMenuNodeFormWidget() {
       'edit any page content',
     ]);
     $this->drupalLogin($admin_user);
-    foreach (['Save and unpublish' => FALSE, 'Save and keep unpublished' => FALSE, 'Save and publish' => TRUE, 'Save and keep published' => TRUE] as $submit => $visible) {
-      $edit = [
-        'menu[enabled]' => 1,
-        'menu[title]' => $node_title,
-      ];
-      $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, $submit);
-      // Assert that the link exists.
-      $this->drupalGet('test-page');
-      if ($visible) {
-        $this->assertLink($node_title, 0, 'Found a menu link after submitted with ' . $submit);
-      }
-      else {
-        $this->assertNoLink($node_title, 'Found no menu link after submitted with ' . $submit);
-      }
-    }
+    // Assert that the link does not exist if unpublished.
+    $edit = [
+      'menu[enabled]' => 1,
+      'menu[title]' => $node_title,
+      'status[value]' => FALSE,
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->drupalGet('test-page');
+    $this->assertNoLink($node_title, 'Found no menu link with the node unpublished');
+    // Assert that the link exists if published.
+    $edit['status[value]'] = TRUE;
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+    $this->drupalGet('test-page');
+    $this->assertLink($node_title, 0, 'Found a menu link with the node published');
 
     // Log back in as normal user.
     $this->drupalLogin($this->editor);
diff --git a/core/modules/node/node.post_update.php b/core/modules/node/node.post_update.php
new file mode 100644
index 0000000000..43e3cd6acc
--- /dev/null
+++ b/core/modules/node/node.post_update.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for Node.
+ */
+
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+
+/**
+* Load all form displays for nodes, add status with these settings, save.
+*/
+function node_post_update_configure_status_field_widget() {
+  $query = \Drupal::entityQuery('entity_form_display')->condition('targetEntityType', 'node');
+  $ids = $query->execute();
+  $form_displays = EntityFormDisplay::loadMultiple($ids);
+
+  // Assign status settings for each 'node' target entity types with 'default'
+  // form mode.
+  foreach ($form_displays as $id => $form_display) {
+    /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface $form_display */
+    $form_display->setComponent('status', [
+      'type' => 'boolean_checkbox',
+      'settings' => [
+        'display_label' => TRUE,
+      ],
+    ])->save();
+  }
+}
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index 4f8efe3c53..d4c95732f5 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -400,6 +400,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ])
       ->setDisplayConfigurable('form', TRUE);
 
+    $fields['status']
+      ->setDisplayOptions('form', [
+        'type' => 'boolean_checkbox',
+        'settings' => [
+          'display_label' => TRUE,
+        ],
+        'weight' => 120,
+      ])
+      ->setDisplayConfigurable('form', TRUE);
+
     $fields['created'] = BaseFieldDefinition::create('created')
       ->setLabel(t('Authored on'))
       ->setDescription(t('The time that the node was created.'))
diff --git a/core/modules/node/src/Entity/Node.php.orig b/core/modules/node/src/Entity/Node.php.orig
new file mode 100644
index 0000000000..4f8efe3c53
--- /dev/null
+++ b/core/modules/node/src/Entity/Node.php.orig
@@ -0,0 +1,501 @@
+<?php
+
+namespace Drupal\node\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityPublishedTrait;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\node\NodeInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Defines the node entity class.
+ *
+ * @ContentEntityType(
+ *   id = "node",
+ *   label = @Translation("Content"),
+ *   label_collection = @Translation("Content"),
+ *   label_singular = @Translation("content item"),
+ *   label_plural = @Translation("content items"),
+ *   label_count = @PluralTranslation(
+ *     singular = "@count content item",
+ *     plural = "@count content items"
+ *   ),
+ *   bundle_label = @Translation("Content type"),
+ *   handlers = {
+ *     "storage" = "Drupal\node\NodeStorage",
+ *     "storage_schema" = "Drupal\node\NodeStorageSchema",
+ *     "view_builder" = "Drupal\node\NodeViewBuilder",
+ *     "access" = "Drupal\node\NodeAccessControlHandler",
+ *     "views_data" = "Drupal\node\NodeViewsData",
+ *     "form" = {
+ *       "default" = "Drupal\node\NodeForm",
+ *       "delete" = "Drupal\node\Form\NodeDeleteForm",
+ *       "edit" = "Drupal\node\NodeForm"
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\node\Entity\NodeRouteProvider",
+ *     },
+ *     "list_builder" = "Drupal\node\NodeListBuilder",
+ *     "translation" = "Drupal\node\NodeTranslationHandler"
+ *   },
+ *   base_table = "node",
+ *   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 = {
+ *     "id" = "nid",
+ *     "revision" = "vid",
+ *     "bundle" = "type",
+ *     "label" = "title",
+ *     "langcode" = "langcode",
+ *     "uuid" = "uuid",
+ *     "status" = "status",
+ *     "published" = "status",
+ *     "uid" = "uid",
+ *   },
+ *   bundle_entity_type = "node_type",
+ *   field_ui_base_route = "entity.node_type.edit_form",
+ *   common_reference_target = TRUE,
+ *   permission_granularity = "bundle",
+ *   links = {
+ *     "canonical" = "/node/{node}",
+ *     "delete-form" = "/node/{node}/delete",
+ *     "edit-form" = "/node/{node}/edit",
+ *     "version-history" = "/node/{node}/revisions",
+ *     "revision" = "/node/{node}/revisions/{node_revision}/view",
+ *   }
+ * )
+ */
+class Node extends ContentEntityBase implements NodeInterface {
+
+  use EntityChangedTrait;
+  use EntityPublishedTrait;
+
+  /**
+   * Whether the node is being previewed or not.
+   *
+   * The variable is set to public as it will give a considerable performance
+   * improvement. See https://www.drupal.org/node/2498919.
+   *
+   * @var true|null
+   *   TRUE if the node is being previewed and NULL if it is not.
+   */
+  public $in_preview = NULL;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageInterface $storage) {
+    parent::preSave($storage);
+
+    foreach (array_keys($this->getTranslationLanguages()) as $langcode) {
+      $translation = $this->getTranslation($langcode);
+
+      // If no owner has been set explicitly, make the anonymous user the owner.
+      if (!$translation->getOwner()) {
+        $translation->setOwnerId(0);
+      }
+    }
+
+    // If no revision author has been set explicitly, make the node owner the
+    // revision author.
+    if (!$this->getRevisionUser()) {
+      $this->setRevisionUserId($this->getOwnerId());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
+    parent::preSaveRevision($storage, $record);
+
+    if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) {
+      // If we are updating an existing node without adding a new revision, we
+      // need to make sure $entity->revision_log is reset whenever it is empty.
+      // Therefore, this code allows us to avoid clobbering an existing log
+      // entry with an empty one.
+      $record->revision_log = $this->original->revision_log->value;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+    parent::postSave($storage, $update);
+
+    // Update the node access table for this node, but only if it is the
+    // default revision. There's no need to delete existing records if the node
+    // is new.
+    if ($this->isDefaultRevision()) {
+      /** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */
+      $access_control_handler = \Drupal::entityManager()->getAccessControlHandler('node');
+      $grants = $access_control_handler->acquireGrants($this);
+      \Drupal::service('node.grant_storage')->write($this, $grants, NULL, $update);
+    }
+
+    // Reindex the node when it is updated. The node is automatically indexed
+    // when it is added, simply by being added to the node table.
+    if ($update) {
+      node_reindex_node_search($this->id());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preDelete(EntityStorageInterface $storage, array $entities) {
+    parent::preDelete($storage, $entities);
+
+    // Ensure that all nodes deleted are removed from the search index.
+    if (\Drupal::moduleHandler()->moduleExists('search')) {
+      foreach ($entities as $entity) {
+        search_index_clear('node_search', $entity->nid->value);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageInterface $storage, array $nodes) {
+    parent::postDelete($storage, $nodes);
+    \Drupal::service('node.grant_storage')->deleteNodeRecords(array_keys($nodes));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getType() {
+    return $this->bundle();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
+    // This override exists to set the operation to the default value "view".
+    return parent::access($operation, $account, $return_as_object);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->get('title')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTitle($title) {
+    $this->set('title', $title);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCreatedTime() {
+    return $this->get('created')->value;
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCreatedTime($timestamp) {
+    $this->set('created', $timestamp);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isPromoted() {
+    return (bool) $this->get('promote')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPromoted($promoted) {
+    $this->set('promote', $promoted ? NodeInterface::PROMOTED : NodeInterface::NOT_PROMOTED);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSticky() {
+    return (bool) $this->get('sticky')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSticky($sticky) {
+    $this->set('sticky', $sticky ? NodeInterface::STICKY : NodeInterface::NOT_STICKY);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwner() {
+    return $this->get('uid')->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerId() {
+    return $this->getEntityKey('uid');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwnerId($uid) {
+    $this->set('uid', $uid);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwner(UserInterface $account) {
+    $this->set('uid', $account->id());
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRevisionCreationTime() {
+    return $this->get('revision_timestamp')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRevisionCreationTime($timestamp) {
+    $this->set('revision_timestamp', $timestamp);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRevisionAuthor() {
+    return $this->getRevisionUser();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRevisionUser() {
+    return $this->get('revision_uid')->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRevisionAuthorId($uid) {
+    $this->setRevisionUserId($uid);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRevisionUser(UserInterface $user) {
+    $this->set('revision_uid', $user);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRevisionUserId() {
+    return $this->get('revision_uid')->entity->id();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRevisionUserId($user_id) {
+    $this->set('revision_uid', $user_id);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRevisionLogMessage() {
+    return $this->get('revision_log')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRevisionLogMessage($revision_log_message) {
+    $this->set('revision_log', $revision_log_message);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+    $fields += static::publishedBaseFieldDefinitions($entity_type);
+
+    $fields['title'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Title'))
+      ->setRequired(TRUE)
+      ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE)
+      ->setSetting('max_length', 255)
+      ->setDisplayOptions('view', [
+        'label' => 'hidden',
+        'type' => 'string',
+        'weight' => -5,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -5,
+      ])
+      ->setDisplayConfigurable('form', TRUE);
+
+    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Authored by'))
+      ->setDescription(t('The username of the content author.'))
+      ->setRevisionable(TRUE)
+      ->setSetting('target_type', 'user')
+      ->setDefaultValueCallback('Drupal\node\Entity\Node::getCurrentUserId')
+      ->setTranslatable(TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'hidden',
+        'type' => 'author',
+        'weight' => 0,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'entity_reference_autocomplete',
+        'weight' => 5,
+        'settings' => [
+          'match_operator' => 'CONTAINS',
+          'size' => '60',
+          'placeholder' => '',
+        ],
+      ])
+      ->setDisplayConfigurable('form', TRUE);
+
+    $fields['created'] = BaseFieldDefinition::create('created')
+      ->setLabel(t('Authored on'))
+      ->setDescription(t('The time that the node was created.'))
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'hidden',
+        'type' => 'timestamp',
+        'weight' => 0,
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'datetime_timestamp',
+        'weight' => 10,
+      ])
+      ->setDisplayConfigurable('form', TRUE);
+
+    $fields['changed'] = BaseFieldDefinition::create('changed')
+      ->setLabel(t('Changed'))
+      ->setDescription(t('The time that the node was last edited.'))
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE);
+
+    $fields['promote'] = BaseFieldDefinition::create('boolean')
+      ->setLabel(t('Promoted to front page'))
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE)
+      ->setDefaultValue(TRUE)
+      ->setDisplayOptions('form', [
+        'type' => 'boolean_checkbox',
+        'settings' => [
+          'display_label' => TRUE,
+        ],
+        'weight' => 15,
+      ])
+      ->setDisplayConfigurable('form', TRUE);
+
+    $fields['sticky'] = BaseFieldDefinition::create('boolean')
+      ->setLabel(t('Sticky at top of lists'))
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE)
+      ->setDefaultValue(FALSE)
+      ->setDisplayOptions('form', [
+        'type' => 'boolean_checkbox',
+        'settings' => [
+          'display_label' => TRUE,
+        ],
+        'weight' => 16,
+      ])
+      ->setDisplayConfigurable('form', TRUE);
+
+    $fields['revision_timestamp'] = BaseFieldDefinition::create('created')
+      ->setLabel(t('Revision timestamp'))
+      ->setDescription(t('The time that the current revision was created.'))
+      ->setQueryable(FALSE)
+      ->setRevisionable(TRUE);
+
+    $fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Revision user ID'))
+      ->setDescription(t('The user ID of the author of the current revision.'))
+      ->setSetting('target_type', 'user')
+      ->setQueryable(FALSE)
+      ->setRevisionable(TRUE);
+
+    $fields['revision_log'] = BaseFieldDefinition::create('string_long')
+      ->setLabel(t('Revision log message'))
+      ->setDescription(t('Briefly describe the changes you have made.'))
+      ->setRevisionable(TRUE)
+      ->setDefaultValue('')
+      ->setDisplayOptions('form', [
+        'type' => 'string_textarea',
+        'weight' => 25,
+        'settings' => [
+          'rows' => 4,
+        ],
+      ]);
+
+    $fields['revision_translation_affected'] = BaseFieldDefinition::create('boolean')
+      ->setLabel(t('Revision translation affected'))
+      ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
+      ->setReadOnly(TRUE)
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE);
+
+    return $fields;
+  }
+
+  /**
+   * Default value callback for 'uid' base field definition.
+   *
+   * @see ::baseFieldDefinitions()
+   *
+   * @return array
+   *   An array of default values.
+   */
+  public static function getCurrentUserId() {
+    return [\Drupal::currentUser()->id()];
+  }
+
+}
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 203c2e6331..007b7c640b 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -86,7 +86,10 @@ public function form(array $form, FormStateInterface $form_state) {
     $node = $this->entity;
 
     if ($this->operation == 'edit') {
-      $form['#title'] = $this->t('<em>Edit @type</em> @title', ['@type' => node_get_type_label($node), '@title' => $node->label()]);
+      $form['#title'] = $this->t('<em>Edit @type</em> @title', [
+        '@type' => node_get_type_label($node),
+        '@title' => $node->label()
+      ]);
     }
 
     // Changed must be sent to the client, for later overwrite error checking.
@@ -99,6 +102,15 @@ public function form(array $form, FormStateInterface $form_state) {
 
     $form['advanced']['#attributes']['class'][] = 'entity-meta';
 
+    $form['footer'] = [
+      '#type' => 'container',
+      '#weight' => 99,
+      '#attributes' => [
+        'class' => ['node-form-footer']
+      ]
+    ];
+    $form['status']['#group'] = 'footer';
+
     // Node author information for administrators.
     $form['author'] = [
       '#type' => 'details',
@@ -147,8 +159,6 @@ public function form(array $form, FormStateInterface $form_state) {
 
     $form['#attached']['library'][] = 'node/form';
 
-    $form['#entity_builders']['update_status'] = '::updateStatus';
-
     return $form;
   }
 
@@ -165,6 +175,9 @@ public function form(array $form, FormStateInterface $form_state) {
    *   The current state of the form.
    *
    * @see \Drupal\node\NodeForm::form()
+   *
+   * @deprecated in Drupal 8.4.x, will be removed before Drupal 9.0.0.
+   *   The "Publish" button was removed.
    */
   public function updateStatus($entity_type_id, NodeInterface $node, array $form, FormStateInterface $form_state) {
     $element = $form_state->getTriggeringElement();
@@ -183,59 +196,6 @@ protected function actions(array $form, FormStateInterface $form_state) {
 
     $element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || $form_state->get('has_been_previewed');
 
-    // If saving is an option, privileged users get dedicated form submit
-    // buttons to adjust the publishing status while saving in one go.
-    // @todo This adjustment makes it close to impossible for contributed
-    //   modules to integrate with "the Save operation" of this form. Modules
-    //   need a way to plug themselves into 1) the ::submit() step, and
-    //   2) the ::save() step, both decoupled from the pressed form button.
-    if ($element['submit']['#access'] && \Drupal::currentUser()->hasPermission('administer nodes')) {
-      // isNew | prev status » default   & publish label             & unpublish label
-      // 1     | 1           » publish   & Save and publish          & Save as unpublished
-      // 1     | 0           » unpublish & Save and publish          & Save as unpublished
-      // 0     | 1           » publish   & Save and keep published   & Save and unpublish
-      // 0     | 0           » unpublish & Save and keep unpublished & Save and publish
-
-      // Add a "Publish" button.
-      $element['publish'] = $element['submit'];
-      // If the "Publish" button is clicked, we want to update the status to "published".
-      $element['publish']['#published_status'] = TRUE;
-      $element['publish']['#dropbutton'] = 'save';
-      if ($node->isNew()) {
-        $element['publish']['#value'] = t('Save and publish');
-      }
-      else {
-        $element['publish']['#value'] = $node->isPublished() ? t('Save and keep published') : t('Save and publish');
-      }
-      $element['publish']['#weight'] = 0;
-
-      // Add a "Unpublish" button.
-      $element['unpublish'] = $element['submit'];
-      // If the "Unpublish" button is clicked, we want to update the status to "unpublished".
-      $element['unpublish']['#published_status'] = FALSE;
-      $element['unpublish']['#dropbutton'] = 'save';
-      if ($node->isNew()) {
-        $element['unpublish']['#value'] = t('Save as unpublished');
-      }
-      else {
-        $element['unpublish']['#value'] = !$node->isPublished() ? t('Save and keep unpublished') : t('Save and unpublish');
-      }
-      $element['unpublish']['#weight'] = 10;
-
-      // If already published, the 'publish' button is primary.
-      if ($node->isPublished()) {
-        unset($element['unpublish']['#button_type']);
-      }
-      // Otherwise, the 'unpublish' button is primary and should come first.
-      else {
-        unset($element['publish']['#button_type']);
-        $element['unpublish']['#weight'] = -10;
-      }
-
-      // Remove the "Save" button.
-      $element['submit']['#access'] = FALSE;
-    }
-
     $element['preview'] = [
       '#type' => 'submit',
       '#access' => $preview_mode != DRUPAL_DISABLED && ($node->access('create') || $node->access('update')),
diff --git a/core/modules/node/src/Tests/Update/NodeUpdateTest.php b/core/modules/node/src/Tests/Update/NodeUpdateTest.php
index b8b30be4f1..b193ea8d21 100644
--- a/core/modules/node/src/Tests/Update/NodeUpdateTest.php
+++ b/core/modules/node/src/Tests/Update/NodeUpdateTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\node\Tests\Update;
 
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
 use Drupal\system\Tests\Update\UpdatePathTestBase;
 
 /**
@@ -38,4 +39,29 @@ public function testPublishedEntityKey() {
     $this->assertEqual('status', $entity_type->getKey('published'));
   }
 
+  /**
+   * Tests that the node entity form has the status checkbox.
+   *
+   * @see node_post_update_configure_status_field_widget()
+   */
+  public function testStatusCheckbox() {
+    // Run updates.
+    $this->runUpdates();
+
+    $query = \Drupal::entityQuery('entity_form_display')
+      ->condition('targetEntityType', 'node');
+    $ids = $query->execute();
+    $form_displays = EntityFormDisplay::loadMultiple($ids);
+
+    /**
+     * @var string $id
+     * @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
+     */
+    foreach ($form_displays as $id => $form_display) {
+      $component = $form_display->getComponent('status');
+      $this->assertEqual('boolean_checkbox', $component['type']);
+      $this->assertEqual(['display_label' => TRUE], $component['settings']);
+    }
+  }
+
 }
diff --git a/core/modules/node/tests/src/Functional/NodeEditFormTest.php b/core/modules/node/tests/src/Functional/NodeEditFormTest.php
index ea2a168635..ce032e5909 100644
--- a/core/modules/node/tests/src/Functional/NodeEditFormTest.php
+++ b/core/modules/node/tests/src/Functional/NodeEditFormTest.php
@@ -98,7 +98,7 @@ public function testNodeEdit() {
     $edit['title[0][value]'] = $this->randomMachineName(8);
     $edit[$body_key] = $this->randomMachineName(16);
     $edit['revision'] = TRUE;
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
 
     // Ensure that the node revision has been created.
     $revised_node = $this->drupalGetNodeByTitle($edit['title[0][value]'], TRUE);
@@ -124,12 +124,21 @@ public function testNodeEdit() {
     $edit['created[0][value][date]'] = $this->randomMachineName(8);
     // Get the current amount of open details elements.
     $open_details_elements = count($this->cssSelect('details[open="open"]'));
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
     // The node author details must be open.
     $this->assertRaw('<details class="node-form-author js-form-wrapper form-wrapper" data-drupal-selector="edit-author" id="edit-author" open="open">');
     // Only one extra details element should now be open.
     $open_details_elements++;
     $this->assertEqual(count($this->cssSelect('details[open="open"]')), $open_details_elements, 'Exactly one extra open &lt;details&gt; element found.');
+
+    // Edit the same node, save it and verify it's unpublished after unchecking
+    // the 'Published' boolean_checkbox and clicking 'Save'.
+    $this->drupalGet("node/" . $node->id() . "/edit");
+    $edit = ['status[value]' => FALSE];
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $this->nodeStorage->resetCache([$node->id()]);
+    $node = $this->nodeStorage->load($node->id());
+    $this->assertFalse($node->isPublished(), 'Node is unpublished');
   }
 
   /**
@@ -143,7 +152,7 @@ public function testNodeEditAuthoredBy() {
     $edit = [];
     $edit['title[0][value]'] = $this->randomMachineName(8);
     $edit[$body_key] = $this->randomMachineName(16);
-    $this->drupalPostForm('node/add/page', $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/page', $edit, t('Save'));
 
     // Check that the node was authored by the currently logged in user.
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
@@ -172,7 +181,7 @@ public function testNodeEditAuthoredBy() {
     $this->drupalLogin($this->adminUser);
 
     // Save the node without making any changes.
-    $this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save'));
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     $this->assertIdentical($this->webUser->id(), $node->getOwner()->id());
@@ -184,7 +193,7 @@ public function testNodeEditAuthoredBy() {
 
     // Check that saving the node without making any changes keeps the proper
     // author ID.
-    $this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', [], t('Save'));
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     $this->assertIdentical($this->webUser->id(), $node->getOwner()->id());
@@ -203,13 +212,13 @@ protected function checkVariousAuthoredByValues(NodeInterface $node, $form_eleme
     $edit = [
       $form_element_name => 'invalid-name',
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
     $this->assertRaw(t('There are no entities matching "%name".', ['%name' => 'invalid-name']));
 
     // Change the authored by field to an empty string, which should assign
     // authorship to the anonymous user (uid 0).
     $edit[$form_element_name] = '';
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     $uid = $node->getOwnerId();
@@ -228,7 +237,7 @@ protected function checkVariousAuthoredByValues(NodeInterface $node, $form_eleme
     // Change the authored by field to another user's name (that is not
     // logged in).
     $edit[$form_element_name] = $this->webUser->getUsername();
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
     $this->nodeStorage->resetCache([$node->id()]);
     $node = $this->nodeStorage->load($node->id());
     $this->assertIdentical($node->getOwnerId(), $this->webUser->id(), 'Node authored by normal user.');
diff --git a/core/modules/node/tests/src/Functional/NodeFormButtonsTest.php b/core/modules/node/tests/src/Functional/NodeFormButtonsTest.php
deleted file mode 100644
index 546a6334ad..0000000000
--- a/core/modules/node/tests/src/Functional/NodeFormButtonsTest.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-namespace Drupal\Tests\node\Functional;
-
-/**
- * Tests all the different buttons on the node form.
- *
- * @group node
- */
-class NodeFormButtonsTest extends NodeTestBase {
-
-  use AssertButtonsTrait;
-
-  /**
-   * A normal logged in user.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $webUser;
-
-  /**
-   * A user with permission to bypass access content.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $adminUser;
-
-  protected function setUp() {
-    parent::setUp();
-
-    // Create a user that has no access to change the state of the node.
-    $this->webUser = $this->drupalCreateUser(['create article content', 'edit own article content']);
-    // Create a user that has access to change the state of the node.
-    $this->adminUser = $this->drupalCreateUser(['administer nodes', 'bypass node access']);
-  }
-
-  /**
-   * Tests that the right buttons are displayed for saving nodes.
-   */
-  public function testNodeFormButtons() {
-    $node_storage = $this->container->get('entity.manager')->getStorage('node');
-    // Log in as administrative user.
-    $this->drupalLogin($this->adminUser);
-
-    // Verify the buttons on a node add form.
-    $this->drupalGet('node/add/article');
-    $this->assertButtons([t('Save and publish'), t('Save as unpublished')]);
-
-    // Save the node and assert it's published after clicking
-    // 'Save and publish'.
-    $edit = ['title[0][value]' => $this->randomString()];
-    $this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
-
-    // Get the node.
-    $node_1 = $node_storage->load(1);
-    $this->assertTrue($node_1->isPublished(), 'Node is published');
-
-    // Verify the buttons on a node edit form.
-    $this->drupalGet('node/' . $node_1->id() . '/edit');
-    $this->assertButtons([t('Save and keep published'), t('Save and unpublish')]);
-
-    // Save the node and verify it's still published after clicking
-    // 'Save and keep published'.
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
-    $node_storage->resetCache([1]);
-    $node_1 = $node_storage->load(1);
-    $this->assertTrue($node_1->isPublished(), 'Node is published');
-
-    // Save the node and verify it's unpublished after clicking
-    // 'Save and unpublish'.
-    $this->drupalPostForm('node/' . $node_1->id() . '/edit', $edit, t('Save and unpublish'));
-    $node_storage->resetCache([1]);
-    $node_1 = $node_storage->load(1);
-    $this->assertFalse($node_1->isPublished(), 'Node is unpublished');
-
-    // Verify the buttons on an unpublished node edit screen.
-    $this->drupalGet('node/' . $node_1->id() . '/edit');
-    $this->assertButtons([t('Save and keep unpublished'), t('Save and publish')]);
-
-    // Create a node as a normal user.
-    $this->drupalLogout();
-    $this->drupalLogin($this->webUser);
-
-    // Verify the buttons for a normal user.
-    $this->drupalGet('node/add/article');
-    $this->assertButtons([t('Save')], FALSE);
-
-    // Create the node.
-    $edit = ['title[0][value]' => $this->randomString()];
-    $this->drupalPostForm('node/add/article', $edit, t('Save'));
-    $node_2 = $node_storage->load(2);
-    $this->assertTrue($node_2->isPublished(), 'Node is published');
-
-    // Log in as an administrator and unpublish the node that just
-    // was created by the normal user.
-    $this->drupalLogout();
-    $this->drupalLogin($this->adminUser);
-    $this->drupalPostForm('node/' . $node_2->id() . '/edit', [], t('Save and unpublish'));
-    $node_storage->resetCache([2]);
-    $node_2 = $node_storage->load(2);
-    $this->assertFalse($node_2->isPublished(), 'Node is unpublished');
-
-    // Log in again as the normal user, save the node and verify
-    // it's still unpublished.
-    $this->drupalLogout();
-    $this->drupalLogin($this->webUser);
-    $this->drupalPostForm('node/' . $node_2->id() . '/edit', [], t('Save'));
-    $node_storage->resetCache([2]);
-    $node_2 = $node_storage->load(2);
-    $this->assertFalse($node_2->isPublished(), 'Node is still unpublished');
-    $this->drupalLogout();
-
-    // Set article content type default to unpublished. This will change the
-    // the initial order of buttons and/or status of the node when creating
-    // a node.
-    $fields = \Drupal::entityManager()->getFieldDefinitions('node', 'article');
-    $fields['status']->getConfig('article')
-      ->setDefaultValue(FALSE)
-      ->save();
-
-    // Verify the buttons on a node add form for an administrator.
-    $this->drupalLogin($this->adminUser);
-    $this->drupalGet('node/add/article');
-    $this->assertButtons([t('Save as unpublished'), t('Save and publish')]);
-
-    // Verify the node is unpublished by default for a normal user.
-    $this->drupalLogout();
-    $this->drupalLogin($this->webUser);
-    $edit = ['title[0][value]' => $this->randomString()];
-    $this->drupalPostForm('node/add/article', $edit, t('Save'));
-    $node_3 = $node_storage->load(3);
-    $this->assertFalse($node_3->isPublished(), 'Node is unpublished');
-  }
-
-}
diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsUiBypassAccessTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsUiBypassAccessTest.php
index a3a575d77e..186b694774 100644
--- a/core/modules/node/tests/src/Functional/NodeRevisionsUiBypassAccessTest.php
+++ b/core/modules/node/tests/src/Functional/NodeRevisionsUiBypassAccessTest.php
@@ -67,7 +67,7 @@ public function testDisplayRevisionTab() {
 
     // Uncheck the create new revision checkbox and save the node.
     $edit = ['revision' => FALSE];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save and keep published');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
 
     $this->assertUrl($node->toUrl());
     $this->assertNoLink(t('Revisions'));
@@ -78,7 +78,7 @@ public function testDisplayRevisionTab() {
 
     // Submit the form without changing the checkbox.
     $edit = [];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save and keep published');
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
 
     $this->assertUrl($node->toUrl());
     $this->assertLink(t('Revisions'));
diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
index cc364cda29..a437e4aba8 100644
--- a/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
+++ b/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
@@ -55,7 +55,7 @@ public function testNodeFormSaveWithoutRevision() {
 
     // Uncheck the create new revision checkbox and save the node.
     $edit = ['revision' => FALSE];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
 
     // Load the node again and check the revision is the same as before.
     $node_storage->resetCache([$node->id()]);
@@ -68,7 +68,7 @@ public function testNodeFormSaveWithoutRevision() {
 
     // Submit the form without changing the checkbox.
     $edit = [];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
 
     // Load the node again and check the revision is different from before.
     $node_storage->resetCache([$node->id()]);
diff --git a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
index a8a74544ec..a44bea131e 100644
--- a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
+++ b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
@@ -99,7 +99,9 @@ public function testPublishedStatusNoFields() {
       'source' => $default_langcode,
       'target' => $langcode
     ], ['language' => $language]);
-    $this->drupalPostForm($add_url, $this->getEditValues($values, $langcode), t('Save and unpublish (this translation)'));
+    $edit = $this->getEditValues($values, $langcode);
+    $edit['status[value]'] = FALSE;
+    $this->drupalPostForm($add_url, $edit, t('Save (this translation)'));
 
     $storage->resetCache([$this->entityId]);
     $entity = $storage->load($this->entityId);
@@ -139,18 +141,6 @@ protected function getNewEntityValues($langcode) {
   /**
    * {@inheritdoc}
    */
-  protected function getFormSubmitAction(EntityInterface $entity, $langcode) {
-    if ($entity->getTranslation($langcode)->isPublished()) {
-      return t('Save and keep published') . $this->getFormSubmitSuffix($entity, $langcode);
-    }
-    else {
-      return t('Save and keep unpublished') . $this->getFormSubmitSuffix($entity, $langcode);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   protected function doTestPublishedStatus() {
     $storage = $this->container->get('entity_type.manager')
       ->getStorage($this->entityTypeId);
@@ -158,18 +148,18 @@ protected function doTestPublishedStatus() {
     $entity = $storage->load($this->entityId);
     $languages = $this->container->get('language_manager')->getLanguages();
 
-    $actions = [
-      t('Save and keep published'),
-      t('Save and unpublish'),
+    $statuses = [
+      TRUE,
+      FALSE,
     ];
 
-    foreach ($actions as $index => $action) {
+    foreach ($statuses as $index => $value) {
       // (Un)publish the node translations and check that the translation
       // statuses are (un)published accordingly.
       foreach ($this->langcodes as $langcode) {
         $options = ['language' => $languages[$langcode]];
         $url = $entity->urlInfo('edit-form', $options);
-        $this->drupalPostForm($url, [], $action . $this->getFormSubmitSuffix($entity, $langcode), $options);
+        $this->drupalPostForm($url, ['status[value]' => $value], t('Save') . $this->getFormSubmitSuffix($entity, $langcode), $options);
       }
       $storage->resetCache([$this->entityId]);
       $entity = $storage->load($this->entityId);
diff --git a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
index 836740dc44..e29a1d0550 100644
--- a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
+++ b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
@@ -328,7 +328,7 @@ public function testNodeDisplay() {
     $edit = [
       $this->fieldName => '1',
     ];
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
 
     // Check the node page and see if the values are correct.
     $file_formatters = ['list_default', 'list_key'];
diff --git a/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php b/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php
index 191e0cf0d5..cc77f2b6a6 100644
--- a/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php
+++ b/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php
@@ -47,7 +47,7 @@ protected function setUp() {
     // also needs the word "pizza" so we can use it as the search keyword.
     $body_key = 'body[0][value]';
     $edit[$body_key] = \Drupal::l($node->label(), $node->urlInfo()) . ' pizza sandwich';
-    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
 
     $this->container->get('plugin.manager.search')->createInstance('node_search')->updateIndex();
     search_update_totals();
diff --git a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
index 64c8c15977..39303fffcd 100644
--- a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
+++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
@@ -206,7 +206,7 @@ public function testBreadCrumbs() {
     $edit = [
       'menu[menu_parent]' => $link->getMenuName() . ':' . $link->getPluginId(),
     ];
-    $this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save'));
     $expected = [
       "node" => $link->getTitle(),
     ];
@@ -227,7 +227,7 @@ public function testBreadCrumbs() {
     $edit = [
       'field_tags[target_id]' => implode(',', array_keys($tags)),
     ];
-    $this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save and keep published'));
+    $this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save'));
 
     // Put both terms into a hierarchy Drupal » Breadcrumbs. Required for both
     // the menu links and the terms itself, since taxonomy_term_page() resets
diff --git a/core/modules/system/src/Tests/Update/UpdatePathRC1TestBaseFilledTest.php b/core/modules/system/src/Tests/Update/UpdatePathRC1TestBaseFilledTest.php
index 09cc586bc7..9ee6d0cd4d 100644
--- a/core/modules/system/src/Tests/Update/UpdatePathRC1TestBaseFilledTest.php
+++ b/core/modules/system/src/Tests/Update/UpdatePathRC1TestBaseFilledTest.php
@@ -117,7 +117,7 @@ public function testUpdatedSite() {
     $this->assertText('Test Article - New title');
     $this->assertText('Test 1');
     $this->assertRaw('0.01');
-    $this->drupalPostForm('node/8/edit', [], 'Save and keep published (this translation)');
+    $this->drupalPostForm('node/8/edit', [], 'Save (this translation)');
     $this->assertResponse(200);
     $this->drupalGet('node/8/edit', ['language' => $spanish]);
     $this->assertText('Test title Spanish');
diff --git a/core/modules/system/src/Tests/Update/UpdatePathTestBaseFilledTest.php b/core/modules/system/src/Tests/Update/UpdatePathTestBaseFilledTest.php
index fe62340af8..3c5aab057e 100644
--- a/core/modules/system/src/Tests/Update/UpdatePathTestBaseFilledTest.php
+++ b/core/modules/system/src/Tests/Update/UpdatePathTestBaseFilledTest.php
@@ -117,7 +117,7 @@ public function testUpdatedSite() {
     $this->assertText('Test Article - New title');
     $this->assertText('Test 1');
     $this->assertRaw('0.01');
-    $this->drupalPostForm('node/8/edit', [], 'Save and keep published (this translation)');
+    $this->drupalPostForm('node/8/edit', [], 'Save (this translation)');
     $this->assertResponse(200);
     $this->drupalGet('node/8/edit', ['language' => $spanish]);
     $this->assertText('Test title Spanish');
diff --git a/core/modules/taxonomy/tests/src/Functional/LegacyTest.php b/core/modules/taxonomy/tests/src/Functional/LegacyTest.php
index 289471533b..ac8e82dd66 100644
--- a/core/modules/taxonomy/tests/src/Functional/LegacyTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/LegacyTest.php
@@ -60,7 +60,7 @@ public function testTaxonomyLegacyNode() {
     $edit['created[0][value][time]'] = $date->format('H:i:s');
     $edit['body[0][value]'] = $this->randomMachineName();
     $edit['field_tags[target_id]'] = $this->randomMachineName();
-    $this->drupalPostForm('node/add/article', $edit, t('Save and publish'));
+    $this->drupalPostForm('node/add/article', $edit, t('Save'));
     // Checks that the node has been saved.
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertEqual($node->getCreatedTime(), $date->getTimestamp(), 'Legacy node was saved with the right date.');
diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
index c94e36e388..9082f2d1bd 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
@@ -66,6 +66,13 @@ content:
     weight: 15
     region: content
     third_party_settings: {  }
+  status:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 120
+    region: content
+    third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
index 0b7ffd133c..682f1a550c 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
@@ -40,6 +40,13 @@ content:
     weight: 15
     region: content
     third_party_settings: {  }
+  status:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 120
+    region: content
+    third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
diff --git a/core/themes/bartik/css/components/form.css b/core/themes/bartik/css/components/form.css
index 3e46920757..5ee577a158 100644
--- a/core/themes/bartik/css/components/form.css
+++ b/core/themes/bartik/css/components/form.css
@@ -172,6 +172,14 @@ input.form-submit:focus {
 .node-form .form-wrapper {
   margin-bottom: 2em;
 }
+.node-form .node-form-footer,
+.node-form .field--name-status {
+  margin-bottom: 0;
+}
+.node-form .form-actions {
+  padding-top: 0;
+  margin-top: 0;
+}
 
 /* Contact Form */
 .contact-form #edit-name {
diff --git a/core/themes/seven/css/layout/node-add.css b/core/themes/seven/css/layout/node-add.css
index 22faf339bb..f28a1e5f3e 100644
--- a/core/themes/seven/css/layout/node-add.css
+++ b/core/themes/seven/css/layout/node-add.css
@@ -1,3 +1,9 @@
+.layout-region-node-footer__content {
+  border-top: 1px solid #bebfb9;
+  padding-top: 0.5em;
+  margin-top: 1.5em;
+}
+
 /**
  * Widescreen
  *
@@ -14,4 +20,7 @@
     margin-top: 1em;
     margin-bottom: 1em;
   }
+  .layout-region-node-footer__content {
+    margin-top: 0.5em;
+  }
 }
diff --git a/core/themes/seven/templates/node-edit-form.html.twig b/core/themes/seven/templates/node-edit-form.html.twig
new file mode 100644
index 0000000000..cf747f9292
--- /dev/null
+++ b/core/themes/seven/templates/node-edit-form.html.twig
@@ -0,0 +1,31 @@
+{#
+/**
+ * @file
+ * Theme override for a node edit form.
+ *
+ * Two column template for the node add/edit form.
+ *
+ * This template will be used when a node edit form specifies 'node_edit_form'
+ * as its #theme callback.  Otherwise, by default, node add/edit forms will be
+ * themed by form.html.twig.
+ *
+ * Available variables:
+ * - form: The node add/edit form.
+ *
+ * @see seven_form_node_form_alter()
+ */
+#}
+<div class="layout-node-form clearfix">
+  <div class="layout-region layout-region-node-main">
+    {{ form|without('advanced', 'footer', 'actions') }}
+  </div>
+  <div class="layout-region layout-region-node-secondary">
+    {{ form.advanced }}
+  </div>
+  <div class="layout-region layout-region-node-footer">
+    <div class="layout-region-node-footer__content">
+      {{ form.footer }}
+      {{ form.actions }}
+    </div>
+  </div>
+</div>
