diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index dfb61e1..eeae8b2 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2468,6 +2468,10 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
     // Register the KeyValueStore factory.
     $container
       ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory');
+
+    // @todo Module bundles are not properly registered in web tests.
+    // @see http://drupal.org/node/1786990
+    $container->register('plugin.manager.system.plugin_ui', 'Drupal\system\Plugin\Type\PluginUIManager');
   }
   return $container;
 }
diff --git a/core/lib/Drupal/Core/Annotation/Plugin.php b/core/lib/Drupal/Core/Annotation/Plugin.php
index cb1ed60..d1a03c8 100644
--- a/core/lib/Drupal/Core/Annotation/Plugin.php
+++ b/core/lib/Drupal/Core/Annotation/Plugin.php
@@ -36,14 +36,22 @@ class Plugin implements AnnotationInterface {
    * classed annotations that were used.
    */
   public function __construct($values) {
+    $this->definition = $this->parse($values);
+  }
+
+  public function parse($values) {
     foreach ($values as $key => $value) {
       if ($value instanceof AnnotationInterface) {
-        $this->definition[$key] = $value->get();
+        $definitions[$key] = $value->get();
+      }
+      elseif (is_array($value)) {
+        $definitions[$key] = $this->parse($value);
       }
       else {
-        $this->definition[$key] = $value;
+        $definitions[$key] = $value;
       }
     }
+    return $definitions;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Plugin/Mapper/ConfigMapper.php b/core/lib/Drupal/Core/Plugin/Mapper/ConfigMapper.php
new file mode 100644
index 0000000..87da30b
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Mapper/ConfigMapper.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Core\Plugin\Mapper;
+
+use Drupal\Component\Plugin\Mapper\MapperInterface;
+
+class ConfigMapper implements MapperInterface {
+  protected $manager;
+
+  public function __construct($manager) {
+    $this->manager = $manager;
+  }
+
+  /**
+   * Implements MapperInterface::getInstance().
+   */
+  public function getInstance(array $options) {
+    $config = config($options['config']);
+    if ($config) {
+      $plugin_id = $config->get('id');
+      $settings = $config->get();
+      $settings['config_id'] = $options['config'];
+      return $this->manager->createInstance($plugin_id, $settings);
+    }
+    // @Todo throw an exception.
+  }
+}
\ No newline at end of file
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 047026a..ceecd3f 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -347,95 +347,6 @@ function aggregator_queue_info() {
 }
 
 /**
- * Implements hook_block_info().
- */
-function aggregator_block_info() {
-  $blocks = array();
-  $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title');
-  foreach ($result as $category) {
-    $blocks['category-' . $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title));
-  }
-  $result = db_query('SELECT fid, title FROM {aggregator_feed} WHERE block <> 0 ORDER BY fid');
-  foreach ($result as $feed) {
-    $blocks['feed-' . $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title));
-  }
-  return $blocks;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function aggregator_block_configure($delta = '') {
-  list($type, $id) = explode('-', $delta);
-  if ($type == 'category') {
-    $value = db_query('SELECT block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchField();
-    $form['block'] = array(
-      '#type' => 'select',
-      '#title' => t('Number of news items in block'),
-      '#default_value' => $value,
-      '#options' => drupal_map_assoc(range(2, 20)),
-    );
-    return $form;
-  }
-}
-
-/**
- * Implements hook_block_save().
- */
-function aggregator_block_save($delta = '', $edit = array()) {
-  list($type, $id) = explode('-', $delta);
-  if ($type == 'category') {
-    db_update('aggregator_category')
-      ->fields(array('block' => $edit['block']))
-      ->condition('cid', $id)
-      ->execute();
-  }
-}
-
-/**
- * Implements hook_block_view().
- *
- * Generates blocks for the latest news items in each category and feed.
- */
-function aggregator_block_view($delta = '') {
-  if (user_access('access news feeds')) {
-    $block = array();
-    list($type, $id) = explode('-', $delta);
-    $result = FALSE;
-    switch ($type) {
-      case 'feed':
-        if ($feed = db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE block <> 0 AND fid = :fid', array(':fid' => $id))->fetchObject()) {
-          $block['subject'] = check_plain($feed->title);
-          $result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", 0, $feed->block, array(':fid' => $id));
-          $read_more = theme('more_link', array('url' => 'aggregator/sources/' . $feed->fid, 'title' => t("View this feed's recent news.")));
-        }
-        break;
-
-      case 'category':
-        if ($category = db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchObject()) {
-          $block['subject'] = check_plain($category->title);
-          $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = :cid ORDER BY i.timestamp DESC, i.iid DESC', 0, $category->block, array(':cid' => $category->cid));
-          $read_more = theme('more_link', array('url' => 'aggregator/categories/' . $category->cid, 'title' => t("View this category's recent news.")));
-        }
-        break;
-    }
-
-    $items = array();
-    if (!empty($result)) {
-      foreach ($result as $item) {
-        $items[] = theme('aggregator_block_item', array('item' => $item));
-      }
-    }
-
-    // Only display the block if there are items to show.
-    if (count($items) > 0) {
-      $block['content'] = theme('item_list', array('items' => $items)) . $read_more;
-    }
-    return $block;
-  }
-}
-
-/**
  * Adds/edits/deletes aggregator categories.
  *
  * @param $edit
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Derivative/CategoryBlock.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Derivative/CategoryBlock.php
new file mode 100644
index 0000000..1a128c9
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Derivative/CategoryBlock.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\aggregator\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+class CategoryBlock implements DerivativeInterface {
+  protected $derivatives = array();
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    // TODO. This check is here for simpletest. See
+    // http://drupal.org/node/1821658
+    if (!module_exists('aggregator')) {
+      return array();
+    }
+    if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+    $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title WHERE cid = :cid', array(':cid' => $derivative_id))->fetchObject();
+    $this->derivatives[$derivative_id] = $base_plugin_definition;
+    $this->derivatives[$derivative_id]['delta'] = $result->cid;
+    $this->derivatives[$derivative_id]['subject'] = t('@title category latest items', array('@title' => $result->title));
+    return $this->derivatives[$derivative_id];
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    // TODO. This check is here for simpletest. See
+    // http://drupal.org/node/1821658
+    if (!module_exists('aggregator')) {
+      return array();
+    }
+    $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title');
+    foreach ($result as $category) {
+      $this->derivatives[$category->cid] = $base_plugin_definition;
+      $this->derivatives[$category->cid]['delta'] = $category->cid;
+      $this->derivatives[$category->cid]['subject'] = t('@title category latest items', array('@title' => $category->title));
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Derivative/FeedBlock.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Derivative/FeedBlock.php
new file mode 100644
index 0000000..23fa03e
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Derivative/FeedBlock.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\aggregator\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+class FeedBlock implements DerivativeInterface {
+  protected $derivatives = array();
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    // TODO. This check is here for simpletest. See
+    // http://drupal.org/node/1821658
+    if (!module_exists('aggregator')) {
+      return array();
+    }
+    if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+    $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title WHERE cid = :cid', array(':cid' => $derivative_id))->fetchObject();
+    $this->derivatives[$derivative_id] = $base_plugin_definition;
+    $this->derivatives[$derivative_id]['delta'] = $result->cid;
+    $this->derivatives[$derivative_id]['subject'] = t('@title feed latest items', array('@title' => $result->title));
+    return $this->derivatives[$derivative_id];
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    // TODO. This check is here for simpletest. See
+    // http://drupal.org/node/1821658
+    if (!module_exists('aggregator')) {
+      return array();
+    }
+    $result = db_query('SELECT fid, title FROM {aggregator_feed} WHERE block <> 0 ORDER BY fid');
+    foreach ($result as $feed) {
+      $this->derivatives[$feed->fid] = $base_plugin_definition;
+      $this->derivatives[$feed->fid]['delta'] = $feed->fid;
+      $this->derivatives[$feed->fid]['subject'] = t('@title feed latest items', array('@title' => $feed->title));
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/block/block/CategoryBlock.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/block/block/CategoryBlock.php
new file mode 100644
index 0000000..c7cd27e
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/block/block/CategoryBlock.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\aggregator\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "aggregator_category_block",
+ *   subject = @Translation("Aggregator category"),
+ *   module = "aggregator",
+ *   derivative = "Drupal\aggregator\Plugin\Derivative\CategoryBlock"
+ * )
+ */
+class CategoryBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'block_count' => 10,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access news feeds');
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $form['block_count'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of news items in block'),
+      '#default_value' => $this->configuration['block_count'],
+      '#options' => drupal_map_assoc(range(2, 20)),
+    );
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['block_count'] = $form_state['values']['block_count'];
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    $id = $this->getPluginId();
+    if ($category = db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchObject()) {
+      $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = :cid ORDER BY i.timestamp DESC, i.iid DESC', 0, $this->configuration['block_count'], array(':cid' => $category->cid));
+      $read_more = theme('more_link', array('url' => 'aggregator/categories/' . $category->cid, 'title' => t("View this category's recent news.")));
+
+      $items = array();
+      foreach ($result as $item) {
+        $items[] = theme('aggregator_block_item', array('item' => $item));
+      }
+
+      // Only display the block if there are items to show.
+      if (count($items) > 0) {
+        return array(
+          '#children' => theme('item_list', array('items' => $items)) . $read_more,
+        );
+      }
+      return array();
+    }
+  }
+}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/block/block/FeedBlock.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/block/block/FeedBlock.php
new file mode 100644
index 0000000..a2a0742
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/block/block/FeedBlock.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\aggregator\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "aggregator_feed_block",
+ *   subject = @Translation("Aggregator feed"),
+ *   module = "aggregator",
+ *   derivative = "Drupal\aggregator\Plugin\Derivative\FeedBlock"
+ * )
+ */
+class FeedBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'block_count' => 10,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access news feeds');
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $form['block_count'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of news items in block'),
+      '#default_value' => $this->configuration['block_count'],
+      '#options' => drupal_map_assoc(range(2, 20)),
+    );
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['block_count'] = $form_state['values']['block_count'];
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    // Plugin ids look something like this: aggregator_feed_block:1.
+    $id = substr($this->getPluginId(), 22);
+    if ($feed = db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE block <> 0 AND fid = :fid', array(':fid' => $id))->fetchObject()) {
+      $result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", 0, $this->configuration['block_count'], array(':fid' => $id));
+      $read_more = theme('more_link', array('url' => 'aggregator/sources/' . $feed->fid, 'title' => t("View this feed's recent news.")));
+
+      $items = array();
+      foreach ($result as $item) {
+        $items[] = theme('aggregator_block_item', array('item' => $item));
+      }
+      // Only display the block if there are items to show.
+      if (count($items) > 0) {
+        return array(
+          '#children' => theme('item_list', array('items' => $items)) . $read_more,
+        );
+      }
+    }
+  }
+}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php
index 7f09317..60e999e 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php
@@ -27,7 +27,6 @@ public function testBlockLinks() {
     $feed = $this->createFeed();
     $this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
 
-    // Place block on page (@see block.test:moveBlockToRegion())
     // Need admin user to be able to access block admin.
     $this->admin_user = $this->drupalCreateUser(array(
       'administer blocks',
@@ -37,21 +36,17 @@ public function testBlockLinks() {
     ));
     $this->drupalLogin($this->admin_user);
 
-    // Prepare to use the block admin form.
+    $current_theme = variable_get('default_theme', 'stark');
+    $machine_name = 'test_aggregator_feed_block';
     $block = array(
-      'module' => 'aggregator',
-      'delta' => 'feed-' . $feed->fid,
-      'title' => $feed->title,
+      'machine_name' => $machine_name,
+      'region' => 'footer',
+      'title' => 'feed-' . $feed->title,
+      'block_count' => 2,
     );
-    $region = 'footer';
-    $edit = array();
-    $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region;
-    // Check the feed block is available in the block list form.
-    $this->drupalGet('admin/structure/block');
-    $this->assertFieldByName('blocks[' . $block['module'] . '_' . $block['delta'] . '][region]', '', 'Aggregator feed block is available for positioning.');
-    // Position it.
-    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
-    $this->assertText(t('The block settings have been updated.'), format_string('Block successfully moved to %region_name region.', array( '%region_name' => $region)));
+    $this->drupalPost("admin/structure/block/manage/aggregator_feed_block:{$feed->fid}/$current_theme", $block, t('Save block'));
+    $this->assertText(t('The block configuration has been saved.'), 'Block was saved.');
+
     // Confirm that the block is now being displayed on pages.
     $this->drupalGet('node');
     $this->assertText(t($block['title']), 'Feed block is displayed on the page.');
diff --git a/core/modules/block/block.admin.inc b/core/modules/block/block.admin.inc
index 2a17a55..581866f 100644
--- a/core/modules/block/block.admin.inc
+++ b/core/modules/block/block.admin.inc
@@ -114,18 +114,15 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme, $block_r
   $form['blocks'] = array();
   $form['#tree'] = TRUE;
 
-  foreach ($blocks as $i => $block) {
-    $key = $block['module'] . '_' . $block['delta'];
-    $form['blocks'][$key]['module'] = array(
+  foreach ($blocks as $key => $instance) {
+    $block = $instance->getConfig();
+    $form['blocks'][$key]['config_id'] = array(
       '#type' => 'value',
-      '#value' => $block['module'],
-    );
-    $form['blocks'][$key]['delta'] = array(
-      '#type' => 'value',
-      '#value' => $block['delta'],
+      '#value' => $block['config_id'],
     );
+    $info = $instance->getDefinition();
     $form['blocks'][$key]['info'] = array(
-      '#markup' => check_plain($block['info']),
+      '#markup' => check_plain($info['subject']),
     );
     $form['blocks'][$key]['theme'] = array(
       '#type' => 'hidden',
@@ -136,26 +133,24 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme, $block_r
       '#default_value' => $block['weight'],
       '#delta' => $weight_delta,
       '#title_display' => 'invisible',
-      '#title' => t('Weight for @block block', array('@block' => $block['info'])),
+      '#title' => t('Weight for @block block', array('@block' => $info['subject'])),
     );
     $form['blocks'][$key]['region'] = array(
       '#type' => 'select',
       '#default_value' => $block['region'] != BLOCK_REGION_NONE ? $block['region'] : NULL,
       '#empty_value' => BLOCK_REGION_NONE,
       '#title_display' => 'invisible',
-      '#title' => t('Region for @block block', array('@block' => $block['info'])),
+      '#title' => t('Region for @block block', array('@block' => $info['subject'])),
       '#options' => $block_regions,
     );
     $links['configure'] = array(
       'title' => t('configure'),
-      'href' => 'admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure',
+      'href' => 'admin/structure/block/manage/' . $block['config_id'] . '/' . $theme . '/configure',
+    );
+    $links['delete'] = array(
+      'title' => t('delete'),
+      'href' => 'admin/structure/block/manage/' . $block['config_id'] . '/' . $theme . '/delete',
     );
-    if ($block['module'] == 'block') {
-      $links['delete'] = array(
-        'title' => t('delete'),
-        'href' => 'admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/delete',
-     );
-    }
     $form['blocks'][$key]['operations'] = array(
       '#type' => 'operations',
       '#links' => $links,
@@ -184,27 +179,11 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme, $block_r
  * @see block_admin_display_form()
  */
 function block_admin_display_form_submit($form, &$form_state) {
-  $transaction = db_transaction();
-  try {
-    foreach ($form_state['values']['blocks'] as $block) {
-      $block['status'] = (int) ($block['region'] != BLOCK_REGION_NONE);
-      $block['region'] = $block['status'] ? $block['region'] : '';
-      db_update('block')
-        ->fields(array(
-          'status' => $block['status'],
-          'weight' => $block['weight'],
-          'region' => $block['region'],
-        ))
-        ->condition('module', $block['module'])
-        ->condition('delta', $block['delta'])
-        ->condition('theme', $block['theme'])
-        ->execute();
-    }
-  }
-  catch (Exception $e) {
-    $transaction->rollback();
-    watchdog_exception('block', $e);
-    throw $e;
+  foreach ($form_state['values']['blocks'] as $block) {
+    $config = config($block['config_id']);
+    $config->set('weight', $block['weight']);
+    $config->set('region', $block['region']);
+    $config->save();
   }
   drupal_set_message(t('The block settings have been updated.'));
   cache_invalidate(array('content' => TRUE));
@@ -215,8 +194,10 @@ function block_admin_display_form_submit($form, &$form_state) {
  *
  * Callback for usort() in block_admin_display_prepare_blocks().
  */
-function _block_compare($a, $b) {
+function _block_compare($ainstance, $binstance) {
   global $theme_key;
+  $a = $ainstance->getConfig();
+  $b = $binstance->getConfig();
 
   // Theme should be set before calling this function, or the current theme
   // is being used.
@@ -249,7 +230,9 @@ function _block_compare($a, $b) {
     }
   }
   // Sort by title.
-  return strcmp($a['info'], $b['info']);
+  $ainfo = $ainstance->getDefinition();
+  $binfo = $binstance->getDefinition();
+  return strcmp($ainfo['subject'], $binfo['subject']);
 }
 
 /**
@@ -267,248 +250,21 @@ function _block_compare($a, $b) {
  * @see block_admin_configure_submit()
  * @ingroup forms
  */
-function block_admin_configure($form, &$form_state, $module, $delta) {
-  $block = block_load($module, $delta);
-  $form['module'] = array(
-    '#type' => 'value',
-    '#value' => $block->module,
-  );
-  $form['delta'] = array(
-    '#type' => 'value',
-    '#value' => $block->delta,
-  );
-
-  // Get the block subject for the page title.
-  $info = module_invoke($block->module, 'block_info');
-  if (isset($info[$block->delta])) {
-    drupal_set_title(t("'%name' block", array('%name' => $info[$block->delta]['info'])), PASS_THROUGH);
-  }
-
-  $form['settings']['title'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Block title'),
-    '#maxlength' => 255,
-    '#size' => 60,
-    '#description' => $block->module == 'block' ? t('The title of the block as shown to the user.') : t('Override the default title for the block. Use <em>!placeholder</em> to display no title, or leave blank to use the default block title.', array('!placeholder' => '&lt;none&gt;')),
-    '#default_value' => isset($block->title) ? $block->title : '',
-    '#weight' => -19,
-  );
-
-  // Module-specific block configuration.
-  if ($settings = module_invoke($block->module, 'block_configure', $block->delta)) {
-    foreach ($settings as $k => $v) {
-      $form['settings'][$k] = $v;
-    }
-  }
-
-  // Region settings.
-  $form['regions'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Region settings'),
-    '#collapsible' => FALSE,
-    '#description' => t('Specify in which themes and regions this block is displayed.'),
-    '#tree' => TRUE,
-  );
-
-  $theme_default = variable_get('theme_default', 'stark');
-  $admin_theme = variable_get('admin_theme');
-  foreach (list_themes() as $key => $theme) {
-    // Only display enabled themes
-    if ($theme->status) {
-      $region = db_query("SELECT region FROM {block} WHERE module = :module AND delta = :delta AND theme = :theme", array(
-        ':module' => $block->module,
-        ':delta' => $block->delta,
-        ':theme' => $key,
-      ))->fetchField();
-
-      // Use a meaningful title for the main site theme and administrative
-      // theme.
-      $theme_title = $theme->info['name'];
-      if ($key == $theme_default) {
-        $theme_title = t('!theme (default theme)', array('!theme' => $theme_title));
-      }
-      elseif ($admin_theme && $key == $admin_theme) {
-        $theme_title = t('!theme (administration theme)', array('!theme' => $theme_title));
-      }
-      $form['regions'][$key] = array(
-        '#type' => 'select',
-        '#title' => $theme_title,
-        '#default_value' => !empty($region) && $region != -1 ? $region : NULL,
-        '#empty_value' => BLOCK_REGION_NONE,
-        '#options' => system_region_list($key, REGIONS_VISIBLE),
-        '#weight' => ($key == $theme_default ? 9 : 10),
-      );
-    }
+function block_admin_configure($form, &$form_state, $plugin_id, $theme = NULL) {
+  $instance = block_load($plugin_id);
+  $form['#instance'] = $instance;
+  $config = $instance->getConfig();
+  if (!isset($config['config_id']) && !$theme) {
+    $theme = variable_get('theme_default', 'stark');
   }
-
-  // Visibility settings.
-  $form['visibility_title'] = array(
-    '#type' => 'item',
-    '#title' => t('Visibility settings'),
-  );
-  $form['visibility'] = array(
-    '#type' => 'vertical_tabs',
-    '#attached' => array(
-      'library' => array(array('block', 'drupal.block')),
-    ),
-  );
-
-  // Per-path visibility.
-  $form['visibility']['path'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Pages'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#group' => 'visibility',
-    '#weight' => 0,
-  );
-
-  $access = user_access('use PHP for settings');
-  if (isset($block->visibility) && $block->visibility == BLOCK_VISIBILITY_PHP && !$access) {
-    $form['visibility']['path']['visibility'] = array(
-      '#type' => 'value',
-      '#value' => BLOCK_VISIBILITY_PHP,
-    );
-    $form['visibility']['path']['pages'] = array(
-      '#type' => 'value',
-      '#value' => isset($block->pages) ? $block->pages : '',
-    );
-  }
-  else {
-    $options = array(
-      BLOCK_VISIBILITY_NOTLISTED => t('All pages except those listed'),
-      BLOCK_VISIBILITY_LISTED => t('Only the listed pages'),
-    );
-    $description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '<front>'));
-
-    if (module_exists('php') && $access) {
-      $options += array(BLOCK_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'));
-      $title = t('Pages or PHP code');
-      $description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>'));
-    }
-    else {
-      $title = t('Pages');
-    }
-    $form['visibility']['path']['visibility'] = array(
-      '#type' => 'radios',
-      '#title' => t('Show block on specific pages'),
-      '#options' => $options,
-      '#default_value' => isset($block->visibility) ? $block->visibility : BLOCK_VISIBILITY_NOTLISTED,
-    );
-    $form['visibility']['path']['pages'] = array(
-      '#type' => 'textarea',
-      '#title' => '<span class="element-invisible">' . $title . '</span>',
-      '#default_value' => isset($block->pages) ? $block->pages : '',
-      '#description' => $description,
-    );
-  }
-
-  // Configure the block visibility per language.
-  if (module_exists('language') && language_multilingual()) {
-    $configurable_language_types = language_types_get_configurable();
-    $existing_language_settings = db_query("SELECT type, langcode FROM {block_language} WHERE module = :module AND delta = :delta", array(
-      ':module' => $form['module']['#value'],
-      ':delta' => $form['delta']['#value'],
-    ))->fetchAll();
-    $default_langcode_options = array();
-    $default_language_type = $configurable_language_types[0];
-    foreach ($existing_language_settings as $setting) {
-      $default_langcode_options[] = $setting->langcode;
-      // Overwrite default language type if we have it set. Although this
-      // theoretically would allow per language type association, our UI
-      // only allows language type association overall for a block, so we
-      // only need a single value.
-      $default_language_type = $setting->type;
-    }
-
-    // Fetch languages.
-    $languages = language_list(LANGUAGE_ALL);
-    foreach ($languages as $language) {
-      // @TODO $language->name is not wrapped with t(), it should be replaced
-      // by CMI translation implementation.
-      $langcodes_options[$language->langcode] = $language->name;
-    }
-    $form['visibility']['language'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Languages'),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-      '#group' => 'visibility',
-      '#weight' => 5,
-    );
-    // If there are multiple configurable language types, let the user pick
-    // which one should be applied to this visibility setting. This way users
-    // can limit blocks by interface language or content language for exmaple.
-    $language_types = language_types_info();
-    $language_type_options = array();
-    foreach ($configurable_language_types as $type_key) {
-      $language_type_options[$type_key] = $language_types[$type_key]['name'];
-    }
-    $form['visibility']['language']['language_type'] = array(
-      '#type' => 'radios',
-      '#title' => t('Language type'),
-      '#options' => $language_type_options,
-      '#default_value' => $default_language_type,
-      '#access' => count($language_type_options) > 1,
-    );
-    $form['visibility']['language']['langcodes'] = array(
-      '#type' => 'checkboxes',
-      '#title' => t('Show this block only for specific languages'),
-      '#default_value' => $default_langcode_options,
-      '#options' => $langcodes_options,
-      '#description' => t('Show this block only for the selected language(s). If you select no languages, the block will be visibile in all languages.'),
-    );
+  elseif (!$theme && isset($config['config_id'])) {
+    list(, , , $theme) = explode('.', $config['config_id']);
   }
-
-  // Per-role visibility.
-  $default_role_options = db_query("SELECT rid FROM {block_role} WHERE module = :module AND delta = :delta", array(
-    ':module' => $block->module,
-    ':delta' => $block->delta,
-  ))->fetchCol();
-  $role_options = array_map('check_plain', user_roles());
-  $form['visibility']['role'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Roles'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#group' => 'visibility',
-    '#weight' => 10,
-  );
-  $form['visibility']['role']['roles'] = array(
-    '#type' => 'checkboxes',
-    '#title' => t('Show block for specific roles'),
-    '#default_value' => $default_role_options,
-    '#options' => $role_options,
-    '#description' => t('Show this block only for the selected role(s). If you select no roles, the block will be visible to all users.'),
-  );
-
-  // Per-user visibility.
-  $form['visibility']['user'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Users'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#group' => 'visibility',
-    '#weight' => 20,
-  );
-  $form['visibility']['user']['custom'] = array(
-    '#type' => 'radios',
-    '#title' => t('Customizable per user'),
-    '#options' => array(
-      BLOCK_CUSTOM_FIXED => t('Not customizable'),
-      BLOCK_CUSTOM_ENABLED => t('Customizable, visible by default'),
-      BLOCK_CUSTOM_DISABLED => t('Customizable, hidden by default'),
-    ),
-    '#description' => t('Allow individual users to customize the visibility of this block in their account settings.'),
-    '#default_value' => isset($block->custom) ? $block->custom : BLOCK_CUSTOM_FIXED,
-  );
-
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save block'),
+  $form['theme'] = array(
+    '#type' => 'value',
+    '#value' => $theme,
   );
-
+  $form += $instance->configureWrapper($form, $form_state);
   return $form;
 }
 
@@ -519,15 +275,7 @@ function block_admin_configure($form, &$form_state, $module, $delta) {
  * @see block_admin_configure_submit()
  */
 function block_admin_configure_validate($form, &$form_state) {
-  if ($form_state['values']['module'] == 'block') {
-    $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', 0, 1, array(
-      ':bid' => $form_state['values']['delta'],
-      ':info' => $form_state['values']['info'],
-    ))->fetchField();
-    if (empty($form_state['values']['info']) || $custom_block_exists) {
-      form_set_error('info', t('Ensure that each block description is unique.'));
-    }
-  }
+  $form['#instance']->configureValidateWrapper($form, $form_state);
 }
 
 /**
@@ -537,186 +285,16 @@ function block_admin_configure_validate($form, &$form_state) {
  * @see block_admin_configure_validate()
  */
 function block_admin_configure_submit($form, &$form_state) {
-  if (!form_get_errors()) {
-    $transaction = db_transaction();
-    try {
-      db_update('block')
-        ->fields(array(
-          'visibility' => (int) $form_state['values']['visibility'],
-          'pages' => trim($form_state['values']['pages']),
-          'custom' => (int) $form_state['values']['custom'],
-          'title' => $form_state['values']['title'],
-        ))
-        ->condition('module', $form_state['values']['module'])
-        ->condition('delta', $form_state['values']['delta'])
-        ->execute();
-
-      db_delete('block_role')
-        ->condition('module', $form_state['values']['module'])
-        ->condition('delta', $form_state['values']['delta'])
-        ->execute();
-      $query = db_insert('block_role')->fields(array('rid', 'module', 'delta'));
-      foreach (array_filter($form_state['values']['roles']) as $rid) {
-        $query->values(array(
-          'rid' => $rid,
-          'module' => $form_state['values']['module'],
-          'delta' => $form_state['values']['delta'],
-        ));
-      }
-      $query->execute();
-
-      // Store regions per theme for this block.
-      foreach ($form_state['values']['regions'] as $theme => $region) {
-        db_merge('block')
-          ->key(array('theme' => $theme, 'delta' => $form_state['values']['delta'], 'module' => $form_state['values']['module']))
-          ->fields(array(
-            'region' => ($region == BLOCK_REGION_NONE ? '' : $region),
-            'pages' => trim($form_state['values']['pages']),
-            'status' => (int) ($region != BLOCK_REGION_NONE),
-          ))
-          ->execute();
-      }
-
-      // Update the block visibility settings if we have settings to store
-      // for the existing languages.
-      if (module_exists('language') && isset($form_state['values']['langcodes'])) {
-        db_delete('block_language')
-          ->condition('module', $form_state['values']['module'])
-          ->condition('delta', $form_state['values']['delta'])
-          ->execute();
-        $query = db_insert('block_language')->fields(array(
-          'type', 'langcode', 'module', 'delta'
-        ));
-        foreach (array_filter($form_state['values']['langcodes']) as $langcode) {
-          $query->values(array(
-            'type' => $form_state['values']['language_type'],
-            'langcode' => $langcode,
-            'module' => $form_state['values']['module'],
-            'delta' => $form_state['values']['delta'],
-          ));
-        }
-        $query->execute();
-      }
-
-      module_invoke($form_state['values']['module'], 'block_save', $form_state['values']['delta'], $form_state['values']);
-    }
-    catch (Exception $e) {
-      $transaction->rollback();
-      watchdog_exception('block', $e);
-      throw $e;
-    }
-    drupal_set_message(t('The block configuration has been saved.'));
-    cache_invalidate(array('content' => TRUE));
-    $form_state['redirect'] = 'admin/structure/block';
+  $form['#instance']->configureSubmitWrapper($form, $form_state);
+  $config_values = $form['#instance']->getConfig();
+  $machine_name = 'plugin.core.block.' . $form_state['values']['theme'] . '.' . $form_state['values']['machine_name'];
+  $config = config($machine_name);
+  $config->set('id', $form['#instance']->getPluginId());
+  foreach ($config_values as $key => $value) {
+    $config->set($key, $value);
   }
-}
-
-/**
- * Form constructor for the add block form.
- *
- * @see block_menu()
- * @see block_add_block_form_validate()
- * @see block_add_block_form_submit()
- * @ingroup forms
- */
-function block_add_block_form($form, &$form_state) {
-  return block_admin_configure($form, $form_state, 'block', NULL);
-}
-
-/**
- * Form validation handler for block_add_block_form().
- *
- * @see block_add_block_form()
- * @see block_add_block_form_submit()
- */
-function block_add_block_form_validate($form, &$form_state) {
-  $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE info = :info', 0, 1, array(':info' => $form_state['values']['info']))->fetchField();
-
-  if (empty($form_state['values']['info']) || $custom_block_exists) {
-    form_set_error('info', t('Ensure that each block description is unique.'));
-  }
-}
-
-/**
- * Form submission handler for block_add_block_form().
- *
- * Saves the new custom block.
- *
- * @see block_add_block_form()
- * @see block_add_block_form_validate()
- */
-function block_add_block_form_submit($form, &$form_state) {
-  $delta = db_insert('block_custom')
-    ->fields(array(
-      'body' => $form_state['values']['body']['value'],
-      'info' => $form_state['values']['info'],
-      'format' => $form_state['values']['body']['format'],
-    ))
-    ->execute();
-  // Store block delta to allow other modules to work with new block.
-  $form_state['values']['delta'] = $delta;
-
-  $query = db_insert('block')->fields(array('visibility', 'pages', 'custom', 'title', 'module', 'theme', 'status', 'weight', 'delta', 'cache'));
-  foreach (list_themes() as $key => $theme) {
-    if ($theme->status) {
-      $query->values(array(
-        'visibility' => (int) $form_state['values']['visibility'],
-        'pages' => trim($form_state['values']['pages']),
-        'custom' => (int) $form_state['values']['custom'],
-        'title' => $form_state['values']['title'],
-        'module' => $form_state['values']['module'],
-        'theme' => $theme->name,
-        'status' => 0,
-        'weight' => 0,
-        'delta' => $delta,
-        'cache' => DRUPAL_NO_CACHE,
-      ));
-    }
-  }
-  $query->execute();
-
-  $query = db_insert('block_role')->fields(array('rid', 'module', 'delta'));
-  foreach (array_filter($form_state['values']['roles']) as $rid) {
-    $query->values(array(
-      'rid' => $rid,
-      'module' => $form_state['values']['module'],
-      'delta' => $delta,
-    ));
-  }
-  $query->execute();
-
-  // Store regions per theme for this block.
-  foreach ($form_state['values']['regions'] as $theme => $region) {
-    db_merge('block')
-      ->key(array('theme' => $theme, 'delta' => $delta, 'module' => $form_state['values']['module']))
-      ->fields(array(
-        'region' => ($region == BLOCK_REGION_NONE ? '' : $region),
-        'pages' => trim($form_state['values']['pages']),
-        'status' => (int) ($region != BLOCK_REGION_NONE),
-      ))
-      ->execute();
-  }
-
-  // Update the block visibility settings if we have settings to store
-  // for the existing languages.
-  if (module_exists('language') && isset($form_state['values']['langcodes'])) {
-    $query = db_insert('block_language')->fields(array(
-      'type', 'langcode', 'module', 'delta'
-    ));
-    foreach (array_filter($form_state['values']['langcodes']) as $langcode) {
-      $query->values(array(
-        'type' => $form_state['values']['language_type'],
-        'langcode' => $langcode,
-        'module' => $form_state['values']['module'],
-        'delta' => $form_state['values']['delta'],
-      ));
-    }
-    $query->execute();
-  }
-
-  drupal_set_message(t('The block has been created.'));
-  cache_invalidate(array('content' => TRUE));
-  $form_state['redirect'] = 'admin/structure/block';
+  $config->save();
+  $form_state['redirect'] = 'admin/structure/block/list/block_plugin_ui:' . $form_state['values']['theme'];
 }
 
 /**
@@ -729,43 +307,30 @@ function block_add_block_form_submit($form, &$form_state) {
  *   The unique ID of the block within the context of $module.
  *
  * @see block_menu()
- * @see block_custom_block_delete_submit()
+ * @see block_admin_block_delete_submit()
  */
-function block_custom_block_delete($form, &$form_state, $module, $delta) {
-  $block = block_load($module, $delta);
-  $custom_block = block_custom_block_get($block->delta);
-  $form['info'] = array('#type' => 'hidden', '#value' => $custom_block['info'] ? $custom_block['info'] : $custom_block['title']);
-  $form['bid'] = array('#type' => 'hidden', '#value' => $block->delta);
-
-  return confirm_form($form, t('Are you sure you want to delete the block %name?', array('%name' => $custom_block['info'])), 'admin/structure/block', '', t('Delete'), t('Cancel'));
+function block_admin_block_delete($form, &$form_state, $plugin_id, $theme) {
+  $block = block_load($plugin_id);
+  $form['id'] = array('#type' => 'value', '#value' => $plugin_id);
+  $form['theme'] = array('#type' => 'value', '#value' => $theme);
+  $definition = $block->getDefinition();
+  $config = $block->getConfig();
+  $subject = empty($config['subject']) ? $definition['subject'] : $config['subject'];
+  $form['subject'] = array('#type' => 'value', '#value' => $subject);
+
+  return confirm_form($form, t('Are you sure you want to delete the block %name?', array('%name' => $subject)), 'admin/structure/block', '', t('Delete'), t('Cancel'));
 }
 
 /**
- * Form submission handler for block_custom_block_delete().
+ * Form submission handler for block_admin_block_delete().
  *
- * @see block_custom_block_delete()
+ * @see block_admin_block_delete()
  */
-function block_custom_block_delete_submit($form, &$form_state) {
-  db_delete('block_custom')
-    ->condition('bid', $form_state['values']['bid'])
-    ->execute();
-  db_delete('block')
-    ->condition('module', 'block')
-    ->condition('delta', $form_state['values']['bid'])
-    ->execute();
-  db_delete('block_role')
-    ->condition('module', 'block')
-    ->condition('delta', $form_state['values']['bid'])
-    ->execute();
-  db_delete('block_language')
-    ->condition('module', 'block')
-    ->condition('delta', $form_state['values']['bid'])
-    ->execute();
-
-  drupal_set_message(t('The block %name has been removed.', array('%name' => $form_state['values']['info'])));
-  cache_invalidate(array('content' => TRUE));
-  $form_state['redirect'] = 'admin/structure/block';
-  return;
+function block_admin_block_delete_submit($form, &$form_state) {
+  $config = config($form_state['values']['id']);
+  $config->delete();
+  drupal_set_message(t('The block %name has been removed.', array('%name' => $form_state['values']['subject'])));
+  $form_state['redirect'] = 'admin/structure/block/list/' . $form_state['values']['theme'];
 }
 
 /**
diff --git a/core/modules/block/block.install b/core/modules/block/block.install
index ee72e0f..520689b 100644
--- a/core/modules/block/block.install
+++ b/core/modules/block/block.install
@@ -136,42 +136,6 @@ function block_schema() {
     ),
   );
 
-  $schema['block_custom'] = array(
-    'description' => 'Stores contents of custom-made blocks.',
-    'fields' => array(
-      'bid' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => "The block's {block}.bid.",
-      ),
-      'body' => array(
-        'type' => 'text',
-        'not null' => FALSE,
-        'size' => 'big',
-        'description' => 'Block contents.',
-        'translatable' => TRUE,
-      ),
-      'info' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'Block description.',
-      ),
-      'format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => FALSE,
-        'description' => 'The {filter_format}.format of the block body.',
-      ),
-    ),
-    'unique keys' => array(
-      'info' => array('info'),
-    ),
-    'primary key' => array('bid'),
-  );
-
   $schema['block_language'] = array(
     'description' => 'Sets up display criteria for blocks based on langcode',
     'fields' => array(
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 2978146..321e4b9 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -54,16 +54,21 @@ function block_help($path, $arg) {
       $output .= '<dt>' . t('Positioning content') . '</dt>';
       $output .= '<dd>' . t('When working with blocks, remember that all themes do <em>not</em> implement the same regions, or display regions in the same way. Blocks are positioned on a per-theme basis. Users with the <em>Administer blocks</em> permission can disable blocks. Disabled blocks are listed on the <a href="@blocks">Blocks administration page</a>, but are not displayed in any region.', array('@block' => 'http://drupal.org/documentation/modules/block', '@blocks' => url('admin/structure/block'))) . '</dd>';
       $output .= '<dt>' . t('Controlling visibility') . '</dt>';
-      $output .= '<dd>' . t('Blocks can be configured to be visible only on certain pages, only to users of certain roles, or only on pages displaying certain <a href="@content-type">content types</a>. Administrators can also allow specific blocks to be enabled or disabled by users when they edit their <a href="@user">My account</a> page. Some dynamic blocks, such as those generated by modules, will be displayed only on certain pages.', array('@content-type' => url('admin/structure/types'), '@user' => url('user'))) . '</dd>';
-      $output .= '<dt>' . t('Creating custom blocks') . '</dt>';
-      $output .= '<dd>' . t('Users with the <em>Administer blocks</em> permission can <a href="@block-add">add custom blocks</a>, which are then listed on the <a href="@blocks">Blocks administration page</a>. Once created, custom blocks behave just like default and module-generated blocks.', array('@blocks' => url('admin/structure/block'), '@block-add' => url('admin/structure/block/add'))) . '</dd>';
+      $output .= '<dd>' . t('Blocks can be configured to be visible only on certain pages, only to users of certain roles, or only on pages displaying certain <a href="@content-type">content types</a>. Some dynamic blocks, such as those generated by modules, will be displayed only on certain pages.', array('@content-type' => url('admin/structure/types'), '@user' => url('user'))) . '</dd>';
+      if (module_exists('custom_block')) {
+        $output .= '<dt>' . t('Creating custom blocks') . '</dt>';
+        $output .= '<dd>' . t('Users with the <em>Administer blocks</em> permission can <a href="@block-add">add custom blocks</a>, which are then listed on the <a href="@blocks">Blocks administration page</a>. Once created, custom blocks behave just like default and module-generated blocks.', array('@blocks' => url('admin/structure/block'), '@block-add' => url('admin/structure/block/list/block_plugin_ui:' . variable_get('theme_default', 'stark') . '/add/custom_blocks'))) . '</dd>';
+      }
       $output .= '</dl>';
       return $output;
-    case 'admin/structure/block/add':
-      return '<p>' . t('Use this page to create a new custom block.') . '</p>';
   }
-  if ($arg[0] == 'admin' && $arg[1] == 'structure' && $arg['2'] == 'block' && (empty($arg[3]) || $arg[3] == 'list')) {
-    $demo_theme = !empty($arg[4]) ? $arg[4] : variable_get('theme_default', 'stark');
+  if ($arg[0] == 'admin' && $arg[1] == 'structure' && $arg['2'] == 'block' && (empty($arg[3]) || $arg[3] == 'list') && empty($arg[5])) {
+    if (!empty($arg[4])) {
+      list(, $demo_theme) = explode(':', $arg[4]);
+    }
+    else {
+      $demo_theme = variable_get('theme_default', 'stark');
+    }
     $themes = list_themes();
     $output = '<p>' . t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the <em>Save blocks</em> button at the bottom of the page. Click the <em>configure</em> link next to each block to configure its specific title and visibility settings.') . '</p>';
     $output .= '<p>' . l(t('Demonstrate block regions (@theme)', array('@theme' => $themes[$demo_theme]->info['name'])), 'admin/structure/block/demo/' . $demo_theme) . '</p>';
@@ -127,51 +132,40 @@ function block_menu() {
   $items['admin/structure/block/manage/%/%/delete'] = array(
     'title' => 'Delete block',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('block_custom_block_delete', 4, 5),
+    'page arguments' => array('block_admin_block_delete', 4, 5),
     'access arguments' => array('administer blocks'),
     'type' => MENU_LOCAL_TASK,
     'context' => MENU_CONTEXT_NONE,
     'file' => 'block.admin.inc',
   );
-  $items['admin/structure/block/add'] = array(
-    'title' => 'Add block',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('block_add_block_form'),
-    'access arguments' => array('administer blocks'),
-    'type' => MENU_LOCAL_ACTION,
-    'file' => 'block.admin.inc',
-  );
-  foreach (list_themes() as $key => $theme) {
-    $items['admin/structure/block/list/' . $key] = array(
-      'title' => check_plain($theme->info['name']),
-      'page arguments' => array($key),
-      'type' => $key == $default_theme ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
-      'weight' => $key == $default_theme ? -10 : 0,
-      'access callback' => '_block_themes_access',
-      'access arguments' => array($key),
-      'file' => 'block.admin.inc',
-    );
-    if ($key != $default_theme) {
-      $items['admin/structure/block/list/' . $key . '/add'] = array(
-        'title' => 'Add block',
-        'page callback' => 'drupal_get_form',
-        'page arguments' => array('block_add_block_form'),
-        'access arguments' => array('administer blocks'),
-        'type' => MENU_LOCAL_ACTION,
+  // Block administration is actually tied to theme and plugin definition so
+  // that the plugin can appropriately attach to this url structure.
+  $themes = list_themes();
+  foreach (system_plugin_manager('plugin_ui')->getDefinitions() as $plugin_id => $plugin) {
+    list($plugin_base, $key) = explode(':', $plugin_id);
+    if ($plugin_base == 'block_plugin_ui') {
+      $theme = $themes[$key];
+      $items['admin/structure/block/list/' . $plugin_id] = array(
+        'title' => check_plain($theme->info['name']),
+        'page arguments' => array($key),
+        'type' => $key == $default_theme ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
+        'weight' => $key == $default_theme ? -10 : 0,
+        'access callback' => '_block_themes_access',
+        'access arguments' => array($key),
+        'file' => 'block.admin.inc',
+      );
+      $items['admin/structure/block/demo/' . $key] = array(
+        'title' => check_plain($theme->info['name']),
+        'page callback' => 'block_admin_demo',
+        'page arguments' => array($key),
+        'type' => MENU_CALLBACK,
+        'access callback' => '_block_themes_access',
+        'access arguments' => array($key),
+        'theme callback' => '_block_custom_theme',
+        'theme arguments' => array($key),
         'file' => 'block.admin.inc',
       );
     }
-    $items['admin/structure/block/demo/' . $key] = array(
-      'title' => check_plain($theme->info['name']),
-      'page callback' => 'block_admin_demo',
-      'page arguments' => array($key),
-      'type' => MENU_CALLBACK,
-      'access callback' => '_block_themes_access',
-      'access arguments' => array($key),
-      'theme callback' => '_block_custom_theme',
-      'theme arguments' => array($key),
-      'file' => 'block.admin.inc',
-    );
   }
   return $items;
 }
@@ -212,53 +206,6 @@ function _block_custom_theme($theme = NULL) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function block_block_info() {
-  $blocks = array();
-
-  $result = db_query('SELECT bid, info FROM {block_custom} ORDER BY info');
-  foreach ($result as $block) {
-    $blocks[$block->bid]['info'] = $block->info;
-    // Not worth caching.
-    $blocks[$block->bid]['cache'] = DRUPAL_NO_CACHE;
-  }
-  return $blocks;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function block_block_configure($delta = 0) {
-  if ($delta) {
-    $custom_block = block_custom_block_get($delta);
-  }
-  else {
-    $custom_block = array();
-  }
-  return block_custom_block_form($custom_block);
-}
-
-/**
- * Implements hook_block_save().
- */
-function block_block_save($delta = 0, $edit = array()) {
-  block_custom_block_save($edit, $delta);
-}
-
-/**
- * Implements hook_block_view().
- *
- * Generates the administrator-defined blocks for display.
- */
-function block_block_view($delta = '') {
-  $block = db_query('SELECT body, format FROM {block_custom} WHERE bid = :bid', array(':bid' => $delta))->fetchObject();
-  $data['subject'] = NULL;
-  $data['content'] = check_markup($block->body, $block->format, '', TRUE);
-  return $data;
-}
-
-/**
  * Implements hook_page_build().
  *
  * Renders blocks into their regions.
@@ -343,7 +290,6 @@ function block_get_blocks_by_region($region) {
  *   A renderable array.
  */
 function _block_get_renderable_region($list = array()) {
-  $weight = 0;
   $build = array();
   // Block caching is not compatible with node_access modules. We also
   // preserve the submission of forms in blocks, by fetching from cache
@@ -356,23 +302,27 @@ function _block_get_renderable_region($list = array()) {
     !in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD'));
 
   foreach ($list as $key => $block) {
+    $config = $block->getConfig();
+    $definition = $block->getDefinition();
     $build[$key] = array(
       '#block' => $block,
-      '#weight' => ++$weight,
+      '#weight' => (int) $config['weight'],
       '#theme_wrappers' => array('block'),
     );
 
-    if ($not_cacheable || in_array($block->cache, array(DRUPAL_NO_CACHE, DRUPAL_CACHE_CUSTOM))) {
+    if ($not_cacheable || in_array($config['cache'], array(DRUPAL_NO_CACHE, DRUPAL_CACHE_CUSTOM))) {
       // Non-cached blocks get built immediately. Provides more content
       // that can be easily manipulated during hook_page_alter().
       $build[$key] = _block_get_renderable_block($build[$key]);
     }
     else {
+      $key_components = explode('.', $key);
+      $id = array_pop($key_components);
       $build[$key] += array(
         '#pre_render' => array('_block_get_renderable_block'),
         '#cache' => array(
-          'keys' => array($block->module, $block->delta),
-          'granularity' => $block->cache,
+          'keys' => array($id, $config['module']),
+          'granularity' => $config['cache'],
           'bin' => 'block',
           'tags' => array('content' => TRUE),
         ),
@@ -384,16 +334,16 @@ function _block_get_renderable_region($list = array()) {
     // skip the help block, since we assume that most users do not need or want
     // to perform contextual actions on the help block, and the links needlessly
     // draw attention on it.
-    if ($key != 'system_main' && $key != 'system_help') {
-      $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($block->module, $block->delta));
+    if ($definition['class'] != 'Drupal\\system\\Plugin\\block\\block\\SystemHelpBlock' && $definition['class'] != 'Drupal\\system\\Plugin\\block\\block\\SystemMainBlock') {
+      global $theme;
+      $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($key, $theme));
     }
   }
-  $build['#sorted'] = TRUE;
   return $build;
 }
 
 /**
- * Updates the 'block' DB table with the blocks currently exported by modules.
+ * Returns an array of block class instances by theme.
  *
  * @param $theme
  *   The theme to rehash blocks for. If not provided, defaults to the currently
@@ -403,251 +353,17 @@ function _block_get_renderable_region($list = array()) {
  *   Blocks currently exported by modules.
  */
 function _block_rehash($theme = NULL) {
-  global $theme_key;
-
-  drupal_theme_initialize();
-  if (!isset($theme)) {
-    // If theme is not specifically set, rehash for the current theme.
-    $theme = $theme_key;
-  }
-  $regions = system_region_list($theme);
-
-  // These are the blocks the function will return.
   $blocks = array();
-  // These are the blocks defined by code and modified by the database.
-  $current_blocks = array();
-  // These are {block}.bid values to be kept.
-  $bids = array();
-  $or = db_or();
-  // Gather the blocks defined by modules.
-  foreach (module_implements('block_info') as $module) {
-    $module_blocks = module_invoke($module, 'block_info');
-    foreach ($module_blocks as $delta => $block) {
-      // Compile a condition to retrieve this block from the database.
-      $condition = db_and()
-        ->condition('module', $module)
-        ->condition('delta', $delta);
-      $or->condition($condition);
-      // Add identifiers.
-      $block['module'] = $module;
-      $block['delta']  = $delta;
-      $block['theme']  = $theme;
-      $current_blocks[$module][$delta] = $block;
-    }
-  }
-  // Save the blocks defined in code for alter context.
-  $code_blocks = $current_blocks;
-  $database_blocks = db_select('block', 'b')
-    ->fields('b')
-    ->condition($or)
-    ->condition('theme', $theme)
-    ->execute();
-  foreach ($database_blocks as $block) {
-    // Preserve info which is not in the database.
-    $block->info = $current_blocks[$block->module][$block->delta]['info'];
-    // The cache mode can only by set from hook_block_info(), so that has
-    // precedence over the database's value.
-    if (isset($current_blocks[$block->module][$block->delta]['cache'])) {
-      $block->cache = $current_blocks[$block->module][$block->delta]['cache'];
-    }
-    // Blocks stored in the database override the blocks defined in code.
-    $current_blocks[$block->module][$block->delta] = get_object_vars($block);
-    // Preserve this block.
-    $bids[$block->bid] = $block->bid;
-  }
-  drupal_alter('block_info', $current_blocks, $theme, $code_blocks);
-  foreach ($current_blocks as $module => $module_blocks) {
-    foreach ($module_blocks as $delta => $block) {
-      if (!isset($block['pages'])) {
-        // {block}.pages is type 'text', so it cannot have a
-        // default value, and not null, so we need to provide
-        // value if the module did not.
-        $block['pages']  = '';
-      }
-      // Make sure weight is set.
-      if (!isset($block['weight'])) {
-        $block['weight'] = 0;
-      }
-      if (!empty($block['region']) && $block['region'] != BLOCK_REGION_NONE && !isset($regions[$block['region']]) && $block['status'] == 1) {
-        drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block['info'], '%region' => $block['region'])), 'warning');
-        // Disabled modules are moved into the BLOCK_REGION_NONE later so no
-        // need to move the block to another region.
-        $block['status'] = 0;
-      }
-      // Set region to none if not enabled and make sure status is set.
-      if (empty($block['status'])) {
-        $block['status'] = 0;
-        $block['region'] = BLOCK_REGION_NONE;
-      }
-      // There is no point saving disabled blocks. Still, we need to save them
-      // because the 'title' attribute is saved to the {blocks} table.
-      if (isset($block['bid'])) {
-        // If the block has a bid property, it comes from the database and
-        // the record needs to be updated, so set the primary key to 'bid'
-        // before passing to drupal_write_record().
-        $primary_keys = array('bid');
-        // Remove a block from the list of blocks to keep if it became disabled.
-        unset($bids[$block['bid']]);
-      }
-      else {
-        $primary_keys = array();
-      }
-      drupal_write_record('block', $block, $primary_keys);
-      // Add to the list of blocks we return.
-      $blocks[] = $block;
-    }
-  }
-  if ($bids) {
-    // Remove disabled that are no longer defined by the code from the
-    // database.
-    db_delete('block')
-      ->condition('bid', $bids, 'NOT IN')
-      ->condition('theme', $theme)
-      ->execute();
+  $instances = array();
+  $theme = $theme ? $theme : variable_get('theme_default', 'stark');
+  $block_configs = config_get_storage_names_with_prefix('plugin.core.block.' . $theme);
+  foreach ($block_configs as $config) {
+    $blocks[$config] = block_load($config);
   }
   return $blocks;
 }
 
 /**
- * Returns information from database about a user-created (custom) block.
- *
- * @param $bid
- *   ID of the block to get information for.
- *
- * @return
- *   Associative array of information stored in the database for this block.
- *   Array keys:
- *   - bid: Block ID.
- *   - info: Block description.
- *   - body: Block contents.
- *   - format: Filter ID of the filter format for the body.
- */
-function block_custom_block_get($bid) {
-  return db_query("SELECT * FROM {block_custom} WHERE bid = :bid", array(':bid' => $bid))->fetchAssoc();
-}
-
-/**
- * Form constructor for the custom block form.
- *
- * @param $edit
- *   (optional) An associative array of information retrieved by
- *   block_custom_get_block() if an existing block is being edited, or an empty
- *   array otherwise. Defaults to array().
- *
- * @ingroup forms
- */
-function block_custom_block_form($edit = array()) {
-  $edit += array(
-    'info' => '',
-    'body' => '',
-  );
-  $form['info'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Block description'),
-    '#default_value' => $edit['info'],
-    '#maxlength' => 64,
-    '#description' => t('A brief description of your block. Used on the <a href="@overview">Blocks administration page</a>.', array('@overview' => url('admin/structure/block'))),
-    '#required' => TRUE,
-    '#weight' => -18,
-  );
-  $form['body_field']['#weight'] = -17;
-  $form['body_field']['body'] = array(
-    '#type' => 'text_format',
-    '#title' => t('Block body'),
-    '#default_value' => $edit['body'],
-    '#format' => isset($edit['format']) ? $edit['format'] : NULL,
-    '#rows' => 15,
-    '#description' => t('The content of the block as shown to the user.'),
-    '#required' => TRUE,
-    '#weight' => -17,
-  );
-
-  return $form;
-}
-
-/**
- * Saves a user-created block in the database.
- *
- * @param $edit
- *   Associative array of fields to save. Array keys:
- *   - info: Block description.
- *   - body: Associative array of body value and format.  Array keys:
- *     - value: Block contents.
- *     - format: Filter ID of the filter format for the body.
- * @param $delta
- *   Block ID of the block to save.
- *
- * @return
- *   Always returns TRUE.
- */
-function block_custom_block_save($edit, $delta) {
-  db_update('block_custom')
-    ->fields(array(
-      'body' => $edit['body']['value'],
-      'info' => $edit['info'],
-      'format' => $edit['body']['format'],
-    ))
-    ->condition('bid', $delta)
-    ->execute();
-  return TRUE;
-}
-
-/**
- * Implements hook_form_FORM_ID_alter() for user_profile_form().
- */
-function block_form_user_profile_form_alter(&$form, &$form_state) {
-  $account = $form_state['controller']->getEntity($form_state);
-  $rids = array_keys($account->roles);
-  $result = db_query("SELECT DISTINCT b.* FROM {block} b LEFT JOIN {block_role} r ON b.module = r.module AND b.delta = r.delta WHERE b.status = 1 AND b.custom <> 0 AND (r.rid IN (:rids) OR r.rid IS NULL) ORDER BY b.weight, b.module", array(':rids' => $rids));
-
-  $blocks = array();
-  foreach ($result as $block) {
-    $data = module_invoke($block->module, 'block_info');
-    if ($data[$block->delta]['info']) {
-      $blocks[$block->module][$block->delta] = array(
-        '#type' => 'checkbox',
-        '#title' => check_plain($data[$block->delta]['info']),
-        '#default_value' => isset($account->data['block'][$block->module][$block->delta]) ? $account->data['block'][$block->module][$block->delta] : ($block->custom == 1),
-      );
-    }
-  }
-  // Only display the fieldset if there are any personalizable blocks.
-  if ($blocks) {
-    $form['block'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Personalize blocks'),
-      '#description' => t('Blocks consist of content or information that complements the main content of the page. Enable or disable optional blocks using the checkboxes below.'),
-      '#weight' => 3,
-      '#collapsible' => TRUE,
-      '#tree' => TRUE,
-    );
-    $form['block'] += $blocks;
-  }
-}
-
-/**
- * Implements hook_field_extra_fields().
- */
-function block_field_extra_fields() {
-  $extra['user']['user']['form']['block'] = array(
-    'label' => t('Personalize blocks'),
-    'description' => t('Block module form element.'),
-    'weight' => 3,
-  );
-
-  return $extra;
-}
-
-/**
- * Implements hook_user_presave().
- */
-function block_user_presave($account) {
-  if (isset($account->block)) {
-    $account->data['block'] = $account->block;
-  }
-}
-
-/**
  * Initializes blocks for enabled themes.
  *
  * @param $theme_list
@@ -672,20 +388,28 @@ function block_themes_enabled($theme_list) {
  */
 function block_theme_initialize($theme) {
   // Initialize theme's blocks if none already registered.
-  $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', 0, 1, array(':theme' => $theme))->fetchField();
+  $has_blocks = config_get_storage_names_with_prefix('plugin.core.block.' . $theme);
   if (!$has_blocks) {
     $default_theme = variable_get('theme_default', 'stark');
     // Apply only to new theme's visible regions.
     $regions = system_region_list($theme, REGIONS_VISIBLE);
-    $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $default_theme), array('fetch' => PDO::FETCH_ASSOC));
-    foreach ($result as $block) {
-      // If the region isn't supported by the theme, assign the block to the theme's default region.
-      if ($block['status'] && !isset($regions[$block['region']])) {
-        $block['region'] = system_default_region($theme);
+    $default_theme_blocks = config_get_storage_names_with_prefix('plugin.core.block.' . $default_theme);
+    foreach ($default_theme_blocks as $config_id) {
+      $block_config = config($config_id)->get();
+      $machine_name = explode('.', $config_id);
+      $machine_name = array_pop($machine_name);
+      $new_config_id = 'plugin.core.block.' . $theme . '.' . $machine_name;
+      $new_block = config($new_config_id);
+      // If the region isn't supported by the theme, assign the block to the
+      // theme's default region.
+      if (!isset($regions[$block_config['region']])) {
+        $new_block->set('region', system_default_region($theme));
+        unset($block_config['region']);
       }
-      $block['theme'] = $theme;
-      unset($block['bid']);
-      drupal_write_record('block', $block);
+      foreach ($block_config as $key => $value) {
+        $new_block->set($key, $value);
+      }
+      $new_block->save();
     }
   }
 }
@@ -697,14 +421,11 @@ function block_theme_initialize($theme) {
  *   The name of a region.
  *
  * @return
- *   An array of block objects, indexed with the module name and block delta
- *   concatenated with an underscore, thus: MODULE_DELTA. If you are displaying
- *   your blocks in one or two sidebars, you may check whether this array is
- *   empty to see how many columns are going to be displayed.
+ *   An array of block objects, indexed with the CMI key that represents the
+ *   configuration. If you are displaying your blocks in one or two sidebars,
+ *   you may check whether this array is empty to see how many columns are
+ *   going to be displayed.
  *
- * @todo
- *   Now that the block table has a primary key, we should use that as the
- *   array key instead of MODULE_DELTA.
  */
 function block_list($region) {
   $blocks = &drupal_static(__FUNCTION__);
@@ -722,176 +443,53 @@ function block_list($region) {
 }
 
 /**
+ * Returns the block plugin manager from the dependency injection container.
+ */
+function block_manager() {
+  return drupal_container()->get('plugin.manager.block');
+}
+
+/**
  * Loads a block object from the database.
  *
- * @param $module
- *   Name of the module that implements the block to load.
- * @param $delta
- *   Unique ID of the block within the context of $module. Pass NULL to return
- *   an empty block object for $module.
+ * @param $plugin_id
+ *   The plugin id to load.
+ * @param $conf
+ *   An optional configuration array for creating a block instance from php
+ *   instead of relying on configuration xml.
  *
  * @return
  *   A block object.
  */
-function block_load($module, $delta) {
-  if (isset($delta)) {
-    $block = db_query('SELECT * FROM {block} WHERE module = :module AND delta = :delta', array(':module' => $module, ':delta' => $delta))->fetchObject();
+function block_load($plugin_id, $conf = array()) {
+  try {
+    $block = block_manager()->getInstance(array('config' => $plugin_id));
   }
-
-  // If the block does not exist in the database yet return a stub block
-  // object.
-  if (empty($block)) {
-    $block = new stdClass();
-    $block->module = $module;
-    $block->delta = $delta;
+  catch (Drupal\Component\Plugin\Exception\PluginException $e) {
+    $block = block_manager()->createInstance($plugin_id, $conf);
   }
-
   return $block;
 }
 
 /**
- * Loads blocks' information from the database.
+ * Loads blocks' information from the configuration management system.
  *
  * @return
  *   An array of blocks grouped by region.
  */
 function _block_load_blocks() {
-  global $theme_key;
-
-  $query = db_select('block', 'b');
-  $query->addField('b', 'title', 'subject');
-  $result = $query
-    ->fields('b')
-    ->condition('b.theme', $theme_key)
-    ->condition('b.status', 1)
-    ->orderBy('b.region')
-    ->orderBy('b.weight')
-    ->orderBy('b.module')
-    ->addTag('block_load')
-    ->addTag('translatable')
-    ->execute();
-
-  $block_info = $result->fetchAllAssoc('bid');
-  // Allow modules to modify the block list.
-  drupal_alter('block_list', $block_info);
-
+  global $theme;
   $blocks = array();
-  foreach ($block_info as $block) {
-    $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block;
+  $instances = config_get_storage_names_with_prefix('plugin.core.block.' . $theme);
+  foreach ($instances as $plugin_id) {
+    $block = block_manager()->getInstance(array('config' => $plugin_id));
+    $config = $block->getConfig();
+    $blocks[$config['region']]["$plugin_id"] = $block;
   }
   return $blocks;
 }
 
 /**
- * Implements hook_block_list_alter().
- *
- * Checks the page, user role, and user-specific visibility settings.
- * Removes the block if the visibility conditions are not met.
- */
-function block_block_list_alter(&$blocks) {
-  global $user, $theme_key;
-
-  // Build an array of roles for each block.
-  $block_roles = array();
-  $result = db_query('SELECT module, delta, rid FROM {block_role}');
-  foreach ($result as $record) {
-    $block_roles[$record->module][$record->delta][] = $record->rid;
-  }
-
-  // Build an array of langcodes allowed per block.
-  $result = db_query('SELECT module, delta, type, langcode FROM {block_language}');
-  $block_langcodes = array();
-  foreach ($result as $record) {
-    $block_langcodes[$record->module][$record->delta][$record->type][$record->langcode] = TRUE;
-  }
-
-  foreach ($blocks as $key => $block) {
-    if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) {
-      // This block was added by a contrib module, leave it in the list.
-      continue;
-    }
-
-    // If a block has no roles associated, it is displayed for every role.
-    // For blocks with roles associated, if none of the user's roles matches
-    // the settings from this block, remove it from the block list.
-    if (isset($block_roles[$block->module][$block->delta]) && !array_intersect($block_roles[$block->module][$block->delta], array_keys($user->roles))) {
-      // No match.
-      unset($blocks[$key]);
-      continue;
-    }
-
-    // Use the user's block visibility setting, if necessary.
-    if ($block->custom != BLOCK_CUSTOM_FIXED) {
-      if ($user->uid && isset($user->data['block'][$block->module][$block->delta])) {
-        $enabled = $user->data['block'][$block->module][$block->delta];
-      }
-      else {
-        $enabled = ($block->custom == BLOCK_CUSTOM_ENABLED);
-      }
-    }
-    else {
-      $enabled = TRUE;
-    }
-
-    // Limited visibility blocks must list at least one page.
-    if ($block->visibility == BLOCK_VISIBILITY_LISTED && empty($block->pages)) {
-      $enabled = FALSE;
-    }
-
-    if (!$enabled) {
-      unset($blocks[$key]);
-      continue;
-    }
-
-    // Match path if necessary.
-    if ($block->pages) {
-      // Convert path to lowercase. This allows comparison of the same path
-      // with different case. Ex: /Page, /page, /PAGE.
-      $pages = drupal_strtolower($block->pages);
-      if ($block->visibility < BLOCK_VISIBILITY_PHP) {
-        // Compare the lowercase path alias (if any) and internal path.
-        $path = current_path();
-        $path_alias = drupal_strtolower(drupal_get_path_alias($path));
-        $page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages));
-        // When $block->visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED),
-        // the block is displayed on all pages except those listed in $block->pages.
-        // When set to 1 (BLOCK_VISIBILITY_LISTED), it is displayed only on those
-        // pages listed in $block->pages.
-        $page_match = !($block->visibility xor $page_match);
-      }
-      elseif (module_exists('php')) {
-        $page_match = php_eval($block->pages);
-      }
-      else {
-        $page_match = FALSE;
-      }
-    }
-    else {
-      $page_match = TRUE;
-    }
-    if (!$page_match) {
-      unset($blocks[$key]);
-      continue;
-    }
-
-    // Language visibility settings.
-    // No language setting for this block, leave it in the list.
-    if (!isset($block_langcodes[$block->module][$block->delta])) {
-      continue;
-    }
-    foreach ($block_langcodes[$block->module][$block->delta] as $language_type => $langcodes) {
-      if (isset($langcodes[language($language_type)->langcode])) {
-        // Found a language type - langcode combination in the configuration
-        // that is applicable to the current request.
-        continue 2;
-      }
-    }
-    // Had language configuration but none matched.
-    unset($blocks[$key]);
-  }
-}
-
-/**
  * Builds the content and subject for a block.
  *
  * For cacheable blocks, this is called during #pre_render.
@@ -904,45 +502,18 @@ function block_block_list_alter(&$blocks) {
  */
 function _block_get_renderable_block($element) {
   $block = $element['#block'];
-
-  // Render the block content if it has not been created already.
-  if (!isset($block->content)) {
-    $array = module_invoke($block->module, 'block_view', $block->delta);
-
-    // Allow modules to modify the block before it is viewed, via either
-    // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter().
-    drupal_alter(array('block_view', "block_view_{$block->module}_{$block->delta}"), $array, $block);
-
-    if (empty($array['content'])) {
-      // Blocks without content should emit no markup at all.
-      $element += array(
-        '#access' => FALSE,
-        '#printed' => TRUE,
-      );
-    }
-    elseif (isset($array) && is_array($array)) {
-      foreach ($array as $k => $v) {
-        $block->$k = $v;
+  // Don't bother to build blocks that aren't accessible.
+  if ($element['#access'] = $block->accessWrapper()) {
+    $build = $block->build();
+    if ($build) {
+      if (isset($build['#title'])) {
+        $element['#title'] = $build['#title'];
       }
+      $element += $build;
     }
-  }
-
-  if (isset($block->content) && $block->content) {
-    // Normalize to the drupal_render() structure.
-    if (is_string($block->content)) {
-      $block->content = array('#markup' => $block->content);
-    }
-    // Override default block title if a custom display title is present.
-    if ($block->title) {
-      // Check plain here to allow module generated titles to keep any
-      // markup.
-      $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
+    else {
+      $element = array();
     }
-
-    // Add the content renderable array to the main element.
-    $element['content'] = $block->content;
-    unset($block->content);
-    $element['#block'] = $block;
   }
   return $element;
 }
@@ -984,7 +555,10 @@ function block_rebuild() {
  */
 function template_preprocess_block(&$variables) {
   $block_counter = &drupal_static(__FUNCTION__, array());
-  $variables['block'] = $variables['elements']['#block'];
+  $variables['block'] = (object) array_merge($variables['elements']['#block']->getDefinition(), $variables['elements']['#block']->getConfig());
+  if (!empty($variables['elements']['#title']) && empty($variables['block']->subject)) {
+    $variables['block']->subject = $variables['elements']['#title'];
+  }
   // All blocks get an independent counter for each region.
   if (!isset($block_counter[$variables['block']->region])) {
     $block_counter[$variables['block']->region] = 1;
@@ -1011,10 +585,22 @@ function template_preprocess_block(&$variables) {
   // contains a hyphen, it will end up as an underscore after this conversion,
   // and your function names won't be recognized. So, we need to convert
   // hyphens to underscores in block deltas for the theme suggestions.
-  $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->module . '__' . strtr($variables['block']->delta, '-', '_');
-
+  if (!empty($variables['block']->derivative)) {
+    list($plugin, $derivative) = explode(':', $variables['block']->id);
+    $variables['theme_hook_suggestions'][] = 'block__' . strtr($plugin, '-', '_');
+    $variables['theme_hook_suggestions'][] = 'block__' . strtr($plugin, '-', '_') . '__' . strtr($derivative, '-', '_');
+  }
+  else {
+    $plugin = $variables['block']->id;
+    $variables['theme_hook_suggestions'][] = 'block__' . strtr($plugin, '-', '_');
+  }
   // Create a valid HTML ID and make sure it is unique.
-  $variables['block_html_id'] = drupal_html_id('block-' . $variables['block']->module . '-' . $variables['block']->delta);
+  if (!empty($variables['block']->config_id)) {
+    $config_id = explode('.', $variables['block']->config_id);
+    $machine_name = array_pop($config_id);
+    $variables['block_html_id'] = drupal_html_id('block-' . strtr($machine_name, '-', '_'));
+    $variables['theme_hook_suggestions'][] = 'block__' . $machine_name;
+  }
 }
 
 /**
@@ -1023,23 +609,29 @@ function template_preprocess_block(&$variables) {
  * Removes deleted role from blocks that use it.
  */
 function block_user_role_delete($role) {
-  db_delete('block_role')
-    ->condition('rid', $role->rid)
-    ->execute();
+  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
+  foreach ($block_configs as $config_id) {
+    $config = config($config_id);
+    $roles = $config->get('visibility.role.roles');
+    if (isset($roles[$role->rid])) {
+      unset($roles[$role->rid]);
+      $config->set('visibility.role.roles', $roles);
+      $config->save();
+    }
+  }
 }
 
 /**
  * Implements hook_menu_delete().
  */
 function block_menu_delete($menu) {
-  db_delete('block')
-    ->condition('module', 'menu')
-    ->condition('delta', $menu['menu_name'])
-    ->execute();
-  db_delete('block_role')
-    ->condition('module', 'menu')
-    ->condition('delta', $menu['menu_name'])
-    ->execute();
+  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
+  foreach ($block_configs as $config_id) {
+    $config = config($config_id);
+    if ($config->get('id') == 'menu_menu_block:' . $menu['menu_name']) {
+      $config->delete();
+    }
+  }
 }
 
 /**
@@ -1057,19 +649,16 @@ function block_admin_paths() {
 /**
  * Implements hook_modules_uninstalled().
  *
- * Cleans up {block}, {block_role} and {block_language} tables
- * from modules' blocks.
+ * Cleans up any block configuration for uninstalled modules.
  */
 function block_modules_uninstalled($modules) {
-  db_delete('block')
-    ->condition('module', $modules, 'IN')
-    ->execute();
-  db_delete('block_role')
-    ->condition('module', $modules, 'IN')
-    ->execute();
-  db_delete('block_language')
-    ->condition('module', $modules, 'IN')
-    ->execute();
+  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
+  foreach ($block_configs as $config_id) {
+    $config = config($config_id);
+    if (in_array($config->get('module'), $modules)) {
+      $config->delete();
+    }
+  }
 }
 
 /**
@@ -1079,9 +668,16 @@ function block_modules_uninstalled($modules) {
  */
 function block_language_delete($language) {
   // Remove the block visibility settings for the deleted language.
-  db_delete('block_language')
-    ->condition('langcode', $language->langcode)
-    ->execute();
+  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
+  foreach ($block_configs as $config_id) {
+    $config = config($config_id);
+    $languages = $config->get('visibility.language.langcodes');
+    if (isset($languages[$language->langcode])) {
+      unset($languages[$language->langcode]);
+      $config->set('visibility.language.langcodes', $languages);
+      $config->save();
+    }
+  }
 }
 
 /**
diff --git a/core/modules/block/custom_block/custom_block.admin.inc b/core/modules/block/custom_block/custom_block.admin.inc
new file mode 100644
index 0000000..e1f015b
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.admin.inc
@@ -0,0 +1,42 @@
+<?php
+
+function custom_block_admin_custom_block($form, &$form_state) {
+  $destination = drupal_get_destination();
+   $header = array(
+    'info' => array(
+      'data' => t('Administrative Title'),
+      'field' => 'cb.info',
+    ),
+    'operations' => array(
+      'data' => t('Operations')
+    )
+  );
+  $header = array(t('Administrative Title'), t('Operations'));
+  $options = array();
+  $results = db_select('block_custom', 'cb')
+    ->fields('cb')
+    ->execute();
+  foreach ($results as $result) {
+    $options[$result->bid] = array(
+      array(
+        l($result->info, 'custom_block/' . $result->bid . '/edit'),
+      ),
+      array(
+        theme('links', array('links' => array(array(
+            'title' => t('edit'),
+            'href' => 'custom_block/' . $result->bid . '/edit',
+            'query' => $destination,
+          )))
+        ),
+      ),
+    );
+  }
+  $form['blocks'] = array(
+    '#type' => 'tableselect',
+    '#header' => $header,
+    '#options' => $options,
+    '#empty' => t('No custom blocks are available.'),
+  );
+  $form['pager'] = array('#markup' => theme('pager'));
+  return $form;
+}
\ No newline at end of file
diff --git a/core/modules/block/custom_block/custom_block.info b/core/modules/block/custom_block/custom_block.info
new file mode 100644
index 0000000..f9e8f05
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.info
@@ -0,0 +1,6 @@
+name = Custom Block
+description = Allows the ability to create custom blocks through the block interface.
+package = Core
+version = VERSION
+core = 8.x
+dependencies[] = block
diff --git a/core/modules/block/custom_block/custom_block.install b/core/modules/block/custom_block/custom_block.install
new file mode 100644
index 0000000..a12ccee
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.install
@@ -0,0 +1,41 @@
+<?php
+
+function custom_block_schema() {
+  $schema = array();
+  $schema['block_custom'] = array(
+    'description' => 'Stores contents of custom-made blocks.',
+    'fields' => array(
+      'bid' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => "The block's {block}.bid.",
+      ),
+      'body' => array(
+        'type' => 'text',
+        'not null' => FALSE,
+        'size' => 'big',
+        'description' => 'Block contents.',
+        'translatable' => TRUE,
+      ),
+      'info' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Block description.',
+      ),
+      'format' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'description' => 'The {filter_format}.format of the block body.',
+      ),
+    ),
+    'unique keys' => array(
+      'info' => array('info'),
+    ),
+    'primary key' => array('bid'),
+  );
+  return $schema;
+}
\ No newline at end of file
diff --git a/core/modules/block/custom_block/custom_block.module b/core/modules/block/custom_block/custom_block.module
new file mode 100644
index 0000000..f7a0d98
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.module
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Implements hook_menu().
+ */
+function custom_block_menu() {
+  $items = array();
+  foreach (system_plugin_manager('plugin_ui')->getDefinitions() as $plugin_id => $plugin) {
+    // We only need this menu item for the block_plugin_ui derivatives.
+    if (strpos($plugin_id, 'block_plugin_ui') === 0) {
+      list(, $theme) = explode(':', $plugin_id);
+      $items['admin/structure/block/list/' . $plugin_id . '/add/custom_blocks'] = array(
+        'title' => 'Add Custom Block',
+        'description' => 'Create a block with custom content and settings.',
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('block_admin_configure', 'custom_block:custom_block', $theme),
+        'access callback' => TRUE,
+        'type' => MENU_LOCAL_ACTION,
+        'file' => 'block.admin.inc',
+        'file path' => drupal_get_path('module', 'block'),
+      );
+      $items['admin/structure/block/list/' . $plugin_id . '/add/custom_blocks_library'] = array(
+        'title' => 'Custom Blocks',
+        'description' => 'Administrate custom blocks content.',
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('custom_block_admin_custom_block'),
+        'access callback' => TRUE,
+        'type' => MENU_LOCAL_TASK,
+        'file' => 'custom_block.admin.inc',
+        'file path' => drupal_get_path('module', 'custom_block'),
+      );
+    }
+  }
+  return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function custom_block_theme($existing, $type, $theme, $path) {
+  return array(
+    'custom_block_block' => array(
+      'variables' => array('body' => NULL, 'format' => NULL),
+    ),
+  );
+}
+
+/**
+ * Returns HTML for a custom block.
+ *
+ * @ingroup themeable
+ */
+function theme_custom_block_block($variables) {
+  $body = $variables['body'];
+  $format = $variables['format'];
+
+  return check_markup($body, $format);
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php
new file mode 100644
index 0000000..f8d9d64
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\custom_block\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+class CustomBlock implements DerivativeInterface {
+  protected $derivatives = array();
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    // TODO. This check is here for simpletest. See
+    // http://drupal.org/node/1821658
+    if (!module_exists('custom_block')) {
+      return array();
+    }
+    if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+    $this->getDerivativeDefinitions($base_plugin_definition);
+    return $this->derivatives[$derivative_id];
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    // TODO. This check is here for simpletest. See
+    // http://drupal.org/node/1821658
+    if (!module_exists('custom_block')) {
+      return array();
+    }
+    $results = db_select('block_custom', 'b')
+      ->fields('b')
+      ->execute();
+    if ($results) {
+      foreach ($results as $result) {
+        $this->derivatives[$result->bid] = $base_plugin_definition;
+        $this->derivatives[$result->bid]['settings'] = array(
+          'info' => $result->info,
+          'body' => $result->body,
+          'format' => $result->format,
+        ) + $base_plugin_definition['settings'];
+        $this->derivatives[$result->bid]['subject'] = $result->info;
+        unset($this->derivatives[$result->bid]['custom']);
+      }
+    }
+    $this->derivatives['custom_block'] = $base_plugin_definition;
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php
new file mode 100644
index 0000000..a00bc93
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\custom_block\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *  id = "custom_block",
+ *  subject = @Translation("Custom Block"),
+ *  module = "custom_block",
+ *  derivative = "Drupal\custom_block\Plugin\Derivative\CustomBlock",
+ *  custom = "TRUE",
+ *  settings = {
+ *    "status" = TRUE,
+ *    "info" = "",
+ *    "body" = "",
+ *    "format" = ""
+ *  }
+ * )
+ */
+class CustomBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::getConfig().
+   */
+  public function getConfig() {
+    $definition = $this->getDefinition();
+    $this->configuration = parent::getConfig();
+    $this->configuration['status'] = $definition['settings']['status'];
+    $this->configuration['info'] = $definition['settings']['info'];
+    $this->configuration['body'] = $definition['settings']['body'];
+    $this->configuration['format'] = $definition['settings']['format'];
+    return $this->configuration;
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $form = parent::configure($form, $form_state);
+    $form['settings']['info'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Block description'),
+      '#required' => TRUE,
+      '#default_value' => $this->configuration['info'],
+      '#description' => t('A brief description of your block. Used on the <a href="@overview">Blocks administration page</a>.', array('@overview' => url('admin/structure/block'))),
+    );
+    if (!empty($this->configuration['info'])) {
+      $form['settings']['info']['#disabled'] = TRUE;
+    }
+    $form['settings']['body'] = array(
+      '#type' => 'text_format',
+      '#title' => t('Block body'),
+      '#default_value' => $this->configuration['body'],
+      '#format' => isset($this->configuration['format']) ? $this->configuration['format'] : filter_default_format(),
+      '#description' => t('The content of the block as shown to the user.'),
+      '#rows' => 15,
+      '#required' => TRUE,
+    );
+    if (!empty($this->configuration['body'])) {
+      $form['settings']['body']['#disabled'] = TRUE;
+    }
+    $form['settings']['title']['#description'] = t('The title of the block as shown to the user.');
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    parent::configureSubmit($form, $form_state);
+    list(, $bid) = explode(':', $this->getPluginId());
+    $block = array(
+      'info' => $form_state['values']['info'],
+      'body' => $form_state['values']['body']['value'],
+      'format' => $form_state['values']['body']['format'],
+      'bid' => is_numeric($bid) ? $bid : NULL,
+    );
+    drupal_write_record('block_custom', $block, !is_null($block['bid']) ? array('bid') : array());
+    $this->configuration['id'] = 'custom_block:' . $block['bid'];
+  }
+
+  /**
+   * Implements BlockInterface::build();
+   */
+  public function build() {
+    return array(
+      '#theme' => 'custom_block_block',
+      '#body' => $this->configuration['body'],
+      '#format' => $this->configuration['format'],
+    );
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTest.php
new file mode 100644
index 0000000..8f0560f
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTest.php
@@ -0,0 +1,427 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\block\Tests\BlockTest.
+ */
+
+namespace Drupal\block\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+class CustomBlockTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block', 'custom_block', 'test_page_test');
+
+  protected $regions;
+  protected $admin_user;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Block functionality',
+      'description' => 'Custom block functionality.',
+      'group' => 'Block',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Use the test page as the front page.
+    config('system.site')->set('page.front', 'test-page')->save();
+
+    // Create Full HTML text format.
+    $full_html_format = array(
+      'format' => 'full_html',
+      'name' => 'Full HTML',
+    );
+    $full_html_format = (object) $full_html_format;
+    filter_format_save($full_html_format);
+    $this->checkPermissions(array(), TRUE);
+
+    // Create and log in an administrative user having access to the Full HTML
+    // text format.
+    $this->admin_user = $this->drupalCreateUser(array(
+      'administer blocks',
+      filter_permission_name($full_html_format),
+      'access administration pages',
+    ));
+    $this->drupalLogin($this->admin_user);
+
+    // Define the existing regions
+    $this->regions = array();
+    $this->regions[] = 'header';
+    $this->regions[] = 'sidebar_first';
+    $this->regions[] = 'content';
+    $this->regions[] = 'sidebar_second';
+    $this->regions[] = 'footer';
+  }
+
+  /**
+   * Test creating custom block, moving it to a specific region and then deleting it.
+   */
+  function testCustomBlock() {
+    // Enable a second theme.
+    theme_enable(array('seven'));
+
+    // Confirm that the add block link appears on block overview pages.
+    $this->drupalGet('admin/structure/block');
+    $this->assertRaw(l('Add block', 'admin/structure/block/add'), 'Add block link is present on block overview page for default theme.');
+    $this->drupalGet('admin/structure/block/list/seven');
+    $this->assertRaw(l('Add block', 'admin/structure/block/list/seven/add'), 'Add block link is present on block overview page for non-default theme.');
+
+    // Confirm that hidden regions are not shown as options for block placement
+    // when adding a new block.
+    theme_enable(array('bartik'));
+    $themes = list_themes();
+    $this->drupalGet('admin/structure/block/add');
+    foreach ($themes as $key => $theme) {
+      if ($theme->status) {
+        foreach ($theme->info['regions_hidden'] as $hidden_region) {
+          $elements = $this->xpath('//select[@id=:id]//option[@value=:value]', array(':id' => 'edit-regions-' . $key, ':value' => $hidden_region));
+          $this->assertFalse(isset($elements[0]), format_string('The hidden region @region is not available for @theme.', array('@region' => $hidden_region, '@theme' => $key)));
+        }
+      }
+    }
+
+    // Add a new custom block by filling out the input form on the admin/structure/block/add page.
+    $custom_block = array();
+    $custom_block['info'] = $this->randomName(8);
+    $custom_block['title'] = $this->randomName(8);
+    $custom_block['body[value]'] = $this->randomName(32);
+    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
+
+    // Confirm that the custom block has been created, and then query the created bid.
+    $this->assertText(t('The block has been created.'), 'Custom block successfully created.');
+    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+
+    // Check to see if the custom block was created by checking that it's in the database.
+    $this->assertNotNull($bid, 'Custom block found in database');
+
+    // Check that block_block_view() returns the correct title and content.
+    $data = block_block_view($bid);
+    $format = db_query("SELECT format FROM {block_custom} WHERE bid = :bid", array(':bid' => $bid))->fetchField();
+    $this->assertTrue(array_key_exists('subject', $data) && empty($data['subject']), 'block_block_view() provides an empty block subject, since custom blocks do not have default titles.');
+    $this->assertEqual(check_markup($custom_block['body[value]'], $format), $data['content'], 'block_block_view() provides correct block content.');
+
+    // Check whether the block can be moved to all available regions.
+    $custom_block['module'] = 'block';
+    $custom_block['delta'] = $bid;
+    foreach ($this->regions as $region) {
+      $this->moveBlockToRegion($custom_block, $region);
+    }
+
+    // Verify presence of configure and delete links for custom block.
+    $this->drupalGet('admin/structure/block');
+    $this->assertLinkByHref('admin/structure/block/manage/block/' . $bid . '/configure', 0, 'Custom block configure link found.');
+    $this->assertLinkByHref('admin/structure/block/manage/block/' . $bid . '/delete', 0, 'Custom block delete link found.');
+
+    // Set visibility only for authenticated users, to verify delete functionality.
+    $edit = array();
+    $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE;
+    $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/configure', $edit, t('Save block'));
+
+    // Delete the created custom block & verify that it's been deleted and no longer appearing on the page.
+    $this->clickLink(t('delete'));
+    $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/delete', array(), t('Delete'));
+    $this->assertRaw(t('The block %title has been removed.', array('%title' => $custom_block['info'])), 'Custom block successfully deleted.');
+    $this->assertNoText(t($custom_block['title']), 'Custom block no longer appears on page.');
+    $count = db_query("SELECT 1 FROM {block_role} WHERE module = :module AND delta = :delta", array(':module' => $custom_block['module'], ':delta' => $custom_block['delta']))->fetchField();
+    $this->assertFalse($count, 'Table block_role being cleaned.');
+  }
+
+  /**
+   * Test creating custom block using Full HTML.
+   */
+  function testCustomBlockFormat() {
+    // Add a new custom block by filling out the input form on the admin/structure/block/add page.
+    $custom_block = array();
+    $custom_block['info'] = $this->randomName(8);
+    $custom_block['title'] = $this->randomName(8);
+    $custom_block['body[value]'] = '<h1>Full HTML</h1>';
+    $full_html_format = filter_format_load('full_html');
+    $custom_block['body[format]'] = $full_html_format->format;
+    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
+
+    // Set the created custom block to a specific region.
+    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+    $edit = array();
+    $edit['blocks[block_' . $bid . '][region]'] = $this->regions[1];
+    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+    // Confirm that the custom block is being displayed using configured text format.
+    $this->drupalGet('');
+    $this->assertRaw('<h1>Full HTML</h1>', 'Custom block successfully being displayed using Full HTML.');
+
+    // Confirm that a user without access to Full HTML can not see the body field,
+    // but can still submit the form without errors.
+    $block_admin = $this->drupalCreateUser(array('administer blocks'));
+    $this->drupalLogin($block_admin);
+    $this->drupalGet('admin/structure/block/manage/block/' . $bid . '/configure');
+    $this->assertFieldByXPath("//textarea[@name='body[value]' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Body field contains denied message');
+    $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/configure', array(), t('Save block'));
+    $this->assertNoText(t('Ensure that each block description is unique.'));
+
+    // Confirm that the custom block is still being displayed using configured text format.
+    $this->drupalGet('');
+    $this->assertRaw('<h1>Full HTML</h1>', 'Custom block successfully being displayed using Full HTML.');
+  }
+
+  /**
+   * Test block visibility.
+   */
+  function testBlockVisibility() {
+    $block = array();
+
+    // Create a random title for the block
+    $title = $this->randomName(8);
+
+    // Create the custom block
+    $custom_block = array();
+    $custom_block['info'] = $this->randomName(8);
+    $custom_block['title'] = $title;
+    $custom_block['body[value]'] = $this->randomName(32);
+    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
+
+    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+    $block['module'] = 'block';
+    $block['delta'] = $bid;
+    $block['title'] = $title;
+
+    // Set the block to be hidden on any user path, and to be shown only to
+    // authenticated users.
+    $edit = array();
+    $edit['pages'] = 'user*';
+    $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE;
+    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
+
+    // Move block to the first sidebar.
+    $this->moveBlockToRegion($block, $this->regions[1]);
+
+    $this->drupalGet('');
+    $this->assertText($title, 'Block was displayed on the front page.');
+
+    $this->drupalGet('user');
+    $this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
+
+    $this->drupalGet('USER/' . $this->admin_user->uid);
+    $this->assertNoText($title, 'Block was not displayed according to block visibility rules regardless of path case.');
+
+    // Confirm that the block is not displayed to anonymous users.
+    $this->drupalLogout();
+    $this->drupalGet('');
+    $this->assertNoText($title, 'Block was not displayed to anonymous users.');
+
+    // Confirm that an empty block is not displayed.
+    $this->assertNoRaw('block-system-help', 'Empty block not displayed.');
+  }
+
+  /**
+   * Test block visibility when using "pages" restriction but leaving
+   * "pages" textarea empty
+   */
+  function testBlockVisibilityListedEmpty() {
+    $block = array();
+
+    // Create a random title for the block
+    $title = $this->randomName(8);
+
+    // Create the custom block
+    $custom_block = array();
+    $custom_block['info'] = $this->randomName(8);
+    $custom_block['title'] = $title;
+    $custom_block['body[value]'] = $this->randomName(32);
+    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
+
+    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+    $block['module'] = 'block';
+    $block['delta'] = $bid;
+    $block['title'] = $title;
+
+    // Move block to the first sidebar.
+    $this->moveBlockToRegion($block, $this->regions[1]);
+
+    // Set the block to be hidden on any user path, and to be shown only to
+    // authenticated users.
+    $edit = array();
+    $edit['visibility'] = BLOCK_VISIBILITY_LISTED;
+    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
+
+    $this->drupalGet('user');
+    $this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
+
+    $this->drupalGet('USER');
+    $this->assertNoText($title, 'Block was not displayed according to block visibility rules regardless of path case.');
+
+    // Confirm that the block is not displayed to anonymous users.
+    $this->drupalLogout();
+    $this->drupalGet('');
+    $this->assertNoText($title, 'Block was not displayed to anonymous users on the front page.');
+  }
+
+  /**
+   * Test user customization of block visibility.
+   */
+  function testBlockVisibilityPerUser() {
+    $block = array();
+
+    // Create a random title for the block.
+    $title = $this->randomName(8);
+
+    // Create our custom test block.
+    $custom_block = array();
+    $custom_block['info'] = $this->randomName(8);
+    $custom_block['title'] = $title;
+    $custom_block['body[value]'] = $this->randomName(32);
+    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
+
+    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
+    $block['module'] = 'block';
+    $block['delta'] = $bid;
+    $block['title'] = $title;
+
+    // Move block to the first sidebar.
+    $this->moveBlockToRegion($block, $this->regions[1]);
+
+    // Set the block to be customizable per user, visible by default.
+    $edit = array();
+    $edit['custom'] = BLOCK_CUSTOM_ENABLED;
+    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
+
+    // Disable block visibility for the admin user.
+    $edit = array();
+    $edit['block[' . $block['module'] . '][' . $block['delta'] . ']'] = FALSE;
+    $this->drupalPost('user/' . $this->admin_user->uid . '/edit', $edit, t('Save'));
+
+    $this->drupalGet('user');
+    $this->assertNoText($block['title'], 'Block was not displayed according to per user block visibility setting.');
+
+    // Set the block to be customizable per user, hidden by default.
+    $edit = array();
+    $edit['custom'] = BLOCK_CUSTOM_DISABLED;
+    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
+
+    // Enable block visibility for the admin user.
+    $edit = array();
+    $edit['block[' . $block['module'] . '][' . $block['delta'] . ']'] = TRUE;
+    $this->drupalPost('user/' . $this->admin_user->uid . '/edit', $edit, t('Save'));
+
+    $this->drupalGet('user');
+    $this->assertText($block['title'], 'Block was displayed according to per user block visibility setting.');
+  }
+
+  /**
+   * Test configuring and moving a module-define block to specific regions.
+   */
+  function testBlock() {
+    // Select the 'Powered by Drupal' block to be configured and moved.
+    $block = array();
+    $block['id'] = 'system_powered_by_block';
+    $block['title'] = $this->randomName(8);
+    $block['machine_name'] = $this->randomName(8);
+    $block['theme'] = variable_get('theme_default', 'stark');
+    $block['region'] = 'header';
+
+    // Set block title to confirm that interface works and override any custom titles.
+    $this->drupalPost('admin/structure/block/manage/' . $block['id'] . '/' . $block['theme'], array('title' => $block['title'], 'machine_name' => $block['machine_name'], 'region' => $block['region']), t('Save block'));
+    $this->assertText(t('The block configuration has been saved.'), 'Block title set.');
+    // Check to see if the block was created by checking its configuration.
+    $block['config_id'] = 'plugin.core.block.' . $block['theme'] . '.' . $block['machine_name'];
+    $instance = block_load($block['config_id']);
+    $config = $instance->getConfig();
+
+    $this->assertEqual($config['subject'], $block['title'], t('Stored block title found.'));
+
+    // Check whether the block can be moved to all available regions.
+    foreach ($this->regions as $region) {
+      $this->moveBlockToRegion($block, $region);
+    }
+
+    // Set the block to the disabled region.
+    $edit = array();
+    $edit['blocks[0][region]'] = -1;
+    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+    // Confirm that the block was moved to the proper region.
+    $this->assertText(t('The block settings have been updated.'), 'Block successfully move to disabled region.');
+    $this->drupalGet('node');
+    $this->assertNoText(t($block['title']), 'Block no longer appears on page.');
+
+    // Confirm that the region's xpath is not available.
+    $xpath = $this->buildXPathQuery('//div[@id=:id]/*', array(':id' => 'block-' . strtr(strtolower($block['machine_name']), '-', '_')));
+    $this->assertNoFieldByXPath($xpath, FALSE, t('Block found in no regions.'));
+  }
+
+  /**
+   * Moves a block to a given region via the UI and confirms the result.
+   *
+   * @param array $block
+   *   An array of information about the block, including the following keys:
+   *   - module: The module providing the block.
+   *   - title: The title of the block.
+   *   - delta: The block's delta key.
+   * @param string $region
+   *   The machine name of the theme region to move the block to, for example
+   *   'header' or 'sidebar_first'.
+   */
+  function moveBlockToRegion(array $block, $region) {
+    // Set the created block to a specific region.
+    $edit = array();
+    $edit['blocks[0][region]'] = $region;
+    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+    // Confirm that the block was moved to the proper region.
+    $this->assertText(t('The block settings have been updated.'), format_string('Block successfully moved to %region_name region.', array( '%region_name' => $region)));
+
+    // Confirm that the block is being displayed.
+    $this->drupalGet('');
+    $this->assertText(t($block['title']), 'Block successfully being displayed on the page.');
+
+    // Confirm that the custom block was found at the proper region.
+    $xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array(
+      ':region-class' => 'region region-' . drupal_html_class($region),
+      ':block-id' => 'block-' . strtr(strtolower($block['machine_name']), '-', '_'),
+    ));
+    $this->assertFieldByXPath($xpath, NULL, t('Block found in %region_name region.', array('%region_name' => drupal_html_class($region))));
+  }
+
+  /**
+   * Test _block_rehash().
+   */
+  function testBlockRehash() {
+    module_enable(array('block_test'));
+    $this->assertTrue(module_exists('block_test'), 'Test block module enabled.');
+
+    // Add a test block.
+    $plugin = block_manager()->getDefinition('test_cache');
+
+    $block = array();
+    $block['id'] = 'test_cache';
+    $block['machine_name'] = $this->randomName(8);
+    $block['theme'] = variable_get('theme_default', 'stark');
+    $block['region'] = 'header';
+    $this->drupalPost('admin/structure/block/manage/' . $block['id'] . '/' . $block['theme'], array('machine_name' => $block['machine_name'], 'region' => $block['region']), t('Save block'));
+
+    // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE.
+    $block['config_id'] = 'plugin.core.block.' . $block['theme'] . '.' . $block['machine_name'];
+    $instance = block_load($block['config_id']);
+    $config = $instance->getConfig();
+    $this->assertEqual($config['cache'], DRUPAL_CACHE_PER_ROLE, t('Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.'));
+
+    // Disable caching for this block.
+    $block_config = config($block['config_id']);
+    $block_config->set('cache', DRUPAL_NO_CACHE);
+    $block_config->save();
+    // Flushing all caches should call _block_rehash().
+    $this->resetAll();
+    // Verify that block is updated with the new caching mode.
+    $instance = block_load($block['config_id']);
+    $config = $instance->getConfig();
+    $this->assertEqual($config['cache'], DRUPAL_NO_CACHE, t("Test block's database entry updated to DRUPAL_NO_CACHE."));
+  }
+}
diff --git a/core/modules/block/lib/Drupal/block/BlockBase.php b/core/modules/block/lib/Drupal/block/BlockBase.php
new file mode 100644
index 0000000..322b550
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/BlockBase.php
@@ -0,0 +1,426 @@
+<?php
+
+namespace Drupal\block;
+
+use Drupal\Component\Plugin\PluginBase;
+use Symfony\Component\Routing\RequestContext;
+
+/**
+ * @file
+ *   An abstract block implementation to be inherited from to take care of many
+ *   common block setup tasks.
+ */
+abstract class BlockBase extends PluginBase implements BlockInterface {
+  protected $context;
+
+  /**
+   * Implements BlockInterface::settingsWrapper().
+   */
+  public function settingsWrapper() {
+    $settings = $this->settings();
+    $settings += array(
+      'status' => TRUE,
+      'cache' => DRUPAL_NO_CACHE,
+    );
+    return $settings;
+  }
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array();
+  }
+
+  /**
+   * Implements BlockInterface::getConfig().
+   */
+  public function getConfig() {
+    if (empty($this->configuration)) {
+      $definition = $this->getDefinition();
+      $this->configuration = $this->settingsWrapper();
+    }
+    return $this->configuration;
+  }
+
+  public function setConfig($key, $value) {
+    $this->configuration[$key] = $value;
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return TRUE;
+  }
+
+  /**
+   * Implements BlockInterface::accessWrapper().
+   */
+  public function accessWrapper() {
+    if ($this->access()) {
+      global $user, $theme_key;
+      // Status handling.
+      if (empty($this->configuration['status'])) {
+        return FALSE;
+      }
+
+      // User Role Handling.
+      // If a block has no roles associated, it is displayed for every role.
+      // For blocks with roles associated, if none of the user's roles matches
+      // the settings from this block, access is denied.
+      if (!empty($this->configuration['visibility']['role']['roles']) && !array_intersect(array_filter($this->configuration['visibility']['role']['roles']), array_keys($user->roles))) {
+        // No match.
+        return FALSE;
+      }
+
+      // Page path handling.
+      // Limited visibility blocks must list at least one page.
+      if (!empty($this->configuration['visibility']['path']['visibility']) && $this->configuration['visibility']['path']['visibility'] == BLOCK_VISIBILITY_LISTED && empty($this->configuration['visibility']['path']['pages'])) {
+        return FALSE;
+      }
+
+      // Match path if necessary.
+      if (!empty($this->configuration['visibility']['path']['pages'])) {
+        // Convert path to lowercase. This allows comparison of the same path
+        // with different case. Ex: /Page, /page, /PAGE.
+        $pages = drupal_strtolower($this->configuration['visibility']['path']['pages']);
+        if ($this->configuration['visibility']['path']['visibility'] < BLOCK_VISIBILITY_PHP) {
+          // Compare the lowercase path alias (if any) and internal path.
+          $path = current_path();
+          $path_alias = drupal_strtolower(drupal_get_path_alias($path));
+          $page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages));
+          // When $block->visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED),
+          // the block is displayed on all pages except those listed in $block->pages.
+          // When set to 1 (BLOCK_VISIBILITY_LISTED), it is displayed only on those
+          // pages listed in $block->pages.
+          $page_match = !($this->configuration['visibility']['path']['visibility'] xor $page_match);
+        }
+        elseif (module_exists('php')) {
+          $page_match = php_eval($this->configuration['visibility']['path']['pages']);
+        }
+        else {
+          $page_match = FALSE;
+        }
+      }
+      else {
+        $page_match = TRUE;
+      }
+      if (!$page_match) {
+        return FALSE;
+      }
+
+      // Language visibility settings.
+      if (!empty($this->configuration['visibility']['language']['langcodes']) && array_filter($this->configuration['visibility']['language']['langcodes'])) {
+        if (empty($this->configuration['visibility']['language']['langcodes'][language($this->configuration['visibility']['language']['language_type'])->langcode])) {
+          return FALSE;
+        }
+      }
+
+      // Check other modules for block access rules.
+      foreach (module_implements('block_access') as $module) {
+        if (module_invoke($module, 'block_access', $this) === FALSE) {
+          return FALSE;
+        }
+      }
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Implements BlockInterface::configureWrapper().
+   */
+  public function configureWrapper($form, &$form_state) {
+    $definition = $this->getDefinition();
+    $config = $this->getConfig();
+    $form['id'] = array(
+      '#type' => 'value',
+      '#value' => $definition['id'],
+    );
+    $form['module'] = array(
+      '#type' => 'value',
+      '#value' => $definition['module'],
+    );
+
+    // Get the block subject for the page title.
+    $subject = isset($config['subject']) ? $config['subject'] : '';
+    if ($subject) {
+      drupal_set_title(t("'%subject' block", array('%subject' => $subject)), PASS_THROUGH);
+    }
+
+    $form['settings']['machine_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Block machine name'),
+      '#maxlength' => 64,
+      '#description' => t('A unique name to save this block configuration. Must be alpha-numeric and be underscore separated.'),
+      '#default_value' => isset($config['config_id']) ? $config['config_id'] : '',
+      '#weight' => -20,
+      '#required' => TRUE,
+    );
+    if (isset($config['config_id'])) {
+      $form['settings']['machine_name']['#disabled'] = TRUE;
+    }
+
+    $form['settings']['title'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Block title'),
+      '#maxlength' => 64,
+      '#description' => $definition['module'] == 'block' ? t('The title of the block as shown to the user.') : t('Override the default title for the block. Use <em>!placeholder</em> to display no title, or leave blank to use the default block title.', array('!placeholder' => '&lt;none&gt;')),
+      '#default_value' => isset($subject) ? $subject : '',
+      '#weight' => -19,
+    );
+
+    // Region settings.
+    $form['regions'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Region settings'),
+      '#collapsible' => FALSE,
+      '#description' => t('Specify in which themes and regions this block is displayed.'),
+    );
+
+    $theme_default = variable_get('theme_default', 'stark');
+    $admin_theme = variable_get('admin_theme');
+    $themes = list_themes();
+    $key = $form['theme']['#value'];
+    $theme = $themes[$key];
+    // Only display enabled themes
+    if ($theme->status) {
+      // Use a meaningful title for the main site theme and administrative
+      // theme.
+      $theme_title = $theme->info['name'];
+      if ($key == $theme_default) {
+        $theme_title = t('!theme (default theme)', array('!theme' => $theme_title));
+      }
+      elseif ($admin_theme && $key == $admin_theme) {
+        $theme_title = t('!theme (administration theme)', array('!theme' => $theme_title));
+      }
+      $form['regions']['region'] = array(
+        '#type' => 'select',
+        '#title' => $theme_title,
+        '#default_value' => !empty($config['region']) && $config['region'] != -1 ? $config['region'] : NULL,
+        '#empty_value' => BLOCK_REGION_NONE,
+        '#options' => system_region_list($key, REGIONS_VISIBLE),
+        '#required' => TRUE,
+      );
+    }
+
+    // Visibility settings.
+    $form['visibility_title'] = array(
+      '#type' => 'item',
+      '#title' => t('Visibility settings'),
+    );
+    $form['visibility'] = array(
+      '#type' => 'vertical_tabs',
+      '#attached' => array(
+        'js' => array(drupal_get_path('module', 'block') . '/block.js'),
+      ),
+      '#tree' => TRUE,
+    );
+
+    // Per-path visibility.
+    $form['visibility']['path'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Pages'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#group' => 'visibility',
+      '#weight' => 0,
+    );
+
+    //@TODO remove this access check and inject it in some other way.
+    // In fact this entire visibility settings section probably needs a
+    // separate user interface in the near future.
+    $access = user_access('use PHP for settings');
+    if (!empty($config['visibility']['path']['visibility']) && $config['visibility']['path']['visibility'] == BLOCK_VISIBILITY_PHP && !$access) {
+      $form['visibility']['path']['visibility'] = array(
+        '#type' => 'value',
+        '#value' => BLOCK_VISIBILITY_PHP,
+      );
+      $form['visibility']['path']['pages'] = array(
+        '#type' => 'value',
+        '#value' => !empty($config['visibility']['path']['pages']) ? $config['visibility']['path']['pages'] : '',
+      );
+    }
+    else {
+      $options = array(
+        BLOCK_VISIBILITY_NOTLISTED => t('All pages except those listed'),
+        BLOCK_VISIBILITY_LISTED => t('Only the listed pages'),
+      );
+      $description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '<front>'));
+
+      if (module_exists('php') && $access) {
+        $options += array(BLOCK_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'));
+        $title = t('Pages or PHP code');
+        $description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>'));
+      }
+      else {
+        $title = t('Pages');
+      }
+      $form['visibility']['path']['visibility'] = array(
+        '#type' => 'radios',
+        '#title' => t('Show block on specific pages'),
+        '#options' => $options,
+        '#default_value' => !empty($this->configuration['visibility']['path']['visibility']) ? $this->configuration['visibility']['path']['visibility'] : BLOCK_VISIBILITY_NOTLISTED,
+      );
+      $form['visibility']['path']['pages'] = array(
+        '#type' => 'textarea',
+        '#title' => '<span class="element-invisible">' . $title . '</span>',
+        '#default_value' => !empty($this->configuration['visibility']['path']['pages']) ? $this->configuration['visibility']['path']['pages'] : '',
+        '#description' => $description,
+      );
+    }
+
+    // Configure the block visibility per language.
+    if (module_exists('language') && language_multilingual()) {
+      $configurable_language_types = language_types_get_configurable();
+
+      // Fetch languages.
+      $languages = language_list(LANGUAGE_ALL);
+      foreach ($languages as $language) {
+        // @TODO $language->name is not wrapped with t(), it should be replaced
+        // by CMI translation implementation.
+        $langcodes_options[$language->langcode] = $language->name;
+      }
+      $form['visibility']['language'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Languages'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+        '#group' => 'visibility',
+        '#weight' => 5,
+      );
+      // If there are multiple configurable language types, let the user pick
+      // which one should be applied to this visibility setting. This way users
+      // can limit blocks by interface language or content language for exmaple.
+      $language_types = language_types_info();
+      $language_type_options = array();
+      foreach ($configurable_language_types as $type_key) {
+        $language_type_options[$type_key] = $language_types[$type_key]['name'];
+      }
+      $form['visibility']['language']['language_type'] = array(
+        '#type' => 'radios',
+        '#title' => t('Language type'),
+        '#options' => $language_type_options,
+        '#default_value' => !empty($this->configuration['visibility']['language']['language_type']) ? $this->configuration['visibility']['language']['language_type'] : $configurable_language_types[0],
+        '#access' => count($language_type_options) > 1,
+      );
+      $form['visibility']['language']['langcodes'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Show this block only for specific languages'),
+        '#default_value' => !empty($this->configuration['visibility']['language']['langcodes']) ? $this->configuration['visibility']['language']['langcodes'] : array(),
+        '#options' => $langcodes_options,
+        '#description' => t('Show this block only for the selected language(s). If you select no languages, the block will be visibile in all languages.'),
+      );
+    }
+
+    // Per-role visibility.
+    $role_options = array_map('check_plain', user_roles());
+    $form['visibility']['role'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Roles'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#group' => 'visibility',
+      '#weight' => 10,
+    );
+    $form['visibility']['role']['roles'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Show block for specific roles'),
+      '#default_value' => !empty($this->configuration['visibility']['role']['roles']) ? $this->configuration['visibility']['role']['roles'] : array(),
+      '#options' => $role_options,
+      '#description' => t('Show this block only for the selected role(s). If you select no roles, the block will be visible to all users.'),
+    );
+
+    $form += $this->configure($form, $form_state);
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save block'),
+    );
+
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    return array();
+  }
+
+  /**
+   * Implements BlockInterface::configureValidateWrapper().
+   */
+  public function configureValidateWrapper($form, &$form_state) {
+    if (empty($form['settings']['machine_name']['#disabled'])) {
+      if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['machine_name'])) {
+        form_set_error('machine_name', t('Block name must be alphanumeric or underscores only.'));
+      }
+      if (in_array('plugin.core.block.' . $form_state['values']['machine_name'], config_get_storage_names_with_prefix('plugin.core.block'))) {
+        form_set_error('machine_name', t('Block name must be unique.'));
+      }
+    }
+    else {
+      $config_id = explode('.', $form_state['values']['machine_name']);
+      $form_state['values']['machine_name'] = array_pop($config_id);
+    }
+    if ($form_state['values']['module'] == 'block') {
+      $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', 0, 1, array(
+        ':bid' => $form_state['values']['delta'],
+        ':info' => $form_state['values']['info'],
+      ))->fetchField();
+      if (empty($form_state['values']['info']) || $custom_block_exists) {
+        form_set_error('info', t('Ensure that each block description is unique.'));
+      }
+    }
+    $form_state['values']['visibility']['role']['roles'] = array_filter($form_state['values']['visibility']['role']['roles']);
+    $this->configureValidate($form, $form_state);
+  }
+
+  /**
+   * Implements BlockInterface::configureValidate().
+   */
+  public function configureValidate($form, &$form_state) {}
+
+  /**
+   * Implements BlockInterface::configureSubmitWrapper().
+   */
+  public function configureSubmitWrapper($form, &$form_state) {
+    if (!form_get_errors()) {
+      $transaction = db_transaction();
+      try {
+        $keys = array(
+          'visibility' => 'visibility',
+          'pages' => 'pages',
+          'custom' => 'custom',
+          'title' => 'subject',
+          'module' => 'module',
+          'region' => 'region',
+        );
+        foreach ($keys as $key => $new_key) {
+          if (isset($form_state['values'][$key])) {
+            $this->configuration[$new_key] = $form_state['values'][$key];
+          }
+        }
+      }
+      catch (Exception $e) {
+        $transaction->rollback();
+        watchdog_exception('block', $e);
+        throw $e;
+      }
+      if (empty($this->configuration['weight'])) {
+        $this->configuration['weight'] = 0;
+      }
+      drupal_set_message(t('The block configuration has been saved.'));
+      drupal_flush_all_caches();
+      $this->configureSubmit($form, $form_state);
+    }
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {}
+}
diff --git a/core/modules/block/lib/Drupal/block/BlockBundle.php b/core/modules/block/lib/Drupal/block/BlockBundle.php
new file mode 100644
index 0000000..5ea31a3
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/BlockBundle.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\block\BlockBundle.
+ */
+
+namespace Drupal\Block;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Block dependency injection container.
+ */
+class BlockBundle extends Bundle {
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle.
+   */
+  public function build(ContainerBuilder $container) {
+    // register the BlockManager class with the dependency injection container.
+    $container->register('plugin.manager.block', 'Drupal\block\Plugin\Type\BlockManager');
+  }
+}
\ No newline at end of file
diff --git a/core/modules/block/lib/Drupal/block/BlockInterface.php b/core/modules/block/lib/Drupal/block/BlockInterface.php
new file mode 100644
index 0000000..601034a
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/BlockInterface.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\block;
+
+/**
+ * Interface definition for Block plugins.
+ */
+interface BlockInterface {
+
+  /**
+   * The default settings for the block.
+   *
+   * @return array().
+   */
+  public function settings();
+
+  /**
+   * An access wrapper method.
+   *
+   * @return bool.
+   */
+  public function accessWrapper();
+
+  /**
+   * A simple access method.
+   *
+   * @return bool.
+   */
+  public function access();
+
+  /**
+   * The configuration wrapper form for the block.
+   *
+   * @return form array().
+   */
+  public function configure($form, &$form_state);
+
+  /**
+   * The validation wrapper for the configuration form.
+   */
+  public function configureValidate($form, &$form_state);
+
+  /**
+   * The submission wrapper for the configuration form.
+   */
+  public function configureSubmit($form, &$form_state);
+
+  /**
+   * The configuration form for the block.
+   *
+   * @return form array().
+   */
+  public function configureWrapper($form, &$form_state);
+
+  /**
+   * The validation for the configuration form.
+   */
+  public function configureValidateWrapper($form, &$form_state);
+
+  /**
+   * The submission for the configuration form.
+   */
+  public function configureSubmitWrapper($form, &$form_state);
+
+  /**
+   * A function for building renderable block arrays. A block theme wrapper
+   * will be wrapped around this by the caller.
+   *
+   * @return renderable array().
+   */
+  public function build();
+}
diff --git a/core/modules/block/lib/Drupal/block/Plugin/Derivative/BlockPluginUI.php b/core/modules/block/lib/Drupal/block/Plugin/Derivative/BlockPluginUI.php
new file mode 100644
index 0000000..13db3ac
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/Plugin/Derivative/BlockPluginUI.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\block\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+class BlockPluginUI implements DerivativeInterface {
+  protected $derivatives = array();
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+    $this->getDerivativeDefinitions($base_plugin_definition);
+    return $this->derivatives[$derivative_id];
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    foreach (list_themes() as $key => $theme) {
+      $this->derivatives[$key] = $base_plugin_definition;
+    }
+    return $this->derivatives;
+  }
+}
\ No newline at end of file
diff --git a/core/modules/block/lib/Drupal/block/Plugin/Type/BlockManager.php b/core/modules/block/lib/Drupal/block/Plugin/Type/BlockManager.php
new file mode 100644
index 0000000..6ffb31f
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/Plugin/Type/BlockManager.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\block\Plugin\Type;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\AlterDecorator;
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Core\Plugin\Mapper\ConfigMapper;
+
+class BlockManager extends PluginManagerBase {
+  public function __construct() {
+    $this->discovery = new AlterDecorator(new DerivativeDiscoveryDecorator(new AnnotatedClassDiscovery('block', 'block')), 'block');
+    $this->factory = new DefaultFactory($this);
+    $this->mapper = new ConfigMapper($this);
+  }
+}
\ No newline at end of file
diff --git a/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php b/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php
new file mode 100644
index 0000000..edf0351
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\block\Plugin\system\plugin_ui;
+
+use Drupal\system\Plugin\PluginUIBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "block_plugin_ui",
+ *   title = @Translation("Block Library"),
+ *   title_attribute = "subject",
+ *   menu = TRUE,
+ *   all_plugins = @Translation("All Blocks"),
+ *   link_title = @Translation("Configure Block"),
+ *   config_path = "admin/structure/block/manage",
+ *   path = "admin/structure/block/list",
+ *   suffix = "add",
+ *   type = MENU_LOCAL_ACTION,
+ *   manager = "Drupal\block\Plugin\Type\BlockManager",
+ *   derivative = "Drupal\block\Plugin\Derivative\BlockPluginUI",
+ *   facets = {
+ *     "module" = @Translation("Modules")
+ *   },
+ *   default_task = TRUE,
+ *   task_title = @Translation("Library"),
+ *   task_suffix = "library"
+ * )
+ */
+class BlockPluginUI extends PluginUIBase {
+  public function form($form, &$form_state, $plugin, $facet) {
+    list($plugin_id, $theme) = explode(':', $plugin);
+    $form['theme'] = array(
+      '#type' => 'value',
+      '#value' => $theme,
+    );
+    return parent::form($form, $form_state, $plugin, $facet);
+  }
+
+  public function excludeDefinitions($definitions) {
+    foreach ($definitions as $key => $definition) {
+      if (!empty($definition['custom'])) {
+        unset($definitions[$key]);
+      }
+    }
+    return $definitions;
+  }
+
+  public function formSubmit($form, &$form_state) {
+    $form_state['redirect'] = 'admin/structure/block/manage/' . $form_state['values']['block'] . '/' . $form_state['values']['theme'];
+  }
+
+  public function access($plugin_id) {
+    list($plugin, $theme) = explode(':', $plugin_id);
+    return _block_themes_access($theme);
+  }
+
+  public function tableHeader() {
+    return array(t('Subject'), t('Operations'));
+  }
+
+  public function row($plugin, $plugin_definition, $plugin_id, $config) {
+    list($plugin, $theme) = explode(':', $plugin);
+    return array($config['subject'], l($plugin_definition['link_title'], $plugin_definition['config_path'] . '/' . $plugin_id . '/' . $theme));
+  }
+}
\ No newline at end of file
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php
index c3a220f..8c64ac8 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php
@@ -38,14 +38,14 @@ function testAdminTheme() {
     $this->drupalLogin($admin_user);
 
     // Ensure that access to block admin page is denied when theme is disabled.
-    $this->drupalGet('admin/structure/block/list/bartik');
+    $this->drupalGet('admin/structure/block/list/block_plugin_ui:bartik');
     $this->assertResponse(403);
 
     // Enable admin theme and confirm that tab is accessible.
     theme_enable(array('bartik'));
     $edit['admin_theme'] = 'bartik';
     $this->drupalPost('admin/appearance', $edit, t('Save configuration'));
-    $this->drupalGet('admin/structure/block/list/bartik');
+    $this->drupalGet('admin/structure/block/list/block_plugin_ui:bartik');
     $this->assertResponse(200);
   }
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php
index 3a5776c..6bbccdf 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php
@@ -49,8 +49,12 @@ function setUp() {
     $this->normal_user_alt->save();
 
     // Enable our test block.
-    $edit['blocks[block_test_test_cache][region]'] = 'sidebar_first';
-    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+    $this->theme = variable_get('theme_default', 'stark');
+    $block = array();
+    $block['machine_name'] = $this->randomName(8);
+    $block['region'] = 'sidebar_first';
+    $this->block = $block;
+    $this->drupalPost('admin/structure/block/manage/test_cache/' . $this->theme,  $block, t('Save block'));
   }
 
   /**
@@ -192,14 +196,17 @@ function testCachePerPage() {
    * Private helper method to set the test block's cache mode.
    */
   private function setCacheMode($cache_mode) {
-    db_update('block')
-      ->fields(array('cache' => $cache_mode))
-      ->condition('module', 'block_test')
-      ->execute();
-
-    $current_mode = db_query("SELECT cache FROM {block} WHERE module = 'block_test'")->fetchField();
-    if ($current_mode != $cache_mode) {
-      $this->fail(t('Unable to set cache mode to %mode. Current mode: %current_mode', array('%mode' => $cache_mode, '%current_mode' => $current_mode)));
+    $block = $this->block;
+    $block['config_id'] = 'plugin.core.block.' . $this->theme . '.' . $block['machine_name'];
+    $block_config = config($block['config_id']);
+    $block_config->set('cache', $cache_mode);
+    $block_config->save();
+
+    $instance = block_load($block['config_id']);
+    $config = $instance->getConfig();
+    if ($config['cache'] != $cache_mode) {
+      $this->fail(t('Unable to set cache mode to %mode. Current mode: %current_mode', array('%mode' => $cache_mode, '%current_mode' => $config['cache'])));
     }
+    $this->assertEqual($config['cache'], $cache_mode, t("Test block's database entry updated to DRUPAL_NO_CACHE."));
   }
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php
index af5acb9..ecfade3 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php
@@ -14,6 +14,7 @@
  */
 class BlockHiddenRegionTest extends WebTestBase {
 
+  protected $admin_user;
   /**
    * Modules to enable.
    *
@@ -32,30 +33,31 @@ public static function getInfo() {
   function setUp() {
     parent::setUp();
 
-    // Enable Search block in default theme.
-    db_merge('block')
-      ->key(array(
-        'module' => 'search',
-        'delta' => 'form',
-        'theme' => variable_get('theme_default', 'stark'),
-      ))
-      ->fields(array(
-        'status' => 1,
-        'weight' => -1,
-        'region' => 'sidebar_first',
-        'pages' => '',
-        'cache' => -1,
-      ))
-      ->execute();
+    // Create administrative user.
+    $this->admin_user = $this->drupalCreateUser(array(
+      'administer blocks',
+      'administer themes',
+      'search content',
+      )
+    );
+
+    $this->drupalLogin($this->admin_user);
+
+    $default_theme = variable_get('theme_default', 'stark');
+
+    $block['machine_name'] = $this->randomName();
+    $block['region'] = 'sidebar_first';
+    $block['title'] = $this->randomName();
+    $this->drupalPost('admin/structure/block/manage/search_form_block/' . $default_theme, $block, t('Save block'));
+    $this->assertText('The block configuration has been saved.', 'Block was saved');
   }
 
   /**
    * Tests that hidden regions do not inherit blocks when a theme is enabled.
    */
-  function testBlockNotInHiddenRegion() {
-    // Create administrative user.
-    $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes', 'search content'));
-    $this->drupalLogin($admin_user);
+  public function testBlockNotInHiddenRegion() {
+
+    $this->drupalLogin($this->admin_user);
 
     // Ensure that the search form block is displayed.
     $this->drupalGet('');
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockHtmlIdTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockHtmlIdTest.php
index 705bc76..91cc7c1 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockHtmlIdTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockHtmlIdTest.php
@@ -14,6 +14,8 @@
  */
 class BlockHtmlIdTest extends WebTestBase {
 
+  protected $adminUser;
+
   /**
    * Modules to enable.
    *
@@ -33,16 +35,19 @@ function setUp() {
     parent::setUp();
 
     // Create an admin user, log in and enable test blocks.
-    $this->admin_user = $this->drupalCreateUser(array('administer blocks', 'access administration pages'));
-    $this->drupalLogin($this->admin_user);
-
-    // Enable our test block.
-    $edit['blocks[block_test_test_html_id][region]'] = 'sidebar_first';
-    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+    $this->adminUser = $this->drupalCreateUser(array('administer blocks', 'access administration pages'));
+    $this->drupalLogin($this->adminUser);
 
-    // Make sure the block has some content so it will appear
+    // Make sure the block has some content so it will appear.
     $current_content = $this->randomName();
     variable_set('block_test_content', $current_content);
+
+    // Enable our test block.
+    $default_theme = variable_get('theme_default', 'stark');
+    $block = array();
+    $block['machine_name'] = 'test_id_block';
+    $block['region'] = 'sidebar_first';
+    $this->drupalPost('admin/structure/block/manage/test_html_id' . '/' . $default_theme, array('machine_name' => $block['machine_name'], 'region' => $block['region']), t('Save block'));
   }
 
   /**
@@ -50,6 +55,6 @@ function setUp() {
    */
   function testHtmlId() {
     $this->drupalGet('');
-    $this->assertRaw('block-block-test-test-html-id', 'HTML id for test block is valid.');
+    $this->assertRaw('id="block-test-id-block"', 'HTML id for test block is valid.');
   }
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php
index 6d0c6bc..927d217 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php
@@ -14,6 +14,7 @@
  */
 class BlockLanguageTest extends WebTestBase {
 
+  protected $adminUser;
   /**
    * Modules to enable.
    *
@@ -29,15 +30,12 @@ public static function getInfo() {
     );
   }
 
-  /**
-   * Tests the visibility settings for the blocks based on language.
-   */
-  public function testLanguageBlockVisibility() {
+  function setUp() {
+    parent::setUp();
+
     // Create a new user, allow him to manage the blocks and the languages.
-    $admin_user = $this->drupalCreateUser(array(
-      'administer languages', 'administer blocks',
-    ));
-    $this->drupalLogin($admin_user);
+    $this->adminUser = $this->drupalCreateUser(array('administer blocks', 'administer languages'));
+    $this->drupalLogin($this->adminUser);
 
     // Add predefined language.
     $edit = array(
@@ -45,156 +43,69 @@ public function testLanguageBlockVisibility() {
     );
     $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
     $this->assertText('French', 'Language added successfully.');
+  }
 
+  /**
+   * Tests the visibility settings for the blocks based on language.
+   */
+  public function testLanguageBlockVisibility() {
     // Check if the visibility setting is available.
-    $this->drupalGet('admin/structure/block/add');
-    $this->assertField('langcodes[en]', 'Language visibility field is visible.');
-
-    // Create a new block.
-    $info_name = $this->randomString(10);
-    $body = '';
-    for ($i = 0; $i <= 100; $i++) {
-      $body .= chr(rand(97, 122));
-    }
-    $edit = array(
-      'regions[stark]' => 'sidebar_first',
-      'info' => $info_name,
-      'title' => 'test',
-      'body[value]' => $body,
-    );
-    $this->drupalPost('admin/structure/block/add', $edit, t('Save block'));
+    $default_theme = variable_get('theme_default', 'stark');
+    $this->drupalGet('admin/structure/block/manage/system_powered_by_block' . '/' . $default_theme);
+
+    $this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.');
 
-    // Set visibility setting for one language.
+    // Enable a standard block and set the visibility setting for one language.
     $edit = array(
-      'langcodes[en]' => TRUE,
+      'visibility[language][langcodes][en]' => TRUE,
+      'machine_name' => $this->randomName(8),
+      'region' => 'sidebar_first',
     );
-    $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block'));
+    $this->drupalPost('admin/structure/block/manage/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block'));
 
     // Change the default language.
     $edit = array(
       'site_default' => 'fr',
     );
-    $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
+    $this->drupalpost('admin/config/regional/language', $edit, t('Save configuration'));
 
     // Reset the static cache of the language list.
     drupal_static_reset('language_list');
 
-    // Check that a page has a block
-    $this->drupalGet('', array('language' => language_load('en')));
-    $this->assertText($body, 'The body of the custom block appears on the page.');
+    // Check that a page has a block.
+    $this->drupalget('', array('language' => language_load('en')));
+    $this->assertText('Powered by Drupal', 'The body of the custom block appears on the page.');
 
-    // Check that a page doesn't has a block for the current language anymore
+    // Check that a page doesn't has a block for the current language anymore.
     $this->drupalGet('', array('language' => language_load('fr')));
-    $this->assertNoText($body, 'The body of the custom block does not appear on the page.');
+    $this->assertNoText('Powered by Drupal', 'The body of the custom block does not appear on the page.');
   }
 
   /**
    * Tests if the visibility settings are removed if the language is deleted.
    */
   public function testLanguageBlockVisibilityLanguageDelete() {
-    // Create a new user, allow him to manage the blocks and the languages.
-    $admin_user = $this->drupalCreateUser(array(
-      'administer languages', 'administer blocks',
-    ));
-    $this->drupalLogin($admin_user);
-
-    // Add predefined language.
-    $edit = array(
-      'predefined_langcode' => 'fr',
-    );
-    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
-    $this->assertText('French', 'Language added successfully.');
 
-    // Create a new block.
-    $info_name = $this->randomString(10);
-    $body = '';
-    for ($i = 0; $i <= 100; $i++) {
-      $body .= chr(rand(97, 122));
-    }
+    $default_theme = variable_get('theme_default', 'stark');
+    // Enable a standard block and set the visibility setting for one language.
     $edit = array(
-      'regions[stark]' => 'sidebar_first',
-      'info' => $info_name,
-      'title' => 'test',
-      'body[value]' => $body,
+      'visibility[language][langcodes][fr]' => TRUE,
+      'machine_name' => 'language_block_test',
+      'region' => 'sidebar_first',
     );
-    $this->drupalPost('admin/structure/block/add', $edit, t('Save block'));
+    $this->drupalPost('admin/structure/block/manage/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block'));
 
-    // Set visibility setting for one language.
-    $edit = array(
-      'langcodes[fr]' => TRUE,
-    );
-    $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block'));
-
-    // Check that we have an entry in the database after saving the setting.
-    $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array(
-      ':module' => 'block',
-      ':delta' => '1'
-    ))->fetchField();
-    $this->assertTrue($count == 1, 'The block language visibility has an entry in the database.');
+    // Check that we have the language in config fter saving the setting.
+    $config = config('plugin.core.block.' . $default_theme . '.language_block_test');
+    $setting = $config->get('visibility.language.langcodes.fr');
+    $this->assertTrue('fr' === $setting, 'Language was set in the block config.');
 
     // Delete the language.
     $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete'));
 
-    // Check that the setting related to this language has been deleted.
-    $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array(
-      ':module' => 'block',
-      ':delta' => '1'
-    ))->fetchField();
-    $this->assertTrue($count == 0, 'The block language visibility do not have an entry in the database.');
-  }
-
-  /**
-   * Tests if the visibility settings are removed if the block is deleted.
-   */
-  public function testLanguageBlockVisibilityBlockDelete() {
-    // Create a new user, allow him to manage the blocks and the languages.
-    $admin_user = $this->drupalCreateUser(array(
-      'administer languages', 'administer blocks',
-    ));
-    $this->drupalLogin($admin_user);
-
-    // Add predefined language.
-    $edit = array(
-      'predefined_langcode' => 'fr',
-    );
-    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
-    $this->assertText('French', 'Language added successfully.');
-
-    // Create a new block.
-    $info_name = $this->randomString(10);
-    $body = '';
-    for ($i = 0; $i <= 100; $i++) {
-      $body .= chr(rand(97, 122));
-    }
-    $edit = array(
-      'regions[stark]' => 'sidebar_first',
-      'info' => $info_name,
-      'title' => 'test',
-      'body[value]' => $body,
-    );
-    $this->drupalPost('admin/structure/block/add', $edit, t('Save block'));
-
-    // Set visibility setting for one language.
-    $edit = array(
-      'langcodes[fr]' => TRUE,
-    );
-    $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block'));
-
-    // Check that we have an entry in the database after saving the setting.
-    $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array(
-      ':module' => 'block',
-      ':delta' => '1'
-    ))->fetchField();
-    $this->assertTrue($count == 1, 'The block language visibility has an entry in the database.');
-
-    // Delete the custom block.
-    $this->drupalPost('admin/structure/block/manage/block/1/delete', array(), t('Delete'));
-
-    // Check that the setting related to this block has been deleted.
-    $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array(
-      ':module' => 'block',
-      ':delta' => '1'
-    ))->fetchField();
-    $this->assertTrue($count == 0, 'The block language visibility do not have an entry in the database.');
+    // Check that we have the language in config fter saving the setting.
+    $config = config('plugin.core.block.' . $default_theme . '.language_block_test');
+    $setting = $config->get('visibility.language.langcodes.fr');
+    $this->assertFalse('fr' === $setting, 'Language was not set in the block config.');
   }
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
index 4eb7683..5dc5378 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
@@ -19,7 +19,7 @@ class BlockTest extends WebTestBase {
   public static $modules = array('block', 'test_page_test');
 
   protected $regions;
-  protected $admin_user;
+  protected $adminUser;
 
   public static function getInfo() {
     return array(
@@ -35,25 +35,15 @@ function setUp() {
     // Use the test page as the front page.
     config('system.site')->set('page.front', 'test-page')->save();
 
-    // Create Full HTML text format.
-    $full_html_format = array(
-      'format' => 'full_html',
-      'name' => 'Full HTML',
-    );
-    $full_html_format = (object) $full_html_format;
-    filter_format_save($full_html_format);
-    $this->checkPermissions(array(), TRUE);
-
     // Create and log in an administrative user having access to the Full HTML
     // text format.
-    $this->admin_user = $this->drupalCreateUser(array(
+    $this->adminUser = $this->drupalCreateUser(array(
       'administer blocks',
-      filter_permission_name($full_html_format),
       'access administration pages',
     ));
-    $this->drupalLogin($this->admin_user);
+    $this->drupalLogin($this->adminUser);
 
-    // Define the existing regions
+    // Define the existing regions.
     $this->regions = array();
     $this->regions[] = 'header';
     $this->regions[] = 'sidebar_first';
@@ -63,145 +53,25 @@ function setUp() {
   }
 
   /**
-   * Test creating custom block, moving it to a specific region and then deleting it.
-   */
-  function testCustomBlock() {
-    // Enable a second theme.
-    theme_enable(array('seven'));
-
-    // Confirm that the add block link appears on block overview pages.
-    $this->drupalGet('admin/structure/block');
-    $this->assertRaw(l('Add block', 'admin/structure/block/add'), 'Add block link is present on block overview page for default theme.');
-    $this->drupalGet('admin/structure/block/list/seven');
-    $this->assertRaw(l('Add block', 'admin/structure/block/list/seven/add'), 'Add block link is present on block overview page for non-default theme.');
-
-    // Confirm that hidden regions are not shown as options for block placement
-    // when adding a new block.
-    theme_enable(array('bartik'));
-    $themes = list_themes();
-    $this->drupalGet('admin/structure/block/add');
-    foreach ($themes as $key => $theme) {
-      if ($theme->status) {
-        foreach ($theme->info['regions_hidden'] as $hidden_region) {
-          $elements = $this->xpath('//select[@id=:id]//option[@value=:value]', array(':id' => 'edit-regions-' . $key, ':value' => $hidden_region));
-          $this->assertFalse(isset($elements[0]), format_string('The hidden region @region is not available for @theme.', array('@region' => $hidden_region, '@theme' => $key)));
-        }
-      }
-    }
-
-    // Add a new custom block by filling out the input form on the admin/structure/block/add page.
-    $custom_block = array();
-    $custom_block['info'] = $this->randomName(8);
-    $custom_block['title'] = $this->randomName(8);
-    $custom_block['body[value]'] = $this->randomName(32);
-    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
-
-    // Confirm that the custom block has been created, and then query the created bid.
-    $this->assertText(t('The block has been created.'), 'Custom block successfully created.');
-    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
-
-    // Check to see if the custom block was created by checking that it's in the database.
-    $this->assertNotNull($bid, 'Custom block found in database');
-
-    // Check that block_block_view() returns the correct title and content.
-    $data = block_block_view($bid);
-    $format = db_query("SELECT format FROM {block_custom} WHERE bid = :bid", array(':bid' => $bid))->fetchField();
-    $this->assertTrue(array_key_exists('subject', $data) && empty($data['subject']), 'block_block_view() provides an empty block subject, since custom blocks do not have default titles.');
-    $this->assertEqual(check_markup($custom_block['body[value]'], $format), $data['content'], 'block_block_view() provides correct block content.');
-
-    // Check whether the block can be moved to all available regions.
-    $custom_block['module'] = 'block';
-    $custom_block['delta'] = $bid;
-    foreach ($this->regions as $region) {
-      $this->moveBlockToRegion($custom_block, $region);
-    }
-
-    // Verify presence of configure and delete links for custom block.
-    $this->drupalGet('admin/structure/block');
-    $this->assertLinkByHref('admin/structure/block/manage/block/' . $bid . '/configure', 0, 'Custom block configure link found.');
-    $this->assertLinkByHref('admin/structure/block/manage/block/' . $bid . '/delete', 0, 'Custom block delete link found.');
-
-    // Set visibility only for authenticated users, to verify delete functionality.
-    $edit = array();
-    $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE;
-    $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/configure', $edit, t('Save block'));
-
-    // Delete the created custom block & verify that it's been deleted and no longer appearing on the page.
-    $this->clickLink(t('delete'));
-    $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/delete', array(), t('Delete'));
-    $this->assertRaw(t('The block %title has been removed.', array('%title' => $custom_block['info'])), 'Custom block successfully deleted.');
-    $this->assertNoText(t($custom_block['title']), 'Custom block no longer appears on page.');
-    $count = db_query("SELECT 1 FROM {block_role} WHERE module = :module AND delta = :delta", array(':module' => $custom_block['module'], ':delta' => $custom_block['delta']))->fetchField();
-    $this->assertFalse($count, 'Table block_role being cleaned.');
-  }
-
-  /**
-   * Test creating custom block using Full HTML.
-   */
-  function testCustomBlockFormat() {
-    // Add a new custom block by filling out the input form on the admin/structure/block/add page.
-    $custom_block = array();
-    $custom_block['info'] = $this->randomName(8);
-    $custom_block['title'] = $this->randomName(8);
-    $custom_block['body[value]'] = '<h1>Full HTML</h1>';
-    $full_html_format = filter_format_load('full_html');
-    $custom_block['body[format]'] = $full_html_format->format;
-    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
-
-    // Set the created custom block to a specific region.
-    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
-    $edit = array();
-    $edit['blocks[block_' . $bid . '][region]'] = $this->regions[1];
-    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
-
-    // Confirm that the custom block is being displayed using configured text format.
-    $this->drupalGet('');
-    $this->assertRaw('<h1>Full HTML</h1>', 'Custom block successfully being displayed using Full HTML.');
-
-    // Confirm that a user without access to Full HTML can not see the body field,
-    // but can still submit the form without errors.
-    $block_admin = $this->drupalCreateUser(array('administer blocks'));
-    $this->drupalLogin($block_admin);
-    $this->drupalGet('admin/structure/block/manage/block/' . $bid . '/configure');
-    $this->assertFieldByXPath("//textarea[@name='body[value]' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Body field contains denied message');
-    $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/configure', array(), t('Save block'));
-    $this->assertNoText(t('Ensure that each block description is unique.'));
-
-    // Confirm that the custom block is still being displayed using configured text format.
-    $this->drupalGet('');
-    $this->assertRaw('<h1>Full HTML</h1>', 'Custom block successfully being displayed using Full HTML.');
-  }
-
-  /**
    * Test block visibility.
    */
   function testBlockVisibility() {
-    $block = array();
-
-    // Create a random title for the block
+    $block_name = 'system_powered_by_block';
+    // Create a random title for the block,
     $title = $this->randomName(8);
-
-    // Create the custom block
-    $custom_block = array();
-    $custom_block['info'] = $this->randomName(8);
-    $custom_block['title'] = $title;
-    $custom_block['body[value]'] = $this->randomName(32);
-    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
-
-    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
-    $block['module'] = 'block';
-    $block['delta'] = $bid;
-    $block['title'] = $title;
-
+    // Enable a standard block.
+    $default_theme = variable_get('theme_default', 'stark');
+    $edit = array(
+      'machine_name' => $this->randomName(8),
+      'region' => 'sidebar_first',
+      'title' => $title,
+    );
     // Set the block to be hidden on any user path, and to be shown only to
     // authenticated users.
-    $edit = array();
-    $edit['pages'] = 'user*';
-    $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE;
-    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
-
-    // Move block to the first sidebar.
-    $this->moveBlockToRegion($block, $this->regions[1]);
+    $edit['visibility[path][pages]'] = 'user*';
+    $edit['visibility[role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE;
+    $this->drupalPost('admin/structure/block/manage/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
+    $this->assertText('The block configuration has been saved.', 'Block was saved');
 
     $this->drupalGet('');
     $this->assertText($title, 'Block was displayed on the front page.');
@@ -209,7 +79,7 @@ function testBlockVisibility() {
     $this->drupalGet('user');
     $this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
 
-    $this->drupalGet('USER/' . $this->admin_user->uid);
+    $this->drupalGet('USER/' . $this->adminUser->uid);
     $this->assertNoText($title, 'Block was not displayed according to block visibility rules regardless of path case.');
 
     // Confirm that the block is not displayed to anonymous users.
@@ -218,7 +88,7 @@ function testBlockVisibility() {
     $this->assertNoText($title, 'Block was not displayed to anonymous users.');
 
     // Confirm that an empty block is not displayed.
-    $this->assertNoRaw('block-system-help', 'Empty block not displayed.');
+    $this->assertNoText('Powered by Drupal', 'Empty block not displayed.');
   }
 
   /**
@@ -226,31 +96,21 @@ function testBlockVisibility() {
    * "pages" textarea empty
    */
   function testBlockVisibilityListedEmpty() {
-    $block = array();
-
-    // Create a random title for the block
+    $block_name = 'system_powered_by_block';
+    // Create a random title for the block,
     $title = $this->randomName(8);
-
-    // Create the custom block
-    $custom_block = array();
-    $custom_block['info'] = $this->randomName(8);
-    $custom_block['title'] = $title;
-    $custom_block['body[value]'] = $this->randomName(32);
-    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
-
-    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
-    $block['module'] = 'block';
-    $block['delta'] = $bid;
-    $block['title'] = $title;
-
-    // Move block to the first sidebar.
-    $this->moveBlockToRegion($block, $this->regions[1]);
-
+    // Enable a standard block.
+    $default_theme = variable_get('theme_default', 'stark');
+    $edit = array(
+      'machine_name' => $this->randomName(8),
+      'region' => 'sidebar_first',
+      'title' => $title,
+      'visibility[path][visibility]' => BLOCK_VISIBILITY_LISTED,
+    );
     // Set the block to be hidden on any user path, and to be shown only to
     // authenticated users.
-    $edit = array();
-    $edit['visibility'] = BLOCK_VISIBILITY_LISTED;
-    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
+    $this->drupalPost('admin/structure/block/manage/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
+    $this->assertText('The block configuration has been saved.', 'Block was saved');
 
     $this->drupalGet('user');
     $this->assertNoText($title, 'Block was not displayed according to block visibility rules.');
@@ -265,76 +125,28 @@ function testBlockVisibilityListedEmpty() {
   }
 
   /**
-   * Test user customization of block visibility.
-   */
-  function testBlockVisibilityPerUser() {
-    $block = array();
-
-    // Create a random title for the block.
-    $title = $this->randomName(8);
-
-    // Create our custom test block.
-    $custom_block = array();
-    $custom_block['info'] = $this->randomName(8);
-    $custom_block['title'] = $title;
-    $custom_block['body[value]'] = $this->randomName(32);
-    $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
-
-    $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField();
-    $block['module'] = 'block';
-    $block['delta'] = $bid;
-    $block['title'] = $title;
-
-    // Move block to the first sidebar.
-    $this->moveBlockToRegion($block, $this->regions[1]);
-
-    // Set the block to be customizable per user, visible by default.
-    $edit = array();
-    $edit['custom'] = BLOCK_CUSTOM_ENABLED;
-    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
-
-    // Disable block visibility for the admin user.
-    $edit = array();
-    $edit['block[' . $block['module'] . '][' . $block['delta'] . ']'] = FALSE;
-    $this->drupalPost('user/' . $this->admin_user->uid . '/edit', $edit, t('Save'));
-
-    $this->drupalGet('user');
-    $this->assertNoText($block['title'], 'Block was not displayed according to per user block visibility setting.');
-
-    // Set the block to be customizable per user, hidden by default.
-    $edit = array();
-    $edit['custom'] = BLOCK_CUSTOM_DISABLED;
-    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
-
-    // Enable block visibility for the admin user.
-    $edit = array();
-    $edit['block[' . $block['module'] . '][' . $block['delta'] . ']'] = TRUE;
-    $this->drupalPost('user/' . $this->admin_user->uid . '/edit', $edit, t('Save'));
-
-    $this->drupalGet('user');
-    $this->assertText($block['title'], 'Block was displayed according to per user block visibility setting.');
-  }
-
-  /**
    * Test configuring and moving a module-define block to specific regions.
    */
-  function testBlock() {
-    // Select the Administration menu block to be configured and moved.
-    $block = array();
-    $block['module'] = 'system';
-    $block['delta'] = 'admin';
-    $block['title'] = $this->randomName(8);
-
-    // Set block title to confirm that interface works and override any custom titles.
-    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', array('title' => $block['title']), t('Save block'));
+  public function testBlock() {
+    // Select the 'Powered by Drupal' block to be configured and moved.
+    $block_id = 'system_powered_by_block';
+    $default_theme = variable_get('theme_default', 'stark');
+
+    $block = array(
+      'title' => $this->randomName(8),
+      'machine_name' => $this->randomName(8),
+      'region' => 'header',
+    );
+    // Set block title to confirm that interface works and override any custom
+    // titles.
+    $this->drupalPost('admin/structure/block/manage/' . $block_id . '/' . $default_theme, $block, t('Save block'));
     $this->assertText(t('The block configuration has been saved.'), 'Block title set.');
-    $bid = db_query("SELECT bid FROM {block} WHERE module = :module AND delta = :delta", array(
-      ':module' => $block['module'],
-      ':delta' => $block['delta'],
-    ))->fetchField();
+    // Check to see if the block was created by checking its configuration.
+    $config_id = 'plugin.core.block.' . $default_theme . '.' . $block['machine_name'];
 
-    // Check to see if the block was created by checking that it's in the database.
-    $this->assertNotNull($bid, 'Block found in database');
+    $config = config($config_id);
+    $stored_title = $config->get('subject');
+    $this->assertEqual($block['title'], $stored_title, 'Stored block title was found.');
 
     // Check whether the block can be moved to all available regions.
     foreach ($this->regions as $region) {
@@ -343,27 +155,20 @@ function testBlock() {
 
     // Set the block to the disabled region.
     $edit = array();
-    $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = '-1';
+    $edit['blocks[0][region]'] = -1;
     $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
 
     // Confirm that the block was moved to the proper region.
     $this->assertText(t('The block settings have been updated.'), 'Block successfully move to disabled region.');
+    $this->drupalGet('node');
     $this->assertNoText(t($block['title']), 'Block no longer appears on page.');
 
     // Confirm that the region's xpath is not available.
-    $xpath = $this->buildXPathQuery('//div[@id=:id]/*', array(':id' => 'block-block-' . $bid));
-    $this->assertNoFieldByXPath($xpath, FALSE, 'Custom block found in no regions.');
-
-    // For convenience of developers, put the Administration menu block back.
-    $edit = array();
-    $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $this->regions[1];
-    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
-    $this->assertText(t('The block settings have been updated.'), 'Block successfully move to first sidebar region.');
-
-    $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', array('title' => 'Tools'), t('Save block'));
-    $this->assertText(t('The block configuration has been saved.'), 'Block title set.');
+    $xpath = $this->buildXPathQuery('//div[@id=:id]/*', array(':id' => 'block-' . strtr(strtolower($block['machine_name']), '-', '_')));
+    $this->assertNoFieldByXPath($xpath, FALSE, t('Block found in no regions.'));
   }
 
+
   /**
    * Moves a block to a given region via the UI and confirms the result.
    *
@@ -376,14 +181,14 @@ function testBlock() {
    *   The machine name of the theme region to move the block to, for example
    *   'header' or 'sidebar_first'.
    */
-  function moveBlockToRegion(array $block, $region) {
+  protected function moveBlockToRegion(array $block, $region) {
     // Set the created block to a specific region.
     $edit = array();
-    $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region;
+    $edit['blocks[0][region]'] = $region;
     $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
 
     // Confirm that the block was moved to the proper region.
-    $this->assertText(t('The block settings have been updated.'), format_string('Block successfully moved to %region_name region.', array( '%region_name' => $region)));
+    $this->assertText(t('The block settings have been updated.'), format_string('Block successfully moved to %region_name region.', array('%region_name' => $region)));
 
     // Confirm that the block is being displayed.
     $this->drupalGet('');
@@ -391,32 +196,42 @@ function moveBlockToRegion(array $block, $region) {
 
     // Confirm that the custom block was found at the proper region.
     $xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array(
-      ':region-class' => 'region region-' . str_replace('_', '-', $region),
-      ':block-id' => 'block-' . $block['module'] . '-' . $block['delta'],
+      ':region-class' => 'region region-' . drupal_html_class($region),
+      ':block-id' => 'block-' . strtr(strtolower($block['machine_name']), '-', '_'),
     ));
-    $this->assertFieldByXPath($xpath, NULL, format_string('Custom block found in %region_name region.', array('%region_name' => $region)));
+    $this->assertFieldByXPath($xpath, NULL, t('Block found in %region_name region.', array('%region_name' => drupal_html_class($region))));
   }
 
   /**
    * Test _block_rehash().
    */
-  function testBlockRehash() {
+  public function testBlockRehash() {
     module_enable(array('block_test'));
     $this->assertTrue(module_exists('block_test'), 'Test block module enabled.');
 
-    // Our new block should be inserted in the database when we visit the
-    // block management page.
-    $this->drupalGet('admin/structure/block');
+    // Add a test block.
+    $block = array();
+    $block['id'] = 'test_cache';
+    $block['machine_name'] = $this->randomName(8);
+    $block['theme'] = variable_get('theme_default', 'stark');
+    $block['region'] = 'header';
+    $this->drupalPost('admin/structure/block/manage/' . $block['id'] . '/' . $block['theme'], array('machine_name' => $block['machine_name'], 'region' => $block['region']), t('Save block'));
+    $this->assertText('The block configuration has been saved.', 'Block was saved');
+
     // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE.
-    $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField();
-    $this->assertEqual($current_caching, DRUPAL_CACHE_PER_ROLE, 'Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.');
+    $block['config_id'] = 'plugin.core.block.' . $block['theme'] . '.' . $block['machine_name'];
+    $config = config($block['config_id']);
+    $setting = $config->get('cache');
+    $this->assertEqual($setting, DRUPAL_CACHE_PER_ROLE, t('Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.'));
 
     // Disable caching for this block.
-    variable_set('block_test_caching', DRUPAL_NO_CACHE);
+    $config->set('cache', DRUPAL_NO_CACHE);
+    $config->save();
     // Flushing all caches should call _block_rehash().
     $this->resetAll();
-    // Verify that the database is updated with the new caching mode.
-    $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField();
-    $this->assertEqual($current_caching, DRUPAL_NO_CACHE, "Test block's database entry updated to DRUPAL_NO_CACHE.");
+    // Verify that block is updated with the new caching mode.
+    $config = config($block['config_id']);
+    $setting = $config->get('cache');
+    $this->assertEqual($setting, DRUPAL_NO_CACHE, "Test block's database entry updated to DRUPAL_NO_CACHE.");
   }
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockUiTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockUiTest.php
new file mode 100644
index 0000000..179464c
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockUiTest.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\block\Tests\BlockTest.
+ */
+
+namespace Drupal\block\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+class BlockUiTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block');
+
+  protected $regions;
+  protected $adminUser;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Block UI',
+      'description' => 'Checks that the block configuration UI stores data correctly.',
+      'group' => 'Block',
+    );
+  }
+  function setUp() {
+    parent::setUp();
+    // Create and log in an administrative user.
+    $this->adminUser = $this->drupalCreateUser(array(
+      'administer blocks',
+      'access administration pages',
+    ));
+    $this->drupalLogin($this->adminUser);
+  }
+
+  /**
+   * Test block visibility.
+   */
+  function testBlockVisibility() {
+  }
+}
diff --git a/core/modules/block/lib/Drupal/block/Tests/NonDefaultBlockAdminTest.php b/core/modules/block/lib/Drupal/block/Tests/NonDefaultBlockAdminTest.php
index 381b49a..7cf114c 100644
--- a/core/modules/block/lib/Drupal/block/Tests/NonDefaultBlockAdminTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/NonDefaultBlockAdminTest.php
@@ -34,6 +34,7 @@ function testNonDefaultBlockAdmin() {
     $this->drupalLogin($admin_user);
     $new_theme = 'bartik';
     theme_enable(array($new_theme));
-    $this->drupalGet('admin/structure/block/list/' . $new_theme);
+    $this->drupalGet('admin/structure/block/list/block_plugin_ui:' . $new_theme);
+    $this->assertText('Bartik(' . t('active tab') . ')', 'Tab for non-default theme found.');
   }
 }
diff --git a/core/modules/block/templates/block.tpl.php b/core/modules/block/templates/block.tpl.php
index 895b49f..b4405eb 100644
--- a/core/modules/block/templates/block.tpl.php
+++ b/core/modules/block/templates/block.tpl.php
@@ -41,7 +41,11 @@
  * @ingroup themeable
  */
 ?>
+<?php if (isset($block_html_id)) { ?>
 <div id="<?php print $block_html_id; ?>" <?php print $attributes; ?>>
+<?php } else { ?>
+<div <?php print $attributes; ?>>
+<?php } ?>
 
   <?php print render($title_prefix); ?>
 <?php if ($block->subject): ?>
diff --git a/core/modules/block/tests/block_test.module b/core/modules/block/tests/block_test.module
index 5e06d5c..03f94b0 100644
--- a/core/modules/block/tests/block_test.module
+++ b/core/modules/block/tests/block_test.module
@@ -12,25 +12,3 @@ function block_test_system_theme_info() {
   $themes['block_test_theme'] = drupal_get_path('module', 'block_test') . '/themes/block_test_theme/block_test_theme.info';
   return $themes;
 }
-
-/**
- * Implements hook_block_info().
- */
-function block_test_block_info() {
-  $blocks['test_cache'] = array(
-    'info' => t('Test block caching'),
-    'cache' => variable_get('block_test_caching', DRUPAL_CACHE_PER_ROLE),
-  );
-
-  $blocks['test_html_id'] = array(
-    'info' => t('Test block html id'),
-  );
-  return $blocks;
-}
-
-/**
- * Implements hook_block_view().
- */
-function block_test_block_view($delta = 0) {
-  return array('content' => variable_get('block_test_content', ''));
-}
diff --git a/core/modules/block/tests/lib/Drupal/block_test/Plugin/block/block/TestCacheBlock.php b/core/modules/block/tests/lib/Drupal/block_test/Plugin/block/block/TestCacheBlock.php
new file mode 100644
index 0000000..c07bdba
--- /dev/null
+++ b/core/modules/block/tests/lib/Drupal/block_test/Plugin/block/block/TestCacheBlock.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\block_test\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "test_cache",
+ *   subject = @Translation("Test block caching"),
+ *   module = "block_test"
+ * )
+ */
+class TestCacheBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'cache' => DRUPAL_CACHE_PER_ROLE,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    return array('#children' => variable_get('block_test_content', ''));
+  }
+}
diff --git a/core/modules/block/tests/lib/Drupal/block_test/Plugin/block/block/TestHtmlIdBlock.php b/core/modules/block/tests/lib/Drupal/block_test/Plugin/block/block/TestHtmlIdBlock.php
new file mode 100644
index 0000000..e7ca2f7
--- /dev/null
+++ b/core/modules/block/tests/lib/Drupal/block_test/Plugin/block/block/TestHtmlIdBlock.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Drupal\block_test\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "test_html_id",
+ *   subject = @Translation("Test block html id"),
+ *   module = "block_test"
+ * )
+ */
+class TestHtmlIdBlock extends TestCacheBlock {}
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 7c43e28..9489920 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -262,103 +262,6 @@ function book_entity_info_alter(&$info) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function book_block_info() {
-  $block = array();
-  $block['navigation']['info'] = t('Book navigation');
-  $block['navigation']['cache'] = DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE;
-
-  return $block;
-}
-
-/**
- * Implements hook_block_view().
- *
- * Displays the book table of contents in a block when the current page is a
- * single-node view of a book node.
- */
-function book_block_view($delta = '') {
-  $block = array();
-  $current_bid = 0;
-  if ($node = menu_get_object()) {
-    $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
-  }
-
-  if (config('book.settings')->get('block.navigation.mode') == 'all pages') {
-    $block['subject'] = t('Book navigation');
-    $book_menus = array();
-    $pseudo_tree = array(0 => array('below' => FALSE));
-    foreach (book_get_books() as $book_id => $book) {
-      if ($book['bid'] == $current_bid) {
-        // If the current page is a node associated with a book, the menu
-        // needs to be retrieved.
-        $book_menus[$book_id] = menu_tree_output(menu_tree_all_data($node->book['menu_name'], $node->book));
-      }
-      else {
-        // Since we know we will only display a link to the top node, there
-        // is no reason to run an additional menu tree query for each book.
-        $book['in_active_trail'] = FALSE;
-        // Check whether user can access the book link.
-        $book_node = node_load($book['nid']);
-        $book['access'] = node_access('view', $book_node);
-        $pseudo_tree[0]['link'] = $book;
-        $book_menus[$book_id] = menu_tree_output($pseudo_tree);
-      }
-    }
-    if ($block['content'] = $book_menus) {
-      $book_menus['#theme'] = 'book_all_books_block';
-    }
-  }
-  elseif ($current_bid) {
-    // Only display this block when the user is browsing a book.
-  $select = db_select('node', 'n')
-    ->fields('n', array('title'))
-    ->condition('n.nid', $node->book['bid'])
-    ->addTag('node_access');
-    $title = $select->execute()->fetchField();
-    // Only show the block if the user has view access for the top-level node.
-    if ($title) {
-      $tree = menu_tree_all_data($node->book['menu_name'], $node->book);
-      // There should only be one element at the top level.
-      $data = array_shift($tree);
-      $block['subject'] = theme('book_title_link', array('link' => $data['link']));
-      $block['content'] = ($data['below']) ? menu_tree_output($data['below']) : '';
-    }
-  }
-
-  return $block;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function book_block_configure($delta = '') {
-  $block = array();
-  $options = array(
-    'all pages' => t('Show block on all pages'),
-    'book pages' => t('Show block only on book pages'),
-  );
-  $form['book_block_mode'] = array(
-    '#type' => 'radios',
-    '#title' => t('Book navigation block display'),
-    '#options' => $options,
-    '#default_value' => config('book.settings')->get('block.navigation.mode'),
-    '#description' => t("If <em>Show block on all pages</em> is selected, the block will contain the automatically generated menus for all of the site's books. If <em>Show block only on book pages</em> is selected, the block will contain only the one menu corresponding to the current page's book. In this case, if the current page is not in a book, no block will be displayed. The <em>Page specific visibility settings</em> or other visibility settings can be used in addition to selectively display this block."),
-    );
-
-  return $form;
-}
-
-/**
- * Implements hook_block_save().
- */
-function book_block_save($delta = '', $edit = array()) {
-  $block = array();
-  config('book.settings')->set('block.navigation.mode', $edit['book_block_mode'])->save();
-}
-
-/**
  * Returns HTML for a link to a book title when used as a block title.
  *
  * @param $variables
diff --git a/core/modules/book/lib/Drupal/book/Plugin/block/block/BookNavigationBlock.php b/core/modules/book/lib/Drupal/book/Plugin/block/block/BookNavigationBlock.php
new file mode 100644
index 0000000..13066b4
--- /dev/null
+++ b/core/modules/book/lib/Drupal/book/Plugin/block/block/BookNavigationBlock.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\book\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "book_navigation",
+ *   subject = @Translation("Book navigation"),
+ *   module = "book"
+ * )
+ */
+class BookNavigationBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'cache' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE,
+      'block_mode' => "all pages",
+    );
+  }
+
+  /**
+   * Implements BlockInterface::configure()
+   */
+  function configure($form, &$form_state) {
+    $options = array(
+      'all pages' => t('Show block on all pages'),
+      'book pages' => t('Show block only on book pages'),
+    );
+    $form['book_block_mode'] = array(
+      '#type' => 'radios',
+      '#title' => t('Book navigation block display'),
+      '#options' => $options,
+      '#default_value' => $this->configuration['block_mode'],
+      '#description' => t("If <em>Show block on all pages</em> is selected, the block will contain the automatically generated menus for all of the site's books. If <em>Show block only on book pages</em> is selected, the block will contain only the one menu corresponding to the current page's book. In this case, if the current page is not in a book, no block will be displayed. The <em>Page specific visibility settings</em> or other visibility settings can be used in addition to selectively display this block."),
+      );
+
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['block_mode'] = $form_state['values']['book_block_mode'];
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    $block = array();
+    $current_bid = 0;
+    if ($node = menu_get_object()) {
+      $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
+    }
+
+    if ($this->configuration['block_mode'] == 'all pages') {
+      $book_menus = array();
+      $pseudo_tree = array(0 => array('below' => FALSE));
+      foreach (book_get_books() as $book_id => $book) {
+        if ($book['bid'] == $current_bid) {
+          // If the current page is a node associated with a book, the menu
+          // needs to be retrieved.
+          $book_menus[$book_id] = menu_tree_output(menu_tree_all_data($node->book['menu_name'], $node->book));
+        }
+        else {
+          // Since we know we will only display a link to the top node, there
+          // is no reason to run an additional menu tree query for each book.
+          $book['in_active_trail'] = FALSE;
+          // Check whether user can access the book link.
+          $book_node = node_load($book['nid']);
+          $book['access'] = node_access('view', $book_node);
+          $pseudo_tree[0]['link'] = $book;
+          $book_menus[$book_id] = menu_tree_output($pseudo_tree);
+        }
+      }
+      if ($book_menus) {
+        return array(
+          '#theme' => 'book_all_books_block',
+          $book_menus
+        );
+      }
+    }
+    elseif ($current_bid) {
+      // Only display this block when the user is browsing a book.
+      $select = db_select('node', 'n')
+        ->fields('n', array('title'))
+        ->condition('n.nid', $node->book['bid'])
+        ->addTag('node_access');
+      $title = $select->execute()->fetchField();
+      // Only show the block if the user has view access for the top-level node.
+      if ($title) {
+        $tree = menu_tree_all_data($node->book['menu_name'], $node->book);
+        // There should only be one element at the top level.
+        $data = array_shift($tree);
+        return array(
+          '#title' => theme('book_title_link', array('link' => $data['link'])),
+          ($data['below']) ? menu_tree_output($data['below']) : '',
+        );
+      }
+    }
+    return array();
+  }
+}
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index df0a33a..d004363 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -217,7 +217,7 @@ function comment_field_extra_fields() {
 function comment_theme() {
   return array(
     'comment_block' => array(
-      'variables' => array(),
+      'variables' => array('number' => NULL),
     ),
     'comment_preview' => array(
       'variables' => array('comment' => NULL),
@@ -443,51 +443,6 @@ function comment_permission() {
 }
 
 /**
- * Implements hook_block_info().
- */
-function comment_block_info() {
-  $blocks['recent']['info'] = t('Recent comments');
-  $blocks['recent']['properties']['administrative'] = TRUE;
-
-  return $blocks;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function comment_block_configure($delta = '') {
-  $form['comment_block_count'] = array(
-    '#type' => 'select',
-    '#title' => t('Number of recent comments'),
-    '#default_value' => variable_get('comment_block_count', 10),
-    '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),
-  );
-
-  return $form;
-}
-
-/**
- * Implements hook_block_save().
- */
-function comment_block_save($delta = '', $edit = array()) {
-  variable_set('comment_block_count', (int) $edit['comment_block_count']);
-}
-
-/**
- * Implements hook_block_view().
- *
- * Generates a block with the most recent comments.
- */
-function comment_block_view($delta = '') {
-  if (user_access('access comments')) {
-    $block['subject'] = t('Recent comments');
-    $block['content'] = theme('comment_block');
-
-    return $block;
-  }
-}
-
-/**
  * Redirects comment links to the correct page depending on comment settings.
  *
  * Since comments are paged there is no way to guarantee which page a comment
@@ -624,9 +579,9 @@ function comment_new_page_count($num_comments, $new_replies, Node $node) {
  *
  * @ingroup themeable
  */
-function theme_comment_block() {
+function theme_comment_block($variables) {
   $items = array();
-  $number = variable_get('comment_block_count', 10);
+  $number = $variables['number'];
   foreach (comment_get_recent($number) as $comment) {
     $items[] = l($comment->subject, 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid)) . '&nbsp;<span>' . t('@time ago', array('@time' => format_interval(REQUEST_TIME - $comment->changed))) . '</span>';
   }
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/block/block/RecentBlock.php b/core/modules/comment/lib/Drupal/comment/Plugin/block/block/RecentBlock.php
new file mode 100644
index 0000000..6f151fc
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/block/block/RecentBlock.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\comment\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *  id = "recent_comments",
+ *  subject = @Translation("Recent comments"),
+ *  module = "comment"
+ * )
+ */
+class RecentBlock extends BlockBase {
+
+  public function settings() {
+    return array(
+      'block_count' => 10,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access comments');
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $form['block_count'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of recent comments'),
+      '#default_value' => $this->configuration['block_count'],
+      '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),
+    );
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['block_count'] = $form_state['values']['block_count'];
+  }
+
+  /**
+   * Implements BlockInterface::build();
+   */
+  public function build() {
+    return array(
+      '#theme' => 'comment_block',
+      '#number' => $this->configuration['block_count'],
+    );
+  }
+}
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php
index 0ec3af2..b8c07e6 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php
@@ -46,21 +46,16 @@ public static function getInfo() {
    */
   function testRecentCommentBlock() {
     $this->drupalLogin($this->admin_user);
-
-    // Set the block to a region to confirm block is available.
+    $current_theme = variable_get('default_theme', 'stark');
+    $machine_name = 'test_recent_comments';
     $edit = array(
-      'blocks[comment_recent][region]' => 'sidebar_first',
-    );
-    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
-    $this->assertText(t('The block settings have been updated.'), 'Block saved to first sidebar region.');
-
-    // Set block title and variables.
-    $block = array(
+      'machine_name' => $machine_name,
+      'region' => 'sidebar_first',
       'title' => $this->randomName(),
-      'comment_block_count' => 2,
+      'block_count' => 2,
     );
-    $this->drupalPost('admin/structure/block/manage/comment/recent/configure', $block, t('Save block'));
-    $this->assertText(t('The block configuration has been saved.'), 'Block saved.');
+    $this->drupalPost('admin/structure/block/manage/recent_comments/' . $current_theme, $edit, t('Save block'));
+    $this->assertText(t('The block configuration has been saved.'), 'Block was saved.');
 
     // Add some test comments, one without a subject.
     $comment1 = $this->postComment($this->node, $this->randomName(), $this->randomName());
@@ -75,14 +70,14 @@ function testRecentCommentBlock() {
     // posting a node from a node form.
     cache_invalidate(array('content' => TRUE));
     $this->drupalGet('');
-    $this->assertNoText($block['title'], 'Block was not found.');
+    $this->assertNoText($edit['title'], 'Block was not found.');
     user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access comments'));
 
     // Test that a user with the 'access comments' permission can see the
     // block.
     $this->drupalLogin($this->web_user);
     $this->drupalGet('');
-    $this->assertText($block['title'], 'Block was found.');
+    $this->assertText($edit['title'], 'Block was found.');
 
     // Test the only the 2 latest comments are shown and in the proper order.
     $this->assertNoText($comment1->subject, 'Comment not found in block.');
@@ -94,9 +89,10 @@ function testRecentCommentBlock() {
     $this->drupalLogout();
     $this->drupalLogin($this->admin_user);
     $block = array(
-      'comment_block_count' => 10,
+      'block_count' => 10,
     );
-    $this->drupalPost('admin/structure/block/manage/comment/recent/configure', $block, t('Save block'));
+
+    $this->drupalPost("admin/structure/block/manage/plugin.core.block.$current_theme.$machine_name/$current_theme/configure", $block, t('Save block'));
     $this->assertText(t('The block configuration has been saved.'), 'Block saved.');
 
     // Post an additional comment.
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 3ec8a41..1819a0a 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -641,81 +641,7 @@ function forum_form_node_form_alter(&$form, &$form_state, $form_id) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function forum_block_info() {
-  $blocks['active'] = array(
-    'info' => t('Active forum topics'),
-    'cache' => DRUPAL_CACHE_CUSTOM,
-    'properties' => array('administrative' => TRUE),
-  );
-  $blocks['new'] = array(
-    'info' => t('New forum topics'),
-    'cache' => DRUPAL_CACHE_CUSTOM,
-    'properties' => array('administrative' => TRUE),
-  );
-  return $blocks;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function forum_block_configure($delta = '') {
-  $form['block_' . $delta . '_limit'] = array(
-    '#type' => 'select',
-    '#title' => t('Number of topics'),
-    '#default_value' => config('forum.settings')->get('block.' . $delta . '.limit'),
-    '#options' => drupal_map_assoc(range(2, 20)),
-  );
-  return $form;
-}
-
-/**
- * Implements hook_block_save().
- */
-function forum_block_save($delta = '', $edit = array()) {
-  config('forum.settings')->set('block.' . $delta . '.limit', $edit['block_' . $delta . '_limit'])->save();
-}
-
-/**
- * Implements hook_block_view().
- *
- * Generates a block containing the currently active forum topics and the most
- * recently added forum topics.
- */
-function forum_block_view($delta = '') {
-  $config = config('forum.settings');
-  $query = db_select('forum_index', 'f')
-    ->fields('f')
-    ->addTag('node_access')
-    ->addMetaData('base_table', 'forum_index');
-  switch ($delta) {
-    case 'active':
-      $title = t('Active forum topics');
-      $query
-        ->orderBy('f.last_comment_timestamp', 'DESC')
-        ->range(0, $config->get('block.active.limit'));
-      break;
-
-    case 'new':
-      $title = t('New forum topics');
-      $query
-        ->orderBy('f.created', 'DESC')
-        ->range(0, $config->get('block.new.limit'));
-      break;
-  }
-
-  $block['subject'] = $title;
-  // Cache based on the altered query. Enables us to cache with node access enabled.
-  $block['content'] = drupal_render_cache_by_query($query, 'forum_block_view');
-  $block['content']['#access'] = user_access('access content');
-  return $block;
-}
-
-/**
- * Render API callback: Lists nodes based on the element's #query property.
- *
- * This function can be used as a #pre_render callback.
+ * A #pre_render callback. Lists nodes based on the element's #query property.
  *
  * @see forum_block_view()
  */
diff --git a/core/modules/forum/lib/Drupal/forum/Plugin/block/block/ActiveBlock.php b/core/modules/forum/lib/Drupal/forum/Plugin/block/block/ActiveBlock.php
new file mode 100644
index 0000000..710918e
--- /dev/null
+++ b/core/modules/forum/lib/Drupal/forum/Plugin/block/block/ActiveBlock.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\forum\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "forum_active_block",
+ *   subject = @Translation("Active forum topics"),
+ *   module = "forum"
+ * )
+ */
+class ActiveBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'cache' => DRUPAL_CACHE_CUSTOM,
+      'properties' => array(
+        'administrative' => TRUE,
+      ),
+      'block_count' => 5,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access content');
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $form['block_count'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of topics'),
+      '#default_value' => $this->configuration['block_count'],
+      '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)));
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['block_count'] = $form_state['values']['block_count'];
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    $query = db_select('forum_index', 'f')
+      ->fields('f')
+      ->addTag('node_access')
+      ->orderBy('f.last_comment_timestamp', 'DESC')
+      ->range(0, $this->configuration['block_count']);
+
+    return array(
+      drupal_render_cache_by_query($query, 'forum_block_view'),
+    );
+  }
+}
diff --git a/core/modules/forum/lib/Drupal/forum/Plugin/block/block/NewBlock.php b/core/modules/forum/lib/Drupal/forum/Plugin/block/block/NewBlock.php
new file mode 100644
index 0000000..0e52714
--- /dev/null
+++ b/core/modules/forum/lib/Drupal/forum/Plugin/block/block/NewBlock.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\forum\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "forum_active_block",
+ *   subject = @Translation("New forum topics"),
+ *   module = "forum"
+ * )
+ */
+class NewBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'cache' => DRUPAL_CACHE_CUSTOM,
+      'properties' => array(
+        'administrative' => TRUE,
+      ),
+      'block_count' => 5,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access content');
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $form['block_count'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of topics'),
+      '#default_value' => $this->configuration['block_count'],
+      '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)));
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['block_count'] = $form_state['values']['block_count'];
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    $query = db_select('forum_index', 'f')
+      ->fields('f')
+      ->addTag('node_access')
+      ->orderBy('f.created', 'DESC')
+      ->range(0, $this->configuration['block_count']);
+
+    return array(
+      drupal_render_cache_by_query($query, 'forum_block_view'),
+    );
+  }
+}
diff --git a/core/modules/help/help.module b/core/modules/help/help.module
index a74bf52..aac8636 100644
--- a/core/modules/help/help.module
+++ b/core/modules/help/help.module
@@ -71,7 +71,7 @@ function help_help($path, $arg) {
  * Implements hook_preprocess_HOOK() for block.tpl.php.
  */
 function help_preprocess_block(&$variables) {
-  if ($variables['block']->module == 'system' && $variables['block']->delta == 'help') {
+  if ($variables['block']->id == 'system_help_block') {
     $variables['attributes']['role'] = 'complementary';
   }
 }
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index 71135a4..db1ddc3 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -736,45 +736,6 @@ function language_language_delete($language) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function language_block_info() {
-  include_once DRUPAL_ROOT . '/core/includes/language.inc';
-  $block = array();
-  $info = language_types_info();
-  foreach (language_types_get_configurable(FALSE) as $type) {
-    $block[$type] = array(
-      // We do not need to escape the language type name since the block 'info'
-      // value is supposed not to be sanitized. It is escaped later, if needed.
-      'info' => t('Language switcher (!type)', array('!type' => $info[$type]['name'])),
-      // Not worth caching.
-      'cache' => DRUPAL_NO_CACHE,
-    );
-  }
-  return $block;
-}
-
-/**
- * Implements hook_block_view().
- *
- * Displays a language switcher. Only show if we have at least two languages.
- */
-function language_block_view($type) {
-  if (language_multilingual()) {
-    $path = drupal_is_front_page() ? '<front>' : current_path();
-    $links = language_negotiation_get_switch_links($type, $path);
-
-    if (isset($links->links)) {
-      $class = "language-switcher-{$links->method_id}";
-      $variables = array('links' => $links->links, 'attributes' => array('class' => array($class)));
-      $block['content'] = theme('links__language_block', $variables);
-      $block['subject'] = t('Languages');
-      return $block;
-    }
-  }
-}
-
-/**
  * Implements hook_preprocess_HOOK() for block.tpl.php.
  */
 function language_preprocess_block(&$variables) {
diff --git a/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php b/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php
new file mode 100644
index 0000000..0dff21a
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\language\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+class LanguageBlock implements DerivativeInterface {
+  protected $derivatives = array();
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+    $this->getDerivativeDefinitions($base_plugin_definition);
+    return $this->derivatives[$derivative_id];
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    include_once DRUPAL_ROOT . '/core/includes/language.inc';
+    $info = language_types_info();
+    foreach (language_types_get_configurable(FALSE) as $type) {
+      $this->derivatives[$type] = $base_plugin_definition;
+      $this->derivatives[$type]['subject'] = t('Language switcher (!type)', array('!type' => $info[$type]['name']));
+      $this->derivatives[$type]['cache'] = DRUPAL_NO_CACHE;
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php b/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php
new file mode 100644
index 0000000..a4d689c
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\language\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "language_block",
+ *   subject = @Translation("Language switcher"),
+ *   module = "language",
+ *   derivative = "Drupal\language\Plugin\Derivative\LanguageBlock"
+ * )
+ */
+class LanguageBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  function access() {
+    return language_multilingual();
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  function build() {
+    $path = drupal_is_front_page() ? '<front>' : current_path();
+    list($plugin_id, $type) = explode(':', $this->getPluginId());
+    $links = language_negotiation_get_switch_links($type, $path);
+
+    if (isset($links->links)) {
+      $class = "language-switcher-{$links->method_id}";
+      $variables = array('links' => $links->links, 'attributes' => array('class' => array($class)));
+      return array(
+        '#children' => theme('links__language_block', $variables)
+      );
+    }
+  }
+}
diff --git a/core/modules/menu/lib/Drupal/menu/Plugin/Derivative/MenuBlock.php b/core/modules/menu/lib/Drupal/menu/Plugin/Derivative/MenuBlock.php
new file mode 100644
index 0000000..04a4c34
--- /dev/null
+++ b/core/modules/menu/lib/Drupal/menu/Plugin/Derivative/MenuBlock.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\menu\Plugin\Derivative;
+
+use Drupal\system\Plugin\Derivative\SystemMenuBlock;
+
+class MenuBlock extends SystemMenuBlock {
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    // TODO. This check is here for simpletest. See
+    // http://drupal.org/node/1821658
+    if (!module_exists('menu')) {
+      return array();
+    }
+    foreach (menu_get_menus(FALSE) as $menu => $name) {
+      $this->derivatives[$menu] = $base_plugin_definition;
+      $this->derivatives[$menu]['delta'] = $menu;
+      $this->derivatives[$menu]['subject'] = t('Menu: @menu', array('@menu' => $name));
+      $this->derivatives[$menu]['cache'] = DRUPAL_NO_CACHE;
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/menu/lib/Drupal/menu/Plugin/block/block/MenuBlock.php b/core/modules/menu/lib/Drupal/menu/Plugin/block/block/MenuBlock.php
new file mode 100644
index 0000000..154106a
--- /dev/null
+++ b/core/modules/menu/lib/Drupal/menu/Plugin/block/block/MenuBlock.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\menu\Plugin\block\block;
+
+use Drupal\system\Plugin\block\block\SystemMenuBlock;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "menu_menu_block",
+ *   subject = @Translation("Menu"),
+ *   module = "menu",
+ *   derivative = "Drupal\menu\Plugin\Derivative\MenuBlock"
+ * )
+ */
+class MenuBlock extends SystemMenuBlock {
+}
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index a55660b..d3b926e 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -467,36 +467,6 @@ function menu_reset_item($link) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function menu_block_info() {
-  $menus = menu_get_menus(FALSE);
-
-  $blocks = array();
-  foreach ($menus as $name => $title) {
-    $blocks[$name]['info'] = check_plain($title);
-    // Menu blocks can't be cached because each menu item can have
-    // a custom access callback. menu.inc manages its own caching.
-    $blocks[$name]['cache'] = DRUPAL_NO_CACHE;
-  }
-  return $blocks;
-}
-
-/**
- * Implements hook_block_view().
- */
-function menu_block_view($delta = '') {
-  $menus = menu_get_menus(FALSE);
-  $data['subject'] = check_plain($menus[$delta]);
-  $data['content'] = menu_tree($delta);
-  // Add contextual links for this block.
-  if (!empty($data['content'])) {
-    $data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($delta));
-  }
-  return $data;
-}
-
-/**
  * Implements hook_block_view_alter().
  */
 function menu_block_view_alter(&$data, $block) {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/block/block/RecentBlock.php b/core/modules/node/lib/Drupal/node/Plugin/block/block/RecentBlock.php
new file mode 100644
index 0000000..de31e7b
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/block/block/RecentBlock.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\node\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "node_recent_block",
+ *   subject = @Translation("Recent content"),
+ *   module = "node"
+ * )
+ */
+class RecentBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'block_count' => 10,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access content');
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $form['block_count'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of recent content items to display'),
+      '#default_value' => $this->configuration['block_count'],
+      '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),
+    );
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['block_count'] = $form_state['values']['block_count'];
+  }
+
+  /**
+   * Implements BlockInterface::build();
+   */
+  public function build() {
+    if ($nodes = node_get_recent($this->configuration['block_count'])) {
+      return array(
+        '#theme' => 'node_recent_block',
+        '#nodes' => $nodes,
+      );
+    }
+    else {
+      return array(
+        '#children' => t('No content available.'),
+      );
+    }
+  }
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/block/block/SyndicateBlock.php b/core/modules/node/lib/Drupal/node/Plugin/block/block/SyndicateBlock.php
new file mode 100644
index 0000000..524b945
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Plugin/block/block/SyndicateBlock.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\node\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "node_syndicate_block",
+ *   subject = @Translation("Syndicate"),
+ *   module = "node"
+ * )
+ */
+class SyndicateBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'block_count' => 10,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access content');
+  }
+
+  /**
+   * Implements BlockInterface::build();
+   */
+  public function build() {
+    return array(
+      '#theme' => 'feed_icon',
+      '#url' => 'rss.xml',
+    );
+  }
+}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index a09aee1..c722ad6 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1170,11 +1170,11 @@ function node_is_page(Node $node) {
  */
 function node_preprocess_block(&$variables) {
   if ($variables['block']->module == 'node') {
-    switch ($variables['block']->delta) {
-      case 'syndicate':
+    switch ($variables['block']->id) {
+      case 'node_syndicate_block':
         $variables['attributes']['role'] = 'complementary';
         break;
-      case 'recent':
+      case 'node_recent_block':
         $variables['attributes']['role'] = 'navigation';
         break;
     }
@@ -1943,78 +1943,6 @@ function node_revision_list(Node $node) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function node_block_info() {
-  $blocks['syndicate']['info'] = t('Syndicate');
-  // Not worth caching.
-  $blocks['syndicate']['cache'] = DRUPAL_NO_CACHE;
-
-  $blocks['recent']['info'] = t('Recent content');
-  $blocks['recent']['properties']['administrative'] = TRUE;
-
-  return $blocks;
-}
-
-/**
- * Implements hook_block_view().
- */
-function node_block_view($delta = '') {
-  $block = array();
-
-  switch ($delta) {
-    case 'syndicate':
-      $block['subject'] = t('Syndicate');
-      $block['content'] = array(
-        '#theme' => 'feed_icon',
-        '#url' => 'rss.xml',
-        '#title' => t('Syndicate'),
-      );
-      break;
-
-    case 'recent':
-      if (user_access('access content')) {
-        $block['subject'] = t('Recent content');
-        if ($nodes = node_get_recent(variable_get('node_recent_block_count', 10))) {
-          $block['content'] = array(
-            '#theme' => 'node_recent_block',
-            '#nodes' => $nodes,
-          );
-        } else {
-          $block['content'] = t('No content available.');
-        }
-      }
-      break;
-  }
-  return $block;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function node_block_configure($delta = '') {
-  $form = array();
-  if ($delta == 'recent') {
-    $form['node_recent_block_count'] = array(
-      '#type' => 'select',
-      '#title' => t('Number of recent content items to display'),
-      '#default_value' => variable_get('node_recent_block_count', 10),
-      '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)),
-    );
-  }
-  return $form;
-}
-
-/**
- * Implements hook_block_save().
- */
-function node_block_save($delta = '', $edit = array()) {
-  if ($delta == 'recent') {
-    variable_set('node_recent_block_count', $edit['node_recent_block_count']);
-  }
-}
-
-/**
  * Finds the most recently changed nodes that are available to the current user.
  *
  * @param $number
@@ -2128,7 +2056,7 @@ function theme_node_recent_content($variables) {
  * Adds node-type specific visibility options to add block form.
  */
 function node_form_block_add_block_form_alter(&$form, &$form_state) {
-  node_form_block_admin_configure_alter($form, $form_state);
+  //node_form_block_admin_configure_alter($form, $form_state);
 }
 
 /**
@@ -2139,10 +2067,7 @@ function node_form_block_add_block_form_alter(&$form, &$form_state) {
  * @see node_form_block_admin_configure_submit()
  */
 function node_form_block_admin_configure_alter(&$form, &$form_state) {
-  $default_type_options = db_query("SELECT type FROM {block_node_type} WHERE module = :module AND delta = :delta", array(
-    ':module' => $form['module']['#value'],
-    ':delta' => $form['delta']['#value'],
-  ))->fetchCol();
+  $config = $form['#instance']->getConfig();
   $form['visibility']['node_type'] = array(
     '#type' => 'fieldset',
     '#title' => t('Content types'),
@@ -2154,51 +2079,10 @@ function node_form_block_admin_configure_alter(&$form, &$form_state) {
   $form['visibility']['node_type']['types'] = array(
     '#type' => 'checkboxes',
     '#title' => t('Show block for specific content types'),
-    '#default_value' => $default_type_options,
+    '#default_value' => !empty($config['visibility']['node_type']['types']) ? $config['visibility']['node_type']['types'] : array(),
     '#options' => node_type_get_names(),
     '#description' => t('Show this block only on pages that display content of the given type(s). If you select no types, there will be no type-specific limitation.'),
   );
-  $form['#submit'][] = 'node_form_block_admin_configure_submit';
-}
-
-/**
- * Form submission handler for node_form_block_admin_configure_alter().
- */
-function node_form_block_admin_configure_submit($form, &$form_state) {
-  db_delete('block_node_type')
-    ->condition('module', $form_state['values']['module'])
-    ->condition('delta', $form_state['values']['delta'])
-    ->execute();
-  $query = db_insert('block_node_type')->fields(array('type', 'module', 'delta'));
-  foreach (array_filter($form_state['values']['types']) as $type) {
-    $query->values(array(
-      'type' => $type,
-      'module' => $form_state['values']['module'],
-      'delta' => $form_state['values']['delta'],
-    ));
-  }
-  $query->execute();
-}
-
-/**
- * Implements hook_form_FORM_ID_alter() for block_custom_block_delete().
- *
- * Adds node specific submit handler to delete custom block form.
- *
- * @see node_form_block_custom_block_delete_submit()
- */
-function node_form_block_custom_block_delete_alter(&$form, &$form_state) {
-  $form['#submit'][] = 'node_form_block_custom_block_delete_submit';
-}
-
-/**
- * Form submission handler for node_form_block_custom_block_delete_alter().
- */
-function node_form_block_custom_block_delete_submit($form, &$form_state) {
-  db_delete('block_node_type')
-    ->condition('module', 'block')
-    ->condition('delta', $form_state['values']['bid'])
-    ->execute();
 }
 
 /**
@@ -2207,62 +2091,51 @@ function node_form_block_custom_block_delete_submit($form, &$form_state) {
  * Cleans up the {block_node_type} table from modules' blocks.
  */
 function node_modules_uninstalled($modules) {
-  db_delete('block_node_type')
-    ->condition('module', $modules, 'IN')
-    ->execute();
+  // Remove the block visibility settings for all node types.
+  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
+  foreach ($block_configs as $config_id) {
+    $config = config($config_id);
+    $node_types = $config->get('visibility.node_type.types');
+    if (!empty($node_types)) {
+      $config->clear('visibility.node_type.types');
+    }
+  }
 }
 
 /**
- * Implements hook_block_list_alter().
+ * Implements hook_block_access().
  *
  * Checks the content type specific visibility settings and removes the block
  * if the visibility conditions are not met.
  */
-function node_block_list_alter(&$blocks) {
-  global $theme_key;
-
-  // Build an array of node types for each block.
-  $block_node_types = array();
-  $result = db_query('SELECT module, delta, type FROM {block_node_type}');
-  foreach ($result as $record) {
-    $block_node_types[$record->module][$record->delta][$record->type] = TRUE;
-  }
-
-  $node = menu_get_object();
-  $node_types = node_type_get_types();
-  if (arg(0) == 'node' && arg(1) == 'add' && arg(2)) {
-    $node_add_arg = strtr(arg(2), '-', '_');
-  }
-  foreach ($blocks as $key => $block) {
-    if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) {
-      // This block was added by a contrib module, leave it in the list.
-      continue;
+function node_block_access($block) {
+  $configuration = $block->getConfig();
+  if (!empty($configuration['visibility'])) {
+    $visibility = $configuration['visibility'];
+    $allowed_types = array();
+    $node = menu_get_object();
+    $node_types = node_type_get_types();
+    if (arg(0) == 'node' && arg(1) == 'add' && arg(2)) {
+      $node_add_arg = strtr(arg(2), '-', '_');
+    }
+    if (!empty($visibility['node_type']['types'])) {
+      $allowed_types = array_filter($visibility['node_type']['types']);
     }
-
     // If a block has no node types associated, it is displayed for every type.
-    // For blocks with node types associated, if the node type does not match
-    // the settings from this block, remove it from the block list.
-    if (isset($block_node_types[$block->module][$block->delta])) {
+    // For blocks with node types associated, if the node type does not match the
+    // settings from this block, deny access to it.
+    if ($allowed_types) {
       if (!empty($node)) {
         // This is a node or node edit page.
-        if (!isset($block_node_types[$block->module][$block->delta][$node->type])) {
-          // This block should not be displayed for this node type.
-          unset($blocks[$key]);
-          continue;
-        }
+        return in_array($node->type, $allowed_types);
       }
       elseif (isset($node_add_arg) && isset($node_types[$node_add_arg])) {
         // This is a node creation page
-        if (!isset($block_node_types[$block->module][$block->delta][$node_add_arg])) {
-          // This block should not be displayed for this node type.
-          unset($blocks[$key]);
-          continue;
-        }
+        return in_array($node_add_arg, $allowed_types);
       }
       else {
         // This is not a node page, remove the block.
-        unset($blocks[$key]);
-        continue;
+        return FALSE;
       }
     }
   }
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index 6d78e43..0982530 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -464,9 +464,9 @@ function theme_overlay_disable_message($variables) {
 }
 
 /**
- * Implements hook_block_list_alter().
+ * Implements hook_block_access().
  */
-function overlay_block_list_alter(&$blocks) {
+function overlay_block_access($block) {
   // If we are limiting rendering to a subset of page regions, hide all blocks
   // which appear in regions not on that list. Note that overlay_page_alter()
   // does a more comprehensive job of preventing unwanted regions from being
@@ -474,10 +474,9 @@ function overlay_block_list_alter(&$blocks) {
   // reason for duplicating effort here is performance; we do not even want
   // these blocks to be built if they are not going to be displayed.
   if ($regions_to_render = overlay_get_regions_to_render()) {
-    foreach ($blocks as $bid => $block) {
-      if (!in_array($block->region, $regions_to_render)) {
-        unset($blocks[$bid]);
-      }
+    $config = $block->getConfig();
+    if (!in_array($config['region'], $regions_to_render)) {
+      unset($blocks[$bid]);
     }
   }
 }
@@ -841,7 +840,7 @@ function _overlay_region_list($type) {
  *   and all regions of the page will be rendered.
  *
  * @see overlay_page_alter()
- * @see overlay_block_list_alter()
+ * @see overlay_block_access()
  * @see overlay_set_regions_to_render()
  */
 function overlay_get_regions_to_render() {
@@ -863,7 +862,7 @@ function overlay_get_regions_to_render() {
  *   are not being limited.
  *
  * @see overlay_page_alter()
- * @see overlay_block_list_alter()
+ * @see overlay_block_access()
  * @see overlay_get_regions_to_render()
  */
 function overlay_set_regions_to_render($regions = NULL) {
@@ -893,7 +892,7 @@ function overlay_set_regions_to_render($regions = NULL) {
  */
 function overlay_render_region($region) {
   // Indicate the region that we will be rendering, so that other regions will
-  // be hidden by overlay_page_alter() and overlay_block_list_alter().
+  // be hidden by overlay_page_alter() and overlay_block_access().
   overlay_set_regions_to_render(array($region));
   // Do what is necessary to force drupal_render_page() to only display HTML
   // from the requested region. Specifically, declare that the main page
diff --git a/core/modules/poll/lib/Drupal/poll/Plugin/block/block/PollRecentBlock.php b/core/modules/poll/lib/Drupal/poll/Plugin/block/block/PollRecentBlock.php
new file mode 100644
index 0000000..81ec76f
--- /dev/null
+++ b/core/modules/poll/lib/Drupal/poll/Plugin/block/block/PollRecentBlock.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\poll\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "poll_recent_block",
+ *   subject = @Translation("Most recent poll"),
+ *   module = "poll"
+ * )
+ */
+class PollRecentBlock extends BlockBase {
+  protected $record;
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'properties' => array(
+        'administrative' => TRUE,
+      ),
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    if (user_access('access content')) {
+      // Retrieve the latest poll.
+      $select = db_select('node', 'n');
+      $select->join('poll', 'p', 'p.nid = n.nid');
+      $select->fields('n', array('nid'))
+        ->condition('n.status', 1)
+        ->condition('p.active', 1)
+        ->orderBy('n.created', 'DESC')
+        ->range(0, 1)
+        ->addTag('node_access');
+
+      $record = $select->execute()->fetchObject();
+      if ($record) {
+        $this->record = $record;
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    $poll = node_load($this->record->nid);
+    if ($poll->nid) {
+      $poll = poll_block_latest_poll_view($poll);
+      return array(
+        $poll->content
+      );
+    }
+    return array();
+  }
+}
\ No newline at end of file
diff --git a/core/modules/poll/poll.module b/core/modules/poll/poll.module
index c85f121..c7f5e56 100644
--- a/core/modules/poll/poll.module
+++ b/core/modules/poll/poll.module
@@ -119,47 +119,6 @@ function _poll_menu_access($node, $perm, $inspect_allowvotes) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function poll_block_info() {
-  $blocks['recent']['info'] = t('Most recent poll');
-  $blocks['recent']['properties']['administrative'] = TRUE;
-  return $blocks;
-}
-
-/**
- * Implements hook_block_view().
- *
- * Generates a block containing the latest poll.
- *
- * @see poll_block_latest_poll_view()
- */
-function poll_block_view($delta = '') {
-  if (user_access('access content')) {
-    // Retrieve the latest poll.
-    $select = db_select('node', 'n');
-    $select->join('poll', 'p', 'p.nid = n.nid');
-    $select->fields('n', array('nid'))
-      ->condition('n.status', 1)
-      ->condition('p.active', 1)
-      ->orderBy('n.created', 'DESC')
-      ->range(0, 1)
-      ->addTag('node_access');
-
-    $record = $select->execute()->fetchObject();
-    if ($record) {
-      $poll = node_load($record->nid);
-      if ($poll->nid) {
-        $poll = poll_block_latest_poll_view($poll);
-        $block['subject'] = t('Poll');
-        $block['content'] = $poll->content;
-        return $block;
-      }
-    }
-  }
-}
-
-/**
  * Implements hook_cron().
  *
  * Closes polls that have exceeded their allowed runtime.
diff --git a/core/modules/search/lib/Drupal/search/Plugin/block/block/SearchBlock.php b/core/modules/search/lib/Drupal/search/Plugin/block/block/SearchBlock.php
new file mode 100644
index 0000000..099e207
--- /dev/null
+++ b/core/modules/search/lib/Drupal/search/Plugin/block/block/SearchBlock.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\search\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "search_form_block",
+ *   subject = @Translation("Search form"),
+ *   module = "search"
+ * )
+ */
+class SearchBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('search content');
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    return drupal_get_form('search_block_form');
+  }
+}
diff --git a/core/modules/search/search.module b/core/modules/search/search.module
index 315ae7a..564c32e 100644
--- a/core/modules/search/search.module
+++ b/core/modules/search/search.module
@@ -134,32 +134,10 @@ function search_permission() {
 }
 
 /**
- * Implements hook_block_info().
- */
-function search_block_info() {
-  $blocks['form']['info'] = t('Search form');
-  // Not worth caching.
-  $blocks['form']['cache'] = DRUPAL_NO_CACHE;
-  $blocks['form']['properties']['administrative'] = TRUE;
-  return $blocks;
-}
-
-/**
- * Implements hook_block_view().
- */
-function search_block_view($delta = '') {
-  if (user_access('search content')) {
-    $block['subject'] = t('Search');
-    $block['content'] = drupal_get_form('search_block_form');
-    return $block;
-  }
-}
-
-/**
- * Implements hook_preprocess_HOOK() for block.tpl.php.
+ * Implements hook_preprocess_block().
  */
 function search_preprocess_block(&$variables) {
-  if ($variables['block']->module == 'search' && $variables['block']->delta == 'form') {
+  if ($variables['block']->id == 'search_form_block') {
     $variables['attributes']['role'] = 'search';
     $variables['content_attributes']['class'][] = 'container-inline';
   }
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Plugin/block/block/ShortcutsBlock.php b/core/modules/shortcut/lib/Drupal/shortcut/Plugin/block/block/ShortcutsBlock.php
new file mode 100644
index 0000000..da184df
--- /dev/null
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Plugin/block/block/ShortcutsBlock.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\shortcut\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *  id = "shortcuts",
+ *  subject = @Translation("Shortcuts"),
+ *  module = "shortcut"
+ * )
+ */
+class ShortcutsBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    $shortcut_set = shortcut_current_displayed_set();
+    return array(
+      shortcut_renderable_links($shortcut_set),
+    );
+  }
+}
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 9bbb12a..a488a45 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -184,29 +184,6 @@ function shortcut_theme() {
 }
 
 /**
- * Implements hook_block_info().
- */
-function shortcut_block_info() {
-  $blocks['shortcuts']['info'] = t('Shortcuts');
-  // Shortcut blocks can't be cached because each menu item can have a custom
-  // access callback. menu.inc manages its own caching.
-  $blocks['shortcuts']['cache'] = DRUPAL_NO_CACHE;
-  return $blocks;
-}
-
-/**
- * Implements hook_block_view().
- */
-function shortcut_block_view($delta = '') {
-  if ($delta == 'shortcuts') {
-    $shortcut_set = shortcut_current_displayed_set();
-    $data['subject'] = t('@shortcut_set shortcuts', array('@shortcut_set' => $shortcut_set->title));
-    $data['content'] = shortcut_renderable_links($shortcut_set);
-    return $data;
-  }
-}
-
-/**
  * Access callback for editing a shortcut set.
  *
  * @param object $shortcut_set
diff --git a/core/modules/statistics/lib/Drupal/statistics/Plugin/block/block/StatisticsPopularBlock.php b/core/modules/statistics/lib/Drupal/statistics/Plugin/block/block/StatisticsPopularBlock.php
new file mode 100644
index 0000000..ba09d08
--- /dev/null
+++ b/core/modules/statistics/lib/Drupal/statistics/Plugin/block/block/StatisticsPopularBlock.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Drupal\statistics\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "statistics_popular_block",
+ *   subject = @Translation("Popular content"),
+ *   module = "statistics"
+ * )
+ */
+class StatisticsPopularBlock extends BlockBase {
+  protected $day_list;
+  protected $all_time_list;
+  protected $last_list;
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'top_day_num' => 0,
+      'top_all_num' => 0,
+      'top_last_num' => 0
+    );
+  }
+
+    /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    if (user_access('access content')) {
+      $daytop = $this->configuration['top_day_num'];
+      if (!$daytop || !($result = statistics_title_list('daycount', $daytop)) || !($this->day_list = node_title_list($result, t("Today's:")))) {
+        return FALSE;
+      }
+      $alltimetop = $this->configuration['top_all_num'];
+      if (!$alltimetop || !($result = statistics_title_list('totalcount', $alltimetop)) || !($this->all_time_list = node_title_list($result, t('All time:')))) {
+        return FALSE;
+      }
+      $lasttop = $this->configuration['top_last_num'];
+      if (!$lasttop || !($result = statistics_title_list('timestamp', $lasttop)) || !($this->last_list = node_title_list($result, t('Last viewed:')))) {
+        return FALSE;
+      }
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    // Popular content block settings
+    $numbers = array('0' => t('Disabled')) + drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40));
+    $form['statistics_block_top_day_num'] = array(
+     '#type' => 'select',
+     '#title' => t("Number of day's top views to display"),
+     '#default_value' => $this->configuration['top_day_num'],
+     '#options' => $numbers,
+     '#description' => t('How many content items to display in "day" list.')
+    );
+    $form['statistics_block_top_all_num'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of all time views to display'),
+      '#default_value' => $this->configuration['top_all_num'],
+      '#options' => $numbers,
+      '#description' => t('How many content items to display in "all time" list.')
+    );
+    $form['statistics_block_top_last_num'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of most recent views to display'),
+      '#default_value' => $this->configuration['top_last_num'],
+      '#options' => $numbers,
+      '#description' => t('How many content items to display in "recently viewed" list.')
+    );
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['top_day_num'] = $form_state['values']['statistics_block_top_day_num'];
+    $this->configuration['top_all_num'] = $form_state['values']['statistics_block_top_all_num'];
+    $this->configuration['top_last_num'] = $form_state['values']['statistics_block_top_last_num'];
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    $content = array();
+
+    if ($this->day_list) {
+      $content['top_day'] = $this->day_list;
+      $content['top_day']['#suffix'] = '<br />';
+    }
+
+    if ($this->all_time_list) {
+      $content['top_all'] = $this->all_time_list;
+      $content['top_all']['#suffix'] = '<br />';
+    }
+
+    if ($this->last_list) {
+      $content['top_last'] = $this->last_list;
+      $content['top_last']['#suffix'] = '<br />';
+    }
+
+    return $content;
+  }
+
+}
diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module
index 8469e52..3141b22 100644
--- a/core/modules/statistics/statistics.module
+++ b/core/modules/statistics/statistics.module
@@ -304,79 +304,6 @@ function statistics_get($nid) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function statistics_block_info() {
-  $blocks = array();
-
-  $statistics_count_content_views = config('statistics.settings')->get('count_content_views');
-  if (!empty($statistics_count_content_views)) {
-    $blocks['popular']['info'] = t('Popular content');
-    // Too dynamic to cache.
-    $blocks['popular']['cache'] = DRUPAL_NO_CACHE;
-  }
-  return $blocks;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function statistics_block_configure($delta = '') {
-  $config = config('statistics.settings');
-  // Popular content block settings
-  $numbers = array('0' => t('Disabled')) + drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40));
-  $form['statistics_block_popular_top_day_limit'] = array('#type' => 'select', '#title' => t("Number of day's top views to display"), '#default_value' => $config->get('block.popular.top_day_limit'), '#options' => $numbers, '#description' => t('How many content items to display in "day" list.'));
-  $form['statistics_block_popular_top_all_limit'] = array('#type' => 'select', '#title' => t('Number of all time views to display'), '#default_value' => $config->get('block.popular.top_all_limit'), '#options' => $numbers, '#description' => t('How many content items to display in "all time" list.'));
-  $form['statistics_block_popular_top_recent_limit'] = array('#type' => 'select', '#title' => t('Number of most recent views to display'), '#default_value' => $config->get('block.popular.top_recent_limit'), '#options' => $numbers, '#description' => t('How many content items to display in "recently viewed" list.'));
-  return $form;
-}
-
-/**
- * Implements hook_block_save().
- */
-function statistics_block_save($delta = '', $edit = array()) {
-  config('statistics.settings')
-    ->set('block.popular.top_day_limit', $edit['statistics_block_popular_top_day_limit'])
-    ->set('block.popular.top_all_limit', $edit['statistics_block_popular_top_all_limit'])
-    ->set('block.popular.top_recent_limit', $edit['statistics_block_popular_top_recent_limit'])
-    ->save();
-}
-
-/**
- * Implements hook_block_view().
- */
-function statistics_block_view($delta = '') {
-  if (user_access('access content')) {
-    $content = array();
-
-    $config = config('statistics.settings');
-    $daytop = $config->get('block.popular.top_day_limit');
-    if ($daytop && ($result = statistics_title_list('daycount', $daytop)) && ($node_title_list = node_title_list($result, t("Today's:")))) {
-      $content['top_day'] = $node_title_list;
-      $content['top_day']['#suffix'] = '<br />';
-    }
-
-    $alltimetop = $config->get('block.popular.top_all_limit');
-    if ($alltimetop && ($result = statistics_title_list('totalcount', $alltimetop)) && ($node_title_list = node_title_list($result, t('All time:')))) {
-      $content['top_all'] = $node_title_list;
-      $content['top_all']['#suffix'] = '<br />';
-    }
-
-    $lasttop = $config->get('block.popular.top_recent_limit');
-    if ($lasttop && ($result = statistics_title_list('timestamp', $lasttop)) && ($node_title_list = node_title_list($result, t('Last viewed:')))) {
-      $content['top_last'] = $node_title_list;
-      $content['top_last']['#suffix'] = '<br />';
-    }
-
-    if (count($content)) {
-      $block['content'] = $content;
-      $block['subject'] = t('Popular content');
-      return $block;
-    }
-  }
-}
-
-/**
  * Generates a link to a path, truncating the displayed text to a given width.
  *
  * @param string $path
@@ -480,3 +407,13 @@ function statistics_library_info() {
 
   return $libraries;
 }
+
+/**
+ * Implements hook_block_alter().
+ */
+function statistics_block_alter(&$definitions) {
+  $statistics_count_content_views = config('statistics.settings')->get('count_content_views');
+  if (empty($statistics_count_content_views)) {
+    unset($definitions['statistics_popular_block']);
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php
new file mode 100644
index 0000000..cae1248
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\system\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+class SystemMenuBlock implements DerivativeInterface {
+  protected $derivatives = array();
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+    $this->getDerivativeDefinitions($base_plugin_definition);
+    return $this->derivatives[$derivative_id];
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    foreach (menu_list_system_menus() as $menu => $name) {
+      $this->derivatives[$menu] = $base_plugin_definition;
+      $this->derivatives[$menu]['delta'] = $menu;
+      $this->derivatives[$menu]['subject'] = t('System Menu: @menu', array('@menu' => $name));
+      $this->derivatives[$menu]['cache'] = DRUPAL_NO_CACHE;
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/PluginUIBase.php b/core/modules/system/lib/Drupal/system/Plugin/PluginUIBase.php
new file mode 100644
index 0000000..c8c6ad2
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/PluginUIBase.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\system\Plugin;
+
+use Drupal\Component\Plugin\PluginBase;
+
+abstract class PluginUIBase extends PluginBase {
+  public function form($form, &$form_state, $plugin, $facet) {
+    $plugin_definition = $this->getDefinition();
+    $manager = new $plugin_definition['manager']();
+    $plugins = $this->excludeDefinitions($manager->getDefinitions());
+    $form['#theme'] = 'system_plugin_ui_form';
+    $form['instance'] = array(
+      '#type' => 'value',
+      '#value' => $this,
+    );
+    $form['right']['block'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Search'),
+      '#autocomplete_path' => 'system/autocomplete/' . $this->getPluginId(),
+    );
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Next'),
+    );
+    $rows = array();
+    foreach ($plugins as $plugin_id => $config) {
+      if (empty($facet) || $this->facetCompare($facet, $config)) {
+        $row = $this->row($plugin, $plugin_definition, $plugin_id, $config);
+        $rows[] = $row;
+      }
+      foreach ($plugin_definition['facets'] as $key => $title) {
+        $facets[$key][$config[$key]] = $this->facetLink($key, $plugin, $plugin_definition, $plugin_id, $config, $key);
+      }
+      $form['right']['all_plugins'] = array(
+        '#type' => 'markup',
+        '#markup' => l($plugin_definition['all_plugins'], $this->allPluginsUrl($plugin, $plugin_definition, $plugin_id, $config)),
+      );
+      foreach ($facets as $group => $values) {
+        $form['right'][$group] = array(
+          '#type' => 'markup',
+          '#markup' => theme('links', array('heading' => array('text' => $plugin_definition['facets'][$group], 'level' => 'h3'), 'links' => $values)),
+        );
+      }
+      $form['left']['plugin_library'] = array(
+        '#type' => 'markup',
+        '#markup' => theme('table', array('header' => $this->tableHeader(), 'rows' => $rows)),
+      );
+    }
+    return $form;
+  }
+
+  public function formValidate($form, &$form_state) {}
+
+  public function formSubmit($form, &$form_state) {}
+
+  public function excludeDefinitions($definitions) {
+    return $definitions;
+  }
+
+  public function access($plugin_id) {
+    $definition = $this->getDefinition($plugin_id);
+    return call_user_func_array('user_access', $definition['access_arguments']);
+  }
+
+  public function row($plugin, $plugin_definition, $plugin_id, $config) {
+    return array($config['title'], l($plugin_definition['link_title'], $plugin_definition['config_path'] . '/' . $plugin_id));
+  }
+
+  public function facetLink($facet, $plugin, $plugin_definition, $plugin_id, $config, $key) {
+    return array('title' => $config[$key], 'href' => $plugin_definition['path'] . '/' . $plugin . '/' . $facet . ':' . $config[$key]);
+  }
+
+  public function allPluginsUrl($plugin, $plugin_definition, $plugin_id, $config) {
+    return $plugin_definition['path'] . '/' . $plugin . '/add';
+  }
+
+  public function tableHeader() {
+    return array(t('Title'), t('Operations'));
+  }
+
+  public function facetCompare($facet, $config) {
+    list($facet_type, $option) = explode(':', $facet);
+    return $option == $config[$facet_type];
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/PluginUIInterface.php b/core/modules/system/lib/Drupal/system/Plugin/PluginUIInterface.php
new file mode 100644
index 0000000..e05e90f
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/PluginUIInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Drupal\system\Plugin;
+
+/**
+ * Interface definition for PluginUI plugins.
+ */
+interface PluginUIInterface {
+
+  /**
+   * Create a form array.
+   *
+   * @return array().
+   */
+  public function form($form, &$form_state, $plugin, $facet);
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Type/PluginUIManager.php b/core/modules/system/lib/Drupal/system/Plugin/Type/PluginUIManager.php
new file mode 100644
index 0000000..4eff8d7
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/Type/PluginUIManager.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\system\Plugin\Type;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Component\Plugin\Factory\ReflectionFactory;
+
+class PluginUIManager extends PluginManagerBase {
+  public function __construct() {
+    $this->discovery = new DerivativeDiscoveryDecorator(new AnnotatedClassDiscovery('system', 'plugin_ui'));
+    $this->factory = new ReflectionFactory($this);
+  }
+
+  protected function processDefinition(&$definition, $plugin_id) {
+    $definition += array(
+      'default_task' => TRUE,
+      'task_title' => t('View'),
+      'task_suffix' => 'view',
+    );
+  }
+}
\ No newline at end of file
diff --git a/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemHelpBlock.php b/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemHelpBlock.php
new file mode 100644
index 0000000..7bfb5e2
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemHelpBlock.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\system\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "system_help_block",
+ *   subject = @Translation("System Help"),
+ *   module = "system"
+ * )
+ */
+class SystemHelpBlock extends BlockBase {
+  private $help;
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    $this->help = menu_get_active_help();
+    return $this->help ? TRUE : FALSE;
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    if (empty($this->help)) {
+      $this->help = menu_get_active_help();
+    }
+    return array(
+      '#children' => $this->help,
+    );
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemMainBlock.php b/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemMainBlock.php
new file mode 100644
index 0000000..2fe6ce4
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemMainBlock.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\system\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "system_main_block",
+ *   subject = @Translation("Main page content"),
+ *   module = "system"
+ * )
+ */
+class SystemMainBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    return array(
+      drupal_set_page_content()
+    );
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemMenuBlock.php b/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemMenuBlock.php
new file mode 100644
index 0000000..4011c99
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemMenuBlock.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\system\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "system_menu_block",
+ *   subject = @Translation("System Menu"),
+ *   module = "system",
+ *   derivative = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
+ * )
+ */
+class SystemMenuBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    global $user;
+    if (!$user->uid) {
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    list($plugin, $derivative) = explode(':', $this->getPluginId());
+    return menu_tree($derivative);
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemPoweredByBlock.php b/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemPoweredByBlock.php
new file mode 100644
index 0000000..5c6e0b9
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/block/block/SystemPoweredByBlock.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\system\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "system_powered_by_block",
+ *   subject = @Translation("Powered by Drupal"),
+ *   module = "system"
+ * )
+ */
+class SystemPoweredByBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    return array(
+      '#children' => theme('system_powered_by'),
+    );
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/SystemBundle.php b/core/modules/system/lib/Drupal/system/SystemBundle.php
new file mode 100644
index 0000000..af2c029
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/SystemBundle.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\SystemBundle.
+ */
+
+namespace Drupal\system;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * System dependency injection container.
+ */
+class SystemBundle extends Bundle {
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle.
+   */
+  public function build(ContainerBuilder $container) {
+    // Register the various system plugin manager classes with the dependency
+    // injection container.
+    $container->register('plugin.manager.system.plugin_ui', 'Drupal\system\Plugin\Type\PluginUIManager');
+  }
+}
\ No newline at end of file
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index a9d951b..a64cfd9 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -195,6 +195,10 @@ function system_theme() {
     'system_date_format_localize_form' => array(
       'render element' => 'form',
     ),
+    'system_plugin_ui_form' => array(
+      'template' => 'system-plugin-ui-form',
+      'render element' => 'form',
+    ),
   ));
 }
 
@@ -1075,10 +1079,124 @@ function system_menu() {
     );
   }
 
+  foreach (system_plugin_manager('plugin_ui')->getDefinitions() as $plugin_id => $plugin) {
+    if ($plugin['menu'] === TRUE) {
+      $items[$plugin['path'] . '/' . $plugin_id . '/' . $plugin['suffix']] = array(
+        'title' => $plugin['title'],
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array($plugin['id'], $plugin_id),
+        'access callback' => 'system_plugin_ui_access',
+        'access arguments' => array($plugin_id),
+        'type' => $plugin['type'],
+      );
+      if (!empty($plugin['default_task'])) {
+        $items[$plugin['path'] . '/' . $plugin_id . '/' . $plugin['suffix'] . '/' . $plugin['task_suffix']] = array(
+          'title' => $plugin['task_title'],
+          'type' => MENU_DEFAULT_LOCAL_TASK,
+          'weight' => -10,
+        );
+      }
+      $items[$plugin['path'] . '/' . $plugin_id . '/%'] = array(
+        'title' => $plugin['title'],
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array($plugin['id'], $plugin_id, (count(explode('/', $plugin['path'])) + 1)),
+        'access callback' => 'system_plugin_ui_access',
+        'access arguments' => array($plugin_id, (count(explode('/', $plugin['path'])) + 1)),
+        'type' => MENU_CALLBACK,
+      );
+      if (!empty($plugin['file'])) {
+        $items[$plugin['path'] . '/' . $plugin_id . '/' . $plugin['suffix']]['file'] = $plugin['file'];
+        $items[$plugin['path'] . '/' . $plugin_id . '/%']['file'] = $plugin['file'];
+        if (!empty($plugin['file_path'])) {
+          $items[$plugin['path'] . '/' . $plugin_id . '/' . $plugin['suffix']]['file path'] = $plugin['file_path'];
+          $items[$plugin['path'] . '/' . $plugin_id . '/%']['file path'] = $plugin['file_path'];
+        }
+      }
+      $items['system/autocomplete/' . $plugin_id] = array(
+        'page callback' => 'system_plugin_autocomplete',
+        'page arguments' => array($plugin_id),
+        'access callback' => 'system_plugin_ui_access',
+        'access arguments' => array($plugin_id),
+        'type' => MENU_CALLBACK,
+      );
+    }
+  }
+
   return $items;
 }
 
 /**
+ * A simple wrapper function for proxying to the plugin class' form method.
+ */
+function system_plugin_ui_form($form, &$form_state, $plugin, $facet = NULL) {
+  $ui = system_plugin_manager('plugin_ui')->createInstance($plugin);
+  $form = $ui->form($form, $form_state, $plugin, $facet);
+  return $form;
+}
+
+/**
+ * A simple wrapper function for proxying to the plugin class' validate method.
+ */
+function system_plugin_ui_form_validate($form, &$form_state) {
+  $form_state['values']['instance']->formValidate($form, $form_state);
+}
+
+/**
+ * A simple wrapper function for proxying to the plugin class' submit method.
+ */
+function system_plugin_ui_form_submit($form, &$form_state) {
+  $form_state['values']['instance']->formSubmit($form, $form_state);
+}
+
+/**
+ * A generic autocomplete page callback for any plugin system tied to a plugin
+ * ui plugin.
+ */
+function system_plugin_autocomplete($plugin_id, $string = '') {
+  $matches = array();
+  $ui = system_plugin_manager('plugin_ui')->getDefinition($plugin_id);
+  $manager = new $ui['manager']();
+  if ($string) {
+    $plugins = $manager->getDefinitions();
+    $titles = array();
+    foreach($plugins as $plugin_id => $plugin) {
+      $titles[$plugin_id] = $plugin[$ui['title_attribute']];
+    }
+    $matches = preg_grep("/\b". $string . "/i", $titles);
+  }
+
+  return new JsonResponse($matches);
+}
+
+/**
+ * A simple wrapper function for proxying to the plugin class' access method.
+ */
+function system_plugin_ui_access($plugin, $facet = NULL) {
+  $ui = system_plugin_manager('plugin_ui')->createInstance($plugin);
+  return $ui->access($plugin, $facet);
+}
+
+/**
+ * A simple function for retreiving system plugin type managers from the DIC.
+ */
+function system_plugin_manager($plugin_type) {
+  return drupal_container()->get("plugin.manager.system.$plugin_type");
+}
+
+/**
+ * Implements hook_forms().
+ */
+function system_forms() {
+  $forms = array();
+  foreach (system_plugin_manager('plugin_ui')->getDefinitions() as $plugin_id => $plugin) {
+    if (empty($forms[$plugin['id']])) {
+      $forms[$plugin['id']]['callback'] = 'system_plugin_ui_form';
+    }
+  }
+  return $forms;
+}
+
+/**
  * Page callback; Execute cron tasks.
  *
  * @see system_cron_access().
@@ -2489,96 +2607,40 @@ function system_user_timezone(&$form, &$form_state) {
 }
 
 /**
- * Implements hook_block_info().
+ * Implements hook_preprocess_HOOK() for block.tpl.php.
  */
-function system_block_info() {
-  $blocks['main'] = array(
-    'info' => t('Main page content'),
-    // Cached elsewhere.
-    'cache' => DRUPAL_NO_CACHE,
-    // Auto-enable in 'content' region by default, which always exists.
-    // @see system_themes_page(), drupal_render_page()
-    'region' => 'content',
-    'status' => 1,
-  );
-  $blocks['powered-by'] = array(
-    'info' => t('Powered by Drupal'),
-    'weight' => '10',
-    'cache' => DRUPAL_NO_CACHE,
-  );
-  $blocks['help'] = array(
-    'info' => t('System help'),
-    'weight' => '5',
-    'cache' => DRUPAL_NO_CACHE,
-    // Auto-enable in 'help' region by default, if the theme defines one.
-    'region' => 'help',
-    'status' => 1,
-  );
-  // System-defined menu blocks.
-  foreach (menu_list_system_menus() as $menu_name => $title) {
-    $blocks[$menu_name]['info'] = t($title);
-    // Menu blocks can't be cached because each menu item can have
-    // a custom access callback. menu.inc manages its own caching.
-    $blocks[$menu_name]['cache'] = DRUPAL_NO_CACHE;
-  }
-  return $blocks;
-}
+function system_preprocess_block(&$variables) {
+  switch ($variables['block']->id) {
+    case 'system_powered_by_block':
+      $variables['attributes_array']['role'] = 'complementary';
+      break;
+    case 'system_help_block':
+      $variables['attributes_array']['role'] = 'complementary';
+      break;
 
-/**
- * Implements hook_block_view().
- *
- * Generate a block with a promotional link to Drupal.org and
- * all system menu blocks.
- */
-function system_block_view($delta = '') {
-  $block = array();
-  switch ($delta) {
-    case 'main':
-      $block['subject'] = NULL;
-      $block['content'] = drupal_set_page_content();
-      return $block;
-    case 'powered-by':
-      $block['subject'] = NULL;
-      $block['content'] = theme('system_powered_by');
-      return $block;
-    case 'help':
-      $block['subject'] = NULL;
-      $block['content'] = menu_get_active_help();
-      return $block;
+    // System menu blocks should get the same class as menu module blocks.
     default:
-      // All system menu blocks.
-      $system_menus = menu_list_system_menus();
-      if (isset($system_menus[$delta])) {
-        $block['subject'] = t($system_menus[$delta]);
-        $block['content'] = menu_tree($delta);
-        return $block;
+      if ($variables['block']->class == 'Drupal\\system\\Plugin\\block\\block\\SystemMenuBlock') {
+        $variables['attributes_array']['role'] = 'navigation';
+        $variables['classes_array'][] = 'block-menu';
       }
-      break;
   }
 }
 
 /**
- * Implements hook_preprocess_HOOK() for block.tpl.php.
+ * Processes variables for block-admin-display-form.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $form
+ *
+ * @see block-admin-display.tpl.php
+ * @see theme_block_admin_display()
  */
-function system_preprocess_block(&$variables) {
-  if ($variables['block']->module == 'system') {
-
-    switch ($variables['block']->delta) {
-      case 'powered-by':
-        $variables['attributes']['role'] = 'complementary';
-        break;
-      case 'help':
-        $variables['attributes']['role'] = 'complementary';
-        break;
-
-      // System menu blocks should get the same class as menu module blocks.
-      default:
-        if (in_array($variables['block']->delta, array_keys(menu_list_system_menus()))) {
-          $variables['attributes']['role'] = 'navigation';
-          $variables['attributes']['class'][] = 'block-menu';
-        }
-    }
-  }
+function template_preprocess_system_plugin_ui_form(&$variables) {
+  drupal_add_css(drupal_get_path('module', 'system') . '/system.plugin.ui.css');
+  $variables['left'] = drupal_render($variables['form']['left']);
+  $variables['right'] = drupal_render($variables['form']['right']);
+  $variables['form_submit'] = drupal_render_children($variables['form']);
 }
 
 /**
diff --git a/core/modules/system/system.plugin.ui.css b/core/modules/system/system.plugin.ui.css
new file mode 100644
index 0000000..207a6ee
--- /dev/null
+++ b/core/modules/system/system.plugin.ui.css
@@ -0,0 +1,29 @@
+#block-library .left-col,
+#block-library .right-col {
+  float:left;
+  width:66%;
+  height:100%;
+  background-color:#ffffff;
+}
+
+#block-library .right-col {
+  width:34%;
+  background-color:#f7f7f7;
+}
+
+#block-library .right-col h3 {
+  margin: 1em -20px;
+  background-color:#d7d7d7;
+  color:#333333;
+  padding:8px 15px;
+  font-size:1.1em;
+}
+
+#block-library .inside {
+  margin:0 20px;
+}
+
+#block-library .bottom-bar {
+  width:100%;
+  clear:both;
+}
\ No newline at end of file
diff --git a/core/modules/system/templates/system-plugin-ui-form.tpl.php b/core/modules/system/templates/system-plugin-ui-form.tpl.php
new file mode 100644
index 0000000..8179076
--- /dev/null
+++ b/core/modules/system/templates/system-plugin-ui-form.tpl.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to configure blocks.
+ *
+ * Available variables:
+ * - $left: Any form array elements that should appear in the left hand column.
+ * - $right: Any form array elements that should appear in the right hand column.
+ * - $form_submit: Form submit button.
+ *
+ * @see template_preprocess_block_library_form()
+ * @see theme_block_library_form()
+ *
+ * @ingroup themeable
+ */
+?>
+<div id="block-library" class="container">
+  <div class="left-col">
+    <div class="inside">
+      <?php print $left; ?>
+    </div>
+  </div>
+  <div class="right-col">
+    <div class="inside">
+      <?php print $right; ?>
+    </div>
+  </div>
+  <?php if ($form_submit) { ?>
+  <div class="bottom-bar"><?php print $form_submit; ?></div>
+  <?php } ?>
+</div>
diff --git a/core/modules/user/lib/Drupal/user/Plugin/block/block/UserLoginBlock.php b/core/modules/user/lib/Drupal/user/Plugin/block/block/UserLoginBlock.php
new file mode 100644
index 0000000..72e8da9
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/block/block/UserLoginBlock.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\user\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "user_login_block",
+ *   subject = @Translation("User login"),
+ *   module = "user"
+ * )
+ */
+class UserLoginBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings();
+   */
+  public function settings() {
+    return array(
+      'whois_new_count' => 5,
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    global $user;
+    return (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1))));
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    return array(
+      drupal_get_form('user_login_block')
+    );
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/block/block/UserNewBlock.php b/core/modules/user/lib/Drupal/user/Plugin/block/block/UserNewBlock.php
new file mode 100644
index 0000000..b9ab4bc
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/block/block/UserNewBlock.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\user\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "user_online_block",
+ *   subject = @Translation("Who's new"),
+ *   module = "user"
+ * )
+ */
+class UserNewBlock extends BlockBase {
+
+  /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'properties' => array(
+        'administrative' => TRUE
+      ),
+      'whois_new_count' => 5
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access content');
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $form['user_block_whois_new_count'] = array(
+      '#type' => 'select',
+      '#title' => t('Number of users to display'),
+      '#default_value' => $this->configuration['whois_new_count'],
+      '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
+    );
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['whois_new_count'] = $form_state['values']['user_block_whois_new_count'];
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    // Retrieve a list of new users who have subsequently accessed the site successfully.
+    $items = db_query_range('SELECT uid, name FROM {users} WHERE status <> 0 AND access <> 0 ORDER BY created DESC', 0, $this->configuration['whois_new_count'])->fetchAll();
+    $build = array(
+      '#theme' => 'item_list__user__new',
+      '#items' => array(),
+    );
+    foreach ($items as $account) {
+      $build['#items'][] = theme('username', array('account' => $account));
+    }
+    return $build;
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/block/block/UserOnlineBlock.php b/core/modules/user/lib/Drupal/user/Plugin/block/block/UserOnlineBlock.php
new file mode 100644
index 0000000..30fabe7
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Plugin/block/block/UserOnlineBlock.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\user\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @Plugin(
+ *   id = "user_online_block",
+ *   subject = @Translation("Who's online"),
+ *   module = "user"
+ * )
+ */
+class UserOnlineBlock extends BlockBase {
+
+    /**
+   * Implements BlockInterface::settings().
+   */
+  public function settings() {
+    return array(
+      'properties' => array(
+        'administrative' => TRUE
+      ),
+      'seconds_online' => 900,
+      'max_list_count' => 10
+    );
+  }
+
+  /**
+   * Implements BlockInterface::access().
+   */
+  public function access() {
+    return user_access('access content');
+  }
+
+  /**
+   * Implements BlockInterface::configure().
+   */
+  public function configure($form, &$form_state) {
+    $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
+    $form['user_block_seconds_online'] = array(
+      '#type' => 'select',
+      '#title' => t('User activity'),
+      '#default_value' => $this->configuration['seconds_online'],
+      '#options' => $period,
+      '#description' => t('A user is considered online for this long after they have last viewed a page.')
+    );
+    $form['user_block_max_list_count'] = array(
+      '#type' => 'select',
+      '#title' => t('User list length'),
+      '#default_value' => $this->configuration['max_list_count'],
+      '#options' => drupal_map_assoc(array(0, 5, 10, 15, 20, 25, 30, 40, 50, 75, 100)),
+      '#description' => t('Maximum number of currently online users to display.')
+    );
+    return $form;
+  }
+
+  /**
+   * Implements BlockInterface::configureSubmit().
+   */
+  public function configureSubmit($form, &$form_state) {
+    $this->configuration['seconds_online'] = $form_state['values']['user_block_seconds_online'];
+    $this->configuration['max_list_count'] = $form_state['values']['user_block_max_list_count'];
+  }
+
+  /**
+   * Implements BlockInterface::build().
+   */
+  public function build() {
+    // Count users active within the defined period.
+    $interval = REQUEST_TIME - $this->configuration['max_list_count'];
+
+    // Perform database queries to gather online user lists. We use s.timestamp
+    // rather than u.access because it is much faster.
+    $authenticated_count = db_query("SELECT COUNT(DISTINCT s.uid) FROM {sessions} s WHERE s.timestamp >= :timestamp AND s.uid > 0", array(':timestamp' => $interval))->fetchField();
+
+    $build = array(
+      '#theme' => 'user_list',
+      '#prefix' => '<p>' . format_plural($authenticated_count, 'There is currently 1 user online.', 'There are currently @count users online.') . '</p>',
+    );
+
+    // Display a list of currently online users.
+    $max_users = $this->configuration['max_list_count'];
+    if ($authenticated_count && $max_users) {
+      $build['#users'] = db_query_range('SELECT u.uid, u.name, MAX(s.timestamp) AS max_timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= :interval AND s.uid > 0 GROUP BY u.uid, u.name ORDER BY max_timestamp DESC', 0, $max_users, array(':interval' => $interval))->fetchAll();
+    }
+
+    return $build;
+  }
+}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index b7ac23d..ffde26f 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -732,181 +732,26 @@ function user_validate_current_pass(&$form, &$form_state) {
 }
 
 /**
- * Implements hook_block_info().
- */
-function user_block_info() {
-  global $user;
-
-  $blocks['login']['info'] = t('User login');
-  // Not worth caching.
-  $blocks['login']['cache'] = DRUPAL_NO_CACHE;
-
-  $blocks['new']['info'] = t('Who\'s new');
-  $blocks['new']['properties']['administrative'] = TRUE;
-
-  // Too dynamic to cache.
-  $blocks['online']['info'] = t('Who\'s online');
-  $blocks['online']['cache'] = DRUPAL_NO_CACHE;
-  $blocks['online']['properties']['administrative'] = TRUE;
-
-  return $blocks;
-}
-
-/**
- * Implements hook_block_configure().
- */
-function user_block_configure($delta = '') {
-  global $user;
-
-  switch ($delta) {
-    case 'new':
-      $form['user_block_whois_new_count'] = array(
-        '#type' => 'select',
-        '#title' => t('Number of users to display'),
-        '#default_value' => variable_get('user_block_whois_new_count', 5),
-        '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
-      );
-      return $form;
-
-    case 'online':
-      $period = drupal_map_assoc(array(30, 60, 120, 180, 300, 600, 900, 1800, 2700, 3600, 5400, 7200, 10800, 21600, 43200, 86400), 'format_interval');
-      $form['user_block_seconds_online'] = array('#type' => 'select', '#title' => t('User activity'), '#default_value' => variable_get('user_block_seconds_online', 900), '#options' => $period, '#description' => t('A user is considered online for this long after they have last viewed a page.'));
-      $form['user_block_max_list_count'] = array('#type' => 'select', '#title' => t('User list length'), '#default_value' => variable_get('user_block_max_list_count', 10), '#options' => drupal_map_assoc(array(0, 5, 10, 15, 20, 25, 30, 40, 50, 75, 100)), '#description' => t('Maximum number of currently online users to display.'));
-      return $form;
-  }
-}
-
-/**
- * Implements hook_block_save().
- */
-function user_block_save($delta = '', $edit = array()) {
-  global $user;
-
-  switch ($delta) {
-    case 'new':
-      variable_set('user_block_whois_new_count', $edit['user_block_whois_new_count']);
-      break;
-
-    case 'online':
-      variable_set('user_block_seconds_online', $edit['user_block_seconds_online']);
-      variable_set('user_block_max_list_count', $edit['user_block_max_list_count']);
-      break;
-  }
-}
-
-/**
- * Implements hook_block_view().
- */
-function user_block_view($delta = '') {
-  global $user;
-
-  $block = array();
-
-  switch ($delta) {
-    case 'login':
-      // For usability's sake, avoid showing two login forms on one page.
-      if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
-        // Customize the login form.
-        $form = drupal_get_form('user_login_form');
-        unset($form['name']['#attributes']['autofocus']);
-        unset($form['name']['#description']);
-        unset($form['pass']['#description']);
-        $form['name']['#size'] = 15;
-        $form['pass']['#size'] = 15;
-        $form['#action'] = url(current_path(), array('query' => drupal_get_destination(), 'external' => FALSE));
-        // Build action links.
-        $items = array();
-        if (config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) {
-          $items['create_account'] = l(t('Create new account'), 'user/register', array(
-            'attributes' => array(
-              'title' => t('Create a new user account.'),
-              'class' => array('create-account-link'),
-            ),
-          ));
-        }
-        $items['request_password'] = l(t('Request new password'), 'user/password', array(
-          'attributes' => array(
-            'title' => t('Request new password via e-mail.'),
-            'class' => array('request-password-link'),
-          ),
-        ));
-        // Build a block as renderable array.
-        $block['subject'] = t('User login');
-        $block['content'] = array(
-          'user_login_form' => $form,
-          'user_links' => array(
-            '#theme' => 'item_list',
-            '#items' => $items,
-          )
-        );
-      }
-      return $block;
-
-    case 'new':
-      if (user_access('access content')) {
-        // Retrieve a list of new users who have subsequently accessed the site successfully.
-        $items = db_query_range('SELECT uid, name FROM {users} WHERE status <> 0 AND access <> 0 ORDER BY created DESC', 0, variable_get('user_block_whois_new_count', 5))->fetchAll();
-
-        $block['subject'] = t('Who\'s new');
-        $block['content'] = array(
-          '#theme' => 'item_list__user__new',
-          '#items' => array(),
-        );
-        foreach ($items as $account) {
-          $block['content']['#items'][] = theme('username', array('account' => $account));
-        }
-      }
-      return $block;
-
-    case 'online':
-      if (user_access('access content')) {
-        // Count users active within the defined period.
-        $interval = REQUEST_TIME - variable_get('user_block_seconds_online', 900);
-
-        // Perform database queries to gather online user lists. We use s.timestamp
-        // rather than u.access because it is much faster.
-        $authenticated_count = db_query("SELECT COUNT(DISTINCT s.uid) FROM {sessions} s WHERE s.timestamp >= :timestamp AND s.uid > 0", array(':timestamp' => $interval))->fetchField();
-
-        $block['subject'] = t('Who\'s online');
-        $block['content'] = array(
-          '#theme' => 'item_list__user__online',
-          '#items' => array(),
-          '#prefix' => '<p>' . format_plural($authenticated_count, 'There is currently 1 user online.', 'There are currently @count users online.') . '</p>',
-        );
-
-        // Display a list of currently online users.
-        $max_users = variable_get('user_block_max_list_count', 10);
-        if ($authenticated_count && $max_users) {
-          $items = db_query_range('SELECT u.uid, u.name, MAX(s.timestamp) AS max_timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= :interval AND s.uid > 0 GROUP BY u.uid, u.name ORDER BY max_timestamp DESC', 0, $max_users, array(':interval' => $interval))->fetchAll();
-
-          foreach ($items as $account) {
-            $block['content']['#items'][] = theme('username', array('account' => $account));
-          }
-        }
-      }
-      return $block;
-  }
-}
-
-/**
  * Implements hook_preprocess_HOOK() for block.tpl.php.
  */
 function user_preprocess_block(&$variables) {
   if ($variables['block']->module == 'user') {
-    switch ($variables['block']->delta) {
-      case 'login':
+    switch ($variables['block']->id) {
+      case 'user_login_block':
         $variables['attributes']['role'] = 'form';
         break;
-      case 'new':
+      case 'user_new_block':
         $variables['attributes']['role'] = 'complementary';
         break;
-      case 'online':
+      case 'user_online_block':
         $variables['attributes']['role'] = 'complementary';
         break;
     }
   }
 }
 
+
+
 /**
  * Format a username.
  *
