diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php index 9c389235ab..e6538a4706 100644 --- a/core/modules/media/src/Entity/Media.php +++ b/core/modules/media/src/Entity/Media.php @@ -10,6 +10,8 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\media\MediaInterface; use Drupal\Core\Entity\EntityChangedTrait; +use Drupal\media\MediaSourceEntityConstraintsInterface; +use Drupal\media\MediaSourceFieldConstraintsInterface; use Drupal\user\UserInterface; /** @@ -255,6 +257,11 @@ protected function shouldUpdateThumbnail($is_new = FALSE) { * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage) { + // If the source plugin defines any constraints we enforce the validation. + if ($this->getSource() instanceof MediaSourceEntityConstraintsInterface || $this->getSource() instanceof MediaSourceFieldConstraintsInterface) { + $this->setValidationRequired(TRUE); + } + parent::preSave($storage); // Try to set fields provided by the media source and mapped in diff --git a/core/modules/media/src/MediaSourceBase.php b/core/modules/media/src/MediaSourceBase.php index 2f117b066a..aa911c69b6 100644 --- a/core/modules/media/src/MediaSourceBase.php +++ b/core/modules/media/src/MediaSourceBase.php @@ -239,6 +239,7 @@ protected function createSourceFieldStorage() { 'entity_type' => 'media', 'field_name' => $this->getSourceFieldName(), 'type' => reset($this->pluginDefinition['allowed_field_types']), + 'locked' => TRUE, ]); } diff --git a/core/modules/media/src/MediaSourceEntityConstraintsInterface.php b/core/modules/media/src/MediaSourceEntityConstraintsInterface.php new file mode 100644 index 0000000000..b62890fda7 --- /dev/null +++ b/core/modules/media/src/MediaSourceEntityConstraintsInterface.php @@ -0,0 +1,29 @@ +label(); + } + elseif ($value instanceof FieldItemListInterface) { + $string_to_test = $value->value; + } + else { + return; + } + + if (strpos($string_to_test, 'love Drupal') === FALSE) { + $this->context->addViolation($constraint->message); + } + } + +} diff --git a/core/modules/media/tests/modules/media_test_source/src/Plugin/media/Source/TestWithConstraints.php b/core/modules/media/tests/modules/media_test_source/src/Plugin/media/Source/TestWithConstraints.php new file mode 100644 index 0000000000..98039ca2d2 --- /dev/null +++ b/core/modules/media/tests/modules/media_test_source/src/Plugin/media/Source/TestWithConstraints.php @@ -0,0 +1,34 @@ +get('media_source_test_entity_constraints', []); + } + + /** + * {@inheritdoc} + */ + public function getSourceFieldConstraints() { + return \Drupal::state()->get('media_source_test_field_constraints', []); + } + +} diff --git a/core/modules/media/tests/src/Kernel/MediaKernelTestBase.php b/core/modules/media/tests/src/Kernel/MediaKernelTestBase.php index e13150e0ff..ffc4224b56 100644 --- a/core/modules/media/tests/src/Kernel/MediaKernelTestBase.php +++ b/core/modules/media/tests/src/Kernel/MediaKernelTestBase.php @@ -33,6 +33,13 @@ protected $testMediaType; /** + * The test media type with constraints. + * + * @var \Drupal\media\MediaTypeInterface + */ + protected $testConstraintsMediaType; + + /** * {@inheritdoc} */ protected function setUp() { @@ -53,14 +60,28 @@ protected function setUp() { 'new_revision' => FALSE, ]); $this->testMediaType->save(); - $source_field = $this->testMediaType->getSource()->createSourceField($this->testMediaType); $source_field->getFieldStorageDefinition()->save(); $source_field->save(); - $this->testMediaType->set('source_configuration', [ 'source_field' => $source_field->getName(), ])->save(); + + // Create a test media type with constraints. + $id = strtolower($this->randomMachineName()); + $this->testConstraintsMediaType = MediaType::create([ + 'id' => $id, + 'label' => $id, + 'source' => 'test_constraints', + 'new_revision' => FALSE, + ]); + $this->testConstraintsMediaType->save(); + $source_field = $this->testConstraintsMediaType->getSource()->createSourceField($this->testConstraintsMediaType); + $source_field->getFieldStorageDefinition()->save(); + $source_field->save(); + $this->testConstraintsMediaType->set('source_configuration', [ + 'source_field' => $source_field->getName(), + ])->save(); } } diff --git a/core/modules/media/tests/src/Kernel/MediaSourceTest.php b/core/modules/media/tests/src/Kernel/MediaSourceTest.php index 14349c69aa..f6b4a69514 100644 --- a/core/modules/media/tests/src/Kernel/MediaSourceTest.php +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php @@ -2,7 +2,9 @@ namespace Drupal\Tests\media\Kernel; +use Drupal\Core\Entity\EntityStorageException; use Drupal\media\Entity\Media; +use Drupal\media\Entity\MediaType; /** * Tests media source plugins related logic. @@ -228,4 +230,154 @@ public function testThumbnail() { $this->assertEquals('This will be title.', $media->thumbnail->title, 'Title text was set on the thumbnail.'); $this->assertEquals('This will be alt.', $media->thumbnail->alt, 'Alt text was set on the thumbnail.'); } + + /** + * Tests the media entity constraints functionality. + */ + public function testConstraints() { + // Test entity constraints. + \Drupal::state()->set('media_source_test_entity_constraints', [ + 'MediaTestConstraint' => [], + ]); + + // Create media that uses source with constraints and make sure it can't + // be saved without validating them. + /** @var \Drupal\media\MediaInterface $media */ + $media = Media::create([ + 'bundle' => $this->testConstraintsMediaType->id(), + 'name' => 'I do not like Drupal', + 'field_media_test_constraints' => 'Not checked', + ]); + try { + $media->save(); + $this->fail('Save was allowed without validation.'); + } + catch (EntityStorageException $exception) { + $this->assertTrue(TRUE, 'Validation was enforced before save.'); + } + + // Validate the entity and make sure violation is reported. + /** @var \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations */ + $violations = $media->validate(); + $this->assertEquals(1, $violations->count(), 'Expected number of validations found.'); + $this->assertEquals('Inappropriate text.', $violations->get(0)->getMessage(), 'Correct constraint validation message found.'); + + // Fix the violation and make sure it is not reported anymore. + $media->set('name', 'I love Drupal!'); + $violations = $media->validate(); + $this->assertEquals(0, $violations->count(), 'Expected number of validations found.'); + + // Save and make sure it succeeded. + $this->assertEmpty($media->id(), 'Entity ID was not found.'); + $media->save(); + $this->assertNotEmpty($media->id(), 'Entity ID was found.'); + + // Test source field constraints. + \Drupal::state()->set('media_source_test_field_constraints', [ + 'MediaTestConstraint' => [], + ]); + \Drupal::state()->set('media_source_test_entity_constraints', []); + + // Create media that uses source with constraints and make sure it can't + // be saved without validating them. + /** @var \Drupal\media\MediaInterface $media */ + $media = Media::create([ + 'bundle' => $this->testConstraintsMediaType->id(), + 'name' => 'Not checked', + 'field_media_test_constraints' => 'I do not like Drupal', + ]); + try { + $media->save(); + $this->fail('Save was allowed without validation.'); + } + catch (EntityStorageException $exception) { + $this->assertTrue(TRUE, 'Validation was enforced before save.'); + } + + // Validate the entity and make sure violation is reported. + /** @var \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations */ + $violations = $media->validate(); + $this->assertEquals(1, $violations->count(), 'Expected number of validations found.'); + $this->assertEquals('Inappropriate text.', $violations->get(0)->getMessage(), 'Correct constraint validation message found.'); + + // Fix the violation and make sure it is not reported anymore. + $media->set('field_media_test_constraints', 'I love Drupal!'); + $violations = $media->validate(); + $this->assertEquals(0, $violations->count(), 'Expected number of validations found.'); + + // Save and make sure it succeeded. + $this->assertEmpty($media->id(), 'Entity ID was not found.'); + $media->save(); + $this->assertNotEmpty($media->id(), 'Entity ID was found.'); + } + + /** + * Tests logic related to the automated source field creation. + */ + public function testSourceFieldCreation() { + /** @var \Drupal\media\MediaTypeInterface $type */ + $type = MediaType::create([ + 'id' => 'test_type', + 'label' => 'Test type', + 'source' => 'test', + ]); + + /** @var \Drupal\field\Entity\FieldConfig $field */ + $field = $type->getSource()->createSourceField($type); + /** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */ + $field_storage = $field->getFieldStorageDefinition(); + + // Test field storage. + $this->assertTrue($field_storage->isNew(), 'Field storage is not saved automatically.'); + $this->assertTrue($field_storage->isLocked(), 'Field storage is locked.'); + $this->assertEquals('string', $field_storage->getType(), 'Field is of correct type.'); + $this->assertEquals('field_media_test_1', $field_storage->getName(), 'Correct field name is used.'); + $this->assertEquals('media', $field_storage->getTargetEntityTypeId(), 'Field is targeting media entities.'); + + // Test field. + $this->assertTrue($field->isNew(), 'Field is not saved automatically.'); + $this->assertEquals('field_media_test_1', $field->getName(), 'Correct field name is used.'); + $this->assertEquals('string', $field->getType(), 'Field is of correct type.'); + $this->assertTrue($field->isRequired(), 'Field is required.'); + $this->assertEquals('Test source', $field->label(), 'Correct label is used.'); + $this->assertEquals('test_type', $field->getTargetBundle(), 'Field is targeting correct bundle.'); + + // Fields should be automatically saved only when using a form. + // Drupal\Tests\media\FunctionalJavascript\MediaTypeCreationTest is testing + // form part of the functionality. + $type->save(); + $storage = $this->container->get('entity_type.manager') + ->getStorage('field_storage_config') + ->load('media.field_media_test_1'); + $this->assertNull($storage, 'Field storage was not saved.'); + $field = $this->container->get('entity_type.manager') + ->getStorage('field_config') + ->load('media.test_type.field_media_test_1'); + $this->assertNull($field, 'Field storage was not saved.'); + + // Test the plugin with a different default source field type. + $type = MediaType::create([ + 'id' => 'test_constraints_type', + 'label' => 'Test type with constraints', + 'source' => 'test_constraints', + ]); + $field = $type->getSource()->createSourceField($type); + $field_storage = $field->getFieldStorageDefinition(); + + // Test field storage. + $this->assertTrue($field_storage->isNew(), 'Field storage is not saved automatically.'); + $this->assertTrue($field_storage->isLocked(), 'Field storage is locked.'); + $this->assertEquals('string_long', $field_storage->getType(), 'Field is of correct type.'); + $this->assertEquals('field_media_test_constraints_1', $field_storage->getName(), 'Correct field name is used.'); + $this->assertEquals('media', $field_storage->getTargetEntityTypeId(), 'Field is targeting media entities.'); + + // Test field. + $this->assertTrue($field->isNew(), 'Field is not saved automatically.'); + $this->assertEquals('field_media_test_constraints_1', $field->getName(), 'Correct field name is used.'); + $this->assertEquals('string_long', $field->getType(), 'Field is of correct type.'); + $this->assertTrue($field->isRequired(), 'Field is required.'); + $this->assertEquals('Test source with constraints', $field->label(), 'Correct label is used.'); + $this->assertEquals('test_constraints_type', $field->getTargetBundle(), 'Field is targeting correct bundle.'); + } + }