Skip to content

Commit 69e6da6

Browse files
committed
Issue #3379725 by Wim Leers, phenaproxima, narendraR, alexpott, quietone, borisson_, tim.plunkett: Make Block config entities fully validatable
1 parent 4950de1 commit 69e6da6

33 files changed

+403
-74
lines changed

config/schema/core.data_types.schema.yml

+11-11
Original file line numberDiff line numberDiff line change
@@ -427,12 +427,16 @@ config_entity:
427427
requiredKey: false
428428
type: _core_config_info
429429

430+
# This applies to all blocks that have no additional settings of their own.
430431
block.settings.*:
431432
type: block_settings
433+
constraints:
434+
FullyValidatable: ~
432435

433436
block_settings:
434437
type: mapping
435438
label: 'Block settings'
439+
# This is intentionally not marked as fully validatable: each `type: block.settings.SOMETHING` must do that.
436440
mapping:
437441
id:
438442
type: string
@@ -441,21 +445,17 @@ block_settings:
441445
type: label
442446
label: 'Description'
443447
label_display:
444-
type: string
448+
type: label
445449
label: 'Display title'
446450
provider:
447451
type: string
448-
label: 'Provider'
449-
status:
450-
type: boolean
451-
label: 'Status'
452-
info:
453-
type: label
454-
label: 'Admin info'
455-
view_mode:
456-
type: string
457-
label: 'View mode'
452+
label: 'Provider of this block plugin'
453+
constraints:
454+
NotBlank: []
455+
ExtensionName: []
456+
ExtensionExists: module
458457
context_mapping:
458+
requiredKey: false
459459
type: sequence
460460
label: 'Context assignments'
461461
sequence:

modules/block/block.post_update.php

+18
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
* Post update functions for Block.
66
*/
77

8+
use Drupal\block\BlockInterface;
9+
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
10+
811
/**
912
* Implements hook_removed_post_updates().
1013
*/
@@ -16,3 +19,18 @@ function block_removed_post_updates() {
1619
'block_post_update_replace_node_type_condition' => '10.0.0',
1720
];
1821
}
22+
23+
/**
24+
* Ensures that all block weights are integers.
25+
*/
26+
function block_post_update_make_weight_integer(array &$sandbox = []): void {
27+
\Drupal::classResolver(ConfigEntityUpdater::class)
28+
->update($sandbox, 'block', function (BlockInterface $block): bool {
29+
$weight = $block->getWeight();
30+
if (!is_int($weight)) {
31+
$block->setWeight($weight);
32+
return TRUE;
33+
}
34+
return FALSE;
35+
});
36+
}

modules/block/config/schema/block.schema.yml

+11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
block.block.*:
44
type: config_entity
55
label: 'Block'
6+
constraints:
7+
FullyValidatable: ~
68
mapping:
79
id:
810
type: machine_name
@@ -17,13 +19,22 @@ block.block.*:
1719
theme:
1820
type: string
1921
label: 'Theme'
22+
constraints:
23+
NotBlank: []
24+
ExtensionName: []
25+
ExtensionExists: theme
2026
region:
2127
type: string
2228
label: 'Region'
29+
constraints:
30+
NotBlank: []
31+
Callback: ['\Drupal\block\Entity\Block', validateRegion]
2332
weight:
2433
type: weight
2534
label: 'Weight'
2635
provider:
36+
# @todo Deprecate this from config schema and remove it from the `config_export` definition in https://www.drupal.org/project/drupal/issues/3426278
37+
nullable: true
2738
type: string
2839
label: 'Provider'
2940
plugin:

modules/block/migrations/d6_block.yml

+8-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,14 @@ process:
9696
right: sidebar
9797
# If mapping fails, put the block in the content region.
9898
default_value: content
99-
weight: weight
99+
weight:
100+
-
101+
plugin: get
102+
source: weight
103+
-
104+
# Block weights must be integers.
105+
plugin: callback
106+
callable: intval
100107
settings:
101108
plugin: block_settings
102109
source:

modules/block/migrations/d7_block.yml

+8-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,14 @@ process:
117117
sidebar_first: 'sidebar_first'
118118
# If mapping fails, put the block in the content region.
119119
default_value: content
120-
weight: weight
120+
weight:
121+
-
122+
plugin: get
123+
source: weight
124+
-
125+
# Block weights must be integers.
126+
plugin: callback
127+
callable: intval
121128
settings:
122129
plugin: block_settings
123130
source:

modules/block/src/BlockForm.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ public function form(array $form, FormStateInterface $form_state) {
174174
}
175175

176176
// Hidden weight setting.
177-
$weight = $entity->isNew() ? $this->getRequest()->query->get('weight', 0) : $entity->getWeight();
177+
$weight = $entity->isNew() ? $this->getRequest()->query->getInt('weight') : $entity->getWeight();
178178
$form['weight'] = [
179179
'#type' => 'hidden',
180180
'#default_value' => $weight,

modules/block/src/Entity/Block.php

+27-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Drupal\Core\Config\Entity\ConfigEntityInterface;
1212
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
1313
use Drupal\Core\Entity\EntityStorageInterface;
14+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
1415
use Drupal\Core\StringTranslation\TranslatableMarkup;
1516

1617
/**
@@ -89,7 +90,7 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin
8990
*
9091
* @var int
9192
*/
92-
protected $weight;
93+
protected $weight = 0;
9394

9495
/**
9596
* The plugin instance ID.
@@ -323,7 +324,7 @@ public function setRegion($region) {
323324
*/
324325
#[ActionMethod(adminLabel: new TranslatableMarkup('Set weight'), pluralize: FALSE)]
325326
public function setWeight($weight) {
326-
$this->weight = $weight;
327+
$this->weight = (int) $weight;
327328
return $this;
328329
}
329330

@@ -347,6 +348,11 @@ public function createDuplicateBlock($new_id = NULL, $new_theme = NULL) {
347348
public function preSave(EntityStorageInterface $storage) {
348349
parent::preSave($storage);
349350

351+
if (!is_int($this->weight)) {
352+
@trigger_error('Saving a block with a non-integer weight is deprecated in drupal:11.1.0 and removed in drupal:12.0.0. See https://www.drupal.org/node/3462474', E_USER_DEPRECATED);
353+
$this->setWeight((int) $this->weight);
354+
}
355+
350356
// Ensure the region is valid to mirror the behavior of block_rebuild().
351357
// This is done primarily for backwards compatibility support of
352358
// \Drupal\block\BlockInterface::BLOCK_REGION_NONE.
@@ -358,4 +364,23 @@ public function preSave(EntityStorageInterface $storage) {
358364
}
359365
}
360366

367+
/**
368+
* Validates that a region exists in the active theme.
369+
*
370+
* @param null|string $region
371+
* The region to validate.
372+
* @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
373+
* The validation context.
374+
*/
375+
public static function validateRegion(?string $region, ExecutionContextInterface $context): void {
376+
if ($theme = $context->getRoot()->get('theme')->getValue()) {
377+
if (!array_key_exists($region, system_region_list($theme))) {
378+
$context->addViolation('This is not a valid region of the %theme theme.', ['%theme' => $theme]);
379+
}
380+
}
381+
else {
382+
$context->addViolation('This block does not say which theme it appears in.');
383+
}
384+
}
385+
361386
}

modules/block/tests/src/Functional/BlockInvalidRegionTest.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ class BlockInvalidRegionTest extends BrowserTestBase {
2121
*/
2222
protected static $modules = ['block', 'block_test'];
2323

24+
/**
25+
* {@inheritdoc}
26+
*/
27+
protected static $configSchemaCheckerExclusions = [
28+
// This block is intentionally put in an invalid region, so it will violate
29+
// config schema.
30+
// @see ::testBlockInvalidRegion()
31+
'block.block.invalid_region',
32+
];
33+
2434
/**
2535
* {@inheritdoc}
2636
*/
@@ -45,7 +55,7 @@ protected function setUp(): void {
4555
*/
4656
public function testBlockInInvalidRegion(): void {
4757
// Enable a test block and place it in an invalid region.
48-
$block = $this->drupalPlaceBlock('test_html');
58+
$block = $this->drupalPlaceBlock('test_html', ['id' => 'invalid_region']);
4959
\Drupal::configFactory()->getEditable('block.block.' . $block->id())->set('region', 'invalid_region')->save();
5060
$block = Block::load($block->id());
5161

modules/block/tests/src/Functional/BlockRenderOrderTest.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@ public function testBlockRenderOrder(): void {
4646
$region = 'header';
4747
$test_blocks = [
4848
'stark_powered' => [
49-
'weight' => '-3',
49+
'weight' => -3,
5050
'id' => 'stark_powered',
5151
'label' => 'Test block A',
5252
],
5353
'stark_by' => [
54-
'weight' => '3',
54+
'weight' => 3,
5555
'id' => 'stark_by',
5656
'label' => 'Test block C',
5757
],
5858
'stark_drupal' => [
59-
'weight' => '3',
59+
'weight' => 3,
6060
'id' => 'stark_drupal',
6161
'label' => 'Test block B',
6262
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Drupal\Tests\block\Functional;
6+
7+
use Drupal\block\Entity\Block;
8+
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
9+
10+
/**
11+
* @covers block_post_update_make_weight_integer
12+
* @group block
13+
*/
14+
class BlockWeightUpdateTest extends UpdatePathTestBase {
15+
16+
/**
17+
* {@inheritdoc}
18+
*/
19+
protected function setDatabaseDumpFiles() {
20+
$this->databaseDumpFiles = [
21+
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz',
22+
];
23+
}
24+
25+
/**
26+
* Tests update path for blocks' `weight` property.
27+
*/
28+
public function testRunUpdates() {
29+
// Find a block and change it to have a null weight.
30+
/** @var \Drupal\Core\Database\Connection $database */
31+
$database = $this->container->get('database');
32+
$block = $database->select('config', 'c')
33+
->fields('c', ['data'])
34+
->condition('name', 'block.block.claro_content')
35+
->execute()
36+
->fetchField();
37+
$block = unserialize($block);
38+
$block['weight'] = NULL;
39+
$database->update('config')
40+
->fields([
41+
'data' => serialize($block),
42+
])
43+
->condition('name', 'block.block.claro_content')
44+
->execute();
45+
46+
$this->assertNull(Block::load('claro_content')->get('weight'));
47+
$this->runUpdates();
48+
$this->assertSame(0, Block::load('claro_content')->get('weight'));
49+
}
50+
51+
}

modules/block/tests/src/Functional/Rest/BlockResourceTestBase.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ protected function getExpectedNormalizedEntity() {
8282
$normalization = [
8383
'uuid' => $this->entity->uuid(),
8484
'id' => 'llama',
85-
'weight' => NULL,
85+
'weight' => 0,
8686
'langcode' => 'en',
8787
'status' => TRUE,
8888
'dependencies' => [

modules/block/tests/src/Kernel/BlockRebuildTest.php

+17-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ class BlockRebuildTest extends KernelTestBase {
2323
*/
2424
protected static $modules = ['block', 'system'];
2525

26+
/**
27+
* {@inheritdoc}
28+
*/
29+
protected static $configSchemaCheckerExclusions = [
30+
// These blocks are intentionally put into invalid regions, so they will
31+
// violate config schema.
32+
// @see ::testRebuildInvalidBlocks()
33+
'block.block.invalid_block1',
34+
'block.block.invalid_block2',
35+
];
36+
2637
/**
2738
* {@inheritdoc}
2839
*/
@@ -71,8 +82,12 @@ public function testRebuildNoInvalidBlocks(): void {
7182
*/
7283
public function testRebuildInvalidBlocks(): void {
7384
$this->placeBlock('system_powered_by_block', ['region' => 'content']);
74-
$block1 = $this->placeBlock('system_powered_by_block');
75-
$block2 = $this->placeBlock('system_powered_by_block');
85+
$block1 = $this->placeBlock('system_powered_by_block', [
86+
'id' => 'invalid_block1',
87+
]);
88+
$block2 = $this->placeBlock('system_powered_by_block', [
89+
'id' => 'invalid_block2',
90+
]);
7691
$block2->disable()->save();
7792
// Use the config API directly to bypass Block::preSave().
7893
\Drupal::configFactory()->getEditable('block.block.' . $block1->id())->set('region', 'INVALID')->save();

modules/block/tests/src/Kernel/BlockStorageUnitTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ protected function createTests() {
9393
'id' => 'test_block',
9494
'theme' => 'stark',
9595
'region' => 'content',
96-
'weight' => NULL,
96+
'weight' => 0,
9797
'provider' => NULL,
9898
'plugin' => 'test_html',
9999
'settings' => [

0 commit comments

Comments
 (0)