diff --git a/commerce.services.yml b/commerce.services.yml index bfec87a9bd3d2efe4ac99857463edb944807c615..65c2fbb02bf53c3f2fe6ba7436b513159cc85828 100644 --- a/commerce.services.yml +++ b/commerce.services.yml @@ -46,6 +46,10 @@ services: class: Drupal\commerce\Config\ConfigUpdater arguments: ['@entity_type.manager', '@config.storage', '@config.factory'] + commerce.entity_uuid_mapper: + class: Drupal\commerce\EntityUuidMapper + arguments: ['@database', '@entity_type.manager'] + commerce.twig_extension: class: Drupal\commerce\TwigExtension\CommerceTwigExtension tags: diff --git a/modules/product/src/Plugin/Commerce/Condition/OrderItemProductCategory.php b/modules/product/src/Plugin/Commerce/Condition/OrderItemProductCategory.php index 400e0731b414a2b37183a583bb54a72231306bfd..7a4dcaa025a7081bcd598e093e2d7a8177ea43e1 100644 --- a/modules/product/src/Plugin/Commerce/Condition/OrderItemProductCategory.php +++ b/modules/product/src/Plugin/Commerce/Condition/OrderItemProductCategory.php @@ -2,6 +2,7 @@ namespace Drupal\commerce_product\Plugin\Commerce\Condition; +use Drupal\commerce\EntityUuidMapperInterface; use Drupal\commerce\Plugin\Commerce\Condition\ConditionBase; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityInterface; @@ -38,12 +39,15 @@ class OrderItemProductCategory extends ConditionBase implements ContainerFactory * The entity field manager. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. + * @param \Drupal\commerce\EntityUuidMapperInterface $entity_uuid_mapper + * The entity UUID mapper. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, EntityUuidMapperInterface $entity_uuid_mapper) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityFieldManager = $entity_field_manager; $this->entityTypeManager = $entity_type_manager; + $this->entityUuidMapper = $entity_uuid_mapper; } /** @@ -55,7 +59,8 @@ class OrderItemProductCategory extends ConditionBase implements ContainerFactory $plugin_id, $plugin_definition, $container->get('entity_field.manager'), - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('commerce.entity_uuid_mapper') ); } @@ -71,9 +76,10 @@ class OrderItemProductCategory extends ConditionBase implements ContainerFactory if (!$purchased_entity || $purchased_entity->getEntityTypeId() != 'commerce_product_variation') { return FALSE; } + $term_ids = $this->getTermIds(); $referenced_ids = $this->getReferencedIds($purchased_entity->getProduct()); - return (bool) array_intersect($this->configuration['terms'], $referenced_ids); + return (bool) array_intersect($term_ids, $referenced_ids); } } diff --git a/modules/product/src/Plugin/Commerce/Condition/OrderProductCategory.php b/modules/product/src/Plugin/Commerce/Condition/OrderProductCategory.php index d5c513678a344f8c26c8166475979f712a61a710..05d03ce5164c5b1b3bda52e08417fe8d941e15c4 100644 --- a/modules/product/src/Plugin/Commerce/Condition/OrderProductCategory.php +++ b/modules/product/src/Plugin/Commerce/Condition/OrderProductCategory.php @@ -2,6 +2,7 @@ namespace Drupal\commerce_product\Plugin\Commerce\Condition; +use Drupal\commerce\EntityUuidMapperInterface; use Drupal\commerce\Plugin\Commerce\Condition\ConditionBase; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityInterface; @@ -38,12 +39,15 @@ class OrderProductCategory extends ConditionBase implements ContainerFactoryPlug * The entity field manager. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. + * @param \Drupal\commerce\EntityUuidMapperInterface $entity_uuid_mapper + * The entity UUID mapper. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, EntityUuidMapperInterface $entity_uuid_mapper) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityFieldManager = $entity_field_manager; $this->entityTypeManager = $entity_type_manager; + $this->entityUuidMapper = $entity_uuid_mapper; } /** @@ -55,7 +59,8 @@ class OrderProductCategory extends ConditionBase implements ContainerFactoryPlug $plugin_id, $plugin_definition, $container->get('entity_field.manager'), - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('commerce.entity_uuid_mapper') ); } @@ -66,6 +71,7 @@ class OrderProductCategory extends ConditionBase implements ContainerFactoryPlug $this->assertEntity($entity); /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ $order = $entity; + $term_ids = $this->getTermIds(); foreach ($order->getItems() as $order_item) { /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $purchased_entity */ $purchased_entity = $order_item->getPurchasedEntity(); @@ -73,7 +79,7 @@ class OrderProductCategory extends ConditionBase implements ContainerFactoryPlug continue; } $referenced_ids = $this->getReferencedIds($purchased_entity->getProduct()); - if (array_intersect($this->configuration['terms'], $referenced_ids)) { + if (array_intersect($term_ids, $referenced_ids)) { return TRUE; } } diff --git a/modules/product/src/Plugin/Commerce/Condition/ProductCategoryTrait.php b/modules/product/src/Plugin/Commerce/Condition/ProductCategoryTrait.php index 38e5b0bfdeb99b4c13c915128b68dc1100b60ff4..772b8ef7e837420eb5d43dd3d290a257eac54546 100644 --- a/modules/product/src/Plugin/Commerce/Condition/ProductCategoryTrait.php +++ b/modules/product/src/Plugin/Commerce/Condition/ProductCategoryTrait.php @@ -24,6 +24,13 @@ trait ProductCategoryTrait { */ protected $entityTypeManager; + /** + * The entity UUID mapper. + * + * @var \Drupal\commerce\EntityUuidMapperInterface + */ + protected $entityUuidMapper; + /** * {@inheritdoc} */ @@ -40,7 +47,7 @@ trait ProductCategoryTrait { $form = parent::buildConfigurationForm($form, $form_state); $terms = NULL; - $ids = $this->configuration['terms']; + $ids = $this->getTermIds(); if (!empty($ids)) { $term_storage = $this->entityTypeManager->getStorage('taxonomy_term'); $terms = $term_storage->loadMultiple($ids); @@ -67,11 +74,21 @@ trait ProductCategoryTrait { public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { parent::submitConfigurationForm($form, $form_state); + // Convert selected IDs into UUIDs, and store them. $values = $form_state->getValue($form['#parents']); - $this->configuration['terms'] = []; - foreach ($values['terms'] as $value) { - $this->configuration['terms'][] = $value['target_id']; - } + $term_ids = array_column($values['terms'], 'target_id'); + $this->configuration['terms'] = $this->entityUuidMapper->mapFromIds('taxonomy_term', $term_ids); + $this->configuration['terms'] = array_values($this->configuration['terms']); + } + + /** + * Gets the configured term IDs. + * + * @return array + * The term IDs. + */ + protected function getTermIds() { + return $this->entityUuidMapper->mapToIds('taxonomy_term', $this->configuration['terms']); } /** diff --git a/modules/product/tests/src/Unit/Plugin/Commerce/Condition/OrderItemProductCategoryTest.php b/modules/product/tests/src/Unit/Plugin/Commerce/Condition/OrderItemProductCategoryTest.php index 38d3afe7360107fe2bb3249c9a29fb48853924dd..bdb65cc6829e7e5302e0e349763e22e5f20a1a9c 100644 --- a/modules/product/tests/src/Unit/Plugin/Commerce/Condition/OrderItemProductCategoryTest.php +++ b/modules/product/tests/src/Unit/Plugin/Commerce/Condition/OrderItemProductCategoryTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\commerce_product\Unit\Plugin\Commerce\Condition; +use Drupal\commerce\EntityUuidMapperInterface; use Drupal\commerce_order\Entity\OrderItemInterface; use Drupal\commerce_product\Entity\ProductInterface; use Drupal\commerce_product\Entity\ProductVariationInterface; @@ -33,9 +34,20 @@ class OrderItemProductCategoryTest extends UnitTestCase { $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); $entity_type_manager = $entity_type_manager->reveal(); + + $uuid_map = [ + '2' => '62e428e1-88a6-478c-a8c6-a554ca2332ae', + '3' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651', + '4' => 'a019d89b-c4d9-4ed4-b859-894e4e2e93cf', + ]; + $entity_uuid_mapper = $this->prophesize(EntityUuidMapperInterface::class); + $entity_uuid_mapper->mapToIds('taxonomy_term', [$uuid_map['3']])->willReturn(['3']); + $entity_uuid_mapper->mapToIds('taxonomy_term', [$uuid_map['4']])->willReturn(['4']); + $entity_uuid_mapper = $entity_uuid_mapper->reveal(); + $configuration = []; - $configuration['terms'] = [3]; - $condition = new OrderItemProductCategory($configuration, 'order_item_product_category', ['entity_type' => 'commerce_order_item'], $entity_field_manager, $entity_type_manager); + $configuration['terms'] = ['30df59bd-7b03-4cf7-bb35-d42fc49f0651']; + $condition = new OrderItemProductCategory($configuration, 'order_item_product_category', ['entity_type' => 'commerce_order_item'], $entity_field_manager, $entity_type_manager, $entity_uuid_mapper); // Order item with no purchasable entity. $order_item = $this->prophesize(OrderItemInterface::class); @@ -70,7 +82,7 @@ class OrderItemProductCategoryTest extends UnitTestCase { $this->assertFalse($condition->evaluate($order_item)); // Matching product category. - $configuration['terms'] = ['4']; + $configuration['terms'] = ['a019d89b-c4d9-4ed4-b859-894e4e2e93cf']; $condition->setConfiguration($configuration); $this->assertTrue($condition->evaluate($order_item)); } diff --git a/modules/product/tests/src/Unit/Plugin/Commerce/Condition/OrderProductCategoryTest.php b/modules/product/tests/src/Unit/Plugin/Commerce/Condition/OrderProductCategoryTest.php index 6c932e340da7d001ba6e5ad626afa86be1ef351d..865a8c5ac40136c6346d11218fbd8a6cfe25274b 100644 --- a/modules/product/tests/src/Unit/Plugin/Commerce/Condition/OrderProductCategoryTest.php +++ b/modules/product/tests/src/Unit/Plugin/Commerce/Condition/OrderProductCategoryTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\commerce_product\Unit\Plugin\Commerce\Condition; +use Drupal\commerce\EntityUuidMapperInterface; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_order\Entity\OrderItemInterface; use Drupal\commerce_product\Entity\ProductInterface; @@ -34,9 +35,20 @@ class OrderProductCategoryTest extends UnitTestCase { $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); $entity_type_manager = $entity_type_manager->reveal(); + + $uuid_map = [ + '2' => '62e428e1-88a6-478c-a8c6-a554ca2332ae', + '3' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651', + '4' => 'a019d89b-c4d9-4ed4-b859-894e4e2e93cf', + ]; + $entity_uuid_mapper = $this->prophesize(EntityUuidMapperInterface::class); + $entity_uuid_mapper->mapToIds('taxonomy_term', [$uuid_map['3']])->willReturn(['3']); + $entity_uuid_mapper->mapToIds('taxonomy_term', [$uuid_map['4']])->willReturn(['4']); + $entity_uuid_mapper = $entity_uuid_mapper->reveal(); + $configuration = []; - $configuration['terms'] = [3]; - $condition = new OrderProductCategory($configuration, 'order_product_category', ['entity_type' => 'commerce_order'], $entity_field_manager, $entity_type_manager); + $configuration['terms'] = ['30df59bd-7b03-4cf7-bb35-d42fc49f0651']; + $condition = new OrderProductCategory($configuration, 'order_product_category', ['entity_type' => 'commerce_order'], $entity_field_manager, $entity_type_manager, $entity_uuid_mapper); // Order item with no purchasable entity. $first_order_item = $this->prophesize(OrderItemInterface::class); @@ -77,7 +89,7 @@ class OrderProductCategoryTest extends UnitTestCase { $order = $this->buildOrder([$first_order_item, $second_order_item]); $this->assertFalse($condition->evaluate($order)); - $configuration['terms'] = [4]; + $configuration['terms'] = ['a019d89b-c4d9-4ed4-b859-894e4e2e93cf']; $condition->setConfiguration($configuration); $order = $this->buildOrder([$first_order_item, $second_order_item]); diff --git a/src/EntityUuidMapper.php b/src/EntityUuidMapper.php new file mode 100644 index 0000000000000000000000000000000000000000..1a50fbcb7c6a2e1c265c4abb49485042607872df --- /dev/null +++ b/src/EntityUuidMapper.php @@ -0,0 +1,117 @@ +<?php + +namespace Drupal\commerce; + +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityTypeManagerInterface; + +class EntityUuidMapper implements EntityUuidMapperInterface { + + /** + * The database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected $database; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * UUID to ID map, grouped by entity type ID. + * + * @var array + */ + protected $map = []; + + /** + * Constructs a new EntityUuidMapper object. + * + * @param \Drupal\Core\Database\Connection $database + * The database connection. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + */ + public function __construct(Connection $database, EntityTypeManagerInterface $entity_type_manager) { + $this->database = $database; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public function mapToIds($entity_type_id, array $uuids) { + // Fetch known UUIDs from the static cache. + $ids = []; + foreach ($uuids as $index => $uuid) { + if (isset($this->map[$entity_type_id][$uuid])) { + $ids[$uuid] = $this->map[$entity_type_id][$uuid]; + unset($uuids[$index]); + } + } + + // Map the remaining UUIDs from the database. + if (!empty($uuids)) { + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); + $id_key = $entity_type->getKey('id'); + $uuid_key = $entity_type->getKey('uuid'); + // Query the storage directly to avoid the performance impact of loading + // the full entities. + $loaded_uuids = $this->database->select($entity_type->getBaseTable(), 't') + ->fields('t', [$uuid_key, $id_key]) + ->condition($uuid_key, $uuids, 'IN') + ->execute() + ->fetchAllKeyed(1, 0); + + foreach ($loaded_uuids as $id => $uuid) { + $ids[$uuid] = $id; + $this->map[$entity_type_id][$uuid] = $id; + } + } + + return $ids; + } + + /** + * {@inheritdoc} + */ + public function mapFromIds($entity_type_id, array $ids) { + // Fetch known IDs from the static cache. + $uuids = []; + foreach ($ids as $index => $id) { + if (isset($this->map[$entity_type_id])) { + $uuid = array_search($id, $this->map[$entity_type_id]); + if ($uuid) { + $uuids[$id] = $uuid; + unset($ids[$index]); + } + } + } + + // Map the remaining IDs from the database. + if (!empty($ids)) { + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); + $id_key = $entity_type->getKey('id'); + $uuid_key = $entity_type->getKey('uuid'); + // Query the storage directly to avoid the performance impact of loading + // the full entities. + $loaded_ids = $this->database->select($entity_type->getBaseTable(), 't') + ->fields('t', [$uuid_key, $id_key]) + ->condition($id_key, $ids, 'IN') + ->execute() + ->fetchAllKeyed(0, 1); + + foreach ($loaded_ids as $uuid => $id) { + $uuids[$id] = $uuid; + $this->map[$entity_type_id][$uuid] = $id; + } + } + + return $uuids; + } + +} diff --git a/src/EntityUuidMapperInterface.php b/src/EntityUuidMapperInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..bbd3b82f57f2d5eddc76b5c682e760550a1fe7ff --- /dev/null +++ b/src/EntityUuidMapperInterface.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\commerce; + +/** + * Maps entity UUIDs to entity IDs, and vice-versa. + */ +interface EntityUuidMapperInterface { + + /** + * Maps the given entity UUIDs to entity IDs. + * + * @param string $entity_type_id + * The entity type ID. + * @param array $uuids + * THe entity UUIDs. + * + * @return array + * The entity IDs. + */ + public function mapToIds($entity_type_id, array $uuids); + + /** + * Maps the given entity IDs to entity UUIDs. + * + * @param string $entity_type_id + * The entity type ID. + * @param array $ids + * THe entity IDs. + * + * @return array + * The entity UUIDs. + */ + public function mapFromIds($entity_type_id, array $ids); + +} diff --git a/tests/src/Kernel/EntityUuidMapperTest.php b/tests/src/Kernel/EntityUuidMapperTest.php new file mode 100644 index 0000000000000000000000000000000000000000..54e35db9997c374a0f120c918f39d0c8f5806064 --- /dev/null +++ b/tests/src/Kernel/EntityUuidMapperTest.php @@ -0,0 +1,49 @@ +<?php + +namespace Drupal\Tests\commerce\Kernel; + +/** + * Tests the EntityUuidMapper class. + * + * @coversDefaultClass \Drupal\commerce\EntityUuidMapper + * @group commerce + */ +class EntityUuidMapperTest extends CommerceKernelTestBase { + + /** + * The entity UUID mapper. + * + * @var \Drupal\commerce\EntityUuidMapperInterface + */ + protected $entityUuidMapper; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->entityUuidMapper = \Drupal::service('commerce.entity_uuid_mapper'); + } + + /** + * Tests the mapper. + * + * @covers ::mapToIds + * @covers ::mapFromIds + */ + public function testMapper() { + $another_store = $this->createStore('Second store', 'second@example.com'); + $map = [ + $this->store->id() => $this->store->uuid(), + $another_store->id() => $another_store->uuid(), + ]; + + $uuid_map = $this->entityUuidMapper->mapToIds('commerce_store', array_values($map)); + $this->assertEquals($uuid_map, array_flip($map)); + + $id_map = $this->entityUuidMapper->mapFromIds('commerce_store', array_keys($map)); + $this->assertEquals($id_map, $map); + } + +}