Issue #1932810 by Berdir, jibran, joegraduate, joelpittet, JeroenT, RenatoG, tim.plunkett, tedbow, andypost, paulocs, hugronaphor, ankithashetty, Meenakshi_j, chr.fritsch, Beanjammin, nikitagupta, EclipseGc, dawehner, xjm, alexpott, fago, tstoeckler, catch, seanB, larowlan: Add entity bundles condition plugin for entities with bundles

merge-requests/915/head
Alex Pott 2021-07-08 10:22:26 +01:00
parent 3fb0eded18
commit 0f22eb1c63
No known key found for this signature in database
GPG Key ID: 31905460D4A69276
17 changed files with 428 additions and 13 deletions

View File

@ -339,6 +339,14 @@ condition.plugin:
sequence:
type: string
condition.plugin.entity_bundle:*:
type: condition.plugin
mapping:
bundles:
type: sequence
sequence:
type: string
display_variant.plugin:
type: mapping
label: 'Display variant'

View File

@ -0,0 +1,59 @@
<?php
namespace Drupal\Core\Entity\Plugin\Condition\Deriver;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Context\EntityContextDefinition;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Deriver that creates a condition for each entity type with bundles.
*/
class EntityBundle extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new EntityBundle.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type->hasKey('bundle')) {
$this->derivatives[$entity_type_id] = $base_plugin_definition;
$this->derivatives[$entity_type_id]['label'] = $entity_type->getBundleLabel();
$this->derivatives[$entity_type_id]['provider'] = $entity_type->getProvider();
$this->derivatives[$entity_type_id]['context_definitions'] = [
$entity_type_id => EntityContextDefinition::fromEntityType($entity_type),
];
}
}
return $this->derivatives;
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace Drupal\Core\Entity\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the 'Entity Bundle' condition.
*
* @Condition(
* id = "entity_bundle",
* deriver = "\Drupal\Core\Entity\Plugin\Condition\Deriver\EntityBundle",
* )
*/
class EntityBundle extends ConditionPluginBase implements ContainerFactoryPluginInterface {
/**
* The entity type bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* Creates a new EntityBundle instance.
*
* @param array $configuration
* The plugin configuration, i.e. an array with configuration values keyed
* by configuration option name. The special key 'context' may be used to
* initialize the defined contexts by setting it to an array of context
* values keyed by context names.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.bundle.info')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$bundles = $this->entityTypeBundleInfo->getBundleInfo($this->getDerivativeId());
$form['bundles'] = [
'#title' => $this->pluginDefinition['label'],
'#type' => 'checkboxes',
'#options' => array_combine(array_keys($bundles), array_column($bundles, 'label')),
'#default_value' => $this->configuration['bundles'],
];
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['bundles'] = array_filter($form_state->getValue('bundles'));
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function evaluate() {
// Returns true if no bundles are selected and negate option is disabled.
if (empty($this->configuration['bundles']) && !$this->isNegated()) {
return TRUE;
}
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getContextValue($this->getDerivativeId());
return !empty($this->configuration['bundles'][$entity->bundle()]);
}
/**
* {@inheritdoc}
*/
public function summary() {
if (count($this->configuration['bundles']) > 1) {
$bundles = $this->configuration['bundles'];
$last = array_pop($bundles);
$bundles = implode(', ', $bundles);
if (empty($this->configuration['negate'])) {
return $this->t('@bundle_type is @bundles or @last', [
'@bundle_type' => $this->pluginDefinition['label'],
'@bundles' => $bundles,
'@last' => $last,
]);
}
else {
return $this->t('@bundle_type is not @bundles or @last', [
'@bundle_type' => $this->pluginDefinition['label'],
'@bundles' => $bundles,
'@last' => $last,
]);
}
}
$bundle = reset($this->configuration['bundles']);
if (empty($this->configuration['negate'])) {
return $this->t('@bundle_type is @bundle', [
'@bundle_type' => $this->pluginDefinition['label'],
'@bundle' => $bundle,
]);
}
else {
return $this->t('@bundle_type is not @bundle', [
'@bundle_type' => $this->pluginDefinition['label'],
'@bundle' => $bundle,
]);
}
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'bundles' => [],
] + parent::defaultConfiguration();
}
}

View File

@ -191,6 +191,7 @@ buildtest
bundable
bundleable
bundleless
bundlenode
buttonpane
buttonset
buytaert

View File

@ -15,3 +15,21 @@ function block_removed_post_updates() {
'block_post_update_fix_negate_in_conditions' => '9.0.0',
];
}
/**
* Updates the node type visibility condition.
*/
function block_post_update_replace_node_type_condition() {
$config_factory = \Drupal::configFactory();
foreach ($config_factory->listAll('block.block.') as $block_config_name) {
$block = $config_factory->getEditable($block_config_name);
if ($block->get('visibility.node_type')) {
$configuration = $block->get('visibility.node_type');
$configuration['id'] = 'entity_bundle:node';
$block->set('visibility.entity_bundle:node', $configuration);
$block->clear('visibility.node_type');
$block->save(TRUE);
}
}
}

View File

@ -46,7 +46,7 @@
}
$(
'[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]',
'[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-entity-bundlenode"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]',
).drupalSetSummary(checkboxesSummary);
$(

View File

@ -28,7 +28,7 @@
return vals.join(', ');
}
$('[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]').drupalSetSummary(checkboxesSummary);
$('[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-entity-bundlenode"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]').drupalSetSummary(checkboxesSummary);
$('[data-drupal-selector="edit-visibility-request-path"]').drupalSetSummary(function (context) {
var $pages = $(context).find('textarea[name="visibility[request_path][pages]"]');

View File

@ -237,6 +237,16 @@ class BlockForm extends EntityForm {
if ($condition_id == 'language' && !$this->language->isMultilingual()) {
continue;
}
// Don't display the deprecated node type condition unless it has existing
// settings.
// @todo Make this more generic in
// https://www.drupal.org/project/drupal/issues/2922451. Also remove
// the node_type specific logic below.
if ($condition_id == 'node_type' && !isset($visibility[$condition_id])) {
continue;
}
/** @var \Drupal\Core\Condition\ConditionInterface $condition */
$condition = $this->manager->createInstance($condition_id, isset($visibility[$condition_id]) ? $visibility[$condition_id] : []);
$form_state->set(['conditions', $condition_id], $condition);
@ -248,12 +258,17 @@ class BlockForm extends EntityForm {
}
if (isset($form['node_type'])) {
$form['node_type']['#title'] = $this->t('Content types');
$form['node_type']['#title'] = $this->t('Content types (deprecated)');
$form['node_type']['bundles']['#title'] = $this->t('Content types');
$form['node_type']['negate']['#type'] = 'value';
$form['node_type']['negate']['#title_display'] = 'invisible';
$form['node_type']['negate']['#value'] = $form['node_type']['negate']['#default_value'];
}
if (isset($form['entity_bundle:node'])) {
$form['entity_bundle:node']['negate']['#type'] = 'value';
$form['entity_bundle:node']['negate']['#title_display'] = 'invisible';
$form['entity_bundle:node']['negate']['#value'] = $form['entity_bundle:node']['negate']['#default_value'];
}
if (isset($form['user_role'])) {
$form['user_role']['#title'] = $this->t('Roles');
unset($form['user_role']['roles']['#description']);

View File

@ -0,0 +1,46 @@
<?php
namespace Drupal\Tests\block\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests the upgrade path for updating the node type visibility condition.
*
* @group Update
*/
class BlockNodeTypeVisibilityUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.0.0.filled.standard.php.gz',
];
}
/**
* Tests that block context mapping is updated properly.
*
* @group legacy
*/
public function testBlock() {
$bundles = [
'article' => 'article',
'test_content_type' => 'test_content_type',
];
$block = \Drupal::config('block.block.stark_testblock');
$this->assertEquals($bundles, $block->get('visibility.node_type.bundles'));
$this->assertNull($block->get('visibility.entity_bundle:node'));
$this->runUpdates();
$block = \Drupal::config('block.block.stark_testblock');
$this->assertEquals($bundles, $block->get('visibility.entity_bundle:node.bundles'));
$this->assertEquals('entity_bundle:node', $block->get('visibility.entity_bundle:node.id'));
$this->assertNull($block->get('visibility.node_type'));
}
}

View File

@ -91,6 +91,7 @@ block.settings.node_syndicate_block:
label: 'Block count'
condition.plugin.node_type:
deprecated: "The 'condition.plugin.node_type' config schema is deprecated in drupal:9.3.0 and is removed from drupal 10.0.0. Use the 'entity_bundle:node_type' key instead to define a node type condition. See https://www.drupal.org/node/2983299."
type: condition.plugin
mapping:
bundles:

View File

@ -13,11 +13,16 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*
* @Condition(
* id = "node_type",
* label = @Translation("Node Bundle"),
* label = @Translation("Node Bundle (Deprecated)"),
* context_definitions = {
* "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
* }
* )
*
* @deprecated in drupal:9.3.0 and is removed from drupal:10.0.0.
* Use \Drupal\Core\Entity\Plugin\Condition\EntityBundle instead.
*
* @see https://www.drupal.org/node/2983299
*/
class NodeType extends ConditionPluginBase implements ContainerFactoryPluginInterface {
@ -45,6 +50,7 @@ class NodeType extends ConditionPluginBase implements ContainerFactoryPluginInte
*/
public function __construct(EntityStorageInterface $entity_storage, array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
@trigger_error('\Drupal\node\Plugin\Condition\NodeType is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Entity\Plugin\Condition\EntityBundle instead. See https://www.drupal.org/node/2983299', E_USER_DEPRECATED);
$this->entityStorage = $entity_storage;
}

View File

@ -141,18 +141,20 @@ class NodeBlockFunctionalTest extends NodeTestBase {
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.site', 'user']);
// Enable the "Powered by Drupal" block only on article nodes.
$theme = \Drupal::service('theme_handler')->getDefault();
$this->drupalGet("admin/structure/block/add/system_powered_by_block/{$theme}");
$this->assertSession()->pageTextContains('Content type');
$this->assertSession()->pageTextNotContains('Content types (Deprecated)');
$edit = [
'id' => strtolower($this->randomMachineName()),
'region' => 'sidebar_first',
'visibility[node_type][bundles][article]' => 'article',
'visibility[entity_bundle:node][bundles][article]' => 'article',
];
$theme = \Drupal::service('theme_handler')->getDefault();
$this->drupalGet("admin/structure/block/add/system_powered_by_block/{$theme}");
$this->submitForm($edit, 'Save block');
$block = Block::load($edit['id']);
$visibility = $block->getVisibility();
$this->assertTrue(isset($visibility['node_type']['bundles']['article']), 'Visibility settings were saved to configuration');
$this->assertTrue(isset($visibility['entity_bundle:node']['bundles']['article']), 'Visibility settings were saved to configuration');
// Create a page node.
$node5 = $this->drupalCreateNode(['uid' => $this->adminUser->id(), 'type' => 'page']);
@ -232,4 +234,33 @@ class NodeBlockFunctionalTest extends NodeTestBase {
$this->assertSession()->linkByHrefExists($block->toUrl()->toString());
}
/**
* Tests customization of deprecated node type condition.
*
* @group legacy
*/
public function testDeprecatedNodeTypeCondition() {
$this->expectDeprecation('\Drupal\node\Plugin\Condition\NodeType is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Entity\Plugin\Condition\EntityBundle instead. See https://www.drupal.org/node/2983299');
$this->expectDeprecation("The 'condition.plugin.node_type' config schema is deprecated in drupal:9.3.0 and is removed from drupal 10.0.0. Use the 'entity_bundle:node_type' key instead to define a node type condition. See https://www.drupal.org/node/2983299.");
$this->drupalLogin($this->adminUser);
$this->drupalPlaceBlock('system_powered_by_block', [
'id' => 'powered_by_deprecated',
'visibility' => [
'node_type' => [
'bundles' => [
'article' => 'article',
],
],
],
'context_mapping' => ['node' => '@node.node_route_context:node'],
]);
// On an existing block with the deprecated plugin, the deprecated
// label is shown.
$this->drupalGet("admin/structure/block/manage/powered_by_deprecated");
$this->assertSession()->pageTextContains('Content type');
$this->assertSession()->pageTextContains('Content types (Deprecated)');
$this->submitForm([], 'Save');
}
}

View File

@ -10,6 +10,7 @@ use Drupal\node\Entity\NodeType;
* Tests that conditions, provided by the node module, are working properly.
*
* @group node
* @group legacy
*/
class NodeConditionTest extends EntityKernelTestBase {
@ -31,6 +32,7 @@ class NodeConditionTest extends EntityKernelTestBase {
* Tests conditions.
*/
public function testConditions() {
$this->expectDeprecation('\Drupal\node\Plugin\Condition\NodeType is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Entity\Plugin\Condition\EntityBundle instead. See https://www.drupal.org/node/2983299');
$manager = $this->container->get('plugin.manager.condition');
$this->createUser();

View File

@ -31,7 +31,7 @@ class FormController implements FormInterface {
*/
public function __construct() {
$manager = new ConditionManager(\Drupal::service('container.namespaces'), \Drupal::cache('discovery'), \Drupal::moduleHandler());
$this->condition = $manager->createInstance('node_type');
$this->condition = $manager->createInstance('entity_bundle:node');
}
/**

View File

@ -180,8 +180,8 @@ class UpdatePathTestBaseFilledTest extends UpdatePathTestBaseTest {
$this->drupalGet('admin/structure/block/manage/testblock');
$this->assertSession()->checkboxNotChecked('edit-visibility-language-langcodes-es');
$this->assertSession()->checkboxChecked('edit-visibility-language-langcodes-en');
$this->assertSession()->checkboxNotChecked('edit-visibility-node-type-bundles-book');
$this->assertSession()->checkboxChecked('edit-visibility-node-type-bundles-test-content-type');
$this->assertSession()->checkboxNotChecked('edit-visibility-entity-bundlenode-bundles-book');
$this->assertSession()->checkboxChecked('edit-visibility-entity-bundlenode-bundles-test-content-type');
// Make sure our block is still translated.
$this->drupalGet('admin/structure/block/manage/testblock/translate/es/edit');

View File

@ -22,8 +22,8 @@ settings:
views_label: ''
items_per_page: none
visibility:
node_type:
id: node_type
entity_bundle:node:
id: entity_bundle:node
bundles:
article: article
negate: false

View File

@ -0,0 +1,84 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\entity_test\Entity\EntityTestWithBundle;
/**
* Tests that entity bundle conditions works properly.
*
* @group Entity
*/
class EntityBundleConditionTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test_with_bundle');
// Create the entity bundles required for testing.
$bundle = EntityTestBundle::create(['id' => 'page', 'label' => 'page']);
$bundle->save();
$bundle = EntityTestBundle::create(['id' => 'article', 'label' => 'article']);
$bundle->save();
$bundle = EntityTestBundle::create(['id' => 'test', 'label' => 'test']);
$bundle->save();
}
/**
* Tests conditions.
*/
public function testConditions() {
$this->createUser();
// Get some entities of various bundles to check against.
$page = EntityTestWithBundle::create(['type' => 'page', 'name' => $this->randomMachineName()]);
$page->save();
$article = EntityTestWithBundle::create(['type' => 'article', 'name' => $this->randomMachineName()]);
$article->save();
$test = EntityTestWithBundle::create(['type' => 'test', 'name' => $this->randomMachineName()]);
$test->save();
// Grab the bundle condition and configure it to check against bundle of
// 'article' and set the context to the page type entity.
/** @var \Drupal\Core\Entity\Plugin\Condition\EntityBundle $condition */
$condition = $this->container->get('plugin.manager.condition')->createInstance('entity_bundle:entity_test_with_bundle')
->setConfig('bundles', ['article' => 'article'])
->setContextValue('entity_test_with_bundle', $page);
$this->assertFalse($condition->execute(), 'Page type entities fail bundle checks for articles.');
// Check for the proper summary.
$this->assertEquals('Test entity bundle is article', $condition->summary());
$this->assertEquals('entity_test', $condition->getPluginDefinition()['provider']);
// Set the bundle check to page.
$condition->setConfig('bundles', ['page' => 'page']);
$this->assertTrue($condition->execute(), 'Page type entities pass bundle checks for pages');
// Check for the proper summary.
$this->assertEquals('Test entity bundle is page', $condition->summary());
// Set the bundle check to page or article.
$condition->setConfig('bundles', ['page' => 'page', 'article' => 'article']);
$this->assertTrue($condition->execute(), 'Page type entities pass bundle checks for pages or articles');
// Check for the proper summary.
$this->assertEquals('Test entity bundle is page or article', $condition->summary());
// Set the context to the article entity.
$condition->setContextValue('entity_test_with_bundle', $article);
$this->assertTrue($condition->execute(), 'Article type entities pass bundle checks for pages or articles');
// Set the context to the test entity.
$condition->setContextValue('entity_test_with_bundle', $test);
$this->assertFalse($condition->execute(), 'Test type entities pass bundle checks for pages or articles');
// Check a greater than 2 bundles summary scenario.
$condition->setConfig('bundles', [
'page' => 'page',
'article' => 'article',
'test' => 'test',
]);
$this->assertEquals('Test entity bundle is page, article or test', $condition->summary());
}
}