Issue #2528178 by dawehner, amateescu, larowlan, tim.plunkett, jibran, catch, jhodgdon, effulgentsia, Berdir: Provide an upgrade path for blocks context IDs #2354889 (context manager)
parent
444c476a4b
commit
6f96411451
|
@ -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 .= '<ul>';
|
||||
foreach ($blocks as $disabled_block_id => $disabled_block) {
|
||||
$message .= '<li>' . 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']))))
|
||||
)) . '</li>';
|
||||
}
|
||||
$message .= '</ul>';
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-beta".
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\block\Tests\Update\BlockContextMappingUpdateTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\block\Tests\Update;
|
||||
|
||||
use Drupal\block\Entity\Block;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\system\Tests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the upgrade path for block context mapping renames.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2354889
|
||||
*
|
||||
* @group Update
|
||||
*/
|
||||
class BlockContextMappingUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block_test', 'language'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->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:<ul><li>User login (Visibility: Baloney spam)</li></ul>');
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\block_test\Plugin\Condition\BaloneySpam.
|
||||
*/
|
||||
|
||||
namespace Drupal\block_test\Plugin\Condition;
|
||||
|
||||
use Drupal\Core\Condition\ConditionPluginBase;
|
||||
|
||||
/**
|
||||
* Provides a 'baloney.spam' condition.
|
||||
*
|
||||
* @Condition(
|
||||
* id = "baloney.spam",
|
||||
* label = @Translation("Baloney spam"),
|
||||
* )
|
||||
*
|
||||
*/
|
||||
class BaloneySpam extends ConditionPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evaluate() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function summary() {
|
||||
return 'Summary';
|
||||
}
|
||||
|
||||
}
|
|
@ -185,7 +185,13 @@ abstract class UpdatePathTestBase extends WebTestBase {
|
|||
$this->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'));
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
23
core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml
vendored
Normal file
23
core/modules/system/tests/fixtures/update/block.block.secondtestfor2354889.yml
vendored
Normal file
|
@ -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: { }
|
|
@ -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
|
30
core/modules/system/tests/fixtures/update/block.block.thirdtestfor2354889.yml
vendored
Normal file
30
core/modules/system/tests/fixtures/update/block.block.thirdtestfor2354889.yml
vendored
Normal file
|
@ -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
|
53
core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php
vendored
Normal file
53
core/modules/system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
|
||||
* upgrade path of https://www.drupal.org/node/2354889.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// A custom block with visibility settings.
|
||||
$block_configs[] = \Drupal\Component\Serialization\Yaml::decode(file_get_contents(__DIR__ . '/block.block.testfor2354889.yml'));
|
||||
|
||||
// A custom block without any visibility settings.
|
||||
$block_configs[] = \Drupal\Component\Serialization\Yaml::decode(file_get_contents(__DIR__ . '/block.block.secondtestfor2354889.yml'));
|
||||
|
||||
// A custom block with visibility settings that contain a non-existing context
|
||||
// mapping.
|
||||
$block_configs[] = \Drupal\Component\Serialization\Yaml::decode(file_get_contents(__DIR__ . '/block.block.thirdtestfor2354889.yml'));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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();
|
Loading…
Reference in New Issue