diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 95311ada2b..aa6f982008 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -333,6 +333,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' diff --git a/core/lib/Drupal/Core/Entity/Plugin/Condition/Deriver/EntityBundle.php b/core/lib/Drupal/Core/Entity/Plugin/Condition/Deriver/EntityBundle.php new file mode 100644 index 0000000000..bde85e1805 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Plugin/Condition/Deriver/EntityBundle.php @@ -0,0 +1,90 @@ +entityTypeManager = $entity_type_manager; + $this->stringTranslation = $string_translation; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, $base_plugin_id) { + return new static( + $container->get('entity_type.manager'), + $container->get('string_translation') + ); + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($base_plugin_definition) { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + // @todo Stop excluding node_type once https://www.drupal.org/node/2914188 + // is in. + if ($entity_type->hasKey('bundle') && $entity_type_id != 'node') { + $this->derivatives[$entity_type_id] = $base_plugin_definition; + $this->derivatives[$entity_type_id]['label'] = $this->getEntityBundleLabel($entity_type); + $this->derivatives[$entity_type_id]['context'] = [ + $entity_type_id => new ContextDefinition('entity:' . $entity_type_id), + ]; + } + } + return $this->derivatives; + } + + /** + * Provides the bundle label with a fallback when not defined. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type we are looking the bundle label for. + * + * @return string|\Drupal\Core\StringTranslation\TranslatableMarkup + * The entity bundle label or a fallback label. + */ + protected function getEntityBundleLabel(EntityTypeInterface $entity_type) { + if ($label = $entity_type->getBundleLabel()) { + return $label; + } + + if ($bundle_entity_type = $entity_type->getBundleEntityType()) { + return $this->entityTypeManager->getDefinition($bundle_entity_type)->getLabel(); + } + + return $this->t('@label bundle', ['@label' => $entity_type->getLabel()]); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Plugin/Condition/EntityBundle.php b/core/lib/Drupal/Core/Entity/Plugin/Condition/EntityBundle.php new file mode 100644 index 0000000000..a485f7eeee --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Plugin/Condition/EntityBundle.php @@ -0,0 +1,173 @@ +entityTypeManager = $entity_type_manager; + $this->entityTypeBundleInfo = $entity_type_bundle_info; + $this->entityType = $entity_type_manager->getDefinition($this->getDerivativeId()); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('entity_type.manager'), + $container->get('entity_type.bundle.info'), + $configuration, + $plugin_id, + $plugin_definition + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $bundles = $this->entityTypeBundleInfo->getBundleInfo($this->entityType->id()); + $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() { + if (empty($this->configuration['bundles']) && !$this->isNegated()) { + return TRUE; + } + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->getContextValue($this->entityType->id()); + return !empty($this->configuration['bundles'][$entity->bundle()]); + } + + /** + * {@inheritdoc} + */ + public function summary() { + if (!$bundle_type = $this->entityType->getBundleLabel()) { + if ($bundle_entity_type_id = $this->entityType->getBundleEntityType()) { + $bundle_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id) + ->getLabel(); + } + else { + $bundle_type = $this->t('@label bundle', ['@label' => $this->entityType->getLabel()]); + } + } + 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' => $bundle_type, + '@bundles' => $bundles, + '@last' => $last, + ]); + } + else { + return $this->t('@bundle_type is not @bundles or @last', [ + '@bundle_type' => $bundle_type, + '@bundles' => $bundles, + '@last' => $last, + ]); + } + + } + $bundle = reset($this->configuration['bundles']); + + if (empty($this->configuration['negate'])) { + return $this->t('@bundle_type is @bundle', [ + '@bundle_type' => $bundle_type, + '@bundle' => $bundle, + ]); + } + else { + return $this->t('@bundle_type is not @bundle', [ + '@bundle_type' => $bundle_type, + '@bundle' => $bundle, + ]); + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'bundles' => [], + ] + parent::defaultConfiguration(); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityBundleConditionTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityBundleConditionTest.php new file mode 100644 index 0000000000..8da2c743a0 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityBundleConditionTest.php @@ -0,0 +1,89 @@ +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() { + $manager = $this->container->get('plugin.manager.condition'); + $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 = $manager->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($condition->summary(), 'Test entity bundle is article'); + + // 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($condition->summary(), 'Test entity bundle is page'); + + // 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($condition->summary(), 'Test entity bundle is page or article'); + + // 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($condition->summary(), 'Test entity bundle is page, article or test'); + + // Test Constructor injection. + $condition = $manager->createInstance('entity_bundle:entity_test_with_bundle', ['bundles' => ['article' => 'article'], 'context' => ['entity_test_with_bundle' => $article]]); + $this->assertTrue($condition->execute(), 'Constructor injection of context and configuration working as anticipated.'); + } + +}