diff --git a/core/modules/block/block.install b/core/modules/block/block.install
new file mode 100644
index 0000000..41e1cdd
--- /dev/null
+++ b/core/modules/block/block.install
@@ -0,0 +1,84 @@
+ 'language.current_language_context',
+ 'node' => 'node.node_route_context',
+ 'user' => 'user.current_user_context',
+ ];
+
+ $condition_plugin_id_ui_label_map = [
+ 'node_type' => \Drupal::translation()->translate('Content types'),
+ 'request_path' => \Drupal::translation()->translate('Pages'),
+ 'user_role' => \Drupal::translation()->translate('Roles'),
+ ];
+
+ $config_factory = \Drupal::configFactory();
+ $message = NULL;
+ $disabled_blocks = $problematic_visibility_plugins = [];
+ // We deal with config objects directly in order to avoid invoking Entity API
+ // hooks. They can be problematic when two modules implement a specific hook,
+ // their code is updated at the same time, yet the update functions run
+ // sequentially so the second hook implementation can not rely on the data not
+ // being altered by the first hook.
+ foreach ($config_factory->listAll('block.block.') as $block_config_name) {
+ $block = $config_factory->getEditable($block_config_name);
+ if ($visibility = $block->get('visibility')) {
+ foreach ($visibility as $condition_plugin_id => &$condition) {
+ foreach ($condition['context_mapping'] as $key => $context) {
+ if (!isset($context_service_id_map[$key])) {
+ // Remove the visibility condition for unknown context mapping
+ // entries, so the update process itself runs through and users can
+ // fix their block placements manually OR alternatively contrib
+ // modules can run their own update functions to update mappings
+ // that they provide.
+ $disabled_blocks[$block->get('id')] = $block->get('settings.label');
+ $problematic_visibility_plugins[$block->get('id')][] = $condition_plugin_id_ui_label_map[$condition_plugin_id];
+
+ unset($visibility[$condition_plugin_id]);
+ continue;
+ }
+ $new_context_id = explode('.', $context, 2);
+ $condition['context_mapping'][$key] = '@' . $context_service_id_map[$key] . ':' . $new_context_id[1];
+ }
+ }
+ if (isset($disabled_blocks[$block->get('id')])) {
+ // This block will have an invalid context mapping service and must be
+ // disabled to avoid the 'You must provide the context IDs in the
+ // @{service_id}:{unqualified_context_id} format' exception.
+ $block->set('status', FALSE);
+ }
+ $block->set('visibility', $visibility);
+ }
+
+ $block->save();
+ }
+
+ if ($disabled_blocks) {
+ $message = \Drupal::translation()->translate('Encountered an unexpected context mapping key, one or more mappings could not be updated. Please manually review your visibility settings for the following blocks:');
+ $message .= '
';
+ foreach ($disabled_blocks as $disabled_block_id => $disabled_block_label) {
+ $message .= '- ' . $disabled_block_label . ' (' . \Drupal::translation()->translate('Visibility') . ': ' . implode(', ', $problematic_visibility_plugins[$disabled_block_id]) . ')
';
+ }
+ $message .= '
';
+ }
+
+ return $message;
+}
+
+/**
+ * @} End of "addtogroup updates-8.0.0-beta".
+ */
diff --git a/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php b/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php
new file mode 100644
index 0000000..6e2b65b
--- /dev/null
+++ b/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php
@@ -0,0 +1,93 @@
+databaseDumpFiles = [
+ __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+ __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php',
+ ];
+ parent::setUp();
+ }
+
+ /**
+ * Tests that block context mapping are updated properly.
+ */
+ public function testUpdateHookN() {
+ $this->runUpdates();
+ $this->assertRaw('Encountered an unexpected context mapping key, one or more mappings could not be updated. Please manually review your visibility settings for the following blocks:- User login (Visibility: Content types)
');
+
+ // Disable maintenance mode.
+ \Drupal::state()->set('system.maintenance_mode', FALSE);
+
+ // The block that we are testing has the following visibility rules:
+ // - only visible on node pages
+ // - only visible to authenticated users.
+ $block_title = 'Test for 2354889';
+
+ // Create two nodes, a page and an article.
+ $page = Node::create([
+ 'type' => 'page',
+ 'title' => 'Page node',
+ ]);
+ $page->save();
+
+ $article = Node::create([
+ 'type' => 'article',
+ 'title' => 'Article node',
+ ]);
+ $article->save();
+
+ // Check that the block appears only on Page nodes for authenticated users.
+ $this->drupalGet('node/' . $page->id());
+ $this->assertRaw($block_title, 'Test block is visible on a Page node as an authenticated user.');
+
+ $this->drupalGet('node/' . $article->id());
+ $this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an authenticated user.');
+
+ $this->drupalLogout();
+
+ // Check that the block does not appear on any page for anonymous users.
+ $this->drupalGet('node/' . $page->id());
+ $this->assertNoRaw($block_title, 'Test block is not visible on a Page node as an anonymous user.');
+
+ $this->drupalGet('node/' . $article->id());
+ $this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an anonymous user.');
+
+ // Check that a block with invalid context is being disabled and that it can
+ // still be edited afterward.
+ $disabled_block = Block::load('thirdtestfor2354889');
+ $this->assertFalse($disabled_block->status(), 'Block with invalid context is disabled');
+
+ $disabled_block_visibility = $disabled_block->get('visibility');
+ $this->assertTrue(!isset($disabled_block_visibility['node_type']), 'The problematic visibility condition has been removed.');
+
+ $admin_user = $this->drupalCreateUser(['administer blocks']);
+ $this->drupalLogin($admin_user);
+
+ $this->drupalGet('admin/structure/block/manage/thirdtestfor2354889');
+ $this->assertResponse('200');
+ }
+
+}
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php b/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php
new file mode 100644
index 0000000..0ad77c5
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php
@@ -0,0 +1,158 @@
+ '9d204071-a923-4707-8200-c298a540fb0c',
+ 'langcode' => 'en',
+ 'status' => TRUE,
+ 'dependencies' => [
+ 'content' => [
+ 'block_content:basic:c1895145-893e-460b-a24e-78cd2cefbb1f',
+ ],
+ 'module' => [
+ 'block_content',
+ 'node',
+ 'user',
+ ],
+ 'theme' => [
+ 'bartik',
+ ],
+ ],
+ 'id' => 'testfor2354889',
+ 'theme' => 'bartik',
+ 'region' => 'content',
+ 'weight' => -6,
+ 'provider' => NULL,
+ 'plugin' => 'block_content:c1895145-893e-460b-a24e-78cd2cefbb1f',
+ 'settings' => [
+ 'id' => 'block_content:c1895145-893e-460b-a24e-78cd2cefbb1f',
+ 'label' => 'Test for 2354889',
+ 'provider' => 'block_content',
+ 'label_display' => 'visible',
+ 'cache' => [
+ 'max_age' => -1,
+ ],
+ 'status' => TRUE,
+ 'info' => '',
+ 'view_mode' => 'full',
+ ],
+ 'visibility' => [
+ 'node_type' => [
+ 'id' => 'node_type',
+ 'bundles' => [
+ 'page' => 'page',
+ ],
+ 'negate' => FALSE,
+ // Context that will be tested.
+ 'context_mapping' => [
+ 'node' => 'node.node',
+ ],
+ ],
+ 'user_role' => [
+ 'id' => 'user_role',
+ 'roles' => [
+ 'authenticated' => 'authenticated',
+ ],
+ 'negate' => FALSE,
+ // Context that will be tested.
+ 'context_mapping' => [
+ 'user' => 'user.current_user',
+ ],
+ ],
+ ],
+],[
+ 'uuid' => 'C499B41D-035E-432E-9462-36410C43C49F',
+ 'langcode' => 'en',
+ 'status' => TRUE,
+ 'dependencies' => [
+ 'module' => [
+ 'search',
+ ],
+ 'theme' => [
+ 'bartik',
+ ],
+ ],
+ 'id' => 'secondtestfor2354889',
+ 'theme' => 'bartik',
+ 'region' => 'sidebar_first',
+ 'weight' => -6,
+ 'provider' => NULL,
+ 'plugin' => 'search_form_block',
+ 'settings' => [
+ 'id' => 'search_form_block',
+ 'label' => 'Search',
+ 'provider' => 'search',
+ 'label_display' => 'visible',
+ 'cache' => [
+ 'max_age' => -1,
+ ],
+ 'status' => TRUE,
+ ],
+ 'visibility' => [],
+],[
+ 'uuid' => '4558907D-2918-48FE-B56F-8A007B5FBDD5',
+ 'langcode' => 'en',
+ 'status' => TRUE,
+ 'dependencies' => [
+ 'module' => [
+ 'user',
+ ],
+ 'theme' => [
+ 'bartik',
+ ],
+ ],
+ 'id' => 'thirdtestfor2354889',
+ 'theme' => 'bartik',
+ 'region' => 'sidebar_first',
+ 'weight' => -6,
+ 'provider' => NULL,
+ 'plugin' => 'user_login_block',
+ 'settings' => [
+ 'id' => 'user_login_block',
+ 'label' => 'User login',
+ 'provider' => 'user',
+ 'label_display' => 'visible',
+ 'cache' => [
+ 'max_age' => -1,
+ ],
+ 'status' => TRUE,
+ ],
+ 'visibility' => [
+ 'node_type' => [
+ 'id' => 'node_type',
+ 'bundles' => [
+ 'page' => 'page',
+ ],
+ 'negate' => FALSE,
+ // Non-existent mapping that will be tested.
+ 'context_mapping' => [
+ 'baloney' => 'baloney.spam',
+ ],
+ ],
+ ],
+]];
+
+foreach ($block_configs as $block_config) {
+ $connection->insert('config')
+ ->fields(array(
+ 'collection',
+ 'name',
+ 'data',
+ ))
+ ->values(array(
+ 'collection' => '',
+ 'name' => 'block.block.' . $block_config['id'],
+ 'data' => serialize($block_config),
+ ))
+ ->execute();
+}