diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 515db0d2f6d3aa249dcea2a9a75bf6eb7d07fb70..d5c9c680f90ff0cc6da3ec18588b9c8c072e73c3 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -79,6 +79,40 @@ protected function doCreate(array $values) { return $entity; } + /** + * {@inheritdoc} + */ + public function createWithSampleValues($bundle = FALSE, array $values = []) { + // ID and revision should never have sample values generated for them. + $forbidden_keys = [ + $this->entityType->getKey('id'), + ]; + if ($revision_key = $this->entityType->getKey('revision')) { + $forbidden_keys[] = $revision_key; + } + if ($bundle_key = $this->entityType->getKey('bundle')) { + if (!$bundle) { + throw new EntityStorageException("No entity bundle was specified"); + } + if (!array_key_exists($bundle, $this->entityManager->getBundleInfo($this->entityTypeId))) { + throw new EntityStorageException(sprintf("Missing entity bundle. The \"%s\" bundle does not exist", $bundle)); + } + $values[$bundle_key] = $bundle; + // Bundle is already set + $forbidden_keys[] = $bundle_key; + } + // Forbid sample generation on any keys whose values were submitted. + $forbidden_keys = array_merge($forbidden_keys, array_keys($values)); + /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ + $entity = $this->create($values); + foreach ($entity as $field_name => $value) { + if (!in_array($field_name, $forbidden_keys, TRUE)) { + $entity->get($field_name)->generateSampleItems(); + } + } + return $entity; + } + /** * Initializes field values. * diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php index 47e058d8e1b557fd185c1a847e46f3fdfa6146a2..eb979c7fd6a508ba3782e0b8cdb79a210d017ebf 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php @@ -23,4 +23,21 @@ interface ContentEntityStorageInterface extends EntityStorageInterface { */ public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []); + + /** + * Creates an entity with sample field values. + * + * @param string|bool $bundle + * (optional) The entity bundle. + * @param array $values + * (optional) Any default values to use during generation. + * + * @return \Drupal\Core\Entity\FieldableEntityInterface + * A fieldable content entity. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * Thrown if the bundle does not exist or was needed but not specified. + */ + public function createWithSampleValues($bundle = FALSE, array $values = []); + } diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php index c42e7c0de5430c015f57037fc1046001a11623ac..b3fc12d6b61b8653f043284a782f3411af455600 100644 --- a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php @@ -18,4 +18,9 @@ public function createTranslation(ContentEntityInterface $entity, $langcode, arr // https://www.drupal.org/node/2618436. } + /** + * {@inheritdoc} + */ + public function createWithSampleValues($bundle = FALSE, array $values = []) {} + } diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index a1a1ebdb9ebb12c02e3789471d6de1becb1c79ac..5d13a5e5cc61a8c6fe690bbe5c6283936a643137 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -259,7 +259,7 @@ public function view($display_options = []) { */ public function generateSampleItems($count = 1) { $field_definition = $this->getFieldDefinition(); - $field_type_class = \Drupal::service('plugin.manager.field.field_type')->getPluginClass($field_definition->getType()); + $field_type_class = $field_definition->getItemDefinition()->getClass(); for ($delta = 0; $delta < $count; $delta++) { $values[$delta] = $field_type_class::generateSampleValue($field_definition); } diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 7529532581e9ee847dee6789f214c800dba3b5bc..f254ccff9377326ed21b6036073b3ce5dfdbc0af 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -9,6 +9,7 @@ use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Language\LanguageInterface; use Drupal\user\RoleInterface; +use Drupal\user\TimeZoneItem; use Drupal\user\UserInterface; /** @@ -498,6 +499,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->addPropertyConstraints('value', [ 'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedTimezones'], ]); + $fields['timezone']->getItemDefinition()->setClass(TimeZoneItem::class); $fields['status'] = BaseFieldDefinition::create('boolean') ->setLabel(t('User status')) diff --git a/core/modules/user/src/TimeZoneItem.php b/core/modules/user/src/TimeZoneItem.php new file mode 100644 index 0000000000000000000000000000000000000000..f109691575bf3b66c08c5d98915659debdb23401 --- /dev/null +++ b/core/modules/user/src/TimeZoneItem.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\user; + +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\Plugin\Field\FieldType\StringItem; +use Drupal\user\Entity\User; + +/** + * Defines a custom field item class for the 'timezone' user entity field. + */ +class TimeZoneItem extends StringItem { + + /** + * {@inheritdoc} + */ + public static function generateSampleValue(FieldDefinitionInterface $field_definition) { + $timezones = User::getAllowedTimezones(); + // We need to vary the selected timezones since we're generating a sample. + $key = rand(0, count($timezones) - 1); + return $timezones[$key]; + } + +} diff --git a/core/modules/user/src/UserNameItem.php b/core/modules/user/src/UserNameItem.php index 6a53eb5d0629c38588ce33a5959e83a412d26e94..e6a0f2d4209db3440453b818c0792194fd705167 100644 --- a/core/modules/user/src/UserNameItem.php +++ b/core/modules/user/src/UserNameItem.php @@ -2,6 +2,7 @@ namespace Drupal\user; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\Plugin\Field\FieldType\StringItem; /** @@ -23,4 +24,14 @@ public function isEmpty() { return $value === NULL || $value === ''; } + /** + * {@inheritdoc} + */ + public static function generateSampleValue(FieldDefinitionInterface $field_definition) { + $values = parent::generateSampleValue($field_definition); + // User names larger than 60 characters won't pass validation. + $values['value'] = substr($values['value'], 0, UserInterface::USERNAME_MAX_LENGTH); + return $values; + } + } diff --git a/core/modules/user/tests/src/Kernel/UserEntityTest.php b/core/modules/user/tests/src/Kernel/UserEntityTest.php index 1c501f049e705ba24eb25e985e1368c84cef4d5a..73fe869ec3d41bdfeb38858db1282b523507780e 100644 --- a/core/modules/user/tests/src/Kernel/UserEntityTest.php +++ b/core/modules/user/tests/src/Kernel/UserEntityTest.php @@ -21,6 +21,14 @@ class UserEntityTest extends KernelTestBase { */ public static $modules = ['system', 'user', 'field']; + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->installEntitySchema('user'); + } + /** * Tests some of the methods. * @@ -65,4 +73,22 @@ public function testUserMethods() { $this->assertEqual([RoleInterface::AUTHENTICATED_ID, 'test_role_two'], $user->getRoles()); } + /** + * Tests that all user fields validate properly. + * + * @see \Drupal\Core\Field\FieldItemListInterface::generateSampleItems + * @see \Drupal\Core\Field\FieldItemInterface::generateSampleValue() + * @see \Drupal\Core\Entity\FieldableEntityInterface::validate() + */ + public function testUserValidation() { + $user = User::create([]); + foreach ($user as $field_name => $field) { + if (!in_array($field_name, ['uid'])) { + $user->$field_name->generateSampleItems(); + } + } + $violations = $user->validate(); + $this->assertFalse((bool) $violations->count()); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/CreateSampleEntityTest.php b/core/tests/Drupal/KernelTests/Core/Entity/CreateSampleEntityTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b5bcd487f2fdb340add1a564248cf0c076237797 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/CreateSampleEntityTest.php @@ -0,0 +1,89 @@ +<?php + +namespace Drupal\KernelTests\Core\Entity; + +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\KernelTests\KernelTestBase; +use Drupal\node\Entity\NodeType; +use Drupal\taxonomy\Entity\Vocabulary; + +/** + * Tests the ContentEntityStorageBase::createWithSampleValues method. + * + * @coversDefaultClass \Drupal\Core\Entity\ContentEntityStorageBase + * @group Entity + */ +class CreateSampleEntityTest extends KernelTestBase { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * {@inheritdoc} + */ + public static $modules = ['system', 'field', 'filter', 'text', 'file', 'user', 'node', 'comment', 'taxonomy']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setup(); + + $this->installEntitySchema('file'); + $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->installEntitySchema('node_type'); + $this->installEntitySchema('file'); + $this->installEntitySchema('comment'); + $this->installEntitySchema('comment_type'); + $this->installEntitySchema('taxonomy_vocabulary'); + $this->installEntitySchema('taxonomy_term'); + $this->entityTypeManager = $this->container->get('entity_type.manager'); + NodeType::create(['type' => 'article', 'name' => 'Article'])->save(); + NodeType::create(['type' => 'page', 'name' => 'Page'])->save(); + Vocabulary::create(['name' => 'Tags', 'vid' => 'tags'])->save(); + } + + /** + * Tests sample value content entity creation of all types. + * + * @covers ::createWithSampleValues + */ + public function testSampleValueContentEntity() { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $definition) { + if ($definition->entityClassImplements(FieldableEntityInterface::class)) { + $label = $definition->getKey('label'); + $values = []; + if ($label) { + $title = $this->randomString(); + $values[$label] = $title; + } + // Create sample entities with bundles. + if ($bundle_type = $definition->getBundleEntityType()) { + foreach ($this->entityTypeManager->getStorage($bundle_type)->loadMultiple() as $bundle) { + $entity = $this->entityTypeManager->getStorage($entity_type_id)->createWithSampleValues($bundle->id(), $values); + $violations = $entity->validate(); + $this->assertCount(0, $violations); + if ($label) { + $this->assertEquals($title, $entity->label()); + } + } + } + // Create sample entities without bundles. + else { + $entity = $this->entityTypeManager->getStorage($entity_type_id)->createWithSampleValues(FALSE, $values); + $violations = $entity->validate(); + $this->assertCount(0, $violations); + if ($label) { + $this->assertEquals($title, $entity->label()); + } + } + } + } + } + +}