From dff09a9d5c562d76725033a5ef49ff3abf669d41 Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Thu, 21 Sep 2017 07:36:14 +1000 Subject: [PATCH] Issue #2905527 by EclipseGc, amateescu, tim.plunkett, larowlan: Introduce a sample value entity method to ease plugin filtering via context --- .../Core/Entity/ContentEntityStorageBase.php | 34 +++++++ .../Entity/ContentEntityStorageInterface.php | 17 ++++ .../KeyValueContentEntityStorage.php | 5 ++ core/lib/Drupal/Core/Field/FieldItemList.php | 2 +- core/modules/user/src/Entity/User.php | 2 + core/modules/user/src/TimeZoneItem.php | 24 +++++ core/modules/user/src/UserNameItem.php | 11 +++ .../user/tests/src/Kernel/UserEntityTest.php | 26 ++++++ .../Core/Entity/CreateSampleEntityTest.php | 89 +++++++++++++++++++ 9 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 core/modules/user/src/TimeZoneItem.php create mode 100644 core/tests/Drupal/KernelTests/Core/Entity/CreateSampleEntityTest.php diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 515db0d2f6d3..d5c9c680f90f 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 47e058d8e1b5..eb979c7fd6a5 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 c42e7c0de543..b3fc12d6b61b 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 a1a1ebdb9ebb..5d13a5e5cc61 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 7529532581e9..f254ccff9377 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 000000000000..f109691575bf --- /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 6a53eb5d0629..e6a0f2d4209d 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 1c501f049e70..73fe869ec3d4 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 000000000000..b5bcd487f2fd --- /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()); + } + } + } + } + } + +} -- GitLab