diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 2b9890f..0c2be9d 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -1674,207 +1674,13 @@ function comment_edit_page(Comment $comment) {
 }
 
 /**
- * Form constructor for the basic commenting form.
- *
- * @see comment_form_validate()
- * @see comment_form_submit()
- * @see comment_form_build_preview()
- * @ingroup forms
- */
-function comment_form($form, &$form_state, Comment $comment) {
-  global $user;
-  $language_content = drupal_container()->get(LANGUAGE_TYPE_CONTENT);
-
-  $node = node_load($comment->nid);
-  // @todo Remove all the usages of $form['#node'].
-  $form['#node'] = $node;
-  $form_state['node'] = $node;
-
-  // Use #comment-form as unique jump target, regardless of node type.
-  $form['#id'] = drupal_html_id('comment_form');
-  $form['#theme'] = array('comment_form__node_' . $node->type, 'comment_form');
-
-  $anonymous_contact = variable_get('comment_anonymous_' . $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT);
-  $is_admin = (!empty($comment->cid) && user_access('administer comments'));
-
-  if (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
-    $form['#attached']['library'][] = array('system', 'jquery.cookie');
-    $form['#attributes']['class'][] = 'user-info-from-cookie';
-  }
-
-  // If not replying to a comment, use our dedicated page callback for new
-  // comments on nodes.
-  if (empty($comment->cid) && empty($comment->pid)) {
-    $form['#action'] = url('comment/reply/' . $comment->nid);
-  }
-
-  if (isset($form_state['comment_preview'])) {
-    $form += $form_state['comment_preview'];
-  }
-
-  $form['author'] = array(
-    '#weight' => 10,
-  );
-  // Display author information in a fieldset for comment moderators.
-  if ($is_admin) {
-    $form['author'] += array(
-      '#type' => 'fieldset',
-      '#title' => t('Administration'),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-    );
-  }
-
-  // Prepare default values for form elements.
-  if ($is_admin) {
-    $author = (!$comment->uid && $comment->name ? $comment->name : $comment->registered_name);
-    $status = (isset($comment->status) ? $comment->status : COMMENT_NOT_PUBLISHED);
-    $date = (!empty($comment->date) ? $comment->date : format_date($comment->created, 'custom', 'Y-m-d H:i O'));
-  }
-  else {
-    if ($user->uid) {
-      $author = $user->name;
-    }
-    else {
-      $author = ($comment->name ? $comment->name : '');
-    }
-    $status = (user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED);
-    $date = '';
-  }
-
-  // Add the author name field depending on the current user.
-  if ($is_admin) {
-    $form['author']['name'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Authored by'),
-      '#default_value' => $author,
-      '#maxlength' => 60,
-      '#size' => 30,
-      '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
-      '#autocomplete_path' => 'user/autocomplete',
-    );
-  }
-  elseif ($user->uid) {
-    $form['author']['_author'] = array(
-      '#type' => 'item',
-      '#title' => t('Your name'),
-      '#markup' => theme('username', array('account' => $user)),
-    );
-    $form['author']['name'] = array(
-      '#type' => 'value',
-      '#value' => $author,
-    );
-  }
-  else {
-    $form['author']['name'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Your name'),
-      '#default_value' => $author,
-      '#required' => (!$user->uid && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
-      '#maxlength' => 60,
-      '#size' => 30,
-    );
-  }
-
-  // Add author e-mail and homepage fields depending on the current user.
-  $form['author']['mail'] = array(
-    '#type' => 'email',
-    '#title' => t('E-mail'),
-    '#default_value' => $comment->mail,
-    '#required' => (!$user->uid && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
-    '#maxlength' => 64,
-    '#size' => 30,
-    '#description' => t('The content of this field is kept private and will not be shown publicly.'),
-    '#access' => $is_admin || (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
-  );
-  $form['author']['homepage'] = array(
-    '#type' => 'url',
-    '#title' => t('Homepage'),
-    '#default_value' => $comment->homepage,
-    '#maxlength' => 255,
-    '#size' => 30,
-    '#access' => $is_admin || (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
-  );
-
-  // Add administrative comment publishing options.
-  $form['author']['date'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Authored on'),
-    '#default_value' => $date,
-    '#maxlength' => 25,
-    '#size' => 20,
-    '#access' => $is_admin,
-  );
-  $form['author']['status'] = array(
-    '#type' => 'radios',
-    '#title' => t('Status'),
-    '#default_value' => $status,
-    '#options' => array(
-      COMMENT_PUBLISHED => t('Published'),
-      COMMENT_NOT_PUBLISHED => t('Not published'),
-    ),
-    '#access' => $is_admin,
-  );
-
-  $form['subject'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Subject'),
-    '#maxlength' => 64,
-    '#default_value' => $comment->subject,
-    '#access' => variable_get('comment_subject_field_' . $node->type, 1) == 1,
-  );
-
-  // Used for conditional validation of author fields.
-  $form['is_anonymous'] = array(
-    '#type' => 'value',
-    '#value' => ($comment->cid ? !$comment->uid : !$user->uid),
-  );
-
-  // Add internal comment properties.
-  foreach (array('cid', 'pid', 'nid', 'uid') as $key) {
-    $form[$key] = array('#type' => 'value', '#value' => $comment->$key);
-  }
-  $form['node_type'] = array('#type' => 'value', '#value' => 'comment_node_' . $node->type);
-
-  // Make the comment inherit the node language unless specifically set.
-  $comment_langcode = $comment->langcode;
-  if ($comment_langcode == LANGUAGE_NOT_SPECIFIED) {
-    $comment_langcode = $language_content->langcode;
-  }
-
-  // Uses the language of the content as comment language.
-  $form['langcode'] = array(
-    '#type' => 'value',
-    '#value' => $comment_langcode,
-  );
-
-  // Attach fields.
-  $comment->node_type = 'comment_node_' . $node->type;
-
-  return $form;
-}
-
-/**
- * Form submission handler for the 'preview' button in comment_form().
- */
-function comment_form_build_preview($form, &$form_state) {
-  $comment = EntityFormController::getFormInstance($form_state)->getEntity();
-  $form_state['comment_preview'] = comment_preview($comment);
-  $form_state['rebuild'] = TRUE;
-}
-
-/**
  * Generates a comment preview.
  *
  * @param Drupal\comment\Comment $comment
- *
- * @see comment_form_build_preview()
  */
 function comment_preview(Comment $comment) {
   global $user;
-
-  drupal_set_title(t('Preview comment'), PASS_THROUGH);
-
+  $preview_build = array();
   $node = node_load($comment->nid);
 
   if (!form_get_errors()) {
@@ -1904,7 +1710,7 @@ function comment_preview(Comment $comment) {
     $comment_build = comment_view($comment, $node);
     $comment_build['#weight'] = -100;
 
-    $form['comment_preview'] = $comment_build;
+    $preview_build['comment_preview'] = $comment_build;
   }
 
   if ($comment->pid) {
@@ -1918,147 +1724,10 @@ function comment_preview(Comment $comment) {
     $build = node_view($node);
   }
 
-  $form['comment_output_below'] = $build;
-  $form['comment_output_below']['#weight'] = 100;
-
-  return $form;
-}
-
-/**
- * Form validation handler for comment_form().
- *
- * @see comment_form_submit()
- */
-function comment_form_validate($form, &$form_state) {
-  global $user;
-
-  if (!empty($form_state['values']['cid'])) {
-    // Verify the name in case it is being changed from being anonymous.
-    $account = user_load_by_name($form_state['values']['name']);
-    $form_state['values']['uid'] = $account ? $account->uid : 0;
+  $preview_build['comment_output_below'] = $build;
+  $preview_build['comment_output_below']['#weight'] = 100;
 
-    if ($form_state['values']['date'] && strtotime($form_state['values']['date']) === FALSE) {
-      form_set_error('date', t('You have to specify a valid date.'));
-    }
-    if ($form_state['values']['name'] && !$form_state['values']['is_anonymous'] && !$account) {
-      form_set_error('name', t('You have to specify a valid author.'));
-    }
-  }
-  elseif ($form_state['values']['is_anonymous']) {
-    // Validate anonymous comment author fields (if given). If the (original)
-    // author of this comment was an anonymous user, verify that no registered
-    // user with this name exists.
-    if ($form_state['values']['name']) {
-      $query = db_select('users', 'u');
-      $query->addField('u', 'uid', 'uid');
-      $taken = $query
-        ->condition('name', db_like($form_state['values']['name']), 'LIKE')
-        ->countQuery()
-        ->execute()
-        ->fetchField();
-      if ($taken) {
-        form_set_error('name', t('The name you used belongs to a registered user.'));
-      }
-    }
-  }
-}
-
-/**
- * Prepare a comment for submission.
- *
- * @param Drupal\comment\Comment $comment
- *
- */
-function comment_submit(Comment $comment) {
-  if (empty($comment->date)) {
-    $comment->date = 'now';
-  }
-  $comment->created = strtotime($comment->date);
-  $comment->changed = REQUEST_TIME;
-
-  // If the comment was posted by a registered user, assign the author's ID.
-  // @todo Too fragile. Should be prepared and stored in comment_form() already.
-  if (!$comment->is_anonymous && !empty($comment->name) && ($account = user_load_by_name($comment->name))) {
-    $comment->uid = $account->uid;
-  }
-  // If the comment was posted by an anonymous user and no author name was
-  // required, use "Anonymous" by default.
-  if ($comment->is_anonymous && (!isset($comment->name) || $comment->name === '')) {
-    $comment->name = variable_get('anonymous', t('Anonymous'));
-  }
-
-  // Validate the comment's subject. If not specified, extract from comment body.
-  if (trim($comment->subject) == '') {
-    // The body may be in any format, so:
-    // 1) Filter it into HTML
-    // 2) Strip out all HTML tags
-    // 3) Convert entities back to plain-text.
-    $comment_body = $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0];
-    if (isset($comment_body['format'])) {
-      $comment_text = check_markup($comment_body['value'], $comment_body['format']);
-    }
-    else {
-      $comment_text = check_plain($comment_body['value']);
-    }
-    $comment->subject = truncate_utf8(trim(decode_entities(strip_tags($comment_text))), 29, TRUE);
-    // Edge cases where the comment body is populated only by HTML tags will
-    // require a default subject.
-    if ($comment->subject == '') {
-      $comment->subject = t('(No subject)');
-    }
-  }
-  return $comment;
-}
-
-/**
- * Form submission handler for comment_form().
- *
- * @see comment_form_validate()
- * @see comment_form_submit_build_comment()
- */
-function comment_form_submit($form, &$form_state) {
-  $node = node_load($form_state['values']['nid']);
-  $comment = EntityFormController::getFormInstance($form_state)->getEntity();
-  if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) {
-    // Save the anonymous user information to a cookie for reuse.
-    if (user_is_anonymous()) {
-      user_cookie_save(array_intersect_key($form_state['values'], array_flip(array('name', 'mail', 'homepage'))));
-    }
-
-    comment_save($comment);
-    $form_state['values']['cid'] = $comment->cid;
-
-    // Add an entry to the watchdog log.
-    watchdog('content', 'Comment posted: %subject.', array('%subject' => $comment->subject), WATCHDOG_NOTICE, l(t('view'), 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid)));
-
-    // Explain the approval queue if necessary.
-    if ($comment->status == COMMENT_NOT_PUBLISHED) {
-      if (!user_access('administer comments')) {
-        drupal_set_message(t('Your comment has been queued for review by site administrators and will be published after approval.'));
-      }
-    }
-    else {
-      drupal_set_message(t('Your comment has been posted.'));
-    }
-    $query = array();
-    // Find the current display page for this comment.
-    $page = comment_get_display_page($comment->cid, $node->type);
-    if ($page > 0) {
-      $query['page'] = $page;
-    }
-    // Redirect to the newly posted comment.
-    $redirect = array('node/' . $node->nid, array('query' => $query, 'fragment' => 'comment-' . $comment->cid));
-  }
-  else {
-    watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject), WATCHDOG_WARNING);
-    drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject)), 'error');
-    // Redirect the user to the node they are commenting on.
-    $redirect = 'node/' . $node->nid;
-  }
-  $form_state['redirect'] = $redirect;
-  // Clear the block and page caches so that anonymous users see the comment
-  // they have posted.
-  cache_invalidate(array('content' => TRUE));
+  return $preview_build;
 }
 
 /**
diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
index 1f45286..c020443 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
@@ -19,7 +19,179 @@ class CommentFormController extends EntityFormController {
    * @see Drupal\entity\EntityFormController::form()
    */
   protected function form(array $form, array &$form_state) {
-    $form = comment_form($form, $form_state, $this->getEntity());
+    global $user;
+    $language_content = drupal_container()->get(LANGUAGE_TYPE_CONTENT);
+    $comment = $this->getEntity();
+
+    $node = node_load($comment->nid);
+    // @todo Remove all the usages of $form['#node'].
+    $form['#node'] = $node;
+    $form_state['node'] = $node;
+
+    // Use #comment-form as unique jump target, regardless of node type.
+    $form['#id'] = drupal_html_id('comment_form');
+    $form['#theme'] = array('comment_form__node_' . $node->type, 'comment_form');
+
+    $anonymous_contact = variable_get('comment_anonymous_' . $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT);
+    $is_admin = (!empty($comment->cid) && user_access('administer comments'));
+
+    if (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
+      $form['#attached']['library'][] = array('system', 'jquery.cookie');
+      $form['#attributes']['class'][] = 'user-info-from-cookie';
+    }
+
+    // If not replying to a comment, use our dedicated page callback for new
+    // comments on nodes.
+    if (empty($comment->cid) && empty($comment->pid)) {
+      $form['#action'] = url('comment/reply/' . $comment->nid);
+    }
+
+    if (isset($form_state['comment_preview'])) {
+      $form += $form_state['comment_preview'];
+    }
+
+    $form['author'] = array(
+      '#weight' => 10,
+    );
+    // Display author information in a fieldset for comment moderators.
+    if ($is_admin) {
+      $form['author'] += array(
+        '#type' => 'fieldset',
+        '#title' => t('Administration'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+      );
+    }
+
+    // Prepare default values for form elements.
+    if ($is_admin) {
+      $author = (!$comment->uid && $comment->name ? $comment->name : $comment->registered_name);
+      $status = (isset($comment->status) ? $comment->status : COMMENT_NOT_PUBLISHED);
+      $date = (!empty($comment->date) ? $comment->date : format_date($comment->created, 'custom', 'Y-m-d H:i O'));
+    }
+    else {
+      if ($user->uid) {
+        $author = $user->name;
+      }
+      else {
+        $author = ($comment->name ? $comment->name : '');
+      }
+      $status = (user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED);
+      $date = '';
+    }
+
+    // Add the author name field depending on the current user.
+    if ($is_admin) {
+      $form['author']['name'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Authored by'),
+        '#default_value' => $author,
+        '#maxlength' => 60,
+        '#size' => 30,
+        '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
+        '#autocomplete_path' => 'user/autocomplete',
+      );
+    }
+    elseif ($user->uid) {
+      $form['author']['_author'] = array(
+        '#type' => 'item',
+        '#title' => t('Your name'),
+        '#markup' => theme('username', array('account' => $user)),
+      );
+
+      $form['author']['name'] = array(
+        '#type' => 'value',
+        '#value' => $author,
+      );
+    }
+    else {
+      $form['author']['name'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Your name'),
+        '#default_value' => $author,
+        '#required' => (!$user->uid && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
+        '#maxlength' => 60,
+        '#size' => 30,
+      );
+    }
+
+    // Add author e-mail and homepage fields depending on the current user.
+    $form['author']['mail'] = array(
+      '#type' => 'email',
+      '#title' => t('E-mail'),
+      '#default_value' => $comment->mail,
+      '#required' => (!$user->uid && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
+      '#maxlength' => 64,
+      '#size' => 30,
+      '#description' => t('The content of this field is kept private and will not be shown publicly.'),
+      '#access' => $is_admin || (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
+    );
+
+    $form['author']['homepage'] = array(
+      '#type' => 'url',
+      '#title' => t('Homepage'),
+      '#default_value' => $comment->homepage,
+      '#maxlength' => 255,
+      '#size' => 30,
+      '#access' => $is_admin || (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
+    );
+
+    // Add administrative comment publishing options.
+    $form['author']['date'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Authored on'),
+      '#default_value' => $date,
+      '#maxlength' => 25,
+      '#size' => 20,
+      '#access' => $is_admin,
+    );
+
+    $form['author']['status'] = array(
+      '#type' => 'radios',
+      '#title' => t('Status'),
+      '#default_value' => $status,
+      '#options' => array(
+        COMMENT_PUBLISHED => t('Published'),
+        COMMENT_NOT_PUBLISHED => t('Not published'),
+      ),
+      '#access' => $is_admin,
+    );
+
+    $form['subject'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Subject'),
+      '#maxlength' => 64,
+      '#default_value' => $comment->subject,
+      '#access' => variable_get('comment_subject_field_' . $node->type, 1) == 1,
+    );
+
+    // Used for conditional validation of author fields.
+    $form['is_anonymous'] = array(
+      '#type' => 'value',
+      '#value' => ($comment->cid ? !$comment->uid : !$user->uid),
+    );
+
+    // Add internal comment properties.
+    foreach (array('cid', 'pid', 'nid', 'uid') as $key) {
+      $form[$key] = array('#type' => 'value', '#value' => $comment->$key);
+    }
+    $form['node_type'] = array('#type' => 'value', '#value' => 'comment_node_' . $node->type);
+
+    // Make the comment inherit the node language unless specifically set.
+    $comment_langcode = $comment->langcode;
+    if ($comment_langcode == LANGUAGE_NOT_SPECIFIED) {
+      $comment_langcode = $language_content->langcode;
+    }
+
+    // Uses the language of the content as comment language.
+    $form['langcode'] = array(
+      '#type' => 'value',
+      '#value' => $comment_langcode,
+    );
+
+    // Attach fields.
+    $comment->node_type = 'comment_node_' . $node->type;
+
     return parent::form($form, $form_state);
   }
 
@@ -43,7 +215,7 @@ class CommentFormController extends EntityFormController {
       '#type' => 'submit',
       '#value' => t('Preview'),
       '#access' => $preview_mode != DRUPAL_DISABLED,
-      '#submit' => array('comment_form_build_preview'),
+      '#submit' => array(array($this, 'preview')),
     );
 
     $element['#weight'] = $form['comment_body']['#weight'] + 0.01;
@@ -56,21 +228,148 @@ class CommentFormController extends EntityFormController {
    */
   public function validate(array $form, array &$form_state) {
     parent::validate($form, $form_state);
-    comment_form_validate($form, $form_state);
+
+    if (!empty($form_state['values']['cid'])) {
+      // Verify the name in case it is being changed from being anonymous.
+      $account = user_load_by_name($form_state['values']['name']);
+      $form_state['values']['uid'] = $account ? $account->uid : 0;
+
+      if ($form_state['values']['date'] && strtotime($form_state['values']['date']) === FALSE) {
+        form_set_error('date', t('You have to specify a valid date.'));
+      }
+      if ($form_state['values']['name'] && !$form_state['values']['is_anonymous'] && !$account) {
+        form_set_error('name', t('You have to specify a valid author.'));
+      }
+    }
+    elseif ($form_state['values']['is_anonymous']) {
+      // Validate anonymous comment author fields (if given). If the (original)
+      // author of this comment was an anonymous user, verify that no registered
+      // user with this name exists.
+      if ($form_state['values']['name']) {
+        $query = db_select('users', 'u');
+        $query->addField('u', 'uid', 'uid');
+        $taken = $query
+        ->condition('name', db_like($form_state['values']['name']), 'LIKE')
+        ->countQuery()
+        ->execute()
+        ->fetchField();
+        if ($taken) {
+          form_set_error('name', t('The name you used belongs to a registered user.'));
+        }
+      }
+    }
   }
 
   /**
    * @see Drupal\entity\EntityFormController::submit()
    */
   public function submit(array $form, array &$form_state) {
-    parent::submit($form, $form_state);
-    comment_submit($this->getEntity());
+    $comment = parent::submit($form, $form_state);
+
+    if (empty($comment->date)) {
+      $comment->date = 'now';
+    }
+    $comment->created = strtotime($comment->date);
+    $comment->changed = REQUEST_TIME;
+
+    // If the comment was posted by a registered user, assign the author's ID.
+    // @todo Too fragile. Should be prepared and stored in comment_form()
+    // already.
+    if (!$comment->is_anonymous && !empty($comment->name) && ($account = user_load_by_name($comment->name))) {
+      $comment->uid = $account->uid;
+    }
+    // If the comment was posted by an anonymous user and no author name was
+    // required, use "Anonymous" by default.
+    if ($comment->is_anonymous && (!isset($comment->name) || $comment->name === '')) {
+      $comment->name = variable_get('anonymous', t('Anonymous'));
+    }
+
+    // Validate the comment's subject. If not specified, extract from comment
+    // body.
+    if (trim($comment->subject) == '') {
+      // The body may be in any format, so:
+      // 1) Filter it into HTML
+      // 2) Strip out all HTML tags
+      // 3) Convert entities back to plain-text.
+      $comment_body = $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0];
+      if (isset($comment_body['format'])) {
+        $comment_text = check_markup($comment_body['value'], $comment_body['format']);
+      }
+      else {
+        $comment_text = check_plain($comment_body['value']);
+      }
+      $comment->subject = truncate_utf8(trim(decode_entities(strip_tags($comment_text))), 29, TRUE);
+      // Edge cases where the comment body is populated only by HTML tags will
+      // require a default subject.
+      if ($comment->subject == '') {
+        $comment->subject = t('(No subject)');
+      }
+    }
+
+    return $comment;
+  }
+
+  /**
+   * Form submission handler for the 'preview' action.
+   *
+   * @param $form
+   *   An associative array containing the structure of the form.
+   * @param $form_state
+   *   A reference to a keyed array containing the current state of the form.
+   */
+  public function preview(array $form, array &$form_state) {
+    $comment = $this->getEntity();
+    drupal_set_title(t('Preview comment'), PASS_THROUGH);
+    $form_state['comment_preview'] = comment_preview($comment);
+    $form_state['rebuild'] = TRUE;
   }
 
   /**
    * @see Drupal\entity\EntityFormController::save()
    */
   public function save(array $form, array &$form_state) {
-    comment_form_submit($form, $form_state);
+    $node = node_load($form_state['values']['nid']);
+    $comment = $this->getEntity();
+
+    if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) {
+      // Save the anonymous user information to a cookie for reuse.
+      if (user_is_anonymous()) {
+        user_cookie_save(array_intersect_key($form_state['values'], array_flip(array('name', 'mail', 'homepage'))));
+      }
+
+      comment_save($comment);
+      $form_state['values']['cid'] = $comment->cid;
+
+      // Add an entry to the watchdog log.
+      watchdog('content', 'Comment posted: %subject.', array('%subject' => $comment->subject), WATCHDOG_NOTICE, l(t('view'), 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid)));
+
+      // Explain the approval queue if necessary.
+      if ($comment->status == COMMENT_NOT_PUBLISHED) {
+        if (!user_access('administer comments')) {
+          drupal_set_message(t('Your comment has been queued for review by site administrators and will be published after approval.'));
+        }
+      }
+      else {
+        drupal_set_message(t('Your comment has been posted.'));
+      }
+      $query = array();
+      // Find the current display page for this comment.
+      $page = comment_get_display_page($comment->cid, $node->type);
+      if ($page > 0) {
+        $query['page'] = $page;
+      }
+      // Redirect to the newly posted comment.
+      $redirect = array('node/' . $node->nid, array('query' => $query, 'fragment' => 'comment-' . $comment->cid));
+    }
+    else {
+      watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject), WATCHDOG_WARNING);
+      drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject)), 'error');
+      // Redirect the user to the node they are commenting on.
+      $redirect = 'node/' . $node->nid;
+    }
+    $form_state['redirect'] = $redirect;
+    // Clear the block and page caches so that anonymous users see the comment
+    // they have posted.
+    cache_invalidate(array('content' => TRUE));
   }
 }
diff --git a/core/modules/entity/lib/Drupal/entity/EntityFormController.php b/core/modules/entity/lib/Drupal/entity/EntityFormController.php
index f5c45f1..459d630 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityFormController.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityFormController.php
@@ -124,6 +124,7 @@ class EntityFormController {
     }
 
     $count = 0;
+    $validate_callback = array($this, 'validate');
     $submit_callback = array($this, 'submit');
 
     foreach (element_children($element) as $action) {
@@ -133,8 +134,9 @@ class EntityFormController {
         '#validate' => array(),
         '#submit' => array(),
       );
-      // Ensure we always preprocess submitted data before calling the actual
-      // submission handlers.
+      // Ensure we always validate and preprocess submitted data before calling
+      // the actual form handlers.
+      array_unshift($element[$action]['#validate'], $validate_callback);
       array_unshift($element[$action]['#submit'], $submit_callback);
     }
 
@@ -153,7 +155,6 @@ class EntityFormController {
       // @todo rename the action key from submit to save.
       'submit' => array(
         '#value' => t('Save'),
-        '#validate' => array(array($this, 'validate')),
         '#submit' => array(array($this, 'save')),
       ),
       'delete' => array(
@@ -190,10 +191,9 @@ class EntityFormController {
    *   A reference to a keyed array containing the current state of the form.
    */
   public function validate(array $form, array &$form_state) {
-    $entity = $this->getEntity();
-
     // @todo Exploit the Property API to validate the values submitted for the
     // entity properties.
+    $entity = $this->getEntity();
     $info = $entity->entityInfo();
     if (!empty($info['fieldable'])) {
       // @todo Consider inlining entity_form_field_validate() here.
@@ -209,6 +209,10 @@ class EntityFormController {
   /**
    * Processes the submitted form values and updates the entity object.
    *
+   * This is the default entity object builder function. It is called before any
+   * other submit handler to build the new entity object to be passed to the
+   * following submit handlers.
+   *
    * @param $form
    *   An associative array containing the structure of the form.
    * @param $form_state
@@ -218,6 +222,7 @@ class EntityFormController {
     // @todo Exploit the Property API to process the submitted entity property.
     $entity = $this->buildEntity($form, $form_state);
     $this->setEntity($entity);
+    return $entity;
   }
 
   /**
@@ -296,7 +301,7 @@ class EntityFormController {
   }
 
   /**
-   * Prepares the form entity.
+   * Prepares the entity object.
    */
   protected function prepareEntity() {
     // @todo Perform common prepare operations.
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 06a65d6..aee4e69 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -590,7 +590,7 @@ function forum_field_storage_pre_update($entity_type, $entity, &$skip_fields) {
 }
 
 /**
- * Implements hook_form_FORM_ID_alter() for taxonomy_form_vocabulary().
+ * Implements hook_form_BASE_FORM_ID_alter().
  */
 function forum_form_taxonomy_vocabulary_form_alter(&$form, &$form_state, $form_id) {
   $vid = variable_get('forum_nav_vocabulary', 0);
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index c8a4872..15edabf 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -16,17 +16,251 @@ use Drupal\entity\EntityFormController;
 class NodeFormController extends EntityFormController {
 
   /**
+   * Prepares the node object.
+   *
+   * Fills in a few default values, and then invokes hook_prepare() on the node
+   * type module, and hook_node_prepare() on all modules.
+   *
    * @see Drupal\entity\EntityFormController::prepareEntity()
    */
   protected function prepareEntity() {
-    node_object_prepare($this->getEntity());
+    $node = $this->getEntity();
+
+    // Set up default values, if required.
+    $node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));
+    // If this is a new node, fill in the default values.
+    if (!isset($node->nid) || isset($node->is_new)) {
+      foreach (array('status', 'promote', 'sticky') as $key) {
+        // Multistep node forms might have filled in something already.
+        if (!isset($node->$key)) {
+          $node->$key = (int) in_array($key, $node_options);
+        }
+      }
+      global $user;
+      $node->uid = $user->uid;
+      $node->created = REQUEST_TIME;
+    }
+    else {
+      $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
+      // Remove the log message from the original node entity.
+      $node->log = NULL;
+    }
+    // Always use the default revision setting.
+    $node->revision = in_array('revision', $node_options);
+
+    node_invoke($node, 'prepare');
+    module_invoke_all('node_prepare', $node);
   }
 
   /**
    * @see Drupal\entity\EntityFormController::form()
    */
   protected function form(array $form, array &$form_state) {
-    $form = node_form($form, $form_state, $this->getEntity());
+    global $user;
+    $node = $this->getEntity();
+
+    // Some special stuff when previewing a node.
+    if (isset($form_state['node_preview'])) {
+      $form['#prefix'] = $form_state['node_preview'];
+      $node->in_preview = TRUE;
+    }
+    else {
+      unset($node->in_preview);
+    }
+
+    // Override the default CSS class name, since the user-defined node type
+    // name in 'TYPE-node-form' potentially clashes with third-party class
+    // names.
+    $form['#attributes']['class'][0] = drupal_html_class('node-' . $node->type . '-form');
+
+    // Basic node information.
+    // These elements are just values so they are not even sent to the client.
+    foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) {
+      $form[$key] = array(
+        '#type' => 'value',
+        '#value' => isset($node->$key) ? $node->$key : NULL,
+      );
+    }
+
+    // Changed must be sent to the client, for later overwrite error checking.
+    $form['changed'] = array(
+      '#type' => 'hidden',
+      '#default_value' => isset($node->changed) ? $node->changed : NULL,
+    );
+
+    // Invoke hook_form() to get the node-specific bits. Can't use node_invoke()
+    // because hook_form() needs to be able to receive $form_state by reference.
+    // @todo hook_form() implementations are unable to add #validate or #submit
+    //   handlers to the form buttons below. Remove hook_form() entirely.
+    $function = node_type_get_base($node) . '_form';
+    if (function_exists($function) && ($extra = $function($node, $form_state))) {
+      $form = array_merge_recursive($form, $extra);
+    }
+    // If the node type has a title, and the node type form defined no special
+    // weight for it, we default to a weight of -5 for consistency.
+    if (isset($form['title']) && !isset($form['title']['#weight'])) {
+      $form['title']['#weight'] = -5;
+    }
+    // @todo D8: Remove. Modules should access the node using
+    // $form_state['node'].
+    $form['#node'] = $node;
+
+    if (module_exists('language')) {
+      $languages = language_list(LANGUAGE_ALL);
+      $language_options = array();
+      foreach ($languages as $langcode => $language) {
+        // Make locked languages appear special in the list.
+        $language_options[$langcode] = $language->locked ? t('- @name -', array('@name' => $language->name)) : $language->name;
+      }
+
+      $form['langcode'] = array(
+        '#type' => 'select',
+        '#title' => t('Language'),
+        '#default_value' => $node->langcode,
+        '#options' => $language_options,
+        '#access' => !variable_get('node_type_language_hidden_' . $node->type, TRUE),
+      );
+    }
+    else {
+      $form['langcode'] = array(
+        '#type' => 'value',
+        '#value' => $node->langcode,
+      );
+    }
+
+    $form['additional_settings'] = array(
+      '#type' => 'vertical_tabs',
+      '#weight' => 99,
+    );
+
+    // Add a log field if the "Create new revision" option is checked, or if the
+    // current user has the ability to check that option.
+    $form['revision_information'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Revision information'),
+      '#collapsible' => TRUE,
+      // Collapsed by default when "Create new revision" is unchecked
+      '#collapsed' => !$node->revision,
+      '#group' => 'additional_settings',
+      '#attributes' => array(
+        'class' => array('node-form-revision-information'),
+      ),
+      '#attached' => array(
+        'js' => array(drupal_get_path('module', 'node') . '/node.js'),
+      ),
+      '#weight' => 20,
+      '#access' => $node->revision || user_access('administer nodes'),
+    );
+
+    $form['revision_information']['revision'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Create new revision'),
+      '#default_value' => $node->revision,
+      '#access' => user_access('administer nodes'),
+    );
+
+    // Check the revision log checkbox when the log textarea is filled in.
+    // This must not happen if "Create new revision" is enabled by default,
+    // since the state would auto-disable the checkbox otherwise.
+    if (!$node->revision) {
+      $form['revision_information']['revision']['#states'] = array(
+        'checked' => array(
+          'textarea[name="log"]' => array('empty' => FALSE),
+        ),
+      );
+    }
+
+    $form['revision_information']['log'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Revision log message'),
+      '#rows' => 4,
+      '#default_value' => !empty($node->log) ? $node->log : '',
+      '#description' => t('Briefly describe the changes you have made.'),
+    );
+
+    // Node author information for administrators.
+    $form['author'] = array(
+      '#type' => 'fieldset',
+      '#access' => user_access('administer nodes'),
+      '#title' => t('Authoring information'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#group' => 'additional_settings',
+      '#attributes' => array(
+        'class' => array('node-form-author'),
+      ),
+      '#attached' => array(
+        'js' => array(
+          drupal_get_path('module', 'node') . '/node.js',
+          array(
+            'type' => 'setting',
+            'data' => array('anonymous' => variable_get('anonymous', t('Anonymous'))),
+          ),
+        ),
+      ),
+      '#weight' => 90,
+    );
+
+    $form['author']['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Authored by'),
+      '#maxlength' => 60,
+      '#autocomplete_path' => 'user/autocomplete',
+      '#default_value' => !empty($node->name) ? $node->name : '',
+      '#weight' => -1,
+      '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
+    );
+
+    $form['author']['date'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Authored on'),
+      '#maxlength' => 25,
+      '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($node->date) ? date_format(date_create($node->date), 'Y-m-d H:i:s O') : format_date($node->created, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->date) ? date_format(date_create($node->date), 'O') : format_date($node->created, 'custom', 'O'))),
+      '#default_value' => !empty($node->date) ? $node->date : '',
+    );
+
+    // Node options for administrators.
+    $form['options'] = array(
+      '#type' => 'fieldset',
+      '#access' => user_access('administer nodes'),
+      '#title' => t('Publishing options'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#group' => 'additional_settings',
+      '#attributes' => array(
+        'class' => array('node-form-options'),
+      ),
+      '#attached' => array(
+        'js' => array(drupal_get_path('module', 'node') . '/node.js'),
+      ),
+      '#weight' => 95,
+    );
+
+    $form['options']['status'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Published'),
+      '#default_value' => $node->status,
+    );
+
+    $form['options']['promote'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Promoted to front page'),
+      '#default_value' => $node->promote,
+    );
+
+    $form['options']['sticky'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Sticky at top of lists'),
+      '#default_value' => $node->sticky,
+    );
+
+    // This form uses a button-level #submit handler for the form's main submit
+    // action. node_form_submit() manually invokes all form-level #submit
+    // handlers of the form. Without explicitly setting #submit, Form API would
+    // auto-detect node_form_submit() as submit handler, but that is the
+    // button-level #submit handler for the 'Save' action.
+    $form += array('#submit' => array());
+
     return parent::form($form, $form_state);
   }
 
@@ -41,7 +275,7 @@ class NodeFormController extends EntityFormController {
     $element['preview'] = array(
       '#access' => $preview_mode != DRUPAL_DISABLED,
       '#value' => t('Preview'),
-      '#submit' => array('node_form_build_preview'),
+      '#submit' => array(array($this, 'preview')),
     );
 
     $element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview']));
@@ -54,31 +288,152 @@ class NodeFormController extends EntityFormController {
    * @see Drupal\entity\EntityFormController::validate()
    */
   public function validate(array $form, array &$form_state) {
-    node_form_validate($form, $form_state);
+    // The entity form controller contains the actual entity being edited, but
+    // we must not update it with form values that have not yet been validated,
+    // so we create an entity clone to use during validation.
+    $node = $this->buildEntity($form, $form_state);
+    $type = node_type_load($node->type);
+
+    if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
+      form_set_error('changed', t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
+    }
+
+    // Validate the "authored by" field.
+    if (!empty($node->name) && !($account = user_load_by_name($node->name))) {
+      // The use of empty() is mandatory in the context of usernames
+      // as the empty string denotes the anonymous user. In case we
+      // are dealing with an anonymous user we set the user ID to 0.
+      form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name)));
+    }
+
+    // Validate the "authored on" field.
+    if (!empty($node->date) && strtotime($node->date) === FALSE) {
+      form_set_error('date', t('You have to specify a valid date.'));
+    }
+
+    // Invoke hook_validate() for node type specific validation and
+    // hook_node_validate() for miscellaneous validation needed by modules.
+    // Can't use node_invoke() or module_invoke_all(), because $form_state must
+    // be receivable by reference.
+    $function = node_type_get_base($node) . '_validate';
+    if (function_exists($function)) {
+      $function($node, $form, $form_state);
+    }
+    foreach (module_implements('node_validate') as $module) {
+      $function = $module . '_node_validate';
+      $function($node, $form, $form_state);
+    }
+
     parent::validate($form, $form_state);
   }
 
   /**
+   * Updates the node object by processing the submitted values.
+   *
+   * This function can be called by a "Next" button of a wizard to update the
+   * form state's entity with the current step's values before proceeding to the
+   * next step.
+   *
    * @see Drupal\entity\EntityFormController::submit()
    */
   public function submit(array $form, array &$form_state) {
-    // Handle possible field translations first and then build the node from the
-    // submitted values.
-    node_field_language_form_submit($form, $form_state);
-    parent::submit($form, $form_state);
+    $this->submitNodeLanguage($form, $form_state);
+
+    // Build the node object from the submitted values.
+    $node = parent::submit($form, $form_state);
+
+    node_submit($node);
+    foreach (module_implements('node_submit') as $module) {
+      $function = $module . '_node_submit';
+      $function($node, $form, $form_state);
+    }
+
+    return $node;
+  }
+
+  /**
+   * Handle possible node language changes.
+   */
+  protected function submitNodeLanguage(array $form, array &$form_state) {
+    if (field_has_translation_handler('node', 'node')) {
+      $bundle = $form_state['values']['type'];
+      $node_language = $form_state['values']['langcode'];
+
+      foreach (field_info_instances('node', $bundle) as $instance) {
+        $field_name = $instance['field_name'];
+        $field = field_info_field($field_name);
+        $previous_langcode = $form[$field_name]['#language'];
+
+        // Handle a possible language change: new language values are inserted,
+        // previous ones are deleted.
+        if ($field['translatable'] && $previous_langcode != $node_language) {
+          $form_state['values'][$field_name][$node_language] = $form_state['values'][$field_name][$previous_langcode];
+          $form_state['values'][$field_name][$previous_langcode] = array();
+        }
+      }
+    }
+  }
+
+  /**
+   * Form submission handler for the 'preview' action.
+   *
+   * @param $form
+   *   An associative array containing the structure of the form.
+   * @param $form_state
+   *   A reference to a keyed array containing the current state of the form.
+   */
+  public function preview(array $form, array &$form_state) {
+    drupal_set_title(t('Preview'), PASS_THROUGH);
+    $form_state['node_preview'] = node_preview($this->getEntity());
+    $form_state['rebuild'] = TRUE;
   }
 
   /**
    * @see Drupal\entity\EntityFormController::save()
    */
   public function save(array $form, array &$form_state) {
-    node_form_submit($form, $form_state);
+    $node = $this->getEntity();
+    $insert = empty($node->nid);
+    $node->save();
+    $node_link = l(t('view'), 'node/' . $node->nid);
+    $watchdog_args = array('@type' => $node->type, '%title' => $node->label());
+    $t_args = array('@type' => node_type_get_name($node), '%title' => $node->label());
+
+    if ($insert) {
+      watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
+      drupal_set_message(t('@type %title has been created.', $t_args));
+    }
+    else {
+      watchdog('content', '@type: updated %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
+      drupal_set_message(t('@type %title has been updated.', $t_args));
+    }
+
+    if ($node->nid) {
+      $form_state['values']['nid'] = $node->nid;
+      $form_state['nid'] = $node->nid;
+      $form_state['redirect'] = 'node/' . $node->nid;
+    }
+    else {
+      // In the unlikely case something went wrong on save, the node will be
+      // rebuilt and node form redisplayed the same way as in preview.
+      drupal_set_message(t('The post could not be saved.'), 'error');
+      $form_state['rebuild'] = TRUE;
+    }
+
+    // Clear the page and block caches.
+    cache_invalidate(array('content' => TRUE));
   }
 
   /**
    * @see Drupal\entity\EntityFormController::delete()
    */
   public function delete(array $form, array &$form_state) {
-    node_form_delete_submit($form, $form_state);
+    $destination = array();
+    if (isset($_GET['destination'])) {
+      $destination = drupal_get_destination();
+      unset($_GET['destination']);
+    }
+    $node = $this->getEntity();
+    $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination));
   }
 }
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index f4fb5f5..e5a8518 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -636,8 +636,8 @@ function hook_node_access($node, $op, $account, $langcode) {
 /**
  * Act on a node object about to be shown on the add/edit form.
  *
- * This hook is invoked from node_object_prepare() after the type-specific
- * hook_prepare() is invoked.
+ * This hook is invoked from NodeFormController::prepareEntity() after the
+ * type-specific hook_prepare() is invoked.
  *
  * @param Drupal\node\Node $node
  *   The node that is about to be shown on the add/edit form.
@@ -740,10 +740,10 @@ function hook_node_update_index(Drupal\node\Node $node) {
 /**
  * Perform node validation before a node is created or updated.
  *
- * This hook is invoked from node_validate(), after a user has has finished
- * editing the node and is previewing or submitting it. It is invoked at the
- * end of all the standard validation steps, and after the type-specific
- * hook_validate() is invoked.
+ * This hook is invoked from NodeFormController::validate(), after a user has
+ * has finished editing the node and is previewing or submitting it. It is
+ * invoked at the end of all the standard validation steps, and after the
+ * type-specific hook_validate() is invoked.
  *
  * To indicate a validation error, use form_set_error().
  *
@@ -1052,8 +1052,8 @@ function hook_delete(Drupal\node\Node $node) {
  * This hook is invoked only on the module that defines the node's content type
  * (use hook_node_prepare() to act on all node preparations).
  *
- * This hook is invoked from node_object_prepare() before the general
- * hook_node_prepare() is invoked.
+ * This hook is invoked from NodeFormController::prepareEntity() before the
+ * general hook_node_prepare() is invoked.
  *
  * @param Drupal\node\Node $node
  *   The node that is about to be shown on the add/edit form.
@@ -1219,10 +1219,10 @@ function hook_update(Drupal\node\Node $node) {
  * This hook is invoked only on the module that defines the node's content type
  * (use hook_node_validate() to act on all node validations).
  *
- * This hook is invoked from node_validate(), after a user has finished
- * editing the node and is previewing or submitting it. It is invoked at the end
- * of all the standard validation steps, and before hook_node_validate() is
- * invoked.
+ * This hook is invoked from NodeFormController::validate(), after a user has
+ * finished editing the node and is previewing or submitting it. It is invoked
+ * at the end of all the standard validation steps, and before
+ * hook_node_validate() is invoked.
  *
  * To indicate a validation error, use form_set_error().
  *
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index ffccea6..109fb91 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1002,81 +1002,6 @@ function node_load($nid = NULL, $vid = NULL, $reset = FALSE) {
 }
 
 /**
- * Prepares a node entity for editing.
- *
- * Fills in a few default values, and then invokes hook_prepare() on the node
- * type module, and hook_node_prepare() on all modules.
- *
- * @param Drupal\node\Node $node
- *   The node entity.
- */
-function node_object_prepare(Node $node) {
-  // Set up default values, if required.
-  $node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));
-  // If this is a new node, fill in the default values.
-  if (!isset($node->nid) || isset($node->is_new)) {
-    foreach (array('status', 'promote', 'sticky') as $key) {
-      // Multistep node forms might have filled in something already.
-      if (!isset($node->$key)) {
-        $node->$key = (int) in_array($key, $node_options);
-      }
-    }
-    global $user;
-    $node->uid = $user->uid;
-    $node->created = REQUEST_TIME;
-  }
-  else {
-    $node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
-    // Remove the log message from the original node entity.
-    $node->log = NULL;
-  }
-  // Always use the default revision setting.
-  $node->revision = in_array('revision', $node_options);
-
-  node_invoke($node, 'prepare');
-  module_invoke_all('node_prepare', $node);
-}
-
-/**
- * Performs validation checks on the given node.
- *
- * @see node_form_validate()
- */
-function node_validate(Node $node, $form, &$form_state) {
-  $type = node_type_load($node->type);
-
-  if (isset($node->nid) && (node_last_changed($node->nid) > $node->changed)) {
-    form_set_error('changed', t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
-  }
-
-  // Validate the "authored by" field.
-  if (!empty($node->name) && !($account = user_load_by_name($node->name))) {
-    // The use of empty() is mandatory in the context of usernames
-    // as the empty string denotes the anonymous user. In case we
-    // are dealing with an anonymous user we set the user ID to 0.
-    form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name)));
-  }
-
-  // Validate the "authored on" field.
-  if (!empty($node->date) && strtotime($node->date) === FALSE) {
-    form_set_error('date', t('You have to specify a valid date.'));
-  }
-
-  // Invoke hook_validate() for node type specific validation and
-  // hook_node_validate() for miscellaneous validation needed by modules. Can't
-  // use node_invoke() or module_invoke_all(), because $form_state must be
-  // receivable by reference.
-  $function = node_type_get_base($node) . '_validate';
-  if (function_exists($function)) {
-    $function($node, $form, $form_state);
-  }
-  foreach (module_implements('node_validate') as $module) {
-    $function = $module . '_node_validate';
-    $function($node, $form, $form_state);
-  }
-}
-
-/**
  * Prepares a node for saving by populating the author and creation date.
  */
 function node_submit($node) {
diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc
index 61df4df..957ba1d 100644
--- a/core/modules/node/node.pages.inc
+++ b/core/modules/node/node.pages.inc
@@ -104,263 +104,6 @@ function node_add($type) {
 }
 
 /**
- * Form validation handler for node_form().
- *
- * @see node_form_delete_submit()
- * @see node_form_build_preview()
- * @see node_form_submit()
- * @see node_form_submit_build_node()
- */
-function node_form_validate($form, &$form_state) {
-  // The entity form controller contains the actual entity being edited, but we
-  // must not update it with form values that have not yet been validated, so we
-  // create a pseudo-entity to use during validation.
-  $node = clone EntityFormController::getFormInstance($form_state)->getEntity();
-  foreach ($form_state['values'] as $key => $value) {
-    $node->{$key} = $value;
-  }
-  node_validate($node, $form, $form_state);
-}
-
-/**
- * Form constructor for the node add/edit form.
- *
- * @see node_form_delete_submit()
- * @see node_form_build_preview()
- * @see node_form_validate()
- * @see node_form_submit()
- * @see node_form_submit_build_node()
- * @ingroup forms
- */
-function node_form($form, &$form_state, Node $node) {
-  global $user;
-
-  // Some special stuff when previewing a node.
-  if (isset($form_state['node_preview'])) {
-    $form['#prefix'] = $form_state['node_preview'];
-    $node->in_preview = TRUE;
-  }
-  else {
-    unset($node->in_preview);
-  }
-
-  // Override the default CSS class name, since the user-defined node type name
-  // in 'TYPE-node-form' potentially clashes with third-party class names.
-  $form['#attributes']['class'][0] = drupal_html_class('node-' . $node->type . '-form');
-
-  // Basic node information.
-  // These elements are just values so they are not even sent to the client.
-  foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) {
-    $form[$key] = array(
-      '#type' => 'value',
-      '#value' => isset($node->$key) ? $node->$key : NULL,
-    );
-  }
-
-  // Changed must be sent to the client, for later overwrite error checking.
-  $form['changed'] = array(
-    '#type' => 'hidden',
-    '#default_value' => isset($node->changed) ? $node->changed : NULL,
-  );
-  // Invoke hook_form() to get the node-specific bits. Can't use node_invoke(),
-  // because hook_form() needs to be able to receive $form_state by reference.
-  // @todo hook_form() implementations are unable to add #validate or #submit
-  //   handlers to the form buttons below. Remove hook_form() entirely.
-  $function = node_type_get_base($node) . '_form';
-  if (function_exists($function) && ($extra = $function($node, $form_state))) {
-    $form = array_merge_recursive($form, $extra);
-  }
-  // If the node type has a title, and the node type form defined no special
-  // weight for it, we default to a weight of -5 for consistency.
-  if (isset($form['title']) && !isset($form['title']['#weight'])) {
-    $form['title']['#weight'] = -5;
-  }
-  // @todo D8: Remove. Modules should access the node using $form_state['node'].
-  $form['#node'] = $node;
-
-  if (module_exists('language')) {
-    $languages = language_list(LANGUAGE_ALL);
-    $language_options = array();
-    foreach ($languages as $langcode => $language) {
-      // Make locked languages appear special in the list.
-      $language_options[$langcode] = $language->locked ? t('- @name -', array('@name' => $language->name)) : $language->name;
-    }
-    $form['langcode'] = array(
-      '#type' => 'select',
-      '#title' => t('Language'),
-      '#default_value' => $node->langcode,
-      '#options' => $language_options,
-      '#access' => !variable_get('node_type_language_hidden_' . $node->type, TRUE),
-    );
-  }
-  else {
-    $form['langcode'] = array(
-      '#type' => 'value',
-      '#value' => $node->langcode,
-    );
-  }
-
-  $form['additional_settings'] = array(
-    '#type' => 'vertical_tabs',
-    '#weight' => 99,
-  );
-
-  // Add a log field if the "Create new revision" option is checked, or if the
-  // current user has the ability to check that option.
-  $form['revision_information'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Revision information'),
-    '#collapsible' => TRUE,
-    // Collapsed by default when "Create new revision" is unchecked
-    '#collapsed' => !$node->revision,
-    '#group' => 'additional_settings',
-    '#attributes' => array(
-      'class' => array('node-form-revision-information'),
-    ),
-    '#attached' => array(
-      'js' => array(drupal_get_path('module', 'node') . '/node.js'),
-    ),
-    '#weight' => 20,
-    '#access' => $node->revision || user_access('administer nodes'),
-  );
-  $form['revision_information']['revision'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Create new revision'),
-    '#default_value' => $node->revision,
-    '#access' => user_access('administer nodes'),
-  );
-  // Check the revision log checkbox when the log textarea is filled in.
-  // This must not happen if "Create new revision" is enabled by default, since
-  // the state would auto-disable the checkbox otherwise.
-  if (!$node->revision) {
-    $form['revision_information']['revision']['#states'] = array(
-      'checked' => array(
-        'textarea[name="log"]' => array('empty' => FALSE),
-      ),
-    );
-  }
-  $form['revision_information']['log'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Revision log message'),
-    '#rows' => 4,
-    '#default_value' => !empty($node->log) ? $node->log : '',
-    '#description' => t('Briefly describe the changes you have made.'),
-  );
-
-  // Node author information for administrators
-  $form['author'] = array(
-    '#type' => 'fieldset',
-    '#access' => user_access('administer nodes'),
-    '#title' => t('Authoring information'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#group' => 'additional_settings',
-    '#attributes' => array(
-      'class' => array('node-form-author'),
-    ),
-    '#attached' => array(
-      'js' => array(
-        drupal_get_path('module', 'node') . '/node.js',
-        array(
-          'type' => 'setting',
-          'data' => array('anonymous' => variable_get('anonymous', t('Anonymous'))),
-        ),
-      ),
-    ),
-    '#weight' => 90,
-  );
-  $form['author']['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Authored by'),
-    '#maxlength' => 60,
-    '#autocomplete_path' => 'user/autocomplete',
-    '#default_value' => !empty($node->name) ? $node->name : '',
-    '#weight' => -1,
-    '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
-  );
-  $form['author']['date'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Authored on'),
-    '#maxlength' => 25,
-    '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($node->date) ? date_format(date_create($node->date), 'Y-m-d H:i:s O') : format_date($node->created, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->date) ? date_format(date_create($node->date), 'O') : format_date($node->created, 'custom', 'O'))),
-    '#default_value' => !empty($node->date) ? $node->date : '',
-  );
-
-  // Node options for administrators
-  $form['options'] = array(
-    '#type' => 'fieldset',
-    '#access' => user_access('administer nodes'),
-    '#title' => t('Publishing options'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#group' => 'additional_settings',
-    '#attributes' => array(
-      'class' => array('node-form-options'),
-    ),
-    '#attached' => array(
-      'js' => array(drupal_get_path('module', 'node') . '/node.js'),
-    ),
-    '#weight' => 95,
-  );
-  $form['options']['status'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Published'),
-    '#default_value' => $node->status,
-  );
-  $form['options']['promote'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Promoted to front page'),
-    '#default_value' => $node->promote,
-  );
-  $form['options']['sticky'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Sticky at top of lists'),
-    '#default_value' => $node->sticky,
-  );
-
-  // This form uses a button-level #submit handler for the form's main submit
-  // action. node_form_submit() manually invokes all form-level #submit handlers
-  // of the form. Without explicitly setting #submit, Form API would auto-detect
-  // node_form_submit() as submit handler, but that is the button-level #submit
-  // handler for the 'Save' action.
-  $form += array('#submit' => array());
-
-  return $form;
-}
-
-/**
- * Form submission handler for the 'Delete' button for node_form().
- *
- * @see node_form_build_preview()
- * @see node_form_validate()
- * @see node_form_submit()
- * @see node_form_submit_build_node()
- */
-function node_form_delete_submit($form, &$form_state) {
-  $destination = array();
-  if (isset($_GET['destination'])) {
-    $destination = drupal_get_destination();
-    unset($_GET['destination']);
-  }
-  $node = EntityFormController::getFormInstance($form_state)->getEntity();
-  $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination));
-}
-
-/**
- * Form submission handler for the 'Preview' button for node_form().
- *
- * @see node_form_delete_submit()
- * @see node_form_validate()
- * @see node_form_submit()
- * @see node_form_submit_build_node()
- */
-function node_form_build_preview($form, &$form_state) {
-  $node = node_form_submit_build_node($form, $form_state);
-  $form_state['node_preview'] = node_preview($node);
-  $form_state['rebuild'] = TRUE;
-}
-
-/**
  * Generates a node preview.
  *
  * @param Drupal\node\Node $node
@@ -402,7 +145,6 @@ function node_preview(Node $node) {
       $output = theme('node_preview', array('node' => $node));
       unset($node->in_preview);
     }
-    drupal_set_title(t('Preview'), PASS_THROUGH);
 
     return $output;
   }
@@ -415,7 +157,7 @@ function node_preview(Node $node) {
  *   An associative array containing:
  *   - node: The node entity which is being previewed.
  *
- * @see node_preview()
+ * @see NodeFormController::preview()
  * @ingroup themeable
  */
 function theme_node_preview($variables) {
@@ -446,94 +188,6 @@ function theme_node_preview($variables) {
 }
 
 /**
- * Form submission handler that saves the node for node_form().
- *
- * @see node_form_delete_submit()
- * @see node_form_build_preview()
- * @see node_form_validate()
- * @see node_form_submit_build_node()
- */
-function node_form_submit($form, &$form_state) {
-  $node = node_form_submit_build_node($form, $form_state);
-  $insert = empty($node->nid);
-  $node->save();
-  $node_link = l(t('view'), 'node/' . $node->nid);
-  $watchdog_args = array('@type' => $node->type, '%title' => $node->label());
-  $t_args = array('@type' => node_type_get_name($node), '%title' => $node->label());
-
-  if ($insert) {
-    watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
-    drupal_set_message(t('@type %title has been created.', $t_args));
-  }
-  else {
-    watchdog('content', '@type: updated %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
-    drupal_set_message(t('@type %title has been updated.', $t_args));
-  }
-  if ($node->nid) {
-    $form_state['values']['nid'] = $node->nid;
-    $form_state['nid'] = $node->nid;
-    $form_state['redirect'] = 'node/' . $node->nid;
-  }
-  else {
-    // In the unlikely case something went wrong on save, the node will be
-    // rebuilt and node form redisplayed the same way as in preview.
-    drupal_set_message(t('The post could not be saved.'), 'error');
-    $form_state['rebuild'] = TRUE;
-  }
-  // Clear the page and block caches.
-  cache_invalidate(array('content' => TRUE));
-}
-
-/**
- * Handles possible node language changes.
- *
- */
-function node_field_language_form_submit($form, &$form_state) {
-  if (field_has_translation_handler('node', 'node')) {
-    $bundle = $form_state['values']['type'];
-    $node_language = $form_state['values']['langcode'];
-
-    foreach (field_info_instances('node', $bundle) as $instance) {
-      $field_name = $instance['field_name'];
-      $field = field_info_field($field_name);
-      $previous_langcode = $form[$field_name]['#language'];
-
-      // Handle a possible language change: New language values are inserted,
-      // previous ones are deleted.
-      if ($field['translatable'] && $previous_langcode != $node_language) {
-        $form_state['values'][$field_name][$node_language] = $form_state['values'][$field_name][$previous_langcode];
-        $form_state['values'][$field_name][$previous_langcode] = array();
-      }
-    }
-  }
-}
-
-/**
- * Updates the form state's node entity by processing this submission's values.
- *
- * This is the default builder function for the node form. It is called
- * during the "Save" and "Preview" submit handlers to retrieve the entity to
- * save or preview. This function can also be called by a "Next" button of a
- * wizard to update the form state's entity with the current step's values
- * before proceeding to the next step.
- *
- * @see node_form()
- * @see node_form_delete_submit()
- * @see node_form_build_preview()
- * @see node_form_validate()
- * @see node_form_submit()
- */
-function node_form_submit_build_node($form, &$form_state) {
-  $node = EntityFormController::getFormInstance($form_state)->getEntity();
-  node_submit($node);
-  foreach (module_implements('node_submit') as $module) {
-    $function = $module . '_node_submit';
-    $function($node, $form, $form_state);
-  }
-  return $node;
-}
-
-/**
  * Page callback: Form constructor for node deletion confirmation form.
  *
  * @see node_menu()
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
index e700277..79848a6 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
@@ -19,7 +19,101 @@ class TermFormController extends EntityFormController {
    * @see Drupal\entity\EntityFormController::form()
    */
   protected function form(array $form, array &$form_state) {
-    $form = taxonomy_term_form($form, $form_state, $this->getEntity());
+    $term = $this->getEntity();
+    $vocabulary = taxonomy_vocabulary_load($term->vid);
+
+    $parent = array_keys(taxonomy_term_load_parents($term->tid));
+    $form['#term'] = (array) $term;
+    $form['#term']['parent'] = $parent;
+    $form['#vocabulary'] = $vocabulary;
+
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Name'),
+      '#default_value' => $term->name,
+      '#maxlength' => 255,
+      '#required' => TRUE,
+      '#weight' => -5,
+    );
+
+    $form['description'] = array(
+      '#type' => 'text_format',
+      '#title' => t('Description'),
+      '#default_value' => $term->description,
+      '#format' => $term->format,
+      '#weight' => 0,
+    );
+
+    $form['vocabulary_machine_name'] = array(
+      '#type' => 'value',
+      '#value' => isset($term->vocabulary_machine_name) ? $term->vocabulary_machine_name : $vocabulary->name,
+    );
+
+    $form['relations'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Relations'),
+      '#collapsible' => TRUE,
+      '#collapsed' => ($vocabulary->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE),
+      '#weight' => 10,
+    );
+
+    // taxonomy_get_tree and taxonomy_term_load_parents may contain large numbers of
+    // items so we check for taxonomy_override_selector before loading the
+    // full vocabulary. Contrib modules can then intercept before
+    // hook_form_alter to provide scalable alternatives.
+    if (!variable_get('taxonomy_override_selector', FALSE)) {
+      $parent = array_keys(taxonomy_term_load_parents($term->tid));
+      $children = taxonomy_get_tree($vocabulary->vid, $term->tid);
+
+      // A term can't be the child of itself, nor of its children.
+      foreach ($children as $child) {
+        $exclude[] = $child->tid;
+      }
+      $exclude[] = $term->tid;
+
+      $tree = taxonomy_get_tree($vocabulary->vid);
+      $options = array('<' . t('root') . '>');
+      if (empty($parent)) {
+        $parent = array(0);
+      }
+      foreach ($tree as $item) {
+        if (!in_array($item->tid, $exclude)) {
+          $options[$item->tid] = str_repeat('-', $item->depth) . $item->name;
+        }
+      }
+
+      $form['relations']['parent'] = array(
+        '#type' => 'select',
+        '#title' => t('Parent terms'),
+        '#options' => $options,
+        '#default_value' => $parent,
+        '#multiple' => TRUE,
+      );
+    }
+
+    $form['relations']['weight'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Weight'),
+      '#size' => 6,
+      '#default_value' => $term->weight,
+      '#description' => t('Terms are displayed in ascending order by weight.'),
+      '#required' => TRUE,
+    );
+
+    $form['vid'] = array(
+      '#type' => 'value',
+      '#value' => $vocabulary->vid,
+    );
+
+    $form['tid'] = array(
+      '#type' => 'value',
+      '#value' => $term->tid,
+    );
+
+    if (empty($term->tid)) {
+      $form_state['redirect'] = current_path();
+    }
+
     return parent::form($form, $form_state);
   }
 
@@ -28,28 +122,84 @@ class TermFormController extends EntityFormController {
    */
   public function validate(array $form, array &$form_state) {
     parent::validate($form, $form_state);
-    taxonomy_term_form_validate($form, $form_state);
+
+    // Ensure numeric values.
+    if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
+      form_set_error('weight', t('Weight value must be numeric.'));
+    }
   }
 
   /**
    * @see Drupal\entity\EntityFormController::submit()
    */
   public function submit(array $form, array &$form_state) {
-    parent::submit($form, $form_state);
-    taxonomy_term_form_submit_build_taxonomy_term($form, $form_state);
+    $term = parent::submit($form, $form_state);
+
+    // Prevent leading and trailing spaces in term names.
+    $term->name = trim($term->name);
+
+    // Convert text_format field into values expected by taxonomy_term_save().
+    $description = $form_state['values']['description'];
+    $term->description = $description['value'];
+    $term->format = $description['format'];
+
+    return $term;
   }
 
   /**
    * @see Drupal\entity\EntityFormController::save()
    */
   public function save(array $form, array &$form_state) {
-    taxonomy_term_form_submit($form, $form_state);
+    $term = $this->getEntity();
+
+    $status = taxonomy_term_save($term);
+    switch ($status) {
+      case SAVED_NEW:
+        drupal_set_message(t('Created new term %term.', array('%term' => $term->name)));
+        watchdog('taxonomy', 'Created new term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
+        break;
+      case SAVED_UPDATED:
+        drupal_set_message(t('Updated term %term.', array('%term' => $term->name)));
+        watchdog('taxonomy', 'Updated term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
+        // Clear the page and block caches to avoid stale data.
+        cache_invalidate(array('content' => TRUE));
+        break;
+    }
+
+    $current_parent_count = count($form_state['values']['parent']);
+    $previous_parent_count = count($form['#term']['parent']);
+    // Root doesn't count if it's the only parent.
+    if ($current_parent_count == 1 && isset($form_state['values']['parent'][0])) {
+      $current_parent_count = 0;
+      $form_state['values']['parent'] = array();
+    }
+
+    // If the number of parents has been reduced to one or none, do a check on the
+    // parents of every term in the vocabulary value.
+    if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
+      taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
+    }
+    // If we've increased the number of parents and this is a single or flat
+    // hierarchy, update the vocabulary immediately.
+    elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE) {
+      $form['#vocabulary']->hierarchy = $current_parent_count == 1 ? TAXONOMY_HIERARCHY_SINGLE : TAXONOMY_HIERARCHY_MULTIPLE;
+      taxonomy_vocabulary_save($form['#vocabulary']);
+    }
+
+    $form_state['values']['tid'] = $term->tid;
+    $form_state['tid'] = $term->tid;
   }
 
   /**
    * @see Drupal\entity\EntityFormController::delete()
    */
   public function delete(array $form, array &$form_state) {
-    taxonomy_term_form_delete_submit($form, $form_state);
+    $destination = array();
+    if (isset($_GET['destination'])) {
+      $destination = drupal_get_destination();
+      unset($_GET['destination']);
+    }
+    $term = $this->getEntity();
+    $form_state['redirect'] = array('taxonomy/term/' . $term->tid . '/delete', array('query' => $destination));
   }
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
index a14cff8..1f34e66 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
@@ -278,7 +278,7 @@ class TermTest extends TaxonomyTestBase {
       'description[value]' => $this->randomName(100),
     );
     // Explicitly set the parents field to 'root', to ensure that
-    // taxonomy_term_form_submit() handles the invalid term ID correctly.
+    // TermFormController::save() handles the invalid term ID correctly.
     $edit['parent[]'] = array(0);
 
     // Create the term to edit.
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php
index 8b1c2c9..a16a1f8 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php
@@ -19,7 +19,47 @@ class VocabularyFormController extends EntityFormController {
    * @see Drupal\entity\EntityFormController::form()
    */
   protected function form(array $form, array &$form_state) {
-    $form = taxonomy_form_vocabulary($form, $form_state, $this->getEntity());
+    $vocabulary = $this->getEntity();
+
+    // @todo Legacy support. Modules are encouraged to access the entity using
+    //   $form_state. Remove in Drupal 8.
+    $form['#vocabulary'] = $vocabulary;
+
+    // Check whether we need a deletion confirmation form.
+    if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
+      return taxonomy_vocabulary_confirm_delete($form, $form_state, $form_state['values']['vid']);
+    }
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Name'),
+      '#default_value' => $vocabulary->name,
+      '#maxlength' => 255,
+      '#required' => TRUE,
+    );
+    $form['machine_name'] = array(
+      '#type' => 'machine_name',
+      '#default_value' => $vocabulary->machine_name,
+      '#maxlength' => 255,
+      '#machine_name' => array(
+        'exists' => 'taxonomy_vocabulary_machine_name_load',
+      ),
+    );
+    $form['description'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Description'),
+      '#default_value' => $vocabulary->description,
+    );
+    // Set the hierarchy to "multiple parents" by default. This simplifies the
+    // vocabulary form and standardizes the term form.
+    $form['hierarchy'] = array(
+      '#type' => 'value',
+      '#value' => '0',
+    );
+
+    if (isset($vocabulary->vid)) {
+      $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid);
+    }
+
     return parent::form($form, $form_state);
   }
 
@@ -36,7 +76,19 @@ class VocabularyFormController extends EntityFormController {
    */
   public function validate(array $form, array &$form_state) {
     parent::validate($form, $form_state);
-    taxonomy_form_vocabulary_validate($form, $form_state);
+
+    // Make sure that the machine name of the vocabulary is not in the
+    // disallowed list (names that conflict with menu items, such as 'list'
+    // and 'add').
+    // During the deletion there is no 'machine_name' key.
+    if (isset($form_state['values']['machine_name'])) {
+      // Do not allow machine names to conflict with taxonomy path arguments.
+      $machine_name = $form_state['values']['machine_name'];
+      $disallowed = array('add', 'list');
+      if (in_array($machine_name, $disallowed)) {
+        form_set_error('machine_name', t('The machine-readable name cannot be "add" or "list".'));
+      }
+    }
   }
 
   /**
@@ -44,14 +96,15 @@ class VocabularyFormController extends EntityFormController {
    */
   public function submit(array $form, array &$form_state) {
     // @todo We should not be calling taxonomy_vocabulary_confirm_delete() from
-    // within taxonomy_form_vocabulary().
+    // within the form builder.
     if ($form_state['triggering_element']['#value'] == t('Delete')) {
       // Rebuild the form to confirm vocabulary deletion.
       $form_state['rebuild'] = TRUE;
       $form_state['confirm_delete'] = TRUE;
+      return NULL;
     }
     else {
-      parent::submit($form, $form_state);
+      return parent::submit($form, $form_state);
     }
   }
 
@@ -59,6 +112,26 @@ class VocabularyFormController extends EntityFormController {
    * @see Drupal\entity\EntityFormController::save()
    */
   public function save(array $form, array &$form_state) {
-    taxonomy_form_vocabulary_submit($form, $form_state);
+    $vocabulary = $this->getEntity();
+
+    // Prevent leading and trailing spaces in vocabulary names.
+    $vocabulary->name = trim($vocabulary->name);
+
+    switch (taxonomy_vocabulary_save($vocabulary)) {
+      case SAVED_NEW:
+        drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
+        watchdog('taxonomy', 'Created new vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
+        $form_state['redirect'] = 'admin/structure/taxonomy/' . $vocabulary->machine_name;
+        break;
+
+      case SAVED_UPDATED:
+        drupal_set_message(t('Updated vocabulary %name.', array('%name' => $vocabulary->name)));
+        watchdog('taxonomy', 'Updated vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
+        $form_state['redirect'] = 'admin/structure/taxonomy';
+        break;
+    }
+
+    $form_state['values']['vid'] = $vocabulary->vid;
+    $form_state['vid'] = $vocabulary->vid;
   }
 }
diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc
index dc6a5af..a1b8799 100644
--- a/core/modules/taxonomy/taxonomy.admin.inc
+++ b/core/modules/taxonomy/taxonomy.admin.inc
@@ -106,136 +106,16 @@ function theme_taxonomy_overview_vocabularies($variables) {
  * Page callback: provides the vocabulary creation form.
  */
 function taxonomy_vocabulary_add() {
-  $vocabulary = entity_create('taxonomy_vocabulary', array());
+  $vocabulary = entity_create('taxonomy_vocabulary', array(
+    // Default the new vocabulary to the site's default language. This is the
+    // most likely default value until we have better flexible settings.
+    // @todo See http://drupal.org/node/258785 and followups.
+    'langcode' => language_default()->langcode,
+  ));
   return entity_get_form($vocabulary);
 }
 
 /**
- * Form builder for the vocabulary editing form.
- *
- * @param Drupal\taxonomy\Vocabulary|null $vocabulary
- *   (optional) The taxonomy vocabulary entity to edit. If NULL or omitted, the
- *   form creates a new vocabulary.
- *
- * @ingroup forms
- * @see taxonomy_form_vocabulary_submit()
- * @see taxonomy_form_vocabulary_validate()
- */
-function taxonomy_form_vocabulary($form, &$form_state, Vocabulary $vocabulary = NULL) {
-  // During initial form build, add the entity to the form state for use
-  // during form building and processing. During a rebuild, use what is in the
-  // form state.
-  if (!isset($form_state['vocabulary'])) {
-    // Create a new Vocabulary entity for the add form.
-    if (!isset($vocabulary)) {
-      $vocabulary = entity_create('taxonomy_vocabulary', array(
-        // Default the new vocabulary to the site's default language. This is
-        // the most likely default value until we have better flexible settings.
-        // @todo See http://drupal.org/node/258785 and followups.
-        'langcode' => language_default()->langcode,
-      ));
-    }
-    $form_state['vocabulary'] = $vocabulary;
-  }
-  else {
-    $vocabulary = $form_state['vocabulary'];
-  }
-
-  // @todo Legacy support. Modules are encouraged to access the entity using
-  //   $form_state. Remove in Drupal 8.
-  $form['#vocabulary'] = $form_state['vocabulary'];
-
-  // Check whether we need a deletion confirmation form.
-  if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
-    return taxonomy_vocabulary_confirm_delete($form, $form_state, $form_state['values']['vid']);
-  }
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Name'),
-    '#default_value' => $vocabulary->name,
-    '#maxlength' => 255,
-    '#required' => TRUE,
-  );
-  $form['machine_name'] = array(
-    '#type' => 'machine_name',
-    '#default_value' => $vocabulary->machine_name,
-    '#maxlength' => 255,
-    '#machine_name' => array(
-      'exists' => 'taxonomy_vocabulary_machine_name_load',
-    ),
-  );
-  $form['description'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Description'),
-    '#default_value' => $vocabulary->description,
-  );
-  // Set the hierarchy to "multiple parents" by default. This simplifies the
-  // vocabulary form and standardizes the term form.
-  $form['hierarchy'] = array(
-    '#type' => 'value',
-    '#value' => '0',
-  );
-
-  if (isset($vocabulary->vid)) {
-    $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid);
-  }
-
-  return $form;
-}
-
-/**
- * Form validation handler for taxonomy_form_vocabulary().
- *
- * Makes sure that the machine name of the vocabulary is not in the
- * disallowed list (names that conflict with menu items, such as 'list'
- * and 'add').
- *
- * @see taxonomy_form_vocabulary()
- * @see taxonomy_form_vocabulary_submit()
- */
-function taxonomy_form_vocabulary_validate($form, &$form_state) {
-  // During the deletion there is no 'machine_name' key
-  if (isset($form_state['values']['machine_name'])) {
-    // Do not allow machine names to conflict with taxonomy path arguments.
-    $machine_name = $form_state['values']['machine_name'];
-    $disallowed = array('add', 'list');
-    if (in_array($machine_name, $disallowed)) {
-      form_set_error('machine_name', t('The machine-readable name cannot be "add" or "list".'));
-    }
-  }
-}
-
-/**
- * Form submission handler for taxonomy_form_vocabulary().
- *
- * @see taxonomy_form_vocabulary()
- * @see taxonomy_form_vocabulary_validate()
- */
-function taxonomy_form_vocabulary_submit($form, &$form_state) {
-  $vocabulary = EntityFormController::getFormInstance($form_state)->getEntity();
-
-  // Prevent leading and trailing spaces in vocabulary names.
-  $vocabulary->name = trim($vocabulary->name);
-
-  switch (taxonomy_vocabulary_save($vocabulary)) {
-    case SAVED_NEW:
-      drupal_set_message(t('Created new vocabulary %name.', array('%name' => $vocabulary->name)));
-      watchdog('taxonomy', 'Created new vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
-      $form_state['redirect'] = 'admin/structure/taxonomy/' . $vocabulary->machine_name;
-      break;
-
-    case SAVED_UPDATED:
-      drupal_set_message(t('Updated vocabulary %name.', array('%name' => $vocabulary->name)));
-      watchdog('taxonomy', 'Updated vocabulary %name.', array('%name' => $vocabulary->name), WATCHDOG_NOTICE, l(t('edit'), 'admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit'));
-      $form_state['redirect'] = 'admin/structure/taxonomy';
-      break;
-  }
-
-  $form_state['values']['vid'] = $vocabulary->vid;
-  $form_state['vid'] = $vocabulary->vid;
-}
-
-/**
  * Form builder for the taxonomy terms overview.
  *
  * Display a tree of all the terms in a vocabulary, with options to edit
@@ -648,229 +528,6 @@ function taxonomy_term_add($vocabulary) {
 }
 
 /**
- * Form function for the term edit form.
- *
- * @param Drupal\taxonomy\Term|null $term
- *   (optional) The taxonomy term entity to edit. If NULL or omitted, the form
- *   creates a new term.
- * @param Drupal\taxonomy\Vocabulary|null $vocabulary
- *   (optional) A taxonomy vocabulary entity to create the term in. Required if
- *   the term is omitted.
- *
- * @ingroup forms
- * @see taxonomy_term_form_validate()
- * @see taxonomy_term_form_submit()
- */
-function taxonomy_term_form($form, &$form_state, Term $term = NULL, Vocabulary $vocabulary = NULL) {
-  // During initial form build, add the term entity to the form state for use
-  // during form building and processing. During a rebuild, use what is in the
-  // form state.
-  if (!isset($form_state['term'])) {
-    // Create a new Term entity for the add form.
-    if (!isset($term)) {
-      $term = entity_create('taxonomy_term', array(
-        'vid' => $vocabulary->vid,
-        'vocabulary_machine_name' => $vocabulary->machine_name,
-        // Default the new vocabulary to the site's default language. This is
-        // the most likely default value until we have better flexible settings.
-        // @todo See http://drupal.org/node/258785 and followups.
-        'langcode' => language_default()->langcode,
-      ));
-    }
-    if (!isset($vocabulary) && isset($term->vid)) {
-      $vocabulary = taxonomy_vocabulary_load($term->vid);
-    }
-    $form_state['term'] = $term;
-  }
-  else {
-    $term = $form_state['term'];
-    if (!isset($vocabulary) && isset($term->vid)) {
-      $vocabulary = taxonomy_vocabulary_load($term->vid);
-    }
-  }
-
-  $parent = array_keys(taxonomy_term_load_parents($term->tid));
-  $form['#term'] = (array) $term;
-  $form['#term']['parent'] = $parent;
-  $form['#vocabulary'] = $vocabulary;
-
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Name'),
-    '#default_value' => $term->name,
-    '#maxlength' => 255,
-    '#required' => TRUE,
-    '#weight' => -5,
-  );
-  $form['description'] = array(
-    '#type' => 'text_format',
-    '#title' => t('Description'),
-    '#default_value' => $term->description,
-    '#format' => $term->format,
-    '#weight' => 0,
-  );
-
-  $form['vocabulary_machine_name'] = array(
-    '#type' => 'value',
-    '#value' => isset($term->vocabulary_machine_name) ? $term->vocabulary_machine_name : $vocabulary->name,
-  );
-
-  $form['relations'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Relations'),
-    '#collapsible' => TRUE,
-    '#collapsed' => ($vocabulary->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE),
-    '#weight' => 10,
-  );
-
-  // taxonomy_get_tree and taxonomy_term_load_parents may contain large numbers of
-  // items so we check for taxonomy_override_selector before loading the
-  // full vocabulary. Contrib modules can then intercept before
-  // hook_form_alter to provide scalable alternatives.
-  if (!variable_get('taxonomy_override_selector', FALSE)) {
-    $parent = array_keys(taxonomy_term_load_parents($term->tid));
-    $children = taxonomy_get_tree($vocabulary->vid, $term->tid);
-
-    // A term can't be the child of itself, nor of its children.
-    foreach ($children as $child) {
-      $exclude[] = $child->tid;
-    }
-    $exclude[] = $term->tid;
-
-    $tree = taxonomy_get_tree($vocabulary->vid);
-    $options = array('<' . t('root') . '>');
-    if (empty($parent)) {
-      $parent = array(0);
-    }
-    foreach ($tree as $item) {
-      if (!in_array($item->tid, $exclude)) {
-        $options[$item->tid] = str_repeat('-', $item->depth) . $item->name;
-      }
-    }
-    $form['relations']['parent'] = array(
-      '#type' => 'select',
-      '#title' => t('Parent terms'),
-      '#options' => $options,
-      '#default_value' => $parent,
-      '#multiple' => TRUE,
-    );
-
-  }
-  $form['relations']['weight'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Weight'),
-    '#size' => 6,
-    '#default_value' => $term->weight,
-    '#description' => t('Terms are displayed in ascending order by weight.'),
-    '#required' => TRUE,
-  );
-  $form['vid'] = array(
-    '#type' => 'value',
-    '#value' => $vocabulary->vid,
-  );
-  $form['tid'] = array(
-    '#type' => 'value',
-    '#value' => $term->tid,
-  );
-
-  if (empty($term->tid)) {
-    $form_state['redirect'] = current_path();
-  }
-
-  return $form;
-}
-
-/**
- * Validation handler for the term form.
- *
- * @see taxonomy_term_form()
- */
-function taxonomy_term_form_validate($form, &$form_state) {
-  // Ensure numeric values.
-  if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) {
-    form_set_error('weight', t('Weight value must be numeric.'));
-  }
-}
-
-/**
- * Submit handler to insert or update a term.
- *
- * @see taxonomy_term_form()
- */
-function taxonomy_term_form_submit($form, &$form_state) {
-  $term = EntityFormController::getFormInstance($form_state)->getEntity();
-
-  $status = taxonomy_term_save($term);
-  switch ($status) {
-    case SAVED_NEW:
-      drupal_set_message(t('Created new term %term.', array('%term' => $term->name)));
-      watchdog('taxonomy', 'Created new term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
-      break;
-    case SAVED_UPDATED:
-      drupal_set_message(t('Updated term %term.', array('%term' => $term->name)));
-      watchdog('taxonomy', 'Updated term %term.', array('%term' => $term->name), WATCHDOG_NOTICE, l(t('edit'), 'taxonomy/term/' . $term->tid . '/edit'));
-      // Clear the page and block caches to avoid stale data.
-      cache_invalidate(array('content' => TRUE));
-      break;
-  }
-
-  $current_parent_count = count($form_state['values']['parent']);
-  $previous_parent_count = count($form['#term']['parent']);
-  // Root doesn't count if it's the only parent.
-  if ($current_parent_count == 1 && isset($form_state['values']['parent'][0])) {
-    $current_parent_count = 0;
-    $form_state['values']['parent'] = array();
-  }
-
-  // If the number of parents has been reduced to one or none, do a check on the
-  // parents of every term in the vocabulary value.
-  if ($current_parent_count < $previous_parent_count && $current_parent_count < 2) {
-    taxonomy_check_vocabulary_hierarchy($form['#vocabulary'], $form_state['values']);
-  }
-  // If we've increased the number of parents and this is a single or flat
-  // hierarchy, update the vocabulary immediately.
-  elseif ($current_parent_count > $previous_parent_count && $form['#vocabulary']->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE) {
-    $form['#vocabulary']->hierarchy = $current_parent_count == 1 ? TAXONOMY_HIERARCHY_SINGLE : TAXONOMY_HIERARCHY_MULTIPLE;
-    taxonomy_vocabulary_save($form['#vocabulary']);
-  }
-
-  $form_state['values']['tid'] = $term->tid;
-  $form_state['tid'] = $term->tid;
-}
-
-/**
- * Updates the form state's term entity by processing this submission's values.
- */
-function taxonomy_term_form_submit_build_taxonomy_term($form, &$form_state) {
-  $term = EntityFormController::getFormInstance($form_state)->getEntity();
-
-  // Prevent leading and trailing spaces in term names.
-  $term->name = trim($term->name);
-
-  // Convert text_format field into values expected by taxonomy_term_save().
-  $description = $form_state['values']['description'];
-  $term->description = $description['value'];
-  $term->format = $description['format'];
-  return $term;
-}
-
-/**
- * Form submission handler for the 'Delete' button for taxonomy_term_form().
- *
- * @see taxonomy_term_form_validate()
- * @see taxonomy_term_form_submit()
- */
-function taxonomy_term_form_delete_submit($form, &$form_state) {
-  $destination = array();
-  if (isset($_GET['destination'])) {
-    $destination = drupal_get_destination();
-    unset($_GET['destination']);
-  }
-  $term = EntityFormController::getFormInstance($form_state)->getEntity();
-  $form_state['redirect'] = array('taxonomy/term/' . $term->tid . '/delete', array('query' => $destination));
-}
-
-/**
  * Form builder for the term delete form.
  *
  * @ingroup forms
diff --git a/core/modules/user/lib/Drupal/user/AccountFormController.php b/core/modules/user/lib/Drupal/user/AccountFormController.php
new file mode 100644
index 0000000..1ecd890
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/AccountFormController.php
@@ -0,0 +1,330 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\user\AccountFormController.
+ */
+
+namespace Drupal\user;
+
+use Drupal\entity\EntityInterface;
+use Drupal\entity\EntityFormController;
+
+/**
+ * Form controller for the user account forms.
+ */
+abstract class AccountFormController extends EntityFormController {
+
+  /**
+   * @see Drupal\entity\EntityFormController::form()
+   */
+  protected function form(array $form, array &$form_state) {
+    global $user;
+
+    $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE);
+    $account = $this->getEntity();
+    $register = empty($account->uid);
+    $admin = user_access('administer users');
+
+    // Account information.
+    $form['account'] = array(
+      '#type'   => 'container',
+      '#weight' => -10,
+    );
+
+    // Only show name field on registration form or user can change own username.
+    $form['account']['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Username'),
+      '#maxlength' => USERNAME_MAX_LENGTH,
+      '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
+      '#required' => TRUE,
+      '#attributes' => array('class' => array('username'), 'autocomplete' => 'off'),
+      '#default_value' => (!$register ? $account->name : ''),
+      '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin),
+      '#weight' => -10,
+    );
+
+    // The mail field is NOT required if account originally had no mail set
+    // and the user performing the edit has 'administer users' permission.
+    // This allows users without e-mail address to be edited and deleted.
+    $form['account']['mail'] = array(
+      '#type' => 'email',
+      '#title' => t('E-mail address'),
+      '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'),
+      '#required' => !(empty($account->mail) && user_access('administer users')),
+      '#default_value' => (!$register ? $account->mail : ''),
+      '#attributes' => array('autocomplete' => 'off'),
+    );
+
+    // Display password field only for existing users or when user is allowed to
+    // assign a password during registration.
+    if (!$register) {
+      $form['account']['pass'] = array(
+        '#type' => 'password_confirm',
+        '#size' => 25,
+        '#description' => t('To change the current user password, enter the new password in both fields.'),
+      );
+
+      // To skip the current password field, the user must have logged in via a
+      // one-time link and have the token in the URL.
+      $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);
+      $protected_values = array();
+      $current_pass_description = '';
+
+      // The user may only change their own password without their current
+      // password if they logged in via a one-time login link.
+      if (!$pass_reset) {
+        $protected_values['mail'] = $form['account']['mail']['#title'];
+        $protected_values['pass'] = t('Password');
+        $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
+        $current_pass_description = t('Required if you want to change the %mail or %pass below. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new));
+      }
+
+      // The user must enter their current password to change to a new one.
+      if ($user->uid == $account->uid) {
+        $form['account']['current_pass_required_values'] = array(
+          '#type' => 'value',
+          '#value' => $protected_values,
+        );
+
+        $form['account']['current_pass'] = array(
+          '#type' => 'password',
+          '#title' => t('Current password'),
+          '#size' => 25,
+          '#access' => !empty($protected_values),
+          '#description' => $current_pass_description,
+          '#weight' => -5,
+          '#attributes' => array('autocomplete' => 'off'),
+        );
+
+        $form['#validate'][] = 'user_validate_current_pass';
+      }
+    }
+    elseif (!variable_get('user_email_verification', TRUE) || $admin) {
+      $form['account']['pass'] = array(
+        '#type' => 'password_confirm',
+        '#size' => 25,
+        '#description' => t('Provide a password for the new account in both fields.'),
+        '#required' => TRUE,
+      );
+    }
+
+    if ($admin) {
+      $status = isset($account->status) ? $account->status : 1;
+    }
+    else {
+      $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status;
+    }
+
+    $form['account']['status'] = array(
+      '#type' => 'radios',
+      '#title' => t('Status'),
+      '#default_value' => $status,
+      '#options' => array(t('Blocked'), t('Active')),
+      '#access' => $admin,
+    );
+
+    $roles = array_map('check_plain', user_roles(TRUE));
+    // The disabled checkbox subelement for the 'authenticated user' role
+    // must be generated separately and added to the checkboxes element,
+    // because of a limitation in Form API not supporting a single disabled
+    // checkbox within a set of checkboxes.
+    // @todo This should be solved more elegantly. See issue #119038.
+    $checkbox_authenticated = array(
+      '#type' => 'checkbox',
+      '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
+      '#default_value' => TRUE,
+      '#disabled' => TRUE,
+    );
+    unset($roles[DRUPAL_AUTHENTICATED_RID]);
+
+    $form['account']['roles'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Roles'),
+      '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
+      '#options' => $roles,
+      '#access' => $roles && user_access('administer permissions'),
+      DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
+    );
+
+    $form['account']['notify'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Notify user of new account'),
+      '#access' => $register && $admin,
+    );
+
+    // Signature.
+    $form['signature_settings'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Signature settings'),
+      '#weight' => 1,
+      '#access' => (!$register && variable_get('user_signatures', 0)),
+    );
+
+    $form['signature_settings']['signature'] = array(
+      '#type' => 'text_format',
+      '#title' => t('Signature'),
+      '#default_value' => isset($account->signature) ? $account->signature : '',
+      '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
+      '#format' => isset($account->signature_format) ? $account->signature_format : NULL,
+    );
+
+    // Picture/avatar.
+    $form['picture'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Picture'),
+      '#weight' => 1,
+      '#access' => (!$register && variable_get('user_pictures', 0)),
+    );
+
+    $form['picture']['picture'] = array(
+      '#type' => 'value',
+      '#value' => isset($account->picture) ? $account->picture : NULL,
+    );
+
+    $form['picture']['picture_current'] = array(
+      '#markup' => theme('user_picture', array('account' => $account)),
+    );
+
+    $form['picture']['picture_delete'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Delete picture'),
+      '#access' => !empty($account->picture->fid),
+      '#description' => t('Check this box to delete your current picture.'),
+    );
+
+    $form['picture']['picture_upload'] = array(
+      '#type' => 'file',
+      '#title' => t('Upload picture'),
+      '#size' => 48,
+      '#description' => t('Your virtual face or picture. Pictures larger than @dimensions pixels will be scaled down.', array('@dimensions' => variable_get('user_picture_dimensions', '85x85'))) . ' ' . filter_xss_admin(variable_get('user_picture_guidelines', '')),
+    );
+
+    $form['#validate'][] = 'user_validate_picture';
+
+    if (module_exists('language') && language_multilingual()) {
+      $languages = language_list();
+
+      // If the user is being created, we set the user language to the page language.
+      $user_preferred_language = $register ? $language_interface : user_preferred_language($account);
+
+      $names = array();
+      foreach ($languages as $langcode => $item) {
+        $names[$langcode] = $item->name;
+      }
+
+      // Is default the interface language?
+      $interface_language_is_default = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_DEFAULT;
+      $form['language'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Language settings'),
+        // Display language selector when either creating a user on the admin
+        // interface or editing a user account.
+        '#access' => !$register || user_access('administer users'),
+      );
+
+      $form['language']['preferred_langcode'] = array(
+        '#type' => (count($names) <= 5 ? 'radios' : 'select'),
+        '#title' => t('Language'),
+        '#default_value' => $user_preferred_language->langcode,
+        '#options' => $names,
+        '#description' => $interface_language_is_default ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."),
+      );
+    }
+    else {
+      $form['language'] = array(
+        '#type' => 'container',
+      );
+
+      $form['language']['preferred_langcode'] = array(
+        '#type' => 'value',
+        '#value' => language_default()->langcode,
+      );
+    }
+
+    // User entities contain both a langcode property (for identifying the
+    // language of the entity data) and a preferred_langcode property (see
+    // above). Rather than provide a UI forcing the user to choose both
+    // separately, assume that the user profile data is in the user's preferred
+    // language. This element provides that synchronization. For use-cases where
+    // this synchronization is not desired, a module can alter or remove this
+    // element.
+    $form['language']['langcode'] = array(
+      '#type' => 'value',
+      '#value_callback' => '_user_language_selector_langcode_value',
+      // For the synchronization to work, this element must have a larger weight
+      // than the preferred_langcode element. Set a large weight here in case
+      // a module alters the weight of the other element.
+      '#weight' => 100,
+    );
+
+    return parent::form($form, $form_state);
+  }
+
+  /**
+   * @see Drupal\entity\EntityFormController::submit()
+   */
+  public function validate(array $form, array &$form_state) {
+    parent::validate($form, $form_state);
+
+    $account = $this->getEntity();
+    // Validate new or changing username.
+    if (isset($form_state['values']['name'])) {
+      if ($error = user_validate_name($form_state['values']['name'])) {
+        form_set_error('name', $error);
+      }
+      // Cast the user ID as an integer. It might have been set to NULL, which
+      // could lead to unexpected results.
+      else {
+        $name_taken = (bool) db_select('users')
+        ->fields('users', array('uid'))
+        ->condition('uid', (int) $account->uid, '<>')
+        ->condition('name', db_like($form_state['values']['name']), 'LIKE')
+        ->range(0, 1)
+        ->execute()
+        ->fetchField();
+
+        if ($name_taken) {
+          form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
+        }
+      }
+    }
+
+    $mail = $form_state['values']['mail'];
+
+    if (!empty($mail)) {
+      $mail_taken = (bool) db_select('users')
+      ->fields('users', array('uid'))
+      ->condition('uid', (int) $account->uid, '<>')
+      ->condition('mail', db_like($mail), 'LIKE')
+      ->range(0, 1)
+      ->execute()
+      ->fetchField();
+
+      if ($mail_taken) {
+        // Format error message dependent on whether the user is logged in or not.
+        if ($GLOBALS['user']->uid) {
+          form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $mail)));
+        }
+        else {
+          form_set_error('mail', t('The e-mail address %email is already registered. <a href="@password">Have you forgotten your password?</a>', array('%email' => $mail, '@password' => url('user/password'))));
+        }
+      }
+    }
+
+    // Make sure the signature isn't longer than the size of the database field.
+    // Signatures are disabled by default, so make sure it exists first.
+    if (isset($form_state['values']['signature'])) {
+      // Move text format for user signature into 'signature_format'.
+      $form_state['values']['signature_format'] = $form_state['values']['signature']['format'];
+      // Move text value for user signature into 'signature'.
+      $form_state['values']['signature'] = $form_state['values']['signature']['value'];
+
+      $user_schema = drupal_get_schema('users');
+      if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) {
+        form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
+      }
+    }
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/ProfileFormController.php b/core/modules/user/lib/Drupal/user/ProfileFormController.php
index 7f59e94..fcd0842 100644
--- a/core/modules/user/lib/Drupal/user/ProfileFormController.php
+++ b/core/modules/user/lib/Drupal/user/ProfileFormController.php
@@ -8,12 +8,11 @@
 namespace Drupal\user;
 
 use Drupal\entity\EntityInterface;
-use Drupal\entity\EntityFormController;
 
 /**
  * Form controller for the profile forms.
  */
-class ProfileFormController extends EntityFormController {
+class ProfileFormController extends AccountFormController {
 
   /**
    * @see Drupal\entity\EntityFormController::form()
@@ -22,9 +21,6 @@ class ProfileFormController extends EntityFormController {
     // @todo Legacy support. Modules are encouraged to access the entity using
     //   $form_state. Remove in Drupal 8.
     $form['#user'] = $this->getEntity();
-
-    user_account_form($form, $form_state);
-
     return parent::form($form, $form_state);
   }
 
diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php
index 5205a04..c3f6249 100644
--- a/core/modules/user/lib/Drupal/user/RegisterFormController.php
+++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php
@@ -8,18 +8,58 @@
 namespace Drupal\user;
 
 use Drupal\entity\EntityInterface;
-use Drupal\entity\EntityFormController;
 
 /**
  * Form controller for the user register forms.
  */
-class RegisterFormController extends EntityFormController {
+class RegisterFormController extends AccountFormController {
 
   /**
    * @see Drupal\entity\EntityFormController::form()
    */
   protected function form(array $form, array &$form_state) {
-    return _user_register_form($form, $form_state);
+    global $user;
+
+    $admin = user_access('administer users');
+
+    // Pass access information to the submit handler. Running an access check
+    // inside the submit function interferes with form processing and breaks
+    // hook_form_alter().
+    $form['administer_users'] = array(
+      '#type' => 'value',
+      '#value' => $admin,
+    );
+
+    // If we aren't admin but already logged on, go to the user page instead.
+    if (!$admin && $user->uid) {
+      drupal_goto('user/' . $user->uid);
+    }
+
+    $account = $this->getEntity();
+    $form['#user'] = $account;
+
+    $form['#attached']['library'][] = array('system', 'jquery.cookie');
+    $form['#attributes']['class'][] = 'user-info-from-cookie';
+
+    // Start with the default user account fields.
+    $form = parent::form($form, $form_state);
+
+    // Attach field widgets, and hide the ones where the 'user_register_form'
+    // setting is not on.
+    field_attach_form('user', $account, $form, $form_state);
+    foreach (field_info_instances('user', 'user') as $field_name => $instance) {
+      if (empty($instance['settings']['user_register_form'])) {
+        $form[$field_name]['#access'] = FALSE;
+      }
+    }
+
+    if ($admin) {
+      // Redirect back to page which initiated the create request; usually
+      // admin/people/create.
+      $form_state['redirect'] = current_path();
+    }
+
+    return $form;
   }
 
   /**
@@ -57,6 +97,62 @@ class RegisterFormController extends EntityFormController {
    * @see Drupal\entity\EntityFormController::submit()
    */
   public function save(array $form, array &$form_state) {
-    user_register_submit($form, $form_state);
+    $account = $this->getEntity();
+    $pass = $account->pass;
+    $admin = $form_state['values']['administer_users'];
+    $notify = !empty($form_state['values']['notify']);
+
+    $status = $account->save();
+
+    // Terminate if an error occurred while saving the account.
+    if ($status =! SAVED_NEW) {
+      drupal_set_message(t("Error saving user account."), 'error');
+      $form_state['redirect'] = '';
+      return;
+    }
+    $form_state['user'] = $account;
+    $form_state['values']['uid'] = $account->uid;
+
+    watchdog('user', 'New user: %name (%email).', array('%name' => $form_state['values']['name'], '%email' => $form_state['values']['mail']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit'));
+
+    // Add plain text password into user account to generate mail tokens.
+    $account->password = $pass;
+
+    // New administrative account without notification.
+    $uri = entity_uri('user', $account);
+    if ($admin && !$notify) {
+      drupal_set_message(t('Created a new user account for <a href="@url">%name</a>. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
+    }
+    // No e-mail verification required; log in user immediately.
+    elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) {
+      _user_mail_notify('register_no_approval_required', $account);
+      $form_state['uid'] = $account->uid;
+      user_login_submit(array(), $form_state);
+      drupal_set_message(t('Registration successful. You are now logged in.'));
+      $form_state['redirect'] = '';
+    }
+    // No administrator approval required.
+    elseif ($account->status || $notify) {
+      if (empty($account->mail) && $notify) {
+        drupal_set_message(t('The new user <a href="@url">%name</a> was created without an email address, so no welcome message was sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
+      }
+      else {
+        $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
+        _user_mail_notify($op, $account);
+        if ($notify) {
+          drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user <a href="@url">%name</a>.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
+        }
+        else {
+          drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.'));
+          $form_state['redirect'] = '';
+        }
+      }
+    }
+    // Administrator approval required.
+    else {
+      _user_mail_notify('register_pending_approval', $account);
+      drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.<br />In the meantime, a welcome message with further instructions has been sent to your e-mail address.'));
+      $form_state['redirect'] = '';
+    }
   }
 }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 8f9a819..b283c92 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -404,7 +404,7 @@ function user_validate_name($name) {
 /**
  * Validates an image uploaded by a user.
  *
- * @see user_account_form()
+ * @see AccountFormController::form()
  */
 function user_validate_picture(&$form, &$form_state) {
   // If required, validate the uploaded picture.
@@ -706,242 +706,6 @@ function user_user_view($account) {
 }
 
 /**
- * Helper function to add default user account fields to user registration and edit form.
- *
- * @see user_account_form_validate()
- * @see user_validate_current_pass()
- * @see user_validate_picture()
- * @see user_validate_mail()
- */
-function user_account_form(&$form, &$form_state) {
-  global $user;
-  $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE);
-
-  $account = EntityFormController::getFormInstance($form_state)->getEntity();
-  $register = ($account->uid > 0 ? FALSE : TRUE);
-
-  $admin = user_access('administer users');
-
-  $form['#validate'][] = 'user_account_form_validate';
-
-  // Account information.
-  $form['account'] = array(
-    '#type'   => 'container',
-    '#weight' => -10,
-  );
-  // Only show name field on registration form or user can change own username.
-  $form['account']['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Username'),
-    '#maxlength' => USERNAME_MAX_LENGTH,
-    '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
-    '#required' => TRUE,
-    '#attributes' => array('class' => array('username'), 'autocomplete' => 'off'),
-    '#default_value' => (!$register ? $account->name : ''),
-    '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin),
-    '#weight' => -10,
-  );
-
-  // The mail field is NOT required if account originally had no mail set
-  // and the user performing the edit has 'administer users' permission.
-  // This allows users without e-mail address to be edited and deleted.
-  $form['account']['mail'] = array(
-    '#type' => 'email',
-    '#title' => t('E-mail address'),
-    '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'),
-    '#required' => !(empty($account->mail) && user_access('administer users')),
-    '#default_value' => (!$register ? $account->mail : ''),
-    '#attributes' => array('autocomplete' => 'off'),
-  );
-
-  // Display password field only for existing users or when user is allowed to
-  // assign a password during registration.
-  if (!$register) {
-    $form['account']['pass'] = array(
-      '#type' => 'password_confirm',
-      '#size' => 25,
-      '#description' => t('To change the current user password, enter the new password in both fields.'),
-    );
-    // To skip the current password field, the user must have logged in via a
-    // one-time link and have the token in the URL.
-    $pass_reset = isset($_SESSION['pass_reset_' . $account->uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $account->uid]);
-    $protected_values = array();
-    $current_pass_description = '';
-    // The user may only change their own password without their current
-    // password if they logged in via a one-time login link.
-    if (!$pass_reset) {
-      $protected_values['mail'] = $form['account']['mail']['#title'];
-      $protected_values['pass'] = t('Password');
-      $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
-      $current_pass_description = t('Required if you want to change the %mail or %pass below. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new));
-    }
-    // The user must enter their current password to change to a new one.
-    if ($user->uid == $account->uid) {
-      $form['account']['current_pass_required_values'] = array(
-        '#type' => 'value',
-        '#value' => $protected_values,
-      );
-      $form['account']['current_pass'] = array(
-        '#type' => 'password',
-        '#title' => t('Current password'),
-        '#size' => 25,
-        '#access' => !empty($protected_values),
-        '#description' => $current_pass_description,
-        '#weight' => -5,
-        '#attributes' => array('autocomplete' => 'off'),
-      );
-      $form['#validate'][] = 'user_validate_current_pass';
-    }
-  }
-  elseif (!variable_get('user_email_verification', TRUE) || $admin) {
-    $form['account']['pass'] = array(
-      '#type' => 'password_confirm',
-      '#size' => 25,
-      '#description' => t('Provide a password for the new account in both fields.'),
-      '#required' => TRUE,
-    );
-  }
-
-  if ($admin) {
-    $status = isset($account->status) ? $account->status : 1;
-  }
-  else {
-    $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status;
-  }
-  $form['account']['status'] = array(
-    '#type' => 'radios',
-    '#title' => t('Status'),
-    '#default_value' => $status,
-    '#options' => array(t('Blocked'), t('Active')),
-    '#access' => $admin,
-  );
-
-  $roles = array_map('check_plain', user_roles(TRUE));
-  // The disabled checkbox subelement for the 'authenticated user' role
-  // must be generated separately and added to the checkboxes element,
-  // because of a limitation in Form API not supporting a single disabled
-  // checkbox within a set of checkboxes.
-  // @todo This should be solved more elegantly. See issue #119038.
-  $checkbox_authenticated = array(
-    '#type' => 'checkbox',
-    '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
-    '#default_value' => TRUE,
-    '#disabled' => TRUE,
-  );
-  unset($roles[DRUPAL_AUTHENTICATED_RID]);
-  $form['account']['roles'] = array(
-    '#type' => 'checkboxes',
-    '#title' => t('Roles'),
-    '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
-    '#options' => $roles,
-    '#access' => $roles && user_access('administer permissions'),
-    DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
-  );
-
-  $form['account']['notify'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Notify user of new account'),
-    '#access' => $register && $admin,
-  );
-
-  // Signature.
-  $form['signature_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Signature settings'),
-    '#weight' => 1,
-    '#access' => (!$register && variable_get('user_signatures', 0)),
-  );
-
-  $form['signature_settings']['signature'] = array(
-    '#type' => 'text_format',
-    '#title' => t('Signature'),
-    '#default_value' => isset($account->signature) ? $account->signature : '',
-    '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
-    '#format' => isset($account->signature_format) ? $account->signature_format : NULL,
-  );
-
-  // Picture/avatar.
-  $form['picture'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Picture'),
-    '#weight' => 1,
-    '#access' => (!$register && variable_get('user_pictures', 0)),
-  );
-  $form['picture']['picture'] = array(
-    '#type' => 'value',
-    '#value' => isset($account->picture) ? $account->picture : NULL,
-  );
-  $form['picture']['picture_current'] = array(
-    '#markup' => theme('user_picture', array('account' => $account)),
-  );
-  $form['picture']['picture_delete'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Delete picture'),
-    '#access' => !empty($account->picture->fid),
-    '#description' => t('Check this box to delete your current picture.'),
-  );
-  $form['picture']['picture_upload'] = array(
-    '#type' => 'file',
-    '#title' => t('Upload picture'),
-    '#size' => 48,
-    '#description' => t('Your virtual face or picture. Pictures larger than @dimensions pixels will be scaled down.', array('@dimensions' => variable_get('user_picture_dimensions', '85x85'))) . ' ' . filter_xss_admin(variable_get('user_picture_guidelines', '')),
-  );
-  $form['#validate'][] = 'user_validate_picture';
-
-  if (module_exists('language') && language_multilingual()) {
-    $languages = language_list();
-
-    // If the user is being created, we set the user language to the page language.
-    $user_preferred_language = $register ? $language_interface : user_preferred_language($account);
-
-    $names = array();
-    foreach ($languages as $langcode => $item) {
-      $names[$langcode] = $item->name;
-    }
-    // Is default the interface language?
-    $interface_language_is_default = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_DEFAULT;
-    $form['language'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Language settings'),
-      // Display language selector when either creating a user on the admin
-      // interface or editing a user account.
-      '#access' => !$register || user_access('administer users'),
-    );
-    $form['language']['preferred_langcode'] = array(
-      '#type' => (count($names) <= 5 ? 'radios' : 'select'),
-      '#title' => t('Language'),
-      '#default_value' => $user_preferred_language->langcode,
-      '#options' => $names,
-      '#description' => $interface_language_is_default ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."),
-    );
-  }
-  else {
-    $form['language'] = array(
-      '#type' => 'container',
-    );
-    $form['language']['preferred_langcode'] = array(
-      '#type' => 'value',
-      '#value' => language_default()->langcode,
-    );
-  }
-
-  // User entities contain both a langcode property (for identifying the
-  // language of the entity data) and a preferred_langcode property (see above).
-  // Rather than provide a UI forcing the user to choose both separately,
-  // assume that the user profile data is in the user's preferred language. This
-  // element provides that synchronization. For use-cases where this
-  // synchronization is not desired, a module can alter or remove this element.
-  $form['language']['langcode'] = array(
-    '#type' => 'value',
-    '#value_callback' => '_user_language_selector_langcode_value',
-    // For the synchronization to work, this element must have a larger weight
-    // than the preferred_langcode element. Set a large weight here in case
-    // a module alters the weight of the other element.
-    '#weight' => 100,
-  );
-}
-
-/**
  * Sets the value of the user register and profile forms' langcode element.
  */
 function _user_language_selector_langcode_value($element, $input, &$form_state) {
@@ -953,9 +717,9 @@ function _user_language_selector_langcode_value($element, $input, &$form_state)
 }
 
 /**
- * Form validation handler for the current password on the user_account_form().
+ * Form validation handler for the current password on the user account form.
  *
- * @see user_account_form()
+ * @see AccountFormController::form()
  */
 function user_validate_current_pass(&$form, &$form_state) {
   $account = $form['#user'];
@@ -976,72 +740,6 @@ function user_validate_current_pass(&$form, &$form_state) {
   }
 }
 
-/**
- * Form validation handler for user_account_form().
- *
- * @see user_account_form()
- */
-function user_account_form_validate($form, &$form_state) {
-  $account = $form['#user'];
-  // Validate new or changing username.
-  if (isset($form_state['values']['name'])) {
-    if ($error = user_validate_name($form_state['values']['name'])) {
-      form_set_error('name', $error);
-    }
-    // Cast the user ID as an integer. It might have been set to NULL, which
-    // could lead to unexpected results.
-    else {
-      $name_taken = (bool) db_select('users')
-        ->fields('users', array('uid'))
-        ->condition('uid', (int) $account->uid, '<>')
-        ->condition('name', db_like($form_state['values']['name']), 'LIKE')
-        ->range(0, 1)
-        ->execute()
-        ->fetchField();
-
-      if ($name_taken) {
-        form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
-      }
-    }
-  }
-
-  $mail = $form_state['values']['mail'];
-
-  if (!empty($mail)) {
-    $mail_taken = (bool) db_select('users')
-      ->fields('users', array('uid'))
-      ->condition('uid', (int) $account->uid, '<>')
-      ->condition('mail', db_like($mail), 'LIKE')
-      ->range(0, 1)
-      ->execute()
-      ->fetchField();
-
-    if ($mail_taken) {
-      // Format error message dependent on whether the user is logged in or not.
-      if ($GLOBALS['user']->uid) {
-        form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $mail)));
-      }
-      else {
-        form_set_error('mail', t('The e-mail address %email is already registered. <a href="@password">Have you forgotten your password?</a>', array('%email' => $mail, '@password' => url('user/password'))));
-      }
-    }
-  }
-
-  // Make sure the signature isn't longer than the size of the database field.
-  // Signatures are disabled by default, so make sure it exists first.
-  if (isset($form_state['values']['signature'])) {
-    // Move text format for user signature into 'signature_format'.
-    $form_state['values']['signature_format'] = $form_state['values']['signature']['format'];
-    // Move text value for user signature into 'signature'.
-    $form_state['values']['signature'] = $form_state['values']['signature']['value'];
-
-    $user_schema = drupal_get_schema('users');
-    if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) {
-      form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
-    }
-  }
-}
-
 function user_login_block($form) {
   $form['#action'] = url(current_path(), array('query' => drupal_get_destination(), 'external' => FALSE));
   $form['#id'] = 'user-login-form';
@@ -3500,124 +3198,6 @@ function user_form_field_ui_field_edit_form_submit($form, &$form_state) {
 }
 
 /**
- * Form builder; the user registration form.
- *
- * @ingroup forms
- * @see user_account_form()
- * @see user_account_form_validate()
- * @see user_register_submit()
- */
-function _user_register_form($form, &$form_state) {
-  global $user;
-
-  $admin = user_access('administer users');
-
-  // Pass access information to the submit handler. Running an access check
-  // inside the submit function interferes with form processing and breaks
-  // hook_form_alter().
-  $form['administer_users'] = array(
-     '#type' => 'value',
-     '#value' => $admin,
-  );
-
-  // If we aren't admin but already logged on, go to the user page instead.
-  if (!$admin && $user->uid) {
-    drupal_goto('user/' . $user->uid);
-  }
-
-  $form['#user'] = EntityFormController::getFormInstance($form_state)->getEntity();
-
-  $form['#attached']['library'][] = array('system', 'jquery.cookie');
-  $form['#attributes']['class'][] = 'user-info-from-cookie';
-
-  // Start with the default user account fields.
-  user_account_form($form, $form_state);
-
-  // Attach field widgets, and hide the ones where the 'user_register_form'
-  // setting is not on.
-  field_attach_form('user', $form['#user'], $form, $form_state);
-  foreach (field_info_instances('user', 'user') as $field_name => $instance) {
-    if (empty($instance['settings']['user_register_form'])) {
-      $form[$field_name]['#access'] = FALSE;
-    }
-  }
-
-  if ($admin) {
-    // Redirect back to page which initiated the create request;
-    // usually admin/people/create.
-    $form_state['redirect'] = current_path();
-  }
-
-  return $form;
-}
-
-/**
- * Submit handler for the user registration form.
- *
- * This function is shared by the installation form and the normal registration
- * form, which is why it can't be in the user.pages.inc file.
- */
-function user_register_submit($form, &$form_state) {
-  $account = EntityFormController::getFormInstance($form_state)->getEntity();
-  $pass = $account->pass;
-  $admin = $form_state['values']['administer_users'];
-  $notify = !empty($form_state['values']['notify']);
-
-  $status = $account->save();
-
-  // Terminate if an error occurred while saving the account.
-  if ($status =! SAVED_NEW) {
-    drupal_set_message(t("Error saving user account."), 'error');
-    $form_state['redirect'] = '';
-    return;
-  }
-  $form_state['user'] = $account;
-  $form_state['values']['uid'] = $account->uid;
-
-  watchdog('user', 'New user: %name (%email).', array('%name' => $form_state['values']['name'], '%email' => $form_state['values']['mail']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit'));
-
-  // Add plain text password into user account to generate mail tokens.
-  $account->password = $pass;
-
-  // New administrative account without notification.
-  $uri = entity_uri('user', $account);
-  if ($admin && !$notify) {
-    drupal_set_message(t('Created a new user account for <a href="@url">%name</a>. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
-  }
-  // No e-mail verification required; log in user immediately.
-  elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) {
-    _user_mail_notify('register_no_approval_required', $account);
-    $form_state['uid'] = $account->uid;
-    user_login_submit(array(), $form_state);
-    drupal_set_message(t('Registration successful. You are now logged in.'));
-    $form_state['redirect'] = '';
-  }
-  // No administrator approval required.
-  elseif ($account->status || $notify) {
-    if (empty($account->mail) && $notify) {
-      drupal_set_message(t('The new user <a href="@url">%name</a> was created without an email address, so no welcome message was sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
-    }
-    else {
-      $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
-      _user_mail_notify($op, $account);
-      if ($notify) {
-        drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user <a href="@url">%name</a>.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name)));
-      }
-      else {
-        drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.'));
-        $form_state['redirect'] = '';
-      }
-    }
-  }
-  // Administrator approval required.
-  else {
-    _user_mail_notify('register_pending_approval', $account);
-    drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.<br />In the meantime, a welcome message with further instructions has been sent to your e-mail address.'));
-    $form_state['redirect'] = '';
-  }
-}
-
-/**
  * Implements hook_modules_installed().
  */
 function user_modules_installed($modules) {
