diff --git a/core/modules/block/block.install b/core/modules/block/block.install index 7010f59..fba7c5d 100644 --- a/core/modules/block/block.install +++ b/core/modules/block/block.install @@ -16,3 +16,129 @@ 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 = [ + 'language' => 'language.current_language_context', + '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; + } + // We replace the previous format of "{$context_id}" + // with "@{$service_id}:{$unqualified_context_id}". + $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. + $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 .= ''; + + 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..da03fa0 --- /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:'); + + // 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 0000000..55e12ab --- /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 9d806bb..a656ab3 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 0000000..1a8a904 --- /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 0000000..d2ac964 --- /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 0000000..472e131 --- /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 0000000..23b67e5 --- /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();