 core/includes/entity.inc                           |   2 +-
 .../Core/Config/Entity/ConfigStorageController.php |  13 ++-
 .../Core/Entity/DatabaseStorageController.php      |  10 +-
 .../Core/Entity/DatabaseStorageControllerNG.php    |  11 +-
 core/lib/Drupal/Core/Entity/EntityManager.php      |  39 +++----
 .../Core/Plugin/Core/Entity/EntityViewMode.php     | 107 ++++++++++++++++++
 core/modules/book/book.module                      |  11 --
 core/modules/book/config/view_mode.node.print.yml  |   5 +
 .../comment/config/view_mode.comment.full.yml      |   5 +
 .../Drupal/comment/Plugin/Core/Entity/Comment.php  |   6 --
 core/modules/entity_ui/css/entity_ui.admin.css     |  15 +++
 core/modules/entity_ui/entity_ui.admin.inc         |  66 ++++++++++++
 core/modules/entity_ui/entity_ui.info              |   6 ++
 core/modules/entity_ui/entity_ui.module            | 120 +++++++++++++++++++++
 .../lib/Drupal/entity_ui/Tests/ViewModeTest.php    |  78 ++++++++++++++
 .../Drupal/entity_ui/ViewModeFormController.php    |  99 +++++++++++++++++
 .../Drupal/entity_ui/ViewModeListController.php    |  86 +++++++++++++++
 core/modules/field_ui/field_ui.module              |   3 +
 core/modules/file/config/view_mode.file.full.yml   |   5 +
 .../lib/Drupal/file/Plugin/Core/Entity/File.php    |   6 --
 core/modules/node/config/view_mode.node.full.yml   |   5 +
 core/modules/node/config/view_mode.node.rss.yml    |   5 +
 core/modules/node/config/view_mode.node.teaser.yml |   5 +
 .../lib/Drupal/node/Plugin/Core/Entity/Node.php    |  14 ---
 core/modules/node/node.module                      |  13 ---
 .../search/config/view_mode.node.search_index.yml  |   5 +
 .../search/config/view_mode.node.search_result.yml |   5 +
 .../Upgrade/FilledStandardUpgradePathTest.php      |   8 ++
 core/modules/system/system.install                 |  45 ++++++++
 .../config/view_mode.taxonomy_term.full.yml        |   5 +
 .../Drupal/taxonomy/Plugin/Core/Entity/Term.php    |   6 --
 .../taxonomy/Plugin/Core/Entity/Vocabulary.php     |   6 --
 core/modules/user/config/view_mode.user.full.yml   |   5 +
 .../lib/Drupal/user/Plugin/Core/Entity/User.php    |   6 --
 core/profiles/standard/standard.info               |   1 +
 35 files changed, 721 insertions(+), 106 deletions(-)

diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index 03c3ffa..1983f53 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -260,7 +260,7 @@ function entity_get_controller($entity_type) {
   if (!isset($controllers[$entity_type])) {
     $type_info = entity_get_info($entity_type);
     $class = $type_info['controller_class'];
-    $controllers[$entity_type] = new $class($entity_type);
+    $controllers[$entity_type] = new $class($entity_type, $type_info);
   }
   return $controllers[$entity_type];
 }
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
index 60c6aa1..6c4f668 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
@@ -57,13 +57,18 @@ class ConfigStorageController implements EntityStorageControllerInterface {
   protected $uuidKey = 'uuid';
 
   /**
-   * Implements Drupal\Core\Entity\EntityStorageControllerInterface::__construct().
+   * Constructs a ConfigStorageController object.
    *
    * Sets basic variables.
+   *
+   * @param string $entity_type
+   *   The entity type for which the instance is created.
+   * @param array $entity_info
+   *   The entity info of the entity type above.
    */
-  public function __construct($entityType) {
-    $this->entityType = $entityType;
-    $this->entityInfo = entity_get_info($entityType);
+  public function __construct($entity_type, $entity_info) {
+    $this->entityType = $entity_type;
+    $this->entityInfo = $entity_info;
     $this->hookLoadArguments = array();
     $this->idKey = $this->entityInfo['entity_keys']['id'];
   }
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index 14fca02..b98d0ac 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -109,12 +109,14 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
   /**
    * Constructs a DatabaseStorageController object.
    *
-   * @param string $entityType
+   * @param string $entity_type
    *   The entity type for which the instance is created.
+   * @param array $entity_info
+   *   The entity info of the entity type above.
    */
-  public function __construct($entityType) {
-    $this->entityType = $entityType;
-    $this->entityInfo = entity_get_info($entityType);
+  public function __construct($entity_type, $entity_info) {
+    $this->entityType = $entity_type;
+    $this->entityInfo = $entity_info;
     $this->entityCache = array();
     $this->hookLoadArguments = array();
     $this->idKey = $this->entityInfo['entity_keys']['id'];
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
index 6f22d83..8b3eb8e 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -37,10 +37,15 @@ class DatabaseStorageControllerNG extends DatabaseStorageController {
   protected $bundleKey;
 
   /**
-   * Overrides DatabaseStorageController::__construct().
+   * Constructs a DatabaseStorageControllerNG object.
+   *
+   * @param string $entity_type
+   *   The entity type for which the instance is created.
+   * @param array $entity_info
+   *   The entity info of the entity type above.
    */
-  public function __construct($entityType) {
-    parent::__construct($entityType);
+  public function __construct($entity_type, $entity_info) {
+    parent::__construct($entity_type, $entity_info);
     $this->bundleKey = !empty($this->entityInfo['entity_keys']['bundle']) ? $this->entityInfo['entity_keys']['bundle'] : FALSE;
     $this->entityClass = $this->entityInfo['class'];
 
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index b8c38ce..7a867b8 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -119,27 +119,6 @@
  *     - access callback: As in hook_menu(). 'user_access' will be assumed if
  *       no value is provided.
  *     - access arguments: As in hook_menu().
- * - view_modes: An array describing the view modes for the entity type. View
- *   modes let entities be displayed differently depending on the context.
- *   For instance, a node can be displayed differently on its own page
- *   ('full' mode), on the home page or taxonomy listings ('teaser' mode), or
- *   in an RSS feed ('rss' mode). Modules taking part in the display of the
- *   entity (notably the Field API) can adjust their behavior depending on
- *   the requested view mode. An additional 'default' view mode is available
- *   for all entity types. This view mode is not intended for actual entity
- *   display, but holds default display settings. For each available view
- *   mode, administrators can configure whether it should use its own set of
- *   field display settings, or just replicate the settings of the 'default'
- *   view mode, thus reducing the amount of display configurations to keep
- *   track of. Keys of the array are view mode names. Each view mode is
- *   described by an array with the following key/value pairs:
- *   - label: The human-readable name of the view mode.
- *   - custom_settings: A boolean specifying whether the view mode should by
- *     default use its own custom field display settings. If FALSE, entities
- *     displayed in this view mode will reuse the 'default' display settings
- *     by default (e.g. right after the module exposing the view mode is
- *     enabled), but administrators can later use the Field UI to apply custom
- *     display settings specific to the view mode.
  *
  * The defaults for the plugin definition are provided in
  * \Drupal\Core\Entity\EntityManager::defaults.
@@ -250,15 +229,22 @@ protected function processDefinition(&$definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
 
     // @todo Remove this check once http://drupal.org/node/1780396 is resolved.
-    if (!module_exists($definition['module'])) {
+    if ($definition['module'] != 'Core' && !module_exists($definition['module'])) {
       $definition = NULL;
       return;
     }
 
-    foreach ($definition['view_modes'] as $view_mode => $view_mode_info) {
-      $definition['view_modes'][$view_mode] += array(
-        'custom_settings' => FALSE,
-      );
+    // Add view modes.
+    // We cannot use entity_load_multiple() as that leads to recursion.
+    $view_modes = drupal_container()->get('config.storage')->listAll('view_mode.');
+    foreach ($view_modes as $config_name) {
+      $view_mode = config($config_name)->get();
+      if ($view_mode['type'] == $plugin_id) {
+        $definition['view_modes'][$view_mode['machine_name']] = array(
+          'label' => check_plain($view_mode['name']),
+          'custom_settings' => $view_mode['custom'],
+        );
+      }
     }
 
     // If no bundle key is provided, assume a single bundle, named after
@@ -266,6 +252,7 @@ protected function processDefinition(&$definition, $plugin_id) {
     if (empty($definition['entity_keys']['bundle']) && empty($definition['bundles'])) {
       $definition['bundles'] = array($plugin_id => array('label' => $definition['label']));
     }
+
     // Prepare entity schema fields SQL info for
     // Drupal\Core\Entity\DatabaseStorageControllerInterface::buildQuery().
     if (isset($definition['base_table'])) {
diff --git a/core/lib/Drupal/Core/Plugin/Core/Entity/EntityViewMode.php b/core/lib/Drupal/Core/Plugin/Core/Entity/EntityViewMode.php
new file mode 100644
index 0000000..9026693
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Core/Entity/EntityViewMode.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Plugin\Core\Entity\EntityViewMode.
+ */
+
+namespace Drupal\Core\Plugin\Core\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines the view mode configuration entity class.
+ *
+ * @Plugin(
+ *   id = "view_mode",
+ *   label = @Translation("View mode"),
+ *   module = "Core",
+ *   entity_class = "Drupal\Core\Entity\ViewMode\EntityViewMode",
+ *   controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
+ *   config_prefix = "view_mode",
+ *   fieldable = FALSE,
+ *   entity_keys = {
+ *     "id" = "machine_name",
+ *     "label" = "name",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class EntityViewMode extends ConfigEntityBase {
+
+  /**
+   * The machine name of the view mode.
+   *
+   * @var string
+   */
+  public $machine_name;
+
+  /**
+   * The UUID of the view mode.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The view mode label.
+   *
+   * @var string
+   */
+  public $name;
+
+  /**
+   * The entity type this view mode is used for.
+   *
+   * @var string
+   */
+  public $type;
+
+  /**
+   * Whether or not this view mode has custom settings in the Field UI.
+   *
+   * @var bool
+   */
+  public $custom = FALSE;
+
+  /**
+   * Whether or not this view mode can be removed.
+   *
+   * @var bool
+   */
+  public $locked = FALSE;
+
+  /**
+   * Overrides Drupal\Core\Core\Entity\Entity::id().
+   */
+  public function id() {
+    return $this->machine_name;
+  }
+
+  /**
+   * Returns the raw view mode name, exluding the entity type.
+   *
+   * @return string
+   *   The raw view mode name.
+   */
+  public function name() {
+    $name = strtok($this->machine_name, '.');
+    return strtok('.');
+  }
+
+  /**
+   * Overrides Drupal\Core\Core\Entity\Entity::uri().
+   */
+  public function uri() {
+    return array(
+      'path' => 'admin/config/system/view-modes/' . $this->id(),
+      'options' => array(
+        'entity_type' => $this->entityType,
+        'entity' => $this,
+      ),
+    );
+  }
+
+}
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 0187bda..c3a4a73 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -249,17 +249,6 @@ function book_admin_paths() {
 }
 
 /**
- * Implements hook_entity_info_alter().
- */
-function book_entity_info_alter(&$info) {
-  // Add the 'Print' view mode for nodes.
-  $info['node']['view_modes']['print'] = array(
-    'label' => t('Print'),
-    'custom_settings' => FALSE,
-  );
-}
-
-/**
  * Implements hook_block_info().
  */
 function book_block_info() {
diff --git a/core/modules/book/config/view_mode.node.print.yml b/core/modules/book/config/view_mode.node.print.yml
new file mode 100644
index 0000000..1ed8183
--- /dev/null
+++ b/core/modules/book/config/view_mode.node.print.yml
@@ -0,0 +1,5 @@
+machine_name: node.print
+name: Print
+custom: '0'
+type: node
+locked: '1'
diff --git a/core/modules/comment/config/view_mode.comment.full.yml b/core/modules/comment/config/view_mode.comment.full.yml
new file mode 100644
index 0000000..43b6233
--- /dev/null
+++ b/core/modules/comment/config/view_mode.comment.full.yml
@@ -0,0 +1,5 @@
+machine_name: comment.full
+name: Full comment
+custom: '0'
+type: comment
+locked: '1'
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
index 2b00d45..c2dd9f2 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
@@ -33,12 +33,6 @@
  *     "bundle" = "node_type",
  *     "label" = "subject",
  *     "uuid" = "uuid"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "Full comment",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/entity_ui/css/entity_ui.admin.css b/core/modules/entity_ui/css/entity_ui.admin.css
new file mode 100644
index 0000000..53093a1
--- /dev/null
+++ b/core/modules/entity_ui/css/entity_ui.admin.css
@@ -0,0 +1,15 @@
+/**
+ * @file
+ * Stylesheet for the administration pages of the Entity UI module.
+ */
+
+.operations {
+  width: 100px;
+}
+
+table caption {
+  font-size: 110%;
+  font-weight: bold;
+  text-align: left;
+  padding-bottom: 0.5em;
+}
diff --git a/core/modules/entity_ui/entity_ui.admin.inc b/core/modules/entity_ui/entity_ui.admin.inc
new file mode 100644
index 0000000..af636cd
--- /dev/null
+++ b/core/modules/entity_ui/entity_ui.admin.inc
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Administrative functions for view modes.
+ */
+
+/**
+ * Menu callback: list all view modes.
+ */
+function entity_ui_view_mode_list() {
+  $build = array();
+  $weight = 0;
+  $controller = entity_list_controller('view_mode');
+  foreach (entity_get_info() as $type => $info) {
+    if ($type != 'view_mode' && !empty($info['fieldable'])) {
+      $build[$type] = $controller->set($type)->render();
+      // Content is usually the most important entity type in the system.
+      $build[$type]['#weight'] = ($type == 'node') ? -10 : $weight++;
+    }
+  }
+  $build['#attached']['css'][] = drupal_get_path('module', 'entity_ui') . '/css/entity_ui.admin.css';
+  return $build;
+}
+
+/**
+ * Menu callback: view mode edit form.
+ */
+function entity_ui_view_mode_add($type) {
+  $view_mode = entity_create('view_mode', array('type' => $type));
+  return entity_get_form($view_mode);
+}
+
+/**
+ * Menu callback: view mode delete confirm form.
+ */
+function entity_ui_view_mode_delete_form($form, $form_state, $view_mode) {
+  $form['#view_mode'] = $view_mode;
+
+  $form['name'] = array(
+    '#type' => 'value',
+    '#value' => $view_mode->id(),
+  );
+
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete the %view-mode view mode?', array(
+      '%view-mode' => $view_mode->label(),
+    )),
+    'admin/config/system/view-modes',
+    t('Deleting a view mode will cause any output still requesting to use that view mode to use the default display settings.'),
+    t('Delete'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Submit handler: delete a view mode.
+ */
+function entity_ui_view_mode_delete_form_submit($form, &$form_state) {
+  drupal_set_message(t('Deleted the %view-mode view mode.', array(
+    '%view-mode' => $form['#view_mode']->label(),
+  )));
+  $form['#view_mode']->delete();
+  $form_state['redirect'] = 'admin/config/system/view-modes';
+}
diff --git a/core/modules/entity_ui/entity_ui.info b/core/modules/entity_ui/entity_ui.info
new file mode 100644
index 0000000..9d2b59c
--- /dev/null
+++ b/core/modules/entity_ui/entity_ui.info
@@ -0,0 +1,6 @@
+name = Entity UI
+description = User interface for the Entity API.
+package = Core
+version = VERSION
+core = 8.x
+configure = admin/config/system/view-modes
diff --git a/core/modules/entity_ui/entity_ui.module b/core/modules/entity_ui/entity_ui.module
new file mode 100644
index 0000000..03bc1a6
--- /dev/null
+++ b/core/modules/entity_ui/entity_ui.module
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Provides a user interface for the Entity API.
+ */
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Implements hook_help().
+ */
+function entity_ui_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#entity_ui':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t("View modes allow you to display and manipulate pieces of content (entities) on your site depending on their context. You're probably familiar with the default Drupal view modes 'full content' and 'teaser', and configuring field visibility, formatters, and template files for each. Any number of custom view modes can be created for each entity type through the view mode administration page, allowing for a very flexible system of configurable contextual display.") . '</p>';
+      $output .= '<h3>' . t('Uses') . '</h3>';
+      $output .= '<p>' . t("@todo") . '</p>';
+      $output .= '<h3>' . t('Creating custom view modes') . '</h3>';
+      $output .= '<p>' . t("@todo") . '</p>';
+      $output .= '<h3>' . t("Managing an entity's display with view modes") . '</h3>';
+      $output .= '<p>' . t("@todo") . '</p>';
+      $output .= '<h3>' . t('Using view modes in template files') . '</h3>';
+      $output .= '<p>' . t("@todo") . '</p>';
+      return $output;
+
+    case 'admin/config/system/view-modes':
+      return '<p>' . t("View modes allow entities to be displayed and manipulated depending on their context. A common example is configuring different field visibility, formatters, and template files for a content type's teaser and full content view modes.") . '</p>';
+
+  }
+}
+
+/**
+ * Implements hook_permission().
+ */
+function entity_ui_permission() {
+  return array(
+    'administer view modes' => array(
+      'title' => t('Add, edit and delete view modes.'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function entity_ui_entity_info_alter(&$entity_info) {
+  // Add a list and form controller for the view mode entity type.
+  $entity_info['view_mode']['list_controller_class'] = 'Drupal\entity_ui\ViewModeListController';
+  $entity_info['view_mode']['form_controller_class'] = array(
+    'default' => 'Drupal\entity_ui\ViewModeFormController',
+  );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function entity_ui_menu() {
+  $items['admin/config/system/view-modes'] = array(
+    'title' => 'View modes',
+    'description' => 'Create and manage view modes for displaying content in different contexts.',
+    'page callback' => 'entity_ui_view_mode_list',
+    'access arguments' => array('administer view modes'),
+    'file' => 'entity_ui.admin.inc',
+  );
+  $items['admin/config/system/view-modes/add/%'] = array(
+    'title' => 'Add view mode',
+    'page callback' => 'entity_ui_view_mode_add',
+    'page arguments' => array(5),
+    'access arguments' => array('administer view modes'),
+    'file' => 'entity_ui.admin.inc',
+  );
+  $items['admin/config/system/view-modes/%view_mode/edit'] = array(
+    'title' => 'Edit view mode',
+    'page callback' => 'entity_get_form',
+    'page arguments' => array(4),
+    'access arguments' => array('administer view modes'),
+  );
+  $items['admin/config/system/view-modes/%view_mode/delete'] = array(
+    'title' => 'Delete view mode',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('entity_ui_view_mode_delete_form', 4),
+    'access arguments' => array('administer view modes'),
+    'file' => 'entity_ui.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Loads a view mode by machine name.
+ */
+function view_mode_load($machine_name) {
+  return entity_load('view_mode', $machine_name);
+}
+
+/**
+ * Checks for an existing view mode.
+ */
+function entity_ui_view_mode_exists($machine_name, $element) {
+  return entity_load('view_mode', $element['#entity_type'] . ".$machine_name");
+}
+
+/**
+ * Implements hook_view_mode_presave().
+ */
+function entity_ui_view_mode_presave(EntityInterface $entity) {
+  entity_info_cache_clear();
+  state()->set('menu_rebuild_needed', TRUE);
+}
+
+/**
+ * Implements hook_view_mode_delete().
+ */
+function entity_ui_view_mode_delete(EntityInterface $entity) {
+  entity_info_cache_clear();
+  state()->set('menu_rebuild_needed', TRUE);
+}
diff --git a/core/modules/entity_ui/lib/Drupal/entity_ui/Tests/ViewModeTest.php b/core/modules/entity_ui/lib/Drupal/entity_ui/Tests/ViewModeTest.php
new file mode 100644
index 0000000..b135277
--- /dev/null
+++ b/core/modules/entity_ui/lib/Drupal/entity_ui/Tests/ViewModeTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\entity_ui\Tests\ViewModeTest.
+ */
+
+namespace Drupal\entity_ui\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests managing of view modes.
+ */
+class ViewModeTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('field_ui', 'entity_ui');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'View modes',
+      'description' => 'Tests for managing view modes.',
+      'group' => 'Entity UI',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    $this->drupalLogin($this->drupalCreateUser(array('administer content types', 'administer users', 'administer view modes', 'access administration pages')));
+    $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
+  }
+
+  /**
+   * Tests managing view modes.
+   */
+  function testManageViewModes() {
+    $edit = array(
+      'name' => 'Testing',
+      'machine_name' => 'testing',
+    );
+    $this->drupalPost('admin/config/system/view-modes/add/node', $edit, t('Save'));
+    $this->assertText(format_string('Saved the !name view mode.', array('!name' => $edit['name'])));
+
+    // Create the same and assert it already exists.
+    $this->drupalPost('admin/config/system/view-modes/add/node', $edit, t('Save'));
+    $this->assertText('The machine-readable name is already in use. It must be unique.', 'View mode testing already exists.');
+
+    // Assert it's found on the Field UI for article.
+    $this->drupalGet('admin/structure/types/manage/article/display');
+    $this->assertRaw('view_modes_custom[node.testing]', 'Testing view mode found on node article.');
+
+    // Assert it's not found on the Field UI for user.
+    $this->drupalGet('admin/config/people/accounts/display');
+    $this->assertNoRaw('view_modes_custom[user.testing]', 'Testing view mode not found on user.');
+
+    // Update view mode label.
+    $edit = array(
+      'name' => 'Testing 2',
+    );
+    $this->drupalPost('admin/config/system/view-modes/node.testing/edit', $edit, t('Save'));
+    $this->assertText('Saved the Testing 2 view mode.', 'Testing label updated.');
+
+    // Remove a view mode.
+    $this->drupalPost('admin/config/system/view-modes/node.testing/delete', array(), t('Delete'));
+    $this->assertText('Deleted the Testing 2 view mode', 'Deleted the Testing view mode.');
+
+    // Assert the view mode is gone at the manage display screen.
+    $this->drupalGet('admin/structure/types/manage/article/display');
+    $this->assertNoRaw('view_modes_custom[testing]', 'Testing view mode not found on node article.');
+  }
+
+}
diff --git a/core/modules/entity_ui/lib/Drupal/entity_ui/ViewModeFormController.php b/core/modules/entity_ui/lib/Drupal/entity_ui/ViewModeFormController.php
new file mode 100644
index 0000000..5fd192c
--- /dev/null
+++ b/core/modules/entity_ui/lib/Drupal/entity_ui/ViewModeFormController.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\entity_ui\ViewModeFormController.
+ */
+
+namespace Drupal\entity_ui;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormController;
+
+/**
+ * Form controller for the view mode forms.
+ */
+class ViewModeFormController extends EntityFormController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $view_mode) {
+    $entity_info = entity_get_info($view_mode->type);
+    $form['type'] = array(
+      '#type' => 'item',
+      '#title' => t('Entity type'),
+      '#markup' => check_plain($entity_info['label']),
+    );
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Name'),
+      '#default_value' => $view_mode->label(),
+      '#required' => TRUE,
+    );
+
+    $form['machine_name'] = array(
+      '#type' => 'machine_name',
+      '#machine_name' => array(
+        'source' => array('name'),
+        'exists' => 'entity_ui_view_mode_exists',
+      ),
+      '#entity_type' => $view_mode->type,
+      '#default_value' => $view_mode->name(),
+      '#disabled' => !$view_mode->isNew(),
+      '#field_prefix' => "$view_mode->type.",
+    );
+
+    return $form;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::actions().
+   */
+  protected function actions(array $form, array &$form_state) {
+    $view_mode = $this->getEntity($form_state);
+    $actions = parent::actions($form, $form_state);
+    $actions['delete']['#access'] = !$view_mode->locked;
+    return $actions;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::validate().
+   */
+  public function validate(array $form, array &$form_state) {
+    $entity = clone $this->getEntity($form_state);
+    form_set_value($form['machine_name'], $entity->type . '.' . $form_state['values']['machine_name'], $form_state);
+    parent::validate($form, $form_state);
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::save().
+   *
+   * @todo Move this code to the base controller: http://drupal.org/node/1728786
+   */
+  public function save(array $form, array &$form_state) {
+    $view_mode = $this->getEntity($form_state);
+    $view_mode->save();
+    drupal_set_message(t('Saved the %view-mode view mode.', array(
+      '%view-mode' => $view_mode->label(),
+    )));
+
+    $form_state['redirect'] = 'admin/config/system/view-modes';
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::delete().
+   *
+   * @todo Move this code to the base controller: http://drupal.org/node/1728786
+   */
+  public function delete(array $form, array &$form_state) {
+    $destination = array();
+    if (isset($_GET['destination'])) {
+      $destination = drupal_get_destination();
+      unset($_GET['destination']);
+    }
+    $view_mode = $this->getEntity($form_state);
+    $form_state['redirect'] = array('admin/config/system/view-modes/' . $view_mode->id() . '/delete', array('query' => $destination));
+  }
+
+}
diff --git a/core/modules/entity_ui/lib/Drupal/entity_ui/ViewModeListController.php b/core/modules/entity_ui/lib/Drupal/entity_ui/ViewModeListController.php
new file mode 100644
index 0000000..f612d91
--- /dev/null
+++ b/core/modules/entity_ui/lib/Drupal/entity_ui/ViewModeListController.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\entity_ui\ViewModeListController.
+ */
+
+namespace Drupal\entity_ui;
+
+use Drupal\Core\Config\Entity\ConfigEntityListController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a listing of view modes.
+ */
+class ViewModeListController extends ConfigEntityListController {
+
+  protected $type;
+
+  public function set($type) {
+    $this->type = entity_get_info($type);
+    $this->type['type'] = $type;
+    return $this;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityListController::load().
+   */
+  public function load() {
+    $type = $this->type['type'];
+    return array_filter(parent::load(), function ($entity) use ($type) {
+      return $entity->type == $type;
+    });
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityListController::buildHeader();
+   */
+  public function buildHeader() {
+    $header['name'] = t('Name');
+    $header['operations'] = t('Operations');
+    return $header;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityListController::buildRow();
+   */
+  public function buildRow(EntityInterface $view_mode) {
+    $row['name'] = check_plain($view_mode->label());
+    $row['operations']['data'] = $this->buildOperations($view_mode);
+    $row['operations']['class'] = array('operations');
+    return $row;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityListController::getOperations();
+   */
+  public function getOperations(EntityInterface $view_mode) {
+    $operations = parent::getOperations($view_mode);
+    if ($view_mode->locked) {
+      unset($operations['delete']);
+    }
+    return $operations;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityListController::buildRow();
+   */
+  public function render() {
+    $build = parent::render();
+    $build['#rows']['_add_new'][] = array(
+      'data' => array(
+        '#type' => 'link',
+        '#href' => 'admin/config/system/view-modes/add/' . $this->type['type'],
+        '#title' => t('Add new %type view mode', array('%type' => $this->type['label'])),
+        '#options' => array(
+          'html' => TRUE,
+        ),
+      ),
+      'colspan' => count($build['#header']),
+    );
+    $build['#caption'] = check_plain($this->type['label']);
+    return $build;
+  }
+
+}
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index fd1bdc3..28fc056 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -1,9 +1,12 @@
 <?php
+
 /**
  * @file
  * Allows administrators to attach custom fields to fieldable types.
  */
 
+use Drupal\Core\Entity\EntityInterface;
+
 /**
  * Implements hook_help().
  */
diff --git a/core/modules/file/config/view_mode.file.full.yml b/core/modules/file/config/view_mode.file.full.yml
new file mode 100644
index 0000000..4746580
--- /dev/null
+++ b/core/modules/file/config/view_mode.file.full.yml
@@ -0,0 +1,5 @@
+machine_name: file.full
+name: File default
+custom: '0'
+type: file
+locked: '1'
diff --git a/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php b/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
index bc54514..d7f2a6b 100644
--- a/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
+++ b/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
@@ -26,12 +26,6 @@
  *     "id" = "fid",
  *     "label" = "filename",
  *     "uuid" = "uuid"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "File default",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/node/config/view_mode.node.full.yml b/core/modules/node/config/view_mode.node.full.yml
new file mode 100644
index 0000000..0e667c5
--- /dev/null
+++ b/core/modules/node/config/view_mode.node.full.yml
@@ -0,0 +1,5 @@
+machine_name: node.full
+name: Full content
+custom: '0'
+type: node
+locked: '1'
diff --git a/core/modules/node/config/view_mode.node.rss.yml b/core/modules/node/config/view_mode.node.rss.yml
new file mode 100644
index 0000000..7b36134
--- /dev/null
+++ b/core/modules/node/config/view_mode.node.rss.yml
@@ -0,0 +1,5 @@
+machine_name: node.rss
+name: RSS
+custom: '0'
+type: node
+locked: '1'
diff --git a/core/modules/node/config/view_mode.node.teaser.yml b/core/modules/node/config/view_mode.node.teaser.yml
new file mode 100644
index 0000000..63efd07
--- /dev/null
+++ b/core/modules/node/config/view_mode.node.teaser.yml
@@ -0,0 +1,5 @@
+machine_name: node.teaser
+name: Teaser
+custom: '1'
+type: node
+locked: '1'
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
index f161d81..c7ce4cc 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
@@ -37,20 +37,6 @@
  *   },
  *   bundle_keys = {
  *     "bundle" = "type"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "Full content",
- *       "custom_settings" = FALSE
- *     },
- *     "teaser" = {
- *       "label" = "Teaser",
- *       "custom_settings" = TRUE
- *     },
- *     "rss" = {
- *       "label" = "RSS",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index a997a04..8815c4a 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -199,19 +199,6 @@ function node_entity_info_alter(&$info) {
     $info['node']['translation']['node'] = TRUE;
   }
 
-  // Search integration is provided by node.module, so search-related
-  // view modes for nodes are defined here and not in search.module.
-  if (module_exists('search')) {
-    $info['node']['view_modes']['search_index'] = array(
-      'label' => t('Search index'),
-      'custom_settings' => FALSE,
-    );
-    $info['node']['view_modes']['search_result'] = array(
-      'label' => t('Search result'),
-      'custom_settings' => FALSE,
-    );
-  }
-
   // Bundles must provide a human readable name so we can create help and error
   // messages, and the path to attach Field admin pages to.
   node_type_cache_reset();
diff --git a/core/modules/search/config/view_mode.node.search_index.yml b/core/modules/search/config/view_mode.node.search_index.yml
new file mode 100644
index 0000000..0324aba
--- /dev/null
+++ b/core/modules/search/config/view_mode.node.search_index.yml
@@ -0,0 +1,5 @@
+machine_name: node.search_index
+name: Search index
+custom: '0'
+type: node
+locked: '1'
diff --git a/core/modules/search/config/view_mode.node.search_result.yml b/core/modules/search/config/view_mode.node.search_result.yml
new file mode 100644
index 0000000..da28216
--- /dev/null
+++ b/core/modules/search/config/view_mode.node.search_result.yml
@@ -0,0 +1,5 @@
+machine_name: node.search_result
+name: Search result
+custom: '0'
+type: node
+locked: '1'
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php
index e1df73e..4839a9c 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FilledStandardUpgradePathTest.php
@@ -96,5 +96,13 @@ public function testFilledStandardUpgrade() {
     $blog_type = node_type_load('blog');
     $this->assertEqual($blog_type->module, 'node', "Content type 'blog' has been reassigned from the blog module to the node module.");
     $this->assertEqual($blog_type->base, 'node_content', "The base string used to construct callbacks corresponding to content type 'Blog' has been reassigned to 'node_content'.");
+
+    // Verify all core view modes have been converted to config.
+    $entity_info = entity_get_info();
+    foreach ($entity_info as $type => $info) {
+      if (!empty($info['fieldable'])) {
+        $this->assertTrue(!empty($info['view modes']), format_string('View modes for @entity_type have been converted', array('@entity_type' => $info['label'])));
+      }
+    }
   }
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 4bced68..8256dcc 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -2202,6 +2202,51 @@ function system_update_8032() {
 }
 
 /**
+ * Moves entity view modes to config.
+ *
+ * @ingroup config_upgrade
+ */
+function system_update_8033() {
+  // We can't call entity_info() in an update hook, so we hardcode them.
+  // We only care about the core view modes as hook_entity_info_alter() will
+  // still work since that was the only way to add custom view modes in D7.
+  $entity_view_modes = array(
+    'node' => array(
+      'full' => 'Full content',
+      'teaser' => 'Teaser',
+      'rss' => 'RSS',
+      'search_index' => 'Search index',
+      'search_result' => 'Search result',
+      'print' => 'Print',
+    ),
+    'file' => array(
+      'full' => 'File default',
+    ),
+    'comment' => array(
+      'full' => 'Full comment',
+    ),
+    'user' => array(
+      'full' => 'User account',
+    ),
+    'taxonomy_term' => array(
+      'full' => 'Taxonomy term page',
+    ),
+  );
+
+  foreach ($entity_view_modes as $entity_type => $view_modes) {
+    foreach ($view_modes as $key => $name) {
+      config('view_mode.' . $entity_type . '.' . $key)
+        ->set('machine_name', $entity_type . '.' . $key)
+        ->set('name', $name)
+        ->set('type', $entity_type)
+        ->set('locked', TRUE)
+        ->set('custom', FALSE)
+        ->save();
+    }
+  }
+}
+
+/**
  * @} End of "defgroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/taxonomy/config/view_mode.taxonomy_term.full.yml b/core/modules/taxonomy/config/view_mode.taxonomy_term.full.yml
new file mode 100644
index 0000000..99dca2d
--- /dev/null
+++ b/core/modules/taxonomy/config/view_mode.taxonomy_term.full.yml
@@ -0,0 +1,5 @@
+machine_name: taxonomy_term.full
+name: Taxonomy term page
+custom: '0'
+type: taxonomy_term
+locked: '1'
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
index 8aedeee..362617a 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
@@ -35,12 +35,6 @@
  *   },
  *   bundle_keys = {
  *     "bundle" = "machine_name"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "Taxonomy term page",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
index 0ecbae8..beee8d1 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
@@ -26,12 +26,6 @@
  *   entity_keys = {
  *     "id" = "vid",
  *     "label" = "name"
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "Taxonomy vocabulary",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/modules/user/config/view_mode.user.full.yml b/core/modules/user/config/view_mode.user.full.yml
new file mode 100644
index 0000000..283935e
--- /dev/null
+++ b/core/modules/user/config/view_mode.user.full.yml
@@ -0,0 +1,5 @@
+machine_name: user.full
+name: User account
+custom: '0'
+type: user
+locked: '1'
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
index 68edca1..dd22246 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
@@ -40,12 +40,6 @@
  *         "access arguments" = {"administer users"}
  *       }
  *     }
- *   },
- *   view_modes = {
- *     "full" = {
- *       "label" = "User account",
- *       "custom_settings" = FALSE
- *     }
  *   }
  * )
  */
diff --git a/core/profiles/standard/standard.info b/core/profiles/standard/standard.info
index 18136bc..04776ef 100644
--- a/core/profiles/standard/standard.info
+++ b/core/profiles/standard/standard.info
@@ -24,3 +24,4 @@ dependencies[] = file
 dependencies[] = rdf
 dependencies[] = views
 dependencies[] = views_ui
+dependencies[] = entity_ui
