diff --git a/core/modules/block/block.install b/core/modules/block/block.install
index 7010f59b028..99ce4ef5ef8 100644
--- a/core/modules/block/block.install
+++ b/core/modules/block/block.install
@@ -16,3 +16,131 @@ function block_install() {
// invalidate all cached HTML output.
Cache::invalidateTags(['rendered']);
}
+
+/**
+ * @addtogroup updates-8.0.0-beta
+ * @{
+ */
+
+/**
+ * Update block visibility context mapping.
+ */
+function block_update_8001() {
+ // This update function updates blocks for the change from
+ // https://www.drupal.org/node/2354889.
+
+ // Core visibility context plugins are updated automatically; blocks with
+ // unknown plugins are disabled and their previous visibility settings are
+ // saved in key value storage; see change record
+ // https://www.drupal.org/node/2527840 for more explanation.
+
+ // These are all the contexts that Drupal core provides.
+ $context_service_id_map = [
+ 'node.node' => '@node.node_route_context:node',
+ 'user.current_user' => '@user.current_user_context:current_user',
+ ];
+
+ foreach (array_keys(\Drupal::languageManager()->getDefinedLanguageTypesInfo()) as $language_type_id) {
+ $context_service_id_map['language.' . $language_type_id] = '@language.current_language_context:' . $language_type_id;
+ }
+
+ // Contributed modules should leverage hook_update_dependencies() in order to
+ // be executed before block_update_8002(), so they can update their context
+ // mappings, if wanted.
+ $config_factory = \Drupal::configFactory();
+ $backup_values = $update_backup = [];
+
+ 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[$context])) {
+ // 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 contributed
+ // modules can run their own update functions to update mappings
+ // that they provide.
+ $backup_values[$context][] = $condition_plugin_id;
+ unset($visibility[$condition_plugin_id]);
+ continue;
+ }
+ // Replace the context ID based on the defined mapping.
+ $condition['context_mapping'][$key] = $context_service_id_map[$context];
+ }
+ }
+ $block->set('visibility', $visibility);
+
+ if ($backup_values) {
+ // We not only store the missing context mappings but also the previous
+ // block status, in order to allow contributed and custom modules to do
+ // their own updates.
+ $update_backup[$block->get('id')] = [
+ 'missing_context_ids' => $backup_values,
+ 'status' => $block->get('status')
+ ];
+ }
+ }
+
+ // Mark the resulting configuration as trusted data. This avoids issues with
+ // future schema changes.
+ $block->save(TRUE);
+ }
+
+ if ($update_backup) {
+ \Drupal::keyValue('update_backup')->set('block_update_8001', $update_backup);
+ }
+
+ return t('Block context IDs updated.');
+}
+
+/**
+ * Disable all blocks with missing context IDs in block_update_8001().
+ */
+function block_update_8002() {
+ $block_update_8001 = \Drupal::keyValue('update_backup')->get('block_update_8001', []);
+
+ $block_ids = array_keys($block_update_8001);
+ $config_factory = \Drupal::configFactory();
+ /** @var \Drupal\Core\Config\Config[] $blocks */
+ $blocks = [];
+ foreach ($block_ids as $block_id) {
+ $blocks[$block_id] = $block = $config_factory->getEditable('block.block.' . $block_id);
+ // This block will have an invalid context mapping service and must be
+ // disabled in order to prevent information disclosure.
+
+ // Disable currently enabled blocks.
+ if ($block_update_8001[$block_id]['status']) {
+ $block->set('status', FALSE);
+ $block->save(TRUE);
+ }
+ }
+
+ // Provides a list of plugin labels, keyed by plugin ID.
+ $condition_plugin_id_label_map = array_column(\Drupal::service('plugin.manager.condition')->getDefinitions(), 'label', 'id');
+
+ // Override with the UI labels we are aware of. Sadly they are not machine
+ // accessible, see
+ // \Drupal\node\Plugin\Condition\NodeType::buildConfigurationForm().
+ $condition_plugin_id_label_map['node_type'] = t('Content types');
+ $condition_plugin_id_label_map['request_path'] = t('Pages');
+ $condition_plugin_id_label_map['user_role'] = t('Roles');
+
+ if (count($block_ids) > 0) {
+ $message = t('Encountered an unknown context mapping key coming probably from a contributed or custom module: One or more mappings could not be updated. Please manually review your visibility settings for the following blocks, which are disabled now:');
+ $message .= '
';
+ foreach ($blocks as $disabled_block_id => $disabled_block) {
+ $message .= '- ' . t('@label (Visibility: @plugin_ids)', array(
+ '@label' => $disabled_block->get('settings.label'),
+ '@plugin_ids' => implode(', ', array_intersect_key($condition_plugin_id_label_map, array_flip(array_keys($block_update_8001[$disabled_block_id]['missing_context_ids']))))
+ )) . '
';
+ }
+ $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 00000000000..da03fa05433
--- /dev/null
+++ b/core/modules/block/src/Tests/Update/BlockContextMappingUpdateTest.php
@@ -0,0 +1,110 @@
+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 is updated properly.
+ */
+ public function testUpdateHookN() {
+ $this->runUpdates();
+ $this->assertRaw('Encountered an unknown context mapping key coming probably from a contributed or custom module: One or more mappings could not be updated. Please manually review your visibility settings for the following blocks, which are disabled now:- User login (Visibility: Baloney spam)
');
+
+ // Disable maintenance mode.
+ \Drupal::state()->set('system.maintenance_mode', FALSE);
+
+ // We finished updating so we can login the user now.
+ $this->drupalLogin($this->rootUser);
+
+ // 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.');
+
+ // Ensure that all the context mappings got updated properly.
+ $block = Block::load('testfor2354889');
+ $visibility = $block->get('visibility');
+ $this->assertEqual('@node.node_route_context:node', $visibility['node_type']['context_mapping']['node']);
+ $this->assertEqual('@user.current_user_context:current_user', $visibility['user_role']['context_mapping']['user']);
+ $this->assertEqual('@language.current_language_context:language_interface', $visibility['language']['context_mapping']['language']);
+
+ // 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');
+
+ $this->assertEqual(['thirdtestfor2354889' => ['missing_context_ids' => ['baloney.spam' => ['node_type']], 'status' => TRUE]], \Drupal::keyValue('update_backup')->get('block_update_8001'));
+
+ $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/block/tests/modules/block_test/src/Plugin/Condition/BaloneySpam.php b/core/modules/block/tests/modules/block_test/src/Plugin/Condition/BaloneySpam.php
new file mode 100644
index 00000000000..55e12abaaef
--- /dev/null
+++ b/core/modules/block/tests/modules/block_test/src/Plugin/Condition/BaloneySpam.php
@@ -0,0 +1,37 @@
+fail('Missing zlib requirement for upgrade tests.');
return FALSE;
}
- $this->drupalLogin($this->rootUser);
+ // The site might be broken at the time so logging in using the UI might
+ // not work, so we use the API itself.
+ drupal_rewrite_settings(['settings' => ['update_free_access' => (object) [
+ 'value' => TRUE,
+ 'required' => TRUE,
+ ]]]);
+
$this->drupalGet($this->updateUrl);
$this->clickLink(t('Continue'));
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 9d806bb532c..a656ab3aaa2 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -454,6 +454,8 @@ system.db_update:
_title: 'Drupal database update'
_controller: '\Drupal\system\Controller\DbUpdateController::handle'
op: 'info'
+ options:
+ _maintenance_access: TRUE
requirements:
_access_system_update: 'TRUE'
diff --git a/core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml b/core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml
new file mode 100644
index 00000000000..1a8a9046ffb
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml
@@ -0,0 +1,23 @@
+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: { }
diff --git a/core/modules/system/tests/fixtures/update/block.block.testfor2354889.yml b/core/modules/system/tests/fixtures/update/block.block.testfor2354889.yml
new file mode 100644
index 00000000000..d2ac964da81
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/block.block.testfor2354889.yml
@@ -0,0 +1,51 @@
+uuid: 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_mapping:
+ node: node.node
+ user_role:
+ id: user_role
+ roles:
+ authenticated: authenticated
+ negate: false
+ context_mapping:
+ user: user.current_user
+ language:
+ id: language
+ langcodes:
+ en: en
+ de: de
+ negate: false
+ context_mapping:
+ language: language.language_interface
diff --git a/core/modules/system/tests/fixtures/update/block.block.thirdtestfor2354889.yml b/core/modules/system/tests/fixtures/update/block.block.thirdtestfor2354889.yml
new file mode 100644
index 00000000000..472e1317e51
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/block.block.thirdtestfor2354889.yml
@@ -0,0 +1,30 @@
+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
+ context_mapping:
+ baloney: baloney.spam
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 00000000000..23b67e55a4c
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php
@@ -0,0 +1,53 @@
+insert('config')
+ ->fields(array(
+ 'collection',
+ 'name',
+ 'data',
+ ))
+ ->values(array(
+ 'collection' => '',
+ 'name' => 'block.block.' . $block_config['id'],
+ 'data' => serialize($block_config),
+ ))
+ ->execute();
+}
+
+// Update the config entity query "index".
+$existing_blocks = $connection->select('key_value')
+ ->fields('key_value', ['value'])
+ ->condition('collection', 'config.entity.key_store.block')
+ ->condition('name', 'theme:bartik')
+ ->execute()
+ ->fetchField();
+$existing_blocks = unserialize($existing_blocks);
+
+$connection->update('key_value')
+ ->fields([
+ 'value' => serialize(array_merge($existing_blocks, ['block.block.testfor2354889', 'block.block.secondtestfor2354889', 'block.block.thirdtestfor2354889']))
+ ])
+ ->condition('collection', 'config.entity.key_store.block')
+ ->condition('name', 'theme:bartik')
+ ->execute();