commit 321a2529b73eeb406a1c041b52199ae5086b2c65
Author: Bart Feenstra <bart@mynameisbart.com>
Date:   Mon Nov 25 13:03:04 2013 +0100

    Grrrrreeeeeeeeen

diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
index b363cc4..2cfb551 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
@@ -27,40 +27,4 @@ public function load() {
     return $entities;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    $uri = $entity->uri();
-
-    // Ensure the edit operation exists since it is access controlled.
-    if (isset($operations['edit'])) {
-      // For configuration entities edit path is the MENU_DEFAULT_LOCAL_TASK and
-      // therefore should be accessed by the short route.
-      $operations['edit']['href'] = $uri['path'];
-    }
-
-    if (isset($this->entityInfo['entity_keys']['status'])) {
-      if (!$entity->status()) {
-        $operations['enable'] = array(
-          'title' => t('Enable'),
-          'href' => $uri['path'] . '/enable',
-          'options' => $uri['options'],
-          'weight' => -10,
-        );
-      }
-      else {
-        $operations['disable'] = array(
-          'title' => t('Disable'),
-          'href' => $uri['path'] . '/disable',
-          'options' => $uri['options'],
-          'weight' => 40,
-        );
-      }
-    }
-
-    return $operations;
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityOperationsProvider.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityOperationsProvider.php
new file mode 100644
index 0000000..8056161
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityOperationsProvider.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Config\Entity\ConfigEntityOperationsProvider.
+ */
+
+namespace Drupal\Core\Config\Entity;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProvider;
+
+/**
+ * Defines a default config entity operations provider.
+ */
+class ConfigEntityOperationsProvider extends EntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $uri = $entity->uri();
+    $operations = parent::getDefaultOperations($entity);
+
+    // Configuration entities' edit pages are located at the entities' canonical
+    // URLs.
+    $operations['update']['href'] = $uri['path'];
+
+    $info = $entity->entityInfo();
+    if (isset($info['entity_keys']['status'])) {
+      if ($entity->status()) {
+        $operations['disable'] = array(
+          'title' => $this->t('Disable'),
+          'href' => $uri['path'] . '/disable',
+          'options' => $uri['options'],
+          'weight' => 40,
+        );
+      }
+      else {
+        $operations['enable'] = array(
+          'title' => $this->t('Enable'),
+          'href' => $uri['path'] . '/enable',
+          'options' => $uri['options'],
+          'weight' => -10,
+        );
+      }
+    }
+
+    return $operations;
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/DraggableListController.php b/core/lib/Drupal/Core/Config/Entity/DraggableListController.php
index 5370be5..9c4c5bd 100644
--- a/core/lib/Drupal/Core/Config/Entity/DraggableListController.php
+++ b/core/lib/Drupal/Core/Config/Entity/DraggableListController.php
@@ -8,9 +8,11 @@
 namespace Drupal\Core\Config\Entity;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Provides a list controller for draggable configuration entities.
@@ -41,8 +43,8 @@
   /**
    * {@inheritdoc}
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
 
     // Check if the entity type supports weighting.
     if (!empty($this->entityInfo['entity_keys']['weight'])) {
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
index 3dbbb39..7cc46cb 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
@@ -57,6 +57,10 @@ class EntityType extends Plugin {
    * - translation: The name of the controller class that should be used to
    *   handle the translation process. The class must implement
    *   \Drupal\content_translation\ContentTranslationControllerInterface.
+   * - operations: The name of the controller class that provides entity
+   *   operations, for which access checks must be run through the "access"
+   *   controller. The class must implement
+   *   \Drupal\Core\Entity\EntityOperationsProviderInterface.
    *
    * @todo Interfaces from outside \Drupal\Core or \Drupal\Component should not
    *   be used here.
diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php
index 0a722a0..18f9e5b 100644
--- a/core/lib/Drupal/Core/Entity/EntityListController.php
+++ b/core/lib/Drupal/Core/Entity/EntityListController.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Component\Utility\String;
@@ -18,6 +19,13 @@
 class EntityListController implements EntityListControllerInterface, EntityControllerInterface {
 
   /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
    * The entity storage controller class.
    *
    * @var \Drupal\Core\Entity\EntityStorageControllerInterface
@@ -25,6 +33,13 @@ class EntityListController implements EntityListControllerInterface, EntityContr
   protected $storage;
 
   /**
+   * The entity operations provider.
+   *
+   * @var \Drupal\Core\Entity\EntityOperationsProviderInterface
+   */
+  protected $operations;
+
+  /**
    * The module handler to invoke hooks on.
    *
    * @var \Drupal\Core\Extension\ModuleHandlerInterface
@@ -62,7 +77,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
-      $container->get('module_handler')
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
+      $container->get('module_handler'),
+      $container->get('current_user')
     );
   }
 
@@ -75,14 +92,20 @@ public static function createInstance(ContainerInterface $container, $entity_typ
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
     $this->entityType = $entity_type;
     $this->storage = $storage;
+    $this->operations = $operations;
     $this->entityInfo = $entity_info;
     $this->moduleHandler = $module_handler;
+    $this->currentUser = $current_user;
   }
 
   /**
@@ -113,33 +136,6 @@ protected function getLabel(EntityInterface $entity) {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public function getOperations(EntityInterface $entity) {
-    $uri = $entity->uri();
-
-    $operations = array();
-    if ($entity->access('update')) {
-      $operations['edit'] = array(
-        'title' => $this->t('Edit'),
-        'href' => $uri['path'] . '/edit',
-        'options' => $uri['options'],
-        'weight' => 10,
-      );
-    }
-    if ($entity->access('delete')) {
-      $operations['delete'] = array(
-        'title' => $this->t('Delete'),
-        'href' => $uri['path'] . '/delete',
-        'options' => $uri['options'],
-        'weight' => 100,
-      );
-    }
-
-    return $operations;
-  }
-
-  /**
    * Builds the header row for the entity listing.
    *
    * @return array
@@ -164,31 +160,12 @@ public function buildHeader() {
    * @see \Drupal\Core\Entity\EntityListController::render()
    */
   public function buildRow(EntityInterface $entity) {
-    $row['operations']['data'] = $this->buildOperations($entity);
-    return $row;
-  }
-
-  /**
-   * Builds a renderable list of operation links for the entity.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity on which the linked operations will be performed.
-   *
-   * @return array
-   *   A renderable array of operation links.
-   *
-   * @see \Drupal\Core\Entity\EntityListController::render()
-   */
-  public function buildOperations(EntityInterface $entity) {
-    // Retrieve and sort operations.
-    $operations = $this->getOperations($entity);
-    $this->moduleHandler->alter('entity_operation', $operations, $entity);
-    uasort($operations, 'drupal_sort_weight');
-    $build = array(
+    $row['operations']['data'] = array(
       '#type' => 'operations',
-      '#links' => $operations,
+      '#links' => $this->operations->getOperations($entity, $this->currentUser),
     );
-    return $build;
+
+    return $row;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
index b7581d8..a56d959 100644
--- a/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
@@ -32,22 +32,6 @@ public function getStorageController();
   public function load();
 
   /**
-   * Provides an array of information to build a list of operation links.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity the operations are for.
-   *
-   * @return array
-   *   An associative array of operation link data for this list, keyed by
-   *   operation name, containing the following key-value pairs:
-   *   - title: The localized title of the operation.
-   *   - href: The path for the operation.
-   *   - options: An array of URL options for the path.
-   *   - weight: The weight of this operation.
-   */
-  public function getOperations(EntityInterface $entity);
-
-  /**
    * Renders the list page markup to be output.
    *
    * @return string
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 221d4ee..3a1b062 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -288,6 +288,13 @@ protected function getController($entity_type, $controller_type) {
   /**
    * {@inheritdoc}
    */
+  public function getOperationsProvider($entity_type) {
+    return $this->getController($entity_type, 'operations');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getForm(EntityInterface $entity, $operation = 'default', array $form_state = array()) {
     $form_state += entity_form_state_defaults($entity, $operation);
     $form_id = $form_state['build_info']['callback_object']->getFormId();
diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
index e89966a..9815e52 100644
--- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
@@ -272,4 +272,14 @@ public function getBundleInfo($entity_type);
    */
   public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array());
 
+  /**
+   * Gets an entity type's operations provider.
+   *
+   * @param string $entity_type
+   *   The entity type for this operations provider.
+   *
+   * @return \Drupal\Core\Entity\EntityOperationsProviderInterface.
+   */
+  public function getOperationsProvider($entity_type);
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityOperationsProvider.php b/core/lib/Drupal/Core/Entity/EntityOperationsProvider.php
new file mode 100644
index 0000000..399316d
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityOperationsProvider.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityOperationsProvider.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\TranslationManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a default entity operations provider.
+ */
+class EntityOperationsProvider implements EntityOperationsProviderInterface, EntityControllerInterface {
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationManager
+   */
+  protected $translationManager;
+
+  /**
+   * Constructs an EntityOperationsProvider object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   * @param \Drupal\Core\StringTranslation\TranslationManager $translation_manager
+   * @param \Drupal\Core\Entity\EntityAccessControllerInterface $access_controller
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, TranslationManager $translation_manager) {
+    $this->moduleHandler = $module_handler;
+    $this->translationManager = $translation_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+    return new static($container->get('module_handler'), $container->get('string_translation'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperations(EntityInterface $entity, AccountInterface $account) {
+    // Operations are retrieved from specific to non-specific contexts (the
+    // controller, the entity-type-specific hook, and the generic hook).
+    $operations = $this->getDefaultOperations($entity);
+    $hooks = array($entity->entityType() . '_operations' , 'entity_operations');
+    foreach ($hooks as $hook) {
+      $operations = array_merge($this->moduleHandler->invokeAll($hook, array($entity)), $operations);
+    }
+    $this->moduleHandler->alter(array('entity_operations', $entity->entityType() . '_operations'), $entity, $operations);
+
+    // Check access to the operations.
+    foreach ($operations as $operation => $operation_link) {
+      if (!$entity->access($operation, $account)) {
+        unset($operations[$operation]);
+      }
+    }
+
+    uasort($operations, array($this, 'sort'));
+
+    return $operations;
+  }
+
+  /**
+   * Sorts operations.
+   *
+   * @param array $operation_a
+   *   The structure is identical to that of an item of the return value of
+   *   self::getOperations().
+   * @param array $operation_b
+   *   The structure is identical to that of an item of the return value of
+   *   self::getOperations().
+   */
+  protected function sort(array $operation_a, array $operation_b) {
+    if ($operation_a['weight'] == $operation_b['weight']) {
+      return strnatcasecmp($operation_a['title'], $operation_b['title']);
+    }
+    else {
+      return $operation_a['weight'] > $operation_b['weight'] ? 1 : -1;
+    }
+  }
+
+  /**
+   * Gets the entity's default operations.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *
+   * @return array
+   *   The structure is identical to that of the return value of
+   *   self::getOperations().
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $uri = $entity->uri();
+    $operations = array();
+    $operations['update'] = array(
+      'title' => $this->t('Edit'),
+      'href' => $uri['path'] . '/edit',
+      'options' => $uri['options'],
+      'weight' => 10,
+    );
+    $operations['delete'] = array(
+      'title' => $this->t('Delete'),
+      'href' => $uri['path'] . '/delete',
+      'options' => $uri['options'],
+      'weight' => 100,
+    );
+
+    return $operations;
+  }
+
+  /**
+   * Translates a string to the current language or to a given language.
+   *
+   * This is a wrapper so POTX can use it to extract translatable strings.
+   */
+  protected function t($string, array $args = array(), array $options = array()) {
+    return $this->translationManager->translate($string, $args, $options);
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityOperationsProviderInterface.php b/core/lib/Drupal/Core/Entity/EntityOperationsProviderInterface.php
new file mode 100644
index 0000000..987ad9b
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityOperationsProviderInterface.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityOperationsProviderInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines an interface to retrieve all operations of an entity.
+ *
+ * operations providers must return all known operations. Access control must
+ * be done through entities' "access" controllers.
+ */
+interface EntityOperationsProviderInterface {
+
+  /**
+   * Gets all operations for an entity that are accessible by a user account.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *
+   * @return array
+   *   Keys are operation machine names and values are arrays with the following
+   *   keys:
+   *   - title: The link text.
+   *   - href: (optional) The link URL. If omitted, the 'title' is shown as a plain text
+   *     item in the links list.
+   *   - html: (optional) Whether or not 'title' is HTML. If set, the title
+   *     will not be passed through
+   *     \Drupal\Component\Utility\String::checkPlain().
+   *   - attributes: (optional) Attributes for the anchor, or for the <span>
+   *     tag used in its place if no 'href' is supplied. If element 'class' is
+   *     included, it must be an array of one or more class names.
+   */
+  public function getOperations(EntityInterface $entity, AccountInterface $account);
+
+}
diff --git a/core/modules/action/lib/Drupal/action/ActionListController.php b/core/modules/action/lib/Drupal/action/ActionListController.php
index 405f224..dd999e2 100644
--- a/core/modules/action/lib/Drupal/action/ActionListController.php
+++ b/core/modules/action/lib/Drupal/action/ActionListController.php
@@ -11,8 +11,10 @@
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Config\Entity\ConfigEntityListController;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -41,13 +43,17 @@ class ActionListController extends ConfigEntityListController implements EntityC
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The action storage controller.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Action\ActionManager $action_manager
    *   The action plugin manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ActionManager $action_manager, ModuleHandlerInterface $module_handler) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ActionManager $action_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
 
     $this->actionManager = $action_manager;
   }
@@ -60,8 +66,10 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $container->get('plugin.manager.action'),
-      $container->get('module_handler')
+      $container->get('module_handler'),
+      $container->get('current_user')
     );
   }
 
@@ -105,17 +113,6 @@ public function buildHeader() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = $entity->isConfigurable() ? parent::getOperations($entity) : array();
-    if (isset($operations['edit'])) {
-      $operations['edit']['title'] = t('Configure');
-    }
-    return $operations;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function render() {
     $build['action_header']['#markup'] = '<h3>' . t('Available actions:') . '</h3>';
     $build['action_table'] = parent::render();
diff --git a/core/modules/action/lib/Drupal/action/ActionOperationsProvider.php b/core/modules/action/lib/Drupal/action/ActionOperationsProvider.php
new file mode 100644
index 0000000..2afc6f7
--- /dev/null
+++ b/core/modules/action/lib/Drupal/action/ActionOperationsProvider.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\action\ActionOperationsProvider.
+ */
+
+namespace Drupal\action;
+
+use Drupal\Core\Config\Entity\ConfigEntityOperationsProvider;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines a entity operations provider for action entities.
+ */
+class ActionOperationsProvider extends ConfigEntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    if (!$entity->isConfigurable()) {
+      return array();
+    }
+
+    $operations = parent::getDefaultOperations($entity);
+    $operations['update']['title'] = t('Configure');
+    return $operations;
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockListController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockListController.php
index efa31b3..012e2b8 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockListController.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockListController.php
@@ -31,18 +31,4 @@ public function buildRow(EntityInterface $entity) {
     return $row + parent::buildRow($entity);
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    // The custom block edit path does not contain '/edit'.
-    if (isset($operations['edit'])) {
-      $uri = $entity->uri();
-      $operations['edit']['href'] = $uri['path'];
-      $operations['edit']['query']['destination'] = 'admin/structure/block/custom-blocks';
-    }
-    return $operations;
-  }
-
 }
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockOperationsProvider.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockOperationsProvider.php
new file mode 100644
index 0000000..be87ca1
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockOperationsProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockOperationsProvider.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProvider;
+
+/**
+ * Provides an operations provider for custom_block entities.
+ */
+class CustomBlockOperationsProvider extends EntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    // The custom block edit path does not contain '/edit'.
+    $uri = $entity->uri();
+    $operations['update']['href'] = $uri['path'];
+    $operations['update']['query']['destination'] = 'admin/structure/block/custom-blocks';
+
+    return $operations;
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
index efa4380..004b1af 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
@@ -16,19 +16,6 @@
 class CustomBlockTypeListController extends ConfigEntityListController {
 
   /**
-   * {@inheritdoc}
-   */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    // Place the edit operation after the operations added by field_ui.module
-    // which have the weights 15, 20, 25.
-    if (isset($operations['edit'])) {
-      $operations['edit']['weight'] = 30;
-    }
-    return $operations;
-  }
-
-  /**
    * Overrides \Drupal\Core\Entity\EntityListController::buildHeader().
    */
   public function buildHeader() {
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeOperationsProvider.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeOperationsProvider.php
new file mode 100644
index 0000000..9f3ae9d
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeOperationsProvider.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockTypeOperationsProvider.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProvider;
+
+/**
+ * Defines a entity operations provider for custom_block entities.
+ */
+class CustomBlockTypeOperationsProvider extends EntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    // Place the edit operation after the operations added by field_ui.module
+    // which have the weights 15, 20, 25.
+    $operations['update']['weight'] = 30;
+
+    return $operations;
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php
index 055bfea..90ef3f3 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php
@@ -21,6 +21,7 @@
  *   label = @Translation("Custom Block"),
  *   bundle_label = @Translation("Custom Block type"),
  *   controllers = {
+ *     "operations" = "Drupal\custom_block\CustomBlockOperationsProvider",
  *     "storage" = "Drupal\custom_block\CustomBlockStorageController",
  *     "access" = "Drupal\custom_block\CustomBlockAccessController",
  *     "list" = "Drupal\custom_block\CustomBlockListController",
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlockType.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlockType.php
index 345c210..d152ae6 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlockType.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlockType.php
@@ -20,6 +20,7 @@
  *   id = "custom_block_type",
  *   label = @Translation("Custom block type"),
  *   controllers = {
+ *     "operations" = "Drupal\Core\Config\Entity\ConfigEntityOperationsProvider",
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *     "form" = {
  *       "default" = "Drupal\custom_block\CustomBlockTypeFormController",
diff --git a/core/modules/block/lib/Drupal/block/BlockListController.php b/core/modules/block/lib/Drupal/block/BlockListController.php
index 9524b0d..5a0105e 100644
--- a/core/modules/block/lib/Drupal/block/BlockListController.php
+++ b/core/modules/block/lib/Drupal/block/BlockListController.php
@@ -13,9 +13,11 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -60,14 +62,18 @@ class BlockListController extends ConfigEntityListController implements FormInte
    * @param array $entity_info
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param \Drupal\Component\Plugin\PluginManagerInterface $block_manager
    *   The block manager.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, PluginManagerInterface $block_manager) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user, PluginManagerInterface $block_manager) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
 
     $this->blockManager = $block_manager;
   }
@@ -80,7 +86,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $container->get('plugin.manager.block')
     );
   }
@@ -292,7 +300,10 @@ public function buildForm(array $form, array &$form_state) {
               'class' => array('block-weight', 'block-weight-' . $region),
             ),
           );
-          $form['blocks'][$entity_id]['operations'] = $this->buildOperations($info['entity']);
+          $form['blocks'][$entity_id]['operations']['data'] = array(
+            '#type' => 'operations',
+            '#links' => $this->operations->getOperations($info['entity'], $this->currentUser),
+          );
         }
       }
     }
@@ -380,19 +391,6 @@ public function buildForm(array $form, array &$form_state) {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-
-    if (isset($operations['edit'])) {
-      $operations['edit']['title'] = t('Configure');
-    }
-
-    return $operations;
-  }
-
-  /**
    * Implements \Drupal\Core\Form\FormInterface::validateForm().
    */
   public function validateForm(array &$form, array &$form_state) {
diff --git a/core/modules/block/lib/Drupal/block/Entity/Block.php b/core/modules/block/lib/Drupal/block/Entity/Block.php
index 44fa169..3e01f8c 100644
--- a/core/modules/block/lib/Drupal/block/Entity/Block.php
+++ b/core/modules/block/lib/Drupal/block/Entity/Block.php
@@ -21,6 +21,7 @@
  *   id = "block",
  *   label = @Translation("Block"),
  *   controllers = {
+ *     "operations" = "Drupal\Core\Config\Entity\ConfigEntityOperationsProvider",
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *     "access" = "Drupal\block\BlockAccessController",
  *     "view_builder" = "Drupal\block\BlockViewBuilder",
diff --git a/core/modules/block/lib/Drupal/block/Entity/BlockOperationsProvider.php b/core/modules/block/lib/Drupal/block/Entity/BlockOperationsProvider.php
new file mode 100644
index 0000000..34b12d5
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/Entity/BlockOperationsProvider.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\BlockOperationsProvider.
+ */
+
+namespace Drupal\block;
+
+use Drupal\Core\Config\Entity\ConfigEntityOperationsProvider;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines a entity operations provider for block entities.
+ */
+class BlockOperationsProvider extends ConfigEntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $operations['update']['title'] = t('Configure');
+
+    return $operations;
+  }
+}
diff --git a/core/modules/comment/lib/Drupal/comment/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Entity/Comment.php
index cf96a59..7bd4daf 100644
--- a/core/modules/comment/lib/Drupal/comment/Entity/Comment.php
+++ b/core/modules/comment/lib/Drupal/comment/Entity/Comment.php
@@ -22,6 +22,7 @@
  *   label = @Translation("Comment"),
  *   bundle_label = @Translation("Content type"),
  *   controllers = {
+ *     "operations" = "\Drupal\Core\Entity\EntityOperationsProvider",
  *     "storage" = "Drupal\comment\CommentStorageController",
  *     "access" = "Drupal\comment\CommentAccessController",
  *     "view_builder" = "Drupal\comment\CommentViewBuilder",
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php
index d1dc721..d286c7d 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php
@@ -50,34 +50,6 @@ function testList() {
     $this->assertTrue(!empty($entity), '"Default" ConfigTest entity ID found.');
     $this->assertTrue($entity instanceof ConfigTest, '"Default" ConfigTest entity is an instance of ConfigTest.');
 
-    // Test getOperations() method.
-    $uri = $entity->uri();
-    $expected_operations = array(
-      'edit' => array (
-        'title' => t('Edit'),
-        'href' => $uri['path'],
-        'options' => $uri['options'],
-        'weight' => 10,
-      ),
-      'disable' => array(
-        'title' => t('Disable'),
-        'href' => $uri['path'] . '/disable',
-        'options' => $uri['options'],
-        'weight' => 40,
-      ),
-      'delete' => array (
-        'title' => t('Delete'),
-        'href' => $uri['path'] . '/delete',
-        'options' => $uri['options'],
-        'weight' => 100,
-      ),
-    );
-
-    $actual_operations = $controller->getOperations($entity);
-    // Sort the operations to normalize link order.
-    uasort($actual_operations, 'drupal_sort_weight');
-    $this->assertIdentical($expected_operations, $actual_operations, 'The operations are identical.');
-
     // Test buildHeader() method.
     $expected_items = array(
       'label' => 'Label',
@@ -88,15 +60,12 @@ function testList() {
     $this->assertIdentical($expected_items, $actual_items, 'Return value from buildHeader matches expected.');
 
     // Test buildRow() method.
-    $build_operations = $controller->buildOperations($entity);
     $expected_items = array(
       'label' => 'Default',
       'id' => 'dotted.default',
-      'operations' => array(
-        'data' => $build_operations,
-      ),
     );
     $actual_items = $controller->buildRow($entity);
+    unset($actual_items['operations']);
     $this->assertIdentical($expected_items, $actual_items, 'Return value from buildRow matches expected.');
     // Test sorting.
     $storage_controller = $controller->getStorageController();
@@ -120,36 +89,6 @@ function testList() {
     $entity->save();
     $list = $controller->load();
     $this->assertIdentical(array_keys($list), array('beta', 'dotted.default', 'alpha', 'omega'));
-
-    // Test that config entities that do not support status, do not have
-    // enable/disable operations.
-    $controller = $this->container->get('entity.manager')
-      ->getListController('config_test_no_status');
-
-    $list = $controller->load();
-    $entity = $list['default'];
-
-    // Test getOperations() method.
-    $uri = $entity->uri();
-    $expected_operations = array(
-      'edit' => array(
-        'title' => t('Edit'),
-        'href' => $uri['path'],
-        'options' => $uri['options'],
-        'weight' => 10,
-      ),
-      'delete' => array(
-        'title' => t('Delete'),
-        'href' => $uri['path'] . '/delete',
-        'options' => $uri['options'],
-        'weight' => 100,
-      ),
-    );
-
-    $actual_operations = $controller->getOperations($entity);
-    // Sort the operations to normalize link order.
-    uasort($actual_operations, 'drupal_sort_weight');
-    $this->assertIdentical($expected_operations, $actual_operations, 'The operations are identical.');
   }
 
   /**
diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php
index 3df6365..d6873ed 100644
--- a/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php
+++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/Entity/ConfigTest.php
@@ -19,6 +19,7 @@
  *   id = "config_test",
  *   label = @Translation("Test configuration"),
  *   controllers = {
+ *     "operations" = "Drupal\Core\Config\Entity\ConfigEntityOperationsProvider",
  *     "storage" = "Drupal\config_test\ConfigTestStorageController",
  *     "list" = "Drupal\config_test\ConfigTestListController",
  *     "form" = {
diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module
index 0fc1170..9da60e0 100644
--- a/core/modules/config_translation/config_translation.module
+++ b/core/modules/config_translation/config_translation.module
@@ -7,6 +7,7 @@
 
 use Drupal\config_translation\Plugin\Derivative\ConfigTranslationLocalTasks;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Exception\RouteNotFoundException;
 
 /**
@@ -137,18 +138,28 @@ function config_translation_config_translation_info(&$info) {
 }
 
 /**
- * Implements hook_entity_operation_alter().
+ * Implements hook_entity_operations().
  */
-function config_translation_entity_operation_alter(array &$operations, EntityInterface $entity) {
-  if (\Drupal::currentUser()->hasPermission('translate configuration')) {
-    $uri = $entity->uri();
-    $operations['translate'] = array(
-      'title' => t('Translate'),
-      'href' => $uri['path'] . '/translate',
-      'options' => $uri['options'],
-      'weight' => 50,
-    );
+function config_translation_entity_operations(EntityInterface $entity) {
+  $uri = $entity->uri();
+  $operations['translate'] = array(
+    'title' => t('Translate'),
+    'href' => $uri['path'] . '/translate',
+    'options' => $uri['options'],
+    'weight' => 50,
+  );
+
+  return $operations;
+}
+
+/**
+ * Implements hook_entity_access().
+ */
+function config_translation_entity_access(EntityInterface $entity, $operation, AccountInterface $account, $langcode) {
+  if ($operation == 'translate') {
+    return $account->hasPermission('translate configuration');
   }
+  return $entity::DENY;
 }
 
 /**
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationBlockListController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationBlockListController.php
index bd0f6f5..5f0ede5 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationBlockListController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationBlockListController.php
@@ -9,8 +9,10 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Defines the config translation controller for blocks.
@@ -27,8 +29,8 @@ class ConfigTranslationBlockListController extends ConfigTranslationEntityListCo
   /**
    * {@inheritdoc}
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
     $this->themes = list_themes();
   }
 
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationEntityListController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationEntityListController.php
index 09a9de6..0479c6b 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationEntityListController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationEntityListController.php
@@ -69,7 +69,17 @@ public function render() {
   public function buildRow(EntityInterface $entity) {
     $row['label']['data'] = $this->getLabel($entity);
     $row['label']['class'] = 'table-filter-text-source';
-    return $row + parent::buildRow($entity);
+    $row['operations']['data'] = array(
+      '#type' => 'operations',
+      '#links' => $this->operations->getOperations($entity, $this->currentUser),
+    );
+    foreach (array_keys($row['operations']['data']['#links']) as $operation) {
+      if ($operation != 'translate') {
+        unset($row['operations']['data']['#links'][$operation]);
+      }
+    }
+
+    return $row;
   }
 
   /**
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationFieldInstanceListController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationFieldInstanceListController.php
index d7ad320..699911e 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationFieldInstanceListController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationFieldInstanceListController.php
@@ -11,8 +11,10 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\field\Field;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -69,7 +71,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $container->get('entity.manager'),
       $definition
     );
@@ -84,15 +88,19 @@ public static function createInstance(ContainerInterface $container, $entity_typ
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param \Drupal\Core\Entity\EntityManager $entity_manager
    *   The entity manager.
    * @param array $definition
    *   The plugin definition of the config translation mapper.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, EntityManager $entity_manager, array $definition) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user, EntityManager $entity_manager, array $definition) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
     $this->entityManager = $entity_manager;
     $this->baseEntityType = $definition['base_entity_type'];
     $this->baseEntityInfo = $this->entityManager->getDefinition($this->baseEntityType);
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationListController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationListController.php
index 48ecad7..1324b2a 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationListController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationListController.php
@@ -19,6 +19,15 @@
 class ConfigTranslationListController extends ControllerBase implements ContainerInjectionInterface {
 
   /**
+   * The service container.
+   *
+   * The container should only be passed on to factory methods of other classes.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
    * The definition of the config mapper.
    *
    * @var array
@@ -35,12 +44,15 @@ class ConfigTranslationListController extends ControllerBase implements Containe
   /**
    * Constructs a new ConfigTranslationListController object.
    *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The service container.
    * @param \Drupal\config_translation\ConfigMapperManagerInterface $mapper_manager
    *   The config mapper manager.
    * @param string $config_translation_mapper
    *   The config mapper id.
    */
-  public function __construct(ConfigMapperManagerInterface $mapper_manager, $config_translation_mapper) {
+  public function __construct(ContainerInterface $container, ConfigMapperManagerInterface $mapper_manager, $config_translation_mapper) {
+    $this->container = $container;
     $this->mapperDefinition = $mapper_manager->getDefinition($config_translation_mapper);
     $this->mapper = $mapper_manager->createInstance($config_translation_mapper, $this->mapperDefinition);
   }
@@ -50,6 +62,7 @@ public function __construct(ConfigMapperManagerInterface $mapper_manager, $confi
    */
   public static function create(ContainerInterface $container) {
     return new static(
+      $container,
       $container->get('plugin.manager.config_translation.mapper'),
       $container->get('request')->attributes->get('_raw_variables')->get('config_translation_mapper')
     );
@@ -75,8 +88,12 @@ public function listing() {
     // node_type and block, fallback to the generic configuration translation
     // list controller.
     $class = $this->mapperDefinition['list_controller'];
-    /** @var \Drupal\config_translation\Controller\ConfigTranslationEntityListControllerInterface $controller */
-    $controller = new $class($entity_type, $this->entityManager()->getDefinition($entity_type), $this->entityManager()->getStorageController($entity_type), $this->moduleHandler(), $this->entityManager(), $this->mapperDefinition);
+    if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
+      $controller = $class::createInstance($this->container, $entity_type, $this->entityManager()->getDefinition($entity_type));
+    }
+    else {
+      $controller = new $class($entity_type, $this->getStorageController($entity_type));
+    }
     $build = $controller->render();
     $build['#title'] = $this->mapper->getTypeLabel();
     return $build;
diff --git a/core/modules/contact/lib/Drupal/contact/Entity/Category.php b/core/modules/contact/lib/Drupal/contact/Entity/Category.php
index 488f2e7..434140e 100644
--- a/core/modules/contact/lib/Drupal/contact/Entity/Category.php
+++ b/core/modules/contact/lib/Drupal/contact/Entity/Category.php
@@ -20,6 +20,7 @@
  *   id = "contact_category",
  *   label = @Translation("Contact category"),
  *   controllers = {
+ *     "operations" = "Drupal\Core\Config\Entity\ConfigEntityOperationsProvider",
  *     "storage" = "Drupal\contact\CategoryStorageController",
  *     "access" = "Drupal\contact\CategoryAccessController",
  *     "list" = "Drupal\contact\CategoryListController",
diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityFormMode.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityFormMode.php
index 8e2284b..ca8627c 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity/EntityFormMode.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityFormMode.php
@@ -33,6 +33,7 @@
  *   id = "form_mode",
  *   label = @Translation("Form mode"),
  *   controllers = {
+ *     "operations" = "Drupal\Core\Config\Entity\ConfigEntityOperationsProvider",
  *     "list" = "Drupal\entity\EntityFormModeListController",
  *     "form" = {
  *       "add" = "Drupal\entity\Form\EntityFormModeAddForm",
diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityViewMode.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityViewMode.php
index 1d17d5e..e061110 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity/EntityViewMode.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityViewMode.php
@@ -34,6 +34,7 @@
  *   id = "view_mode",
  *   label = @Translation("View mode"),
  *   controllers = {
+ *     "operations" = "Drupal\Core\Config\Entity\ConfigEntityOperationsProvider",
  *     "list" = "Drupal\entity\EntityDisplayModeListController",
  *     "form" = {
  *       "add" = "Drupal\entity\Form\EntityDisplayModeAddForm",
diff --git a/core/modules/entity/lib/Drupal/entity/EntityDisplayModeListController.php b/core/modules/entity/lib/Drupal/entity/EntityDisplayModeListController.php
index 835c1c6..81227c8 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityDisplayModeListController.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityDisplayModeListController.php
@@ -9,8 +9,10 @@
 
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -34,13 +36,17 @@ class EntityDisplayModeListController extends ConfigEntityListController {
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param array $entity_info_complete
    *   The entity info for all entity types.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, array $entity_info_complete) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user, array $entity_info_complete) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
 
     $this->entityInfoComplete = $entity_info_complete;
   }
@@ -54,7 +60,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $entity_manager->getStorageController($entity_type),
+      $entity_manager->getOperationsProvider($entity_type),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $entity_manager->getDefinitions()
     );
   }
diff --git a/core/modules/field/lib/Drupal/field/Entity/Field.php b/core/modules/field/lib/Drupal/field/Entity/Field.php
index 47003fb..ad194d6 100644
--- a/core/modules/field/lib/Drupal/field/Entity/Field.php
+++ b/core/modules/field/lib/Drupal/field/Entity/Field.php
@@ -23,6 +23,7 @@
  *   id = "field_entity",
  *   label = @Translation("Field"),
  *   controllers = {
+ *     "operations" = "Drupal\Core\Config\Entity\ConfigEntityOperationsProvider",
  *     "storage" = "Drupal\field\FieldStorageController"
  *   },
  *   config_prefix = "field.field",
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 2033cf6..fcc6dbe 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -92,6 +92,64 @@ function field_ui_permission() {
 }
 
 /**
+ * Implements hook_entity_operations().
+ */
+function field_ui_entity_operations(EntityInterface $entity) {
+  $info = $entity->entityInfo();
+  $operations = array();
+
+  // If the entity is fieldable, or if it is the bundle of a fieldable entity
+  // type, add field administration links.
+  if ($info['fieldable'] || !empty($info['bundle_of'])) {
+    $uri = $entity->uri();
+    $operations['manage-fields'] = array(
+      'title' => t('Manage fields'),
+      'href' => $uri['path'] . '/fields',
+      'options' => $uri['options'],
+      'weight' => 15,
+    );
+    $operations['manage-display'] = array(
+      'title' => t('Manage display'),
+      'href' => $uri['path'] . '/display',
+      'options' => $uri['options'],
+      'weight' => 25,
+    );
+  }
+
+  // If the entity is the bundle of a fieldable entity type, add additional
+  // field administration links.
+  if (!empty($info['bundle_of'])) {
+    $uri = $entity->uri();
+    $operations['manage-form-display'] = array(
+      'title' => t('Manage form display'),
+      'href' => $uri['path'] . '/form-display',
+      'options' => $uri['options'],
+      'weight' => 20,
+    );
+  }
+
+  return $operations;
+}
+
+/**
+ * Implements hook_entity_access().
+ */
+function field_ui_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
+  $info = $entity->entityInfo();
+  $entity_type = isset($info['bundle_of']) ? $info['bundle_of'] : $entity->entityType();
+
+  if ($operation == 'manage-fields') {
+    return $account->hasPermission('administer ' . $entity_type . ' fields');
+  }
+  if ($operation == 'manage-form-display') {
+    return $account->hasPermission('administer ' . $entity_type . ' form display');
+  }
+  if ($operation == 'manage-display') {
+    return $account->hasPermission('administer ' . $entity_type . ' display');
+  }
+}
+
+/**
  * Menu loader callback: Loads a field instance based on field and bundle name.
  *
  * @param $field_name
@@ -206,43 +264,6 @@ function field_ui_form_node_type_form_alter(&$form, $form_state) {
 }
 
 /**
- * Implements hook_entity_operation_alter().
- */
-function field_ui_entity_operation_alter(array &$operations, EntityInterface $entity) {
-  $info = $entity->entityInfo();
-  // Add manage fields and display links if this entity type is the bundle
-  // of another.
-  if (!empty($info['bundle_of'])) {
-    $bundle_of = $info['bundle_of'];
-    $uri = $entity->uri();
-    if (user_access('administer '. $bundle_of . ' fields')) {
-      $operations['manage-fields'] = array(
-        'title' => t('Manage fields'),
-        'href' => $uri['path'] . '/fields',
-        'options' => $uri['options'],
-        'weight' => 15,
-      );
-    }
-    if (user_access('administer '. $bundle_of . ' form display')) {
-      $operations['manage-form-display'] = array(
-        'title' => t('Manage form display'),
-        'href' => $uri['path'] . '/form-display',
-        'options' => $uri['options'],
-        'weight' => 20,
-      );
-    }
-    if (user_access('administer '. $bundle_of . ' display')) {
-      $operations['manage-display'] = array(
-        'title' => t('Manage display'),
-        'href' => $uri['path'] . '/display',
-        'options' => $uri['options'],
-        'weight' => 25,
-      );
-    }
-  }
-}
-
-/**
  * Form submission handler for the 'Save and manage fields' button.
  *
  * @see field_ui_form_node_type_form_alter()
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php b/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
index 0025379..5ea2ac5 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Field\FieldTypePluginManager;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -58,13 +59,15 @@ class FieldListController extends ConfigEntityListController {
    *   The entity manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param \Drupal\field\FieldInfo $field_info
    *   The field info service.
    * @param \Drupal\Core\Field\FieldTypePluginManager $field_type_manager
    *   The 'field type' plugin manager.
    */
-  public function __construct($entity_type, array $entity_info, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, FieldTypePluginManager $field_type_manager) {
-    parent::__construct($entity_type, $entity_info, $entity_manager->getStorageController($entity_type), $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, FieldTypePluginManager $field_type_manager) {
+    parent::__construct($entity_type, $entity_info, $entity_manager->getStorageController($entity_type), $entity_manager->getOperationsProvider($entity_type), $module_handler, $current_user);
 
     $this->entityManager = $entity_manager;
     $this->bundles = entity_get_bundles();
@@ -81,6 +84,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_info,
       $container->get('entity.manager'),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $container->get('plugin.manager.field.field_type')
     );
   }
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php b/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
index 85c2e26..ed70247 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/FieldOverview.php
@@ -8,8 +8,9 @@
 namespace Drupal\field_ui;
 
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Field\FieldTypePluginManager;
-use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\field_ui\OverviewBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\field\Entity\Field;
@@ -20,18 +21,18 @@
 class FieldOverview extends OverviewBase {
 
   /**
-   *  The field type manager.
+   *  The current user.
    *
-   * @var \Drupal\Core\Field\FieldTypePluginManager
+   * @var \Drupal\Core\Session\AccountInterface
    */
-  protected $fieldTypeManager;
+  protected $currentUser;
 
   /**
-   * The module handler service.
+   *  The field type manager.
    *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   * @var \Drupal\Core\Field\FieldTypePluginManager
    */
-  protected $moduleHandler;
+  protected $fieldTypeManager;
 
   /**
    * Constructs a new FieldOverview.
@@ -40,13 +41,12 @@ class FieldOverview extends OverviewBase {
    *   The entity manager.
    * @param \Drupal\Core\Field\FieldTypePluginManager $field_type_manager
    *   The field type manager
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler to invoke hooks on.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
    */
-  public function __construct(EntityManagerInterface $entity_manager, FieldTypePluginManager $field_type_manager, ModuleHandlerInterface $module_handler) {
+  public function __construct(EntityManagerInterface $entity_manager, FieldTypePluginManager $field_type_manager, AccountInterface $current_user) {
     parent::__construct($entity_manager);
+    $this->currentUser = $current_user;
     $this->fieldTypeManager = $field_type_manager;
-    $this->moduleHandler = $module_handler;
   }
 
   /**
@@ -56,7 +56,7 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity.manager'),
       $container->get('plugin.manager.field.field_type'),
-      $container->get('module_handler')
+      $container->get('current_user')
     );
   }
 
@@ -137,28 +137,9 @@ public function buildForm(array $form, array &$form_state, $entity_type = NULL,
           '#options' => array('attributes' => array('title' => $this->t('Edit field settings.'))),
         ),
       );
-
-      $links = array();
-      $links['edit'] = array(
-        'title' => $this->t('Edit'),
-        'href' => $admin_field_path,
-        'attributes' => array('title' => $this->t('Edit instance settings.')),
-      );
-      $links['field-settings'] = array(
-        'title' => $this->t('Field settings'),
-        'href' => $admin_field_path . '/field',
-        'attributes' => array('title' => $this->t('Edit field settings.')),
-      );
-      $links['delete'] = array(
-        'title' => $this->t('Delete'),
-        'href' => "$admin_field_path/delete",
-        'attributes' => array('title' => $this->t('Delete instance.')),
-      );
-      // Allow altering the operations on this entity listing.
-      $this->moduleHandler->alter('entity_operation', $links, $instance);
       $table[$name]['operations']['data'] = array(
         '#type' => 'operations',
-        '#links' => $links,
+        '#links' => $this->entityManager->getOperationsProvider($entity_type)->getOperations($instance, $this->currentUser),
       );
 
       if (!empty($field->locked)) {
diff --git a/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php b/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php
index 9571584..0e00aa3 100644
--- a/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php
+++ b/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php
@@ -21,6 +21,7 @@
  *   id = "filter_format",
  *   label = @Translation("Text format"),
  *   controllers = {
+ *     "operations" = "Drupal\filter\FilterFormatOperationsProvider",
  *     "form" = {
  *       "add" = "Drupal\filter\FilterFormatAddFormController",
  *       "edit" = "Drupal\filter\FilterFormatEditFormController",
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatAccessController.php b/core/modules/filter/lib/Drupal/filter/FilterFormatAccessController.php
index a1edbd9..2d30711 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterFormatAccessController.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterFormatAccessController.php
@@ -30,6 +30,10 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
     if ($operation == 'delete') {
       return FALSE;
     }
+    // The fallback format may not be disabled.
+    if ($operation == 'disable' && $entity->isFallbackFormat()) {
+      return FALSE;
+    }
 
     if ($operation != 'view' && $account->hasPermission('administer filters')) {
       return TRUE;
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php b/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
index cd94f86..ca980c5 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
@@ -12,8 +12,10 @@
 use Drupal\Core\Config\Entity\DraggableListController;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -42,13 +44,17 @@ class FilterFormatListController extends DraggableListController implements Enti
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param \Drupal\Core\Config\ConfigFactory $config_factory
    *   The config factory.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user, ConfigFactory $config_factory) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
 
     $this->configFactory = $config_factory;
   }
@@ -61,7 +67,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $container->get('config.factory')
     );
   }
@@ -123,24 +131,6 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-
-    if (isset($operations['edit'])) {
-      $operations['edit']['title'] = t('Configure');
-    }
-
-    // The fallback format may not be disabled.
-    if ($entity->isFallbackFormat()) {
-      unset($operations['disable']);
-    }
-
-    return $operations;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function buildForm(array $form, array &$form_state) {
     $form = parent::buildForm($form, $form_state);
     $form['actions']['submit']['#value'] = t('Save changes');
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatOperationsProvider.php b/core/modules/filter/lib/Drupal/filter/FilterFormatOperationsProvider.php
new file mode 100644
index 0000000..836877a
--- /dev/null
+++ b/core/modules/filter/lib/Drupal/filter/FilterFormatOperationsProvider.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filter\FilterFormatOperationsProvider.
+ */
+
+namespace Drupal\filter;
+
+use Drupal\Core\Config\Entity\ConfigEntityOperationsProvider;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines a entity operations provider for filter_format entities.
+ */
+class FilterFormatOperationsProvider extends ConfigEntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $operations['update']['title'] = t('Configure');
+
+    return $operations;
+  }
+}
diff --git a/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php
index d67bcad..11e04f9 100644
--- a/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php
+++ b/core/modules/image/lib/Drupal/image/Entity/ImageStyle.php
@@ -25,6 +25,8 @@
  *   id = "image_style",
  *   label = @Translation("Image style"),
  *   controllers = {
+ *     "access" = "Drupal\Core\Entity\EntityAccessController",
+ *     "operations" = "Drupal\image\ImageStyleOperationsProvider",
  *     "form" = {
  *       "add" = "Drupal\image\Form\ImageStyleAddForm",
  *       "edit" = "Drupal\image\Form\ImageStyleEditForm",
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleListController.php b/core/modules/image/lib/Drupal/image/ImageStyleListController.php
index 3c4f3e7..54997f7 100644
--- a/core/modules/image/lib/Drupal/image/ImageStyleListController.php
+++ b/core/modules/image/lib/Drupal/image/ImageStyleListController.php
@@ -10,9 +10,11 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -37,13 +39,17 @@ class ImageStyleListController extends ConfigEntityListController implements Ent
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $image_style_storage
    *   The image style entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
    *   The URL generator.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $image_style_storage, ModuleHandlerInterface $module_handler, UrlGeneratorInterface $url_generator) {
-    parent::__construct($entity_type, $entity_info, $image_style_storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $image_style_storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user, UrlGeneratorInterface $url_generator) {
+    parent::__construct($entity_type, $entity_info, $image_style_storage, $operations, $module_handler, $current_user);
     $this->urlGenerator = $url_generator;
   }
 
@@ -55,7 +61,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $container->get('url_generator'),
       $container->get('string_translation')
     );
@@ -80,21 +88,6 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $uri = $entity->uri('edit-form');
-    $flush = array(
-      'title' => t('Flush'),
-      'href' => $uri['path'] . '/flush',
-      'options' => $uri['options'],
-      'weight' => 200,
-    );
-
-    return parent::getOperations($entity) + array('flush' => $flush);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function render() {
     $build = parent::render();
     $build['#empty'] = $this->t('There are currently no styles. <a href="!url">Add a new one</a>.', array(
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleOperationsProvider.php b/core/modules/image/lib/Drupal/image/ImageStyleOperationsProvider.php
new file mode 100644
index 0000000..a326a34
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageStyleOperationsProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\ImageStyleOperationsProvider.
+ */
+
+namespace Drupal\image;
+
+use Drupal\Core\Config\Entity\ConfigEntityOperationsProvider;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines a entity operations provider for image_style entities.
+ */
+class ImageStyleOperationsProvider extends ConfigEntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $uri = $entity->uri('edit-form');
+    $operations['flush'] = array(
+      'title' => $this->translationManager->translate('Flush'),
+      'href' => $uri['path'] . '/flush',
+      'options' => $uri['options'],
+      'weight' => 200,
+    );
+
+    return $operations;
+  }
+}
diff --git a/core/modules/image/tests/Drupal/image/ImageStyleOperationsProviderUnitTest.php b/core/modules/image/tests/Drupal/image/ImageStyleOperationsProviderUnitTest.php
new file mode 100644
index 0000000..817ce24
--- /dev/null
+++ b/core/modules/image/tests/Drupal/image/ImageStyleOperationsProviderUnitTest.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Tests\ImageStyleOperationsProviderUnitTest.
+ */
+
+namespace Drupal\image\Tests;
+
+use Drupal\image\ImageStyleOperationsProvider;
+use Drupal\Tests\UnitTestCase;
+
+
+/**
+ * Tests \Drupal\image\ImageStyleOperationsProvider.
+ */
+class ImageStyleOperationsProviderUnitTest extends UnitTestCase {
+
+  /**
+   * The module handler used for testing.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The current user used for testing.
+   *
+   * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $currentUser;
+
+  /**
+   * The translation manager used for testing.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $translationManager;
+
+  /**
+   * The operations provider under test.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityOperationsProvider
+   */
+  protected $operationsProvider;
+
+  /**
+   * The entity used for testing.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'group' => 'Image',
+      'name' => '\Drupal\image\ImageStyleOperationsProvider',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->moduleHandler->expects($this->exactly(2))
+      ->method('invokeAll')
+      ->will($this->returnValue(array()));
+
+    $this->currentUser = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $this->translationManager = $this->getMockBuilder('\Drupal\Core\StringTranslation\TranslationManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->entity = $this->getMock('\Drupal\Core\Config\Entity\ConfigEntityInterface');
+    $this->entity->expects($this->any())
+      ->method('access')
+      ->will($this->returnValue(TRUE));
+
+    $this->operationsProvider = new ImageStyleOperationsProvider($this->moduleHandler, $this->translationManager);
+  }
+
+  /**
+   * Tests getOperations().
+   */
+  public function testGetOperations() {
+    $operations = $this->operationsProvider->getOperations($this->entity, $this->currentUser);
+    $this->assertInternalType('array', $operations);
+    $this->assertArrayHasKey('flush', $operations);
+    $this->assertInternalType('array', $operations['flush']);
+  }
+}
diff --git a/core/modules/language/lib/Drupal/language/Entity/Language.php b/core/modules/language/lib/Drupal/language/Entity/Language.php
index 8580d87..6f6d25e 100644
--- a/core/modules/language/lib/Drupal/language/Entity/Language.php
+++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
@@ -21,6 +21,7 @@
  *   id = "language_entity",
  *   label = @Translation("Language"),
  *   controllers = {
+ *     "operations" = "Drupal\language\LanguageOperationsProvider",
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *     "list" = "Drupal\language\LanguageListController",
  *     "access" = "Drupal\language\LanguageAccessController",
diff --git a/core/modules/language/lib/Drupal/language/LanguageAccessController.php b/core/modules/language/lib/Drupal/language/LanguageAccessController.php
index 2cc17d0..f49b64d 100644
--- a/core/modules/language/lib/Drupal/language/LanguageAccessController.php
+++ b/core/modules/language/lib/Drupal/language/LanguageAccessController.php
@@ -18,6 +18,11 @@ class LanguageAccessController extends EntityAccessController {
    * {@inheritdoc}
    */
   public function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
+    // Deleting the site default language is not allowed.
+    if ($operation == 'delete' && $entity->id() == language_default()->id) {
+      return FALSE;
+    }
+
     switch ($operation) {
       case 'update':
       case 'delete':
diff --git a/core/modules/language/lib/Drupal/language/LanguageListController.php b/core/modules/language/lib/Drupal/language/LanguageListController.php
index 35724b7..54b6855 100644
--- a/core/modules/language/lib/Drupal/language/LanguageListController.php
+++ b/core/modules/language/lib/Drupal/language/LanguageListController.php
@@ -41,31 +41,6 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    $default = language_default();
-
-    // Edit and delete path for Languages entities have a different pattern
-    // than other config entities.
-    $path = 'admin/config/regional/language';
-    if (isset($operations['edit'])) {
-      $operations['edit']['href'] = $path . '/edit/' . $entity->id();
-    }
-    if (isset($operations['delete'])) {
-      $operations['delete']['href'] = $path . '/delete/' . $entity->id();
-    }
-
-    // Deleting the site default language is not allowed.
-    if ($entity->id() == $default->id) {
-      unset($operations['delete']);
-    }
-
-    return $operations;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function buildHeader() {
     $header['label'] = t('Name');
     return $header + parent::buildHeader();
diff --git a/core/modules/language/lib/Drupal/language/LanguageOperationsProvider.php b/core/modules/language/lib/Drupal/language/LanguageOperationsProvider.php
new file mode 100644
index 0000000..82ad384
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageOperationsProvider.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\language\LanguageOperationsProvider.
+ */
+
+namespace Drupal\language;
+
+use Drupal\Core\Config\Entity\ConfigEntityOperationsProvider;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines a entity operations provider for language entities.
+ */
+class LanguageOperationsProvider extends ConfigEntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+
+    // Edit and delete path for language entities have a different pattern
+    // than other config entities.
+    $path = 'admin/config/regional/language';
+    $operations['update']['href'] = $path . '/edit/' . $entity->id();
+    $operations['delete']['href'] = $path . '/delete/' . $entity->id();
+
+    return $operations;
+  }
+}
diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageListTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageListTest.php
index 8b2190d..9472987 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageListTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageListTest.php
@@ -76,10 +76,10 @@ function testLanguageList() {
 
     // Ensure we can't delete the default language.
     $this->drupalGet('admin/config/regional/language/delete/' . $langcode);
-    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
-    $this->assertText(t('The default language cannot be deleted.'), 'Failed to delete the default language.');
+    $this->assertResponse('403');
 
     // Ensure 'Edit' link works.
+    $this->drupalGet('admin/config/regional/language');
     $this->clickLink(t('Edit'));
     $this->assertTitle(t('Edit language | Drupal'), 'Page title is "Edit language".');
     // Edit a language.
diff --git a/core/modules/menu/lib/Drupal/menu/MenuListController.php b/core/modules/menu/lib/Drupal/menu/MenuListController.php
index 4c5a408..6b3da33 100644
--- a/core/modules/menu/lib/Drupal/menu/MenuListController.php
+++ b/core/modules/menu/lib/Drupal/menu/MenuListController.php
@@ -39,28 +39,6 @@ public function buildRow(EntityInterface $entity) {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    $uri = $entity->uri();
-
-    if (isset($operations['edit'])) {
-      $operations['edit']['title'] = t('Edit menu');
-      $operations['add'] = array(
-        'title' => t('Add link'),
-        'href' => $uri['path'] . '/add',
-        'options' => $uri['options'],
-        'weight' => 20,
-      );
-    }
-    if (isset($operations['delete'])) {
-      $operations['delete']['title'] = t('Delete menu');
-    }
-    return $operations;
-  }
-
-  /**
    * Overrides \Drupal\Core\Entity\EntityListController::render();
    */
   public function render() {
diff --git a/core/modules/menu/lib/Drupal/menu/MenuOperationsProvider.php b/core/modules/menu/lib/Drupal/menu/MenuOperationsProvider.php
new file mode 100644
index 0000000..778a894
--- /dev/null
+++ b/core/modules/menu/lib/Drupal/menu/MenuOperationsProvider.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu\MenuOperationsProvider.
+ */
+
+namespace Drupal\menu;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProvider;
+
+/**
+ * Defines a entity operations provider for menu entities.
+ */
+class MenuOperationsProvider extends EntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $uri = $entity->uri();
+
+    $operations['update']['title'] = t('Edit menu');
+    $operations['delete']['title'] = t('Delete menu');
+    $operations['add'] = array(
+      'title' => $this->t('Add link'),
+      'href' => $uri['path'] . '/add',
+      'options' => $uri['options'],
+      'weight' => 20,
+    );
+
+    return $operations;
+  }
+}
diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php
index 3700499..9af37f0 100644
--- a/core/modules/node/lib/Drupal/node/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Entity/Node.php
@@ -21,6 +21,7 @@
  *   label = @Translation("Content"),
  *   bundle_label = @Translation("Content type"),
  *   controllers = {
+ *     "operations" = "\Drupal\Core\Entity\EntityOperationsProvider",
  *     "storage" = "Drupal\node\NodeStorageController",
  *     "view_builder" = "Drupal\node\NodeViewBuilder",
  *     "access" = "Drupal\node\NodeAccessController",
diff --git a/core/modules/node/lib/Drupal/node/Entity/NodeType.php b/core/modules/node/lib/Drupal/node/Entity/NodeType.php
index b06c583..49bca00 100644
--- a/core/modules/node/lib/Drupal/node/Entity/NodeType.php
+++ b/core/modules/node/lib/Drupal/node/Entity/NodeType.php
@@ -20,6 +20,7 @@
  *   id = "node_type",
  *   label = @Translation("Content type"),
  *   controllers = {
+ *     "operations" = "Drupal\node\NodeTypeOperationsProvider",
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *     "access" = "Drupal\node\NodeTypeAccessController",
  *     "form" = {
diff --git a/core/modules/node/lib/Drupal/node/NodeTypeListController.php b/core/modules/node/lib/Drupal/node/NodeTypeListController.php
index 0cb406f..5aa5fbc 100644
--- a/core/modules/node/lib/Drupal/node/NodeTypeListController.php
+++ b/core/modules/node/lib/Drupal/node/NodeTypeListController.php
@@ -8,6 +8,8 @@
 
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -37,13 +39,17 @@ class NodeTypeListController extends ConfigEntityListController implements Entit
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
    *   The url generator service.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, UrlGeneratorInterface $url_generator) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user, UrlGeneratorInterface $url_generator) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
     $this->urlGenerator = $url_generator;
   }
   /**
@@ -54,7 +60,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $container->get('url_generator')
     );
   }
@@ -86,19 +94,6 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    // Place the edit operation after the operations added by field_ui.module
-    // which have the weights 15, 20, 25.
-    if (isset($operations['edit'])) {
-      $operations['edit']['weight'] = 30;
-    }
-    return $operations;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function render() {
     $build = parent::render();
     $build['#empty'] = t('No content types available. <a href="@link">Add content type</a>.', array(
diff --git a/core/modules/node/lib/Drupal/node/NodeTypeOperationsProvider.php b/core/modules/node/lib/Drupal/node/NodeTypeOperationsProvider.php
new file mode 100644
index 0000000..6e6a302
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeTypeOperationsProvider.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\NodeTypeOperationsProvider.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Config\Entity\ConfigEntityOperationsProvider;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines a entity operations provider for node_type entities.
+ */
+class NodeTypeOperationsProvider extends ConfigEntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    // Place the edit operation after the operations added by field_ui.module
+    // which have the weights 15, 20, 25.
+    $operations['update']['weight'] = 30;
+
+    return $operations;
+  }
+}
diff --git a/core/modules/picture/lib/Drupal/picture/Entity/PictureMapping.php b/core/modules/picture/lib/Drupal/picture/Entity/PictureMapping.php
index e6ce436..c0ed69e 100644
--- a/core/modules/picture/lib/Drupal/picture/Entity/PictureMapping.php
+++ b/core/modules/picture/lib/Drupal/picture/Entity/PictureMapping.php
@@ -19,6 +19,7 @@
  *   id = "picture_mapping",
  *   label = @Translation("Picture mapping"),
  *   controllers = {
+ *     "operations" = "Drupal\picture\PictureMappingOperationsProvider",
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *     "list" = "Drupal\picture\PictureMappingListController",
  *     "form" = {
diff --git a/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php b/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
index ffe4c0c..5594053 100644
--- a/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
+++ b/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
@@ -33,19 +33,4 @@ public function buildRow(EntityInterface $entity) {
     return $row + parent::buildRow($entity);
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    $uri = $entity->uri();
-    $operations['duplicate'] = array(
-      'title' => t('Duplicate'),
-      'href' => $uri['path'] . '/duplicate',
-      'options' => $uri['options'],
-      'weight' => 15,
-    );
-    return $operations;
-  }
-
 }
diff --git a/core/modules/picture/lib/Drupal/picture/PictureMappingOperationsProvider.php b/core/modules/picture/lib/Drupal/picture/PictureMappingOperationsProvider.php
new file mode 100644
index 0000000..a645cb6
--- /dev/null
+++ b/core/modules/picture/lib/Drupal/picture/PictureMappingOperationsProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\picture\PictureMappingOperationsProvider.
+ */
+
+namespace Drupal\picture;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProvider;
+
+/**
+ * Defines a entity operations provider for picture_mapping entities.
+ */
+class PictureMappingOperationsProvider extends EntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $uri = $entity->uri();
+    $operations['duplicate'] = array(
+      'title' => $this->t('Duplicate'),
+      'href' => $uri['path'] . '/duplicate',
+      'options' => $uri['options'],
+      'weight' => 15,
+    );
+
+    return $operations;
+  }
+}
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Entity/ShortcutSet.php b/core/modules/shortcut/lib/Drupal/shortcut/Entity/ShortcutSet.php
index 275e79a..462451f 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Entity/ShortcutSet.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Entity/ShortcutSet.php
@@ -20,6 +20,7 @@
  *   id = "shortcut_set",
  *   label = @Translation("Shortcut set"),
  *   controllers = {
+ *     "operations" = "Drupal\shortcut\ShortcutSetOperationsProvider",
  *     "storage" = "Drupal\shortcut\ShortcutSetStorageController",
  *     "access" = "Drupal\shortcut\ShortcutSetAccessController",
  *     "list" = "Drupal\shortcut\ShortcutSetListController",
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetListController.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetListController.php
index 6215a87..ca66977 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetListController.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetListController.php
@@ -24,25 +24,6 @@ public function buildHeader() {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    $uri = $entity->uri();
-
-    if (isset($operations['edit'])) {
-      $operations['edit']['title'] = t('Edit menu');
-      $operations['edit']['href'] = $uri['path'] . '/edit';
-    }
-
-    $operations['list'] = array(
-      'title' => t('List links'),
-      'href' => $uri['path'],
-    );
-    return $operations;
-  }
-
-  /**
    * Overrides \Drupal\Core\Entity\EntityListController::buildRow().
    */
   public function buildRow(EntityInterface $entity) {
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetOperationsProvider.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetOperationsProvider.php
new file mode 100644
index 0000000..4a0a785
--- /dev/null
+++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetOperationsProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\shortcut\ShortcutSetOperationsProvider.
+ */
+
+namespace Drupal\shortcut;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProvider;
+
+/**
+ * Defines a entity operations provider for action entities.
+ */
+class ShortcutSetOperationsProvider extends EntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $uri = $entity->uri();
+    $operations['update']['title'] = $this->t('Edit menu');
+    $operations['update']['href'] = $uri['path'] . '/edit';
+    $operations['list'] = array(
+      'title' => $this->t('List links'),
+      'href' => $uri['path'],
+    );
+
+    return $operations;
+  }
+}
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index 05f9e57..d561d95 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -684,15 +684,54 @@ function hook_entity_field_info_alter(&$info, $entity_type) {
 }
 
 /**
- * Alter entity operations.
+ * Declares operations for an entity.
+ *
+ * @see \Drupal\Core\Entity\EntityOperationsProvider
  *
- * @param array $operations
- *   Operations array as returned by
- *   \Drupal\Core\Entity\EntityStorageControllerInterface::getOperations().
  * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity on which the linked operations will be performed.
+ *
+ * @return array
+ *   The structure is identical to that of the return value of
+ *   \Drupal\Core\Entity\EntityOperationsProviderInterface::getOperations().
+ */
+function hook_entity_operations(\Drupal\Core\Entity\EntityInterface $entity) {
+  $uri = $entity->uri();
+  $operations['translate'] = array(
+    'title' => t('Translate'),
+    'href' => $uri['path'] . '/translate',
+    'weight' => 50,
+  );
+
+  return $operations;
+}
+
+/**
+ * Declares operations for an entity of a specific type.
+ *
+ * @see \Drupal\Core\Entity\EntityOperationsProvider
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *
+ * @return array
+ *   The structure is identical to that of the return value of
+ *   \Drupal\Core\Entity\EntityOperationsProviderInterface::getOperations().
  */
-function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\EntityInterface $entity) {
+function hook_ENTITY_TYPE_operations(\Drupal\Core\Entity\EntityInterface $entity) {
+  return array();
+}
+
+/**
+ * Alters entity operations.
+ *
+ * @see \Drupal\Core\Entity\EntityOperationsProvider
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity on which the linked operations will be performed.
+  * @param array $operations
+  *   Operations array as returned by
+  *   \Drupal\Core\Entity\EntityStorageControllerInterface::getOperations().
+  */
+function hook_entity_operations_alter(\Drupal\Core\Entity\EntityInterface $entity, array &$operations) {
   $uri = $entity->uri();
   $operations['translate'] = array(
     'title' => t('Translate'),
@@ -702,6 +741,20 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent
 }
 
 /**
+ * Alters entity operations for an entity of a specific type.
+ *
+ * @see \Drupal\Core\Entity\EntityOperationsProvider
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity on which the linked operations will be performed.
+  * @param array $operations
+  *   Operations array as returned by
+  *   \Drupal\Core\Entity\EntityStorageControllerInterface::getOperations().
+  */
+function hook_ENTITY_TYPE_operations_alter(\Drupal\Core\Entity\EntityInterface $entity, array &$operations) {
+}
+
+/**
  * Control access to fields.
  *
  * This hook is invoked from
diff --git a/core/modules/system/lib/Drupal/system/DateFormatListController.php b/core/modules/system/lib/Drupal/system/DateFormatListController.php
index d22642b..d87f2a4 100644
--- a/core/modules/system/lib/Drupal/system/DateFormatListController.php
+++ b/core/modules/system/lib/Drupal/system/DateFormatListController.php
@@ -11,8 +11,10 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Datetime\Date;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -36,13 +38,17 @@ class DateFormatListController extends ConfigEntityListController {
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param \Drupal\Core\Datetime\Date $date_service
    *   The date service.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, Date $date_service) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user, Date $date_service) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
 
     $this->dateService = $date_service;
   }
@@ -55,7 +61,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $container->get('date')
     );
   }
diff --git a/core/modules/system/lib/Drupal/system/Entity/Action.php b/core/modules/system/lib/Drupal/system/Entity/Action.php
index 4962a92..da1e65e 100644
--- a/core/modules/system/lib/Drupal/system/Entity/Action.php
+++ b/core/modules/system/lib/Drupal/system/Entity/Action.php
@@ -22,6 +22,7 @@
  *   id = "action",
  *   label = @Translation("Action"),
  *   controllers = {
+ *     "operations" = "Drupal\action\ActionOperationsProvider",
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *   },
  *   admin_permission = "administer actions",
diff --git a/core/modules/system/lib/Drupal/system/Entity/DateFormat.php b/core/modules/system/lib/Drupal/system/Entity/DateFormat.php
index 447f800..d143377 100644
--- a/core/modules/system/lib/Drupal/system/Entity/DateFormat.php
+++ b/core/modules/system/lib/Drupal/system/Entity/DateFormat.php
@@ -21,6 +21,7 @@
  *   id = "date_format",
  *   label = @Translation("Date format"),
  *   controllers = {
+ *     "operations" = "\Drupal\Core\Config\Entity\ConfigEntityOperationsProvider",
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *     "access" = "Drupal\system\DateFormatAccessController",
  *     "list" = "Drupal\system\DateFormatListController",
diff --git a/core/modules/system/lib/Drupal/system/Entity/Menu.php b/core/modules/system/lib/Drupal/system/Entity/Menu.php
index 5f6d075..ac93af2 100644
--- a/core/modules/system/lib/Drupal/system/Entity/Menu.php
+++ b/core/modules/system/lib/Drupal/system/Entity/Menu.php
@@ -19,6 +19,7 @@
  *   id = "menu",
  *   label = @Translation("Menu"),
  *   controllers = {
+ *     "operations" = "Drupal\menu\MenuOperationsProvider",
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *     "access" = "Drupal\system\MenuAccessController"
  *   },
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityOperationsTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityOperationsTest.php
index 776021d..424b290 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityOperationsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityOperationsTest.php
@@ -40,11 +40,11 @@ public function setUp() {
   }
 
   /**
-   * Checks that hook_entity_operation_alter() can add an operation.
+   * Checks that hook_entity_operations() can add an operation.
    *
-   * @see entity_test_entity_operation_alter()
+   * @see entity_test_entity_operations()
    */
-  public function testEntityOperationAlter() {
+  public function testEntityOperations() {
     // Check that role listing contain our test_operation operation.
     $this->drupalGet('admin/people/roles');
     $roles = user_roles();
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 2350766..8d2683f 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -452,15 +452,17 @@ function entity_test_entity_predelete(EntityInterface $entity) {
 }
 
 /**
- * Implements hook_entity_operation_alter().
+ * Implements hook_entity_operations().
  */
-function entity_test_entity_operation_alter(array &$operations, EntityInterface $entity) {
+function entity_test_entity_operations(EntityInterface $entity) {
   $uri = $entity->uri();
   $operations['test_operation'] = array(
     'title' => format_string('Test Operation: @label', array('@label' => $entity->label())),
     'href' => $uri['path'] . '/test_operation',
     'weight' => 50,
   );
+
+  return $operations;
 }
 
 /**
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
index b4ad49b..7b14ede 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
@@ -19,6 +19,7 @@
  *   id = "entity_test",
  *   label = @Translation("Test entity"),
  *   controllers = {
+ *     "operations" = "\Drupal\Core\Entity\EntityOperationsProvider",
  *     "storage" = "Drupal\entity_test\EntityTestStorageController",
  *     "list" = "Drupal\entity_test\EntityTestListController",
  *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
index a091d1a..9017057 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
@@ -22,6 +22,7 @@
  *   label = @Translation("Taxonomy term"),
  *   bundle_label = @Translation("Vocabulary"),
  *   controllers = {
+ *     "operations" = "\Drupal\Core\Entity\EntityOperationsProvider",
  *     "storage" = "Drupal\taxonomy\TermStorageController",
  *     "view_builder" = "Drupal\taxonomy\TermViewBuilder",
  *     "access" = "Drupal\taxonomy\TermAccessController",
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php
index ec25ec1..a2f5dd3 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Vocabulary.php
@@ -20,6 +20,7 @@
  *   id = "taxonomy_vocabulary",
  *   label = @Translation("Taxonomy vocabulary"),
  *   controllers = {
+ *     "operations" = "Drupal\taxonomy\VocabularyOperationsProvider",
  *     "storage" = "Drupal\taxonomy\VocabularyStorageController",
  *     "list" = "Drupal\taxonomy\VocabularyListController",
  *     "form" = {
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyListController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyListController.php
index ff93ede..eb0ed5f 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyListController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyListController.php
@@ -30,35 +30,6 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    $uri = $entity->uri();
-
-    if (isset($operations['edit'])) {
-      $operations['edit']['title'] = t('edit vocabulary');
-      $operations['edit']['href'] = $uri['path'] . '/edit';
-    }
-
-    $operations['list'] = array(
-      'title' => t('list terms'),
-      'href' => $uri['path'],
-      'options' => $uri['options'],
-      'weight' => 0,
-    );
-    $operations['add'] = array(
-      'title' => t('add terms'),
-      'href' => $uri['path'] . '/add',
-      'options' => $uri['options'],
-      'weight' => 10,
-    );
-    unset($operations['delete']);
-
-    return $operations;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function buildHeader() {
     $header['label'] = t('Vocabulary name');
     return $header + parent::buildHeader();
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyOperationsProvider.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyOperationsProvider.php
new file mode 100644
index 0000000..e7d537e
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyOperationsProvider.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\VocabularyOperationsProvider.
+ */
+
+namespace Drupal\taxonomy;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProvider;
+
+/**
+ * Defines a entity operations provider for taxonomy_vocabulary entities.
+ */
+class VocabularyOperationsProvider extends EntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $uri = $entity->uri();
+
+    $operations['update']['title'] = $this->t('edit vocabulary');
+    $operations['update']['href'] = $uri['path'] . '/edit';
+    $operations['list'] = array(
+      'title' => $this->t('list terms'),
+      'href' => $uri['path'],
+      'options' => $uri['options'],
+      'weight' => 0,
+    );
+    $operations['add'] = array(
+      'title' => $this->t('add terms'),
+      'href' => $uri['path'] . '/add',
+      'options' => $uri['options'],
+      'weight' => 10,
+    );
+    unset($operations['delete']);
+
+    return $operations;
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/Controller/UserListController.php b/core/modules/user/lib/Drupal/user/Controller/UserListController.php
index 7f8da1d..beb8a55 100644
--- a/core/modules/user/lib/Drupal/user/Controller/UserListController.php
+++ b/core/modules/user/lib/Drupal/user/Controller/UserListController.php
@@ -10,9 +10,11 @@
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityListController;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -38,13 +40,17 @@ class UserListController extends EntityListController implements EntityControlle
    *   An array of entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
    *   The entity query factory.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, QueryFactory $query_factory) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, ModuleHandlerInterface $module_handler, AccountInterface $current_user, QueryFactory $query_factory) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
     $this->queryFactory = $query_factory;
   }
 
@@ -56,7 +62,9 @@ public static function createInstance(ContainerInterface $container, $entity_typ
       $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $container->get('module_handler'),
+      $container->get('current_user'),
       $container->get('entity.query')
     );
   }
diff --git a/core/modules/user/lib/Drupal/user/Entity/Role.php b/core/modules/user/lib/Drupal/user/Entity/Role.php
index b4e9a12..781c0bf 100644
--- a/core/modules/user/lib/Drupal/user/Entity/Role.php
+++ b/core/modules/user/lib/Drupal/user/Entity/Role.php
@@ -18,6 +18,7 @@
  *   id = "user_role",
  *   label = @Translation("Role"),
  *   controllers = {
+ *     "operations" = "Drupal\user\RoleOperationsProvider",
  *     "storage" = "Drupal\user\RoleStorageController",
  *     "access" = "Drupal\user\RoleAccessController",
  *     "list" = "Drupal\user\RoleListController",
diff --git a/core/modules/user/lib/Drupal/user/Entity/User.php b/core/modules/user/lib/Drupal/user/Entity/User.php
index 3d1815d..8704991 100644
--- a/core/modules/user/lib/Drupal/user/Entity/User.php
+++ b/core/modules/user/lib/Drupal/user/Entity/User.php
@@ -19,6 +19,7 @@
  *   id = "user",
  *   label = @Translation("User"),
  *   controllers = {
+ *     "operations" = "\Drupal\Core\Entity\EntityOperationsProvider",
  *     "storage" = "Drupal\user\UserStorageController",
  *     "access" = "Drupal\user\UserAccessController",
  *     "list" = "Drupal\user\Controller\UserListController",
diff --git a/core/modules/user/lib/Drupal/user/RoleListController.php b/core/modules/user/lib/Drupal/user/RoleListController.php
index 4aa4f52..51e7f08 100644
--- a/core/modules/user/lib/Drupal/user/RoleListController.php
+++ b/core/modules/user/lib/Drupal/user/RoleListController.php
@@ -41,24 +41,6 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-
-    $operations['permissions'] = array(
-      'title' => t('Edit permissions'),
-      'href' => 'admin/people/permissions/' . $entity->id(),
-      'weight' => 20,
-    );
-    // Built-in roles could not be deleted or disabled.
-    if (in_array($entity->id(), array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
-      unset($operations['delete']);
-    }
-    return $operations;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function submitForm(array &$form, array &$form_state) {
     parent::submitForm($form, $form_state);
 
diff --git a/core/modules/user/lib/Drupal/user/RoleOperationsProvider.php b/core/modules/user/lib/Drupal/user/RoleOperationsProvider.php
new file mode 100644
index 0000000..8bcca57
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/RoleOperationsProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\RoleOperationsProvider.
+ */
+
+namespace Drupal\user;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityOperationsProvider;
+
+/**
+ * Defines a entity operations provider for user_role entities.
+ */
+class RoleOperationsProvider extends EntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $operations['permissions'] = array(
+      'title' => $this->t('Edit permissions'),
+      'href' => 'admin/people/permissions/' . $entity->id(),
+      'weight' => 20,
+    );
+
+    return $operations;
+  }
+}
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
index ef88707..84ceecc 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
@@ -12,8 +12,10 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityOperationsProviderInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -35,9 +37,11 @@ public static function createInstance(ContainerInterface $container, $entity_typ
     return new static(
       $entity_type,
       $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getOperationsProvider($entity_type),
       $entity_info,
       $container->get('plugin.manager.views.display'),
-      $container->get('module_handler')
+      $container->get('module_handler'),
+      $container->get('current_user')
     );
   }
 
@@ -48,15 +52,19 @@ public static function createInstance(ContainerInterface $container, $entity_typ
    *   The type of entity to be listed.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage.
    *   The entity storage controller class.
+   * @param \Drupal\Core\Entity\EntityOperationsProviderInterface $operations
+   *   The entity operations provider.
    * @param array $entity_info
    *   An array of entity info for this entity type.
    * @param \Drupal\Component\Plugin\PluginManagerInterface $display_manager
    *   The views display plugin manager to use.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
+   * @param |Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    */
-  public function __construct($entity_type, EntityStorageControllerInterface $storage, $entity_info, PluginManagerInterface $display_manager, ModuleHandlerInterface $module_handler) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct($entity_type, EntityStorageControllerInterface $storage, EntityOperationsProviderInterface $operations, $entity_info, PluginManagerInterface $display_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
+    parent::__construct($entity_type, $entity_info, $storage, $operations, $module_handler, $current_user);
 
     $this->displayManager = $display_manager;
   }
@@ -140,31 +148,6 @@ public function buildHeader() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
-    $uri = $entity->uri();
-
-    $operations['clone'] = array(
-      'title' => $this->t('Clone'),
-      'href' => $uri['path'] . '/clone',
-      'options' => $uri['options'],
-      'weight' => 15,
-    );
-
-    // Add AJAX functionality to enable/disable operations.
-    foreach (array('enable', 'disable') as $op) {
-      if (isset($operations[$op])) {
-        $operations[$op]['ajax'] = TRUE;
-        $operations[$op]['query']['token'] = drupal_get_token($op);
-      }
-    }
-
-    return $operations;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function buildOperations(EntityInterface $entity) {
     $build = parent::buildOperations($entity);
 
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewOperationsProvider.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewOperationsProvider.php
new file mode 100644
index 0000000..da33d71
--- /dev/null
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewOperationsProvider.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views_ui\ViewOperationsProvider.
+ */
+
+namespace Drupal\views_ui;
+
+use Drupal\Core\Config\Entity\ConfigEntityOperationsProvider;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines a entity operations provider for view entities.
+ */
+class ViewOperationsProvider extends ConfigEntityOperationsProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $uri = $entity->uri();
+    $operations['clone'] = array(
+      'title' => $this->translationManager->translate('Clone'),
+      'href' => $uri['path'] . '/clone',
+      'options' => $uri['options'],
+      'weight' => 15,
+    );
+    // Add AJAX functionality to enable/disable operations.
+    foreach (array('enable', 'disable') as $operation) {
+      // @todo This if statement can be removed once
+      //   https://drupal.org/node/2129953 is fixed.
+      if (isset($operations[$operation])) {
+        $operations[$operation]['ajax'] = TRUE;
+        $operations[$operation]['query']['token'] = drupal_get_token($operation);
+      }
+    }
+
+    return $operations;
+  }
+
+
+
+}
diff --git a/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php b/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
index b30bb34..4dd1596 100644
--- a/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
+++ b/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
@@ -124,9 +124,21 @@ public function testBuildRowEntityList() {
       ->disableOriginalConstructor()
       ->getMock();
 
+    $operations = $this->getMock('\Drupal\Core\Entity\EntityOperationsProviderInterface');
+    $operations->expects($this->once())
+      ->method('getOperations')
+      ->will($this->returnValue(array(
+        'foo' => array(
+          'href' => 'bar',
+          'title' => 'Baz',
+        ),
+      )));
+
+    $current_user = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
     // Setup a view list controller with a mocked buildOperations method,
     // because t() is called on there.
-    $view_list_controller = $this->getMock('Drupal\views_ui\ViewListController', array('buildOperations'), array('view', $storage_controller, $entity_info, $display_manager, $module_handler));
+    $view_list_controller = $this->getMock('Drupal\views_ui\ViewListController', array('buildOperations'), array('view', $storage_controller, $operations, $entity_info, $display_manager, $module_handler, $current_user));
     $view_list_controller->expects($this->any())
       ->method('buildOperations')
       ->will($this->returnValue(array()));
diff --git a/core/modules/views_ui/tests/Drupal/views_ui/ViewOperationsProviderUnitTest.php b/core/modules/views_ui/tests/Drupal/views_ui/ViewOperationsProviderUnitTest.php
new file mode 100644
index 0000000..54a0891
--- /dev/null
+++ b/core/modules/views_ui/tests/Drupal/views_ui/ViewOperationsProviderUnitTest.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views_ui\Tests\ViewOperationsProviderUnitTest.
+ */
+
+namespace Drupal\views_ui\Tests;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\views_ui\ViewOperationsProvider;
+
+
+/**
+ * Tests \Drupal\views_ui\ViewOperationsProvider.
+ */
+class ViewOperationsProviderUnitTest extends UnitTestCase {
+
+  /**
+   * The module handler used for testing.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The current user used for testing.
+   *
+   * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $currentUser;
+
+  /**
+   * The translation manager used for testing.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $translationManager;
+
+  /**
+   * The operations provider under test.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityOperationsProvider
+   */
+  protected $operationsProvider;
+
+  /**
+   * The entity used for testing.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'group' => 'Views UI',
+      'name' => '\Drupal\views_ui\ViewOperationsProvider',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->moduleHandler->expects($this->exactly(2))
+      ->method('invokeAll')
+      ->will($this->returnValue(array()));
+
+    $this->currentUser = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $this->translationManager = $this->getMockBuilder('\Drupal\Core\StringTranslation\TranslationManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->entity = $this->getMock('\Drupal\Core\Config\Entity\ConfigEntityInterface');
+    $this->entity->expects($this->any())
+      ->method('access')
+      ->will($this->returnValue(TRUE));
+
+    $this->operationsProvider = new ViewOperationsProvider($this->moduleHandler, $this->translationManager);
+  }
+
+  /**
+   * Tests getOperations().
+   */
+  public function testGetOperations() {
+    $operations = $this->operationsProvider->getOperations($this->entity, $this->currentUser);
+    $this->assertInternalType('array', $operations);
+    $this->assertArrayHasKey('update', $operations);
+    $this->assertInternalType('array', $operations['update']);
+    $this->assertArrayHasKey('delete', $operations);
+    $this->assertInternalType('array', $operations['delete']);
+    $this->assertArrayHasKey('clone', $operations);
+    $this->assertInternalType('array', $operations['clone']);
+  }
+}
diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module
index c6a6954..e1c0c57 100644
--- a/core/modules/views_ui/views_ui.module
+++ b/core/modules/views_ui/views_ui.module
@@ -40,6 +40,7 @@ function views_ui_menu() {
  */
 function views_ui_entity_info(&$entity_info) {
   $entity_info['view']['controllers'] += array(
+    'operations' => 'Drupal\views_ui\ViewOperationsProvider',
     'list' => 'Drupal\views_ui\ViewListController',
     'form' => array(
       'edit' => 'Drupal\views_ui\ViewEditFormController',
diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityOperationsProviderUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityOperationsProviderUnitTest.php
new file mode 100644
index 0000000..52ce23d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityOperationsProviderUnitTest.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Config\ConfigEntityOperationsProviderUnitTest.
+ */
+
+namespace Drupal\Tests\Core\Config\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityOperationsProvider;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests \Drupal\Core\Config\Entity\ConfigEntityOperationsProvider.
+ */
+class ConfigEntityOperationsProviderUnitTest extends UnitTestCase {
+
+  /**
+   * The module handler used for testing.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The current user used for testing.
+   *
+   * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $currentUser;
+
+  /**
+   * The translation manager used for testing.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $translationManager;
+
+  /**
+   * The operations provider under test.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityOperationsProvider
+   */
+  protected $operationsProvider;
+
+  /**
+   * The entity used for testing.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'group' => 'Configuration',
+      'name' => '\Drupal\Core\Config\Entity\ConfigEntityOperationsProvider',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->moduleHandler->expects($this->any())
+      ->method('invokeAll')
+      ->will($this->returnValue(array()));
+
+    $this->currentUser = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $this->translationManager = $this->getMockBuilder('\Drupal\Core\StringTranslation\TranslationManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->entity = $this->getMock('\Drupal\Core\Config\Entity\ConfigEntityInterface');
+    $this->entity->expects($this->any())
+      ->method('access')
+      ->will($this->returnValue(TRUE));
+    $this->entity->expects($this->any())
+      ->method('entityInfo')
+      ->will($this->returnValue(array(
+        'entity_keys' => array(
+          'status' => 'foo',
+        ),
+      )));
+
+    $this->operationsProvider = new ConfigEntityOperationsProvider($this->moduleHandler, $this->translationManager);
+  }
+
+  /**
+   * Tests getOperations().
+   */
+  public function testGetOperations() {
+    $this->entity->expects($this->at(3))
+      ->method('status')
+      ->will($this->returnValue(TRUE));
+
+    $operations = $this->operationsProvider->getOperations($this->entity, $this->currentUser);
+    $this->assertInternalType('array', $operations);
+    $this->assertArrayHasKey('disable', $operations);
+    $this->assertInternalType('array', $operations['disable']);
+
+    $operations = $this->operationsProvider->getOperations($this->entity, $this->currentUser);
+    $this->assertInternalType('array', $operations);
+    $this->assertArrayHasKey('enable', $operations);
+    $this->assertInternalType('array', $operations['enable']);
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
index c8b07d6..d5841a2 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
@@ -74,11 +74,23 @@ protected function setUp() {
       ->disableOriginalConstructor()
       ->getMock();
 
+    $operations = $this->getMock('\Drupal\Core\Entity\EntityOperationsProviderInterface');
+    $operations->expects($this->once())
+      ->method('getOperations')
+      ->will($this->returnValue(array(
+        'foo' => array(
+          'href' => 'bar',
+          'title' => 'Baz',
+        ),
+      )));
+
+    $current_user = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
       ->disableOriginalConstructor()
       ->getMock();
 
-    $this->entityListController = $this->getMock('Drupal\entity_test\EntityTestListController', array('buildOperations'), array('user_role', static::$entityInfo, $role_storage_controller, $module_handler));
+    $this->entityListController = $this->getMock('Drupal\entity_test\EntityTestListController', array('buildOperations'), array('user_role', static::$entityInfo, $role_storage_controller, $operations, $module_handler, $current_user));
 
     $this->entityListController->expects($this->any())
       ->method('buildOperations')
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityOperationsProviderUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityOperationsProviderUnitTest.php
new file mode 100644
index 0000000..3cafd24
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityOperationsProviderUnitTest.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\EntityOperationsProviderUnitTest.
+ */
+
+namespace Drupal\Tests\Core\Entity;
+
+use Drupal\Core\Entity\EntityOperationsProvider;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests \Drupal\Core\Entity\EntityOperationsProvider.
+ */
+class EntityOperationsProviderUnitTest extends UnitTestCase {
+
+  /**
+   * The module handler used for testing.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The current user used for testing.
+   *
+   * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $currentUser;
+
+  /**
+   * The translation manager used for testing.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $translationManager;
+
+  /**
+   * The operations provider under test.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityOperationsProvider
+   */
+  protected $operationsProvider;
+
+  /**
+   * The entity used for testing.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'group' => 'Entity',
+      'name' => '\Drupal\Core\Entity\EntityOperationsProvider',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->moduleHandler->expects($this->exactly(2))
+      ->method('invokeAll')
+      ->will($this->returnValue(array()));
+
+    $this->currentUser = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $this->translationManager = $this->getMockBuilder('\Drupal\Core\StringTranslation\TranslationManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->entity = $this->getMock('\Drupal\Core\Entity\EntityInterface');
+    $this->entity->expects($this->any())
+      ->method('access')
+      ->will($this->returnValue(TRUE));
+
+    $this->operationsProvider = new EntityOperationsProvider($this->moduleHandler, $this->translationManager);
+  }
+
+  /**
+   * Tests getOperations().
+   */
+  public function testGetOperations() {
+    $operations = $this->operationsProvider->getOperations($this->entity, $this->currentUser);
+    $this->assertInternalType('array', $operations);
+    $this->assertArrayHasKey('update', $operations);
+    $this->assertInternalType('array', $operations['update']);
+    $this->assertArrayHasKey('delete', $operations);
+    $this->assertInternalType('array', $operations['delete']);
+  }
+}
