From 92cef7f8f8b5afc7e08f7ff83f6b91659ec38be5 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Tue, 21 Sep 2021 16:56:53 -0400 Subject: [PATCH] Validate that only one birth log can reference an asset. --- modules/log/birth/farm_birth.module | 13 ++++ .../Constraint/UniqueBirthLogConstraint.php | 24 ++++++ .../UniqueBirthLogConstraintValidator.php | 73 +++++++++++++++++++ .../log/birth/tests/src/Kernel/BirthTest.php | 52 +++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 modules/log/birth/src/Plugin/Validation/Constraint/UniqueBirthLogConstraint.php create mode 100644 modules/log/birth/src/Plugin/Validation/Constraint/UniqueBirthLogConstraintValidator.php diff --git a/modules/log/birth/farm_birth.module b/modules/log/birth/farm_birth.module index 125ebdf9..c9c539be 100644 --- a/modules/log/birth/farm_birth.module +++ b/modules/log/birth/farm_birth.module @@ -7,6 +7,7 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormStateInterface; /** @@ -38,3 +39,15 @@ function farm_birth_log_view_alter(array &$build, EntityInterface $entity, Entit $build['asset']['#title'] = t('Children'); } } + +/** + * Implements hook_entity_base_field_info_alter(). + */ +function farm_birth_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) { + /** @var \Drupal\field\Entity\FieldConfig[] $fields */ + + // Validate that only one birth log references an asset. + if ($entity_type->id() == 'log' && !empty($fields['asset'])) { + $fields['asset']->addConstraint('UniqueBirthLog'); + } +} diff --git a/modules/log/birth/src/Plugin/Validation/Constraint/UniqueBirthLogConstraint.php b/modules/log/birth/src/Plugin/Validation/Constraint/UniqueBirthLogConstraint.php new file mode 100644 index 00000000..0561f32d --- /dev/null +++ b/modules/log/birth/src/Plugin/Validation/Constraint/UniqueBirthLogConstraint.php @@ -0,0 +1,24 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) { + /** @var \Drupal\Core\Field\FieldItemListInterface[] $value */ + /** @var \Drupal\farm_birth\Plugin\Validation\Constraint\UniqueBirthLogConstraint $constraint */ + foreach ($value as $item) { + + // Get the referenced asset ID. + $item_value = $item->getValue(); + $asset_id = $item_value['target_id'] ?? FALSE; + + // If there is no asset, skip. + if (empty($asset_id)) { + continue; + } + + // Perform an entity field query to find logs that reference the asset. + $query = $this->entityTypeManager->getStorage('log')->getQuery(); + $ids = $query->condition('type', 'birth') + ->condition('asset', $asset_id) + ->execute(); + + // If more than 1 birth logs reference the asset, add a violation. + if (count($ids) > 1) { + $asset = $this->entityTypeManager->getStorage('asset')->load($asset_id); + $this->context->addViolation($constraint->message, ['%child' => $asset->label()]); + } + } + } + +} diff --git a/modules/log/birth/tests/src/Kernel/BirthTest.php b/modules/log/birth/tests/src/Kernel/BirthTest.php index fa092423..9a8fe47b 100644 --- a/modules/log/birth/tests/src/Kernel/BirthTest.php +++ b/modules/log/birth/tests/src/Kernel/BirthTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\farm_birth\Kernel; use Drupal\asset\Entity\Asset; +use Drupal\Component\Render\FormattableMarkup; use Drupal\KernelTests\KernelTestBase; use Drupal\log\Entity\Log; use Drupal\taxonomy\Entity\Term; @@ -36,6 +37,7 @@ class BirthTest extends KernelTestBase { 'user', 'taxonomy', 'text', + 'views', ]; /** @@ -130,4 +132,54 @@ class BirthTest extends KernelTestBase { $this->assertEquals($timestamp, $child2->get('birthdate')->value); } + /** + * Test that only one birth log can reference an asset. + */ + public function testUniqueBirthLogConstraint() { + + // Create a Cow animal type term. + /** @var \Drupal\taxonomy\TermInterface $cow */ + $cow = Term::create([ + 'name' => 'Cow', + 'vid' => 'animal_type', + ]); + $cow->save(); + + // Create an asset. + /** @var \Drupal\asset\Entity\AssetInterface $animal */ + $asset = Asset::create([ + 'name' => $this->randomMachineName(), + 'type' => 'animal', + 'animal_type' => ['tid' => $cow->id()], + 'status' => 'active', + ]); + $asset->save(); + + // Create a birth log that references the asset. + $log1 = Log::create([ + 'type' => 'birth', + 'timestamp' => \Drupal::time()->getRequestTime(), + 'asset' => [['target_id' => $asset->id()]], + ]); + $log1->save(); + + // Confirm that there were no validation errors. + $errors = $log1->validate(); + $this->assertCount(0, $errors); + + // Create a second birth log that references the asset. + $log2 = Log::create([ + 'type' => 'birth', + 'timestamp' => \Drupal::time()->getRequestTime(), + 'asset' => [['target_id' => $asset->id()]], + ]); + $log2->save(); + + // Confirm that validation fails. + $errors = $log2->validate(); + $this->assertCount(1, $errors); + $this->assertEquals(new FormattableMarkup('%child already has a birth log. More than one birth log cannot reference the same child.', ['%child' => $asset->label()]), $errors[0]->getMessage()); + $this->assertEquals('asset', $errors[0]->getPropertyPath()); + } + }